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