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