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