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