Mercurial > hg
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' |