hgext/shelve.py
branchstable
changeset 42637 e386b5f4f836
parent 42564 740a3f39e764
parent 42636 12addcc7956c
child 42638 29403026458a
equal deleted inserted replaced
42564:740a3f39e764 42637:e386b5f4f836
     1 # shelve.py - save/restore working directory state
       
     2 #
       
     3 # Copyright 2013 Facebook, Inc.
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 """save and restore changes to the working directory
       
     9 
       
    10 The "hg shelve" command saves changes made to the working directory
       
    11 and reverts those changes, resetting the working directory to a clean
       
    12 state.
       
    13 
       
    14 Later on, the "hg unshelve" command restores the changes saved by "hg
       
    15 shelve". Changes can be restored even after updating to a different
       
    16 parent, in which case Mercurial's merge machinery will resolve any
       
    17 conflicts if necessary.
       
    18 
       
    19 You can have more than one shelved change outstanding at a time; each
       
    20 shelved change has a distinct name. For details, see the help for "hg
       
    21 shelve".
       
    22 """
       
    23 from __future__ import absolute_import
       
    24 
       
    25 import collections
       
    26 import errno
       
    27 import itertools
       
    28 import stat
       
    29 
       
    30 from mercurial.i18n import _
       
    31 from mercurial import (
       
    32     bookmarks,
       
    33     bundle2,
       
    34     bundlerepo,
       
    35     changegroup,
       
    36     cmdutil,
       
    37     discovery,
       
    38     error,
       
    39     exchange,
       
    40     hg,
       
    41     lock as lockmod,
       
    42     mdiff,
       
    43     merge,
       
    44     node as nodemod,
       
    45     patch,
       
    46     phases,
       
    47     pycompat,
       
    48     registrar,
       
    49     repair,
       
    50     scmutil,
       
    51     templatefilters,
       
    52     util,
       
    53     vfs as vfsmod,
       
    54 )
       
    55 
       
    56 from . import (
       
    57     rebase,
       
    58 )
       
    59 from mercurial.utils import (
       
    60     dateutil,
       
    61     stringutil,
       
    62 )
       
    63 
       
    64 cmdtable = {}
       
    65 command = registrar.command(cmdtable)
       
    66 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
       
    67 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
       
    68 # be specifying the version(s) of Mercurial they are tested with, or
       
    69 # leave the attribute unspecified.
       
    70 testedwith = 'ships-with-hg-core'
       
    71 
       
    72 configtable = {}
       
    73 configitem = registrar.configitem(configtable)
       
    74 
       
    75 configitem('shelve', 'maxbackups',
       
    76     default=10,
       
    77 )
       
    78 
       
    79 backupdir = 'shelve-backup'
       
    80 shelvedir = 'shelved'
       
    81 shelvefileextensions = ['hg', 'patch', 'shelve']
       
    82 # universal extension is present in all types of shelves
       
    83 patchextension = 'patch'
       
    84 
       
    85 # we never need the user, so we use a
       
    86 # generic user for all shelve operations
       
    87 shelveuser = 'shelve@localhost'
       
    88 
       
    89 class shelvedfile(object):
       
    90     """Helper for the file storing a single shelve
       
    91 
       
    92     Handles common functions on shelve files (.hg/.patch) using
       
    93     the vfs layer"""
       
    94     def __init__(self, repo, name, filetype=None):
       
    95         self.repo = repo
       
    96         self.name = name
       
    97         self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
       
    98         self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
       
    99         self.ui = self.repo.ui
       
   100         if filetype:
       
   101             self.fname = name + '.' + filetype
       
   102         else:
       
   103             self.fname = name
       
   104 
       
   105     def exists(self):
       
   106         return self.vfs.exists(self.fname)
       
   107 
       
   108     def filename(self):
       
   109         return self.vfs.join(self.fname)
       
   110 
       
   111     def backupfilename(self):
       
   112         def gennames(base):
       
   113             yield base
       
   114             base, ext = base.rsplit('.', 1)
       
   115             for i in itertools.count(1):
       
   116                 yield '%s-%d.%s' % (base, i, ext)
       
   117 
       
   118         name = self.backupvfs.join(self.fname)
       
   119         for n in gennames(name):
       
   120             if not self.backupvfs.exists(n):
       
   121                 return n
       
   122 
       
   123     def movetobackup(self):
       
   124         if not self.backupvfs.isdir():
       
   125             self.backupvfs.makedir()
       
   126         util.rename(self.filename(), self.backupfilename())
       
   127 
       
   128     def stat(self):
       
   129         return self.vfs.stat(self.fname)
       
   130 
       
   131     def opener(self, mode='rb'):
       
   132         try:
       
   133             return self.vfs(self.fname, mode)
       
   134         except IOError as err:
       
   135             if err.errno != errno.ENOENT:
       
   136                 raise
       
   137             raise error.Abort(_("shelved change '%s' not found") % self.name)
       
   138 
       
   139     def applybundle(self, tr):
       
   140         fp = self.opener()
       
   141         try:
       
   142             targetphase = phases.internal
       
   143             if not phases.supportinternal(self.repo):
       
   144                 targetphase = phases.secret
       
   145             gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
       
   146             pretip = self.repo['tip']
       
   147             bundle2.applybundle(self.repo, gen, tr,
       
   148                                 source='unshelve',
       
   149                                 url='bundle:' + self.vfs.join(self.fname),
       
   150                                 targetphase=targetphase)
       
   151             shelvectx = self.repo['tip']
       
   152             if pretip == shelvectx:
       
   153                 shelverev = tr.changes['revduplicates'][-1]
       
   154                 shelvectx = self.repo[shelverev]
       
   155             return shelvectx
       
   156         finally:
       
   157             fp.close()
       
   158 
       
   159     def bundlerepo(self):
       
   160         path = self.vfs.join(self.fname)
       
   161         return bundlerepo.instance(self.repo.baseui,
       
   162                                    'bundle://%s+%s' % (self.repo.root, path))
       
   163 
       
   164     def writebundle(self, bases, node):
       
   165         cgversion = changegroup.safeversion(self.repo)
       
   166         if cgversion == '01':
       
   167             btype = 'HG10BZ'
       
   168             compression = None
       
   169         else:
       
   170             btype = 'HG20'
       
   171             compression = 'BZ'
       
   172 
       
   173         repo = self.repo.unfiltered()
       
   174 
       
   175         outgoing = discovery.outgoing(repo, missingroots=bases,
       
   176                                       missingheads=[node])
       
   177         cg = changegroup.makechangegroup(repo, outgoing, cgversion, 'shelve')
       
   178 
       
   179         bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
       
   180                                 compression=compression)
       
   181 
       
   182     def writeinfo(self, info):
       
   183         scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info)
       
   184 
       
   185     def readinfo(self):
       
   186         return scmutil.simplekeyvaluefile(self.vfs, self.fname).read()
       
   187 
       
   188 class shelvedstate(object):
       
   189     """Handle persistence during unshelving operations.
       
   190 
       
   191     Handles saving and restoring a shelved state. Ensures that different
       
   192     versions of a shelved state are possible and handles them appropriately.
       
   193     """
       
   194     _version = 2
       
   195     _filename = 'shelvedstate'
       
   196     _keep = 'keep'
       
   197     _nokeep = 'nokeep'
       
   198     # colon is essential to differentiate from a real bookmark name
       
   199     _noactivebook = ':no-active-bookmark'
       
   200 
       
   201     @classmethod
       
   202     def _verifyandtransform(cls, d):
       
   203         """Some basic shelvestate syntactic verification and transformation"""
       
   204         try:
       
   205             d['originalwctx'] = nodemod.bin(d['originalwctx'])
       
   206             d['pendingctx'] = nodemod.bin(d['pendingctx'])
       
   207             d['parents'] = [nodemod.bin(h)
       
   208                             for h in d['parents'].split(' ')]
       
   209             d['nodestoremove'] = [nodemod.bin(h)
       
   210                                   for h in d['nodestoremove'].split(' ')]
       
   211         except (ValueError, TypeError, KeyError) as err:
       
   212             raise error.CorruptedState(pycompat.bytestr(err))
       
   213 
       
   214     @classmethod
       
   215     def _getversion(cls, repo):
       
   216         """Read version information from shelvestate file"""
       
   217         fp = repo.vfs(cls._filename)
       
   218         try:
       
   219             version = int(fp.readline().strip())
       
   220         except ValueError as err:
       
   221             raise error.CorruptedState(pycompat.bytestr(err))
       
   222         finally:
       
   223             fp.close()
       
   224         return version
       
   225 
       
   226     @classmethod
       
   227     def _readold(cls, repo):
       
   228         """Read the old position-based version of a shelvestate file"""
       
   229         # Order is important, because old shelvestate file uses it
       
   230         # to detemine values of fields (i.g. name is on the second line,
       
   231         # originalwctx is on the third and so forth). Please do not change.
       
   232         keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents',
       
   233                 'nodestoremove', 'branchtorestore', 'keep', 'activebook']
       
   234         # this is executed only seldomly, so it is not a big deal
       
   235         # that we open this file twice
       
   236         fp = repo.vfs(cls._filename)
       
   237         d = {}
       
   238         try:
       
   239             for key in keys:
       
   240                 d[key] = fp.readline().strip()
       
   241         finally:
       
   242             fp.close()
       
   243         return d
       
   244 
       
   245     @classmethod
       
   246     def load(cls, repo):
       
   247         version = cls._getversion(repo)
       
   248         if version < cls._version:
       
   249             d = cls._readold(repo)
       
   250         elif version == cls._version:
       
   251             d = scmutil.simplekeyvaluefile(
       
   252                 repo.vfs, cls._filename).read(firstlinenonkeyval=True)
       
   253         else:
       
   254             raise error.Abort(_('this version of shelve is incompatible '
       
   255                                 'with the version used in this repo'))
       
   256 
       
   257         cls._verifyandtransform(d)
       
   258         try:
       
   259             obj = cls()
       
   260             obj.name = d['name']
       
   261             obj.wctx = repo[d['originalwctx']]
       
   262             obj.pendingctx = repo[d['pendingctx']]
       
   263             obj.parents = d['parents']
       
   264             obj.nodestoremove = d['nodestoremove']
       
   265             obj.branchtorestore = d.get('branchtorestore', '')
       
   266             obj.keep = d.get('keep') == cls._keep
       
   267             obj.activebookmark = ''
       
   268             if d.get('activebook', '') != cls._noactivebook:
       
   269                 obj.activebookmark = d.get('activebook', '')
       
   270         except (error.RepoLookupError, KeyError) as err:
       
   271             raise error.CorruptedState(pycompat.bytestr(err))
       
   272 
       
   273         return obj
       
   274 
       
   275     @classmethod
       
   276     def save(cls, repo, name, originalwctx, pendingctx, nodestoremove,
       
   277              branchtorestore, keep=False, activebook=''):
       
   278         info = {
       
   279             "name": name,
       
   280             "originalwctx": nodemod.hex(originalwctx.node()),
       
   281             "pendingctx": nodemod.hex(pendingctx.node()),
       
   282             "parents": ' '.join([nodemod.hex(p)
       
   283                                  for p in repo.dirstate.parents()]),
       
   284             "nodestoremove": ' '.join([nodemod.hex(n)
       
   285                                       for n in nodestoremove]),
       
   286             "branchtorestore": branchtorestore,
       
   287             "keep": cls._keep if keep else cls._nokeep,
       
   288             "activebook": activebook or cls._noactivebook
       
   289         }
       
   290         scmutil.simplekeyvaluefile(
       
   291             repo.vfs, cls._filename).write(info,
       
   292                                            firstline=("%d" % cls._version))
       
   293 
       
   294     @classmethod
       
   295     def clear(cls, repo):
       
   296         repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
       
   297 
       
   298 def cleanupoldbackups(repo):
       
   299     vfs = vfsmod.vfs(repo.vfs.join(backupdir))
       
   300     maxbackups = repo.ui.configint('shelve', 'maxbackups')
       
   301     hgfiles = [f for f in vfs.listdir()
       
   302                if f.endswith('.' + patchextension)]
       
   303     hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles])
       
   304     if maxbackups > 0 and maxbackups < len(hgfiles):
       
   305         bordermtime = hgfiles[-maxbackups][0]
       
   306     else:
       
   307         bordermtime = None
       
   308     for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
       
   309         if mtime == bordermtime:
       
   310             # keep it, because timestamp can't decide exact order of backups
       
   311             continue
       
   312         base = f[:-(1 + len(patchextension))]
       
   313         for ext in shelvefileextensions:
       
   314             vfs.tryunlink(base + '.' + ext)
       
   315 
       
   316 def _backupactivebookmark(repo):
       
   317     activebookmark = repo._activebookmark
       
   318     if activebookmark:
       
   319         bookmarks.deactivate(repo)
       
   320     return activebookmark
       
   321 
       
   322 def _restoreactivebookmark(repo, mark):
       
   323     if mark:
       
   324         bookmarks.activate(repo, mark)
       
   325 
       
   326 def _aborttransaction(repo, tr):
       
   327     '''Abort current transaction for shelve/unshelve, but keep dirstate
       
   328     '''
       
   329     dirstatebackupname = 'dirstate.shelve'
       
   330     repo.dirstate.savebackup(tr, dirstatebackupname)
       
   331     tr.abort()
       
   332     repo.dirstate.restorebackup(None, dirstatebackupname)
       
   333 
       
   334 def getshelvename(repo, parent, opts):
       
   335     """Decide on the name this shelve is going to have"""
       
   336     def gennames():
       
   337         yield label
       
   338         for i in itertools.count(1):
       
   339             yield '%s-%02d' % (label, i)
       
   340     name = opts.get('name')
       
   341     label = repo._activebookmark or parent.branch() or 'default'
       
   342     # slashes aren't allowed in filenames, therefore we rename it
       
   343     label = label.replace('/', '_')
       
   344     label = label.replace('\\', '_')
       
   345     # filenames must not start with '.' as it should not be hidden
       
   346     if label.startswith('.'):
       
   347         label = label.replace('.', '_', 1)
       
   348 
       
   349     if name:
       
   350         if shelvedfile(repo, name, patchextension).exists():
       
   351             e = _("a shelved change named '%s' already exists") % name
       
   352             raise error.Abort(e)
       
   353 
       
   354         # ensure we are not creating a subdirectory or a hidden file
       
   355         if '/' in name or '\\' in name:
       
   356             raise error.Abort(_('shelved change names can not contain slashes'))
       
   357         if name.startswith('.'):
       
   358             raise error.Abort(_("shelved change names can not start with '.'"))
       
   359 
       
   360     else:
       
   361         for n in gennames():
       
   362             if not shelvedfile(repo, n, patchextension).exists():
       
   363                 name = n
       
   364                 break
       
   365 
       
   366     return name
       
   367 
       
   368 def mutableancestors(ctx):
       
   369     """return all mutable ancestors for ctx (included)
       
   370 
       
   371     Much faster than the revset ancestors(ctx) & draft()"""
       
   372     seen = {nodemod.nullrev}
       
   373     visit = collections.deque()
       
   374     visit.append(ctx)
       
   375     while visit:
       
   376         ctx = visit.popleft()
       
   377         yield ctx.node()
       
   378         for parent in ctx.parents():
       
   379             rev = parent.rev()
       
   380             if rev not in seen:
       
   381                 seen.add(rev)
       
   382                 if parent.mutable():
       
   383                     visit.append(parent)
       
   384 
       
   385 def getcommitfunc(extra, interactive, editor=False):
       
   386     def commitfunc(ui, repo, message, match, opts):
       
   387         hasmq = util.safehasattr(repo, 'mq')
       
   388         if hasmq:
       
   389             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
       
   390 
       
   391         targetphase = phases.internal
       
   392         if not phases.supportinternal(repo):
       
   393             targetphase = phases.secret
       
   394         overrides = {('phases', 'new-commit'): targetphase}
       
   395         try:
       
   396             editor_ = False
       
   397             if editor:
       
   398                 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
       
   399                                                   **pycompat.strkwargs(opts))
       
   400             with repo.ui.configoverride(overrides):
       
   401                 return repo.commit(message, shelveuser, opts.get('date'),
       
   402                                    match, editor=editor_, extra=extra)
       
   403         finally:
       
   404             if hasmq:
       
   405                 repo.mq.checkapplied = saved
       
   406 
       
   407     def interactivecommitfunc(ui, repo, *pats, **opts):
       
   408         opts = pycompat.byteskwargs(opts)
       
   409         match = scmutil.match(repo['.'], pats, {})
       
   410         message = opts['message']
       
   411         return commitfunc(ui, repo, message, match, opts)
       
   412 
       
   413     return interactivecommitfunc if interactive else commitfunc
       
   414 
       
   415 def _nothingtoshelvemessaging(ui, repo, pats, opts):
       
   416     stat = repo.status(match=scmutil.match(repo[None], pats, opts))
       
   417     if stat.deleted:
       
   418         ui.status(_("nothing changed (%d missing files, see "
       
   419                     "'hg status')\n") % len(stat.deleted))
       
   420     else:
       
   421         ui.status(_("nothing changed\n"))
       
   422 
       
   423 def _shelvecreatedcommit(repo, node, name, match):
       
   424     info = {'node': nodemod.hex(node)}
       
   425     shelvedfile(repo, name, 'shelve').writeinfo(info)
       
   426     bases = list(mutableancestors(repo[node]))
       
   427     shelvedfile(repo, name, 'hg').writebundle(bases, node)
       
   428     with shelvedfile(repo, name, patchextension).opener('wb') as fp:
       
   429         cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True),
       
   430                            match=match)
       
   431 
       
   432 def _includeunknownfiles(repo, pats, opts, extra):
       
   433     s = repo.status(match=scmutil.match(repo[None], pats, opts),
       
   434                     unknown=True)
       
   435     if s.unknown:
       
   436         extra['shelve_unknown'] = '\0'.join(s.unknown)
       
   437         repo[None].add(s.unknown)
       
   438 
       
   439 def _finishshelve(repo, tr):
       
   440     if phases.supportinternal(repo):
       
   441         tr.close()
       
   442     else:
       
   443         _aborttransaction(repo, tr)
       
   444 
       
   445 def createcmd(ui, repo, pats, opts):
       
   446     """subcommand that creates a new shelve"""
       
   447     with repo.wlock():
       
   448         cmdutil.checkunfinished(repo)
       
   449         return _docreatecmd(ui, repo, pats, opts)
       
   450 
       
   451 def _docreatecmd(ui, repo, pats, opts):
       
   452     wctx = repo[None]
       
   453     parents = wctx.parents()
       
   454     if len(parents) > 1:
       
   455         raise error.Abort(_('cannot shelve while merging'))
       
   456     parent = parents[0]
       
   457     origbranch = wctx.branch()
       
   458 
       
   459     if parent.node() != nodemod.nullid:
       
   460         desc = "changes to: %s" % parent.description().split('\n', 1)[0]
       
   461     else:
       
   462         desc = '(changes in empty repository)'
       
   463 
       
   464     if not opts.get('message'):
       
   465         opts['message'] = desc
       
   466 
       
   467     lock = tr = activebookmark = None
       
   468     try:
       
   469         lock = repo.lock()
       
   470 
       
   471         # use an uncommitted transaction to generate the bundle to avoid
       
   472         # pull races. ensure we don't print the abort message to stderr.
       
   473         tr = repo.transaction('shelve', report=lambda x: None)
       
   474 
       
   475         interactive = opts.get('interactive', False)
       
   476         includeunknown = (opts.get('unknown', False) and
       
   477                           not opts.get('addremove', False))
       
   478 
       
   479         name = getshelvename(repo, parent, opts)
       
   480         activebookmark = _backupactivebookmark(repo)
       
   481         extra = {'internal': 'shelve'}
       
   482         if includeunknown:
       
   483             _includeunknownfiles(repo, pats, opts, extra)
       
   484 
       
   485         if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
       
   486             # In non-bare shelve we don't store newly created branch
       
   487             # at bundled commit
       
   488             repo.dirstate.setbranch(repo['.'].branch())
       
   489 
       
   490         commitfunc = getcommitfunc(extra, interactive, editor=True)
       
   491         if not interactive:
       
   492             node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
       
   493         else:
       
   494             node = cmdutil.dorecord(ui, repo, commitfunc, None,
       
   495                                     False, cmdutil.recordfilter, *pats,
       
   496                                     **pycompat.strkwargs(opts))
       
   497         if not node:
       
   498             _nothingtoshelvemessaging(ui, repo, pats, opts)
       
   499             return 1
       
   500 
       
   501         # Create a matcher so that prefetch doesn't attempt to fetch
       
   502         # the entire repository pointlessly, and as an optimisation
       
   503         # for movedirstate, if needed.
       
   504         match = scmutil.matchfiles(repo, repo[node].files())
       
   505         _shelvecreatedcommit(repo, node, name, match)
       
   506 
       
   507         if ui.formatted():
       
   508             desc = stringutil.ellipsis(desc, ui.termwidth())
       
   509         ui.status(_('shelved as %s\n') % name)
       
   510         if opts['keep']:
       
   511             with repo.dirstate.parentchange():
       
   512                 scmutil.movedirstate(repo, parent, match)
       
   513         else:
       
   514             hg.update(repo, parent.node())
       
   515         if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
       
   516             repo.dirstate.setbranch(origbranch)
       
   517 
       
   518         _finishshelve(repo, tr)
       
   519     finally:
       
   520         _restoreactivebookmark(repo, activebookmark)
       
   521         lockmod.release(tr, lock)
       
   522 
       
   523 def _isbareshelve(pats, opts):
       
   524     return (not pats
       
   525             and not opts.get('interactive', False)
       
   526             and not opts.get('include', False)
       
   527             and not opts.get('exclude', False))
       
   528 
       
   529 def _iswctxonnewbranch(repo):
       
   530     return repo[None].branch() != repo['.'].branch()
       
   531 
       
   532 def cleanupcmd(ui, repo):
       
   533     """subcommand that deletes all shelves"""
       
   534 
       
   535     with repo.wlock():
       
   536         for (name, _type) in repo.vfs.readdir(shelvedir):
       
   537             suffix = name.rsplit('.', 1)[-1]
       
   538             if suffix in shelvefileextensions:
       
   539                 shelvedfile(repo, name).movetobackup()
       
   540             cleanupoldbackups(repo)
       
   541 
       
   542 def deletecmd(ui, repo, pats):
       
   543     """subcommand that deletes a specific shelve"""
       
   544     if not pats:
       
   545         raise error.Abort(_('no shelved changes specified!'))
       
   546     with repo.wlock():
       
   547         try:
       
   548             for name in pats:
       
   549                 for suffix in shelvefileextensions:
       
   550                     shfile = shelvedfile(repo, name, suffix)
       
   551                     # patch file is necessary, as it should
       
   552                     # be present for any kind of shelve,
       
   553                     # but the .hg file is optional as in future we
       
   554                     # will add obsolete shelve with does not create a
       
   555                     # bundle
       
   556                     if shfile.exists() or suffix == patchextension:
       
   557                         shfile.movetobackup()
       
   558             cleanupoldbackups(repo)
       
   559         except OSError as err:
       
   560             if err.errno != errno.ENOENT:
       
   561                 raise
       
   562             raise error.Abort(_("shelved change '%s' not found") % name)
       
   563 
       
   564 def listshelves(repo):
       
   565     """return all shelves in repo as list of (time, filename)"""
       
   566     try:
       
   567         names = repo.vfs.readdir(shelvedir)
       
   568     except OSError as err:
       
   569         if err.errno != errno.ENOENT:
       
   570             raise
       
   571         return []
       
   572     info = []
       
   573     for (name, _type) in names:
       
   574         pfx, sfx = name.rsplit('.', 1)
       
   575         if not pfx or sfx != patchextension:
       
   576             continue
       
   577         st = shelvedfile(repo, name).stat()
       
   578         info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
       
   579     return sorted(info, reverse=True)
       
   580 
       
   581 def listcmd(ui, repo, pats, opts):
       
   582     """subcommand that displays the list of shelves"""
       
   583     pats = set(pats)
       
   584     width = 80
       
   585     if not ui.plain():
       
   586         width = ui.termwidth()
       
   587     namelabel = 'shelve.newest'
       
   588     ui.pager('shelve')
       
   589     for mtime, name in listshelves(repo):
       
   590         sname = util.split(name)[1]
       
   591         if pats and sname not in pats:
       
   592             continue
       
   593         ui.write(sname, label=namelabel)
       
   594         namelabel = 'shelve.name'
       
   595         if ui.quiet:
       
   596             ui.write('\n')
       
   597             continue
       
   598         ui.write(' ' * (16 - len(sname)))
       
   599         used = 16
       
   600         date = dateutil.makedate(mtime)
       
   601         age = '(%s)' % templatefilters.age(date, abbrev=True)
       
   602         ui.write(age, label='shelve.age')
       
   603         ui.write(' ' * (12 - len(age)))
       
   604         used += 12
       
   605         with open(name + '.' + patchextension, 'rb') as fp:
       
   606             while True:
       
   607                 line = fp.readline()
       
   608                 if not line:
       
   609                     break
       
   610                 if not line.startswith('#'):
       
   611                     desc = line.rstrip()
       
   612                     if ui.formatted():
       
   613                         desc = stringutil.ellipsis(desc, width - used)
       
   614                     ui.write(desc)
       
   615                     break
       
   616             ui.write('\n')
       
   617             if not (opts['patch'] or opts['stat']):
       
   618                 continue
       
   619             difflines = fp.readlines()
       
   620             if opts['patch']:
       
   621                 for chunk, label in patch.difflabel(iter, difflines):
       
   622                     ui.write(chunk, label=label)
       
   623             if opts['stat']:
       
   624                 for chunk, label in patch.diffstatui(difflines, width=width):
       
   625                     ui.write(chunk, label=label)
       
   626 
       
   627 def patchcmds(ui, repo, pats, opts):
       
   628     """subcommand that displays shelves"""
       
   629     if len(pats) == 0:
       
   630         shelves = listshelves(repo)
       
   631         if not shelves:
       
   632             raise error.Abort(_("there are no shelves to show"))
       
   633         mtime, name = shelves[0]
       
   634         sname = util.split(name)[1]
       
   635         pats = [sname]
       
   636 
       
   637     for shelfname in pats:
       
   638         if not shelvedfile(repo, shelfname, patchextension).exists():
       
   639             raise error.Abort(_("cannot find shelf %s") % shelfname)
       
   640 
       
   641     listcmd(ui, repo, pats, opts)
       
   642 
       
   643 def checkparents(repo, state):
       
   644     """check parent while resuming an unshelve"""
       
   645     if state.parents != repo.dirstate.parents():
       
   646         raise error.Abort(_('working directory parents do not match unshelve '
       
   647                            'state'))
       
   648 
       
   649 def unshelveabort(ui, repo, state, opts):
       
   650     """subcommand that abort an in-progress unshelve"""
       
   651     with repo.lock():
       
   652         try:
       
   653             checkparents(repo, state)
       
   654 
       
   655             merge.update(repo, state.pendingctx, branchmerge=False, force=True)
       
   656             if (state.activebookmark
       
   657                     and state.activebookmark in repo._bookmarks):
       
   658                 bookmarks.activate(repo, state.activebookmark)
       
   659 
       
   660             if repo.vfs.exists('unshelverebasestate'):
       
   661                 repo.vfs.rename('unshelverebasestate', 'rebasestate')
       
   662                 rebase.clearstatus(repo)
       
   663 
       
   664             mergefiles(ui, repo, state.wctx, state.pendingctx)
       
   665             if not phases.supportinternal(repo):
       
   666                 repair.strip(ui, repo, state.nodestoremove, backup=False,
       
   667                              topic='shelve')
       
   668         finally:
       
   669             shelvedstate.clear(repo)
       
   670             ui.warn(_("unshelve of '%s' aborted\n") % state.name)
       
   671 
       
   672 def mergefiles(ui, repo, wctx, shelvectx):
       
   673     """updates to wctx and merges the changes from shelvectx into the
       
   674     dirstate."""
       
   675     with ui.configoverride({('ui', 'quiet'): True}):
       
   676         hg.update(repo, wctx.node())
       
   677         ui.pushbuffer(True)
       
   678         cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents())
       
   679         ui.popbuffer()
       
   680 
       
   681 def restorebranch(ui, repo, branchtorestore):
       
   682     if branchtorestore and branchtorestore != repo.dirstate.branch():
       
   683         repo.dirstate.setbranch(branchtorestore)
       
   684         ui.status(_('marked working directory as branch %s\n')
       
   685                   % branchtorestore)
       
   686 
       
   687 def unshelvecleanup(ui, repo, name, opts):
       
   688     """remove related files after an unshelve"""
       
   689     if not opts.get('keep'):
       
   690         for filetype in shelvefileextensions:
       
   691             shfile = shelvedfile(repo, name, filetype)
       
   692             if shfile.exists():
       
   693                 shfile.movetobackup()
       
   694         cleanupoldbackups(repo)
       
   695 
       
   696 def unshelvecontinue(ui, repo, state, opts):
       
   697     """subcommand to continue an in-progress unshelve"""
       
   698     # We're finishing off a merge. First parent is our original
       
   699     # parent, second is the temporary "fake" commit we're unshelving.
       
   700     with repo.lock():
       
   701         checkparents(repo, state)
       
   702         ms = merge.mergestate.read(repo)
       
   703         if list(ms.unresolved()):
       
   704             raise error.Abort(
       
   705                 _("unresolved conflicts, can't continue"),
       
   706                 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
       
   707 
       
   708         shelvectx = repo[state.parents[1]]
       
   709         pendingctx = state.pendingctx
       
   710 
       
   711         with repo.dirstate.parentchange():
       
   712             repo.setparents(state.pendingctx.node(), nodemod.nullid)
       
   713             repo.dirstate.write(repo.currenttransaction())
       
   714 
       
   715         targetphase = phases.internal
       
   716         if not phases.supportinternal(repo):
       
   717             targetphase = phases.secret
       
   718         overrides = {('phases', 'new-commit'): targetphase}
       
   719         with repo.ui.configoverride(overrides, 'unshelve'):
       
   720             with repo.dirstate.parentchange():
       
   721                 repo.setparents(state.parents[0], nodemod.nullid)
       
   722                 newnode = repo.commit(text=shelvectx.description(),
       
   723                                       extra=shelvectx.extra(),
       
   724                                       user=shelvectx.user(),
       
   725                                       date=shelvectx.date())
       
   726 
       
   727         if newnode is None:
       
   728             # If it ended up being a no-op commit, then the normal
       
   729             # merge state clean-up path doesn't happen, so do it
       
   730             # here. Fix issue5494
       
   731             merge.mergestate.clean(repo)
       
   732             shelvectx = state.pendingctx
       
   733             msg = _('note: unshelved changes already existed '
       
   734                     'in the working copy\n')
       
   735             ui.status(msg)
       
   736         else:
       
   737             # only strip the shelvectx if we produced one
       
   738             state.nodestoremove.append(newnode)
       
   739             shelvectx = repo[newnode]
       
   740 
       
   741         hg.updaterepo(repo, pendingctx.node(), overwrite=False)
       
   742 
       
   743         if repo.vfs.exists('unshelverebasestate'):
       
   744             repo.vfs.rename('unshelverebasestate', 'rebasestate')
       
   745             rebase.clearstatus(repo)
       
   746 
       
   747         mergefiles(ui, repo, state.wctx, shelvectx)
       
   748         restorebranch(ui, repo, state.branchtorestore)
       
   749 
       
   750         if not phases.supportinternal(repo):
       
   751             repair.strip(ui, repo, state.nodestoremove, backup=False,
       
   752                          topic='shelve')
       
   753         _restoreactivebookmark(repo, state.activebookmark)
       
   754         shelvedstate.clear(repo)
       
   755         unshelvecleanup(ui, repo, state.name, opts)
       
   756         ui.status(_("unshelve of '%s' complete\n") % state.name)
       
   757 
       
   758 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
       
   759     """Temporarily commit working copy changes before moving unshelve commit"""
       
   760     # Store pending changes in a commit and remember added in case a shelve
       
   761     # contains unknown files that are part of the pending change
       
   762     s = repo.status()
       
   763     addedbefore = frozenset(s.added)
       
   764     if not (s.modified or s.added or s.removed):
       
   765         return tmpwctx, addedbefore
       
   766     ui.status(_("temporarily committing pending changes "
       
   767                 "(restore with 'hg unshelve --abort')\n"))
       
   768     extra = {'internal': 'shelve'}
       
   769     commitfunc = getcommitfunc(extra=extra, interactive=False,
       
   770                                editor=False)
       
   771     tempopts = {}
       
   772     tempopts['message'] = "pending changes temporary commit"
       
   773     tempopts['date'] = opts.get('date')
       
   774     with ui.configoverride({('ui', 'quiet'): True}):
       
   775         node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
       
   776     tmpwctx = repo[node]
       
   777     return tmpwctx, addedbefore
       
   778 
       
   779 def _unshelverestorecommit(ui, repo, tr, basename):
       
   780     """Recreate commit in the repository during the unshelve"""
       
   781     repo = repo.unfiltered()
       
   782     node = None
       
   783     if shelvedfile(repo, basename, 'shelve').exists():
       
   784         node = shelvedfile(repo, basename, 'shelve').readinfo()['node']
       
   785     if node is None or node not in repo:
       
   786         with ui.configoverride({('ui', 'quiet'): True}):
       
   787             shelvectx = shelvedfile(repo, basename, 'hg').applybundle(tr)
       
   788         # We might not strip the unbundled changeset, so we should keep track of
       
   789         # the unshelve node in case we need to reuse it (eg: unshelve --keep)
       
   790         if node is None:
       
   791             info = {'node': nodemod.hex(shelvectx.node())}
       
   792             shelvedfile(repo, basename, 'shelve').writeinfo(info)
       
   793     else:
       
   794         shelvectx = repo[node]
       
   795 
       
   796     return repo, shelvectx
       
   797 
       
   798 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
       
   799                           tmpwctx, shelvectx, branchtorestore,
       
   800                           activebookmark):
       
   801     """Rebase restored commit from its original location to a destination"""
       
   802     # If the shelve is not immediately on top of the commit
       
   803     # we'll be merging with, rebase it to be on top.
       
   804     if tmpwctx.node() == shelvectx.p1().node():
       
   805         return shelvectx
       
   806 
       
   807     overrides = {
       
   808         ('ui', 'forcemerge'): opts.get('tool', ''),
       
   809         ('phases', 'new-commit'): phases.secret,
       
   810     }
       
   811     with repo.ui.configoverride(overrides, 'unshelve'):
       
   812         ui.status(_('rebasing shelved changes\n'))
       
   813         stats = merge.graft(repo, shelvectx, shelvectx.p1(),
       
   814                            labels=['shelve', 'working-copy'],
       
   815                            keepconflictparent=True)
       
   816         if stats.unresolvedcount:
       
   817             tr.close()
       
   818 
       
   819             nodestoremove = [repo.changelog.node(rev)
       
   820                              for rev in pycompat.xrange(oldtiprev, len(repo))]
       
   821             shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
       
   822                               branchtorestore, opts.get('keep'), activebookmark)
       
   823             raise error.InterventionRequired(
       
   824                 _("unresolved conflicts (see 'hg resolve', then "
       
   825                   "'hg unshelve --continue')"))
       
   826 
       
   827         with repo.dirstate.parentchange():
       
   828             repo.setparents(tmpwctx.node(), nodemod.nullid)
       
   829             newnode = repo.commit(text=shelvectx.description(),
       
   830                                   extra=shelvectx.extra(),
       
   831                                   user=shelvectx.user(),
       
   832                                   date=shelvectx.date())
       
   833 
       
   834         if newnode is None:
       
   835             # If it ended up being a no-op commit, then the normal
       
   836             # merge state clean-up path doesn't happen, so do it
       
   837             # here. Fix issue5494
       
   838             merge.mergestate.clean(repo)
       
   839             shelvectx = tmpwctx
       
   840             msg = _('note: unshelved changes already existed '
       
   841                     'in the working copy\n')
       
   842             ui.status(msg)
       
   843         else:
       
   844             shelvectx = repo[newnode]
       
   845             hg.updaterepo(repo, tmpwctx.node(), False)
       
   846 
       
   847     return shelvectx
       
   848 
       
   849 def _forgetunknownfiles(repo, shelvectx, addedbefore):
       
   850     # Forget any files that were unknown before the shelve, unknown before
       
   851     # unshelve started, but are now added.
       
   852     shelveunknown = shelvectx.extra().get('shelve_unknown')
       
   853     if not shelveunknown:
       
   854         return
       
   855     shelveunknown = frozenset(shelveunknown.split('\0'))
       
   856     addedafter = frozenset(repo.status().added)
       
   857     toforget = (addedafter & shelveunknown) - addedbefore
       
   858     repo[None].forget(toforget)
       
   859 
       
   860 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
       
   861     _restoreactivebookmark(repo, activebookmark)
       
   862     # The transaction aborting will strip all the commits for us,
       
   863     # but it doesn't update the inmemory structures, so addchangegroup
       
   864     # hooks still fire and try to operate on the missing commits.
       
   865     # Clean up manually to prevent this.
       
   866     repo.unfiltered().changelog.strip(oldtiprev, tr)
       
   867     _aborttransaction(repo, tr)
       
   868 
       
   869 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
       
   870     """Check potential problems which may result from working
       
   871     copy having untracked changes."""
       
   872     wcdeleted = set(repo.status().deleted)
       
   873     shelvetouched = set(shelvectx.files())
       
   874     intersection = wcdeleted.intersection(shelvetouched)
       
   875     if intersection:
       
   876         m = _("shelved change touches missing files")
       
   877         hint = _("run hg status to see which files are missing")
       
   878         raise error.Abort(m, hint=hint)
       
   879 
       
   880 @command('unshelve',
       
   881          [('a', 'abort', None,
       
   882            _('abort an incomplete unshelve operation')),
       
   883           ('c', 'continue', None,
       
   884            _('continue an incomplete unshelve operation')),
       
   885           ('k', 'keep', None,
       
   886            _('keep shelve after unshelving')),
       
   887           ('n', 'name', '',
       
   888            _('restore shelved change with given name'), _('NAME')),
       
   889           ('t', 'tool', '', _('specify merge tool')),
       
   890           ('', 'date', '',
       
   891            _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
       
   892          _('hg unshelve [[-n] SHELVED]'),
       
   893          helpcategory=command.CATEGORY_WORKING_DIRECTORY)
       
   894 def unshelve(ui, repo, *shelved, **opts):
       
   895     """restore a shelved change to the working directory
       
   896 
       
   897     This command accepts an optional name of a shelved change to
       
   898     restore. If none is given, the most recent shelved change is used.
       
   899 
       
   900     If a shelved change is applied successfully, the bundle that
       
   901     contains the shelved changes is moved to a backup location
       
   902     (.hg/shelve-backup).
       
   903 
       
   904     Since you can restore a shelved change on top of an arbitrary
       
   905     commit, it is possible that unshelving will result in a conflict
       
   906     between your changes and the commits you are unshelving onto. If
       
   907     this occurs, you must resolve the conflict, then use
       
   908     ``--continue`` to complete the unshelve operation. (The bundle
       
   909     will not be moved until you successfully complete the unshelve.)
       
   910 
       
   911     (Alternatively, you can use ``--abort`` to abandon an unshelve
       
   912     that causes a conflict. This reverts the unshelved changes, and
       
   913     leaves the bundle in place.)
       
   914 
       
   915     If bare shelved change(when no files are specified, without interactive,
       
   916     include and exclude option) was done on newly created branch it would
       
   917     restore branch information to the working directory.
       
   918 
       
   919     After a successful unshelve, the shelved changes are stored in a
       
   920     backup directory. Only the N most recent backups are kept. N
       
   921     defaults to 10 but can be overridden using the ``shelve.maxbackups``
       
   922     configuration option.
       
   923 
       
   924     .. container:: verbose
       
   925 
       
   926        Timestamp in seconds is used to decide order of backups. More
       
   927        than ``maxbackups`` backups are kept, if same timestamp
       
   928        prevents from deciding exact order of them, for safety.
       
   929     """
       
   930     with repo.wlock():
       
   931         return _dounshelve(ui, repo, *shelved, **opts)
       
   932 
       
   933 def _dounshelve(ui, repo, *shelved, **opts):
       
   934     opts = pycompat.byteskwargs(opts)
       
   935     abortf = opts.get('abort')
       
   936     continuef = opts.get('continue')
       
   937     if not abortf and not continuef:
       
   938         cmdutil.checkunfinished(repo)
       
   939     shelved = list(shelved)
       
   940     if opts.get("name"):
       
   941         shelved.append(opts["name"])
       
   942 
       
   943     if abortf or continuef:
       
   944         if abortf and continuef:
       
   945             raise error.Abort(_('cannot use both abort and continue'))
       
   946         if shelved:
       
   947             raise error.Abort(_('cannot combine abort/continue with '
       
   948                                'naming a shelved change'))
       
   949         if abortf and opts.get('tool', False):
       
   950             ui.warn(_('tool option will be ignored\n'))
       
   951 
       
   952         try:
       
   953             state = shelvedstate.load(repo)
       
   954             if opts.get('keep') is None:
       
   955                 opts['keep'] = state.keep
       
   956         except IOError as err:
       
   957             if err.errno != errno.ENOENT:
       
   958                 raise
       
   959             cmdutil.wrongtooltocontinue(repo, _('unshelve'))
       
   960         except error.CorruptedState as err:
       
   961             ui.debug(pycompat.bytestr(err) + '\n')
       
   962             if continuef:
       
   963                 msg = _('corrupted shelved state file')
       
   964                 hint = _('please run hg unshelve --abort to abort unshelve '
       
   965                          'operation')
       
   966                 raise error.Abort(msg, hint=hint)
       
   967             elif abortf:
       
   968                 msg = _('could not read shelved state file, your working copy '
       
   969                         'may be in an unexpected state\nplease update to some '
       
   970                         'commit\n')
       
   971                 ui.warn(msg)
       
   972                 shelvedstate.clear(repo)
       
   973             return
       
   974 
       
   975         if abortf:
       
   976             return unshelveabort(ui, repo, state, opts)
       
   977         elif continuef:
       
   978             return unshelvecontinue(ui, repo, state, opts)
       
   979     elif len(shelved) > 1:
       
   980         raise error.Abort(_('can only unshelve one change at a time'))
       
   981 
       
   982     # abort unshelve while merging (issue5123)
       
   983     parents = repo[None].parents()
       
   984     if len(parents) > 1:
       
   985         raise error.Abort(_('cannot unshelve while merging'))
       
   986 
       
   987     elif not shelved:
       
   988         shelved = listshelves(repo)
       
   989         if not shelved:
       
   990             raise error.Abort(_('no shelved changes to apply!'))
       
   991         basename = util.split(shelved[0][1])[1]
       
   992         ui.status(_("unshelving change '%s'\n") % basename)
       
   993     else:
       
   994         basename = shelved[0]
       
   995 
       
   996     if not shelvedfile(repo, basename, patchextension).exists():
       
   997         raise error.Abort(_("shelved change '%s' not found") % basename)
       
   998 
       
   999     repo = repo.unfiltered()
       
  1000     lock = tr = None
       
  1001     try:
       
  1002         lock = repo.lock()
       
  1003         tr = repo.transaction('unshelve', report=lambda x: None)
       
  1004         oldtiprev = len(repo)
       
  1005 
       
  1006         pctx = repo['.']
       
  1007         tmpwctx = pctx
       
  1008         # The goal is to have a commit structure like so:
       
  1009         # ...-> pctx -> tmpwctx -> shelvectx
       
  1010         # where tmpwctx is an optional commit with the user's pending changes
       
  1011         # and shelvectx is the unshelved changes. Then we merge it all down
       
  1012         # to the original pctx.
       
  1013 
       
  1014         activebookmark = _backupactivebookmark(repo)
       
  1015         tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
       
  1016                                                          tmpwctx)
       
  1017         repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
       
  1018         _checkunshelveuntrackedproblems(ui, repo, shelvectx)
       
  1019         branchtorestore = ''
       
  1020         if shelvectx.branch() != shelvectx.p1().branch():
       
  1021             branchtorestore = shelvectx.branch()
       
  1022 
       
  1023         shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
       
  1024                                           basename, pctx, tmpwctx,
       
  1025                                           shelvectx, branchtorestore,
       
  1026                                           activebookmark)
       
  1027         overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
       
  1028         with ui.configoverride(overrides, 'unshelve'):
       
  1029             mergefiles(ui, repo, pctx, shelvectx)
       
  1030         restorebranch(ui, repo, branchtorestore)
       
  1031         _forgetunknownfiles(repo, shelvectx, addedbefore)
       
  1032 
       
  1033         shelvedstate.clear(repo)
       
  1034         _finishunshelve(repo, oldtiprev, tr, activebookmark)
       
  1035         unshelvecleanup(ui, repo, basename, opts)
       
  1036     finally:
       
  1037         if tr:
       
  1038             tr.release()
       
  1039         lockmod.release(lock)
       
  1040 
       
  1041 @command('shelve',
       
  1042          [('A', 'addremove', None,
       
  1043            _('mark new/missing files as added/removed before shelving')),
       
  1044           ('u', 'unknown', None,
       
  1045            _('store unknown files in the shelve')),
       
  1046           ('', 'cleanup', None,
       
  1047            _('delete all shelved changes')),
       
  1048           ('', 'date', '',
       
  1049            _('shelve with the specified commit date'), _('DATE')),
       
  1050           ('d', 'delete', None,
       
  1051            _('delete the named shelved change(s)')),
       
  1052           ('e', 'edit', False,
       
  1053            _('invoke editor on commit messages')),
       
  1054           ('k', 'keep', False,
       
  1055            _('shelve, but keep changes in the working directory')),
       
  1056           ('l', 'list', None,
       
  1057            _('list current shelves')),
       
  1058           ('m', 'message', '',
       
  1059            _('use text as shelve message'), _('TEXT')),
       
  1060           ('n', 'name', '',
       
  1061            _('use the given name for the shelved commit'), _('NAME')),
       
  1062           ('p', 'patch', None,
       
  1063            _('output patches for changes (provide the names of the shelved '
       
  1064              'changes as positional arguments)')),
       
  1065           ('i', 'interactive', None,
       
  1066            _('interactive mode, only works while creating a shelve')),
       
  1067           ('', 'stat', None,
       
  1068            _('output diffstat-style summary of changes (provide the names of '
       
  1069              'the shelved changes as positional arguments)')
       
  1070            )] + cmdutil.walkopts,
       
  1071          _('hg shelve [OPTION]... [FILE]...'),
       
  1072          helpcategory=command.CATEGORY_WORKING_DIRECTORY)
       
  1073 def shelvecmd(ui, repo, *pats, **opts):
       
  1074     '''save and set aside changes from the working directory
       
  1075 
       
  1076     Shelving takes files that "hg status" reports as not clean, saves
       
  1077     the modifications to a bundle (a shelved change), and reverts the
       
  1078     files so that their state in the working directory becomes clean.
       
  1079 
       
  1080     To restore these changes to the working directory, using "hg
       
  1081     unshelve"; this will work even if you switch to a different
       
  1082     commit.
       
  1083 
       
  1084     When no files are specified, "hg shelve" saves all not-clean
       
  1085     files. If specific files or directories are named, only changes to
       
  1086     those files are shelved.
       
  1087 
       
  1088     In bare shelve (when no files are specified, without interactive,
       
  1089     include and exclude option), shelving remembers information if the
       
  1090     working directory was on newly created branch, in other words working
       
  1091     directory was on different branch than its first parent. In this
       
  1092     situation unshelving restores branch information to the working directory.
       
  1093 
       
  1094     Each shelved change has a name that makes it easier to find later.
       
  1095     The name of a shelved change defaults to being based on the active
       
  1096     bookmark, or if there is no active bookmark, the current named
       
  1097     branch.  To specify a different name, use ``--name``.
       
  1098 
       
  1099     To see a list of existing shelved changes, use the ``--list``
       
  1100     option. For each shelved change, this will print its name, age,
       
  1101     and description; use ``--patch`` or ``--stat`` for more details.
       
  1102 
       
  1103     To delete specific shelved changes, use ``--delete``. To delete
       
  1104     all shelved changes, use ``--cleanup``.
       
  1105     '''
       
  1106     opts = pycompat.byteskwargs(opts)
       
  1107     allowables = [
       
  1108         ('addremove', {'create'}), # 'create' is pseudo action
       
  1109         ('unknown', {'create'}),
       
  1110         ('cleanup', {'cleanup'}),
       
  1111 #       ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
       
  1112         ('delete', {'delete'}),
       
  1113         ('edit', {'create'}),
       
  1114         ('keep', {'create'}),
       
  1115         ('list', {'list'}),
       
  1116         ('message', {'create'}),
       
  1117         ('name', {'create'}),
       
  1118         ('patch', {'patch', 'list'}),
       
  1119         ('stat', {'stat', 'list'}),
       
  1120     ]
       
  1121     def checkopt(opt):
       
  1122         if opts.get(opt):
       
  1123             for i, allowable in allowables:
       
  1124                 if opts[i] and opt not in allowable:
       
  1125                     raise error.Abort(_("options '--%s' and '--%s' may not be "
       
  1126                                        "used together") % (opt, i))
       
  1127             return True
       
  1128     if checkopt('cleanup'):
       
  1129         if pats:
       
  1130             raise error.Abort(_("cannot specify names when using '--cleanup'"))
       
  1131         return cleanupcmd(ui, repo)
       
  1132     elif checkopt('delete'):
       
  1133         return deletecmd(ui, repo, pats)
       
  1134     elif checkopt('list'):
       
  1135         return listcmd(ui, repo, pats, opts)
       
  1136     elif checkopt('patch') or checkopt('stat'):
       
  1137         return patchcmds(ui, repo, pats, opts)
       
  1138     else:
       
  1139         return createcmd(ui, repo, pats, opts)
       
  1140 
       
  1141 def extsetup(ui):
       
  1142     cmdutil.unfinishedstates.append(
       
  1143         [shelvedstate._filename, False, False,
       
  1144          _('unshelve already in progress'),
       
  1145          _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
       
  1146     cmdutil.afterresolvedstates.append(
       
  1147         [shelvedstate._filename, _('hg unshelve --continue')])