##// END OF EJS Templates
shelve: move 'nothing changed' messaging to a separate function...
Kostia Balytskyi -
r30382:dedf0915 default
parent child Browse files
Show More
@@ -1,934 +1,937
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 def _nothingtoshelvemessaging(ui, repo, pats, opts):
324 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
325 if stat.deleted:
326 ui.status(_("nothing changed (%d missing files, see "
327 "'hg status')\n") % len(stat.deleted))
328 else:
329 ui.status(_("nothing changed\n"))
330
323 331 def _docreatecmd(ui, repo, pats, opts):
324 332 wctx = repo[None]
325 333 parents = wctx.parents()
326 334 if len(parents) > 1:
327 335 raise error.Abort(_('cannot shelve while merging'))
328 336 parent = parents[0]
329 337 origbranch = wctx.branch()
330 338
331 339 if parent.node() != nodemod.nullid:
332 340 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
333 341 else:
334 342 desc = '(changes in empty repository)'
335 343
336 344 if not opts.get('message'):
337 345 opts['message'] = desc
338 346
339 347 lock = tr = None
340 348 try:
341 349 lock = repo.lock()
342 350
343 351 # use an uncommitted transaction to generate the bundle to avoid
344 352 # pull races. ensure we don't print the abort message to stderr.
345 353 tr = repo.transaction('commit', report=lambda x: None)
346 354
347 355 interactive = opts.get('interactive', False)
348 356 includeunknown = (opts.get('unknown', False) and
349 357 not opts.get('addremove', False))
350 358
351 359 name = getshelvename(repo, parent, opts)
352 360 extra={}
353 361 if includeunknown:
354 362 s = repo.status(match=scmutil.match(repo[None], pats, opts),
355 363 unknown=True)
356 364 if s.unknown:
357 365 extra['shelve_unknown'] = '\0'.join(s.unknown)
358 366 repo[None].add(s.unknown)
359 367
360 368 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
361 369 # In non-bare shelve we don't store newly created branch
362 370 # at bundled commit
363 371 repo.dirstate.setbranch(repo['.'].branch())
364 372
365 373 commitfunc = getcommitfunc(extra, interactive, editor=True)
366 374 if not interactive:
367 375 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
368 376 else:
369 377 node = cmdutil.dorecord(ui, repo, commitfunc, None,
370 378 False, cmdutil.recordfilter, *pats, **opts)
371 379 if not node:
372 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
373 if stat.deleted:
374 ui.status(_("nothing changed (%d missing files, see "
375 "'hg status')\n") % len(stat.deleted))
376 else:
377 ui.status(_("nothing changed\n"))
380 _nothingtoshelvemessaging(ui, repo, pats, opts)
378 381 return 1
379 382
380 383 bases = list(mutableancestors(repo[node]))
381 384 shelvedfile(repo, name, 'hg').writebundle(bases, node)
382 385 cmdutil.export(repo, [node],
383 386 fp=shelvedfile(repo, name, 'patch').opener('wb'),
384 387 opts=mdiff.diffopts(git=True))
385 388
386 389
387 390 if ui.formatted():
388 391 desc = util.ellipsis(desc, ui.termwidth())
389 392 ui.status(_('shelved as %s\n') % name)
390 393 hg.update(repo, parent.node())
391 394 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
392 395 repo.dirstate.setbranch(origbranch)
393 396
394 397 _aborttransaction(repo)
395 398 finally:
396 399 lockmod.release(tr, lock)
397 400
398 401 def _isbareshelve(pats, opts):
399 402 return (not pats
400 403 and not opts.get('interactive', False)
401 404 and not opts.get('include', False)
402 405 and not opts.get('exclude', False))
403 406
404 407 def _iswctxonnewbranch(repo):
405 408 return repo[None].branch() != repo['.'].branch()
406 409
407 410 def cleanupcmd(ui, repo):
408 411 """subcommand that deletes all shelves"""
409 412
410 413 with repo.wlock():
411 414 for (name, _type) in repo.vfs.readdir(shelvedir):
412 415 suffix = name.rsplit('.', 1)[-1]
413 416 if suffix in shelvefileextensions:
414 417 shelvedfile(repo, name).movetobackup()
415 418 cleanupoldbackups(repo)
416 419
417 420 def deletecmd(ui, repo, pats):
418 421 """subcommand that deletes a specific shelve"""
419 422 if not pats:
420 423 raise error.Abort(_('no shelved changes specified!'))
421 424 with repo.wlock():
422 425 try:
423 426 for name in pats:
424 427 for suffix in shelvefileextensions:
425 428 shfile = shelvedfile(repo, name, suffix)
426 429 # patch file is necessary, as it should
427 430 # be present for any kind of shelve,
428 431 # but the .hg file is optional as in future we
429 432 # will add obsolete shelve with does not create a
430 433 # bundle
431 434 if shfile.exists() or suffix == 'patch':
432 435 shfile.movetobackup()
433 436 cleanupoldbackups(repo)
434 437 except OSError as err:
435 438 if err.errno != errno.ENOENT:
436 439 raise
437 440 raise error.Abort(_("shelved change '%s' not found") % name)
438 441
439 442 def listshelves(repo):
440 443 """return all shelves in repo as list of (time, filename)"""
441 444 try:
442 445 names = repo.vfs.readdir(shelvedir)
443 446 except OSError as err:
444 447 if err.errno != errno.ENOENT:
445 448 raise
446 449 return []
447 450 info = []
448 451 for (name, _type) in names:
449 452 pfx, sfx = name.rsplit('.', 1)
450 453 if not pfx or sfx != 'patch':
451 454 continue
452 455 st = shelvedfile(repo, name).stat()
453 456 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
454 457 return sorted(info, reverse=True)
455 458
456 459 def listcmd(ui, repo, pats, opts):
457 460 """subcommand that displays the list of shelves"""
458 461 pats = set(pats)
459 462 width = 80
460 463 if not ui.plain():
461 464 width = ui.termwidth()
462 465 namelabel = 'shelve.newest'
463 466 for mtime, name in listshelves(repo):
464 467 sname = util.split(name)[1]
465 468 if pats and sname not in pats:
466 469 continue
467 470 ui.write(sname, label=namelabel)
468 471 namelabel = 'shelve.name'
469 472 if ui.quiet:
470 473 ui.write('\n')
471 474 continue
472 475 ui.write(' ' * (16 - len(sname)))
473 476 used = 16
474 477 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
475 478 ui.write(age, label='shelve.age')
476 479 ui.write(' ' * (12 - len(age)))
477 480 used += 12
478 481 with open(name + '.patch', 'rb') as fp:
479 482 while True:
480 483 line = fp.readline()
481 484 if not line:
482 485 break
483 486 if not line.startswith('#'):
484 487 desc = line.rstrip()
485 488 if ui.formatted():
486 489 desc = util.ellipsis(desc, width - used)
487 490 ui.write(desc)
488 491 break
489 492 ui.write('\n')
490 493 if not (opts['patch'] or opts['stat']):
491 494 continue
492 495 difflines = fp.readlines()
493 496 if opts['patch']:
494 497 for chunk, label in patch.difflabel(iter, difflines):
495 498 ui.write(chunk, label=label)
496 499 if opts['stat']:
497 500 for chunk, label in patch.diffstatui(difflines, width=width,
498 501 git=True):
499 502 ui.write(chunk, label=label)
500 503
501 504 def singlepatchcmds(ui, repo, pats, opts, subcommand):
502 505 """subcommand that displays a single shelf"""
503 506 if len(pats) != 1:
504 507 raise error.Abort(_("--%s expects a single shelf") % subcommand)
505 508 shelfname = pats[0]
506 509
507 510 if not shelvedfile(repo, shelfname, 'patch').exists():
508 511 raise error.Abort(_("cannot find shelf %s") % shelfname)
509 512
510 513 listcmd(ui, repo, pats, opts)
511 514
512 515 def checkparents(repo, state):
513 516 """check parent while resuming an unshelve"""
514 517 if state.parents != repo.dirstate.parents():
515 518 raise error.Abort(_('working directory parents do not match unshelve '
516 519 'state'))
517 520
518 521 def pathtofiles(repo, files):
519 522 cwd = repo.getcwd()
520 523 return [repo.pathto(f, cwd) for f in files]
521 524
522 525 def unshelveabort(ui, repo, state, opts):
523 526 """subcommand that abort an in-progress unshelve"""
524 527 with repo.lock():
525 528 try:
526 529 checkparents(repo, state)
527 530
528 531 util.rename(repo.join('unshelverebasestate'),
529 532 repo.join('rebasestate'))
530 533 try:
531 534 rebase.rebase(ui, repo, **{
532 535 'abort' : True
533 536 })
534 537 except Exception:
535 538 util.rename(repo.join('rebasestate'),
536 539 repo.join('unshelverebasestate'))
537 540 raise
538 541
539 542 mergefiles(ui, repo, state.wctx, state.pendingctx)
540 543 repair.strip(ui, repo, state.stripnodes, backup=False,
541 544 topic='shelve')
542 545 finally:
543 546 shelvedstate.clear(repo)
544 547 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
545 548
546 549 def mergefiles(ui, repo, wctx, shelvectx):
547 550 """updates to wctx and merges the changes from shelvectx into the
548 551 dirstate."""
549 552 oldquiet = ui.quiet
550 553 try:
551 554 ui.quiet = True
552 555 hg.update(repo, wctx.node())
553 556 files = []
554 557 files.extend(shelvectx.files())
555 558 files.extend(shelvectx.parents()[0].files())
556 559
557 560 # revert will overwrite unknown files, so move them out of the way
558 561 for file in repo.status(unknown=True).unknown:
559 562 if file in files:
560 563 util.rename(file, scmutil.origpath(ui, repo, file))
561 564 ui.pushbuffer(True)
562 565 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
563 566 *pathtofiles(repo, files),
564 567 **{'no_backup': True})
565 568 ui.popbuffer()
566 569 finally:
567 570 ui.quiet = oldquiet
568 571
569 572 def restorebranch(ui, repo, branchtorestore):
570 573 if branchtorestore and branchtorestore != repo.dirstate.branch():
571 574 repo.dirstate.setbranch(branchtorestore)
572 575 ui.status(_('marked working directory as branch %s\n')
573 576 % branchtorestore)
574 577
575 578 def unshelvecleanup(ui, repo, name, opts):
576 579 """remove related files after an unshelve"""
577 580 if not opts.get('keep'):
578 581 for filetype in shelvefileextensions:
579 582 shfile = shelvedfile(repo, name, filetype)
580 583 if shfile.exists():
581 584 shfile.movetobackup()
582 585 cleanupoldbackups(repo)
583 586
584 587 def unshelvecontinue(ui, repo, state, opts):
585 588 """subcommand to continue an in-progress unshelve"""
586 589 # We're finishing off a merge. First parent is our original
587 590 # parent, second is the temporary "fake" commit we're unshelving.
588 591 with repo.lock():
589 592 checkparents(repo, state)
590 593 ms = merge.mergestate.read(repo)
591 594 if [f for f in ms if ms[f] == 'u']:
592 595 raise error.Abort(
593 596 _("unresolved conflicts, can't continue"),
594 597 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
595 598
596 599 util.rename(repo.join('unshelverebasestate'),
597 600 repo.join('rebasestate'))
598 601 try:
599 602 rebase.rebase(ui, repo, **{
600 603 'continue' : True
601 604 })
602 605 except Exception:
603 606 util.rename(repo.join('rebasestate'),
604 607 repo.join('unshelverebasestate'))
605 608 raise
606 609
607 610 shelvectx = repo['tip']
608 611 if not shelvectx in state.pendingctx.children():
609 612 # rebase was a no-op, so it produced no child commit
610 613 shelvectx = state.pendingctx
611 614 else:
612 615 # only strip the shelvectx if the rebase produced it
613 616 state.stripnodes.append(shelvectx.node())
614 617
615 618 mergefiles(ui, repo, state.wctx, shelvectx)
616 619 restorebranch(ui, repo, state.branchtorestore)
617 620
618 621 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
619 622 shelvedstate.clear(repo)
620 623 unshelvecleanup(ui, repo, state.name, opts)
621 624 ui.status(_("unshelve of '%s' complete\n") % state.name)
622 625
623 626 @command('unshelve',
624 627 [('a', 'abort', None,
625 628 _('abort an incomplete unshelve operation')),
626 629 ('c', 'continue', None,
627 630 _('continue an incomplete unshelve operation')),
628 631 ('k', 'keep', None,
629 632 _('keep shelve after unshelving')),
630 633 ('t', 'tool', '', _('specify merge tool')),
631 634 ('', 'date', '',
632 635 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
633 636 _('hg unshelve [SHELVED]'))
634 637 def unshelve(ui, repo, *shelved, **opts):
635 638 """restore a shelved change to the working directory
636 639
637 640 This command accepts an optional name of a shelved change to
638 641 restore. If none is given, the most recent shelved change is used.
639 642
640 643 If a shelved change is applied successfully, the bundle that
641 644 contains the shelved changes is moved to a backup location
642 645 (.hg/shelve-backup).
643 646
644 647 Since you can restore a shelved change on top of an arbitrary
645 648 commit, it is possible that unshelving will result in a conflict
646 649 between your changes and the commits you are unshelving onto. If
647 650 this occurs, you must resolve the conflict, then use
648 651 ``--continue`` to complete the unshelve operation. (The bundle
649 652 will not be moved until you successfully complete the unshelve.)
650 653
651 654 (Alternatively, you can use ``--abort`` to abandon an unshelve
652 655 that causes a conflict. This reverts the unshelved changes, and
653 656 leaves the bundle in place.)
654 657
655 658 If bare shelved change(when no files are specified, without interactive,
656 659 include and exclude option) was done on newly created branch it would
657 660 restore branch information to the working directory.
658 661
659 662 After a successful unshelve, the shelved changes are stored in a
660 663 backup directory. Only the N most recent backups are kept. N
661 664 defaults to 10 but can be overridden using the ``shelve.maxbackups``
662 665 configuration option.
663 666
664 667 .. container:: verbose
665 668
666 669 Timestamp in seconds is used to decide order of backups. More
667 670 than ``maxbackups`` backups are kept, if same timestamp
668 671 prevents from deciding exact order of them, for safety.
669 672 """
670 673 with repo.wlock():
671 674 return _dounshelve(ui, repo, *shelved, **opts)
672 675
673 676 def _dounshelve(ui, repo, *shelved, **opts):
674 677 abortf = opts.get('abort')
675 678 continuef = opts.get('continue')
676 679 if not abortf and not continuef:
677 680 cmdutil.checkunfinished(repo)
678 681
679 682 if abortf or continuef:
680 683 if abortf and continuef:
681 684 raise error.Abort(_('cannot use both abort and continue'))
682 685 if shelved:
683 686 raise error.Abort(_('cannot combine abort/continue with '
684 687 'naming a shelved change'))
685 688 if abortf and opts.get('tool', False):
686 689 ui.warn(_('tool option will be ignored\n'))
687 690
688 691 try:
689 692 state = shelvedstate.load(repo)
690 693 except IOError as err:
691 694 if err.errno != errno.ENOENT:
692 695 raise
693 696 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
694 697 except error.CorruptedState as err:
695 698 ui.debug(str(err) + '\n')
696 699 if continuef:
697 700 msg = _('corrupted shelved state file')
698 701 hint = _('please run hg unshelve --abort to abort unshelve '
699 702 'operation')
700 703 raise error.Abort(msg, hint=hint)
701 704 elif abortf:
702 705 msg = _('could not read shelved state file, your working copy '
703 706 'may be in an unexpected state\nplease update to some '
704 707 'commit\n')
705 708 ui.warn(msg)
706 709 shelvedstate.clear(repo)
707 710 return
708 711
709 712 if abortf:
710 713 return unshelveabort(ui, repo, state, opts)
711 714 elif continuef:
712 715 return unshelvecontinue(ui, repo, state, opts)
713 716 elif len(shelved) > 1:
714 717 raise error.Abort(_('can only unshelve one change at a time'))
715 718 elif not shelved:
716 719 shelved = listshelves(repo)
717 720 if not shelved:
718 721 raise error.Abort(_('no shelved changes to apply!'))
719 722 basename = util.split(shelved[0][1])[1]
720 723 ui.status(_("unshelving change '%s'\n") % basename)
721 724 else:
722 725 basename = shelved[0]
723 726
724 727 if not shelvedfile(repo, basename, 'patch').exists():
725 728 raise error.Abort(_("shelved change '%s' not found") % basename)
726 729
727 730 oldquiet = ui.quiet
728 731 lock = tr = None
729 732 forcemerge = ui.backupconfig('ui', 'forcemerge')
730 733 try:
731 734 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
732 735 lock = repo.lock()
733 736
734 737 tr = repo.transaction('unshelve', report=lambda x: None)
735 738 oldtiprev = len(repo)
736 739
737 740 pctx = repo['.']
738 741 tmpwctx = pctx
739 742 # The goal is to have a commit structure like so:
740 743 # ...-> pctx -> tmpwctx -> shelvectx
741 744 # where tmpwctx is an optional commit with the user's pending changes
742 745 # and shelvectx is the unshelved changes. Then we merge it all down
743 746 # to the original pctx.
744 747
745 748 # Store pending changes in a commit and remember added in case a shelve
746 749 # contains unknown files that are part of the pending change
747 750 s = repo.status()
748 751 addedbefore = frozenset(s.added)
749 752 if s.modified or s.added or s.removed or s.deleted:
750 753 ui.status(_("temporarily committing pending changes "
751 754 "(restore with 'hg unshelve --abort')\n"))
752 755 commitfunc = getcommitfunc(extra=None, interactive=False,
753 756 editor=False)
754 757 tempopts = {}
755 758 tempopts['message'] = "pending changes temporary commit"
756 759 tempopts['date'] = opts.get('date')
757 760 ui.quiet = True
758 761 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
759 762 tmpwctx = repo[node]
760 763
761 764 ui.quiet = True
762 765 shelvedfile(repo, basename, 'hg').applybundle()
763 766
764 767 ui.quiet = oldquiet
765 768
766 769 shelvectx = repo['tip']
767 770
768 771 branchtorestore = ''
769 772 if shelvectx.branch() != shelvectx.p1().branch():
770 773 branchtorestore = shelvectx.branch()
771 774
772 775 # If the shelve is not immediately on top of the commit
773 776 # we'll be merging with, rebase it to be on top.
774 777 if tmpwctx.node() != shelvectx.parents()[0].node():
775 778 ui.status(_('rebasing shelved changes\n'))
776 779 try:
777 780 rebase.rebase(ui, repo, **{
778 781 'rev' : [shelvectx.rev()],
779 782 'dest' : str(tmpwctx.rev()),
780 783 'keep' : True,
781 784 'tool' : opts.get('tool', ''),
782 785 })
783 786 except error.InterventionRequired:
784 787 tr.close()
785 788
786 789 stripnodes = [repo.changelog.node(rev)
787 790 for rev in xrange(oldtiprev, len(repo))]
788 791 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
789 792 branchtorestore)
790 793
791 794 util.rename(repo.join('rebasestate'),
792 795 repo.join('unshelverebasestate'))
793 796 raise error.InterventionRequired(
794 797 _("unresolved conflicts (see 'hg resolve', then "
795 798 "'hg unshelve --continue')"))
796 799
797 800 # refresh ctx after rebase completes
798 801 shelvectx = repo['tip']
799 802
800 803 if not shelvectx in tmpwctx.children():
801 804 # rebase was a no-op, so it produced no child commit
802 805 shelvectx = tmpwctx
803 806
804 807 mergefiles(ui, repo, pctx, shelvectx)
805 808 restorebranch(ui, repo, branchtorestore)
806 809
807 810 # Forget any files that were unknown before the shelve, unknown before
808 811 # unshelve started, but are now added.
809 812 shelveunknown = shelvectx.extra().get('shelve_unknown')
810 813 if shelveunknown:
811 814 shelveunknown = frozenset(shelveunknown.split('\0'))
812 815 addedafter = frozenset(repo.status().added)
813 816 toforget = (addedafter & shelveunknown) - addedbefore
814 817 repo[None].forget(toforget)
815 818
816 819 shelvedstate.clear(repo)
817 820
818 821 # The transaction aborting will strip all the commits for us,
819 822 # but it doesn't update the inmemory structures, so addchangegroup
820 823 # hooks still fire and try to operate on the missing commits.
821 824 # Clean up manually to prevent this.
822 825 repo.unfiltered().changelog.strip(oldtiprev, tr)
823 826
824 827 unshelvecleanup(ui, repo, basename, opts)
825 828
826 829 _aborttransaction(repo)
827 830 finally:
828 831 ui.quiet = oldquiet
829 832 if tr:
830 833 tr.release()
831 834 lockmod.release(lock)
832 835 ui.restoreconfig(forcemerge)
833 836
834 837 @command('shelve',
835 838 [('A', 'addremove', None,
836 839 _('mark new/missing files as added/removed before shelving')),
837 840 ('u', 'unknown', None,
838 841 _('store unknown files in the shelve')),
839 842 ('', 'cleanup', None,
840 843 _('delete all shelved changes')),
841 844 ('', 'date', '',
842 845 _('shelve with the specified commit date'), _('DATE')),
843 846 ('d', 'delete', None,
844 847 _('delete the named shelved change(s)')),
845 848 ('e', 'edit', False,
846 849 _('invoke editor on commit messages')),
847 850 ('l', 'list', None,
848 851 _('list current shelves')),
849 852 ('m', 'message', '',
850 853 _('use text as shelve message'), _('TEXT')),
851 854 ('n', 'name', '',
852 855 _('use the given name for the shelved commit'), _('NAME')),
853 856 ('p', 'patch', None,
854 857 _('show patch')),
855 858 ('i', 'interactive', None,
856 859 _('interactive mode, only works while creating a shelve')),
857 860 ('', 'stat', None,
858 861 _('output diffstat-style summary of changes'))] + commands.walkopts,
859 862 _('hg shelve [OPTION]... [FILE]...'))
860 863 def shelvecmd(ui, repo, *pats, **opts):
861 864 '''save and set aside changes from the working directory
862 865
863 866 Shelving takes files that "hg status" reports as not clean, saves
864 867 the modifications to a bundle (a shelved change), and reverts the
865 868 files so that their state in the working directory becomes clean.
866 869
867 870 To restore these changes to the working directory, using "hg
868 871 unshelve"; this will work even if you switch to a different
869 872 commit.
870 873
871 874 When no files are specified, "hg shelve" saves all not-clean
872 875 files. If specific files or directories are named, only changes to
873 876 those files are shelved.
874 877
875 878 In bare shelve(when no files are specified, without interactive,
876 879 include and exclude option), shelving remembers information if the
877 880 working directory was on newly created branch, in other words working
878 881 directory was on different branch than its first parent. In this
879 882 situation unshelving restores branch information to the working directory.
880 883
881 884 Each shelved change has a name that makes it easier to find later.
882 885 The name of a shelved change defaults to being based on the active
883 886 bookmark, or if there is no active bookmark, the current named
884 887 branch. To specify a different name, use ``--name``.
885 888
886 889 To see a list of existing shelved changes, use the ``--list``
887 890 option. For each shelved change, this will print its name, age,
888 891 and description; use ``--patch`` or ``--stat`` for more details.
889 892
890 893 To delete specific shelved changes, use ``--delete``. To delete
891 894 all shelved changes, use ``--cleanup``.
892 895 '''
893 896 allowables = [
894 897 ('addremove', set(['create'])), # 'create' is pseudo action
895 898 ('unknown', set(['create'])),
896 899 ('cleanup', set(['cleanup'])),
897 900 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
898 901 ('delete', set(['delete'])),
899 902 ('edit', set(['create'])),
900 903 ('list', set(['list'])),
901 904 ('message', set(['create'])),
902 905 ('name', set(['create'])),
903 906 ('patch', set(['patch', 'list'])),
904 907 ('stat', set(['stat', 'list'])),
905 908 ]
906 909 def checkopt(opt):
907 910 if opts.get(opt):
908 911 for i, allowable in allowables:
909 912 if opts[i] and opt not in allowable:
910 913 raise error.Abort(_("options '--%s' and '--%s' may not be "
911 914 "used together") % (opt, i))
912 915 return True
913 916 if checkopt('cleanup'):
914 917 if pats:
915 918 raise error.Abort(_("cannot specify names when using '--cleanup'"))
916 919 return cleanupcmd(ui, repo)
917 920 elif checkopt('delete'):
918 921 return deletecmd(ui, repo, pats)
919 922 elif checkopt('list'):
920 923 return listcmd(ui, repo, pats, opts)
921 924 elif checkopt('patch'):
922 925 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
923 926 elif checkopt('stat'):
924 927 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
925 928 else:
926 929 return createcmd(ui, repo, pats, opts)
927 930
928 931 def extsetup(ui):
929 932 cmdutil.unfinishedstates.append(
930 933 [shelvedstate._filename, False, False,
931 934 _('unshelve already in progress'),
932 935 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
933 936 cmdutil.afterresolvedstates.append(
934 937 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now