##// END OF EJS Templates
shelve: use matcher to restrict prefetch to just the modified files...
Kyle Lippincott -
r40662:29e4a77b default
parent child Browse files
Show More
@@ -1,1152 +1,1156 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 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 # Create a matcher so that prefetch doesn't attempt to fetch the entire
434 # repository pointlessly.
435 match = scmutil.matchfiles(repo, repo[node].files())
433 436 with shelvedfile(repo, name, patchextension).opener('wb') as fp:
434 cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True))
437 cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True),
438 match=match)
435 439
436 440 def _includeunknownfiles(repo, pats, opts, extra):
437 441 s = repo.status(match=scmutil.match(repo[None], pats, opts),
438 442 unknown=True)
439 443 if s.unknown:
440 444 extra['shelve_unknown'] = '\0'.join(s.unknown)
441 445 repo[None].add(s.unknown)
442 446
443 447 def _finishshelve(repo):
444 448 if phases.supportinternal(repo):
445 449 backupname = 'dirstate.shelve'
446 450 tr = repo.currenttransaction()
447 451 repo.dirstate.savebackup(tr, backupname)
448 452 tr.close()
449 453 repo.dirstate.restorebackup(None, backupname)
450 454 else:
451 455 _aborttransaction(repo)
452 456
453 457 def createcmd(ui, repo, pats, opts):
454 458 """subcommand that creates a new shelve"""
455 459 with repo.wlock():
456 460 cmdutil.checkunfinished(repo)
457 461 return _docreatecmd(ui, repo, pats, opts)
458 462
459 463 def _docreatecmd(ui, repo, pats, opts):
460 464 wctx = repo[None]
461 465 parents = wctx.parents()
462 466 if len(parents) > 1:
463 467 raise error.Abort(_('cannot shelve while merging'))
464 468 parent = parents[0]
465 469 origbranch = wctx.branch()
466 470
467 471 if parent.node() != nodemod.nullid:
468 472 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
469 473 else:
470 474 desc = '(changes in empty repository)'
471 475
472 476 if not opts.get('message'):
473 477 opts['message'] = desc
474 478
475 479 lock = tr = activebookmark = None
476 480 try:
477 481 lock = repo.lock()
478 482
479 483 # use an uncommitted transaction to generate the bundle to avoid
480 484 # pull races. ensure we don't print the abort message to stderr.
481 485 tr = repo.transaction('commit', report=lambda x: None)
482 486
483 487 interactive = opts.get('interactive', False)
484 488 includeunknown = (opts.get('unknown', False) and
485 489 not opts.get('addremove', False))
486 490
487 491 name = getshelvename(repo, parent, opts)
488 492 activebookmark = _backupactivebookmark(repo)
489 493 extra = {'internal': 'shelve'}
490 494 if includeunknown:
491 495 _includeunknownfiles(repo, pats, opts, extra)
492 496
493 497 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
494 498 # In non-bare shelve we don't store newly created branch
495 499 # at bundled commit
496 500 repo.dirstate.setbranch(repo['.'].branch())
497 501
498 502 commitfunc = getcommitfunc(extra, interactive, editor=True)
499 503 if not interactive:
500 504 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
501 505 else:
502 506 node = cmdutil.dorecord(ui, repo, commitfunc, None,
503 507 False, cmdutil.recordfilter, *pats,
504 508 **pycompat.strkwargs(opts))
505 509 if not node:
506 510 _nothingtoshelvemessaging(ui, repo, pats, opts)
507 511 return 1
508 512
509 513 _shelvecreatedcommit(repo, node, name)
510 514
511 515 if ui.formatted():
512 516 desc = stringutil.ellipsis(desc, ui.termwidth())
513 517 ui.status(_('shelved as %s\n') % name)
514 518 hg.update(repo, parent.node())
515 519 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
516 520 repo.dirstate.setbranch(origbranch)
517 521
518 522 _finishshelve(repo)
519 523 finally:
520 524 _restoreactivebookmark(repo, activebookmark)
521 525 lockmod.release(tr, lock)
522 526
523 527 def _isbareshelve(pats, opts):
524 528 return (not pats
525 529 and not opts.get('interactive', False)
526 530 and not opts.get('include', False)
527 531 and not opts.get('exclude', False))
528 532
529 533 def _iswctxonnewbranch(repo):
530 534 return repo[None].branch() != repo['.'].branch()
531 535
532 536 def cleanupcmd(ui, repo):
533 537 """subcommand that deletes all shelves"""
534 538
535 539 with repo.wlock():
536 540 for (name, _type) in repo.vfs.readdir(shelvedir):
537 541 suffix = name.rsplit('.', 1)[-1]
538 542 if suffix in shelvefileextensions:
539 543 shelvedfile(repo, name).movetobackup()
540 544 cleanupoldbackups(repo)
541 545
542 546 def deletecmd(ui, repo, pats):
543 547 """subcommand that deletes a specific shelve"""
544 548 if not pats:
545 549 raise error.Abort(_('no shelved changes specified!'))
546 550 with repo.wlock():
547 551 try:
548 552 for name in pats:
549 553 for suffix in shelvefileextensions:
550 554 shfile = shelvedfile(repo, name, suffix)
551 555 # patch file is necessary, as it should
552 556 # be present for any kind of shelve,
553 557 # but the .hg file is optional as in future we
554 558 # will add obsolete shelve with does not create a
555 559 # bundle
556 560 if shfile.exists() or suffix == patchextension:
557 561 shfile.movetobackup()
558 562 cleanupoldbackups(repo)
559 563 except OSError as err:
560 564 if err.errno != errno.ENOENT:
561 565 raise
562 566 raise error.Abort(_("shelved change '%s' not found") % name)
563 567
564 568 def listshelves(repo):
565 569 """return all shelves in repo as list of (time, filename)"""
566 570 try:
567 571 names = repo.vfs.readdir(shelvedir)
568 572 except OSError as err:
569 573 if err.errno != errno.ENOENT:
570 574 raise
571 575 return []
572 576 info = []
573 577 for (name, _type) in names:
574 578 pfx, sfx = name.rsplit('.', 1)
575 579 if not pfx or sfx != patchextension:
576 580 continue
577 581 st = shelvedfile(repo, name).stat()
578 582 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
579 583 return sorted(info, reverse=True)
580 584
581 585 def listcmd(ui, repo, pats, opts):
582 586 """subcommand that displays the list of shelves"""
583 587 pats = set(pats)
584 588 width = 80
585 589 if not ui.plain():
586 590 width = ui.termwidth()
587 591 namelabel = 'shelve.newest'
588 592 ui.pager('shelve')
589 593 for mtime, name in listshelves(repo):
590 594 sname = util.split(name)[1]
591 595 if pats and sname not in pats:
592 596 continue
593 597 ui.write(sname, label=namelabel)
594 598 namelabel = 'shelve.name'
595 599 if ui.quiet:
596 600 ui.write('\n')
597 601 continue
598 602 ui.write(' ' * (16 - len(sname)))
599 603 used = 16
600 604 date = dateutil.makedate(mtime)
601 605 age = '(%s)' % templatefilters.age(date, abbrev=True)
602 606 ui.write(age, label='shelve.age')
603 607 ui.write(' ' * (12 - len(age)))
604 608 used += 12
605 609 with open(name + '.' + patchextension, 'rb') as fp:
606 610 while True:
607 611 line = fp.readline()
608 612 if not line:
609 613 break
610 614 if not line.startswith('#'):
611 615 desc = line.rstrip()
612 616 if ui.formatted():
613 617 desc = stringutil.ellipsis(desc, width - used)
614 618 ui.write(desc)
615 619 break
616 620 ui.write('\n')
617 621 if not (opts['patch'] or opts['stat']):
618 622 continue
619 623 difflines = fp.readlines()
620 624 if opts['patch']:
621 625 for chunk, label in patch.difflabel(iter, difflines):
622 626 ui.write(chunk, label=label)
623 627 if opts['stat']:
624 628 for chunk, label in patch.diffstatui(difflines, width=width):
625 629 ui.write(chunk, label=label)
626 630
627 631 def patchcmds(ui, repo, pats, opts):
628 632 """subcommand that displays shelves"""
629 633 if len(pats) == 0:
630 634 shelves = listshelves(repo)
631 635 if not shelves:
632 636 raise error.Abort(_("there are no shelves to show"))
633 637 mtime, name = shelves[0]
634 638 sname = util.split(name)[1]
635 639 pats = [sname]
636 640
637 641 for shelfname in pats:
638 642 if not shelvedfile(repo, shelfname, patchextension).exists():
639 643 raise error.Abort(_("cannot find shelf %s") % shelfname)
640 644
641 645 listcmd(ui, repo, pats, opts)
642 646
643 647 def checkparents(repo, state):
644 648 """check parent while resuming an unshelve"""
645 649 if state.parents != repo.dirstate.parents():
646 650 raise error.Abort(_('working directory parents do not match unshelve '
647 651 'state'))
648 652
649 653 def pathtofiles(repo, files):
650 654 cwd = repo.getcwd()
651 655 return [repo.pathto(f, cwd) for f in files]
652 656
653 657 def unshelveabort(ui, repo, state, opts):
654 658 """subcommand that abort an in-progress unshelve"""
655 659 with repo.lock():
656 660 try:
657 661 checkparents(repo, state)
658 662
659 663 merge.update(repo, state.pendingctx, branchmerge=False, force=True)
660 664 if (state.activebookmark
661 665 and state.activebookmark in repo._bookmarks):
662 666 bookmarks.activate(repo, state.activebookmark)
663 667
664 668 if repo.vfs.exists('unshelverebasestate'):
665 669 repo.vfs.rename('unshelverebasestate', 'rebasestate')
666 670 rebase.clearstatus(repo)
667 671
668 672 mergefiles(ui, repo, state.wctx, state.pendingctx)
669 673 if not phases.supportinternal(repo):
670 674 repair.strip(ui, repo, state.nodestoremove, backup=False,
671 675 topic='shelve')
672 676 finally:
673 677 shelvedstate.clear(repo)
674 678 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
675 679
676 680 def mergefiles(ui, repo, wctx, shelvectx):
677 681 """updates to wctx and merges the changes from shelvectx into the
678 682 dirstate."""
679 683 with ui.configoverride({('ui', 'quiet'): True}):
680 684 hg.update(repo, wctx.node())
681 685 files = []
682 686 files.extend(shelvectx.files())
683 687 files.extend(shelvectx.parents()[0].files())
684 688
685 689 # revert will overwrite unknown files, so move them out of the way
686 690 for file in repo.status(unknown=True).unknown:
687 691 if file in files:
688 692 util.rename(file, scmutil.origpath(ui, repo, file))
689 693 ui.pushbuffer(True)
690 694 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
691 695 *pathtofiles(repo, files),
692 696 **{r'no_backup': True})
693 697 ui.popbuffer()
694 698
695 699 def restorebranch(ui, repo, branchtorestore):
696 700 if branchtorestore and branchtorestore != repo.dirstate.branch():
697 701 repo.dirstate.setbranch(branchtorestore)
698 702 ui.status(_('marked working directory as branch %s\n')
699 703 % branchtorestore)
700 704
701 705 def unshelvecleanup(ui, repo, name, opts):
702 706 """remove related files after an unshelve"""
703 707 if not opts.get('keep'):
704 708 for filetype in shelvefileextensions:
705 709 shfile = shelvedfile(repo, name, filetype)
706 710 if shfile.exists():
707 711 shfile.movetobackup()
708 712 cleanupoldbackups(repo)
709 713
710 714 def unshelvecontinue(ui, repo, state, opts):
711 715 """subcommand to continue an in-progress unshelve"""
712 716 # We're finishing off a merge. First parent is our original
713 717 # parent, second is the temporary "fake" commit we're unshelving.
714 718 with repo.lock():
715 719 checkparents(repo, state)
716 720 ms = merge.mergestate.read(repo)
717 721 if list(ms.unresolved()):
718 722 raise error.Abort(
719 723 _("unresolved conflicts, can't continue"),
720 724 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
721 725
722 726 shelvectx = repo[state.parents[1]]
723 727 pendingctx = state.pendingctx
724 728
725 729 with repo.dirstate.parentchange():
726 730 repo.setparents(state.pendingctx.node(), nodemod.nullid)
727 731 repo.dirstate.write(repo.currenttransaction())
728 732
729 733 targetphase = phases.internal
730 734 if not phases.supportinternal(repo):
731 735 targetphase = phases.secret
732 736 overrides = {('phases', 'new-commit'): targetphase}
733 737 with repo.ui.configoverride(overrides, 'unshelve'):
734 738 with repo.dirstate.parentchange():
735 739 repo.setparents(state.parents[0], nodemod.nullid)
736 740 newnode = repo.commit(text=shelvectx.description(),
737 741 extra=shelvectx.extra(),
738 742 user=shelvectx.user(),
739 743 date=shelvectx.date())
740 744
741 745 if newnode is None:
742 746 # If it ended up being a no-op commit, then the normal
743 747 # merge state clean-up path doesn't happen, so do it
744 748 # here. Fix issue5494
745 749 merge.mergestate.clean(repo)
746 750 shelvectx = state.pendingctx
747 751 msg = _('note: unshelved changes already existed '
748 752 'in the working copy\n')
749 753 ui.status(msg)
750 754 else:
751 755 # only strip the shelvectx if we produced one
752 756 state.nodestoremove.append(newnode)
753 757 shelvectx = repo[newnode]
754 758
755 759 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
756 760
757 761 if repo.vfs.exists('unshelverebasestate'):
758 762 repo.vfs.rename('unshelverebasestate', 'rebasestate')
759 763 rebase.clearstatus(repo)
760 764
761 765 mergefiles(ui, repo, state.wctx, shelvectx)
762 766 restorebranch(ui, repo, state.branchtorestore)
763 767
764 768 if not phases.supportinternal(repo):
765 769 repair.strip(ui, repo, state.nodestoremove, backup=False,
766 770 topic='shelve')
767 771 _restoreactivebookmark(repo, state.activebookmark)
768 772 shelvedstate.clear(repo)
769 773 unshelvecleanup(ui, repo, state.name, opts)
770 774 ui.status(_("unshelve of '%s' complete\n") % state.name)
771 775
772 776 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
773 777 """Temporarily commit working copy changes before moving unshelve commit"""
774 778 # Store pending changes in a commit and remember added in case a shelve
775 779 # contains unknown files that are part of the pending change
776 780 s = repo.status()
777 781 addedbefore = frozenset(s.added)
778 782 if not (s.modified or s.added or s.removed):
779 783 return tmpwctx, addedbefore
780 784 ui.status(_("temporarily committing pending changes "
781 785 "(restore with 'hg unshelve --abort')\n"))
782 786 extra = {'internal': 'shelve'}
783 787 commitfunc = getcommitfunc(extra=extra, interactive=False,
784 788 editor=False)
785 789 tempopts = {}
786 790 tempopts['message'] = "pending changes temporary commit"
787 791 tempopts['date'] = opts.get('date')
788 792 with ui.configoverride({('ui', 'quiet'): True}):
789 793 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
790 794 tmpwctx = repo[node]
791 795 return tmpwctx, addedbefore
792 796
793 797 def _unshelverestorecommit(ui, repo, basename):
794 798 """Recreate commit in the repository during the unshelve"""
795 799 repo = repo.unfiltered()
796 800 node = None
797 801 if shelvedfile(repo, basename, 'shelve').exists():
798 802 node = shelvedfile(repo, basename, 'shelve').readinfo()['node']
799 803 if node is None or node not in repo:
800 804 with ui.configoverride({('ui', 'quiet'): True}):
801 805 shelvectx = shelvedfile(repo, basename, 'hg').applybundle()
802 806 # We might not strip the unbundled changeset, so we should keep track of
803 807 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
804 808 if node is None:
805 809 info = {'node': nodemod.hex(shelvectx.node())}
806 810 shelvedfile(repo, basename, 'shelve').writeinfo(info)
807 811 else:
808 812 shelvectx = repo[node]
809 813
810 814 return repo, shelvectx
811 815
812 816 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
813 817 tmpwctx, shelvectx, branchtorestore,
814 818 activebookmark):
815 819 """Rebase restored commit from its original location to a destination"""
816 820 # If the shelve is not immediately on top of the commit
817 821 # we'll be merging with, rebase it to be on top.
818 822 if tmpwctx.node() == shelvectx.parents()[0].node():
819 823 return shelvectx
820 824
821 825 overrides = {
822 826 ('ui', 'forcemerge'): opts.get('tool', ''),
823 827 ('phases', 'new-commit'): phases.secret,
824 828 }
825 829 with repo.ui.configoverride(overrides, 'unshelve'):
826 830 ui.status(_('rebasing shelved changes\n'))
827 831 stats = merge.graft(repo, shelvectx, shelvectx.p1(),
828 832 labels=['shelve', 'working-copy'],
829 833 keepconflictparent=True)
830 834 if stats.unresolvedcount:
831 835 tr.close()
832 836
833 837 nodestoremove = [repo.changelog.node(rev)
834 838 for rev in pycompat.xrange(oldtiprev, len(repo))]
835 839 shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
836 840 branchtorestore, opts.get('keep'), activebookmark)
837 841 raise error.InterventionRequired(
838 842 _("unresolved conflicts (see 'hg resolve', then "
839 843 "'hg unshelve --continue')"))
840 844
841 845 with repo.dirstate.parentchange():
842 846 repo.setparents(tmpwctx.node(), nodemod.nullid)
843 847 newnode = repo.commit(text=shelvectx.description(),
844 848 extra=shelvectx.extra(),
845 849 user=shelvectx.user(),
846 850 date=shelvectx.date())
847 851
848 852 if newnode is None:
849 853 # If it ended up being a no-op commit, then the normal
850 854 # merge state clean-up path doesn't happen, so do it
851 855 # here. Fix issue5494
852 856 merge.mergestate.clean(repo)
853 857 shelvectx = tmpwctx
854 858 msg = _('note: unshelved changes already existed '
855 859 'in the working copy\n')
856 860 ui.status(msg)
857 861 else:
858 862 shelvectx = repo[newnode]
859 863 hg.updaterepo(repo, tmpwctx.node(), False)
860 864
861 865 return shelvectx
862 866
863 867 def _forgetunknownfiles(repo, shelvectx, addedbefore):
864 868 # Forget any files that were unknown before the shelve, unknown before
865 869 # unshelve started, but are now added.
866 870 shelveunknown = shelvectx.extra().get('shelve_unknown')
867 871 if not shelveunknown:
868 872 return
869 873 shelveunknown = frozenset(shelveunknown.split('\0'))
870 874 addedafter = frozenset(repo.status().added)
871 875 toforget = (addedafter & shelveunknown) - addedbefore
872 876 repo[None].forget(toforget)
873 877
874 878 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
875 879 _restoreactivebookmark(repo, activebookmark)
876 880 # The transaction aborting will strip all the commits for us,
877 881 # but it doesn't update the inmemory structures, so addchangegroup
878 882 # hooks still fire and try to operate on the missing commits.
879 883 # Clean up manually to prevent this.
880 884 repo.unfiltered().changelog.strip(oldtiprev, tr)
881 885 _aborttransaction(repo)
882 886
883 887 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
884 888 """Check potential problems which may result from working
885 889 copy having untracked changes."""
886 890 wcdeleted = set(repo.status().deleted)
887 891 shelvetouched = set(shelvectx.files())
888 892 intersection = wcdeleted.intersection(shelvetouched)
889 893 if intersection:
890 894 m = _("shelved change touches missing files")
891 895 hint = _("run hg status to see which files are missing")
892 896 raise error.Abort(m, hint=hint)
893 897
894 898 @command('unshelve',
895 899 [('a', 'abort', None,
896 900 _('abort an incomplete unshelve operation')),
897 901 ('c', 'continue', None,
898 902 _('continue an incomplete unshelve operation')),
899 903 ('k', 'keep', None,
900 904 _('keep shelve after unshelving')),
901 905 ('n', 'name', '',
902 906 _('restore shelved change with given name'), _('NAME')),
903 907 ('t', 'tool', '', _('specify merge tool')),
904 908 ('', 'date', '',
905 909 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
906 910 _('hg unshelve [[-n] SHELVED]'),
907 911 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
908 912 def unshelve(ui, repo, *shelved, **opts):
909 913 """restore a shelved change to the working directory
910 914
911 915 This command accepts an optional name of a shelved change to
912 916 restore. If none is given, the most recent shelved change is used.
913 917
914 918 If a shelved change is applied successfully, the bundle that
915 919 contains the shelved changes is moved to a backup location
916 920 (.hg/shelve-backup).
917 921
918 922 Since you can restore a shelved change on top of an arbitrary
919 923 commit, it is possible that unshelving will result in a conflict
920 924 between your changes and the commits you are unshelving onto. If
921 925 this occurs, you must resolve the conflict, then use
922 926 ``--continue`` to complete the unshelve operation. (The bundle
923 927 will not be moved until you successfully complete the unshelve.)
924 928
925 929 (Alternatively, you can use ``--abort`` to abandon an unshelve
926 930 that causes a conflict. This reverts the unshelved changes, and
927 931 leaves the bundle in place.)
928 932
929 933 If bare shelved change(when no files are specified, without interactive,
930 934 include and exclude option) was done on newly created branch it would
931 935 restore branch information to the working directory.
932 936
933 937 After a successful unshelve, the shelved changes are stored in a
934 938 backup directory. Only the N most recent backups are kept. N
935 939 defaults to 10 but can be overridden using the ``shelve.maxbackups``
936 940 configuration option.
937 941
938 942 .. container:: verbose
939 943
940 944 Timestamp in seconds is used to decide order of backups. More
941 945 than ``maxbackups`` backups are kept, if same timestamp
942 946 prevents from deciding exact order of them, for safety.
943 947 """
944 948 with repo.wlock():
945 949 return _dounshelve(ui, repo, *shelved, **opts)
946 950
947 951 def _dounshelve(ui, repo, *shelved, **opts):
948 952 opts = pycompat.byteskwargs(opts)
949 953 abortf = opts.get('abort')
950 954 continuef = opts.get('continue')
951 955 if not abortf and not continuef:
952 956 cmdutil.checkunfinished(repo)
953 957 shelved = list(shelved)
954 958 if opts.get("name"):
955 959 shelved.append(opts["name"])
956 960
957 961 if abortf or continuef:
958 962 if abortf and continuef:
959 963 raise error.Abort(_('cannot use both abort and continue'))
960 964 if shelved:
961 965 raise error.Abort(_('cannot combine abort/continue with '
962 966 'naming a shelved change'))
963 967 if abortf and opts.get('tool', False):
964 968 ui.warn(_('tool option will be ignored\n'))
965 969
966 970 try:
967 971 state = shelvedstate.load(repo)
968 972 if opts.get('keep') is None:
969 973 opts['keep'] = state.keep
970 974 except IOError as err:
971 975 if err.errno != errno.ENOENT:
972 976 raise
973 977 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
974 978 except error.CorruptedState as err:
975 979 ui.debug(pycompat.bytestr(err) + '\n')
976 980 if continuef:
977 981 msg = _('corrupted shelved state file')
978 982 hint = _('please run hg unshelve --abort to abort unshelve '
979 983 'operation')
980 984 raise error.Abort(msg, hint=hint)
981 985 elif abortf:
982 986 msg = _('could not read shelved state file, your working copy '
983 987 'may be in an unexpected state\nplease update to some '
984 988 'commit\n')
985 989 ui.warn(msg)
986 990 shelvedstate.clear(repo)
987 991 return
988 992
989 993 if abortf:
990 994 return unshelveabort(ui, repo, state, opts)
991 995 elif continuef:
992 996 return unshelvecontinue(ui, repo, state, opts)
993 997 elif len(shelved) > 1:
994 998 raise error.Abort(_('can only unshelve one change at a time'))
995 999 elif not shelved:
996 1000 shelved = listshelves(repo)
997 1001 if not shelved:
998 1002 raise error.Abort(_('no shelved changes to apply!'))
999 1003 basename = util.split(shelved[0][1])[1]
1000 1004 ui.status(_("unshelving change '%s'\n") % basename)
1001 1005 else:
1002 1006 basename = shelved[0]
1003 1007
1004 1008 if not shelvedfile(repo, basename, patchextension).exists():
1005 1009 raise error.Abort(_("shelved change '%s' not found") % basename)
1006 1010
1007 1011 repo = repo.unfiltered()
1008 1012 lock = tr = None
1009 1013 try:
1010 1014 lock = repo.lock()
1011 1015 tr = repo.transaction('unshelve', report=lambda x: None)
1012 1016 oldtiprev = len(repo)
1013 1017
1014 1018 pctx = repo['.']
1015 1019 tmpwctx = pctx
1016 1020 # The goal is to have a commit structure like so:
1017 1021 # ...-> pctx -> tmpwctx -> shelvectx
1018 1022 # where tmpwctx is an optional commit with the user's pending changes
1019 1023 # and shelvectx is the unshelved changes. Then we merge it all down
1020 1024 # to the original pctx.
1021 1025
1022 1026 activebookmark = _backupactivebookmark(repo)
1023 1027 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
1024 1028 tmpwctx)
1025 1029 repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
1026 1030 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1027 1031 branchtorestore = ''
1028 1032 if shelvectx.branch() != shelvectx.p1().branch():
1029 1033 branchtorestore = shelvectx.branch()
1030 1034
1031 1035 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
1032 1036 basename, pctx, tmpwctx,
1033 1037 shelvectx, branchtorestore,
1034 1038 activebookmark)
1035 1039 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
1036 1040 with ui.configoverride(overrides, 'unshelve'):
1037 1041 mergefiles(ui, repo, pctx, shelvectx)
1038 1042 restorebranch(ui, repo, branchtorestore)
1039 1043 _forgetunknownfiles(repo, shelvectx, addedbefore)
1040 1044
1041 1045 shelvedstate.clear(repo)
1042 1046 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1043 1047 unshelvecleanup(ui, repo, basename, opts)
1044 1048 finally:
1045 1049 if tr:
1046 1050 tr.release()
1047 1051 lockmod.release(lock)
1048 1052
1049 1053 @command('shelve',
1050 1054 [('A', 'addremove', None,
1051 1055 _('mark new/missing files as added/removed before shelving')),
1052 1056 ('u', 'unknown', None,
1053 1057 _('store unknown files in the shelve')),
1054 1058 ('', 'cleanup', None,
1055 1059 _('delete all shelved changes')),
1056 1060 ('', 'date', '',
1057 1061 _('shelve with the specified commit date'), _('DATE')),
1058 1062 ('d', 'delete', None,
1059 1063 _('delete the named shelved change(s)')),
1060 1064 ('e', 'edit', False,
1061 1065 _('invoke editor on commit messages')),
1062 1066 ('l', 'list', None,
1063 1067 _('list current shelves')),
1064 1068 ('m', 'message', '',
1065 1069 _('use text as shelve message'), _('TEXT')),
1066 1070 ('n', 'name', '',
1067 1071 _('use the given name for the shelved commit'), _('NAME')),
1068 1072 ('p', 'patch', None,
1069 1073 _('output patches for changes (provide the names of the shelved '
1070 1074 'changes as positional arguments)')),
1071 1075 ('i', 'interactive', None,
1072 1076 _('interactive mode, only works while creating a shelve')),
1073 1077 ('', 'stat', None,
1074 1078 _('output diffstat-style summary of changes (provide the names of '
1075 1079 'the shelved changes as positional arguments)')
1076 1080 )] + cmdutil.walkopts,
1077 1081 _('hg shelve [OPTION]... [FILE]...'),
1078 1082 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
1079 1083 def shelvecmd(ui, repo, *pats, **opts):
1080 1084 '''save and set aside changes from the working directory
1081 1085
1082 1086 Shelving takes files that "hg status" reports as not clean, saves
1083 1087 the modifications to a bundle (a shelved change), and reverts the
1084 1088 files so that their state in the working directory becomes clean.
1085 1089
1086 1090 To restore these changes to the working directory, using "hg
1087 1091 unshelve"; this will work even if you switch to a different
1088 1092 commit.
1089 1093
1090 1094 When no files are specified, "hg shelve" saves all not-clean
1091 1095 files. If specific files or directories are named, only changes to
1092 1096 those files are shelved.
1093 1097
1094 1098 In bare shelve (when no files are specified, without interactive,
1095 1099 include and exclude option), shelving remembers information if the
1096 1100 working directory was on newly created branch, in other words working
1097 1101 directory was on different branch than its first parent. In this
1098 1102 situation unshelving restores branch information to the working directory.
1099 1103
1100 1104 Each shelved change has a name that makes it easier to find later.
1101 1105 The name of a shelved change defaults to being based on the active
1102 1106 bookmark, or if there is no active bookmark, the current named
1103 1107 branch. To specify a different name, use ``--name``.
1104 1108
1105 1109 To see a list of existing shelved changes, use the ``--list``
1106 1110 option. For each shelved change, this will print its name, age,
1107 1111 and description; use ``--patch`` or ``--stat`` for more details.
1108 1112
1109 1113 To delete specific shelved changes, use ``--delete``. To delete
1110 1114 all shelved changes, use ``--cleanup``.
1111 1115 '''
1112 1116 opts = pycompat.byteskwargs(opts)
1113 1117 allowables = [
1114 1118 ('addremove', {'create'}), # 'create' is pseudo action
1115 1119 ('unknown', {'create'}),
1116 1120 ('cleanup', {'cleanup'}),
1117 1121 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
1118 1122 ('delete', {'delete'}),
1119 1123 ('edit', {'create'}),
1120 1124 ('list', {'list'}),
1121 1125 ('message', {'create'}),
1122 1126 ('name', {'create'}),
1123 1127 ('patch', {'patch', 'list'}),
1124 1128 ('stat', {'stat', 'list'}),
1125 1129 ]
1126 1130 def checkopt(opt):
1127 1131 if opts.get(opt):
1128 1132 for i, allowable in allowables:
1129 1133 if opts[i] and opt not in allowable:
1130 1134 raise error.Abort(_("options '--%s' and '--%s' may not be "
1131 1135 "used together") % (opt, i))
1132 1136 return True
1133 1137 if checkopt('cleanup'):
1134 1138 if pats:
1135 1139 raise error.Abort(_("cannot specify names when using '--cleanup'"))
1136 1140 return cleanupcmd(ui, repo)
1137 1141 elif checkopt('delete'):
1138 1142 return deletecmd(ui, repo, pats)
1139 1143 elif checkopt('list'):
1140 1144 return listcmd(ui, repo, pats, opts)
1141 1145 elif checkopt('patch') or checkopt('stat'):
1142 1146 return patchcmds(ui, repo, pats, opts)
1143 1147 else:
1144 1148 return createcmd(ui, repo, pats, opts)
1145 1149
1146 1150 def extsetup(ui):
1147 1151 cmdutil.unfinishedstates.append(
1148 1152 [shelvedstate._filename, False, False,
1149 1153 _('unshelve already in progress'),
1150 1154 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
1151 1155 cmdutil.afterresolvedstates.append(
1152 1156 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now