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