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