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