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