shelve.py
1058 lines
| 39.1 KiB
| text/x-python
|
PythonLexer
/ hgext / shelve.py
|
r19854 | # shelve.py - save/restore working directory state | ||
# | ||||
# Copyright 2013 Facebook, Inc. | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
"""save and restore changes to the working directory | ||||
The "hg shelve" command saves changes made to the working directory | ||||
and reverts those changes, resetting the working directory to a clean | ||||
state. | ||||
Later on, the "hg unshelve" command restores the changes saved by "hg | ||||
shelve". Changes can be restored even after updating to a different | ||||
parent, in which case Mercurial's merge machinery will resolve any | ||||
conflicts if necessary. | ||||
You can have more than one shelved change outstanding at a time; each | ||||
shelved change has a distinct name. For details, see the help for "hg | ||||
shelve". | ||||
""" | ||||
|
r28378 | from __future__ import absolute_import | ||
|
r19854 | |||
|
r25113 | import collections | ||
|
r28378 | import errno | ||
|
r25712 | import itertools | ||
|
r29205 | |||
from mercurial.i18n import _ | ||||
|
r28378 | from mercurial import ( | ||
|
r31664 | bookmarks, | ||
|
r28378 | bundle2, | ||
bundlerepo, | ||||
changegroup, | ||||
cmdutil, | ||||
|
r34099 | discovery, | ||
|
r28378 | error, | ||
exchange, | ||||
hg, | ||||
lock as lockmod, | ||||
mdiff, | ||||
merge, | ||||
node as nodemod, | ||||
patch, | ||||
phases, | ||||
|
r35006 | pycompat, | ||
|
r32337 | registrar, | ||
|
r28378 | repair, | ||
scmutil, | ||||
templatefilters, | ||||
util, | ||||
|
r31244 | vfs as vfsmod, | ||
|
r28378 | ) | ||
from . import ( | ||||
rebase, | ||||
) | ||||
|
r19854 | |||
cmdtable = {} | ||||
|
r32337 | command = registrar.command(cmdtable) | ||
|
r29841 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||
|
r25186 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||
# be specifying the version(s) of Mercurial they are tested with, or | ||||
# leave the attribute unspecified. | ||||
|
r29841 | testedwith = 'ships-with-hg-core' | ||
|
r19854 | |||
|
r34497 | configtable = {} | ||
configitem = registrar.configitem(configtable) | ||||
configitem('shelve', 'maxbackups', | ||||
default=10, | ||||
) | ||||
|
r25713 | backupdir = 'shelve-backup' | ||
|
r28862 | shelvedir = 'shelved' | ||
|
r31554 | shelvefileextensions = ['hg', 'patch', 'oshelve'] | ||
|
r30554 | # universal extension is present in all types of shelves | ||
patchextension = 'patch' | ||||
|
r25713 | |||
|
r30381 | # we never need the user, so we use a | ||
# generic user for all shelve operations | ||||
shelveuser = 'shelve@localhost' | ||||
|
r25713 | |||
|
r19854 | class shelvedfile(object): | ||
|
r19909 | """Helper for the file storing a single shelve | ||
|
r22581 | Handles common functions on shelve files (.hg/.patch) using | ||
|
r19854 | the vfs layer""" | ||
def __init__(self, repo, name, filetype=None): | ||||
self.repo = repo | ||||
self.name = name | ||||
|
r31335 | self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) | ||
self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | ||||
|
r23895 | self.ui = self.repo.ui | ||
|
r19854 | if filetype: | ||
self.fname = name + '.' + filetype | ||||
else: | ||||
self.fname = name | ||||
def exists(self): | ||||
return self.vfs.exists(self.fname) | ||||
def filename(self): | ||||
return self.vfs.join(self.fname) | ||||
|
r25712 | def backupfilename(self): | ||
def gennames(base): | ||||
yield base | ||||
base, ext = base.rsplit('.', 1) | ||||
for i in itertools.count(1): | ||||
yield '%s-%d.%s' % (base, i, ext) | ||||
name = self.backupvfs.join(self.fname) | ||||
for n in gennames(name): | ||||
if not self.backupvfs.exists(n): | ||||
return n | ||||
def movetobackup(self): | ||||
if not self.backupvfs.isdir(): | ||||
self.backupvfs.makedir() | ||||
util.rename(self.filename(), self.backupfilename()) | ||||
|
r19854 | |||
def stat(self): | ||||
return self.vfs.stat(self.fname) | ||||
def opener(self, mode='rb'): | ||||
try: | ||||
return self.vfs(self.fname, mode) | ||||
|
r25660 | except IOError as err: | ||
|
r19854 | if err.errno != errno.ENOENT: | ||
raise | ||||
|
r26587 | raise error.Abort(_("shelved change '%s' not found") % self.name) | ||
|
r19854 | |||
|
r20982 | def applybundle(self): | ||
fp = self.opener() | ||||
try: | ||||
|
r21064 | gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) | ||
|
r33043 | bundle2.applybundle(self.repo, gen, self.repo.currenttransaction(), | ||
source='unshelve', | ||||
url='bundle:' + self.vfs.join(self.fname), | ||||
targetphase=phases.secret) | ||||
|
r20982 | finally: | ||
fp.close() | ||||
|
r22898 | def bundlerepo(self): | ||
return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root, | ||||
self.vfs.join(self.fname)) | ||||
|
r26506 | def writebundle(self, bases, node): | ||
|
r27931 | cgversion = changegroup.safeversion(self.repo) | ||
if cgversion == '01': | ||||
btype = 'HG10BZ' | ||||
compression = None | ||||
else: | ||||
|
r26507 | btype = 'HG20' | ||
compression = 'BZ' | ||||
|
r34099 | outgoing = discovery.outgoing(self.repo, missingroots=bases, | ||
missingheads=[node]) | ||||
cg = changegroup.makechangegroup(self.repo, outgoing, cgversion, | ||||
'shelve') | ||||
|
r28666 | bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs, | ||
|
r26507 | compression=compression) | ||
|
r20983 | |||
|
r31554 | def writeobsshelveinfo(self, info): | ||
scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info) | ||||
def readobsshelveinfo(self): | ||||
return scmutil.simplekeyvaluefile(self.vfs, self.fname).read() | ||||
|
r19854 | class shelvedstate(object): | ||
|
r19911 | """Handle persistence during unshelving operations. | ||
|
r19909 | |||
Handles saving and restoring a shelved state. Ensures that different | ||||
|
r19911 | versions of a shelved state are possible and handles them appropriately. | ||
""" | ||||
|
r32285 | _version = 2 | ||
|
r19854 | _filename = 'shelvedstate' | ||
|
r30522 | _keep = 'keep' | ||
_nokeep = 'nokeep' | ||||
|
r31664 | # colon is essential to differentiate from a real bookmark name | ||
_noactivebook = ':no-active-bookmark' | ||||
|
r19854 | |||
@classmethod | ||||
|
r32285 | def _verifyandtransform(cls, d): | ||
"""Some basic shelvestate syntactic verification and transformation""" | ||||
|
r19904 | try: | ||
|
r32284 | d['originalwctx'] = nodemod.bin(d['originalwctx']) | ||
d['pendingctx'] = nodemod.bin(d['pendingctx']) | ||||
d['parents'] = [nodemod.bin(h) | ||||
for h in d['parents'].split(' ')] | ||||
d['nodestoremove'] = [nodemod.bin(h) | ||||
for h in d['nodestoremove'].split(' ')] | ||||
except (ValueError, TypeError, KeyError) as err: | ||||
raise error.CorruptedState(str(err)) | ||||
|
r32285 | @classmethod | ||
def _getversion(cls, repo): | ||||
"""Read version information from shelvestate file""" | ||||
fp = repo.vfs(cls._filename) | ||||
try: | ||||
version = int(fp.readline().strip()) | ||||
except ValueError as err: | ||||
raise error.CorruptedState(str(err)) | ||||
finally: | ||||
fp.close() | ||||
return version | ||||
@classmethod | ||||
def _readold(cls, repo): | ||||
"""Read the old position-based version of a shelvestate file""" | ||||
# Order is important, because old shelvestate file uses it | ||||
# to detemine values of fields (i.g. name is on the second line, | ||||
# originalwctx is on the third and so forth). Please do not change. | ||||
keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents', | ||||
'nodestoremove', 'branchtorestore', 'keep', 'activebook'] | ||||
# this is executed only seldomly, so it is not a big deal | ||||
# that we open this file twice | ||||
fp = repo.vfs(cls._filename) | ||||
d = {} | ||||
try: | ||||
for key in keys: | ||||
d[key] = fp.readline().strip() | ||||
finally: | ||||
fp.close() | ||||
return d | ||||
@classmethod | ||||
def load(cls, repo): | ||||
version = cls._getversion(repo) | ||||
if version < cls._version: | ||||
d = cls._readold(repo) | ||||
elif version == cls._version: | ||||
d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\ | ||||
.read(firstlinenonkeyval=True) | ||||
else: | ||||
raise error.Abort(_('this version of shelve is incompatible ' | ||||
'with the version used in this repo')) | ||||
cls._verifyandtransform(d) | ||||
|
r29536 | try: | ||
obj = cls() | ||||
|
r32284 | obj.name = d['name'] | ||
obj.wctx = repo[d['originalwctx']] | ||||
obj.pendingctx = repo[d['pendingctx']] | ||||
obj.parents = d['parents'] | ||||
obj.nodestoremove = d['nodestoremove'] | ||||
obj.branchtorestore = d.get('branchtorestore', '') | ||||
obj.keep = d.get('keep') == cls._keep | ||||
|
r31664 | obj.activebookmark = '' | ||
|
r32284 | if d.get('activebook', '') != cls._noactivebook: | ||
obj.activebookmark = d.get('activebook', '') | ||||
except (error.RepoLookupError, KeyError) as err: | ||||
|
r29536 | raise error.CorruptedState(str(err)) | ||
|
r19854 | |||
return obj | ||||
@classmethod | ||||
|
r31888 | def save(cls, repo, name, originalwctx, pendingctx, nodestoremove, | ||
|
r31664 | branchtorestore, keep=False, activebook=''): | ||
|
r32285 | info = { | ||
"name": name, | ||||
"originalwctx": nodemod.hex(originalwctx.node()), | ||||
"pendingctx": nodemod.hex(pendingctx.node()), | ||||
"parents": ' '.join([nodemod.hex(p) | ||||
for p in repo.dirstate.parents()]), | ||||
"nodestoremove": ' '.join([nodemod.hex(n) | ||||
for n in nodestoremove]), | ||||
"branchtorestore": branchtorestore, | ||||
"keep": cls._keep if keep else cls._nokeep, | ||||
"activebook": activebook or cls._noactivebook | ||||
} | ||||
scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\ | ||||
.write(info, firstline=str(cls._version)) | ||||
|
r19854 | |||
|
r19908 | @classmethod | ||
def clear(cls, repo): | ||||
|
r31311 | repo.vfs.unlinkpath(cls._filename, ignoremissing=True) | ||
|
r19854 | |||
|
r25713 | def cleanupoldbackups(repo): | ||
|
r31335 | vfs = vfsmod.vfs(repo.vfs.join(backupdir)) | ||
|
r34497 | maxbackups = repo.ui.configint('shelve', 'maxbackups') | ||
|
r30554 | hgfiles = [f for f in vfs.listdir() | ||
if f.endswith('.' + patchextension)] | ||||
|
r25713 | hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles]) | ||
|
r25774 | if 0 < maxbackups and maxbackups < len(hgfiles): | ||
bordermtime = hgfiles[-maxbackups][0] | ||||
else: | ||||
bordermtime = None | ||||
|
r25713 | for mtime, f in hgfiles[:len(hgfiles) - maxbackups]: | ||
|
r25774 | if mtime == bordermtime: | ||
# keep it, because timestamp can't decide exact order of backups | ||||
continue | ||||
|
r30554 | base = f[:-(1 + len(patchextension))] | ||
|
r30378 | for ext in shelvefileextensions: | ||
|
r31543 | vfs.tryunlink(base + '.' + ext) | ||
|
r25713 | |||
|
r31664 | def _backupactivebookmark(repo): | ||
activebookmark = repo._activebookmark | ||||
if activebookmark: | ||||
bookmarks.deactivate(repo) | ||||
return activebookmark | ||||
def _restoreactivebookmark(repo, mark): | ||||
if mark: | ||||
bookmarks.activate(repo, mark) | ||||
|
r26522 | def _aborttransaction(repo): | ||
'''Abort current transaction for shelve/unshelve, but keep dirstate | ||||
''' | ||||
|
r29270 | tr = repo.currenttransaction() | ||
|
r33440 | backupname = 'dirstate.shelve' | ||
repo.dirstate.savebackup(tr, backupname) | ||||
|
r29270 | tr.abort() | ||
|
r33440 | repo.dirstate.restorebackup(None, backupname) | ||
|
r26522 | |||
|
r19854 | def createcmd(ui, repo, pats, opts): | ||
|
r19911 | """subcommand that creates a new shelve""" | ||
|
r27834 | with repo.wlock(): | ||
|
r27198 | cmdutil.checkunfinished(repo) | ||
|
r27197 | return _docreatecmd(ui, repo, pats, opts) | ||
|
r19909 | |||
|
r30379 | def getshelvename(repo, parent, opts): | ||
"""Decide on the name this shelve is going to have""" | ||||
def gennames(): | ||||
yield label | ||||
|
r32968 | for i in itertools.count(1): | ||
|
r30379 | yield '%s-%02d' % (label, i) | ||
name = opts.get('name') | ||||
label = repo._activebookmark or parent.branch() or 'default' | ||||
# slashes aren't allowed in filenames, therefore we rename it | ||||
label = label.replace('/', '_') | ||||
|
r30671 | label = label.replace('\\', '_') | ||
# filenames must not start with '.' as it should not be hidden | ||||
if label.startswith('.'): | ||||
label = label.replace('.', '_', 1) | ||||
|
r30379 | |||
if name: | ||||
|
r30554 | if shelvedfile(repo, name, patchextension).exists(): | ||
|
r30379 | e = _("a shelved change named '%s' already exists") % name | ||
raise error.Abort(e) | ||||
|
r30671 | |||
# ensure we are not creating a subdirectory or a hidden file | ||||
if '/' in name or '\\' in name: | ||||
raise error.Abort(_('shelved change names can not contain slashes')) | ||||
if name.startswith('.'): | ||||
raise error.Abort(_("shelved change names can not start with '.'")) | ||||
|
r30379 | else: | ||
for n in gennames(): | ||||
|
r30554 | if not shelvedfile(repo, n, patchextension).exists(): | ||
|
r30379 | name = n | ||
break | ||||
return name | ||||
|
r30380 | def mutableancestors(ctx): | ||
"""return all mutable ancestors for ctx (included) | ||||
|
r19854 | |||
|
r30380 | Much faster than the revset ancestors(ctx) & draft()""" | ||
|
r32291 | seen = {nodemod.nullrev} | ||
|
r30380 | visit = collections.deque() | ||
visit.append(ctx) | ||||
while visit: | ||||
ctx = visit.popleft() | ||||
yield ctx.node() | ||||
for parent in ctx.parents(): | ||||
rev = parent.rev() | ||||
if rev not in seen: | ||||
seen.add(rev) | ||||
if parent.mutable(): | ||||
visit.append(parent) | ||||
|
r19854 | |||
|
r30381 | def getcommitfunc(extra, interactive, editor=False): | ||
def commitfunc(ui, repo, message, match, opts): | ||||
hasmq = util.safehasattr(repo, 'mq') | ||||
if hasmq: | ||||
saved, repo.mq.checkapplied = repo.mq.checkapplied, False | ||||
|
r31462 | overrides = {('phases', 'new-commit'): phases.secret} | ||
|
r30381 | try: | ||
editor_ = False | ||||
if editor: | ||||
editor_ = cmdutil.getcommiteditor(editform='shelve.shelve', | ||||
|
r35006 | **pycompat.strkwargs(opts)) | ||
|
r31462 | with repo.ui.configoverride(overrides): | ||
return repo.commit(message, shelveuser, opts.get('date'), | ||||
match, editor=editor_, extra=extra) | ||||
|
r30381 | finally: | ||
if hasmq: | ||||
repo.mq.checkapplied = saved | ||||
def interactivecommitfunc(ui, repo, *pats, **opts): | ||||
|
r35006 | opts = pycompat.byteskwargs(opts) | ||
|
r30381 | match = scmutil.match(repo['.'], pats, {}) | ||
message = opts['message'] | ||||
return commitfunc(ui, repo, message, match, opts) | ||||
return interactivecommitfunc if interactive else commitfunc | ||||
|
r19854 | |||
|
r30382 | def _nothingtoshelvemessaging(ui, repo, pats, opts): | ||
stat = repo.status(match=scmutil.match(repo[None], pats, opts)) | ||||
if stat.deleted: | ||||
ui.status(_("nothing changed (%d missing files, see " | ||||
"'hg status')\n") % len(stat.deleted)) | ||||
else: | ||||
ui.status(_("nothing changed\n")) | ||||
|
r30383 | def _shelvecreatedcommit(repo, node, name): | ||
bases = list(mutableancestors(repo[node])) | ||||
shelvedfile(repo, name, 'hg').writebundle(bases, node) | ||||
cmdutil.export(repo, [node], | ||||
|
r30554 | fp=shelvedfile(repo, name, patchextension).opener('wb'), | ||
|
r30383 | opts=mdiff.diffopts(git=True)) | ||
|
r30384 | def _includeunknownfiles(repo, pats, opts, extra): | ||
s = repo.status(match=scmutil.match(repo[None], pats, opts), | ||||
unknown=True) | ||||
if s.unknown: | ||||
extra['shelve_unknown'] = '\0'.join(s.unknown) | ||||
repo[None].add(s.unknown) | ||||
|
r30385 | def _finishshelve(repo): | ||
_aborttransaction(repo) | ||||
|
r30380 | def _docreatecmd(ui, repo, pats, opts): | ||
|
r19854 | wctx = repo[None] | ||
parents = wctx.parents() | ||||
if len(parents) > 1: | ||||
|
r26587 | raise error.Abort(_('cannot shelve while merging')) | ||
|
r19854 | parent = parents[0] | ||
|
r28571 | origbranch = wctx.branch() | ||
|
r19854 | |||
|
r28378 | if parent.node() != nodemod.nullid: | ||
|
r27092 | desc = "changes to: %s" % parent.description().split('\n', 1)[0] | ||
|
r19854 | else: | ||
|
r20411 | desc = '(changes in empty repository)' | ||
|
r19854 | |||
|
r28401 | if not opts.get('message'): | ||
|
r19854 | opts['message'] = desc | ||
|
r31664 | lock = tr = activebookmark = None | ||
|
r19854 | try: | ||
lock = repo.lock() | ||||
|
r19951 | # use an uncommitted transaction to generate the bundle to avoid | ||
|
r19854 | # pull races. ensure we don't print the abort message to stderr. | ||
tr = repo.transaction('commit', report=lambda x: None) | ||||
|
r24477 | interactive = opts.get('interactive', False) | ||
|
r27908 | includeunknown = (opts.get('unknown', False) and | ||
not opts.get('addremove', False)) | ||||
|
r30379 | name = getshelvename(repo, parent, opts) | ||
|
r31664 | activebookmark = _backupactivebookmark(repo) | ||
|
r30384 | extra = {} | ||
|
r27908 | if includeunknown: | ||
|
r30384 | _includeunknownfiles(repo, pats, opts, extra) | ||
|
r19854 | |||
|
r28572 | if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): | ||
# In non-bare shelve we don't store newly created branch | ||||
# at bundled commit | ||||
repo.dirstate.setbranch(repo['.'].branch()) | ||||
|
r30381 | commitfunc = getcommitfunc(extra, interactive, editor=True) | ||
|
r24478 | if not interactive: | ||
node = cmdutil.commit(ui, repo, commitfunc, pats, opts) | ||||
else: | ||||
|
r30381 | node = cmdutil.dorecord(ui, repo, commitfunc, None, | ||
|
r31664 | False, cmdutil.recordfilter, *pats, | ||
|
r35006 | **pycompat.strkwargs(opts)) | ||
|
r19854 | if not node: | ||
|
r30382 | _nothingtoshelvemessaging(ui, repo, pats, opts) | ||
|
r19854 | return 1 | ||
|
r30383 | _shelvecreatedcommit(repo, node, name) | ||
|
r19874 | |||
|
r19854 | if ui.formatted(): | ||
desc = util.ellipsis(desc, ui.termwidth()) | ||||
ui.status(_('shelved as %s\n') % name) | ||||
hg.update(repo, parent.node()) | ||||
|
r28571 | if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts): | ||
repo.dirstate.setbranch(origbranch) | ||||
|
r26523 | |||
|
r30385 | _finishshelve(repo) | ||
|
r19854 | finally: | ||
|
r31664 | _restoreactivebookmark(repo, activebookmark) | ||
|
r27197 | lockmod.release(tr, lock) | ||
|
r19854 | |||
|
r28571 | def _isbareshelve(pats, opts): | ||
return (not pats | ||||
and not opts.get('interactive', False) | ||||
and not opts.get('include', False) | ||||
and not opts.get('exclude', False)) | ||||
|
r28572 | def _iswctxonnewbranch(repo): | ||
return repo[None].branch() != repo['.'].branch() | ||||
|
r19854 | def cleanupcmd(ui, repo): | ||
|
r19911 | """subcommand that deletes all shelves""" | ||
|
r19909 | |||
|
r27835 | with repo.wlock(): | ||
|
r28862 | for (name, _type) in repo.vfs.readdir(shelvedir): | ||
|
r19854 | suffix = name.rsplit('.', 1)[-1] | ||
|
r30378 | if suffix in shelvefileextensions: | ||
|
r25712 | shelvedfile(repo, name).movetobackup() | ||
|
r25713 | cleanupoldbackups(repo) | ||
|
r19854 | |||
def deletecmd(ui, repo, pats): | ||||
|
r19911 | """subcommand that deletes a specific shelve""" | ||
|
r19854 | if not pats: | ||
|
r26587 | raise error.Abort(_('no shelved changes specified!')) | ||
|
r27836 | with repo.wlock(): | ||
try: | ||||
for name in pats: | ||||
|
r30378 | for suffix in shelvefileextensions: | ||
shfile = shelvedfile(repo, name, suffix) | ||||
# patch file is necessary, as it should | ||||
# be present for any kind of shelve, | ||||
# but the .hg file is optional as in future we | ||||
# will add obsolete shelve with does not create a | ||||
# bundle | ||||
|
r30554 | if shfile.exists() or suffix == patchextension: | ||
|
r30378 | shfile.movetobackup() | ||
|
r27836 | cleanupoldbackups(repo) | ||
except OSError as err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
raise error.Abort(_("shelved change '%s' not found") % name) | ||||
|
r19854 | |||
def listshelves(repo): | ||||
|
r19911 | """return all shelves in repo as list of (time, filename)""" | ||
|
r19854 | try: | ||
|
r28862 | names = repo.vfs.readdir(shelvedir) | ||
|
r25660 | except OSError as err: | ||
|
r19854 | if err.errno != errno.ENOENT: | ||
raise | ||||
return [] | ||||
info = [] | ||||
|
r22199 | for (name, _type) in names: | ||
|
r19854 | pfx, sfx = name.rsplit('.', 1) | ||
|
r30554 | if not pfx or sfx != patchextension: | ||
|
r19854 | continue | ||
st = shelvedfile(repo, name).stat() | ||||
info.append((st.st_mtime, shelvedfile(repo, pfx).filename())) | ||||
return sorted(info, reverse=True) | ||||
def listcmd(ui, repo, pats, opts): | ||||
|
r19911 | """subcommand that displays the list of shelves""" | ||
|
r19854 | pats = set(pats) | ||
width = 80 | ||||
if not ui.plain(): | ||||
width = ui.termwidth() | ||||
namelabel = 'shelve.newest' | ||||
|
r31096 | ui.pager('shelve') | ||
|
r19854 | for mtime, name in listshelves(repo): | ||
sname = util.split(name)[1] | ||||
if pats and sname not in pats: | ||||
continue | ||||
ui.write(sname, label=namelabel) | ||||
namelabel = 'shelve.name' | ||||
if ui.quiet: | ||||
ui.write('\n') | ||||
continue | ||||
ui.write(' ' * (16 - len(sname))) | ||||
used = 16 | ||||
|
r19855 | age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True) | ||
|
r19854 | ui.write(age, label='shelve.age') | ||
|
r19855 | ui.write(' ' * (12 - len(age))) | ||
used += 12 | ||||
|
r30554 | with open(name + '.' + patchextension, 'rb') as fp: | ||
|
r19854 | while True: | ||
line = fp.readline() | ||||
if not line: | ||||
break | ||||
if not line.startswith('#'): | ||||
desc = line.rstrip() | ||||
if ui.formatted(): | ||||
desc = util.ellipsis(desc, width - used) | ||||
ui.write(desc) | ||||
break | ||||
ui.write('\n') | ||||
if not (opts['patch'] or opts['stat']): | ||||
continue | ||||
difflines = fp.readlines() | ||||
if opts['patch']: | ||||
for chunk, label in patch.difflabel(iter, difflines): | ||||
ui.write(chunk, label=label) | ||||
if opts['stat']: | ||||
|
r30407 | for chunk, label in patch.diffstatui(difflines, width=width): | ||
|
r19854 | ui.write(chunk, label=label) | ||
|
r30823 | def patchcmds(ui, repo, pats, opts, subcommand): | ||
"""subcommand that displays shelves""" | ||||
if len(pats) == 0: | ||||
raise error.Abort(_("--%s expects at least one shelf") % subcommand) | ||||
|
r25104 | |||
|
r30823 | for shelfname in pats: | ||
if not shelvedfile(repo, shelfname, patchextension).exists(): | ||||
raise error.Abort(_("cannot find shelf %s") % shelfname) | ||||
|
r25104 | |||
listcmd(ui, repo, pats, opts) | ||||
|
r19854 | def checkparents(repo, state): | ||
|
r19909 | """check parent while resuming an unshelve""" | ||
|
r19854 | if state.parents != repo.dirstate.parents(): | ||
|
r26587 | raise error.Abort(_('working directory parents do not match unshelve ' | ||
|
r19854 | 'state')) | ||
|
r19943 | def pathtofiles(repo, files): | ||
cwd = repo.getcwd() | ||||
return [repo.pathto(f, cwd) for f in files] | ||||
|
r19854 | def unshelveabort(ui, repo, state, opts): | ||
|
r19909 | """subcommand that abort an in-progress unshelve""" | ||
|
r27841 | with repo.lock(): | ||
|
r19961 | try: | ||
|
r27841 | checkparents(repo, state) | ||
|
r19961 | |||
|
r31312 | repo.vfs.rename('unshelverebasestate', 'rebasestate') | ||
|
r27841 | try: | ||
rebase.rebase(ui, repo, **{ | ||||
|
r35930 | r'abort' : True | ||
|
r27841 | }) | ||
except Exception: | ||||
|
r31312 | repo.vfs.rename('rebasestate', 'unshelverebasestate') | ||
|
r27841 | raise | ||
|
r19961 | |||
|
r27841 | mergefiles(ui, repo, state.wctx, state.pendingctx) | ||
|
r31888 | repair.strip(ui, repo, state.nodestoremove, backup=False, | ||
|
r27841 | topic='shelve') | ||
finally: | ||||
shelvedstate.clear(repo) | ||||
ui.warn(_("unshelve of '%s' aborted\n") % state.name) | ||||
|
r19854 | |||
|
r20149 | def mergefiles(ui, repo, wctx, shelvectx): | ||
|
r19961 | """updates to wctx and merges the changes from shelvectx into the | ||
|
r20149 | dirstate.""" | ||
|
r31757 | with ui.configoverride({('ui', 'quiet'): True}): | ||
|
r19961 | hg.update(repo, wctx.node()) | ||
files = [] | ||||
files.extend(shelvectx.files()) | ||||
files.extend(shelvectx.parents()[0].files()) | ||||
|
r20149 | |||
# revert will overwrite unknown files, so move them out of the way | ||||
|
r22922 | for file in repo.status(unknown=True).unknown: | ||
|
r20149 | if file in files: | ||
|
r27651 | util.rename(file, scmutil.origpath(ui, repo, file)) | ||
|
r22184 | ui.pushbuffer(True) | ||
|
r19961 | cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), | ||
*pathtofiles(repo, files), | ||||
|
r35930 | **{r'no_backup': True}) | ||
|
r22184 | ui.popbuffer() | ||
|
r19961 | |||
|
r28573 | def restorebranch(ui, repo, branchtorestore): | ||
if branchtorestore and branchtorestore != repo.dirstate.branch(): | ||||
repo.dirstate.setbranch(branchtorestore) | ||||
ui.status(_('marked working directory as branch %s\n') | ||||
% branchtorestore) | ||||
|
r19854 | def unshelvecleanup(ui, repo, name, opts): | ||
|
r19911 | """remove related files after an unshelve""" | ||
|
r28401 | if not opts.get('keep'): | ||
|
r30378 | for filetype in shelvefileextensions: | ||
shfile = shelvedfile(repo, name, filetype) | ||||
if shfile.exists(): | ||||
shfile.movetobackup() | ||||
|
r25713 | cleanupoldbackups(repo) | ||
|
r19854 | |||
def unshelvecontinue(ui, repo, state, opts): | ||||
|
r19909 | """subcommand to continue an in-progress unshelve""" | ||
|
r19854 | # We're finishing off a merge. First parent is our original | ||
# parent, second is the temporary "fake" commit we're unshelving. | ||||
|
r27838 | with repo.lock(): | ||
|
r19854 | checkparents(repo, state) | ||
|
r26992 | ms = merge.mergestate.read(repo) | ||
|
r33313 | if list(ms.unresolved()): | ||
|
r26587 | raise error.Abort( | ||
|
r19854 | _("unresolved conflicts, can't continue"), | ||
hint=_("see 'hg resolve', then 'hg unshelve --continue'")) | ||||
|
r19961 | |||
|
r31312 | repo.vfs.rename('unshelverebasestate', 'rebasestate') | ||
|
r19961 | try: | ||
rebase.rebase(ui, repo, **{ | ||||
|
r35930 | r'continue' : True | ||
|
r19961 | }) | ||
except Exception: | ||||
|
r31312 | repo.vfs.rename('rebasestate', 'unshelverebasestate') | ||
|
r19961 | raise | ||
shelvectx = repo['tip'] | ||||
|
r32899 | if state.pendingctx not in shelvectx.parents(): | ||
|
r19961 | # rebase was a no-op, so it produced no child commit | ||
shelvectx = state.pendingctx | ||||
|
r22842 | else: | ||
# only strip the shelvectx if the rebase produced it | ||||
|
r31888 | state.nodestoremove.append(shelvectx.node()) | ||
|
r19961 | |||
|
r20149 | mergefiles(ui, repo, state.wctx, shelvectx) | ||
|
r28573 | restorebranch(ui, repo, state.branchtorestore) | ||
|
r19961 | |||
|
r31888 | repair.strip(ui, repo, state.nodestoremove, backup=False, | ||
topic='shelve') | ||||
|
r31664 | _restoreactivebookmark(repo, state.activebookmark) | ||
|
r19961 | shelvedstate.clear(repo) | ||
|
r19854 | unshelvecleanup(ui, repo, state.name, opts) | ||
ui.status(_("unshelve of '%s' complete\n") % state.name) | ||||
|
r30453 | def _commitworkingcopychanges(ui, repo, opts, tmpwctx): | ||
"""Temporarily commit working copy changes before moving unshelve commit""" | ||||
# Store pending changes in a commit and remember added in case a shelve | ||||
# contains unknown files that are part of the pending change | ||||
s = repo.status() | ||||
addedbefore = frozenset(s.added) | ||||
|
r30846 | if not (s.modified or s.added or s.removed): | ||
|
r30453 | return tmpwctx, addedbefore | ||
ui.status(_("temporarily committing pending changes " | ||||
"(restore with 'hg unshelve --abort')\n")) | ||||
commitfunc = getcommitfunc(extra=None, interactive=False, | ||||
editor=False) | ||||
tempopts = {} | ||||
tempopts['message'] = "pending changes temporary commit" | ||||
tempopts['date'] = opts.get('date') | ||||
|
r31757 | with ui.configoverride({('ui', 'quiet'): True}): | ||
node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) | ||||
|
r30453 | tmpwctx = repo[node] | ||
return tmpwctx, addedbefore | ||||
|
r31757 | def _unshelverestorecommit(ui, repo, basename): | ||
|
r30454 | """Recreate commit in the repository during the unshelve""" | ||
|
r31757 | with ui.configoverride({('ui', 'quiet'): True}): | ||
shelvedfile(repo, basename, 'hg').applybundle() | ||||
shelvectx = repo['tip'] | ||||
|
r30454 | return repo, shelvectx | ||
|
r30455 | def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx, | ||
|
r31664 | tmpwctx, shelvectx, branchtorestore, | ||
activebookmark): | ||||
|
r30455 | """Rebase restored commit from its original location to a destination""" | ||
# If the shelve is not immediately on top of the commit | ||||
# we'll be merging with, rebase it to be on top. | ||||
if tmpwctx.node() == shelvectx.parents()[0].node(): | ||||
return shelvectx | ||||
ui.status(_('rebasing shelved changes\n')) | ||||
try: | ||||
rebase.rebase(ui, repo, **{ | ||||
|
r35930 | r'rev': [shelvectx.rev()], | ||
r'dest': str(tmpwctx.rev()), | ||||
r'keep': True, | ||||
r'tool': opts.get('tool', ''), | ||||
|
r30455 | }) | ||
except error.InterventionRequired: | ||||
tr.close() | ||||
|
r31888 | nodestoremove = [repo.changelog.node(rev) | ||
for rev in xrange(oldtiprev, len(repo))] | ||||
shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove, | ||||
|
r31664 | branchtorestore, opts.get('keep'), activebookmark) | ||
|
r30455 | |||
|
r31312 | repo.vfs.rename('rebasestate', 'unshelverebasestate') | ||
|
r30455 | raise error.InterventionRequired( | ||
_("unresolved conflicts (see 'hg resolve', then " | ||||
"'hg unshelve --continue')")) | ||||
# refresh ctx after rebase completes | ||||
shelvectx = repo['tip'] | ||||
|
r32899 | if tmpwctx not in shelvectx.parents(): | ||
|
r30455 | # rebase was a no-op, so it produced no child commit | ||
shelvectx = tmpwctx | ||||
return shelvectx | ||||
|
r30456 | def _forgetunknownfiles(repo, shelvectx, addedbefore): | ||
# Forget any files that were unknown before the shelve, unknown before | ||||
# unshelve started, but are now added. | ||||
shelveunknown = shelvectx.extra().get('shelve_unknown') | ||||
if not shelveunknown: | ||||
return | ||||
shelveunknown = frozenset(shelveunknown.split('\0')) | ||||
addedafter = frozenset(repo.status().added) | ||||
toforget = (addedafter & shelveunknown) - addedbefore | ||||
repo[None].forget(toforget) | ||||
|
r31664 | def _finishunshelve(repo, oldtiprev, tr, activebookmark): | ||
_restoreactivebookmark(repo, activebookmark) | ||||
|
r30457 | # The transaction aborting will strip all the commits for us, | ||
# but it doesn't update the inmemory structures, so addchangegroup | ||||
# hooks still fire and try to operate on the missing commits. | ||||
# Clean up manually to prevent this. | ||||
repo.unfiltered().changelog.strip(oldtiprev, tr) | ||||
_aborttransaction(repo) | ||||
|
r30846 | def _checkunshelveuntrackedproblems(ui, repo, shelvectx): | ||
"""Check potential problems which may result from working | ||||
copy having untracked changes.""" | ||||
wcdeleted = set(repo.status().deleted) | ||||
shelvetouched = set(shelvectx.files()) | ||||
intersection = wcdeleted.intersection(shelvetouched) | ||||
if intersection: | ||||
m = _("shelved change touches missing files") | ||||
hint = _("run hg status to see which files are missing") | ||||
raise error.Abort(m, hint=hint) | ||||
|
r19854 | @command('unshelve', | ||
[('a', 'abort', None, | ||||
_('abort an incomplete unshelve operation')), | ||||
('c', 'continue', None, | ||||
_('continue an incomplete unshelve operation')), | ||||
|
r27019 | ('k', 'keep', None, | ||
|
r20960 | _('keep shelve after unshelving')), | ||
|
r31021 | ('n', 'name', '', | ||
_('restore shelved change with given name'), _('NAME')), | ||||
|
r27021 | ('t', 'tool', '', _('specify merge tool')), | ||
|
r20960 | ('', 'date', '', | ||
_('set date for temporary commits (DEPRECATED)'), _('DATE'))], | ||||
|
r31021 | _('hg unshelve [[-n] SHELVED]')) | ||
|
r19854 | def unshelve(ui, repo, *shelved, **opts): | ||
"""restore a shelved change to the working directory | ||||
This command accepts an optional name of a shelved change to | ||||
restore. If none is given, the most recent shelved change is used. | ||||
If a shelved change is applied successfully, the bundle that | ||||
|
r25712 | contains the shelved changes is moved to a backup location | ||
(.hg/shelve-backup). | ||||
|
r19854 | |||
Since you can restore a shelved change on top of an arbitrary | ||||
commit, it is possible that unshelving will result in a conflict | ||||
between your changes and the commits you are unshelving onto. If | ||||
this occurs, you must resolve the conflict, then use | ||||
``--continue`` to complete the unshelve operation. (The bundle | ||||
|
r25712 | will not be moved until you successfully complete the unshelve.) | ||
|
r19854 | |||
(Alternatively, you can use ``--abort`` to abandon an unshelve | ||||
that causes a conflict. This reverts the unshelved changes, and | ||||
|
r25712 | leaves the bundle in place.) | ||
|
r25713 | |||
|
r28573 | If bare shelved change(when no files are specified, without interactive, | ||
include and exclude option) was done on newly created branch it would | ||||
restore branch information to the working directory. | ||||
|
r25713 | After a successful unshelve, the shelved changes are stored in a | ||
backup directory. Only the N most recent backups are kept. N | ||||
|
r25852 | defaults to 10 but can be overridden using the ``shelve.maxbackups`` | ||
|
r25713 | configuration option. | ||
|
r25774 | |||
.. container:: verbose | ||||
Timestamp in seconds is used to decide order of backups. More | ||||
than ``maxbackups`` backups are kept, if same timestamp | ||||
prevents from deciding exact order of them, for safety. | ||||
|
r19854 | """ | ||
|
r27837 | with repo.wlock(): | ||
|
r27287 | return _dounshelve(ui, repo, *shelved, **opts) | ||
def _dounshelve(ui, repo, *shelved, **opts): | ||||
|
r35006 | opts = pycompat.byteskwargs(opts) | ||
|
r28401 | abortf = opts.get('abort') | ||
continuef = opts.get('continue') | ||||
|
r19854 | if not abortf and not continuef: | ||
cmdutil.checkunfinished(repo) | ||||
|
r31021 | shelved = list(shelved) | ||
if opts.get("name"): | ||||
shelved.append(opts["name"]) | ||||
|
r19854 | |||
if abortf or continuef: | ||||
if abortf and continuef: | ||||
|
r26587 | raise error.Abort(_('cannot use both abort and continue')) | ||
|
r19854 | if shelved: | ||
|
r26587 | raise error.Abort(_('cannot combine abort/continue with ' | ||
|
r19854 | 'naming a shelved change')) | ||
|
r27021 | if abortf and opts.get('tool', False): | ||
ui.warn(_('tool option will be ignored\n')) | ||||
|
r19854 | |||
try: | ||||
state = shelvedstate.load(repo) | ||||
|
r30522 | if opts.get('keep') is None: | ||
opts['keep'] = state.keep | ||||
|
r25660 | except IOError as err: | ||
|
r19854 | if err.errno != errno.ENOENT: | ||
raise | ||||
|
r28124 | cmdutil.wrongtooltocontinue(repo, _('unshelve')) | ||
|
r29536 | except error.CorruptedState as err: | ||
ui.debug(str(err) + '\n') | ||||
if continuef: | ||||
msg = _('corrupted shelved state file') | ||||
hint = _('please run hg unshelve --abort to abort unshelve ' | ||||
'operation') | ||||
raise error.Abort(msg, hint=hint) | ||||
elif abortf: | ||||
msg = _('could not read shelved state file, your working copy ' | ||||
'may be in an unexpected state\nplease update to some ' | ||||
'commit\n') | ||||
ui.warn(msg) | ||||
shelvedstate.clear(repo) | ||||
return | ||||
|
r19854 | |||
if abortf: | ||||
return unshelveabort(ui, repo, state, opts) | ||||
elif continuef: | ||||
return unshelvecontinue(ui, repo, state, opts) | ||||
elif len(shelved) > 1: | ||||
|
r26587 | raise error.Abort(_('can only unshelve one change at a time')) | ||
|
r19854 | elif not shelved: | ||
shelved = listshelves(repo) | ||||
if not shelved: | ||||
|
r26587 | raise error.Abort(_('no shelved changes to apply!')) | ||
|
r19854 | basename = util.split(shelved[0][1])[1] | ||
ui.status(_("unshelving change '%s'\n") % basename) | ||||
else: | ||||
basename = shelved[0] | ||||
|
r30554 | if not shelvedfile(repo, basename, patchextension).exists(): | ||
|
r26587 | raise error.Abort(_("shelved change '%s' not found") % basename) | ||
|
r19854 | |||
|
r27287 | lock = tr = None | ||
|
r19854 | try: | ||
lock = repo.lock() | ||||
tr = repo.transaction('unshelve', report=lambda x: None) | ||||
oldtiprev = len(repo) | ||||
|
r19961 | |||
|
r20958 | pctx = repo['.'] | ||
tmpwctx = pctx | ||||
|
r19961 | # The goal is to have a commit structure like so: | ||
|
r20958 | # ...-> pctx -> tmpwctx -> shelvectx | ||
|
r19961 | # where tmpwctx is an optional commit with the user's pending changes | ||
# and shelvectx is the unshelved changes. Then we merge it all down | ||||
|
r20958 | # to the original pctx. | ||
|
r19961 | |||
|
r31664 | activebookmark = _backupactivebookmark(repo) | ||
|
r31462 | overrides = {('ui', 'forcemerge'): opts.get('tool', '')} | ||
with ui.configoverride(overrides, 'unshelve'): | ||||
tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, | ||||
tmpwctx) | ||||
|
r31757 | repo, shelvectx = _unshelverestorecommit(ui, repo, basename) | ||
|
r31462 | _checkunshelveuntrackedproblems(ui, repo, shelvectx) | ||
branchtorestore = '' | ||||
if shelvectx.branch() != shelvectx.p1().branch(): | ||||
branchtorestore = shelvectx.branch() | ||||
|
r27908 | |||
|
r31462 | shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, | ||
basename, pctx, tmpwctx, | ||||
|
r31664 | shelvectx, branchtorestore, | ||
activebookmark) | ||||
|
r31462 | mergefiles(ui, repo, pctx, shelvectx) | ||
restorebranch(ui, repo, branchtorestore) | ||||
_forgetunknownfiles(repo, shelvectx, addedbefore) | ||||
shelvedstate.clear(repo) | ||||
|
r31664 | _finishunshelve(repo, oldtiprev, tr, activebookmark) | ||
|
r31462 | unshelvecleanup(ui, repo, basename, opts) | ||
|
r19854 | finally: | ||
if tr: | ||||
tr.release() | ||||
|
r27287 | lockmod.release(lock) | ||
|
r19854 | |||
@command('shelve', | ||||
[('A', 'addremove', None, | ||||
_('mark new/missing files as added/removed before shelving')), | ||||
|
r27908 | ('u', 'unknown', None, | ||
|
r27921 | _('store unknown files in the shelve')), | ||
|
r19854 | ('', 'cleanup', None, | ||
_('delete all shelved changes')), | ||||
('', 'date', '', | ||||
_('shelve with the specified commit date'), _('DATE')), | ||||
('d', 'delete', None, | ||||
_('delete the named shelved change(s)')), | ||||
|
r21852 | ('e', 'edit', False, | ||
_('invoke editor on commit messages')), | ||||
|
r19854 | ('l', 'list', None, | ||
_('list current shelves')), | ||||
('m', 'message', '', | ||||
_('use text as shelve message'), _('TEXT')), | ||||
('n', 'name', '', | ||||
_('use the given name for the shelved commit'), _('NAME')), | ||||
('p', 'patch', None, | ||||
_('show patch')), | ||||
|
r24477 | ('i', 'interactive', None, | ||
|
r25260 | _('interactive mode, only works while creating a shelve')), | ||
|
r19854 | ('', 'stat', None, | ||
|
r32375 | _('output diffstat-style summary of changes'))] + cmdutil.walkopts, | ||
|
r20410 | _('hg shelve [OPTION]... [FILE]...')) | ||
|
r19854 | def shelvecmd(ui, repo, *pats, **opts): | ||
'''save and set aside changes from the working directory | ||||
Shelving takes files that "hg status" reports as not clean, saves | ||||
the modifications to a bundle (a shelved change), and reverts the | ||||
files so that their state in the working directory becomes clean. | ||||
To restore these changes to the working directory, using "hg | ||||
unshelve"; this will work even if you switch to a different | ||||
commit. | ||||
When no files are specified, "hg shelve" saves all not-clean | ||||
files. If specific files or directories are named, only changes to | ||||
those files are shelved. | ||||
|
r30419 | In bare shelve (when no files are specified, without interactive, | ||
|
r28573 | include and exclude option), shelving remembers information if the | ||
working directory was on newly created branch, in other words working | ||||
directory was on different branch than its first parent. In this | ||||
situation unshelving restores branch information to the working directory. | ||||
|
r19854 | Each shelved change has a name that makes it easier to find later. | ||
The name of a shelved change defaults to being based on the active | ||||
bookmark, or if there is no active bookmark, the current named | ||||
branch. To specify a different name, use ``--name``. | ||||
To see a list of existing shelved changes, use the ``--list`` | ||||
option. For each shelved change, this will print its name, age, | ||||
and description; use ``--patch`` or ``--stat`` for more details. | ||||
To delete specific shelved changes, use ``--delete``. To delete | ||||
all shelved changes, use ``--cleanup``. | ||||
''' | ||||
|
r35006 | opts = pycompat.byteskwargs(opts) | ||
|
r21851 | allowables = [ | ||
|
r32291 | ('addremove', {'create'}), # 'create' is pseudo action | ||
('unknown', {'create'}), | ||||
('cleanup', {'cleanup'}), | ||||
# ('date', {'create'}), # ignored for passing '--date "0 0"' in tests | ||||
('delete', {'delete'}), | ||||
('edit', {'create'}), | ||||
('list', {'list'}), | ||||
('message', {'create'}), | ||||
('name', {'create'}), | ||||
('patch', {'patch', 'list'}), | ||||
('stat', {'stat', 'list'}), | ||||
|
r21851 | ] | ||
def checkopt(opt): | ||||
|
r28401 | if opts.get(opt): | ||
|
r21851 | for i, allowable in allowables: | ||
|
r25103 | if opts[i] and opt not in allowable: | ||
|
r26587 | raise error.Abort(_("options '--%s' and '--%s' may not be " | ||
|
r19854 | "used together") % (opt, i)) | ||
return True | ||||
|
r21851 | if checkopt('cleanup'): | ||
|
r19854 | if pats: | ||
|
r26587 | raise error.Abort(_("cannot specify names when using '--cleanup'")) | ||
|
r19854 | return cleanupcmd(ui, repo) | ||
|
r21851 | elif checkopt('delete'): | ||
|
r19854 | return deletecmd(ui, repo, pats) | ||
|
r21851 | elif checkopt('list'): | ||
|
r19854 | return listcmd(ui, repo, pats, opts) | ||
|
r25104 | elif checkopt('patch'): | ||
|
r30823 | return patchcmds(ui, repo, pats, opts, subcommand='patch') | ||
|
r25104 | elif checkopt('stat'): | ||
|
r30823 | return patchcmds(ui, repo, pats, opts, subcommand='stat') | ||
|
r19854 | else: | ||
return createcmd(ui, repo, pats, opts) | ||||
def extsetup(ui): | ||||
cmdutil.unfinishedstates.append( | ||||
|
r19963 | [shelvedstate._filename, False, False, | ||
_('unshelve already in progress'), | ||||
|
r19854 | _("use 'hg unshelve --continue' or 'hg unshelve --abort'")]) | ||
|
r27694 | cmdutil.afterresolvedstates.append( | ||
[shelvedstate._filename, _('hg unshelve --continue')]) | ||||