##// END OF EJS Templates
rebase: support multiple roots for rebaseset...
Pierre-Yves David -
r18424:100fdc84 default
parent child Browse files
Show More
@@ -574,9 +574,9 b' def abort(repo, originalwd, target, stat'
574 574 merge.update(repo, repo[originalwd].rev(), False, True, False)
575 575 rebased = filter(lambda x: x > -1 and x != target, state.values())
576 576 if rebased:
577 strippoint = min(rebased)
577 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
578 578 # no backup of rebased cset versions needed
579 repair.strip(repo.ui, repo, repo[strippoint].node())
579 repair.strip(repo.ui, repo, strippoints)
580 580 clearstatus(repo)
581 581 repo.ui.warn(_('rebase aborted\n'))
582 582 return 0
@@ -599,65 +599,65 b' def buildstate(repo, dest, rebaseset, co'
599 599 roots = list(repo.set('roots(%ld)', rebaseset))
600 600 if not roots:
601 601 raise util.Abort(_('no matching revisions'))
602 if len(roots) > 1:
603 raise util.Abort(_("can't rebase multiple roots"))
604 root = roots[0]
605
606 commonbase = root.ancestor(dest)
607 if commonbase == root:
608 raise util.Abort(_('source is ancestor of destination'))
609 if commonbase == dest:
610 samebranch = root.branch() == dest.branch()
611 if not collapse and samebranch and root in dest.children():
612 repo.ui.debug('source is a child of destination\n')
613 return None
602 roots.sort()
603 state = {}
604 detachset = set()
605 for root in roots:
606 commonbase = root.ancestor(dest)
607 if commonbase == root:
608 raise util.Abort(_('source is ancestor of destination'))
609 if commonbase == dest:
610 samebranch = root.branch() == dest.branch()
611 if not collapse and samebranch and root in dest.children():
612 repo.ui.debug('source is a child of destination\n')
613 return None
614 614
615 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
616 state = dict.fromkeys(rebaseset, nullrev)
617 # Rebase tries to turn <dest> into a parent of <root> while
618 # preserving the number of parents of rebased changesets:
619 #
620 # - A changeset with a single parent will always be rebased as a
621 # changeset with a single parent.
622 #
623 # - A merge will be rebased as merge unless its parents are both
624 # ancestors of <dest> or are themselves in the rebased set and
625 # pruned while rebased.
626 #
627 # If one parent of <root> is an ancestor of <dest>, the rebased
628 # version of this parent will be <dest>. This is always true with
629 # --base option.
630 #
631 # Otherwise, we need to *replace* the original parents with
632 # <dest>. This "detaches" the rebased set from its former location
633 # and rebases it onto <dest>. Changes introduced by ancestors of
634 # <root> not common with <dest> (the detachset, marked as
635 # nullmerge) are "removed" from the rebased changesets.
636 #
637 # - If <root> has a single parent, set it to <dest>.
638 #
639 # - If <root> is a merge, we cannot decide which parent to
640 # replace, the rebase operation is not clearly defined.
641 #
642 # The table below sums up this behavior:
643 #
644 # +--------------------+----------------------+-------------------------+
645 # | | one parent | merge |
646 # +--------------------+----------------------+-------------------------+
647 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
648 # | | | remapped to <dest> |
649 # +--------------------+----------------------+-------------------------+
650 # | unrelated source | new parent is <dest> | ambiguous, abort |
651 # +--------------------+----------------------+-------------------------+
652 #
653 # The actual abort is handled by `defineparents`
654 if len(root.parents()) <= 1:
655 # ancestors of <root> not ancestors of <dest>
656 detachset = repo.changelog.findmissingrevs([commonbase.rev()],
657 [root.rev()])
658 state.update(dict.fromkeys(detachset, nullmerge))
659 # detachset can have root, and we definitely want to rebase that
660 state[root.rev()] = nullrev
615 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
616 state.update(dict.fromkeys(rebaseset, nullrev))
617 # Rebase tries to turn <dest> into a parent of <root> while
618 # preserving the number of parents of rebased changesets:
619 #
620 # - A changeset with a single parent will always be rebased as a
621 # changeset with a single parent.
622 #
623 # - A merge will be rebased as merge unless its parents are both
624 # ancestors of <dest> or are themselves in the rebased set and
625 # pruned while rebased.
626 #
627 # If one parent of <root> is an ancestor of <dest>, the rebased
628 # version of this parent will be <dest>. This is always true with
629 # --base option.
630 #
631 # Otherwise, we need to *replace* the original parents with
632 # <dest>. This "detaches" the rebased set from its former location
633 # and rebases it onto <dest>. Changes introduced by ancestors of
634 # <root> not common with <dest> (the detachset, marked as
635 # nullmerge) are "removed" from the rebased changesets.
636 #
637 # - If <root> has a single parent, set it to <dest>.
638 #
639 # - If <root> is a merge, we cannot decide which parent to
640 # replace, the rebase operation is not clearly defined.
641 #
642 # The table below sums up this behavior:
643 #
644 # +------------------+----------------------+-------------------------+
645 # | | one parent | merge |
646 # +------------------+----------------------+-------------------------+
647 # | parent in | new parent is <dest> | parents in ::<dest> are |
648 # | ::<dest> | | remapped to <dest> |
649 # +------------------+----------------------+-------------------------+
650 # | unrelated source | new parent is <dest> | ambiguous, abort |
651 # +------------------+----------------------+-------------------------+
652 #
653 # The actual abort is handled by `defineparents`
654 if len(root.parents()) <= 1:
655 # ancestors of <root> not ancestors of <dest>
656 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
657 [root.rev()]))
658 for r in detachset:
659 if r not in state:
660 state[r] = nullmerge
661 661 return repo['.'].rev(), dest.rev(), state
662 662
663 663 def clearrebased(ui, repo, state, collapsedas=None):
@@ -677,12 +677,16 b' def clearrebased(ui, repo, state, collap'
677 677 else:
678 678 rebased = [rev for rev in state if state[rev] != nullmerge]
679 679 if rebased:
680 if set(repo.changelog.descendants([min(rebased)])) - set(state):
681 ui.warn(_("warning: new changesets detected "
682 "on source branch, not stripping\n"))
683 else:
680 stripped = []
681 for root in repo.set('roots(%ld)', rebased):
682 if set(repo.changelog.descendants([root.rev()])) - set(state):
683 ui.warn(_("warning: new changesets detected "
684 "on source branch, not stripping\n"))
685 else:
686 stripped.append(root.node())
687 if stripped:
684 688 # backup the old csets by default
685 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
689 repair.strip(ui, repo, stripped, "all")
686 690
687 691
688 692 def pullrebase(orig, ui, repo, *args, **opts):
@@ -306,3 +306,26 b' Test that rewriting leaving instability '
306 306
307 307
308 308
309 Test multiple root handling
310 ------------------------------------
311
312 $ hg rebase --dest 4 --rev '7+11+9'
313 $ hg log -G
314 @ 14:00891d85fcfc C
315 |
316 | o 13:102b4c1d889b D
317 |/
318 | o 12:bfe264faf697 H
319 |/
320 | o 10:7c6027df6a99 B
321 | |
322 | x 7:02de42196ebe H
323 | |
324 +---o 6:eea13746799a G
325 | |/
326 | o 5:24b6387c8c8c F
327 | |
328 o | 4:9520eea781bc E
329 |/
330 o 0:cd010b8cd998 A
331
@@ -542,6 +542,108 b' We would expect heads are I, F if it was'
542 542 $ hg clone -q -u . ah ah6
543 543 $ cd ah6
544 544 $ hg rebase -r '(4+6)::' -d 1
545 abort: can't rebase multiple roots
546 [255]
545 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
546 $ hg tglog
547 @ 8: 'I'
548 |
549 o 7: 'H'
550 |
551 o 6: 'G'
552 |
553 | o 5: 'F'
554 | |
555 | o 4: 'E'
556 |/
557 | o 3: 'D'
558 | |
559 | o 2: 'C'
560 | |
561 o | 1: 'B'
562 |/
563 o 0: 'A'
564
547 565 $ cd ..
566
567 More complexe rebase with multiple roots
568 each root have a different common ancestor with the destination and this is a detach
569
570 (setup)
571
572 $ hg clone -q -u . a a8
573 $ cd a8
574 $ echo I > I
575 $ hg add I
576 $ hg commit -m I
577 $ hg up 4
578 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
579 $ echo I > J
580 $ hg add J
581 $ hg commit -m J
582 created new head
583 $ echo I > K
584 $ hg add K
585 $ hg commit -m K
586 $ hg tglog
587 @ 10: 'K'
588 |
589 o 9: 'J'
590 |
591 | o 8: 'I'
592 | |
593 | o 7: 'H'
594 | |
595 +---o 6: 'G'
596 | |/
597 | o 5: 'F'
598 | |
599 o | 4: 'E'
600 |/
601 | o 3: 'D'
602 | |
603 | o 2: 'C'
604 | |
605 | o 1: 'B'
606 |/
607 o 0: 'A'
608
609 (actual test)
610
611 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
612 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-backup.hg (glob)
613 $ hg log --rev 'children(desc(G))'
614 changeset: 9:adb617877056
615 parent: 6:eea13746799a
616 user: test
617 date: Thu Jan 01 00:00:00 1970 +0000
618 summary: I
619
620 changeset: 10:882431a34a0e
621 tag: tip
622 parent: 6:eea13746799a
623 user: test
624 date: Thu Jan 01 00:00:00 1970 +0000
625 summary: K
626
627 $ hg tglog
628 @ 10: 'K'
629 |
630 | o 9: 'I'
631 |/
632 | o 8: 'J'
633 | |
634 | | o 7: 'H'
635 | | |
636 o---+ 6: 'G'
637 |/ /
638 | o 5: 'F'
639 | |
640 o | 4: 'E'
641 |/
642 | o 3: 'D'
643 | |
644 | o 2: 'C'
645 | |
646 | o 1: 'B'
647 |/
648 o 0: 'A'
649
General Comments 0
You need to be logged in to leave comments. Login now