##// END OF EJS Templates
shelve: pass 'editform' argument to 'cmdutil.getcommiteditor'...
FUJIWARA Katsunori -
r22005:dabf8fb8 default
parent child Browse files
Show More
@@ -1,719 +1,720
1 1 # shelve.py - save/restore working directory state
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """save and restore changes to the working directory
9 9
10 10 The "hg shelve" command saves changes made to the working directory
11 11 and reverts those changes, resetting the working directory to a clean
12 12 state.
13 13
14 14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 15 shelve". Changes can be restored even after updating to a different
16 16 parent, in which case Mercurial's merge machinery will resolve any
17 17 conflicts if necessary.
18 18
19 19 You can have more than one shelved change outstanding at a time; each
20 20 shelved change has a distinct name. For details, see the help for "hg
21 21 shelve".
22 22 """
23 23
24 24 from mercurial.i18n import _
25 25 from mercurial.node import nullid, nullrev, bin, hex
26 26 from mercurial import changegroup, cmdutil, scmutil, phases, commands
27 27 from mercurial import error, hg, mdiff, merge, patch, repair, util
28 28 from mercurial import templatefilters, changegroup, exchange
29 29 from mercurial import lock as lockmod
30 30 from hgext import rebase
31 31 import errno
32 32
33 33 cmdtable = {}
34 34 command = cmdutil.command(cmdtable)
35 35 testedwith = 'internal'
36 36
37 37 class shelvedfile(object):
38 38 """Helper for the file storing a single shelve
39 39
40 40 Handles common functions on shelve files (.hg/.files/.patch) using
41 41 the vfs layer"""
42 42 def __init__(self, repo, name, filetype=None):
43 43 self.repo = repo
44 44 self.name = name
45 45 self.vfs = scmutil.vfs(repo.join('shelved'))
46 46 if filetype:
47 47 self.fname = name + '.' + filetype
48 48 else:
49 49 self.fname = name
50 50
51 51 def exists(self):
52 52 return self.vfs.exists(self.fname)
53 53
54 54 def filename(self):
55 55 return self.vfs.join(self.fname)
56 56
57 57 def unlink(self):
58 58 util.unlink(self.filename())
59 59
60 60 def stat(self):
61 61 return self.vfs.stat(self.fname)
62 62
63 63 def opener(self, mode='rb'):
64 64 try:
65 65 return self.vfs(self.fname, mode)
66 66 except IOError, err:
67 67 if err.errno != errno.ENOENT:
68 68 raise
69 69 raise util.Abort(_("shelved change '%s' not found") % self.name)
70 70
71 71 def applybundle(self):
72 72 fp = self.opener()
73 73 try:
74 74 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
75 75 changegroup.addchangegroup(self.repo, gen, 'unshelve',
76 76 'bundle:' + self.vfs.join(self.fname))
77 77 finally:
78 78 fp.close()
79 79
80 80 def writebundle(self, cg):
81 81 changegroup.writebundle(cg, self.fname, 'HG10UN', self.vfs)
82 82
83 83 class shelvedstate(object):
84 84 """Handle persistence during unshelving operations.
85 85
86 86 Handles saving and restoring a shelved state. Ensures that different
87 87 versions of a shelved state are possible and handles them appropriately.
88 88 """
89 89 _version = 1
90 90 _filename = 'shelvedstate'
91 91
92 92 @classmethod
93 93 def load(cls, repo):
94 94 fp = repo.opener(cls._filename)
95 95 try:
96 96 version = int(fp.readline().strip())
97 97
98 98 if version != cls._version:
99 99 raise util.Abort(_('this version of shelve is incompatible '
100 100 'with the version used in this repo'))
101 101 name = fp.readline().strip()
102 102 wctx = fp.readline().strip()
103 103 pendingctx = fp.readline().strip()
104 104 parents = [bin(h) for h in fp.readline().split()]
105 105 stripnodes = [bin(h) for h in fp.readline().split()]
106 106 finally:
107 107 fp.close()
108 108
109 109 obj = cls()
110 110 obj.name = name
111 111 obj.wctx = repo[bin(wctx)]
112 112 obj.pendingctx = repo[bin(pendingctx)]
113 113 obj.parents = parents
114 114 obj.stripnodes = stripnodes
115 115
116 116 return obj
117 117
118 118 @classmethod
119 119 def save(cls, repo, name, originalwctx, pendingctx, stripnodes):
120 120 fp = repo.opener(cls._filename, 'wb')
121 121 fp.write('%i\n' % cls._version)
122 122 fp.write('%s\n' % name)
123 123 fp.write('%s\n' % hex(originalwctx.node()))
124 124 fp.write('%s\n' % hex(pendingctx.node()))
125 125 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
126 126 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
127 127 fp.close()
128 128
129 129 @classmethod
130 130 def clear(cls, repo):
131 131 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
132 132
133 133 def createcmd(ui, repo, pats, opts):
134 134 """subcommand that creates a new shelve"""
135 135
136 136 def publicancestors(ctx):
137 137 """Compute the public ancestors of a commit.
138 138
139 139 Much faster than the revset ancestors(ctx) & draft()"""
140 140 seen = set([nullrev])
141 141 visit = util.deque()
142 142 visit.append(ctx)
143 143 while visit:
144 144 ctx = visit.popleft()
145 145 yield ctx.node()
146 146 for parent in ctx.parents():
147 147 rev = parent.rev()
148 148 if rev not in seen:
149 149 seen.add(rev)
150 150 if parent.mutable():
151 151 visit.append(parent)
152 152
153 153 wctx = repo[None]
154 154 parents = wctx.parents()
155 155 if len(parents) > 1:
156 156 raise util.Abort(_('cannot shelve while merging'))
157 157 parent = parents[0]
158 158
159 159 # we never need the user, so we use a generic user for all shelve operations
160 160 user = 'shelve@localhost'
161 161 label = repo._bookmarkcurrent or parent.branch() or 'default'
162 162
163 163 # slashes aren't allowed in filenames, therefore we rename it
164 164 label = label.replace('/', '_')
165 165
166 166 def gennames():
167 167 yield label
168 168 for i in xrange(1, 100):
169 169 yield '%s-%02d' % (label, i)
170 170
171 171 shelvedfiles = []
172 172
173 173 def commitfunc(ui, repo, message, match, opts):
174 174 # check modified, added, removed, deleted only
175 175 for flist in repo.status(match=match)[:4]:
176 176 shelvedfiles.extend(flist)
177 177 hasmq = util.safehasattr(repo, 'mq')
178 178 if hasmq:
179 179 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
180 180 try:
181 editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts)
181 182 return repo.commit(message, user, opts.get('date'), match,
182 editor=cmdutil.getcommiteditor(**opts))
183 editor=editor)
183 184 finally:
184 185 if hasmq:
185 186 repo.mq.checkapplied = saved
186 187
187 188 if parent.node() != nullid:
188 189 desc = "changes to '%s'" % parent.description().split('\n', 1)[0]
189 190 else:
190 191 desc = '(changes in empty repository)'
191 192
192 193 if not opts['message']:
193 194 opts['message'] = desc
194 195
195 196 name = opts['name']
196 197
197 198 wlock = lock = tr = bms = None
198 199 try:
199 200 wlock = repo.wlock()
200 201 lock = repo.lock()
201 202
202 203 bms = repo._bookmarks.copy()
203 204 # use an uncommitted transaction to generate the bundle to avoid
204 205 # pull races. ensure we don't print the abort message to stderr.
205 206 tr = repo.transaction('commit', report=lambda x: None)
206 207
207 208 if name:
208 209 if shelvedfile(repo, name, 'hg').exists():
209 210 raise util.Abort(_("a shelved change named '%s' already exists")
210 211 % name)
211 212 else:
212 213 for n in gennames():
213 214 if not shelvedfile(repo, n, 'hg').exists():
214 215 name = n
215 216 break
216 217 else:
217 218 raise util.Abort(_("too many shelved changes named '%s'") %
218 219 label)
219 220
220 221 # ensure we are not creating a subdirectory or a hidden file
221 222 if '/' in name or '\\' in name:
222 223 raise util.Abort(_('shelved change names may not contain slashes'))
223 224 if name.startswith('.'):
224 225 raise util.Abort(_("shelved change names may not start with '.'"))
225 226
226 227 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
227 228
228 229 if not node:
229 230 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
230 231 if stat[3]:
231 232 ui.status(_("nothing changed (%d missing files, see "
232 233 "'hg status')\n") % len(stat[3]))
233 234 else:
234 235 ui.status(_("nothing changed\n"))
235 236 return 1
236 237
237 238 phases.retractboundary(repo, phases.secret, [node])
238 239
239 240 fp = shelvedfile(repo, name, 'files').opener('wb')
240 241 fp.write('\0'.join(shelvedfiles))
241 242
242 243 bases = list(publicancestors(repo[node]))
243 244 cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve')
244 245 shelvedfile(repo, name, 'hg').writebundle(cg)
245 246 cmdutil.export(repo, [node],
246 247 fp=shelvedfile(repo, name, 'patch').opener('wb'),
247 248 opts=mdiff.diffopts(git=True))
248 249
249 250
250 251 if ui.formatted():
251 252 desc = util.ellipsis(desc, ui.termwidth())
252 253 ui.status(_('shelved as %s\n') % name)
253 254 hg.update(repo, parent.node())
254 255 finally:
255 256 if bms:
256 257 # restore old bookmarks
257 258 repo._bookmarks.update(bms)
258 259 repo._bookmarks.write()
259 260 if tr:
260 261 tr.abort()
261 262 lockmod.release(lock, wlock)
262 263
263 264 def cleanupcmd(ui, repo):
264 265 """subcommand that deletes all shelves"""
265 266
266 267 wlock = None
267 268 try:
268 269 wlock = repo.wlock()
269 270 for (name, _) in repo.vfs.readdir('shelved'):
270 271 suffix = name.rsplit('.', 1)[-1]
271 272 if suffix in ('hg', 'files', 'patch'):
272 273 shelvedfile(repo, name).unlink()
273 274 finally:
274 275 lockmod.release(wlock)
275 276
276 277 def deletecmd(ui, repo, pats):
277 278 """subcommand that deletes a specific shelve"""
278 279 if not pats:
279 280 raise util.Abort(_('no shelved changes specified!'))
280 281 wlock = None
281 282 try:
282 283 wlock = repo.wlock()
283 284 try:
284 285 for name in pats:
285 286 for suffix in 'hg files patch'.split():
286 287 shelvedfile(repo, name, suffix).unlink()
287 288 except OSError, err:
288 289 if err.errno != errno.ENOENT:
289 290 raise
290 291 raise util.Abort(_("shelved change '%s' not found") % name)
291 292 finally:
292 293 lockmod.release(wlock)
293 294
294 295 def listshelves(repo):
295 296 """return all shelves in repo as list of (time, filename)"""
296 297 try:
297 298 names = repo.vfs.readdir('shelved')
298 299 except OSError, err:
299 300 if err.errno != errno.ENOENT:
300 301 raise
301 302 return []
302 303 info = []
303 304 for (name, _) in names:
304 305 pfx, sfx = name.rsplit('.', 1)
305 306 if not pfx or sfx != 'patch':
306 307 continue
307 308 st = shelvedfile(repo, name).stat()
308 309 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
309 310 return sorted(info, reverse=True)
310 311
311 312 def listcmd(ui, repo, pats, opts):
312 313 """subcommand that displays the list of shelves"""
313 314 pats = set(pats)
314 315 width = 80
315 316 if not ui.plain():
316 317 width = ui.termwidth()
317 318 namelabel = 'shelve.newest'
318 319 for mtime, name in listshelves(repo):
319 320 sname = util.split(name)[1]
320 321 if pats and sname not in pats:
321 322 continue
322 323 ui.write(sname, label=namelabel)
323 324 namelabel = 'shelve.name'
324 325 if ui.quiet:
325 326 ui.write('\n')
326 327 continue
327 328 ui.write(' ' * (16 - len(sname)))
328 329 used = 16
329 330 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
330 331 ui.write(age, label='shelve.age')
331 332 ui.write(' ' * (12 - len(age)))
332 333 used += 12
333 334 fp = open(name + '.patch', 'rb')
334 335 try:
335 336 while True:
336 337 line = fp.readline()
337 338 if not line:
338 339 break
339 340 if not line.startswith('#'):
340 341 desc = line.rstrip()
341 342 if ui.formatted():
342 343 desc = util.ellipsis(desc, width - used)
343 344 ui.write(desc)
344 345 break
345 346 ui.write('\n')
346 347 if not (opts['patch'] or opts['stat']):
347 348 continue
348 349 difflines = fp.readlines()
349 350 if opts['patch']:
350 351 for chunk, label in patch.difflabel(iter, difflines):
351 352 ui.write(chunk, label=label)
352 353 if opts['stat']:
353 354 for chunk, label in patch.diffstatui(difflines, width=width,
354 355 git=True):
355 356 ui.write(chunk, label=label)
356 357 finally:
357 358 fp.close()
358 359
359 360 def checkparents(repo, state):
360 361 """check parent while resuming an unshelve"""
361 362 if state.parents != repo.dirstate.parents():
362 363 raise util.Abort(_('working directory parents do not match unshelve '
363 364 'state'))
364 365
365 366 def pathtofiles(repo, files):
366 367 cwd = repo.getcwd()
367 368 return [repo.pathto(f, cwd) for f in files]
368 369
369 370 def unshelveabort(ui, repo, state, opts):
370 371 """subcommand that abort an in-progress unshelve"""
371 372 wlock = repo.wlock()
372 373 lock = None
373 374 try:
374 375 checkparents(repo, state)
375 376
376 377 util.rename(repo.join('unshelverebasestate'),
377 378 repo.join('rebasestate'))
378 379 try:
379 380 rebase.rebase(ui, repo, **{
380 381 'abort' : True
381 382 })
382 383 except Exception:
383 384 util.rename(repo.join('rebasestate'),
384 385 repo.join('unshelverebasestate'))
385 386 raise
386 387
387 388 lock = repo.lock()
388 389
389 390 mergefiles(ui, repo, state.wctx, state.pendingctx)
390 391
391 392 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
392 393 shelvedstate.clear(repo)
393 394 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
394 395 finally:
395 396 lockmod.release(lock, wlock)
396 397
397 398 def mergefiles(ui, repo, wctx, shelvectx):
398 399 """updates to wctx and merges the changes from shelvectx into the
399 400 dirstate."""
400 401 oldquiet = ui.quiet
401 402 try:
402 403 ui.quiet = True
403 404 hg.update(repo, wctx.node())
404 405 files = []
405 406 files.extend(shelvectx.files())
406 407 files.extend(shelvectx.parents()[0].files())
407 408
408 409 # revert will overwrite unknown files, so move them out of the way
409 410 m, a, r, d, u = repo.status(unknown=True)[:5]
410 411 for file in u:
411 412 if file in files:
412 413 util.rename(file, file + ".orig")
413 414 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
414 415 *pathtofiles(repo, files),
415 416 **{'no_backup': True})
416 417 finally:
417 418 ui.quiet = oldquiet
418 419
419 420 def unshelvecleanup(ui, repo, name, opts):
420 421 """remove related files after an unshelve"""
421 422 if not opts['keep']:
422 423 for filetype in 'hg files patch'.split():
423 424 shelvedfile(repo, name, filetype).unlink()
424 425
425 426 def unshelvecontinue(ui, repo, state, opts):
426 427 """subcommand to continue an in-progress unshelve"""
427 428 # We're finishing off a merge. First parent is our original
428 429 # parent, second is the temporary "fake" commit we're unshelving.
429 430 wlock = repo.wlock()
430 431 lock = None
431 432 try:
432 433 checkparents(repo, state)
433 434 ms = merge.mergestate(repo)
434 435 if [f for f in ms if ms[f] == 'u']:
435 436 raise util.Abort(
436 437 _("unresolved conflicts, can't continue"),
437 438 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
438 439
439 440 lock = repo.lock()
440 441
441 442 util.rename(repo.join('unshelverebasestate'),
442 443 repo.join('rebasestate'))
443 444 try:
444 445 rebase.rebase(ui, repo, **{
445 446 'continue' : True
446 447 })
447 448 except Exception:
448 449 util.rename(repo.join('rebasestate'),
449 450 repo.join('unshelverebasestate'))
450 451 raise
451 452
452 453 shelvectx = repo['tip']
453 454 if not shelvectx in state.pendingctx.children():
454 455 # rebase was a no-op, so it produced no child commit
455 456 shelvectx = state.pendingctx
456 457
457 458 mergefiles(ui, repo, state.wctx, shelvectx)
458 459
459 460 state.stripnodes.append(shelvectx.node())
460 461 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
461 462 shelvedstate.clear(repo)
462 463 unshelvecleanup(ui, repo, state.name, opts)
463 464 ui.status(_("unshelve of '%s' complete\n") % state.name)
464 465 finally:
465 466 lockmod.release(lock, wlock)
466 467
467 468 @command('unshelve',
468 469 [('a', 'abort', None,
469 470 _('abort an incomplete unshelve operation')),
470 471 ('c', 'continue', None,
471 472 _('continue an incomplete unshelve operation')),
472 473 ('', 'keep', None,
473 474 _('keep shelve after unshelving')),
474 475 ('', 'date', '',
475 476 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
476 477 _('hg unshelve [SHELVED]'))
477 478 def unshelve(ui, repo, *shelved, **opts):
478 479 """restore a shelved change to the working directory
479 480
480 481 This command accepts an optional name of a shelved change to
481 482 restore. If none is given, the most recent shelved change is used.
482 483
483 484 If a shelved change is applied successfully, the bundle that
484 485 contains the shelved changes is deleted afterwards.
485 486
486 487 Since you can restore a shelved change on top of an arbitrary
487 488 commit, it is possible that unshelving will result in a conflict
488 489 between your changes and the commits you are unshelving onto. If
489 490 this occurs, you must resolve the conflict, then use
490 491 ``--continue`` to complete the unshelve operation. (The bundle
491 492 will not be deleted until you successfully complete the unshelve.)
492 493
493 494 (Alternatively, you can use ``--abort`` to abandon an unshelve
494 495 that causes a conflict. This reverts the unshelved changes, and
495 496 does not delete the bundle.)
496 497 """
497 498 abortf = opts['abort']
498 499 continuef = opts['continue']
499 500 if not abortf and not continuef:
500 501 cmdutil.checkunfinished(repo)
501 502
502 503 if abortf or continuef:
503 504 if abortf and continuef:
504 505 raise util.Abort(_('cannot use both abort and continue'))
505 506 if shelved:
506 507 raise util.Abort(_('cannot combine abort/continue with '
507 508 'naming a shelved change'))
508 509
509 510 try:
510 511 state = shelvedstate.load(repo)
511 512 except IOError, err:
512 513 if err.errno != errno.ENOENT:
513 514 raise
514 515 raise util.Abort(_('no unshelve operation underway'))
515 516
516 517 if abortf:
517 518 return unshelveabort(ui, repo, state, opts)
518 519 elif continuef:
519 520 return unshelvecontinue(ui, repo, state, opts)
520 521 elif len(shelved) > 1:
521 522 raise util.Abort(_('can only unshelve one change at a time'))
522 523 elif not shelved:
523 524 shelved = listshelves(repo)
524 525 if not shelved:
525 526 raise util.Abort(_('no shelved changes to apply!'))
526 527 basename = util.split(shelved[0][1])[1]
527 528 ui.status(_("unshelving change '%s'\n") % basename)
528 529 else:
529 530 basename = shelved[0]
530 531
531 532 if not shelvedfile(repo, basename, 'files').exists():
532 533 raise util.Abort(_("shelved change '%s' not found") % basename)
533 534
534 535 oldquiet = ui.quiet
535 536 wlock = lock = tr = None
536 537 try:
537 538 lock = repo.lock()
538 539 wlock = repo.wlock()
539 540
540 541 tr = repo.transaction('unshelve', report=lambda x: None)
541 542 oldtiprev = len(repo)
542 543
543 544 pctx = repo['.']
544 545 tmpwctx = pctx
545 546 # The goal is to have a commit structure like so:
546 547 # ...-> pctx -> tmpwctx -> shelvectx
547 548 # where tmpwctx is an optional commit with the user's pending changes
548 549 # and shelvectx is the unshelved changes. Then we merge it all down
549 550 # to the original pctx.
550 551
551 552 # Store pending changes in a commit
552 553 m, a, r, d = repo.status()[:4]
553 554 if m or a or r or d:
554 555 ui.status(_("temporarily committing pending changes "
555 556 "(restore with 'hg unshelve --abort')\n"))
556 557 def commitfunc(ui, repo, message, match, opts):
557 558 hasmq = util.safehasattr(repo, 'mq')
558 559 if hasmq:
559 560 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
560 561
561 562 try:
562 563 return repo.commit(message, 'shelve@localhost',
563 564 opts.get('date'), match)
564 565 finally:
565 566 if hasmq:
566 567 repo.mq.checkapplied = saved
567 568
568 569 tempopts = {}
569 570 tempopts['message'] = "pending changes temporary commit"
570 571 tempopts['date'] = opts.get('date')
571 572 ui.quiet = True
572 573 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
573 574 tmpwctx = repo[node]
574 575
575 576 ui.quiet = True
576 577 shelvedfile(repo, basename, 'hg').applybundle()
577 578 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
578 579 phases.retractboundary(repo, phases.secret, nodes)
579 580
580 581 ui.quiet = oldquiet
581 582
582 583 shelvectx = repo['tip']
583 584
584 585 # If the shelve is not immediately on top of the commit
585 586 # we'll be merging with, rebase it to be on top.
586 587 if tmpwctx.node() != shelvectx.parents()[0].node():
587 588 ui.status(_('rebasing shelved changes\n'))
588 589 try:
589 590 rebase.rebase(ui, repo, **{
590 591 'rev' : [shelvectx.rev()],
591 592 'dest' : str(tmpwctx.rev()),
592 593 'keep' : True,
593 594 })
594 595 except error.InterventionRequired:
595 596 tr.close()
596 597
597 598 stripnodes = [repo.changelog.node(rev)
598 599 for rev in xrange(oldtiprev, len(repo))]
599 600 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
600 601
601 602 util.rename(repo.join('rebasestate'),
602 603 repo.join('unshelverebasestate'))
603 604 raise error.InterventionRequired(
604 605 _("unresolved conflicts (see 'hg resolve', then "
605 606 "'hg unshelve --continue')"))
606 607
607 608 # refresh ctx after rebase completes
608 609 shelvectx = repo['tip']
609 610
610 611 if not shelvectx in tmpwctx.children():
611 612 # rebase was a no-op, so it produced no child commit
612 613 shelvectx = tmpwctx
613 614
614 615 mergefiles(ui, repo, pctx, shelvectx)
615 616 shelvedstate.clear(repo)
616 617
617 618 # The transaction aborting will strip all the commits for us,
618 619 # but it doesn't update the inmemory structures, so addchangegroup
619 620 # hooks still fire and try to operate on the missing commits.
620 621 # Clean up manually to prevent this.
621 622 repo.unfiltered().changelog.strip(oldtiprev, tr)
622 623
623 624 unshelvecleanup(ui, repo, basename, opts)
624 625 finally:
625 626 ui.quiet = oldquiet
626 627 if tr:
627 628 tr.release()
628 629 lockmod.release(lock, wlock)
629 630
630 631 @command('shelve',
631 632 [('A', 'addremove', None,
632 633 _('mark new/missing files as added/removed before shelving')),
633 634 ('', 'cleanup', None,
634 635 _('delete all shelved changes')),
635 636 ('', 'date', '',
636 637 _('shelve with the specified commit date'), _('DATE')),
637 638 ('d', 'delete', None,
638 639 _('delete the named shelved change(s)')),
639 640 ('e', 'edit', False,
640 641 _('invoke editor on commit messages')),
641 642 ('l', 'list', None,
642 643 _('list current shelves')),
643 644 ('m', 'message', '',
644 645 _('use text as shelve message'), _('TEXT')),
645 646 ('n', 'name', '',
646 647 _('use the given name for the shelved commit'), _('NAME')),
647 648 ('p', 'patch', None,
648 649 _('show patch')),
649 650 ('', 'stat', None,
650 651 _('output diffstat-style summary of changes'))] + commands.walkopts,
651 652 _('hg shelve [OPTION]... [FILE]...'))
652 653 def shelvecmd(ui, repo, *pats, **opts):
653 654 '''save and set aside changes from the working directory
654 655
655 656 Shelving takes files that "hg status" reports as not clean, saves
656 657 the modifications to a bundle (a shelved change), and reverts the
657 658 files so that their state in the working directory becomes clean.
658 659
659 660 To restore these changes to the working directory, using "hg
660 661 unshelve"; this will work even if you switch to a different
661 662 commit.
662 663
663 664 When no files are specified, "hg shelve" saves all not-clean
664 665 files. If specific files or directories are named, only changes to
665 666 those files are shelved.
666 667
667 668 Each shelved change has a name that makes it easier to find later.
668 669 The name of a shelved change defaults to being based on the active
669 670 bookmark, or if there is no active bookmark, the current named
670 671 branch. To specify a different name, use ``--name``.
671 672
672 673 To see a list of existing shelved changes, use the ``--list``
673 674 option. For each shelved change, this will print its name, age,
674 675 and description; use ``--patch`` or ``--stat`` for more details.
675 676
676 677 To delete specific shelved changes, use ``--delete``. To delete
677 678 all shelved changes, use ``--cleanup``.
678 679 '''
679 680 cmdutil.checkunfinished(repo)
680 681
681 682 allowables = [
682 683 ('addremove', 'create'), # 'create' is pseudo action
683 684 ('cleanup', 'cleanup'),
684 685 # ('date', 'create'), # ignored for passing '--date "0 0"' in tests
685 686 ('delete', 'delete'),
686 687 ('edit', 'create'),
687 688 ('list', 'list'),
688 689 ('message', 'create'),
689 690 ('name', 'create'),
690 691 ('patch', 'list'),
691 692 ('stat', 'list'),
692 693 ]
693 694 def checkopt(opt):
694 695 if opts[opt]:
695 696 for i, allowable in allowables:
696 697 if opts[i] and opt != allowable:
697 698 raise util.Abort(_("options '--%s' and '--%s' may not be "
698 699 "used together") % (opt, i))
699 700 return True
700 701 if checkopt('cleanup'):
701 702 if pats:
702 703 raise util.Abort(_("cannot specify names when using '--cleanup'"))
703 704 return cleanupcmd(ui, repo)
704 705 elif checkopt('delete'):
705 706 return deletecmd(ui, repo, pats)
706 707 elif checkopt('list'):
707 708 return listcmd(ui, repo, pats, opts)
708 709 else:
709 710 for i in ('patch', 'stat'):
710 711 if opts[i]:
711 712 raise util.Abort(_("option '--%s' may not be "
712 713 "used when shelving a change") % (i,))
713 714 return createcmd(ui, repo, pats, opts)
714 715
715 716 def extsetup(ui):
716 717 cmdutil.unfinishedstates.append(
717 718 [shelvedstate._filename, False, False,
718 719 _('unshelve already in progress'),
719 720 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now