##// END OF EJS Templates
with: use context manager for wlock in unshelve
Bryan O'Sullivan -
r27837:496ca4de default
parent child Browse files
Show More
@@ -1,844 +1,841
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 357 with repo.wlock():
358 358 for (name, _type) in repo.vfs.readdir('shelved'):
359 359 suffix = name.rsplit('.', 1)[-1]
360 360 if suffix in ('hg', 'patch'):
361 361 shelvedfile(repo, name).movetobackup()
362 362 cleanupoldbackups(repo)
363 363
364 364 def deletecmd(ui, repo, pats):
365 365 """subcommand that deletes a specific shelve"""
366 366 if not pats:
367 367 raise error.Abort(_('no shelved changes specified!'))
368 368 with repo.wlock():
369 369 try:
370 370 for name in pats:
371 371 for suffix in 'hg patch'.split():
372 372 shelvedfile(repo, name, suffix).movetobackup()
373 373 cleanupoldbackups(repo)
374 374 except OSError as err:
375 375 if err.errno != errno.ENOENT:
376 376 raise
377 377 raise error.Abort(_("shelved change '%s' not found") % name)
378 378
379 379 def listshelves(repo):
380 380 """return all shelves in repo as list of (time, filename)"""
381 381 try:
382 382 names = repo.vfs.readdir('shelved')
383 383 except OSError as err:
384 384 if err.errno != errno.ENOENT:
385 385 raise
386 386 return []
387 387 info = []
388 388 for (name, _type) in names:
389 389 pfx, sfx = name.rsplit('.', 1)
390 390 if not pfx or sfx != 'patch':
391 391 continue
392 392 st = shelvedfile(repo, name).stat()
393 393 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
394 394 return sorted(info, reverse=True)
395 395
396 396 def listcmd(ui, repo, pats, opts):
397 397 """subcommand that displays the list of shelves"""
398 398 pats = set(pats)
399 399 width = 80
400 400 if not ui.plain():
401 401 width = ui.termwidth()
402 402 namelabel = 'shelve.newest'
403 403 for mtime, name in listshelves(repo):
404 404 sname = util.split(name)[1]
405 405 if pats and sname not in pats:
406 406 continue
407 407 ui.write(sname, label=namelabel)
408 408 namelabel = 'shelve.name'
409 409 if ui.quiet:
410 410 ui.write('\n')
411 411 continue
412 412 ui.write(' ' * (16 - len(sname)))
413 413 used = 16
414 414 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
415 415 ui.write(age, label='shelve.age')
416 416 ui.write(' ' * (12 - len(age)))
417 417 used += 12
418 418 with open(name + '.patch', 'rb') as fp:
419 419 while True:
420 420 line = fp.readline()
421 421 if not line:
422 422 break
423 423 if not line.startswith('#'):
424 424 desc = line.rstrip()
425 425 if ui.formatted():
426 426 desc = util.ellipsis(desc, width - used)
427 427 ui.write(desc)
428 428 break
429 429 ui.write('\n')
430 430 if not (opts['patch'] or opts['stat']):
431 431 continue
432 432 difflines = fp.readlines()
433 433 if opts['patch']:
434 434 for chunk, label in patch.difflabel(iter, difflines):
435 435 ui.write(chunk, label=label)
436 436 if opts['stat']:
437 437 for chunk, label in patch.diffstatui(difflines, width=width,
438 438 git=True):
439 439 ui.write(chunk, label=label)
440 440
441 441 def singlepatchcmds(ui, repo, pats, opts, subcommand):
442 442 """subcommand that displays a single shelf"""
443 443 if len(pats) != 1:
444 444 raise error.Abort(_("--%s expects a single shelf") % subcommand)
445 445 shelfname = pats[0]
446 446
447 447 if not shelvedfile(repo, shelfname, 'patch').exists():
448 448 raise error.Abort(_("cannot find shelf %s") % shelfname)
449 449
450 450 listcmd(ui, repo, pats, opts)
451 451
452 452 def checkparents(repo, state):
453 453 """check parent while resuming an unshelve"""
454 454 if state.parents != repo.dirstate.parents():
455 455 raise error.Abort(_('working directory parents do not match unshelve '
456 456 'state'))
457 457
458 458 def pathtofiles(repo, files):
459 459 cwd = repo.getcwd()
460 460 return [repo.pathto(f, cwd) for f in files]
461 461
462 462 def unshelveabort(ui, repo, state, opts):
463 463 """subcommand that abort an in-progress unshelve"""
464 464 lock = None
465 465 try:
466 466 checkparents(repo, state)
467 467
468 468 util.rename(repo.join('unshelverebasestate'),
469 469 repo.join('rebasestate'))
470 470 try:
471 471 rebase.rebase(ui, repo, **{
472 472 'abort' : True
473 473 })
474 474 except Exception:
475 475 util.rename(repo.join('rebasestate'),
476 476 repo.join('unshelverebasestate'))
477 477 raise
478 478
479 479 lock = repo.lock()
480 480
481 481 mergefiles(ui, repo, state.wctx, state.pendingctx)
482 482
483 483 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
484 484 finally:
485 485 shelvedstate.clear(repo)
486 486 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
487 487 lockmod.release(lock)
488 488
489 489 def mergefiles(ui, repo, wctx, shelvectx):
490 490 """updates to wctx and merges the changes from shelvectx into the
491 491 dirstate."""
492 492 oldquiet = ui.quiet
493 493 try:
494 494 ui.quiet = True
495 495 hg.update(repo, wctx.node())
496 496 files = []
497 497 files.extend(shelvectx.files())
498 498 files.extend(shelvectx.parents()[0].files())
499 499
500 500 # revert will overwrite unknown files, so move them out of the way
501 501 for file in repo.status(unknown=True).unknown:
502 502 if file in files:
503 503 util.rename(file, scmutil.origpath(ui, repo, file))
504 504 ui.pushbuffer(True)
505 505 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
506 506 *pathtofiles(repo, files),
507 507 **{'no_backup': True})
508 508 ui.popbuffer()
509 509 finally:
510 510 ui.quiet = oldquiet
511 511
512 512 def unshelvecleanup(ui, repo, name, opts):
513 513 """remove related files after an unshelve"""
514 514 if not opts['keep']:
515 515 for filetype in 'hg patch'.split():
516 516 shelvedfile(repo, name, filetype).movetobackup()
517 517 cleanupoldbackups(repo)
518 518
519 519 def unshelvecontinue(ui, repo, state, opts):
520 520 """subcommand to continue an in-progress unshelve"""
521 521 # We're finishing off a merge. First parent is our original
522 522 # parent, second is the temporary "fake" commit we're unshelving.
523 523 lock = None
524 524 try:
525 525 checkparents(repo, state)
526 526 ms = merge.mergestate.read(repo)
527 527 if [f for f in ms if ms[f] == 'u']:
528 528 raise error.Abort(
529 529 _("unresolved conflicts, can't continue"),
530 530 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
531 531
532 532 lock = repo.lock()
533 533
534 534 util.rename(repo.join('unshelverebasestate'),
535 535 repo.join('rebasestate'))
536 536 try:
537 537 rebase.rebase(ui, repo, **{
538 538 'continue' : True
539 539 })
540 540 except Exception:
541 541 util.rename(repo.join('rebasestate'),
542 542 repo.join('unshelverebasestate'))
543 543 raise
544 544
545 545 shelvectx = repo['tip']
546 546 if not shelvectx in state.pendingctx.children():
547 547 # rebase was a no-op, so it produced no child commit
548 548 shelvectx = state.pendingctx
549 549 else:
550 550 # only strip the shelvectx if the rebase produced it
551 551 state.stripnodes.append(shelvectx.node())
552 552
553 553 mergefiles(ui, repo, state.wctx, shelvectx)
554 554
555 555 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
556 556 shelvedstate.clear(repo)
557 557 unshelvecleanup(ui, repo, state.name, opts)
558 558 ui.status(_("unshelve of '%s' complete\n") % state.name)
559 559 finally:
560 560 lockmod.release(lock)
561 561
562 562 @command('unshelve',
563 563 [('a', 'abort', None,
564 564 _('abort an incomplete unshelve operation')),
565 565 ('c', 'continue', None,
566 566 _('continue an incomplete unshelve operation')),
567 567 ('k', 'keep', None,
568 568 _('keep shelve after unshelving')),
569 569 ('t', 'tool', '', _('specify merge tool')),
570 570 ('', 'date', '',
571 571 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
572 572 _('hg unshelve [SHELVED]'))
573 573 def unshelve(ui, repo, *shelved, **opts):
574 574 """restore a shelved change to the working directory
575 575
576 576 This command accepts an optional name of a shelved change to
577 577 restore. If none is given, the most recent shelved change is used.
578 578
579 579 If a shelved change is applied successfully, the bundle that
580 580 contains the shelved changes is moved to a backup location
581 581 (.hg/shelve-backup).
582 582
583 583 Since you can restore a shelved change on top of an arbitrary
584 584 commit, it is possible that unshelving will result in a conflict
585 585 between your changes and the commits you are unshelving onto. If
586 586 this occurs, you must resolve the conflict, then use
587 587 ``--continue`` to complete the unshelve operation. (The bundle
588 588 will not be moved until you successfully complete the unshelve.)
589 589
590 590 (Alternatively, you can use ``--abort`` to abandon an unshelve
591 591 that causes a conflict. This reverts the unshelved changes, and
592 592 leaves the bundle in place.)
593 593
594 594 After a successful unshelve, the shelved changes are stored in a
595 595 backup directory. Only the N most recent backups are kept. N
596 596 defaults to 10 but can be overridden using the ``shelve.maxbackups``
597 597 configuration option.
598 598
599 599 .. container:: verbose
600 600
601 601 Timestamp in seconds is used to decide order of backups. More
602 602 than ``maxbackups`` backups are kept, if same timestamp
603 603 prevents from deciding exact order of them, for safety.
604 604 """
605 wlock = repo.wlock()
606 try:
605 with repo.wlock():
607 606 return _dounshelve(ui, repo, *shelved, **opts)
608 finally:
609 lockmod.release(wlock)
610 607
611 608 def _dounshelve(ui, repo, *shelved, **opts):
612 609 abortf = opts['abort']
613 610 continuef = opts['continue']
614 611 if not abortf and not continuef:
615 612 cmdutil.checkunfinished(repo)
616 613
617 614 if abortf or continuef:
618 615 if abortf and continuef:
619 616 raise error.Abort(_('cannot use both abort and continue'))
620 617 if shelved:
621 618 raise error.Abort(_('cannot combine abort/continue with '
622 619 'naming a shelved change'))
623 620 if abortf and opts.get('tool', False):
624 621 ui.warn(_('tool option will be ignored\n'))
625 622
626 623 try:
627 624 state = shelvedstate.load(repo)
628 625 except IOError as err:
629 626 if err.errno != errno.ENOENT:
630 627 raise
631 628 raise error.Abort(_('no unshelve operation underway'))
632 629
633 630 if abortf:
634 631 return unshelveabort(ui, repo, state, opts)
635 632 elif continuef:
636 633 return unshelvecontinue(ui, repo, state, opts)
637 634 elif len(shelved) > 1:
638 635 raise error.Abort(_('can only unshelve one change at a time'))
639 636 elif not shelved:
640 637 shelved = listshelves(repo)
641 638 if not shelved:
642 639 raise error.Abort(_('no shelved changes to apply!'))
643 640 basename = util.split(shelved[0][1])[1]
644 641 ui.status(_("unshelving change '%s'\n") % basename)
645 642 else:
646 643 basename = shelved[0]
647 644
648 645 if not shelvedfile(repo, basename, 'patch').exists():
649 646 raise error.Abort(_("shelved change '%s' not found") % basename)
650 647
651 648 oldquiet = ui.quiet
652 649 lock = tr = None
653 650 forcemerge = ui.backupconfig('ui', 'forcemerge')
654 651 try:
655 652 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
656 653 lock = repo.lock()
657 654
658 655 tr = repo.transaction('unshelve', report=lambda x: None)
659 656 oldtiprev = len(repo)
660 657
661 658 pctx = repo['.']
662 659 tmpwctx = pctx
663 660 # The goal is to have a commit structure like so:
664 661 # ...-> pctx -> tmpwctx -> shelvectx
665 662 # where tmpwctx is an optional commit with the user's pending changes
666 663 # and shelvectx is the unshelved changes. Then we merge it all down
667 664 # to the original pctx.
668 665
669 666 # Store pending changes in a commit
670 667 s = repo.status()
671 668 if s.modified or s.added or s.removed or s.deleted:
672 669 ui.status(_("temporarily committing pending changes "
673 670 "(restore with 'hg unshelve --abort')\n"))
674 671 def commitfunc(ui, repo, message, match, opts):
675 672 hasmq = util.safehasattr(repo, 'mq')
676 673 if hasmq:
677 674 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
678 675
679 676 backup = repo.ui.backupconfig('phases', 'new-commit')
680 677 try:
681 678 repo.ui.setconfig('phases', 'new-commit', phases.secret)
682 679 return repo.commit(message, 'shelve@localhost',
683 680 opts.get('date'), match)
684 681 finally:
685 682 repo.ui.restoreconfig(backup)
686 683 if hasmq:
687 684 repo.mq.checkapplied = saved
688 685
689 686 tempopts = {}
690 687 tempopts['message'] = "pending changes temporary commit"
691 688 tempopts['date'] = opts.get('date')
692 689 ui.quiet = True
693 690 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
694 691 tmpwctx = repo[node]
695 692
696 693 ui.quiet = True
697 694 shelvedfile(repo, basename, 'hg').applybundle()
698 695
699 696 ui.quiet = oldquiet
700 697
701 698 shelvectx = repo['tip']
702 699
703 700 # If the shelve is not immediately on top of the commit
704 701 # we'll be merging with, rebase it to be on top.
705 702 if tmpwctx.node() != shelvectx.parents()[0].node():
706 703 ui.status(_('rebasing shelved changes\n'))
707 704 try:
708 705 rebase.rebase(ui, repo, **{
709 706 'rev' : [shelvectx.rev()],
710 707 'dest' : str(tmpwctx.rev()),
711 708 'keep' : True,
712 709 'tool' : opts.get('tool', ''),
713 710 })
714 711 except error.InterventionRequired:
715 712 tr.close()
716 713
717 714 stripnodes = [repo.changelog.node(rev)
718 715 for rev in xrange(oldtiprev, len(repo))]
719 716 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
720 717
721 718 util.rename(repo.join('rebasestate'),
722 719 repo.join('unshelverebasestate'))
723 720 raise error.InterventionRequired(
724 721 _("unresolved conflicts (see 'hg resolve', then "
725 722 "'hg unshelve --continue')"))
726 723
727 724 # refresh ctx after rebase completes
728 725 shelvectx = repo['tip']
729 726
730 727 if not shelvectx in tmpwctx.children():
731 728 # rebase was a no-op, so it produced no child commit
732 729 shelvectx = tmpwctx
733 730
734 731 mergefiles(ui, repo, pctx, shelvectx)
735 732 shelvedstate.clear(repo)
736 733
737 734 # The transaction aborting will strip all the commits for us,
738 735 # but it doesn't update the inmemory structures, so addchangegroup
739 736 # hooks still fire and try to operate on the missing commits.
740 737 # Clean up manually to prevent this.
741 738 repo.unfiltered().changelog.strip(oldtiprev, tr)
742 739
743 740 unshelvecleanup(ui, repo, basename, opts)
744 741
745 742 _aborttransaction(repo)
746 743 finally:
747 744 ui.quiet = oldquiet
748 745 if tr:
749 746 tr.release()
750 747 lockmod.release(lock)
751 748 ui.restoreconfig(forcemerge)
752 749
753 750 @command('shelve',
754 751 [('A', 'addremove', None,
755 752 _('mark new/missing files as added/removed before shelving')),
756 753 ('', 'cleanup', None,
757 754 _('delete all shelved changes')),
758 755 ('', 'date', '',
759 756 _('shelve with the specified commit date'), _('DATE')),
760 757 ('d', 'delete', None,
761 758 _('delete the named shelved change(s)')),
762 759 ('e', 'edit', False,
763 760 _('invoke editor on commit messages')),
764 761 ('l', 'list', None,
765 762 _('list current shelves')),
766 763 ('m', 'message', '',
767 764 _('use text as shelve message'), _('TEXT')),
768 765 ('n', 'name', '',
769 766 _('use the given name for the shelved commit'), _('NAME')),
770 767 ('p', 'patch', None,
771 768 _('show patch')),
772 769 ('i', 'interactive', None,
773 770 _('interactive mode, only works while creating a shelve')),
774 771 ('', 'stat', None,
775 772 _('output diffstat-style summary of changes'))] + commands.walkopts,
776 773 _('hg shelve [OPTION]... [FILE]...'))
777 774 def shelvecmd(ui, repo, *pats, **opts):
778 775 '''save and set aside changes from the working directory
779 776
780 777 Shelving takes files that "hg status" reports as not clean, saves
781 778 the modifications to a bundle (a shelved change), and reverts the
782 779 files so that their state in the working directory becomes clean.
783 780
784 781 To restore these changes to the working directory, using "hg
785 782 unshelve"; this will work even if you switch to a different
786 783 commit.
787 784
788 785 When no files are specified, "hg shelve" saves all not-clean
789 786 files. If specific files or directories are named, only changes to
790 787 those files are shelved.
791 788
792 789 Each shelved change has a name that makes it easier to find later.
793 790 The name of a shelved change defaults to being based on the active
794 791 bookmark, or if there is no active bookmark, the current named
795 792 branch. To specify a different name, use ``--name``.
796 793
797 794 To see a list of existing shelved changes, use the ``--list``
798 795 option. For each shelved change, this will print its name, age,
799 796 and description; use ``--patch`` or ``--stat`` for more details.
800 797
801 798 To delete specific shelved changes, use ``--delete``. To delete
802 799 all shelved changes, use ``--cleanup``.
803 800 '''
804 801 allowables = [
805 802 ('addremove', set(['create'])), # 'create' is pseudo action
806 803 ('cleanup', set(['cleanup'])),
807 804 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
808 805 ('delete', set(['delete'])),
809 806 ('edit', set(['create'])),
810 807 ('list', set(['list'])),
811 808 ('message', set(['create'])),
812 809 ('name', set(['create'])),
813 810 ('patch', set(['patch', 'list'])),
814 811 ('stat', set(['stat', 'list'])),
815 812 ]
816 813 def checkopt(opt):
817 814 if opts[opt]:
818 815 for i, allowable in allowables:
819 816 if opts[i] and opt not in allowable:
820 817 raise error.Abort(_("options '--%s' and '--%s' may not be "
821 818 "used together") % (opt, i))
822 819 return True
823 820 if checkopt('cleanup'):
824 821 if pats:
825 822 raise error.Abort(_("cannot specify names when using '--cleanup'"))
826 823 return cleanupcmd(ui, repo)
827 824 elif checkopt('delete'):
828 825 return deletecmd(ui, repo, pats)
829 826 elif checkopt('list'):
830 827 return listcmd(ui, repo, pats, opts)
831 828 elif checkopt('patch'):
832 829 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
833 830 elif checkopt('stat'):
834 831 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
835 832 else:
836 833 return createcmd(ui, repo, pats, opts)
837 834
838 835 def extsetup(ui):
839 836 cmdutil.unfinishedstates.append(
840 837 [shelvedstate._filename, False, False,
841 838 _('unshelve already in progress'),
842 839 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
843 840 cmdutil.afterresolvedstates.append(
844 841 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now