##// END OF EJS Templates
merge with stable
Augie Fackler -
r30542:64b55bff merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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