Mercurial > hg
comparison hgext/shelve.py @ 19854:49d4919d21c2
shelve: add a shelve extension to save/restore working changes
This extension saves shelved changes using a temporary draft commit,
and bundles the temporary commit and its draft ancestors, then
strips them.
This strategy makes it possible to use Mercurial's bundle and merge
machinery to resolve conflicts if necessary when unshelving, even
when the destination commit or its ancestors have been amended,
squashed, or evolved. (Once a change has been unshelved, its
associated unbundled commits are either rolled back or stripped.)
Storing the shelved change as a bundle also avoids the difficulty
that hidden commits would cause, of making it impossible to amend
the parent if it is a draft commits (a common scenario).
Although this extension shares its name and some functionality with
the third party hgshelve extension, it has little else in common.
Notably, the hgshelve extension shelves changes as unified diffs,
which makes conflict resolution a matter of finding .rej files and
conflict markers, and cleaning up the mess by hand.
We do not yet allow hunk-level choosing of changes to record.
Compared to the hgshelve extension, this is a small regression in
usability, but we hope to integrate that at a later point, once the
record machinery becomes more reusable and robust.
author | David Soria Parra <dsp@experimentalworks.net> |
---|---|
date | Thu, 29 Aug 2013 09:22:13 -0700 |
parents | |
children | a3b285882724 |
comparison
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'")]) |