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