##// END OF EJS Templates
shelve: move actual created commit shelving to a separate function...
Kostia Balytskyi -
r30383:455f7856 default
parent child Browse files
Show More
@@ -1,937 +1,939
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 from __future__ import absolute_import
24 24
25 25 import collections
26 26 import errno
27 27 import itertools
28 28
29 29 from mercurial.i18n import _
30 30 from mercurial import (
31 31 bundle2,
32 32 bundlerepo,
33 33 changegroup,
34 34 cmdutil,
35 35 commands,
36 36 error,
37 37 exchange,
38 38 hg,
39 39 lock as lockmod,
40 40 mdiff,
41 41 merge,
42 42 node as nodemod,
43 43 patch,
44 44 phases,
45 45 repair,
46 46 scmutil,
47 47 templatefilters,
48 48 util,
49 49 )
50 50
51 51 from . import (
52 52 rebase,
53 53 )
54 54
55 55 cmdtable = {}
56 56 command = cmdutil.command(cmdtable)
57 57 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
58 58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 59 # be specifying the version(s) of Mercurial they are tested with, or
60 60 # leave the attribute unspecified.
61 61 testedwith = 'ships-with-hg-core'
62 62
63 63 backupdir = 'shelve-backup'
64 64 shelvedir = 'shelved'
65 65 shelvefileextensions = ['hg', 'patch']
66 66
67 67 # we never need the user, so we use a
68 68 # generic user for all shelve operations
69 69 shelveuser = 'shelve@localhost'
70 70
71 71 class shelvedfile(object):
72 72 """Helper for the file storing a single shelve
73 73
74 74 Handles common functions on shelve files (.hg/.patch) using
75 75 the vfs layer"""
76 76 def __init__(self, repo, name, filetype=None):
77 77 self.repo = repo
78 78 self.name = name
79 79 self.vfs = scmutil.vfs(repo.join(shelvedir))
80 80 self.backupvfs = scmutil.vfs(repo.join(backupdir))
81 81 self.ui = self.repo.ui
82 82 if filetype:
83 83 self.fname = name + '.' + filetype
84 84 else:
85 85 self.fname = name
86 86
87 87 def exists(self):
88 88 return self.vfs.exists(self.fname)
89 89
90 90 def filename(self):
91 91 return self.vfs.join(self.fname)
92 92
93 93 def backupfilename(self):
94 94 def gennames(base):
95 95 yield base
96 96 base, ext = base.rsplit('.', 1)
97 97 for i in itertools.count(1):
98 98 yield '%s-%d.%s' % (base, i, ext)
99 99
100 100 name = self.backupvfs.join(self.fname)
101 101 for n in gennames(name):
102 102 if not self.backupvfs.exists(n):
103 103 return n
104 104
105 105 def movetobackup(self):
106 106 if not self.backupvfs.isdir():
107 107 self.backupvfs.makedir()
108 108 util.rename(self.filename(), self.backupfilename())
109 109
110 110 def stat(self):
111 111 return self.vfs.stat(self.fname)
112 112
113 113 def opener(self, mode='rb'):
114 114 try:
115 115 return self.vfs(self.fname, mode)
116 116 except IOError as err:
117 117 if err.errno != errno.ENOENT:
118 118 raise
119 119 raise error.Abort(_("shelved change '%s' not found") % self.name)
120 120
121 121 def applybundle(self):
122 122 fp = self.opener()
123 123 try:
124 124 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
125 125 if not isinstance(gen, bundle2.unbundle20):
126 126 gen.apply(self.repo, 'unshelve',
127 127 'bundle:' + self.vfs.join(self.fname),
128 128 targetphase=phases.secret)
129 129 if isinstance(gen, bundle2.unbundle20):
130 130 bundle2.applybundle(self.repo, gen,
131 131 self.repo.currenttransaction(),
132 132 source='unshelve',
133 133 url='bundle:' + self.vfs.join(self.fname))
134 134 finally:
135 135 fp.close()
136 136
137 137 def bundlerepo(self):
138 138 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
139 139 self.vfs.join(self.fname))
140 140 def writebundle(self, bases, node):
141 141 cgversion = changegroup.safeversion(self.repo)
142 142 if cgversion == '01':
143 143 btype = 'HG10BZ'
144 144 compression = None
145 145 else:
146 146 btype = 'HG20'
147 147 compression = 'BZ'
148 148
149 149 cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
150 150 version=cgversion)
151 151 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
152 152 compression=compression)
153 153
154 154 class shelvedstate(object):
155 155 """Handle persistence during unshelving operations.
156 156
157 157 Handles saving and restoring a shelved state. Ensures that different
158 158 versions of a shelved state are possible and handles them appropriately.
159 159 """
160 160 _version = 1
161 161 _filename = 'shelvedstate'
162 162
163 163 @classmethod
164 164 def load(cls, repo):
165 165 fp = repo.vfs(cls._filename)
166 166 try:
167 167 version = int(fp.readline().strip())
168 168
169 169 if version != cls._version:
170 170 raise error.Abort(_('this version of shelve is incompatible '
171 171 'with the version used in this repo'))
172 172 name = fp.readline().strip()
173 173 wctx = nodemod.bin(fp.readline().strip())
174 174 pendingctx = nodemod.bin(fp.readline().strip())
175 175 parents = [nodemod.bin(h) for h in fp.readline().split()]
176 176 stripnodes = [nodemod.bin(h) for h in fp.readline().split()]
177 177 branchtorestore = fp.readline().strip()
178 178 except (ValueError, TypeError) as err:
179 179 raise error.CorruptedState(str(err))
180 180 finally:
181 181 fp.close()
182 182
183 183 try:
184 184 obj = cls()
185 185 obj.name = name
186 186 obj.wctx = repo[wctx]
187 187 obj.pendingctx = repo[pendingctx]
188 188 obj.parents = parents
189 189 obj.stripnodes = stripnodes
190 190 obj.branchtorestore = branchtorestore
191 191 except error.RepoLookupError as err:
192 192 raise error.CorruptedState(str(err))
193 193
194 194 return obj
195 195
196 196 @classmethod
197 197 def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
198 198 branchtorestore):
199 199 fp = repo.vfs(cls._filename, 'wb')
200 200 fp.write('%i\n' % cls._version)
201 201 fp.write('%s\n' % name)
202 202 fp.write('%s\n' % nodemod.hex(originalwctx.node()))
203 203 fp.write('%s\n' % nodemod.hex(pendingctx.node()))
204 204 fp.write('%s\n' %
205 205 ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]))
206 206 fp.write('%s\n' %
207 207 ' '.join([nodemod.hex(n) for n in stripnodes]))
208 208 fp.write('%s\n' % branchtorestore)
209 209 fp.close()
210 210
211 211 @classmethod
212 212 def clear(cls, repo):
213 213 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
214 214
215 215 def cleanupoldbackups(repo):
216 216 vfs = scmutil.vfs(repo.join(backupdir))
217 217 maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
218 218 hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')]
219 219 hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
220 220 if 0 < maxbackups and maxbackups < len(hgfiles):
221 221 bordermtime = hgfiles[-maxbackups][0]
222 222 else:
223 223 bordermtime = None
224 224 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
225 225 if mtime == bordermtime:
226 226 # keep it, because timestamp can't decide exact order of backups
227 227 continue
228 228 base = f[:-3]
229 229 for ext in shelvefileextensions:
230 230 try:
231 231 vfs.unlink(base + '.' + ext)
232 232 except OSError as err:
233 233 if err.errno != errno.ENOENT:
234 234 raise
235 235
236 236 def _aborttransaction(repo):
237 237 '''Abort current transaction for shelve/unshelve, but keep dirstate
238 238 '''
239 239 tr = repo.currenttransaction()
240 240 repo.dirstate.savebackup(tr, suffix='.shelve')
241 241 tr.abort()
242 242 repo.dirstate.restorebackup(None, suffix='.shelve')
243 243
244 244 def createcmd(ui, repo, pats, opts):
245 245 """subcommand that creates a new shelve"""
246 246 with repo.wlock():
247 247 cmdutil.checkunfinished(repo)
248 248 return _docreatecmd(ui, repo, pats, opts)
249 249
250 250 def getshelvename(repo, parent, opts):
251 251 """Decide on the name this shelve is going to have"""
252 252 def gennames():
253 253 yield label
254 254 for i in xrange(1, 100):
255 255 yield '%s-%02d' % (label, i)
256 256 name = opts.get('name')
257 257 label = repo._activebookmark or parent.branch() or 'default'
258 258 # slashes aren't allowed in filenames, therefore we rename it
259 259 label = label.replace('/', '_')
260 260
261 261 if name:
262 262 if shelvedfile(repo, name, 'hg').exists():
263 263 e = _("a shelved change named '%s' already exists") % name
264 264 raise error.Abort(e)
265 265 else:
266 266 for n in gennames():
267 267 if not shelvedfile(repo, n, 'hg').exists():
268 268 name = n
269 269 break
270 270 else:
271 271 raise error.Abort(_("too many shelved changes named '%s'") % label)
272 272
273 273 # ensure we are not creating a subdirectory or a hidden file
274 274 if '/' in name or '\\' in name:
275 275 raise error.Abort(_('shelved change names may not contain slashes'))
276 276 if name.startswith('.'):
277 277 raise error.Abort(_("shelved change names may not start with '.'"))
278 278 return name
279 279
280 280 def mutableancestors(ctx):
281 281 """return all mutable ancestors for ctx (included)
282 282
283 283 Much faster than the revset ancestors(ctx) & draft()"""
284 284 seen = set([nodemod.nullrev])
285 285 visit = collections.deque()
286 286 visit.append(ctx)
287 287 while visit:
288 288 ctx = visit.popleft()
289 289 yield ctx.node()
290 290 for parent in ctx.parents():
291 291 rev = parent.rev()
292 292 if rev not in seen:
293 293 seen.add(rev)
294 294 if parent.mutable():
295 295 visit.append(parent)
296 296
297 297 def getcommitfunc(extra, interactive, editor=False):
298 298 def commitfunc(ui, repo, message, match, opts):
299 299 hasmq = util.safehasattr(repo, 'mq')
300 300 if hasmq:
301 301 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
302 302 backup = repo.ui.backupconfig('phases', 'new-commit')
303 303 try:
304 304 repo.ui.setconfig('phases', 'new-commit', phases.secret)
305 305 editor_ = False
306 306 if editor:
307 307 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
308 308 **opts)
309 309 return repo.commit(message, shelveuser, opts.get('date'), match,
310 310 editor=editor_, extra=extra)
311 311 finally:
312 312 repo.ui.restoreconfig(backup)
313 313 if hasmq:
314 314 repo.mq.checkapplied = saved
315 315
316 316 def interactivecommitfunc(ui, repo, *pats, **opts):
317 317 match = scmutil.match(repo['.'], pats, {})
318 318 message = opts['message']
319 319 return commitfunc(ui, repo, message, match, opts)
320 320
321 321 return interactivecommitfunc if interactive else commitfunc
322 322
323 323 def _nothingtoshelvemessaging(ui, repo, pats, opts):
324 324 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
325 325 if stat.deleted:
326 326 ui.status(_("nothing changed (%d missing files, see "
327 327 "'hg status')\n") % len(stat.deleted))
328 328 else:
329 329 ui.status(_("nothing changed\n"))
330 330
331 def _shelvecreatedcommit(repo, node, name):
332 bases = list(mutableancestors(repo[node]))
333 shelvedfile(repo, name, 'hg').writebundle(bases, node)
334 cmdutil.export(repo, [node],
335 fp=shelvedfile(repo, name, 'patch').opener('wb'),
336 opts=mdiff.diffopts(git=True))
337
331 338 def _docreatecmd(ui, repo, pats, opts):
332 339 wctx = repo[None]
333 340 parents = wctx.parents()
334 341 if len(parents) > 1:
335 342 raise error.Abort(_('cannot shelve while merging'))
336 343 parent = parents[0]
337 344 origbranch = wctx.branch()
338 345
339 346 if parent.node() != nodemod.nullid:
340 347 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
341 348 else:
342 349 desc = '(changes in empty repository)'
343 350
344 351 if not opts.get('message'):
345 352 opts['message'] = desc
346 353
347 354 lock = tr = None
348 355 try:
349 356 lock = repo.lock()
350 357
351 358 # use an uncommitted transaction to generate the bundle to avoid
352 359 # pull races. ensure we don't print the abort message to stderr.
353 360 tr = repo.transaction('commit', report=lambda x: None)
354 361
355 362 interactive = opts.get('interactive', False)
356 363 includeunknown = (opts.get('unknown', False) and
357 364 not opts.get('addremove', False))
358 365
359 366 name = getshelvename(repo, parent, opts)
360 367 extra={}
361 368 if includeunknown:
362 369 s = repo.status(match=scmutil.match(repo[None], pats, opts),
363 370 unknown=True)
364 371 if s.unknown:
365 372 extra['shelve_unknown'] = '\0'.join(s.unknown)
366 373 repo[None].add(s.unknown)
367 374
368 375 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
369 376 # In non-bare shelve we don't store newly created branch
370 377 # at bundled commit
371 378 repo.dirstate.setbranch(repo['.'].branch())
372 379
373 380 commitfunc = getcommitfunc(extra, interactive, editor=True)
374 381 if not interactive:
375 382 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
376 383 else:
377 384 node = cmdutil.dorecord(ui, repo, commitfunc, None,
378 385 False, cmdutil.recordfilter, *pats, **opts)
379 386 if not node:
380 387 _nothingtoshelvemessaging(ui, repo, pats, opts)
381 388 return 1
382 389
383 bases = list(mutableancestors(repo[node]))
384 shelvedfile(repo, name, 'hg').writebundle(bases, node)
385 cmdutil.export(repo, [node],
386 fp=shelvedfile(repo, name, 'patch').opener('wb'),
387 opts=mdiff.diffopts(git=True))
388
390 _shelvecreatedcommit(repo, node, name)
389 391
390 392 if ui.formatted():
391 393 desc = util.ellipsis(desc, ui.termwidth())
392 394 ui.status(_('shelved as %s\n') % name)
393 395 hg.update(repo, parent.node())
394 396 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
395 397 repo.dirstate.setbranch(origbranch)
396 398
397 399 _aborttransaction(repo)
398 400 finally:
399 401 lockmod.release(tr, lock)
400 402
401 403 def _isbareshelve(pats, opts):
402 404 return (not pats
403 405 and not opts.get('interactive', False)
404 406 and not opts.get('include', False)
405 407 and not opts.get('exclude', False))
406 408
407 409 def _iswctxonnewbranch(repo):
408 410 return repo[None].branch() != repo['.'].branch()
409 411
410 412 def cleanupcmd(ui, repo):
411 413 """subcommand that deletes all shelves"""
412 414
413 415 with repo.wlock():
414 416 for (name, _type) in repo.vfs.readdir(shelvedir):
415 417 suffix = name.rsplit('.', 1)[-1]
416 418 if suffix in shelvefileextensions:
417 419 shelvedfile(repo, name).movetobackup()
418 420 cleanupoldbackups(repo)
419 421
420 422 def deletecmd(ui, repo, pats):
421 423 """subcommand that deletes a specific shelve"""
422 424 if not pats:
423 425 raise error.Abort(_('no shelved changes specified!'))
424 426 with repo.wlock():
425 427 try:
426 428 for name in pats:
427 429 for suffix in shelvefileextensions:
428 430 shfile = shelvedfile(repo, name, suffix)
429 431 # patch file is necessary, as it should
430 432 # be present for any kind of shelve,
431 433 # but the .hg file is optional as in future we
432 434 # will add obsolete shelve with does not create a
433 435 # bundle
434 436 if shfile.exists() or suffix == 'patch':
435 437 shfile.movetobackup()
436 438 cleanupoldbackups(repo)
437 439 except OSError as err:
438 440 if err.errno != errno.ENOENT:
439 441 raise
440 442 raise error.Abort(_("shelved change '%s' not found") % name)
441 443
442 444 def listshelves(repo):
443 445 """return all shelves in repo as list of (time, filename)"""
444 446 try:
445 447 names = repo.vfs.readdir(shelvedir)
446 448 except OSError as err:
447 449 if err.errno != errno.ENOENT:
448 450 raise
449 451 return []
450 452 info = []
451 453 for (name, _type) in names:
452 454 pfx, sfx = name.rsplit('.', 1)
453 455 if not pfx or sfx != 'patch':
454 456 continue
455 457 st = shelvedfile(repo, name).stat()
456 458 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
457 459 return sorted(info, reverse=True)
458 460
459 461 def listcmd(ui, repo, pats, opts):
460 462 """subcommand that displays the list of shelves"""
461 463 pats = set(pats)
462 464 width = 80
463 465 if not ui.plain():
464 466 width = ui.termwidth()
465 467 namelabel = 'shelve.newest'
466 468 for mtime, name in listshelves(repo):
467 469 sname = util.split(name)[1]
468 470 if pats and sname not in pats:
469 471 continue
470 472 ui.write(sname, label=namelabel)
471 473 namelabel = 'shelve.name'
472 474 if ui.quiet:
473 475 ui.write('\n')
474 476 continue
475 477 ui.write(' ' * (16 - len(sname)))
476 478 used = 16
477 479 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
478 480 ui.write(age, label='shelve.age')
479 481 ui.write(' ' * (12 - len(age)))
480 482 used += 12
481 483 with open(name + '.patch', 'rb') as fp:
482 484 while True:
483 485 line = fp.readline()
484 486 if not line:
485 487 break
486 488 if not line.startswith('#'):
487 489 desc = line.rstrip()
488 490 if ui.formatted():
489 491 desc = util.ellipsis(desc, width - used)
490 492 ui.write(desc)
491 493 break
492 494 ui.write('\n')
493 495 if not (opts['patch'] or opts['stat']):
494 496 continue
495 497 difflines = fp.readlines()
496 498 if opts['patch']:
497 499 for chunk, label in patch.difflabel(iter, difflines):
498 500 ui.write(chunk, label=label)
499 501 if opts['stat']:
500 502 for chunk, label in patch.diffstatui(difflines, width=width,
501 503 git=True):
502 504 ui.write(chunk, label=label)
503 505
504 506 def singlepatchcmds(ui, repo, pats, opts, subcommand):
505 507 """subcommand that displays a single shelf"""
506 508 if len(pats) != 1:
507 509 raise error.Abort(_("--%s expects a single shelf") % subcommand)
508 510 shelfname = pats[0]
509 511
510 512 if not shelvedfile(repo, shelfname, 'patch').exists():
511 513 raise error.Abort(_("cannot find shelf %s") % shelfname)
512 514
513 515 listcmd(ui, repo, pats, opts)
514 516
515 517 def checkparents(repo, state):
516 518 """check parent while resuming an unshelve"""
517 519 if state.parents != repo.dirstate.parents():
518 520 raise error.Abort(_('working directory parents do not match unshelve '
519 521 'state'))
520 522
521 523 def pathtofiles(repo, files):
522 524 cwd = repo.getcwd()
523 525 return [repo.pathto(f, cwd) for f in files]
524 526
525 527 def unshelveabort(ui, repo, state, opts):
526 528 """subcommand that abort an in-progress unshelve"""
527 529 with repo.lock():
528 530 try:
529 531 checkparents(repo, state)
530 532
531 533 util.rename(repo.join('unshelverebasestate'),
532 534 repo.join('rebasestate'))
533 535 try:
534 536 rebase.rebase(ui, repo, **{
535 537 'abort' : True
536 538 })
537 539 except Exception:
538 540 util.rename(repo.join('rebasestate'),
539 541 repo.join('unshelverebasestate'))
540 542 raise
541 543
542 544 mergefiles(ui, repo, state.wctx, state.pendingctx)
543 545 repair.strip(ui, repo, state.stripnodes, backup=False,
544 546 topic='shelve')
545 547 finally:
546 548 shelvedstate.clear(repo)
547 549 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
548 550
549 551 def mergefiles(ui, repo, wctx, shelvectx):
550 552 """updates to wctx and merges the changes from shelvectx into the
551 553 dirstate."""
552 554 oldquiet = ui.quiet
553 555 try:
554 556 ui.quiet = True
555 557 hg.update(repo, wctx.node())
556 558 files = []
557 559 files.extend(shelvectx.files())
558 560 files.extend(shelvectx.parents()[0].files())
559 561
560 562 # revert will overwrite unknown files, so move them out of the way
561 563 for file in repo.status(unknown=True).unknown:
562 564 if file in files:
563 565 util.rename(file, scmutil.origpath(ui, repo, file))
564 566 ui.pushbuffer(True)
565 567 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
566 568 *pathtofiles(repo, files),
567 569 **{'no_backup': True})
568 570 ui.popbuffer()
569 571 finally:
570 572 ui.quiet = oldquiet
571 573
572 574 def restorebranch(ui, repo, branchtorestore):
573 575 if branchtorestore and branchtorestore != repo.dirstate.branch():
574 576 repo.dirstate.setbranch(branchtorestore)
575 577 ui.status(_('marked working directory as branch %s\n')
576 578 % branchtorestore)
577 579
578 580 def unshelvecleanup(ui, repo, name, opts):
579 581 """remove related files after an unshelve"""
580 582 if not opts.get('keep'):
581 583 for filetype in shelvefileextensions:
582 584 shfile = shelvedfile(repo, name, filetype)
583 585 if shfile.exists():
584 586 shfile.movetobackup()
585 587 cleanupoldbackups(repo)
586 588
587 589 def unshelvecontinue(ui, repo, state, opts):
588 590 """subcommand to continue an in-progress unshelve"""
589 591 # We're finishing off a merge. First parent is our original
590 592 # parent, second is the temporary "fake" commit we're unshelving.
591 593 with repo.lock():
592 594 checkparents(repo, state)
593 595 ms = merge.mergestate.read(repo)
594 596 if [f for f in ms if ms[f] == 'u']:
595 597 raise error.Abort(
596 598 _("unresolved conflicts, can't continue"),
597 599 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
598 600
599 601 util.rename(repo.join('unshelverebasestate'),
600 602 repo.join('rebasestate'))
601 603 try:
602 604 rebase.rebase(ui, repo, **{
603 605 'continue' : True
604 606 })
605 607 except Exception:
606 608 util.rename(repo.join('rebasestate'),
607 609 repo.join('unshelverebasestate'))
608 610 raise
609 611
610 612 shelvectx = repo['tip']
611 613 if not shelvectx in state.pendingctx.children():
612 614 # rebase was a no-op, so it produced no child commit
613 615 shelvectx = state.pendingctx
614 616 else:
615 617 # only strip the shelvectx if the rebase produced it
616 618 state.stripnodes.append(shelvectx.node())
617 619
618 620 mergefiles(ui, repo, state.wctx, shelvectx)
619 621 restorebranch(ui, repo, state.branchtorestore)
620 622
621 623 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
622 624 shelvedstate.clear(repo)
623 625 unshelvecleanup(ui, repo, state.name, opts)
624 626 ui.status(_("unshelve of '%s' complete\n") % state.name)
625 627
626 628 @command('unshelve',
627 629 [('a', 'abort', None,
628 630 _('abort an incomplete unshelve operation')),
629 631 ('c', 'continue', None,
630 632 _('continue an incomplete unshelve operation')),
631 633 ('k', 'keep', None,
632 634 _('keep shelve after unshelving')),
633 635 ('t', 'tool', '', _('specify merge tool')),
634 636 ('', 'date', '',
635 637 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
636 638 _('hg unshelve [SHELVED]'))
637 639 def unshelve(ui, repo, *shelved, **opts):
638 640 """restore a shelved change to the working directory
639 641
640 642 This command accepts an optional name of a shelved change to
641 643 restore. If none is given, the most recent shelved change is used.
642 644
643 645 If a shelved change is applied successfully, the bundle that
644 646 contains the shelved changes is moved to a backup location
645 647 (.hg/shelve-backup).
646 648
647 649 Since you can restore a shelved change on top of an arbitrary
648 650 commit, it is possible that unshelving will result in a conflict
649 651 between your changes and the commits you are unshelving onto. If
650 652 this occurs, you must resolve the conflict, then use
651 653 ``--continue`` to complete the unshelve operation. (The bundle
652 654 will not be moved until you successfully complete the unshelve.)
653 655
654 656 (Alternatively, you can use ``--abort`` to abandon an unshelve
655 657 that causes a conflict. This reverts the unshelved changes, and
656 658 leaves the bundle in place.)
657 659
658 660 If bare shelved change(when no files are specified, without interactive,
659 661 include and exclude option) was done on newly created branch it would
660 662 restore branch information to the working directory.
661 663
662 664 After a successful unshelve, the shelved changes are stored in a
663 665 backup directory. Only the N most recent backups are kept. N
664 666 defaults to 10 but can be overridden using the ``shelve.maxbackups``
665 667 configuration option.
666 668
667 669 .. container:: verbose
668 670
669 671 Timestamp in seconds is used to decide order of backups. More
670 672 than ``maxbackups`` backups are kept, if same timestamp
671 673 prevents from deciding exact order of them, for safety.
672 674 """
673 675 with repo.wlock():
674 676 return _dounshelve(ui, repo, *shelved, **opts)
675 677
676 678 def _dounshelve(ui, repo, *shelved, **opts):
677 679 abortf = opts.get('abort')
678 680 continuef = opts.get('continue')
679 681 if not abortf and not continuef:
680 682 cmdutil.checkunfinished(repo)
681 683
682 684 if abortf or continuef:
683 685 if abortf and continuef:
684 686 raise error.Abort(_('cannot use both abort and continue'))
685 687 if shelved:
686 688 raise error.Abort(_('cannot combine abort/continue with '
687 689 'naming a shelved change'))
688 690 if abortf and opts.get('tool', False):
689 691 ui.warn(_('tool option will be ignored\n'))
690 692
691 693 try:
692 694 state = shelvedstate.load(repo)
693 695 except IOError as err:
694 696 if err.errno != errno.ENOENT:
695 697 raise
696 698 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
697 699 except error.CorruptedState as err:
698 700 ui.debug(str(err) + '\n')
699 701 if continuef:
700 702 msg = _('corrupted shelved state file')
701 703 hint = _('please run hg unshelve --abort to abort unshelve '
702 704 'operation')
703 705 raise error.Abort(msg, hint=hint)
704 706 elif abortf:
705 707 msg = _('could not read shelved state file, your working copy '
706 708 'may be in an unexpected state\nplease update to some '
707 709 'commit\n')
708 710 ui.warn(msg)
709 711 shelvedstate.clear(repo)
710 712 return
711 713
712 714 if abortf:
713 715 return unshelveabort(ui, repo, state, opts)
714 716 elif continuef:
715 717 return unshelvecontinue(ui, repo, state, opts)
716 718 elif len(shelved) > 1:
717 719 raise error.Abort(_('can only unshelve one change at a time'))
718 720 elif not shelved:
719 721 shelved = listshelves(repo)
720 722 if not shelved:
721 723 raise error.Abort(_('no shelved changes to apply!'))
722 724 basename = util.split(shelved[0][1])[1]
723 725 ui.status(_("unshelving change '%s'\n") % basename)
724 726 else:
725 727 basename = shelved[0]
726 728
727 729 if not shelvedfile(repo, basename, 'patch').exists():
728 730 raise error.Abort(_("shelved change '%s' not found") % basename)
729 731
730 732 oldquiet = ui.quiet
731 733 lock = tr = None
732 734 forcemerge = ui.backupconfig('ui', 'forcemerge')
733 735 try:
734 736 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
735 737 lock = repo.lock()
736 738
737 739 tr = repo.transaction('unshelve', report=lambda x: None)
738 740 oldtiprev = len(repo)
739 741
740 742 pctx = repo['.']
741 743 tmpwctx = pctx
742 744 # The goal is to have a commit structure like so:
743 745 # ...-> pctx -> tmpwctx -> shelvectx
744 746 # where tmpwctx is an optional commit with the user's pending changes
745 747 # and shelvectx is the unshelved changes. Then we merge it all down
746 748 # to the original pctx.
747 749
748 750 # Store pending changes in a commit and remember added in case a shelve
749 751 # contains unknown files that are part of the pending change
750 752 s = repo.status()
751 753 addedbefore = frozenset(s.added)
752 754 if s.modified or s.added or s.removed or s.deleted:
753 755 ui.status(_("temporarily committing pending changes "
754 756 "(restore with 'hg unshelve --abort')\n"))
755 757 commitfunc = getcommitfunc(extra=None, interactive=False,
756 758 editor=False)
757 759 tempopts = {}
758 760 tempopts['message'] = "pending changes temporary commit"
759 761 tempopts['date'] = opts.get('date')
760 762 ui.quiet = True
761 763 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
762 764 tmpwctx = repo[node]
763 765
764 766 ui.quiet = True
765 767 shelvedfile(repo, basename, 'hg').applybundle()
766 768
767 769 ui.quiet = oldquiet
768 770
769 771 shelvectx = repo['tip']
770 772
771 773 branchtorestore = ''
772 774 if shelvectx.branch() != shelvectx.p1().branch():
773 775 branchtorestore = shelvectx.branch()
774 776
775 777 # If the shelve is not immediately on top of the commit
776 778 # we'll be merging with, rebase it to be on top.
777 779 if tmpwctx.node() != shelvectx.parents()[0].node():
778 780 ui.status(_('rebasing shelved changes\n'))
779 781 try:
780 782 rebase.rebase(ui, repo, **{
781 783 'rev' : [shelvectx.rev()],
782 784 'dest' : str(tmpwctx.rev()),
783 785 'keep' : True,
784 786 'tool' : opts.get('tool', ''),
785 787 })
786 788 except error.InterventionRequired:
787 789 tr.close()
788 790
789 791 stripnodes = [repo.changelog.node(rev)
790 792 for rev in xrange(oldtiprev, len(repo))]
791 793 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
792 794 branchtorestore)
793 795
794 796 util.rename(repo.join('rebasestate'),
795 797 repo.join('unshelverebasestate'))
796 798 raise error.InterventionRequired(
797 799 _("unresolved conflicts (see 'hg resolve', then "
798 800 "'hg unshelve --continue')"))
799 801
800 802 # refresh ctx after rebase completes
801 803 shelvectx = repo['tip']
802 804
803 805 if not shelvectx in tmpwctx.children():
804 806 # rebase was a no-op, so it produced no child commit
805 807 shelvectx = tmpwctx
806 808
807 809 mergefiles(ui, repo, pctx, shelvectx)
808 810 restorebranch(ui, repo, branchtorestore)
809 811
810 812 # Forget any files that were unknown before the shelve, unknown before
811 813 # unshelve started, but are now added.
812 814 shelveunknown = shelvectx.extra().get('shelve_unknown')
813 815 if shelveunknown:
814 816 shelveunknown = frozenset(shelveunknown.split('\0'))
815 817 addedafter = frozenset(repo.status().added)
816 818 toforget = (addedafter & shelveunknown) - addedbefore
817 819 repo[None].forget(toforget)
818 820
819 821 shelvedstate.clear(repo)
820 822
821 823 # The transaction aborting will strip all the commits for us,
822 824 # but it doesn't update the inmemory structures, so addchangegroup
823 825 # hooks still fire and try to operate on the missing commits.
824 826 # Clean up manually to prevent this.
825 827 repo.unfiltered().changelog.strip(oldtiprev, tr)
826 828
827 829 unshelvecleanup(ui, repo, basename, opts)
828 830
829 831 _aborttransaction(repo)
830 832 finally:
831 833 ui.quiet = oldquiet
832 834 if tr:
833 835 tr.release()
834 836 lockmod.release(lock)
835 837 ui.restoreconfig(forcemerge)
836 838
837 839 @command('shelve',
838 840 [('A', 'addremove', None,
839 841 _('mark new/missing files as added/removed before shelving')),
840 842 ('u', 'unknown', None,
841 843 _('store unknown files in the shelve')),
842 844 ('', 'cleanup', None,
843 845 _('delete all shelved changes')),
844 846 ('', 'date', '',
845 847 _('shelve with the specified commit date'), _('DATE')),
846 848 ('d', 'delete', None,
847 849 _('delete the named shelved change(s)')),
848 850 ('e', 'edit', False,
849 851 _('invoke editor on commit messages')),
850 852 ('l', 'list', None,
851 853 _('list current shelves')),
852 854 ('m', 'message', '',
853 855 _('use text as shelve message'), _('TEXT')),
854 856 ('n', 'name', '',
855 857 _('use the given name for the shelved commit'), _('NAME')),
856 858 ('p', 'patch', None,
857 859 _('show patch')),
858 860 ('i', 'interactive', None,
859 861 _('interactive mode, only works while creating a shelve')),
860 862 ('', 'stat', None,
861 863 _('output diffstat-style summary of changes'))] + commands.walkopts,
862 864 _('hg shelve [OPTION]... [FILE]...'))
863 865 def shelvecmd(ui, repo, *pats, **opts):
864 866 '''save and set aside changes from the working directory
865 867
866 868 Shelving takes files that "hg status" reports as not clean, saves
867 869 the modifications to a bundle (a shelved change), and reverts the
868 870 files so that their state in the working directory becomes clean.
869 871
870 872 To restore these changes to the working directory, using "hg
871 873 unshelve"; this will work even if you switch to a different
872 874 commit.
873 875
874 876 When no files are specified, "hg shelve" saves all not-clean
875 877 files. If specific files or directories are named, only changes to
876 878 those files are shelved.
877 879
878 880 In bare shelve(when no files are specified, without interactive,
879 881 include and exclude option), shelving remembers information if the
880 882 working directory was on newly created branch, in other words working
881 883 directory was on different branch than its first parent. In this
882 884 situation unshelving restores branch information to the working directory.
883 885
884 886 Each shelved change has a name that makes it easier to find later.
885 887 The name of a shelved change defaults to being based on the active
886 888 bookmark, or if there is no active bookmark, the current named
887 889 branch. To specify a different name, use ``--name``.
888 890
889 891 To see a list of existing shelved changes, use the ``--list``
890 892 option. For each shelved change, this will print its name, age,
891 893 and description; use ``--patch`` or ``--stat`` for more details.
892 894
893 895 To delete specific shelved changes, use ``--delete``. To delete
894 896 all shelved changes, use ``--cleanup``.
895 897 '''
896 898 allowables = [
897 899 ('addremove', set(['create'])), # 'create' is pseudo action
898 900 ('unknown', set(['create'])),
899 901 ('cleanup', set(['cleanup'])),
900 902 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
901 903 ('delete', set(['delete'])),
902 904 ('edit', set(['create'])),
903 905 ('list', set(['list'])),
904 906 ('message', set(['create'])),
905 907 ('name', set(['create'])),
906 908 ('patch', set(['patch', 'list'])),
907 909 ('stat', set(['stat', 'list'])),
908 910 ]
909 911 def checkopt(opt):
910 912 if opts.get(opt):
911 913 for i, allowable in allowables:
912 914 if opts[i] and opt not in allowable:
913 915 raise error.Abort(_("options '--%s' and '--%s' may not be "
914 916 "used together") % (opt, i))
915 917 return True
916 918 if checkopt('cleanup'):
917 919 if pats:
918 920 raise error.Abort(_("cannot specify names when using '--cleanup'"))
919 921 return cleanupcmd(ui, repo)
920 922 elif checkopt('delete'):
921 923 return deletecmd(ui, repo, pats)
922 924 elif checkopt('list'):
923 925 return listcmd(ui, repo, pats, opts)
924 926 elif checkopt('patch'):
925 927 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
926 928 elif checkopt('stat'):
927 929 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
928 930 else:
929 931 return createcmd(ui, repo, pats, opts)
930 932
931 933 def extsetup(ui):
932 934 cmdutil.unfinishedstates.append(
933 935 [shelvedstate._filename, False, False,
934 936 _('unshelve already in progress'),
935 937 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
936 938 cmdutil.afterresolvedstates.append(
937 939 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now