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