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