evolve: consider all obsolete ancestors when finding new parent for orphan draft
authorManuel Jacob <me@manueljacob.de>
Sat, 21 Mar 2020 20:36:41 +0100
changeset 5314 f12d2172a133
parent 5312 9035901412e6
child 5315 c5020c7cdf74
evolve: consider all obsolete ancestors when finding new parent for orphan Both the old and the new logic have in common that the orphan changeset is moved to the tipmost successor of one or more ancestors of the orphan changeset. The previous logic considered only the first non-pruned ancestor of the orphan changeset. The new logic considers all obsolete ancestors of the orphan changeset. The logic in _possibledestination() had to be changed to be consistent with the new logic.
hgext3rd/evolve/evolvecmd.py
hgext3rd/evolve/utility.py
tests/test-evolve-issue5832.t
tests/test-evolve-orphan-split.t
tests/test-evolve-orphan-switched-ancestors.t
--- a/hgext3rd/evolve/evolvecmd.py	Wed Apr 29 00:44:14 2020 +0800
+++ b/hgext3rd/evolve/evolvecmd.py	Sat Mar 21 20:36:41 2020 +0100
@@ -122,28 +122,37 @@
     if not pctx.obsolete():
         ui.warn(_(b"cannot solve instability of %s, skipping\n") % orig)
         return (False, b".")
-    obs = pctx
-    try:
-        newer = utility._singlesuccessor(repo, obs)
-        # search of a parent which isn't the orig
-        while repo[newer].node() == orig.node():
-            obs = obs.parents()[0]
-            newer = utility._singlesuccessor(repo, obs)
-    except utility.MultipleSuccessorsError as exc:
-        if exc.divergenceflag:
+    targetancs = []
+    # Find successors of the changesets that cause the orphan. These will be
+    # the ancestors of the target (unless they are split over multiple
+    # topological branches, in which case we ask the user to choose one).
+    for obsrev in repo.revs(b'::%d and obsolete()', pctx.rev()):
+        obs = repo[obsrev]
+        succs = obsutil.successorssets(repo, obs.node())
+        if not succs:
+            continue
+        if len(succs) > 1:
             msg = _(b"skipping %s: divergent rewriting. can't choose "
                     b"destination\n") % obs
             ui.write_err(msg)
             return (False, b".")
-        if exc.splitflag:
-            splitsucc = utility.picksplitsuccessor(ui, repo, obs, orig)
-            if not splitsucc[0]:
+        targetancs.extend(node for node in succs[0] if node != orig.node())
+    if not targetancs:
+        # If no successors of the changesets that cause the orphan could be
+        # found, fall back to next non-obsolete ancestor.
+        newer = repo.revs(b'::%d and not obsolete()', pctx.rev()).last()
+        if newer is None:
+            newer = nodemod.nullid
+    else:
+        if True: # keep indentation for now
+            orphantarget = utility.pickorphantarget(ui, repo, targetancs, orig)
+            if not orphantarget[0]:
                 msg = _(b"could not solve instability, "
                         b"ambiguous destination: "
-                        b"parent split across two branches\n")
+                        b"ancestors split across different branches\n")
                 ui.write_err(msg)
                 return (False, b".")
-            newer = splitsucc[1]
+            newer = orphantarget[1]
     target = repo[newer]
     if not ui.quiet or confirm:
         repo.ui.write(_(b'move:'), label=b'evolve.operation')
@@ -1203,10 +1212,15 @@
         r = tovisit.pop()
         if r == -1:
             continue
+        ctx = repo[r]
+        if not (ctx.obsolete() or ctx.orphan()):
+            # None of the ancestors are obsolete, therefore we don't have to
+            # search them. The revision itself is a possible destination.
+            dest.add(r)
+            continue
+        tovisit.extend(parents(r))
         succsets = obsutil.successorssets(repo, tonode(r), cache=cache)
-        if not succsets:
-            tovisit.extend(parents(r))
-        else:
+        if succsets:
             # We should probably pick only one destination from split
             # (case where '1 < len(ss)'), This could be the currently tipmost
             # but logic is less clear when result of the split are now on
@@ -1214,7 +1228,7 @@
             for ss in succsets:
                 for n in ss:
                     dest.add(torev(n))
-    return dest
+    return set(repo.revs(b'heads(%ld::%ld)', dest, dest))
 
 def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat):
     """Used by the evolve function to display an error message when
--- a/hgext3rd/evolve/utility.py	Wed Apr 29 00:44:14 2020 +0800
+++ b/hgext3rd/evolve/utility.py	Sat Mar 21 20:36:41 2020 +0100
@@ -126,24 +126,21 @@
 
     return repo[newer[0][0]].rev()
 
-def picksplitsuccessor(ui, repo, ctx, evolvecand):
-    """choose a successor of ctx from split targets
+def pickorphantarget(ui, repo, targetancestors, evolvecand):
+    """choose a new target for an orphan changeset
 
-    Choose highest one if all successors are in a topological branch. And if
-    they are split over multiple topological branches, we ask user to choose
-    an evolve destination.
+    Choose highest one if all successors of its ancestors are in a topological
+    branch. And if they are on different topological branches, we ask the user
+    to choose an evolve destination.
 
-    Return (True, succ) unless split targets are split over multiple
-    topological branches and user didn't choose any evolve destination,
-    in which case return (False, '.')
+    Return (True, succ) unless successors of ancestors are on different
+    topological branches and user didn't choose any evolve destination, in
+    which case return (False, '.').
     """
-    targets = obsutil.successorssets(repo, ctx.node())[0]
-    assert targets
-    targetrevs = [repo[r].rev() for r in targets]
-    heads = repo.revs(b'heads(%ld::%ld)', targetrevs, targetrevs)
+    heads = repo.revs(b'heads(%ln::%ln)', targetancestors, targetancestors)
     if len(heads) > 1:
-        cheader = (_(b"ancestor of '%s' split over multiple topological"
-                     b" branches.\nchoose an evolve destination:") %
+        cheader = (_(b"successors of ancestors of %s are on different "
+                     b"topological branches\nchoose an evolve destination:") %
                    evolvecand)
         selectedrev = revselectionprompt(ui, repo, list(heads), cheader)
         if selectedrev is None:
--- a/tests/test-evolve-issue5832.t	Wed Apr 29 00:44:14 2020 +0800
+++ b/tests/test-evolve-issue5832.t	Sat Mar 21 20:36:41 2020 +0100
@@ -107,7 +107,7 @@
   move:[2] added b
   atop:[5] added a
   hg rebase -r a1da0651488c -d 7014ec2829cd
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
 Resolving instability using `hg evolve`
 
@@ -117,7 +117,7 @@
   move:[2] added b
   atop:[5] added a
   move:[4] merge commit
-  ancestor of '7235ef625ea3' split over multiple topological branches.
+  successors of ancestors of 7235ef625ea3 are on different topological branches
   choose an evolve destination:
   1: [62fb70414f99] added c
   2: [5841d7cf9893] added d
@@ -249,7 +249,7 @@
   move:[2] added b
   atop:[6] added a
   hg rebase -r a1da0651488c -d 5568b87b1491
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
   $ hg evolve --any --all --config ui.interactive=True <<EOF
   > 2
@@ -257,7 +257,7 @@
   move:[2] added b
   atop:[6] added a
   move:[4] merge commit
-  ancestor of 'cdf2ea1b9312' split over multiple topological branches.
+  successors of ancestors of cdf2ea1b9312 are on different topological branches
   choose an evolve destination:
   1: [62fb70414f99] added c
   2: [5841d7cf9893] added d
@@ -395,14 +395,14 @@
   move:[2] added b
   atop:[6] added a
   hg rebase -r a1da0651488c -d 5568b87b1491
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
   $ hg evolve --any --all --config ui.interactive=True <<EOF
   > 2
   > EOF
   move:[2] added b
   atop:[6] added a
-  ancestor of 'b9b387427a53' split over multiple topological branches.
+  successors of ancestors of b9b387427a53 are on different topological branches
   choose an evolve destination:
   1: [62fb70414f99] added c
   2: [5841d7cf9893] added d
--- a/tests/test-evolve-orphan-split.t	Wed Apr 29 00:44:14 2020 +0800
+++ b/tests/test-evolve-orphan-split.t	Sat Mar 21 20:36:41 2020 +0100
@@ -193,7 +193,7 @@
   $ hg evolve --dry-run <<EOF
   > 1
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
@@ -206,7 +206,7 @@
   $ hg evolve --dry-run <<EOF
   > 2
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
@@ -222,56 +222,56 @@
   $ hg evolve --all <<EOF
   > foo
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
   enter the index of the revision you want to select: foo
   invalid value 'foo' entered for index
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
   $ hg evolve --all <<EOF
   > 4
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
   enter the index of the revision you want to select: 4
   invalid value '4' entered for index
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
   $ hg evolve --all <<EOF
   > -1
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
   enter the index of the revision you want to select: -1
   invalid value '-1' entered for index
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
   $ hg evolve --all <<EOF
   > q
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
   enter the index of the revision you want to select: q
-  could not solve instability, ambiguous destination: parent split across two branches
+  could not solve instability, ambiguous destination: ancestors split across different branches
 
 Doing the evolve with the interactive prompt
 
   $ hg evolve --all <<EOF
   > 1
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
+  successors of ancestors of d48a30875f01 are on different topological branches
   choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-orphan-switched-ancestors.t	Sat Mar 21 20:36:41 2020 +0100
@@ -0,0 +1,37 @@
+  $ . $TESTDIR/testlib/common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > evolve=
+  > [alias]
+  > glog = log -GT "{rev}:{node|short} {desc}\n"
+  > EOF
+
+Test that, even if the order of the ancestors has been switched, the orphan is
+rebased such that it has the sucessors of both original ancestors are the new
+ancestors.
+
+  $ hg init repo
+  $ cd repo
+  $ mkcommit a
+  $ mkcommit b
+  $ echo a1 > a; echo b1 > b; hg ci -m 'mod ab'
+  $ hg up null -q
+  $ hg pick 1
+  picking 1:d2ae7f538514 "b"
+  1 new orphan changesets
+  $ hg pick 0
+  picking 0:cb9a9f314b8b "a"
+  $ hg next --dry-run
+  move:[2] mod ab
+  atop:[4] a
+  hg rebase -r cc6607605d1e -d f4a8092719b1
+  $ hg evolve
+  move:[2] mod ab
+  atop:[4] a
+  $ hg glog
+  o  5:866738c73814 mod ab
+  |
+  @  4:f4a8092719b1 a
+  |
+  o  3:6563da9dcf87 b
+