hgext/shelve.py
changeset 19854 49d4919d21c2
child 19855 a3b285882724
equal deleted inserted replaced
19853:eddc2a2d57e6 19854:49d4919d21c2
       
     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 
       
    24 try:
       
    25     import cPickle as pickle
       
    26     pickle.dump # import now
       
    27 except ImportError:
       
    28     import pickle
       
    29 from mercurial.i18n import _
       
    30 from mercurial.node import nullid
       
    31 from mercurial import changegroup, cmdutil, scmutil, phases
       
    32 from mercurial import error, hg, mdiff, merge, patch, repair, util
       
    33 from mercurial import templatefilters
       
    34 from mercurial import lock as lockmod
       
    35 import errno
       
    36 
       
    37 cmdtable = {}
       
    38 command = cmdutil.command(cmdtable)
       
    39 testedwith = 'internal'
       
    40 
       
    41 class shelvedfile(object):
       
    42     """Handles common functions on shelve files (.hg/.files/.patch) using
       
    43     the vfs layer"""
       
    44     def __init__(self, repo, name, filetype=None):
       
    45         self.repo = repo
       
    46         self.name = name
       
    47         self.vfs = scmutil.vfs(repo.join('shelved'))
       
    48         if filetype:
       
    49             self.fname = name + '.' + filetype
       
    50         else:
       
    51             self.fname = name
       
    52 
       
    53     def exists(self):
       
    54         return self.vfs.exists(self.fname)
       
    55 
       
    56     def filename(self):
       
    57         return self.vfs.join(self.fname)
       
    58 
       
    59     def unlink(self):
       
    60         util.unlink(self.filename())
       
    61 
       
    62     def stat(self):
       
    63         return self.vfs.stat(self.fname)
       
    64 
       
    65     def opener(self, mode='rb'):
       
    66         try:
       
    67             return self.vfs(self.fname, mode)
       
    68         except IOError, err:
       
    69             if err.errno != errno.ENOENT:
       
    70                 raise
       
    71             if mode[0] in 'wa':
       
    72                 try:
       
    73                     self.vfs.mkdir()
       
    74                     return self.vfs(self.fname, mode)
       
    75                 except IOError, err:
       
    76                     if err.errno != errno.EEXIST:
       
    77                         raise
       
    78             elif mode[0] == 'r':
       
    79                 raise util.Abort(_("shelved change '%s' not found") %
       
    80                                  self.name)
       
    81 
       
    82 class shelvedstate(object):
       
    83     """Handles saving and restoring a shelved state. Ensures that different
       
    84     versions of a shelved state are possible and handles them appropriate"""
       
    85     _version = 1
       
    86     _filename = 'shelvedstate'
       
    87 
       
    88     @classmethod
       
    89     def load(cls, repo):
       
    90         fp = repo.opener(cls._filename)
       
    91         (version, name, parents, stripnodes) = pickle.load(fp)
       
    92 
       
    93         if version != cls._version:
       
    94             raise util.Abort(_('this version of shelve is incompatible '
       
    95                                'with the version used in this repo'))
       
    96 
       
    97         obj = cls()
       
    98         obj.name = name
       
    99         obj.parents = parents
       
   100         obj.stripnodes = stripnodes
       
   101 
       
   102         return obj
       
   103 
       
   104     @classmethod
       
   105     def save(cls, repo, name, stripnodes):
       
   106         fp = repo.opener(cls._filename, 'wb')
       
   107         pickle.dump((cls._version, name,
       
   108                      repo.dirstate.parents(),
       
   109                      stripnodes), fp)
       
   110         fp.close()
       
   111 
       
   112     @staticmethod
       
   113     def clear(repo):
       
   114         util.unlinkpath(repo.join('shelvedstate'), ignoremissing=True)
       
   115 
       
   116 def createcmd(ui, repo, pats, opts):
       
   117     def publicancestors(ctx):
       
   118         """Compute the heads of the public ancestors of a commit.
       
   119 
       
   120         Much faster than the revset heads(ancestors(ctx) - draft())"""
       
   121         seen = set()
       
   122         visit = util.deque()
       
   123         visit.append(ctx)
       
   124         while visit:
       
   125             ctx = visit.popleft()
       
   126             for parent in ctx.parents():
       
   127                 rev = parent.rev()
       
   128                 if rev not in seen:
       
   129                     seen.add(rev)
       
   130                     if parent.mutable():
       
   131                         visit.append(parent)
       
   132                     else:
       
   133                         yield parent.node()
       
   134 
       
   135     wctx = repo[None]
       
   136     parents = wctx.parents()
       
   137     if len(parents) > 1:
       
   138         raise util.Abort(_('cannot shelve while merging'))
       
   139     parent = parents[0]
       
   140 
       
   141     # we never need the user, so we use a generic user for all shelve operations
       
   142     user = 'shelve@localhost'
       
   143     label = repo._bookmarkcurrent or parent.branch() or 'default'
       
   144 
       
   145     # slashes aren't allowed in filenames, therefore we rename it
       
   146     origlabel, label = label, label.replace('/', '_')
       
   147 
       
   148     def gennames():
       
   149         yield label
       
   150         for i in xrange(1, 100):
       
   151             yield '%s-%02d' % (label, i)
       
   152 
       
   153     shelvedfiles = []
       
   154 
       
   155     def commitfunc(ui, repo, message, match, opts):
       
   156         # check modified, added, removed, deleted only
       
   157         for flist in repo.status(match=match)[:4]:
       
   158             shelvedfiles.extend(flist)
       
   159         return repo.commit(message, user, opts.get('date'), match)
       
   160 
       
   161     if parent.node() != nullid:
       
   162         desc = parent.description().split('\n', 1)[0]
       
   163         desc = _('shelved from %s (%s): %s') % (label, str(parent)[:8], desc)
       
   164     else:
       
   165         desc = '(empty repository)'
       
   166 
       
   167     if not opts['message']:
       
   168         opts['message'] = desc
       
   169 
       
   170     name = opts['name']
       
   171 
       
   172     wlock = lock = tr = None
       
   173     try:
       
   174         wlock = repo.wlock()
       
   175         lock = repo.lock()
       
   176 
       
   177         # use an uncommited transaction to generate the bundle to avoid
       
   178         # pull races. ensure we don't print the abort message to stderr.
       
   179         tr = repo.transaction('commit', report=lambda x: None)
       
   180 
       
   181         if name:
       
   182             if shelvedfile(repo, name, 'hg').exists():
       
   183                 raise util.Abort(_("a shelved change named '%s' already exists")
       
   184                                  % name)
       
   185         else:
       
   186             for n in gennames():
       
   187                 if not shelvedfile(repo, n, 'hg').exists():
       
   188                     name = n
       
   189                     break
       
   190             else:
       
   191                 raise util.Abort(_("too many shelved changes named '%s'") %
       
   192                                  label)
       
   193 
       
   194         # ensure we are not creating a subdirectory or a hidden file
       
   195         if '/' in name or '\\' in name:
       
   196             raise util.Abort(_('shelved change names may not contain slashes'))
       
   197         if name.startswith('.'):
       
   198             raise util.Abort(_("shelved change names may not start with '.'"))
       
   199 
       
   200         node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
       
   201 
       
   202         if not node:
       
   203             stat = repo.status(match=scmutil.match(repo[None], pats, opts))
       
   204             if stat[3]:
       
   205                 ui.status(_("nothing changed (%d missing files, see "
       
   206                             "'hg status')\n") % len(stat[3]))
       
   207             else:
       
   208                 ui.status(_("nothing changed\n"))
       
   209             return 1
       
   210 
       
   211         phases.retractboundary(repo, phases.secret, [node])
       
   212 
       
   213         fp = shelvedfile(repo, name, 'files').opener('wb')
       
   214         fp.write('\0'.join(shelvedfiles))
       
   215 
       
   216         bases = list(publicancestors(repo[node]))
       
   217         cg = repo.changegroupsubset(bases, [node], 'shelve')
       
   218         changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
       
   219                                 'HG10UN')
       
   220         cmdutil.export(repo, [node],
       
   221                        fp=shelvedfile(repo, name, 'patch').opener('wb'),
       
   222                        opts=mdiff.diffopts(git=True))
       
   223 
       
   224         if ui.formatted():
       
   225             desc = util.ellipsis(desc, ui.termwidth())
       
   226         ui.status(desc + '\n')
       
   227         ui.status(_('shelved as %s\n') % name)
       
   228         hg.update(repo, parent.node())
       
   229     finally:
       
   230         if tr:
       
   231             tr.abort()
       
   232         lockmod.release(lock, wlock)
       
   233 
       
   234 def cleanupcmd(ui, repo):
       
   235     wlock = None
       
   236     try:
       
   237         wlock = repo.wlock()
       
   238         for (name, _) in repo.vfs.readdir('shelved'):
       
   239             suffix = name.rsplit('.', 1)[-1]
       
   240             if suffix in ('hg', 'files', 'patch'):
       
   241                 shelvedfile(repo, name).unlink()
       
   242     finally:
       
   243         lockmod.release(wlock)
       
   244 
       
   245 def deletecmd(ui, repo, pats):
       
   246     if not pats:
       
   247         raise util.Abort(_('no shelved changes specified!'))
       
   248     wlock = None
       
   249     try:
       
   250         wlock = repo.wlock()
       
   251         try:
       
   252             for name in pats:
       
   253                 for suffix in 'hg files patch'.split():
       
   254                     shelvedfile(repo, name, suffix).unlink()
       
   255         except OSError, err:
       
   256             if err.errno != errno.ENOENT:
       
   257                 raise
       
   258             raise util.Abort(_("shelved change '%s' not found") % name)
       
   259     finally:
       
   260         lockmod.release(wlock)
       
   261 
       
   262 def listshelves(repo):
       
   263     try:
       
   264         names = repo.vfs.readdir('shelved')
       
   265     except OSError, err:
       
   266         if err.errno != errno.ENOENT:
       
   267             raise
       
   268         return []
       
   269     info = []
       
   270     for (name, _) in names:
       
   271         pfx, sfx = name.rsplit('.', 1)
       
   272         if not pfx or sfx != 'patch':
       
   273             continue
       
   274         st = shelvedfile(repo, name).stat()
       
   275         info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
       
   276     return sorted(info, reverse=True)
       
   277 
       
   278 def listcmd(ui, repo, pats, opts):
       
   279     pats = set(pats)
       
   280     width = 80
       
   281     if not ui.plain():
       
   282         width = ui.termwidth()
       
   283     namelabel = 'shelve.newest'
       
   284     for mtime, name in listshelves(repo):
       
   285         sname = util.split(name)[1]
       
   286         if pats and sname not in pats:
       
   287             continue
       
   288         ui.write(sname, label=namelabel)
       
   289         namelabel = 'shelve.name'
       
   290         if ui.quiet:
       
   291             ui.write('\n')
       
   292             continue
       
   293         ui.write(' ' * (16 - len(sname)))
       
   294         used = 16
       
   295         age = '[%s]' % templatefilters.age(util.makedate(mtime))
       
   296         ui.write(age, label='shelve.age')
       
   297         ui.write(' ' * (18 - len(age)))
       
   298         used += 18
       
   299         fp = open(name + '.patch', 'rb')
       
   300         try:
       
   301             while True:
       
   302                 line = fp.readline()
       
   303                 if not line:
       
   304                     break
       
   305                 if not line.startswith('#'):
       
   306                     desc = line.rstrip()
       
   307                     if ui.formatted():
       
   308                         desc = util.ellipsis(desc, width - used)
       
   309                     ui.write(desc)
       
   310                     break
       
   311             ui.write('\n')
       
   312             if not (opts['patch'] or opts['stat']):
       
   313                 continue
       
   314             difflines = fp.readlines()
       
   315             if opts['patch']:
       
   316                 for chunk, label in patch.difflabel(iter, difflines):
       
   317                     ui.write(chunk, label=label)
       
   318             if opts['stat']:
       
   319                 for chunk, label in patch.diffstatui(difflines, width=width,
       
   320                                                      git=True):
       
   321                     ui.write(chunk, label=label)
       
   322         finally:
       
   323             fp.close()
       
   324 
       
   325 def readshelvedfiles(repo, basename):
       
   326     fp = shelvedfile(repo, basename, 'files').opener()
       
   327     return fp.read().split('\0')
       
   328 
       
   329 def checkparents(repo, state):
       
   330     if state.parents != repo.dirstate.parents():
       
   331         raise util.Abort(_('working directory parents do not match unshelve '
       
   332                            'state'))
       
   333 
       
   334 def unshelveabort(ui, repo, state, opts):
       
   335     wlock = repo.wlock()
       
   336     lock = None
       
   337     try:
       
   338         checkparents(repo, state)
       
   339         lock = repo.lock()
       
   340         merge.mergestate(repo).reset()
       
   341         if opts['keep']:
       
   342             repo.setparents(repo.dirstate.parents()[0])
       
   343         else:
       
   344             revertfiles = readshelvedfiles(repo, state.name)
       
   345             wctx = repo.parents()[0]
       
   346             cmdutil.revert(ui, repo, wctx, [wctx.node(), nullid],
       
   347                            *revertfiles, no_backup=True)
       
   348             # fix up the weird dirstate states the merge left behind
       
   349             mf = wctx.manifest()
       
   350             dirstate = repo.dirstate
       
   351             for f in revertfiles:
       
   352                 if f in mf:
       
   353                     dirstate.normallookup(f)
       
   354                 else:
       
   355                     dirstate.drop(f)
       
   356             dirstate._pl = (wctx.node(), nullid)
       
   357             dirstate._dirty = True
       
   358         repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
       
   359         shelvedstate.clear(repo)
       
   360         ui.warn(_("unshelve of '%s' aborted\n") % state.name)
       
   361     finally:
       
   362         lockmod.release(lock, wlock)
       
   363 
       
   364 def unshelvecleanup(ui, repo, name, opts):
       
   365     if not opts['keep']:
       
   366         for filetype in 'hg files patch'.split():
       
   367             shelvedfile(repo, name, filetype).unlink()
       
   368 
       
   369 def finishmerge(ui, repo, ms, stripnodes, name, opts):
       
   370     # Reset the working dir so it's no longer in a merge state.
       
   371     dirstate = repo.dirstate
       
   372     for f in ms:
       
   373         if dirstate[f] == 'm':
       
   374             dirstate.normallookup(f)
       
   375     dirstate._pl = (dirstate._pl[0], nullid)
       
   376     dirstate._dirty = dirstate._dirtypl = True
       
   377     shelvedstate.clear(repo)
       
   378 
       
   379 def unshelvecontinue(ui, repo, state, opts):
       
   380     # We're finishing off a merge. First parent is our original
       
   381     # parent, second is the temporary "fake" commit we're unshelving.
       
   382     wlock = repo.wlock()
       
   383     lock = None
       
   384     try:
       
   385         checkparents(repo, state)
       
   386         ms = merge.mergestate(repo)
       
   387         if [f for f in ms if ms[f] == 'u']:
       
   388             raise util.Abort(
       
   389                 _("unresolved conflicts, can't continue"),
       
   390                 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
       
   391         finishmerge(ui, repo, ms, state.stripnodes, state.name, opts)
       
   392         lock = repo.lock()
       
   393         repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
       
   394         unshelvecleanup(ui, repo, state.name, opts)
       
   395         ui.status(_("unshelve of '%s' complete\n") % state.name)
       
   396     finally:
       
   397         lockmod.release(lock, wlock)
       
   398 
       
   399 @command('unshelve',
       
   400          [('a', 'abort', None,
       
   401            _('abort an incomplete unshelve operation')),
       
   402           ('c', 'continue', None,
       
   403            _('continue an incomplete unshelve operation')),
       
   404           ('', 'keep', None,
       
   405            _('keep shelve after unshelving'))],
       
   406          _('hg unshelve [SHELVED]'))
       
   407 def unshelve(ui, repo, *shelved, **opts):
       
   408     """restore a shelved change to the working directory
       
   409 
       
   410     This command accepts an optional name of a shelved change to
       
   411     restore. If none is given, the most recent shelved change is used.
       
   412 
       
   413     If a shelved change is applied successfully, the bundle that
       
   414     contains the shelved changes is deleted afterwards.
       
   415 
       
   416     Since you can restore a shelved change on top of an arbitrary
       
   417     commit, it is possible that unshelving will result in a conflict
       
   418     between your changes and the commits you are unshelving onto. If
       
   419     this occurs, you must resolve the conflict, then use
       
   420     ``--continue`` to complete the unshelve operation. (The bundle
       
   421     will not be deleted until you successfully complete the unshelve.)
       
   422 
       
   423     (Alternatively, you can use ``--abort`` to abandon an unshelve
       
   424     that causes a conflict. This reverts the unshelved changes, and
       
   425     does not delete the bundle.)
       
   426     """
       
   427     abortf = opts['abort']
       
   428     continuef = opts['continue']
       
   429     if not abortf and not continuef:
       
   430         cmdutil.checkunfinished(repo)
       
   431 
       
   432     if abortf or continuef:
       
   433         if abortf and continuef:
       
   434             raise util.Abort(_('cannot use both abort and continue'))
       
   435         if shelved:
       
   436             raise util.Abort(_('cannot combine abort/continue with '
       
   437                                'naming a shelved change'))
       
   438 
       
   439         try:
       
   440             state = shelvedstate.load(repo)
       
   441         except IOError, err:
       
   442             if err.errno != errno.ENOENT:
       
   443                 raise
       
   444             raise util.Abort(_('no unshelve operation underway'))
       
   445 
       
   446         if abortf:
       
   447             return unshelveabort(ui, repo, state, opts)
       
   448         elif continuef:
       
   449             return unshelvecontinue(ui, repo, state, opts)
       
   450     elif len(shelved) > 1:
       
   451         raise util.Abort(_('can only unshelve one change at a time'))
       
   452     elif not shelved:
       
   453         shelved = listshelves(repo)
       
   454         if not shelved:
       
   455             raise util.Abort(_('no shelved changes to apply!'))
       
   456         basename = util.split(shelved[0][1])[1]
       
   457         ui.status(_("unshelving change '%s'\n") % basename)
       
   458     else:
       
   459         basename = shelved[0]
       
   460 
       
   461     shelvedfiles = readshelvedfiles(repo, basename)
       
   462 
       
   463     m, a, r, d = repo.status()[:4]
       
   464     unsafe = set(m + a + r + d).intersection(shelvedfiles)
       
   465     if unsafe:
       
   466         ui.warn(_('the following shelved files have been modified:\n'))
       
   467         for f in sorted(unsafe):
       
   468             ui.warn('  %s\n' % f)
       
   469         ui.warn(_('you must commit, revert, or shelve your changes before you '
       
   470                   'can proceed\n'))
       
   471         raise util.Abort(_('cannot unshelve due to local changes\n'))
       
   472 
       
   473     wlock = lock = tr = None
       
   474     try:
       
   475         lock = repo.lock()
       
   476 
       
   477         tr = repo.transaction('unshelve', report=lambda x: None)
       
   478         oldtiprev = len(repo)
       
   479         try:
       
   480             fp = shelvedfile(repo, basename, 'hg').opener()
       
   481             gen = changegroup.readbundle(fp, fp.name)
       
   482             repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
       
   483             nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
       
   484             phases.retractboundary(repo, phases.secret, nodes)
       
   485             tr.close()
       
   486         finally:
       
   487             fp.close()
       
   488 
       
   489         tip = repo['tip']
       
   490         wctx = repo['.']
       
   491         ancestor = tip.ancestor(wctx)
       
   492 
       
   493         wlock = repo.wlock()
       
   494 
       
   495         if ancestor.node() != wctx.node():
       
   496             conflicts = hg.merge(repo, tip.node(), force=True, remind=False)
       
   497             ms = merge.mergestate(repo)
       
   498             stripnodes = [repo.changelog.node(rev)
       
   499                           for rev in xrange(oldtiprev, len(repo))]
       
   500             if conflicts:
       
   501                 shelvedstate.save(repo, basename, stripnodes)
       
   502                 # Fix up the dirstate entries of files from the second
       
   503                 # parent as if we were not merging, except for those
       
   504                 # with unresolved conflicts.
       
   505                 parents = repo.parents()
       
   506                 revertfiles = set(parents[1].files()).difference(ms)
       
   507                 cmdutil.revert(ui, repo, parents[1],
       
   508                                (parents[0].node(), nullid),
       
   509                                *revertfiles, no_backup=True)
       
   510                 raise error.InterventionRequired(
       
   511                     _("unresolved conflicts (see 'hg resolve', then "
       
   512                       "'hg unshelve --continue')"))
       
   513             finishmerge(ui, repo, ms, stripnodes, basename, opts)
       
   514         else:
       
   515             parent = tip.parents()[0]
       
   516             hg.update(repo, parent.node())
       
   517             cmdutil.revert(ui, repo, tip, repo.dirstate.parents(), *tip.files(),
       
   518                            no_backup=True)
       
   519 
       
   520         prevquiet = ui.quiet
       
   521         ui.quiet = True
       
   522         try:
       
   523             repo.rollback(force=True)
       
   524         finally:
       
   525             ui.quiet = prevquiet
       
   526 
       
   527         unshelvecleanup(ui, repo, basename, opts)
       
   528     finally:
       
   529         if tr:
       
   530             tr.release()
       
   531         lockmod.release(lock, wlock)
       
   532 
       
   533 @command('shelve',
       
   534          [('A', 'addremove', None,
       
   535            _('mark new/missing files as added/removed before shelving')),
       
   536           ('', 'cleanup', None,
       
   537            _('delete all shelved changes')),
       
   538           ('', 'date', '',
       
   539            _('shelve with the specified commit date'), _('DATE')),
       
   540           ('d', 'delete', None,
       
   541            _('delete the named shelved change(s)')),
       
   542           ('l', 'list', None,
       
   543            _('list current shelves')),
       
   544           ('m', 'message', '',
       
   545            _('use text as shelve message'), _('TEXT')),
       
   546           ('n', 'name', '',
       
   547            _('use the given name for the shelved commit'), _('NAME')),
       
   548           ('p', 'patch', None,
       
   549            _('show patch')),
       
   550           ('', 'stat', None,
       
   551            _('output diffstat-style summary of changes'))],
       
   552          _('hg shelve'))
       
   553 def shelvecmd(ui, repo, *pats, **opts):
       
   554     '''save and set aside changes from the working directory
       
   555 
       
   556     Shelving takes files that "hg status" reports as not clean, saves
       
   557     the modifications to a bundle (a shelved change), and reverts the
       
   558     files so that their state in the working directory becomes clean.
       
   559 
       
   560     To restore these changes to the working directory, using "hg
       
   561     unshelve"; this will work even if you switch to a different
       
   562     commit.
       
   563 
       
   564     When no files are specified, "hg shelve" saves all not-clean
       
   565     files. If specific files or directories are named, only changes to
       
   566     those files are shelved.
       
   567 
       
   568     Each shelved change has a name that makes it easier to find later.
       
   569     The name of a shelved change defaults to being based on the active
       
   570     bookmark, or if there is no active bookmark, the current named
       
   571     branch.  To specify a different name, use ``--name``.
       
   572 
       
   573     To see a list of existing shelved changes, use the ``--list``
       
   574     option. For each shelved change, this will print its name, age,
       
   575     and description; use ``--patch`` or ``--stat`` for more details.
       
   576 
       
   577     To delete specific shelved changes, use ``--delete``. To delete
       
   578     all shelved changes, use ``--cleanup``.
       
   579     '''
       
   580     cmdutil.checkunfinished(repo)
       
   581 
       
   582     def checkopt(opt, incompatible):
       
   583         if opts[opt]:
       
   584             for i in incompatible.split():
       
   585                 if opts[i]:
       
   586                     raise util.Abort(_("options '--%s' and '--%s' may not be "
       
   587                                        "used together") % (opt, i))
       
   588             return True
       
   589     if checkopt('cleanup', 'addremove delete list message name patch stat'):
       
   590         if pats:
       
   591             raise util.Abort(_("cannot specify names when using '--cleanup'"))
       
   592         return cleanupcmd(ui, repo)
       
   593     elif checkopt('delete', 'addremove cleanup list message name patch stat'):
       
   594         return deletecmd(ui, repo, pats)
       
   595     elif checkopt('list', 'addremove cleanup delete message name'):
       
   596         return listcmd(ui, repo, pats, opts)
       
   597     else:
       
   598         for i in ('patch', 'stat'):
       
   599             if opts[i]:
       
   600                 raise util.Abort(_("option '--%s' may not be "
       
   601                                    "used when shelving a change") % (i,))
       
   602         return createcmd(ui, repo, pats, opts)
       
   603 
       
   604 def extsetup(ui):
       
   605     cmdutil.unfinishedstates.append(
       
   606         [shelvedstate._filename, False, True, _('unshelve already in progress'),
       
   607          _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])