hgext/shelve.py
changeset 31664 1cbeefa59343
parent 31555 7c7d3ad7ca5c
child 31757 473f2fcc7629
equal deleted inserted replaced
31663:f33c9a5e81cd 31664:1cbeefa59343
    26 import errno
    26 import errno
    27 import itertools
    27 import itertools
    28 
    28 
    29 from mercurial.i18n import _
    29 from mercurial.i18n import _
    30 from mercurial import (
    30 from mercurial import (
       
    31     bookmarks,
    31     bundle2,
    32     bundle2,
    32     bundlerepo,
    33     bundlerepo,
    33     changegroup,
    34     changegroup,
    34     cmdutil,
    35     cmdutil,
    35     commands,
    36     commands,
   168     """
   169     """
   169     _version = 1
   170     _version = 1
   170     _filename = 'shelvedstate'
   171     _filename = 'shelvedstate'
   171     _keep = 'keep'
   172     _keep = 'keep'
   172     _nokeep = 'nokeep'
   173     _nokeep = 'nokeep'
       
   174     # colon is essential to differentiate from a real bookmark name
       
   175     _noactivebook = ':no-active-bookmark'
   173 
   176 
   174     @classmethod
   177     @classmethod
   175     def load(cls, repo):
   178     def load(cls, repo):
   176         fp = repo.vfs(cls._filename)
   179         fp = repo.vfs(cls._filename)
   177         try:
   180         try:
   185             pendingctx = nodemod.bin(fp.readline().strip())
   188             pendingctx = nodemod.bin(fp.readline().strip())
   186             parents = [nodemod.bin(h) for h in fp.readline().split()]
   189             parents = [nodemod.bin(h) for h in fp.readline().split()]
   187             nodestoprune = [nodemod.bin(h) for h in fp.readline().split()]
   190             nodestoprune = [nodemod.bin(h) for h in fp.readline().split()]
   188             branchtorestore = fp.readline().strip()
   191             branchtorestore = fp.readline().strip()
   189             keep = fp.readline().strip() == cls._keep
   192             keep = fp.readline().strip() == cls._keep
       
   193             activebook = fp.readline().strip()
   190         except (ValueError, TypeError) as err:
   194         except (ValueError, TypeError) as err:
   191             raise error.CorruptedState(str(err))
   195             raise error.CorruptedState(str(err))
   192         finally:
   196         finally:
   193             fp.close()
   197             fp.close()
   194 
   198 
   199             obj.pendingctx = repo[pendingctx]
   203             obj.pendingctx = repo[pendingctx]
   200             obj.parents = parents
   204             obj.parents = parents
   201             obj.nodestoprune = nodestoprune
   205             obj.nodestoprune = nodestoprune
   202             obj.branchtorestore = branchtorestore
   206             obj.branchtorestore = branchtorestore
   203             obj.keep = keep
   207             obj.keep = keep
       
   208             obj.activebookmark = ''
       
   209             if activebook != cls._noactivebook:
       
   210                 obj.activebookmark = activebook
   204         except error.RepoLookupError as err:
   211         except error.RepoLookupError as err:
   205             raise error.CorruptedState(str(err))
   212             raise error.CorruptedState(str(err))
   206 
   213 
   207         return obj
   214         return obj
   208 
   215 
   209     @classmethod
   216     @classmethod
   210     def save(cls, repo, name, originalwctx, pendingctx, nodestoprune,
   217     def save(cls, repo, name, originalwctx, pendingctx, nodestoprune,
   211              branchtorestore, keep=False):
   218              branchtorestore, keep=False, activebook=''):
   212         fp = repo.vfs(cls._filename, 'wb')
   219         fp = repo.vfs(cls._filename, 'wb')
   213         fp.write('%i\n' % cls._version)
   220         fp.write('%i\n' % cls._version)
   214         fp.write('%s\n' % name)
   221         fp.write('%s\n' % name)
   215         fp.write('%s\n' % nodemod.hex(originalwctx.node()))
   222         fp.write('%s\n' % nodemod.hex(originalwctx.node()))
   216         fp.write('%s\n' % nodemod.hex(pendingctx.node()))
   223         fp.write('%s\n' % nodemod.hex(pendingctx.node()))
   218                  ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]))
   225                  ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]))
   219         fp.write('%s\n' %
   226         fp.write('%s\n' %
   220                  ' '.join([nodemod.hex(n) for n in nodestoprune]))
   227                  ' '.join([nodemod.hex(n) for n in nodestoprune]))
   221         fp.write('%s\n' % branchtorestore)
   228         fp.write('%s\n' % branchtorestore)
   222         fp.write('%s\n' % (cls._keep if keep else cls._nokeep))
   229         fp.write('%s\n' % (cls._keep if keep else cls._nokeep))
       
   230         fp.write('%s\n' % (activebook or cls._noactivebook))
   223         fp.close()
   231         fp.close()
   224 
   232 
   225     @classmethod
   233     @classmethod
   226     def clear(cls, repo):
   234     def clear(cls, repo):
   227         repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
   235         repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
   242             continue
   250             continue
   243         base = f[:-(1 + len(patchextension))]
   251         base = f[:-(1 + len(patchextension))]
   244         for ext in shelvefileextensions:
   252         for ext in shelvefileextensions:
   245             vfs.tryunlink(base + '.' + ext)
   253             vfs.tryunlink(base + '.' + ext)
   246 
   254 
       
   255 def _backupactivebookmark(repo):
       
   256     activebookmark = repo._activebookmark
       
   257     if activebookmark:
       
   258         bookmarks.deactivate(repo)
       
   259     return activebookmark
       
   260 
       
   261 def _restoreactivebookmark(repo, mark):
       
   262     if mark:
       
   263         bookmarks.activate(repo, mark)
       
   264 
   247 def _aborttransaction(repo):
   265 def _aborttransaction(repo):
   248     '''Abort current transaction for shelve/unshelve, but keep dirstate
   266     '''Abort current transaction for shelve/unshelve, but keep dirstate
   249     '''
   267     '''
   250     tr = repo.currenttransaction()
   268     tr = repo.currenttransaction()
   251     repo.dirstate.savebackup(tr, suffix='.shelve')
   269     repo.dirstate.savebackup(tr, suffix='.shelve')
   375         desc = '(changes in empty repository)'
   393         desc = '(changes in empty repository)'
   376 
   394 
   377     if not opts.get('message'):
   395     if not opts.get('message'):
   378         opts['message'] = desc
   396         opts['message'] = desc
   379 
   397 
   380     lock = tr = None
   398     lock = tr = activebookmark = None
   381     try:
   399     try:
   382         lock = repo.lock()
   400         lock = repo.lock()
   383 
   401 
   384         # use an uncommitted transaction to generate the bundle to avoid
   402         # use an uncommitted transaction to generate the bundle to avoid
   385         # pull races. ensure we don't print the abort message to stderr.
   403         # pull races. ensure we don't print the abort message to stderr.
   388         interactive = opts.get('interactive', False)
   406         interactive = opts.get('interactive', False)
   389         includeunknown = (opts.get('unknown', False) and
   407         includeunknown = (opts.get('unknown', False) and
   390                           not opts.get('addremove', False))
   408                           not opts.get('addremove', False))
   391 
   409 
   392         name = getshelvename(repo, parent, opts)
   410         name = getshelvename(repo, parent, opts)
       
   411         activebookmark = _backupactivebookmark(repo)
   393         extra = {}
   412         extra = {}
   394         if includeunknown:
   413         if includeunknown:
   395             _includeunknownfiles(repo, pats, opts, extra)
   414             _includeunknownfiles(repo, pats, opts, extra)
   396 
   415 
   397         if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
   416         if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
   402         commitfunc = getcommitfunc(extra, interactive, editor=True)
   421         commitfunc = getcommitfunc(extra, interactive, editor=True)
   403         if not interactive:
   422         if not interactive:
   404             node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
   423             node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
   405         else:
   424         else:
   406             node = cmdutil.dorecord(ui, repo, commitfunc, None,
   425             node = cmdutil.dorecord(ui, repo, commitfunc, None,
   407                                     False, cmdutil.recordfilter, *pats, **opts)
   426                                     False, cmdutil.recordfilter, *pats,
       
   427                                     **opts)
   408         if not node:
   428         if not node:
   409             _nothingtoshelvemessaging(ui, repo, pats, opts)
   429             _nothingtoshelvemessaging(ui, repo, pats, opts)
   410             return 1
   430             return 1
   411 
   431 
   412         _shelvecreatedcommit(repo, node, name)
   432         _shelvecreatedcommit(repo, node, name)
   418         if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
   438         if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
   419             repo.dirstate.setbranch(origbranch)
   439             repo.dirstate.setbranch(origbranch)
   420 
   440 
   421         _finishshelve(repo)
   441         _finishshelve(repo)
   422     finally:
   442     finally:
       
   443         _restoreactivebookmark(repo, activebookmark)
   423         lockmod.release(tr, lock)
   444         lockmod.release(tr, lock)
   424 
   445 
   425 def _isbareshelve(pats, opts):
   446 def _isbareshelve(pats, opts):
   426     return (not pats
   447     return (not pats
   427             and not opts.get('interactive', False)
   448             and not opts.get('interactive', False)
   637 
   658 
   638         mergefiles(ui, repo, state.wctx, shelvectx)
   659         mergefiles(ui, repo, state.wctx, shelvectx)
   639         restorebranch(ui, repo, state.branchtorestore)
   660         restorebranch(ui, repo, state.branchtorestore)
   640 
   661 
   641         repair.strip(ui, repo, state.nodestoprune, backup=False, topic='shelve')
   662         repair.strip(ui, repo, state.nodestoprune, backup=False, topic='shelve')
       
   663         _restoreactivebookmark(repo, state.activebookmark)
   642         shelvedstate.clear(repo)
   664         shelvedstate.clear(repo)
   643         unshelvecleanup(ui, repo, state.name, opts)
   665         unshelvecleanup(ui, repo, state.name, opts)
   644         ui.status(_("unshelve of '%s' complete\n") % state.name)
   666         ui.status(_("unshelve of '%s' complete\n") % state.name)
   645 
   667 
   646 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
   668 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
   670     shelvectx = repo['tip']
   692     shelvectx = repo['tip']
   671     ui.quiet = oldquiet
   693     ui.quiet = oldquiet
   672     return repo, shelvectx
   694     return repo, shelvectx
   673 
   695 
   674 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
   696 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
   675                           tmpwctx, shelvectx, branchtorestore):
   697                           tmpwctx, shelvectx, branchtorestore,
       
   698                           activebookmark):
   676     """Rebase restored commit from its original location to a destination"""
   699     """Rebase restored commit from its original location to a destination"""
   677     # If the shelve is not immediately on top of the commit
   700     # If the shelve is not immediately on top of the commit
   678     # we'll be merging with, rebase it to be on top.
   701     # we'll be merging with, rebase it to be on top.
   679     if tmpwctx.node() == shelvectx.parents()[0].node():
   702     if tmpwctx.node() == shelvectx.parents()[0].node():
   680         return shelvectx
   703         return shelvectx
   691         tr.close()
   714         tr.close()
   692 
   715 
   693         nodestoprune = [repo.changelog.node(rev)
   716         nodestoprune = [repo.changelog.node(rev)
   694                         for rev in xrange(oldtiprev, len(repo))]
   717                         for rev in xrange(oldtiprev, len(repo))]
   695         shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoprune,
   718         shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoprune,
   696                           branchtorestore, opts.get('keep'))
   719                           branchtorestore, opts.get('keep'), activebookmark)
   697 
   720 
   698         repo.vfs.rename('rebasestate', 'unshelverebasestate')
   721         repo.vfs.rename('rebasestate', 'unshelverebasestate')
   699         raise error.InterventionRequired(
   722         raise error.InterventionRequired(
   700             _("unresolved conflicts (see 'hg resolve', then "
   723             _("unresolved conflicts (see 'hg resolve', then "
   701               "'hg unshelve --continue')"))
   724               "'hg unshelve --continue')"))
   717     shelveunknown = frozenset(shelveunknown.split('\0'))
   740     shelveunknown = frozenset(shelveunknown.split('\0'))
   718     addedafter = frozenset(repo.status().added)
   741     addedafter = frozenset(repo.status().added)
   719     toforget = (addedafter & shelveunknown) - addedbefore
   742     toforget = (addedafter & shelveunknown) - addedbefore
   720     repo[None].forget(toforget)
   743     repo[None].forget(toforget)
   721 
   744 
   722 def _finishunshelve(repo, oldtiprev, tr):
   745 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
       
   746     _restoreactivebookmark(repo, activebookmark)
   723     # The transaction aborting will strip all the commits for us,
   747     # The transaction aborting will strip all the commits for us,
   724     # but it doesn't update the inmemory structures, so addchangegroup
   748     # but it doesn't update the inmemory structures, so addchangegroup
   725     # hooks still fire and try to operate on the missing commits.
   749     # hooks still fire and try to operate on the missing commits.
   726     # Clean up manually to prevent this.
   750     # Clean up manually to prevent this.
   727     repo.unfiltered().changelog.strip(oldtiprev, tr)
   751     repo.unfiltered().changelog.strip(oldtiprev, tr)
   863         # ...-> pctx -> tmpwctx -> shelvectx
   887         # ...-> pctx -> tmpwctx -> shelvectx
   864         # where tmpwctx is an optional commit with the user's pending changes
   888         # where tmpwctx is an optional commit with the user's pending changes
   865         # and shelvectx is the unshelved changes. Then we merge it all down
   889         # and shelvectx is the unshelved changes. Then we merge it all down
   866         # to the original pctx.
   890         # to the original pctx.
   867 
   891 
       
   892         activebookmark = _backupactivebookmark(repo)
   868         overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
   893         overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
   869         with ui.configoverride(overrides, 'unshelve'):
   894         with ui.configoverride(overrides, 'unshelve'):
   870             tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
   895             tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
   871                                                              tmpwctx)
   896                                                              tmpwctx)
   872 
   897 
   877             if shelvectx.branch() != shelvectx.p1().branch():
   902             if shelvectx.branch() != shelvectx.p1().branch():
   878                 branchtorestore = shelvectx.branch()
   903                 branchtorestore = shelvectx.branch()
   879 
   904 
   880             shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
   905             shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
   881                                               basename, pctx, tmpwctx,
   906                                               basename, pctx, tmpwctx,
   882                                               shelvectx, branchtorestore)
   907                                               shelvectx, branchtorestore,
       
   908                                               activebookmark)
   883             mergefiles(ui, repo, pctx, shelvectx)
   909             mergefiles(ui, repo, pctx, shelvectx)
   884             restorebranch(ui, repo, branchtorestore)
   910             restorebranch(ui, repo, branchtorestore)
   885             _forgetunknownfiles(repo, shelvectx, addedbefore)
   911             _forgetunknownfiles(repo, shelvectx, addedbefore)
   886 
   912 
   887             shelvedstate.clear(repo)
   913             shelvedstate.clear(repo)
   888             _finishunshelve(repo, oldtiprev, tr)
   914             _finishunshelve(repo, oldtiprev, tr, activebookmark)
   889             unshelvecleanup(ui, repo, basename, opts)
   915             unshelvecleanup(ui, repo, basename, opts)
   890     finally:
   916     finally:
   891         ui.quiet = oldquiet
   917         ui.quiet = oldquiet
   892         if tr:
   918         if tr:
   893             tr.release()
   919             tr.release()