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