hgext/shelve.py
branchstable
changeset 26813 b66e3ca0b90c
parent 26799 ae03d4190321
child 26942 d55d22840592
equal deleted inserted replaced
26535:d3712209921d 26813:b66e3ca0b90c
    25 import itertools
    25 import itertools
    26 from mercurial.i18n import _
    26 from mercurial.i18n import _
    27 from mercurial.node import nullid, nullrev, bin, hex
    27 from mercurial.node import nullid, nullrev, bin, hex
    28 from mercurial import changegroup, cmdutil, scmutil, phases, commands
    28 from mercurial import changegroup, cmdutil, scmutil, phases, commands
    29 from mercurial import error, hg, mdiff, merge, patch, repair, util
    29 from mercurial import error, hg, mdiff, merge, patch, repair, util
    30 from mercurial import templatefilters, exchange, bundlerepo
    30 from mercurial import templatefilters, exchange, bundlerepo, bundle2
    31 from mercurial import lock as lockmod
    31 from mercurial import lock as lockmod
    32 from hgext import rebase
    32 from hgext import rebase
    33 import errno
    33 import errno
    34 
    34 
    35 cmdtable = {}
    35 cmdtable = {}
    88         try:
    88         try:
    89             return self.vfs(self.fname, mode)
    89             return self.vfs(self.fname, mode)
    90         except IOError as err:
    90         except IOError as err:
    91             if err.errno != errno.ENOENT:
    91             if err.errno != errno.ENOENT:
    92                 raise
    92                 raise
    93             raise util.Abort(_("shelved change '%s' not found") % self.name)
    93             raise error.Abort(_("shelved change '%s' not found") % self.name)
    94 
    94 
    95     def applybundle(self):
    95     def applybundle(self):
    96         fp = self.opener()
    96         fp = self.opener()
    97         try:
    97         try:
    98             gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
    98             gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
    99             changegroup.addchangegroup(self.repo, gen, 'unshelve',
    99             if not isinstance(gen, bundle2.unbundle20):
   100                                        'bundle:' + self.vfs.join(self.fname),
   100                 gen.apply(self.repo, 'unshelve',
   101                                        targetphase=phases.secret)
   101                           'bundle:' + self.vfs.join(self.fname),
       
   102                           targetphase=phases.secret)
       
   103             if isinstance(gen, bundle2.unbundle20):
       
   104                 bundle2.applybundle(self.repo, gen,
       
   105                                     self.repo.currenttransaction(),
       
   106                                     source='unshelve',
       
   107                                     url='bundle:' + self.vfs.join(self.fname))
   102         finally:
   108         finally:
   103             fp.close()
   109             fp.close()
   104 
   110 
   105     def bundlerepo(self):
   111     def bundlerepo(self):
   106         return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
   112         return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
   107                                            self.vfs.join(self.fname))
   113                                            self.vfs.join(self.fname))
   108     def writebundle(self, cg):
   114     def writebundle(self, bases, node):
   109         changegroup.writebundle(self.ui, cg, self.fname, 'HG10UN', self.vfs)
   115         btype = 'HG10BZ'
       
   116         cgversion = '01'
       
   117         compression = None
       
   118         if 'generaldelta' in self.repo.requirements:
       
   119             btype = 'HG20'
       
   120             cgversion = '02'
       
   121             compression = 'BZ'
       
   122 
       
   123         cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
       
   124                                            version=cgversion)
       
   125         changegroup.writebundle(self.ui, cg, self.fname, btype, self.vfs,
       
   126                                 compression=compression)
   110 
   127 
   111 class shelvedstate(object):
   128 class shelvedstate(object):
   112     """Handle persistence during unshelving operations.
   129     """Handle persistence during unshelving operations.
   113 
   130 
   114     Handles saving and restoring a shelved state. Ensures that different
   131     Handles saving and restoring a shelved state. Ensures that different
   122         fp = repo.vfs(cls._filename)
   139         fp = repo.vfs(cls._filename)
   123         try:
   140         try:
   124             version = int(fp.readline().strip())
   141             version = int(fp.readline().strip())
   125 
   142 
   126             if version != cls._version:
   143             if version != cls._version:
   127                 raise util.Abort(_('this version of shelve is incompatible '
   144                 raise error.Abort(_('this version of shelve is incompatible '
   128                                    'with the version used in this repo'))
   145                                    'with the version used in this repo'))
   129             name = fp.readline().strip()
   146             name = fp.readline().strip()
   130             wctx = fp.readline().strip()
   147             wctx = fp.readline().strip()
   131             pendingctx = fp.readline().strip()
   148             pendingctx = fp.readline().strip()
   132             parents = [bin(h) for h in fp.readline().split()]
   149             parents = [bin(h) for h in fp.readline().split()]
   177                 vfs.unlink(base + '.' + ext)
   194                 vfs.unlink(base + '.' + ext)
   178             except OSError as err:
   195             except OSError as err:
   179                 if err.errno != errno.ENOENT:
   196                 if err.errno != errno.ENOENT:
   180                     raise
   197                     raise
   181 
   198 
       
   199 def _aborttransaction(repo):
       
   200     '''Abort current transaction for shelve/unshelve, but keep dirstate
       
   201     '''
       
   202     backupname = 'dirstate.shelve'
       
   203     dirstatebackup = None
       
   204     try:
       
   205         # create backup of (un)shelved dirstate, because aborting transaction
       
   206         # should restore dirstate to one at the beginning of the
       
   207         # transaction, which doesn't include the result of (un)shelving
       
   208         fp = repo.vfs.open(backupname, "w")
       
   209         dirstatebackup = backupname
       
   210         # clearing _dirty/_dirtypl of dirstate by _writedirstate below
       
   211         # is unintentional. but it doesn't cause problem in this case,
       
   212         # because no code path refers them until transaction is aborted.
       
   213         repo.dirstate._writedirstate(fp) # write in-memory changes forcibly
       
   214 
       
   215         tr = repo.currenttransaction()
       
   216         tr.abort()
       
   217 
       
   218         # restore to backuped dirstate
       
   219         repo.vfs.rename(dirstatebackup, 'dirstate')
       
   220         dirstatebackup = None
       
   221     finally:
       
   222         if dirstatebackup:
       
   223             repo.vfs.unlink(dirstatebackup)
       
   224 
   182 def createcmd(ui, repo, pats, opts):
   225 def createcmd(ui, repo, pats, opts):
   183     """subcommand that creates a new shelve"""
   226     """subcommand that creates a new shelve"""
   184 
   227 
   185     def publicancestors(ctx):
   228     def mutableancestors(ctx):
   186         """Compute the public ancestors of a commit.
   229         """return all mutable ancestors for ctx (included)
   187 
   230 
   188         Much faster than the revset ancestors(ctx) & draft()"""
   231         Much faster than the revset ancestors(ctx) & draft()"""
   189         seen = set([nullrev])
   232         seen = set([nullrev])
   190         visit = collections.deque()
   233         visit = collections.deque()
   191         visit.append(ctx)
   234         visit.append(ctx)
   200                         visit.append(parent)
   243                         visit.append(parent)
   201 
   244 
   202     wctx = repo[None]
   245     wctx = repo[None]
   203     parents = wctx.parents()
   246     parents = wctx.parents()
   204     if len(parents) > 1:
   247     if len(parents) > 1:
   205         raise util.Abort(_('cannot shelve while merging'))
   248         raise error.Abort(_('cannot shelve while merging'))
   206     parent = parents[0]
   249     parent = parents[0]
   207 
   250 
   208     # we never need the user, so we use a generic user for all shelve operations
   251     # we never need the user, so we use a generic user for all shelve operations
   209     user = 'shelve@localhost'
   252     user = 'shelve@localhost'
   210     label = repo._activebookmark or parent.branch() or 'default'
   253     label = repo._activebookmark or parent.branch() or 'default'
   240     if not opts['message']:
   283     if not opts['message']:
   241         opts['message'] = desc
   284         opts['message'] = desc
   242 
   285 
   243     name = opts['name']
   286     name = opts['name']
   244 
   287 
   245     wlock = lock = tr = bms = None
   288     wlock = lock = tr = None
   246     try:
   289     try:
   247         wlock = repo.wlock()
   290         wlock = repo.wlock()
   248         lock = repo.lock()
   291         lock = repo.lock()
   249 
   292 
   250         bms = repo._bookmarks.copy()
       
   251         # use an uncommitted transaction to generate the bundle to avoid
   293         # use an uncommitted transaction to generate the bundle to avoid
   252         # pull races. ensure we don't print the abort message to stderr.
   294         # pull races. ensure we don't print the abort message to stderr.
   253         tr = repo.transaction('commit', report=lambda x: None)
   295         tr = repo.transaction('commit', report=lambda x: None)
   254 
   296 
   255         if name:
   297         if name:
   256             if shelvedfile(repo, name, 'hg').exists():
   298             if shelvedfile(repo, name, 'hg').exists():
   257                 raise util.Abort(_("a shelved change named '%s' already exists")
   299                 raise error.Abort(_("a shelved change named '%s' already exists"
   258                                  % name)
   300                                    ) % name)
   259         else:
   301         else:
   260             for n in gennames():
   302             for n in gennames():
   261                 if not shelvedfile(repo, n, 'hg').exists():
   303                 if not shelvedfile(repo, n, 'hg').exists():
   262                     name = n
   304                     name = n
   263                     break
   305                     break
   264             else:
   306             else:
   265                 raise util.Abort(_("too many shelved changes named '%s'") %
   307                 raise error.Abort(_("too many shelved changes named '%s'") %
   266                                  label)
   308                                  label)
   267 
   309 
   268         # ensure we are not creating a subdirectory or a hidden file
   310         # ensure we are not creating a subdirectory or a hidden file
   269         if '/' in name or '\\' in name:
   311         if '/' in name or '\\' in name:
   270             raise util.Abort(_('shelved change names may not contain slashes'))
   312             raise error.Abort(_('shelved change names may not contain slashes'))
   271         if name.startswith('.'):
   313         if name.startswith('.'):
   272             raise util.Abort(_("shelved change names may not start with '.'"))
   314             raise error.Abort(_("shelved change names may not start with '.'"))
   273         interactive = opts.get('interactive', False)
   315         interactive = opts.get('interactive', False)
   274 
   316 
   275         def interactivecommitfunc(ui, repo, *pats, **opts):
   317         def interactivecommitfunc(ui, repo, *pats, **opts):
   276             match = scmutil.match(repo['.'], pats, {})
   318             match = scmutil.match(repo['.'], pats, {})
   277             message = opts['message']
   319             message = opts['message']
   288                             "'hg status')\n") % len(stat.deleted))
   330                             "'hg status')\n") % len(stat.deleted))
   289             else:
   331             else:
   290                 ui.status(_("nothing changed\n"))
   332                 ui.status(_("nothing changed\n"))
   291             return 1
   333             return 1
   292 
   334 
   293         bases = list(publicancestors(repo[node]))
   335         bases = list(mutableancestors(repo[node]))
   294         cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve')
   336         shelvedfile(repo, name, 'hg').writebundle(bases, node)
   295         shelvedfile(repo, name, 'hg').writebundle(cg)
       
   296         cmdutil.export(repo, [node],
   337         cmdutil.export(repo, [node],
   297                        fp=shelvedfile(repo, name, 'patch').opener('wb'),
   338                        fp=shelvedfile(repo, name, 'patch').opener('wb'),
   298                        opts=mdiff.diffopts(git=True))
   339                        opts=mdiff.diffopts(git=True))
   299 
   340 
   300 
   341 
   301         if ui.formatted():
   342         if ui.formatted():
   302             desc = util.ellipsis(desc, ui.termwidth())
   343             desc = util.ellipsis(desc, ui.termwidth())
   303         ui.status(_('shelved as %s\n') % name)
   344         ui.status(_('shelved as %s\n') % name)
   304         hg.update(repo, parent.node())
   345         hg.update(repo, parent.node())
       
   346 
       
   347         _aborttransaction(repo)
   305     finally:
   348     finally:
   306         if bms:
   349         lockmod.release(tr, lock, wlock)
   307             # restore old bookmarks
       
   308             repo._bookmarks.update(bms)
       
   309             repo._bookmarks.write()
       
   310         if tr:
       
   311             tr.abort()
       
   312         lockmod.release(lock, wlock)
       
   313 
   350 
   314 def cleanupcmd(ui, repo):
   351 def cleanupcmd(ui, repo):
   315     """subcommand that deletes all shelves"""
   352     """subcommand that deletes all shelves"""
   316 
   353 
   317     wlock = None
   354     wlock = None
   326         lockmod.release(wlock)
   363         lockmod.release(wlock)
   327 
   364 
   328 def deletecmd(ui, repo, pats):
   365 def deletecmd(ui, repo, pats):
   329     """subcommand that deletes a specific shelve"""
   366     """subcommand that deletes a specific shelve"""
   330     if not pats:
   367     if not pats:
   331         raise util.Abort(_('no shelved changes specified!'))
   368         raise error.Abort(_('no shelved changes specified!'))
   332     wlock = repo.wlock()
   369     wlock = repo.wlock()
   333     try:
   370     try:
   334         for name in pats:
   371         for name in pats:
   335             for suffix in 'hg patch'.split():
   372             for suffix in 'hg patch'.split():
   336                 shelvedfile(repo, name, suffix).movetobackup()
   373                 shelvedfile(repo, name, suffix).movetobackup()
   337         cleanupoldbackups(repo)
   374         cleanupoldbackups(repo)
   338     except OSError as err:
   375     except OSError as err:
   339         if err.errno != errno.ENOENT:
   376         if err.errno != errno.ENOENT:
   340             raise
   377             raise
   341         raise util.Abort(_("shelved change '%s' not found") % name)
   378         raise error.Abort(_("shelved change '%s' not found") % name)
   342     finally:
   379     finally:
   343         lockmod.release(wlock)
   380         lockmod.release(wlock)
   344 
   381 
   345 def listshelves(repo):
   382 def listshelves(repo):
   346     """return all shelves in repo as list of (time, filename)"""
   383     """return all shelves in repo as list of (time, filename)"""
   408             fp.close()
   445             fp.close()
   409 
   446 
   410 def singlepatchcmds(ui, repo, pats, opts, subcommand):
   447 def singlepatchcmds(ui, repo, pats, opts, subcommand):
   411     """subcommand that displays a single shelf"""
   448     """subcommand that displays a single shelf"""
   412     if len(pats) != 1:
   449     if len(pats) != 1:
   413         raise util.Abort(_("--%s expects a single shelf") % subcommand)
   450         raise error.Abort(_("--%s expects a single shelf") % subcommand)
   414     shelfname = pats[0]
   451     shelfname = pats[0]
   415 
   452 
   416     if not shelvedfile(repo, shelfname, 'patch').exists():
   453     if not shelvedfile(repo, shelfname, 'patch').exists():
   417         raise util.Abort(_("cannot find shelf %s") % shelfname)
   454         raise error.Abort(_("cannot find shelf %s") % shelfname)
   418 
   455 
   419     listcmd(ui, repo, pats, opts)
   456     listcmd(ui, repo, pats, opts)
   420 
   457 
   421 def checkparents(repo, state):
   458 def checkparents(repo, state):
   422     """check parent while resuming an unshelve"""
   459     """check parent while resuming an unshelve"""
   423     if state.parents != repo.dirstate.parents():
   460     if state.parents != repo.dirstate.parents():
   424         raise util.Abort(_('working directory parents do not match unshelve '
   461         raise error.Abort(_('working directory parents do not match unshelve '
   425                            'state'))
   462                            'state'))
   426 
   463 
   427 def pathtofiles(repo, files):
   464 def pathtofiles(repo, files):
   428     cwd = repo.getcwd()
   465     cwd = repo.getcwd()
   429     return [repo.pathto(f, cwd) for f in files]
   466     return [repo.pathto(f, cwd) for f in files]
   449         lock = repo.lock()
   486         lock = repo.lock()
   450 
   487 
   451         mergefiles(ui, repo, state.wctx, state.pendingctx)
   488         mergefiles(ui, repo, state.wctx, state.pendingctx)
   452 
   489 
   453         repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
   490         repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
       
   491     finally:
   454         shelvedstate.clear(repo)
   492         shelvedstate.clear(repo)
   455         ui.warn(_("unshelve of '%s' aborted\n") % state.name)
   493         ui.warn(_("unshelve of '%s' aborted\n") % state.name)
   456     finally:
       
   457         lockmod.release(lock, wlock)
   494         lockmod.release(lock, wlock)
   458 
   495 
   459 def mergefiles(ui, repo, wctx, shelvectx):
   496 def mergefiles(ui, repo, wctx, shelvectx):
   460     """updates to wctx and merges the changes from shelvectx into the
   497     """updates to wctx and merges the changes from shelvectx into the
   461     dirstate."""
   498     dirstate."""
   494     lock = None
   531     lock = None
   495     try:
   532     try:
   496         checkparents(repo, state)
   533         checkparents(repo, state)
   497         ms = merge.mergestate(repo)
   534         ms = merge.mergestate(repo)
   498         if [f for f in ms if ms[f] == 'u']:
   535         if [f for f in ms if ms[f] == 'u']:
   499             raise util.Abort(
   536             raise error.Abort(
   500                 _("unresolved conflicts, can't continue"),
   537                 _("unresolved conflicts, can't continue"),
   501                 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
   538                 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
   502 
   539 
   503         lock = repo.lock()
   540         lock = repo.lock()
   504 
   541 
   577     if not abortf and not continuef:
   614     if not abortf and not continuef:
   578         cmdutil.checkunfinished(repo)
   615         cmdutil.checkunfinished(repo)
   579 
   616 
   580     if abortf or continuef:
   617     if abortf or continuef:
   581         if abortf and continuef:
   618         if abortf and continuef:
   582             raise util.Abort(_('cannot use both abort and continue'))
   619             raise error.Abort(_('cannot use both abort and continue'))
   583         if shelved:
   620         if shelved:
   584             raise util.Abort(_('cannot combine abort/continue with '
   621             raise error.Abort(_('cannot combine abort/continue with '
   585                                'naming a shelved change'))
   622                                'naming a shelved change'))
   586 
   623 
   587         try:
   624         try:
   588             state = shelvedstate.load(repo)
   625             state = shelvedstate.load(repo)
   589         except IOError as err:
   626         except IOError as err:
   590             if err.errno != errno.ENOENT:
   627             if err.errno != errno.ENOENT:
   591                 raise
   628                 raise
   592             raise util.Abort(_('no unshelve operation underway'))
   629             raise error.Abort(_('no unshelve operation underway'))
   593 
   630 
   594         if abortf:
   631         if abortf:
   595             return unshelveabort(ui, repo, state, opts)
   632             return unshelveabort(ui, repo, state, opts)
   596         elif continuef:
   633         elif continuef:
   597             return unshelvecontinue(ui, repo, state, opts)
   634             return unshelvecontinue(ui, repo, state, opts)
   598     elif len(shelved) > 1:
   635     elif len(shelved) > 1:
   599         raise util.Abort(_('can only unshelve one change at a time'))
   636         raise error.Abort(_('can only unshelve one change at a time'))
   600     elif not shelved:
   637     elif not shelved:
   601         shelved = listshelves(repo)
   638         shelved = listshelves(repo)
   602         if not shelved:
   639         if not shelved:
   603             raise util.Abort(_('no shelved changes to apply!'))
   640             raise error.Abort(_('no shelved changes to apply!'))
   604         basename = util.split(shelved[0][1])[1]
   641         basename = util.split(shelved[0][1])[1]
   605         ui.status(_("unshelving change '%s'\n") % basename)
   642         ui.status(_("unshelving change '%s'\n") % basename)
   606     else:
   643     else:
   607         basename = shelved[0]
   644         basename = shelved[0]
   608 
   645 
   609     if not shelvedfile(repo, basename, 'patch').exists():
   646     if not shelvedfile(repo, basename, 'patch').exists():
   610         raise util.Abort(_("shelved change '%s' not found") % basename)
   647         raise error.Abort(_("shelved change '%s' not found") % basename)
   611 
   648 
   612     oldquiet = ui.quiet
   649     oldquiet = ui.quiet
   613     wlock = lock = tr = None
   650     wlock = lock = tr = None
   614     try:
   651     try:
   615         wlock = repo.wlock()
   652         wlock = repo.wlock()
   698         # hooks still fire and try to operate on the missing commits.
   735         # hooks still fire and try to operate on the missing commits.
   699         # Clean up manually to prevent this.
   736         # Clean up manually to prevent this.
   700         repo.unfiltered().changelog.strip(oldtiprev, tr)
   737         repo.unfiltered().changelog.strip(oldtiprev, tr)
   701 
   738 
   702         unshelvecleanup(ui, repo, basename, opts)
   739         unshelvecleanup(ui, repo, basename, opts)
       
   740 
       
   741         _aborttransaction(repo)
   703     finally:
   742     finally:
   704         ui.quiet = oldquiet
   743         ui.quiet = oldquiet
   705         if tr:
   744         if tr:
   706             tr.release()
   745             tr.release()
   707         lockmod.release(lock, wlock)
   746         lockmod.release(lock, wlock)
   773     ]
   812     ]
   774     def checkopt(opt):
   813     def checkopt(opt):
   775         if opts[opt]:
   814         if opts[opt]:
   776             for i, allowable in allowables:
   815             for i, allowable in allowables:
   777                 if opts[i] and opt not in allowable:
   816                 if opts[i] and opt not in allowable:
   778                     raise util.Abort(_("options '--%s' and '--%s' may not be "
   817                     raise error.Abort(_("options '--%s' and '--%s' may not be "
   779                                        "used together") % (opt, i))
   818                                        "used together") % (opt, i))
   780             return True
   819             return True
   781     if checkopt('cleanup'):
   820     if checkopt('cleanup'):
   782         if pats:
   821         if pats:
   783             raise util.Abort(_("cannot specify names when using '--cleanup'"))
   822             raise error.Abort(_("cannot specify names when using '--cleanup'"))
   784         return cleanupcmd(ui, repo)
   823         return cleanupcmd(ui, repo)
   785     elif checkopt('delete'):
   824     elif checkopt('delete'):
   786         return deletecmd(ui, repo, pats)
   825         return deletecmd(ui, repo, pats)
   787     elif checkopt('list'):
   826     elif checkopt('list'):
   788         return listcmd(ui, repo, pats, opts)
   827         return listcmd(ui, repo, pats, opts)