##// END OF EJS Templates
shelve: use rebase instead of merge (issue4068)...
Durham Goode -
r19961:1d7a36ff stable
parent child Browse files
Show More
@@ -27,6 +27,7 b' from mercurial import changegroup, cmdut'
27 from mercurial import error, hg, mdiff, merge, patch, repair, util
27 from mercurial import error, hg, mdiff, merge, patch, repair, util
28 from mercurial import templatefilters
28 from mercurial import templatefilters
29 from mercurial import lock as lockmod
29 from mercurial import lock as lockmod
30 from hgext import rebase
30 import errno
31 import errno
31
32
32 cmdtable = {}
33 cmdtable = {}
@@ -95,25 +96,35 b' class shelvedstate(object):'
95 raise util.Abort(_('this version of shelve is incompatible '
96 raise util.Abort(_('this version of shelve is incompatible '
96 'with the version used in this repo'))
97 'with the version used in this repo'))
97 name = fp.readline().strip()
98 name = fp.readline().strip()
99 wctx = fp.readline().strip()
100 pendingctx = fp.readline().strip()
98 parents = [bin(h) for h in fp.readline().split()]
101 parents = [bin(h) for h in fp.readline().split()]
99 stripnodes = [bin(h) for h in fp.readline().split()]
102 stripnodes = [bin(h) for h in fp.readline().split()]
103 unknownfiles = fp.readline()[:-1].split('\0')
100 finally:
104 finally:
101 fp.close()
105 fp.close()
102
106
103 obj = cls()
107 obj = cls()
104 obj.name = name
108 obj.name = name
109 obj.wctx = repo[bin(wctx)]
110 obj.pendingctx = repo[bin(pendingctx)]
105 obj.parents = parents
111 obj.parents = parents
106 obj.stripnodes = stripnodes
112 obj.stripnodes = stripnodes
113 obj.unknownfiles = unknownfiles
107
114
108 return obj
115 return obj
109
116
110 @classmethod
117 @classmethod
111 def save(cls, repo, name, stripnodes):
118 def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
119 unknownfiles):
112 fp = repo.opener(cls._filename, 'wb')
120 fp = repo.opener(cls._filename, 'wb')
113 fp.write('%i\n' % cls._version)
121 fp.write('%i\n' % cls._version)
114 fp.write('%s\n' % name)
122 fp.write('%s\n' % name)
123 fp.write('%s\n' % hex(originalwctx.node()))
124 fp.write('%s\n' % hex(pendingctx.node()))
115 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
125 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
116 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
126 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
127 fp.write('%s\n' % '\0'.join(unknownfiles))
117 fp.close()
128 fp.close()
118
129
119 @classmethod
130 @classmethod
@@ -368,44 +379,55 b' def unshelveabort(ui, repo, state, opts)'
368 lock = None
379 lock = None
369 try:
380 try:
370 checkparents(repo, state)
381 checkparents(repo, state)
382
383 util.rename(repo.join('unshelverebasestate'),
384 repo.join('rebasestate'))
385 try:
386 rebase.rebase(ui, repo, **{
387 'abort' : True
388 })
389 except Exception:
390 util.rename(repo.join('rebasestate'),
391 repo.join('unshelverebasestate'))
392 raise
393
371 lock = repo.lock()
394 lock = repo.lock()
372 merge.mergestate(repo).reset()
395
373 if opts['keep']:
396 mergefiles(ui, repo, state.wctx, state.pendingctx, state.unknownfiles)
374 repo.setparents(repo.dirstate.parents()[0])
397
375 else:
376 revertfiles = readshelvedfiles(repo, state.name)
377 wctx = repo.parents()[0]
378 cmdutil.revert(ui, repo, wctx, [wctx.node(), nullid],
379 *pathtofiles(repo, revertfiles),
380 **{'no_backup': True})
381 # fix up the weird dirstate states the merge left behind
382 mf = wctx.manifest()
383 dirstate = repo.dirstate
384 for f in revertfiles:
385 if f in mf:
386 dirstate.normallookup(f)
387 else:
388 dirstate.drop(f)
389 dirstate._pl = (wctx.node(), nullid)
390 dirstate._dirty = True
391 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
398 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
392 shelvedstate.clear(repo)
399 shelvedstate.clear(repo)
393 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
400 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
394 finally:
401 finally:
395 lockmod.release(lock, wlock)
402 lockmod.release(lock, wlock)
396
403
404 def mergefiles(ui, repo, wctx, shelvectx, unknownfiles):
405 """updates to wctx and merges the changes from shelvectx into the
406 dirstate. drops any files in unknownfiles from the dirstate."""
407 oldquiet = ui.quiet
408 try:
409 ui.quiet = True
410 hg.update(repo, wctx.node())
411 files = []
412 files.extend(shelvectx.files())
413 files.extend(shelvectx.parents()[0].files())
414 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
415 *pathtofiles(repo, files),
416 **{'no_backup': True})
417 finally:
418 ui.quiet = oldquiet
419
420 # Send untracked files back to being untracked
421 dirstate = repo.dirstate
422 for f in unknownfiles:
423 dirstate.drop(f)
424
397 def unshelvecleanup(ui, repo, name, opts):
425 def unshelvecleanup(ui, repo, name, opts):
398 """remove related files after an unshelve"""
426 """remove related files after an unshelve"""
399 if not opts['keep']:
427 if not opts['keep']:
400 for filetype in 'hg files patch'.split():
428 for filetype in 'hg files patch'.split():
401 shelvedfile(repo, name, filetype).unlink()
429 shelvedfile(repo, name, filetype).unlink()
402
430
403 def finishmerge(ui, repo, ms, stripnodes, name, opts):
404 # Reset the working dir so it's no longer in a merge state.
405 dirstate = repo.dirstate
406 dirstate.setparents(dirstate._pl[0])
407 shelvedstate.clear(repo)
408
409 def unshelvecontinue(ui, repo, state, opts):
431 def unshelvecontinue(ui, repo, state, opts):
410 """subcommand to continue an in-progress unshelve"""
432 """subcommand to continue an in-progress unshelve"""
411 # We're finishing off a merge. First parent is our original
433 # We're finishing off a merge. First parent is our original
@@ -419,9 +441,30 b' def unshelvecontinue(ui, repo, state, op'
419 raise util.Abort(
441 raise util.Abort(
420 _("unresolved conflicts, can't continue"),
442 _("unresolved conflicts, can't continue"),
421 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
443 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
422 finishmerge(ui, repo, ms, state.stripnodes, state.name, opts)
444
423 lock = repo.lock()
445 lock = repo.lock()
446
447 util.rename(repo.join('unshelverebasestate'),
448 repo.join('rebasestate'))
449 try:
450 rebase.rebase(ui, repo, **{
451 'continue' : True
452 })
453 except Exception:
454 util.rename(repo.join('rebasestate'),
455 repo.join('unshelverebasestate'))
456 raise
457
458 shelvectx = repo['tip']
459 if not shelvectx in state.pendingctx.children():
460 # rebase was a no-op, so it produced no child commit
461 shelvectx = state.pendingctx
462
463 mergefiles(ui, repo, state.wctx, shelvectx, state.unknownfiles)
464
465 state.stripnodes.append(shelvectx.node())
424 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
466 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
467 shelvedstate.clear(repo)
425 unshelvecleanup(ui, repo, state.name, opts)
468 unshelvecleanup(ui, repo, state.name, opts)
426 ui.status(_("unshelve of '%s' complete\n") % state.name)
469 ui.status(_("unshelve of '%s' complete\n") % state.name)
427 finally:
470 finally:
@@ -491,71 +534,96 b' def unshelve(ui, repo, *shelved, **opts)'
491
534
492 shelvedfiles = readshelvedfiles(repo, basename)
535 shelvedfiles = readshelvedfiles(repo, basename)
493
536
494 m, a, r, d = repo.status()[:4]
495 unsafe = set(m + a + r + d).intersection(shelvedfiles)
496 if unsafe:
497 ui.warn(_('the following shelved files have been modified:\n'))
498 for f in sorted(unsafe):
499 ui.warn(' %s\n' % f)
500 ui.warn(_('you must commit, revert, or shelve your changes before you '
501 'can proceed\n'))
502 raise util.Abort(_('cannot unshelve due to local changes\n'))
503
504 wlock = lock = tr = None
537 wlock = lock = tr = None
505 try:
538 try:
506 lock = repo.lock()
539 lock = repo.lock()
540 wlock = repo.wlock()
507
541
508 tr = repo.transaction('unshelve', report=lambda x: None)
542 tr = repo.transaction('unshelve', report=lambda x: None)
509 oldtiprev = len(repo)
543 oldtiprev = len(repo)
544
545 wctx = repo['.']
546 tmpwctx = wctx
547 # The goal is to have a commit structure like so:
548 # ...-> wctx -> tmpwctx -> shelvectx
549 # where tmpwctx is an optional commit with the user's pending changes
550 # and shelvectx is the unshelved changes. Then we merge it all down
551 # to the original wctx.
552
553 # Store pending changes in a commit
554 m, a, r, d, u = repo.status(unknown=True)[:5]
555 if m or a or r or d or u:
556 def commitfunc(ui, repo, message, match, opts):
557 hasmq = util.safehasattr(repo, 'mq')
558 if hasmq:
559 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
560
561 try:
562 return repo.commit(message, 'shelve@localhost',
563 opts.get('date'), match)
564 finally:
565 if hasmq:
566 repo.mq.checkapplied = saved
567
568 tempopts = {}
569 tempopts['message'] = "pending changes temporary commit"
570 tempopts['addremove'] = True
571 oldquiet = ui.quiet
572 try:
573 ui.quiet = True
574 node = cmdutil.commit(ui, repo, commitfunc, None, tempopts)
575 finally:
576 ui.quiet = oldquiet
577 tmpwctx = repo[node]
578
510 try:
579 try:
511 fp = shelvedfile(repo, basename, 'hg').opener()
580 fp = shelvedfile(repo, basename, 'hg').opener()
512 gen = changegroup.readbundle(fp, fp.name)
581 gen = changegroup.readbundle(fp, fp.name)
513 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
582 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
514 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
583 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
515 phases.retractboundary(repo, phases.secret, nodes)
584 phases.retractboundary(repo, phases.secret, nodes)
516 tr.close()
517 finally:
585 finally:
518 fp.close()
586 fp.close()
519
587
520 tip = repo['tip']
588 shelvectx = repo['tip']
521 wctx = repo['.']
522 ancestor = tip.ancestor(wctx)
523
589
524 wlock = repo.wlock()
590 # If the shelve is not immediately on top of the commit
591 # we'll be merging with, rebase it to be on top.
592 if tmpwctx.node() != shelvectx.parents()[0].node():
593 try:
594 rebase.rebase(ui, repo, **{
595 'rev' : [shelvectx.rev()],
596 'dest' : str(tmpwctx.rev()),
597 'keep' : True,
598 })
599 except error.InterventionRequired:
600 tr.close()
525
601
526 if ancestor.node() != wctx.node():
527 conflicts = hg.merge(repo, tip.node(), force=True, remind=False)
528 ms = merge.mergestate(repo)
529 stripnodes = [repo.changelog.node(rev)
602 stripnodes = [repo.changelog.node(rev)
530 for rev in xrange(oldtiprev, len(repo))]
603 for rev in xrange(oldtiprev, len(repo))]
531 if conflicts:
604 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes, u)
532 shelvedstate.save(repo, basename, stripnodes)
605
533 # Fix up the dirstate entries of files from the second
606 util.rename(repo.join('rebasestate'),
534 # parent as if we were not merging, except for those
607 repo.join('unshelverebasestate'))
535 # with unresolved conflicts.
536 parents = repo.parents()
537 revertfiles = set(parents[1].files()).difference(ms)
538 cmdutil.revert(ui, repo, parents[1],
539 (parents[0].node(), nullid),
540 *pathtofiles(repo, revertfiles),
541 **{'no_backup': True})
542 raise error.InterventionRequired(
608 raise error.InterventionRequired(
543 _("unresolved conflicts (see 'hg resolve', then "
609 _("unresolved conflicts (see 'hg resolve', then "
544 "'hg unshelve --continue')"))
610 "'hg unshelve --continue')"))
545 finishmerge(ui, repo, ms, stripnodes, basename, opts)
611
546 else:
612 # refresh ctx after rebase completes
547 parent = tip.parents()[0]
613 shelvectx = repo['tip']
548 hg.update(repo, parent.node())
614
549 cmdutil.revert(ui, repo, tip, repo.dirstate.parents(),
615 if not shelvectx in tmpwctx.children():
550 *pathtofiles(repo, tip.files()),
616 # rebase was a no-op, so it produced no child commit
551 **{'no_backup': True})
617 shelvectx = tmpwctx
552
618
553 prevquiet = ui.quiet
619 mergefiles(ui, repo, wctx, shelvectx, u)
554 ui.quiet = True
620 shelvedstate.clear(repo)
555 try:
621
556 repo.rollback(force=True)
622 # The transaction aborting will strip all the commits for us,
557 finally:
623 # but it doesn't update the inmemory structures, so addchangegroup
558 ui.quiet = prevquiet
624 # hooks still fire and try to operate on the missing commits.
625 # Clean up manually to prevent this.
626 repo.changelog.strip(oldtiprev, tr)
559
627
560 unshelvecleanup(ui, repo, basename, opts)
628 unshelvecleanup(ui, repo, basename, opts)
561 finally:
629 finally:
@@ -27,7 +27,6 b' shelving in an empty repo should be poss'
27 adding manifests
27 adding manifests
28 adding file changes
28 adding file changes
29 added 1 changesets with 5 changes to 5 files
29 added 1 changesets with 5 changes to 5 files
30 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
31
30
32 $ hg commit -q -m 'initial commit'
31 $ hg commit -q -m 'initial commit'
33
32
@@ -100,19 +99,19 b' delete our older shelved change'
100 $ hg shelve -d default
99 $ hg shelve -d default
101 $ hg qfinish -a -q
100 $ hg qfinish -a -q
102
101
103 local edits should prevent a shelved change from applying
102 local edits should not prevent a shelved change from applying
104
103
105 $ echo e>>a/a
104 $ printf "z\na\n" > a/a
106 $ hg unshelve
105 $ hg unshelve --keep
107 unshelving change 'default-01'
106 unshelving change 'default-01'
108 the following shelved files have been modified:
107 adding changesets
109 a/a
108 adding manifests
110 you must commit, revert, or shelve your changes before you can proceed
109 adding file changes
111 abort: cannot unshelve due to local changes
110 added 1 changesets with 3 changes to 8 files (+1 heads)
111 merging a/a
112
112
113 [255]
113 $ hg revert --all -q
114
114 $ rm a/a.orig b.rename/b c.copy
115 $ hg revert -C a/a
116
115
117 apply it and make sure our state is as expected
116 apply it and make sure our state is as expected
118
117
@@ -122,7 +121,6 b' apply it and make sure our state is as e'
122 adding manifests
121 adding manifests
123 adding file changes
122 adding file changes
124 added 1 changesets with 3 changes to 8 files
123 added 1 changesets with 3 changes to 8 files
125 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 $ hg status -C
124 $ hg status -C
127 M a/a
125 M a/a
128 A b.rename/b
126 A b.rename/b
@@ -201,24 +199,21 b' force a conflicted merge to occur'
201 merging a/a
199 merging a/a
202 warning: conflicts during merge.
200 warning: conflicts during merge.
203 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
201 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
204 2 files updated, 0 files merged, 1 files removed, 1 files unresolved
205 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
206 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
202 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
207 [1]
203 [1]
208
204
209 ensure that we have a merge with unresolved conflicts
205 ensure that we have a merge with unresolved conflicts
210
206
211 $ hg heads -q
207 $ hg heads -q --template '{rev}\n'
212 4:cebf2b8de087
208 5
213 3:2e69b451d1ea
209 4
214 $ hg parents -q
210 $ hg parents -q --template '{rev}\n'
215 3:2e69b451d1ea
211 4
216 4:cebf2b8de087
212 5
217 $ hg status
213 $ hg status
218 M a/a
214 M a/a
219 M b.rename/b
215 M b.rename/b
220 M c.copy
216 M c.copy
221 A foo/foo
222 R b/b
217 R b/b
223 ? a/a.orig
218 ? a/a.orig
224 $ hg diff
219 $ hg diff
@@ -248,12 +243,6 b' ensure that we have a merge with unresol'
248 +++ b/c.copy
243 +++ b/c.copy
249 @@ -0,0 +1,1 @@
244 @@ -0,0 +1,1 @@
250 +c
245 +c
251 diff --git a/foo/foo b/foo/foo
252 new file mode 100644
253 --- /dev/null
254 +++ b/foo/foo
255 @@ -0,0 +1,1 @@
256 +foo
257 $ hg resolve -l
246 $ hg resolve -l
258 U a/a
247 U a/a
259
248
@@ -268,10 +257,10 b' abort the unshelve and be happy'
268 M a/a
257 M a/a
269 M b.rename/b
258 M b.rename/b
270 M c.copy
259 M c.copy
271 A foo/foo
272 R b/b
260 R b/b
273 ? a/a.orig
261 ? a/a.orig
274 $ hg unshelve -a
262 $ hg unshelve -a
263 rebase aborted
275 unshelve of 'default' aborted
264 unshelve of 'default' aborted
276 $ hg heads -q
265 $ hg heads -q
277 3:2e69b451d1ea
266 3:2e69b451d1ea
@@ -330,9 +319,9 b' ensure the repo is as we hope'
330 3:2e69b451d1ea
319 3:2e69b451d1ea
331
320
332 $ hg status -C
321 $ hg status -C
333 M b.rename/b
322 A b.rename/b
334 b/b
323 b/b
335 M c.copy
324 A c.copy
336 c
325 c
337 A foo/foo
326 A foo/foo
338 R b/b
327 R b/b
@@ -372,6 +361,7 b' ensure that metadata-only changes are sh'
372 set up another conflict between a commit and a shelved change
361 set up another conflict between a commit and a shelved change
373
362
374 $ hg revert -q -C -a
363 $ hg revert -q -C -a
364 $ rm a/a.orig b.rename/b c.copy
375 $ echo a >> a/a
365 $ echo a >> a/a
376 $ hg shelve -q
366 $ hg shelve -q
377 $ echo x >> a/a
367 $ echo x >> a/a
@@ -387,7 +377,6 b' if we resolve a conflict while unshelvin'
387 adding file changes
377 adding file changes
388 added 1 changesets with 1 changes to 6 files (+1 heads)
378 added 1 changesets with 1 changes to 6 files (+1 heads)
389 merging a/a
379 merging a/a
390 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
391 $ hg parents -q
380 $ hg parents -q
392 4:33f7f61e6c5e
381 4:33f7f61e6c5e
393 $ hg shelve -l
382 $ hg shelve -l
@@ -411,7 +400,6 b' test keep and cleanup'
411 adding manifests
400 adding manifests
412 adding file changes
401 adding file changes
413 added 1 changesets with 1 changes to 7 files
402 added 1 changesets with 1 changes to 7 files
414 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 $ hg shelve --list
403 $ hg shelve --list
416 default (*) create conflict (glob)
404 default (*) create conflict (glob)
417 $ hg shelve --cleanup
405 $ hg shelve --cleanup
@@ -433,7 +421,6 b' test bookmarks'
433 adding manifests
421 adding manifests
434 adding file changes
422 adding file changes
435 added 1 changesets with 1 changes to 7 files
423 added 1 changesets with 1 changes to 7 files
436 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 $ hg bookmark
424 $ hg bookmark
438 * test 4:33f7f61e6c5e
425 * test 4:33f7f61e6c5e
439
426
@@ -450,7 +437,6 b' shelve should still work even if mq is d'
450 adding manifests
437 adding manifests
451 adding file changes
438 adding file changes
452 added 1 changesets with 1 changes to 7 files
439 added 1 changesets with 1 changes to 7 files
453 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
454
440
455 shelve should leave dirstate clean (issue 4055)
441 shelve should leave dirstate clean (issue 4055)
456
442
@@ -479,8 +465,52 b' shelve should leave dirstate clean (issu'
479 adding manifests
465 adding manifests
480 adding file changes
466 adding file changes
481 added 2 changesets with 2 changes to 2 files (+1 heads)
467 added 2 changesets with 2 changes to 2 files (+1 heads)
482 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 $ hg status
468 $ hg status
484 M z
469 M z
485
470
486 $ cd ..
471 $ cd ..
472
473 shelve should only unshelve pending changes (issue 4068)
474
475 $ hg init onlypendingchanges
476 $ cd onlypendingchanges
477 $ touch a
478 $ hg ci -Aqm a
479 $ touch b
480 $ hg ci -Aqm b
481 $ hg up -q 0
482 $ touch c
483 $ hg ci -Aqm c
484
485 $ touch d
486 $ hg add d
487 $ hg shelve
488 shelved as default
489 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
490 $ hg up -q 1
491 $ hg unshelve
492 unshelving change 'default'
493 adding changesets
494 adding manifests
495 adding file changes
496 added 1 changesets with 1 changes to 3 files
497 $ hg status
498 A d
499
500 unshelve should work on an ancestor of the original commit
501
502 $ hg shelve
503 shelved as default
504 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
505 $ hg up 0
506 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
507 $ hg unshelve
508 unshelving change 'default'
509 adding changesets
510 adding manifests
511 adding file changes
512 added 1 changesets with 1 changes to 3 files
513 $ hg status
514 A d
515
516 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now