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