hgext/rebase.py
changeset 33863 3160876c6e4e
parent 33849 3ae2eaecb49e
child 33864 70354bd4f19b
--- a/hgext/rebase.py	Wed Aug 16 10:44:06 2017 -0700
+++ b/hgext/rebase.py	Thu Aug 10 22:17:15 2017 -0700
@@ -1041,11 +1041,14 @@
             for j, x in enumerate(newps[:i]):
                 if x == nullrev:
                     continue
-                if isancestor(np, x):
+                if isancestor(np, x): # CASE-1
                     np = nullrev
-                elif isancestor(x, np):
+                elif isancestor(x, np): # CASE-2
                     newps[j] = np
                     np = nullrev
+                    # New parents forming an ancestor relationship does not
+                    # mean the old parents have a similar relationship. Do not
+                    # set bases[x] to nullrev.
                     bases[j], bases[i] = bases[i], bases[j]
 
             newps[i] = np
@@ -1069,8 +1072,6 @@
                                 'moving at least one of its parents')
                               % (rev, repo[rev]))
 
-    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
-
     # "rebasenode" updates to new p1, use the corresponding merge base.
     if bases[0] != nullrev:
         base = bases[0]
@@ -1093,28 +1094,47 @@
     # better than the default (ancestor(F, Z) == null). Therefore still
     # pick one (so choose p1 above).
     if sum(1 for b in bases if b != nullrev) > 1:
-        assert base is not None
+        unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
+        for i, base in enumerate(bases):
+            if base == nullrev:
+                continue
+            # Revisions in the side (not chosen as merge base) branch that
+            # might contain "surprising" contents
+            siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
+                                      bases, base, base, dest))
 
-        # Revisions in the side (not chosen as merge base) branch that might
-        # contain "surprising" contents
-        siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
-                                  bases, base, base, dest))
+            # If those revisions are covered by rebaseset, the result is good.
+            # A merge in rebaseset would be considered to cover its ancestors.
+            if siderevs:
+                rebaseset = [r for r, d in state.items() if d > 0]
+                merges = [r for r in rebaseset
+                          if cl.parentrevs(r)[1] != nullrev]
+                unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
+                                             siderevs, merges, rebaseset))
 
-        # If those revisions are covered by rebaseset, the result is good.
-        # A merge in rebaseset would be considered to cover its ancestors.
-        if siderevs:
-            rebaseset = [r for r, d in state.items() if d > 0]
-            merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev]
-            unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld',
-                                          siderevs, merges, rebaseset))
+        # Choose a merge base that has a minimal number of unwanted revs.
+        l, i = min((len(revs), i)
+                   for i, revs in enumerate(unwanted) if revs is not None)
+        base = bases[i]
+
+        # newps[0] should match merge base if possible. Currently, if newps[i]
+        # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
+        # the other's ancestor. In that case, it's fine to not swap newps here.
+        # (see CASE-1 and CASE-2 above)
+        if i != 0 and newps[i] != nullrev:
+            newps[0], newps[i] = newps[i], newps[0]
 
-            # For revs not covered, it is worth a warning.
-            if unwantedrevs:
-                repo.ui.warn(
-                    _('warning: rebasing %d:%s may include unwanted changes '
-                      'from %s\n')
-                    % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r])
-                                                 for r in unwantedrevs)))
+        # The merge will include unwanted revisions. Abort now. Revisit this if
+        # we have a more advanced merge algorithm that handles multiple bases.
+        if l > 0:
+            unwanteddesc = _(' or ').join(
+                (', '.join('%d:%s' % (r, repo[r]) for r in revs)
+                 for revs in unwanted if revs is not None))
+            raise error.Abort(
+                _('rebasing %d:%s will include unwanted changes from %s')
+                % (rev, repo[rev], unwanteddesc))
+
+    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
 
     return newps[0], newps[1], base