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