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