shelve.py
725 lines
| 25.5 KiB
| text/x-python
|
PythonLexer
/ hgext / shelve.py
David Soria Parra
|
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". | ||||
""" | ||||
from mercurial.i18n import _ | ||||
Mads Kiilerich
|
r20407 | from mercurial.node import nullid, nullrev, bin, hex | ||
Mads Kiilerich
|
r20409 | from mercurial import changegroup, cmdutil, scmutil, phases, commands | ||
David Soria Parra
|
r19854 | from mercurial import error, hg, mdiff, merge, patch, repair, util | ||
Mads Kiilerich
|
r22202 | from mercurial import templatefilters, exchange | ||
David Soria Parra
|
r19854 | from mercurial import lock as lockmod | ||
Durham Goode
|
r19961 | from hgext import rebase | ||
David Soria Parra
|
r19854 | import errno | ||
cmdtable = {} | ||||
command = cmdutil.command(cmdtable) | ||||
testedwith = 'internal' | ||||
class shelvedfile(object): | ||||
Pierre-Yves David
|
r19909 | """Helper for the file storing a single shelve | ||
Handles common functions on shelve files (.hg/.files/.patch) using | ||||
David Soria Parra
|
r19854 | the vfs layer""" | ||
def __init__(self, repo, name, filetype=None): | ||||
self.repo = repo | ||||
self.name = name | ||||
self.vfs = scmutil.vfs(repo.join('shelved')) | ||||
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) | ||||
def unlink(self): | ||||
util.unlink(self.filename()) | ||||
def stat(self): | ||||
return self.vfs.stat(self.fname) | ||||
def opener(self, mode='rb'): | ||||
try: | ||||
return self.vfs(self.fname, mode) | ||||
except IOError, err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
FUJIWARA Katsunori
|
r19964 | raise util.Abort(_("shelved change '%s' not found") % self.name) | ||
David Soria Parra
|
r19854 | |||
FUJIWARA Katsunori
|
r20982 | def applybundle(self): | ||
fp = self.opener() | ||||
try: | ||||
Pierre-Yves David
|
r21064 | gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) | ||
FUJIWARA Katsunori
|
r20982 | changegroup.addchangegroup(self.repo, gen, 'unshelve', | ||
Pierre-Yves David
|
r22042 | 'bundle:' + self.vfs.join(self.fname), | ||
targetphase=phases.secret) | ||||
FUJIWARA Katsunori
|
r20982 | finally: | ||
fp.close() | ||||
FUJIWARA Katsunori
|
r20983 | def writebundle(self, cg): | ||
changegroup.writebundle(cg, self.fname, 'HG10UN', self.vfs) | ||||
David Soria Parra
|
r19854 | class shelvedstate(object): | ||
Augie Fackler
|
r19911 | """Handle persistence during unshelving operations. | ||
Pierre-Yves David
|
r19909 | |||
Handles saving and restoring a shelved state. Ensures that different | ||||
Augie Fackler
|
r19911 | versions of a shelved state are possible and handles them appropriately. | ||
""" | ||||
David Soria Parra
|
r19854 | _version = 1 | ||
_filename = 'shelvedstate' | ||||
@classmethod | ||||
def load(cls, repo): | ||||
fp = repo.opener(cls._filename) | ||||
Pierre-Yves David
|
r19904 | try: | ||
version = int(fp.readline().strip()) | ||||
David Soria Parra
|
r19854 | |||
Pierre-Yves David
|
r19904 | if version != cls._version: | ||
raise util.Abort(_('this version of shelve is incompatible ' | ||||
'with the version used in this repo')) | ||||
name = fp.readline().strip() | ||||
Durham Goode
|
r19961 | wctx = fp.readline().strip() | ||
pendingctx = fp.readline().strip() | ||||
Pierre-Yves David
|
r19904 | parents = [bin(h) for h in fp.readline().split()] | ||
stripnodes = [bin(h) for h in fp.readline().split()] | ||||
finally: | ||||
fp.close() | ||||
David Soria Parra
|
r19854 | |||
obj = cls() | ||||
obj.name = name | ||||
Durham Goode
|
r19961 | obj.wctx = repo[bin(wctx)] | ||
obj.pendingctx = repo[bin(pendingctx)] | ||||
David Soria Parra
|
r19854 | obj.parents = parents | ||
obj.stripnodes = stripnodes | ||||
return obj | ||||
@classmethod | ||||
Durham Goode
|
r20149 | def save(cls, repo, name, originalwctx, pendingctx, stripnodes): | ||
David Soria Parra
|
r19854 | fp = repo.opener(cls._filename, 'wb') | ||
Pierre-Yves David
|
r19904 | fp.write('%i\n' % cls._version) | ||
fp.write('%s\n' % name) | ||||
Durham Goode
|
r19961 | fp.write('%s\n' % hex(originalwctx.node())) | ||
fp.write('%s\n' % hex(pendingctx.node())) | ||||
Pierre-Yves David
|
r19904 | fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()])) | ||
fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes])) | ||||
David Soria Parra
|
r19854 | fp.close() | ||
Pierre-Yves David
|
r19908 | @classmethod | ||
def clear(cls, repo): | ||||
util.unlinkpath(repo.join(cls._filename), ignoremissing=True) | ||||
David Soria Parra
|
r19854 | |||
def createcmd(ui, repo, pats, opts): | ||||
Augie Fackler
|
r19911 | """subcommand that creates a new shelve""" | ||
Pierre-Yves David
|
r19909 | |||
David Soria Parra
|
r19854 | def publicancestors(ctx): | ||
Mads Kiilerich
|
r20408 | """Compute the public ancestors of a commit. | ||
David Soria Parra
|
r19854 | |||
Mads Kiilerich
|
r20408 | Much faster than the revset ancestors(ctx) & draft()""" | ||
Mads Kiilerich
|
r20407 | seen = set([nullrev]) | ||
David Soria Parra
|
r19854 | visit = util.deque() | ||
visit.append(ctx) | ||||
while visit: | ||||
ctx = visit.popleft() | ||||
Mads Kiilerich
|
r20408 | yield ctx.node() | ||
David Soria Parra
|
r19854 | for parent in ctx.parents(): | ||
rev = parent.rev() | ||||
if rev not in seen: | ||||
seen.add(rev) | ||||
if parent.mutable(): | ||||
visit.append(parent) | ||||
wctx = repo[None] | ||||
parents = wctx.parents() | ||||
if len(parents) > 1: | ||||
raise util.Abort(_('cannot shelve while merging')) | ||||
parent = parents[0] | ||||
# we never need the user, so we use a generic user for all shelve operations | ||||
user = 'shelve@localhost' | ||||
label = repo._bookmarkcurrent or parent.branch() or 'default' | ||||
# slashes aren't allowed in filenames, therefore we rename it | ||||
Sean Farley
|
r20937 | label = label.replace('/', '_') | ||
David Soria Parra
|
r19854 | |||
def gennames(): | ||||
yield label | ||||
for i in xrange(1, 100): | ||||
yield '%s-%02d' % (label, i) | ||||
shelvedfiles = [] | ||||
def commitfunc(ui, repo, message, match, opts): | ||||
# check modified, added, removed, deleted only | ||||
for flist in repo.status(match=match)[:4]: | ||||
shelvedfiles.extend(flist) | ||||
Sean Farley
|
r19885 | hasmq = util.safehasattr(repo, 'mq') | ||
if hasmq: | ||||
saved, repo.mq.checkapplied = repo.mq.checkapplied, False | ||||
Pierre-Yves David
|
r22040 | backup = repo.ui.backupconfig('phases', 'new-commit') | ||
David Soria Parra
|
r19856 | try: | ||
Pierre-Yves David
|
r22040 | repo.ui. setconfig('phases', 'new-commit', phases.secret) | ||
FUJIWARA Katsunori
|
r22005 | editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts) | ||
FUJIWARA Katsunori
|
r21852 | return repo.commit(message, user, opts.get('date'), match, | ||
FUJIWARA Katsunori
|
r22005 | editor=editor) | ||
David Soria Parra
|
r19856 | finally: | ||
Pierre-Yves David
|
r22040 | repo.ui.restoreconfig(backup) | ||
Sean Farley
|
r19885 | if hasmq: | ||
repo.mq.checkapplied = saved | ||||
David Soria Parra
|
r19854 | |||
if parent.node() != nullid: | ||||
Mads Kiilerich
|
r20411 | desc = "changes to '%s'" % parent.description().split('\n', 1)[0] | ||
David Soria Parra
|
r19854 | else: | ||
Mads Kiilerich
|
r20411 | desc = '(changes in empty repository)' | ||
David Soria Parra
|
r19854 | |||
if not opts['message']: | ||||
opts['message'] = desc | ||||
name = opts['name'] | ||||
David Soria Parra
|
r19874 | wlock = lock = tr = bms = None | ||
David Soria Parra
|
r19854 | try: | ||
wlock = repo.wlock() | ||||
lock = repo.lock() | ||||
David Soria Parra
|
r19874 | bms = repo._bookmarks.copy() | ||
Mads Kiilerich
|
r19951 | # use an uncommitted transaction to generate the bundle to avoid | ||
David Soria Parra
|
r19854 | # pull races. ensure we don't print the abort message to stderr. | ||
tr = repo.transaction('commit', report=lambda x: None) | ||||
if name: | ||||
if shelvedfile(repo, name, 'hg').exists(): | ||||
raise util.Abort(_("a shelved change named '%s' already exists") | ||||
% name) | ||||
else: | ||||
for n in gennames(): | ||||
if not shelvedfile(repo, n, 'hg').exists(): | ||||
name = n | ||||
break | ||||
else: | ||||
raise util.Abort(_("too many shelved changes named '%s'") % | ||||
label) | ||||
# ensure we are not creating a subdirectory or a hidden file | ||||
if '/' in name or '\\' in name: | ||||
raise util.Abort(_('shelved change names may not contain slashes')) | ||||
if name.startswith('.'): | ||||
raise util.Abort(_("shelved change names may not start with '.'")) | ||||
node = cmdutil.commit(ui, repo, commitfunc, pats, opts) | ||||
if not node: | ||||
stat = repo.status(match=scmutil.match(repo[None], pats, opts)) | ||||
if stat[3]: | ||||
ui.status(_("nothing changed (%d missing files, see " | ||||
"'hg status')\n") % len(stat[3])) | ||||
else: | ||||
ui.status(_("nothing changed\n")) | ||||
return 1 | ||||
fp = shelvedfile(repo, name, 'files').opener('wb') | ||||
fp.write('\0'.join(shelvedfiles)) | ||||
bases = list(publicancestors(repo[node])) | ||||
Pierre-Yves David
|
r20927 | cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve') | ||
FUJIWARA Katsunori
|
r20983 | shelvedfile(repo, name, 'hg').writebundle(cg) | ||
David Soria Parra
|
r19854 | cmdutil.export(repo, [node], | ||
fp=shelvedfile(repo, name, 'patch').opener('wb'), | ||||
opts=mdiff.diffopts(git=True)) | ||||
David Soria Parra
|
r19874 | |||
David Soria Parra
|
r19854 | if ui.formatted(): | ||
desc = util.ellipsis(desc, ui.termwidth()) | ||||
ui.status(_('shelved as %s\n') % name) | ||||
hg.update(repo, parent.node()) | ||||
finally: | ||||
David Soria Parra
|
r19874 | if bms: | ||
# restore old bookmarks | ||||
repo._bookmarks.update(bms) | ||||
repo._bookmarks.write() | ||||
David Soria Parra
|
r19854 | if tr: | ||
tr.abort() | ||||
lockmod.release(lock, wlock) | ||||
def cleanupcmd(ui, repo): | ||||
Augie Fackler
|
r19911 | """subcommand that deletes all shelves""" | ||
Pierre-Yves David
|
r19909 | |||
David Soria Parra
|
r19854 | wlock = None | ||
try: | ||||
wlock = repo.wlock() | ||||
Mads Kiilerich
|
r22199 | for (name, _type) in repo.vfs.readdir('shelved'): | ||
David Soria Parra
|
r19854 | suffix = name.rsplit('.', 1)[-1] | ||
if suffix in ('hg', 'files', 'patch'): | ||||
shelvedfile(repo, name).unlink() | ||||
finally: | ||||
lockmod.release(wlock) | ||||
def deletecmd(ui, repo, pats): | ||||
Augie Fackler
|
r19911 | """subcommand that deletes a specific shelve""" | ||
David Soria Parra
|
r19854 | if not pats: | ||
raise util.Abort(_('no shelved changes specified!')) | ||||
wlock = None | ||||
try: | ||||
wlock = repo.wlock() | ||||
try: | ||||
for name in pats: | ||||
for suffix in 'hg files patch'.split(): | ||||
shelvedfile(repo, name, suffix).unlink() | ||||
except OSError, err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
raise util.Abort(_("shelved change '%s' not found") % name) | ||||
finally: | ||||
lockmod.release(wlock) | ||||
def listshelves(repo): | ||||
Augie Fackler
|
r19911 | """return all shelves in repo as list of (time, filename)""" | ||
David Soria Parra
|
r19854 | try: | ||
names = repo.vfs.readdir('shelved') | ||||
except OSError, err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
return [] | ||||
info = [] | ||||
Mads Kiilerich
|
r22199 | for (name, _type) in names: | ||
David Soria Parra
|
r19854 | pfx, sfx = name.rsplit('.', 1) | ||
if not pfx or sfx != 'patch': | ||||
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): | ||||
Augie Fackler
|
r19911 | """subcommand that displays the list of shelves""" | ||
David Soria Parra
|
r19854 | pats = set(pats) | ||
width = 80 | ||||
if not ui.plain(): | ||||
width = ui.termwidth() | ||||
namelabel = 'shelve.newest' | ||||
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 | ||||
David Soria Parra
|
r19855 | age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True) | ||
David Soria Parra
|
r19854 | ui.write(age, label='shelve.age') | ||
David Soria Parra
|
r19855 | ui.write(' ' * (12 - len(age))) | ||
used += 12 | ||||
David Soria Parra
|
r19854 | fp = open(name + '.patch', 'rb') | ||
try: | ||||
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']: | ||||
for chunk, label in patch.diffstatui(difflines, width=width, | ||||
git=True): | ||||
ui.write(chunk, label=label) | ||||
finally: | ||||
fp.close() | ||||
def checkparents(repo, state): | ||||
Pierre-Yves David
|
r19909 | """check parent while resuming an unshelve""" | ||
David Soria Parra
|
r19854 | if state.parents != repo.dirstate.parents(): | ||
raise util.Abort(_('working directory parents do not match unshelve ' | ||||
'state')) | ||||
Takumi IINO
|
r19943 | def pathtofiles(repo, files): | ||
cwd = repo.getcwd() | ||||
return [repo.pathto(f, cwd) for f in files] | ||||
David Soria Parra
|
r19854 | def unshelveabort(ui, repo, state, opts): | ||
Pierre-Yves David
|
r19909 | """subcommand that abort an in-progress unshelve""" | ||
David Soria Parra
|
r19854 | wlock = repo.wlock() | ||
lock = None | ||||
try: | ||||
checkparents(repo, state) | ||||
Durham Goode
|
r19961 | |||
util.rename(repo.join('unshelverebasestate'), | ||||
repo.join('rebasestate')) | ||||
try: | ||||
rebase.rebase(ui, repo, **{ | ||||
'abort' : True | ||||
}) | ||||
except Exception: | ||||
util.rename(repo.join('rebasestate'), | ||||
repo.join('unshelverebasestate')) | ||||
raise | ||||
David Soria Parra
|
r19854 | lock = repo.lock() | ||
Durham Goode
|
r19961 | |||
Durham Goode
|
r20149 | mergefiles(ui, repo, state.wctx, state.pendingctx) | ||
Durham Goode
|
r19961 | |||
Jordi Gutiérrez Hermoso
|
r22057 | repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') | ||
David Soria Parra
|
r19854 | shelvedstate.clear(repo) | ||
ui.warn(_("unshelve of '%s' aborted\n") % state.name) | ||||
finally: | ||||
lockmod.release(lock, wlock) | ||||
Durham Goode
|
r20149 | def mergefiles(ui, repo, wctx, shelvectx): | ||
Durham Goode
|
r19961 | """updates to wctx and merges the changes from shelvectx into the | ||
Durham Goode
|
r20149 | dirstate.""" | ||
Durham Goode
|
r19961 | oldquiet = ui.quiet | ||
try: | ||||
ui.quiet = True | ||||
hg.update(repo, wctx.node()) | ||||
files = [] | ||||
files.extend(shelvectx.files()) | ||||
files.extend(shelvectx.parents()[0].files()) | ||||
Durham Goode
|
r20149 | |||
# revert will overwrite unknown files, so move them out of the way | ||||
m, a, r, d, u = repo.status(unknown=True)[:5] | ||||
for file in u: | ||||
if file in files: | ||||
util.rename(file, file + ".orig") | ||||
Matt Mackall
|
r22184 | ui.pushbuffer(True) | ||
Durham Goode
|
r19961 | cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), | ||
*pathtofiles(repo, files), | ||||
**{'no_backup': True}) | ||||
Matt Mackall
|
r22184 | ui.popbuffer() | ||
Durham Goode
|
r19961 | finally: | ||
ui.quiet = oldquiet | ||||
David Soria Parra
|
r19854 | def unshelvecleanup(ui, repo, name, opts): | ||
Augie Fackler
|
r19911 | """remove related files after an unshelve""" | ||
David Soria Parra
|
r19854 | if not opts['keep']: | ||
for filetype in 'hg files patch'.split(): | ||||
shelvedfile(repo, name, filetype).unlink() | ||||
def unshelvecontinue(ui, repo, state, opts): | ||||
Pierre-Yves David
|
r19909 | """subcommand to continue an in-progress unshelve""" | ||
David Soria Parra
|
r19854 | # We're finishing off a merge. First parent is our original | ||
# parent, second is the temporary "fake" commit we're unshelving. | ||||
wlock = repo.wlock() | ||||
lock = None | ||||
try: | ||||
checkparents(repo, state) | ||||
ms = merge.mergestate(repo) | ||||
if [f for f in ms if ms[f] == 'u']: | ||||
raise util.Abort( | ||||
_("unresolved conflicts, can't continue"), | ||||
hint=_("see 'hg resolve', then 'hg unshelve --continue'")) | ||||
Durham Goode
|
r19961 | |||
David Soria Parra
|
r19854 | lock = repo.lock() | ||
Durham Goode
|
r19961 | |||
util.rename(repo.join('unshelverebasestate'), | ||||
repo.join('rebasestate')) | ||||
try: | ||||
rebase.rebase(ui, repo, **{ | ||||
'continue' : True | ||||
}) | ||||
except Exception: | ||||
util.rename(repo.join('rebasestate'), | ||||
repo.join('unshelverebasestate')) | ||||
raise | ||||
shelvectx = repo['tip'] | ||||
if not shelvectx in state.pendingctx.children(): | ||||
# rebase was a no-op, so it produced no child commit | ||||
shelvectx = state.pendingctx | ||||
Durham Goode
|
r20149 | mergefiles(ui, repo, state.wctx, shelvectx) | ||
Durham Goode
|
r19961 | |||
state.stripnodes.append(shelvectx.node()) | ||||
Jordi Gutiérrez Hermoso
|
r22057 | repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve') | ||
Durham Goode
|
r19961 | shelvedstate.clear(repo) | ||
David Soria Parra
|
r19854 | unshelvecleanup(ui, repo, state.name, opts) | ||
ui.status(_("unshelve of '%s' complete\n") % state.name) | ||||
finally: | ||||
lockmod.release(lock, wlock) | ||||
@command('unshelve', | ||||
[('a', 'abort', None, | ||||
_('abort an incomplete unshelve operation')), | ||||
('c', 'continue', None, | ||||
_('continue an incomplete unshelve operation')), | ||||
('', 'keep', None, | ||||
Mads Kiilerich
|
r20960 | _('keep shelve after unshelving')), | ||
('', 'date', '', | ||||
_('set date for temporary commits (DEPRECATED)'), _('DATE'))], | ||||
David Soria Parra
|
r19854 | _('hg unshelve [SHELVED]')) | ||
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 | ||||
contains the shelved changes is deleted afterwards. | ||||
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 | ||||
will not be deleted until you successfully complete the unshelve.) | ||||
(Alternatively, you can use ``--abort`` to abandon an unshelve | ||||
that causes a conflict. This reverts the unshelved changes, and | ||||
does not delete the bundle.) | ||||
""" | ||||
abortf = opts['abort'] | ||||
continuef = opts['continue'] | ||||
if not abortf and not continuef: | ||||
cmdutil.checkunfinished(repo) | ||||
if abortf or continuef: | ||||
if abortf and continuef: | ||||
raise util.Abort(_('cannot use both abort and continue')) | ||||
if shelved: | ||||
raise util.Abort(_('cannot combine abort/continue with ' | ||||
'naming a shelved change')) | ||||
try: | ||||
state = shelvedstate.load(repo) | ||||
except IOError, err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
raise util.Abort(_('no unshelve operation underway')) | ||||
if abortf: | ||||
return unshelveabort(ui, repo, state, opts) | ||||
elif continuef: | ||||
return unshelvecontinue(ui, repo, state, opts) | ||||
elif len(shelved) > 1: | ||||
raise util.Abort(_('can only unshelve one change at a time')) | ||||
elif not shelved: | ||||
shelved = listshelves(repo) | ||||
if not shelved: | ||||
raise util.Abort(_('no shelved changes to apply!')) | ||||
basename = util.split(shelved[0][1])[1] | ||||
ui.status(_("unshelving change '%s'\n") % basename) | ||||
else: | ||||
basename = shelved[0] | ||||
FUJIWARA Katsunori
|
r19970 | if not shelvedfile(repo, basename, 'files').exists(): | ||
raise util.Abort(_("shelved change '%s' not found") % basename) | ||||
David Soria Parra
|
r19854 | |||
Mads Kiilerich
|
r20412 | oldquiet = ui.quiet | ||
David Soria Parra
|
r19854 | wlock = lock = tr = None | ||
try: | ||||
lock = repo.lock() | ||||
Durham Goode
|
r19961 | wlock = repo.wlock() | ||
David Soria Parra
|
r19854 | |||
tr = repo.transaction('unshelve', report=lambda x: None) | ||||
oldtiprev = len(repo) | ||||
Durham Goode
|
r19961 | |||
Mads Kiilerich
|
r20958 | pctx = repo['.'] | ||
tmpwctx = pctx | ||||
Durham Goode
|
r19961 | # The goal is to have a commit structure like so: | ||
Mads Kiilerich
|
r20958 | # ...-> pctx -> tmpwctx -> shelvectx | ||
Durham Goode
|
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 | ||||
Mads Kiilerich
|
r20958 | # to the original pctx. | ||
Durham Goode
|
r19961 | |||
# Store pending changes in a commit | ||||
Durham Goode
|
r20149 | m, a, r, d = repo.status()[:4] | ||
if m or a or r or d: | ||||
Mads Kiilerich
|
r20413 | ui.status(_("temporarily committing pending changes " | ||
"(restore with 'hg unshelve --abort')\n")) | ||||
Durham Goode
|
r19961 | def commitfunc(ui, repo, message, match, opts): | ||
hasmq = util.safehasattr(repo, 'mq') | ||||
if hasmq: | ||||
saved, repo.mq.checkapplied = repo.mq.checkapplied, False | ||||
Pierre-Yves David
|
r22040 | backup = repo.ui.backupconfig('phases', 'new-commit') | ||
Durham Goode
|
r19961 | try: | ||
Pierre-Yves David
|
r22040 | repo.ui. setconfig('phases', 'new-commit', phases.secret) | ||
Durham Goode
|
r19961 | return repo.commit(message, 'shelve@localhost', | ||
opts.get('date'), match) | ||||
finally: | ||||
Pierre-Yves David
|
r22040 | repo.ui.restoreconfig(backup) | ||
Durham Goode
|
r19961 | if hasmq: | ||
repo.mq.checkapplied = saved | ||||
tempopts = {} | ||||
tempopts['message'] = "pending changes temporary commit" | ||||
Mads Kiilerich
|
r20960 | tempopts['date'] = opts.get('date') | ||
Mads Kiilerich
|
r20412 | ui.quiet = True | ||
node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) | ||||
Durham Goode
|
r19961 | tmpwctx = repo[node] | ||
FUJIWARA Katsunori
|
r20982 | ui.quiet = True | ||
shelvedfile(repo, basename, 'hg').applybundle() | ||||
David Soria Parra
|
r19854 | |||
Mads Kiilerich
|
r20412 | ui.quiet = oldquiet | ||
Durham Goode
|
r19961 | shelvectx = repo['tip'] | ||
David Soria Parra
|
r19854 | |||
Durham Goode
|
r19961 | # 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(): | ||||
Mads Kiilerich
|
r20413 | ui.status(_('rebasing shelved changes\n')) | ||
Durham Goode
|
r19961 | try: | ||
rebase.rebase(ui, repo, **{ | ||||
'rev' : [shelvectx.rev()], | ||||
'dest' : str(tmpwctx.rev()), | ||||
'keep' : True, | ||||
}) | ||||
except error.InterventionRequired: | ||||
tr.close() | ||||
stripnodes = [repo.changelog.node(rev) | ||||
for rev in xrange(oldtiprev, len(repo))] | ||||
Mads Kiilerich
|
r20958 | shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes) | ||
Durham Goode
|
r19961 | |||
util.rename(repo.join('rebasestate'), | ||||
repo.join('unshelverebasestate')) | ||||
David Soria Parra
|
r19854 | raise error.InterventionRequired( | ||
_("unresolved conflicts (see 'hg resolve', then " | ||||
"'hg unshelve --continue')")) | ||||
Durham Goode
|
r19961 | |||
# refresh ctx after rebase completes | ||||
shelvectx = repo['tip'] | ||||
if not shelvectx in tmpwctx.children(): | ||||
# rebase was a no-op, so it produced no child commit | ||||
shelvectx = tmpwctx | ||||
David Soria Parra
|
r19854 | |||
Mads Kiilerich
|
r20958 | mergefiles(ui, repo, pctx, shelvectx) | ||
Durham Goode
|
r19961 | shelvedstate.clear(repo) | ||
# 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. | ||||
David Soria Parra
|
r20064 | repo.unfiltered().changelog.strip(oldtiprev, tr) | ||
David Soria Parra
|
r19854 | |||
unshelvecleanup(ui, repo, basename, opts) | ||||
finally: | ||||
Mads Kiilerich
|
r20412 | ui.quiet = oldquiet | ||
David Soria Parra
|
r19854 | if tr: | ||
tr.release() | ||||
lockmod.release(lock, wlock) | ||||
@command('shelve', | ||||
[('A', 'addremove', None, | ||||
_('mark new/missing files as added/removed before shelving')), | ||||
('', 'cleanup', None, | ||||
_('delete all shelved changes')), | ||||
('', 'date', '', | ||||
_('shelve with the specified commit date'), _('DATE')), | ||||
('d', 'delete', None, | ||||
_('delete the named shelved change(s)')), | ||||
FUJIWARA Katsunori
|
r21852 | ('e', 'edit', False, | ||
_('invoke editor on commit messages')), | ||||
David Soria Parra
|
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')), | ||||
('', 'stat', None, | ||||
Mads Kiilerich
|
r20409 | _('output diffstat-style summary of changes'))] + commands.walkopts, | ||
Mads Kiilerich
|
r20410 | _('hg shelve [OPTION]... [FILE]...')) | ||
David Soria Parra
|
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. | ||||
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``. | ||||
''' | ||||
cmdutil.checkunfinished(repo) | ||||
FUJIWARA Katsunori
|
r21851 | allowables = [ | ||
('addremove', 'create'), # 'create' is pseudo action | ||||
('cleanup', 'cleanup'), | ||||
# ('date', 'create'), # ignored for passing '--date "0 0"' in tests | ||||
('delete', 'delete'), | ||||
FUJIWARA Katsunori
|
r21852 | ('edit', 'create'), | ||
FUJIWARA Katsunori
|
r21851 | ('list', 'list'), | ||
('message', 'create'), | ||||
('name', 'create'), | ||||
('patch', 'list'), | ||||
('stat', 'list'), | ||||
] | ||||
def checkopt(opt): | ||||
David Soria Parra
|
r19854 | if opts[opt]: | ||
FUJIWARA Katsunori
|
r21851 | for i, allowable in allowables: | ||
if opts[i] and opt != allowable: | ||||
David Soria Parra
|
r19854 | raise util.Abort(_("options '--%s' and '--%s' may not be " | ||
"used together") % (opt, i)) | ||||
return True | ||||
FUJIWARA Katsunori
|
r21851 | if checkopt('cleanup'): | ||
David Soria Parra
|
r19854 | if pats: | ||
raise util.Abort(_("cannot specify names when using '--cleanup'")) | ||||
return cleanupcmd(ui, repo) | ||||
FUJIWARA Katsunori
|
r21851 | elif checkopt('delete'): | ||
David Soria Parra
|
r19854 | return deletecmd(ui, repo, pats) | ||
FUJIWARA Katsunori
|
r21851 | elif checkopt('list'): | ||
David Soria Parra
|
r19854 | return listcmd(ui, repo, pats, opts) | ||
else: | ||||
for i in ('patch', 'stat'): | ||||
if opts[i]: | ||||
raise util.Abort(_("option '--%s' may not be " | ||||
"used when shelving a change") % (i,)) | ||||
return createcmd(ui, repo, pats, opts) | ||||
def extsetup(ui): | ||||
cmdutil.unfinishedstates.append( | ||||
FUJIWARA Katsunori
|
r19963 | [shelvedstate._filename, False, False, | ||
_('unshelve already in progress'), | ||||
David Soria Parra
|
r19854 | _("use 'hg unshelve --continue' or 'hg unshelve --abort'")]) | ||