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