comparison hgext/rebase.py @ 33863:3160876c6e4e

rebase: choose merge base without unwanted revisions Previously, when there are 2 merge base candidates, we choose p1 blindly, which may make the merge result to have "unwanted content". This patch makes rebase smarter - choose a merge base that does not have "unwanted revs" if possible. Since we don't really have a good solution when there are "unwanted revs", abort in that case. Differential Revision: https://phab.mercurial-scm.org/D340
author Jun Wu <quark@fb.com>
date Thu, 10 Aug 2017 22:17:15 -0700
parents 3ae2eaecb49e
children 70354bd4f19b
comparison
equal deleted inserted replaced
33860:3cfc9070245f 33863:3160876c6e4e
1039 1039
1040 # If one parent becomes an ancestor of the other, drop the ancestor 1040 # If one parent becomes an ancestor of the other, drop the ancestor
1041 for j, x in enumerate(newps[:i]): 1041 for j, x in enumerate(newps[:i]):
1042 if x == nullrev: 1042 if x == nullrev:
1043 continue 1043 continue
1044 if isancestor(np, x): 1044 if isancestor(np, x): # CASE-1
1045 np = nullrev 1045 np = nullrev
1046 elif isancestor(x, np): 1046 elif isancestor(x, np): # CASE-2
1047 newps[j] = np 1047 newps[j] = np
1048 np = nullrev 1048 np = nullrev
1049 # New parents forming an ancestor relationship does not
1050 # mean the old parents have a similar relationship. Do not
1051 # set bases[x] to nullrev.
1049 bases[j], bases[i] = bases[i], bases[j] 1052 bases[j], bases[i] = bases[i], bases[j]
1050 1053
1051 newps[i] = np 1054 newps[i] = np
1052 1055
1053 # "rebasenode" updates to new p1, and the old p1 will be used as merge 1056 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1066 # A B D 1069 # A B D
1067 if set(newps) == set(oldps) and dest not in newps: 1070 if set(newps) == set(oldps) and dest not in newps:
1068 raise error.Abort(_('cannot rebase %d:%s without ' 1071 raise error.Abort(_('cannot rebase %d:%s without '
1069 'moving at least one of its parents') 1072 'moving at least one of its parents')
1070 % (rev, repo[rev])) 1073 % (rev, repo[rev]))
1071
1072 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1073 1074
1074 # "rebasenode" updates to new p1, use the corresponding merge base. 1075 # "rebasenode" updates to new p1, use the corresponding merge base.
1075 if bases[0] != nullrev: 1076 if bases[0] != nullrev:
1076 base = bases[0] 1077 base = bases[0]
1077 else: 1078 else:
1091 # 1092 #
1092 # But our merge base candidates (D and E in above case) could still be 1093 # But our merge base candidates (D and E in above case) could still be
1093 # better than the default (ancestor(F, Z) == null). Therefore still 1094 # better than the default (ancestor(F, Z) == null). Therefore still
1094 # pick one (so choose p1 above). 1095 # pick one (so choose p1 above).
1095 if sum(1 for b in bases if b != nullrev) > 1: 1096 if sum(1 for b in bases if b != nullrev) > 1:
1096 assert base is not None 1097 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1097 1098 for i, base in enumerate(bases):
1098 # Revisions in the side (not chosen as merge base) branch that might 1099 if base == nullrev:
1099 # contain "surprising" contents 1100 continue
1100 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))', 1101 # Revisions in the side (not chosen as merge base) branch that
1101 bases, base, base, dest)) 1102 # might contain "surprising" contents
1102 1103 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1103 # If those revisions are covered by rebaseset, the result is good. 1104 bases, base, base, dest))
1104 # A merge in rebaseset would be considered to cover its ancestors. 1105
1105 if siderevs: 1106 # If those revisions are covered by rebaseset, the result is good.
1106 rebaseset = [r for r, d in state.items() if d > 0] 1107 # A merge in rebaseset would be considered to cover its ancestors.
1107 merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev] 1108 if siderevs:
1108 unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld', 1109 rebaseset = [r for r, d in state.items() if d > 0]
1109 siderevs, merges, rebaseset)) 1110 merges = [r for r in rebaseset
1110 1111 if cl.parentrevs(r)[1] != nullrev]
1111 # For revs not covered, it is worth a warning. 1112 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1112 if unwantedrevs: 1113 siderevs, merges, rebaseset))
1113 repo.ui.warn( 1114
1114 _('warning: rebasing %d:%s may include unwanted changes ' 1115 # Choose a merge base that has a minimal number of unwanted revs.
1115 'from %s\n') 1116 l, i = min((len(revs), i)
1116 % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r]) 1117 for i, revs in enumerate(unwanted) if revs is not None)
1117 for r in unwantedrevs))) 1118 base = bases[i]
1119
1120 # newps[0] should match merge base if possible. Currently, if newps[i]
1121 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1122 # the other's ancestor. In that case, it's fine to not swap newps here.
1123 # (see CASE-1 and CASE-2 above)
1124 if i != 0 and newps[i] != nullrev:
1125 newps[0], newps[i] = newps[i], newps[0]
1126
1127 # The merge will include unwanted revisions. Abort now. Revisit this if
1128 # we have a more advanced merge algorithm that handles multiple bases.
1129 if l > 0:
1130 unwanteddesc = _(' or ').join(
1131 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1132 for revs in unwanted if revs is not None))
1133 raise error.Abort(
1134 _('rebasing %d:%s will include unwanted changes from %s')
1135 % (rev, repo[rev], unwanteddesc))
1136
1137 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1118 1138
1119 return newps[0], newps[1], base 1139 return newps[0], newps[1], base
1120 1140
1121 def isagitpatch(repo, patchname): 1141 def isagitpatch(repo, patchname):
1122 'Return true if the given patch is in git format' 1142 'Return true if the given patch is in git format'