##// END OF EJS Templates
rebase: rewrite core algorithm (issue5578) (issue5630)...
Jun Wu -
r33783:09755061 default
parent child Browse files
Show More
@@ -66,7 +66,6 b' revignored = -3'
66 66 revprecursor = -4
67 67 # plain prune (no successor)
68 68 revpruned = -5
69 revskipped = (revignored, revprecursor, revpruned)
70 69
71 70 cmdtable = {}
72 71 command = registrar.command(cmdtable)
@@ -390,10 +389,7 b' class rebaseruntime(object):'
390 389 ui.status(_('rebasing %s\n') % desc)
391 390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
392 391 _('changesets'), total)
393 p1, p2, base = defineparents(repo, rev, self.dest,
394 self.state,
395 self.destancestors,
396 self.obsoletenotrebased)
392 p1, p2, base = defineparents(repo, rev, self.dest, self.state)
397 393 self.storestatus(tr=tr)
398 394 storecollapsemsg(repo, self.collapsemsg)
399 395 if len(repo[None].parents()) == 2:
@@ -463,9 +459,7 b' class rebaseruntime(object):'
463 459 repo, ui, opts = self.repo, self.ui, self.opts
464 460 if self.collapsef and not self.keepopen:
465 461 p1, p2, _base = defineparents(repo, min(self.state),
466 self.dest, self.state,
467 self.destancestors,
468 self.obsoletenotrebased)
462 self.dest, self.state)
469 463 editopt = opts.get('edit')
470 464 editform = 'rebase.collapse'
471 465 if self.collapsemsg:
@@ -960,15 +954,6 b' def adjustdest(repo, rev, dest, state):'
960 954 result.append(adjusted)
961 955 return result
962 956
963 def nearestrebased(repo, rev, state):
964 """return the nearest ancestors of rev in the rebase result"""
965 rebased = [r for r in state if state[r] > nullmerge]
966 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
967 if candidates:
968 return state[candidates.first()]
969 else:
970 return None
971
972 957 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
973 958 """
974 959 Abort if rebase will create divergence or rebase is noop because of markers
@@ -992,107 +977,173 b' def _checkobsrebase(repo, ui, rebaseobsr'
992 977 "experimental.allowdivergence=True")
993 978 raise error.Abort(msg % (",".join(divhashes),), hint=h)
994 979
995 def defineparents(repo, rev, dest, state, destancestors,
996 obsoletenotrebased):
997 'Return the new parent relationship of the revision that will be rebased'
998 parents = repo[rev].parents()
999 p1 = p2 = nullrev
1000 rp1 = None
980 def successorrevs(repo, rev):
981 """yield revision numbers for successors of rev"""
982 unfi = repo.unfiltered()
983 nodemap = unfi.changelog.nodemap
984 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
985 if s in nodemap:
986 yield nodemap[s]
987
988 def defineparents(repo, rev, dest, state):
989 """Return new parents and optionally a merge base for rev being rebased
990
991 The destination specified by "dest" cannot always be used directly because
992 previously rebase result could affect destination. For example,
1001 993
1002 p1n = parents[0].rev()
1003 if p1n in destancestors:
1004 p1 = dest
1005 elif p1n in state:
1006 if state[p1n] == nullmerge:
1007 p1 = dest
1008 elif state[p1n] in revskipped:
1009 p1 = nearestrebased(repo, p1n, state)
1010 if p1 is None:
1011 p1 = dest
1012 else:
1013 p1 = state[p1n]
1014 else: # p1n external
1015 p1 = dest
1016 p2 = p1n
994 D E rebase -r C+D+E -d B
995 |/ C will be rebased to C'
996 B C D's new destination will be C' instead of B
997 |/ E's new destination will be C' instead of B
998 A
999
1000 The new parents of a merge is slightly more complicated. See the comment
1001 block below.
1002 """
1003 cl = repo.changelog
1004 def isancestor(a, b):
1005 # take revision numbers instead of nodes
1006 if a == b:
1007 return True
1008 elif a > b:
1009 return False
1010 return cl.isancestor(cl.node(a), cl.node(b))
1011
1012 oldps = repo.changelog.parentrevs(rev) # old parents
1013 newps = [nullrev, nullrev] # new parents
1014 dests = adjustdest(repo, rev, dest, state) # adjusted destinations
1015 bases = list(oldps) # merge base candidates, initially just old parents
1017 1016
1018 if len(parents) == 2 and parents[1].rev() not in destancestors:
1019 p2n = parents[1].rev()
1020 # interesting second parent
1021 if p2n in state:
1022 if p1 == dest: # p1n in destancestors or external
1023 p1 = state[p2n]
1024 if p1 == revprecursor:
1025 rp1 = obsoletenotrebased[p2n]
1026 elif state[p2n] in revskipped:
1027 p2 = nearestrebased(repo, p2n, state)
1028 if p2 is None:
1029 # no ancestors rebased yet, detach
1030 p2 = dest
1031 else:
1032 p2 = state[p2n]
1033 else: # p2n external
1034 if p2 != nullrev: # p1n external too => rev is a merged revision
1035 raise error.Abort(_('cannot use revision %d as base, result '
1036 'would have 3 parents') % rev)
1037 p2 = p2n
1038 repo.ui.debug(" future parents are %d and %d\n" %
1039 (repo[rp1 or p1].rev(), repo[p2].rev()))
1017 if all(r == nullrev for r in oldps[1:]):
1018 # For non-merge changeset, just move p to adjusted dest as requested.
1019 newps[0] = dests[0]
1020 else:
1021 # For merge changeset, if we move p to dests[i] unconditionally, both
1022 # parents may change and the end result looks like "the merge loses a
1023 # parent", which is a surprise. This is a limit because "--dest" only
1024 # accepts one dest per src.
1025 #
1026 # Therefore, only move p with reasonable conditions (in this order):
1027 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1028 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1029 #
1030 # Comparing with adjustdest, the logic here does some additional work:
1031 # 1. decide which parents will not be moved towards dest
1032 # 2. if the above decision is "no", should a parent still be moved
1033 # because it was rebased?
1034 #
1035 # For example:
1036 #
1037 # C # "rebase -r C -d D" is an error since none of the parents
1038 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1039 # A B D # B (using rule "2."), since B will be rebased.
1040 #
1041 # The loop tries to be not rely on the fact that a Mercurial node has
1042 # at most 2 parents.
1043 for i, p in enumerate(oldps):
1044 np = p # new parent
1045 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1046 np = dests[i]
1047 elif p in state and state[p] > 0:
1048 np = state[p]
1040 1049
1041 if not any(p.rev() in state for p in parents):
1042 # Case (1) root changeset of a non-detaching rebase set.
1043 # Let the merge mechanism find the base itself.
1050 # "bases" only record "special" merge bases that cannot be
1051 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1052 # For example:
1053 #
1054 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1055 # | C # is B', but merge base for C is B, instead of
1056 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1057 # | B # "state" edges are merged (so there will be an edge from
1058 # |/ # B to B'), the merge base is still ancestor(C, B') in
1059 # A # the merged graph.
1060 #
1061 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1062 # which uses "virtual null merge" to explain this situation.
1063 if isancestor(p, np):
1064 bases[i] = nullrev
1065
1066 # If one parent becomes an ancestor of the other, drop the ancestor
1067 for j, x in enumerate(newps[:i]):
1068 if x == nullrev:
1069 continue
1070 if isancestor(np, x):
1071 np = nullrev
1072 elif isancestor(x, np):
1073 newps[j] = np
1074 np = nullrev
1075 bases[j], bases[i] = bases[i], bases[j]
1076
1077 newps[i] = np
1078
1079 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1080 # base. If only p2 changes, merging using unchanged p1 as merge base is
1081 # suboptimal. Therefore swap parents to make the merge sane.
1082 if newps[1] != nullrev and oldps[0] == newps[0]:
1083 assert len(newps) == 2 and len(oldps) == 2
1084 newps.reverse()
1085 bases.reverse()
1086
1087 # No parent change might be an error because we fail to make rev a
1088 # descendent of requested dest. This can happen, for example:
1089 #
1090 # C # rebase -r C -d D
1091 # /| # None of A and B will be changed to D and rebase fails.
1092 # A B D
1093 if set(newps) == set(oldps) and dest not in newps:
1094 # The error message is for compatibility. It's a bit misleading
1095 # since rebase is not supposed to add new parents.
1096 raise error.Abort(_('cannot use revision %d as base, '
1097 'result would have 3 parents') % rev)
1098
1099 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1100
1101 # "rebasenode" updates to new p1, use the corresponding merge base.
1102 if bases[0] != nullrev:
1103 base = bases[0]
1104 else:
1044 1105 base = None
1045 elif not repo[rev].p2():
1046 # Case (2) detaching the node with a single parent, use this parent
1047 base = repo[rev].p1().rev()
1048 else:
1049 # Assuming there is a p1, this is the case where there also is a p2.
1050 # We are thus rebasing a merge and need to pick the right merge base.
1051 #
1052 # Imagine we have:
1053 # - M: current rebase revision in this step
1054 # - A: one parent of M
1055 # - B: other parent of M
1056 # - D: destination of this merge step (p1 var)
1057 #
1058 # Consider the case where D is a descendant of A or B and the other is
1059 # 'outside'. In this case, the right merge base is the D ancestor.
1060 #
1061 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1062 #
1063 # If we pick B as the base, the merge involves:
1064 # - changes from B to M (actual changeset payload)
1065 # - changes from B to D (induced by rebase) as D is a rebased
1066 # version of B)
1067 # Which exactly represent the rebase operation.
1068 #
1069 # If we pick A as the base, the merge involves:
1070 # - changes from A to M (actual changeset payload)
1071 # - changes from A to D (with include changes between unrelated A and B
1072 # plus changes induced by rebase)
1073 # Which does not represent anything sensible and creates a lot of
1074 # conflicts. A is thus not the right choice - B is.
1075 #
1076 # Note: The base found in this 'proof' is only correct in the specified
1077 # case. This base does not make sense if is not D a descendant of A or B
1078 # or if the other is not parent 'outside' (especially not if the other
1079 # parent has been rebased). The current implementation does not
1080 # make it feasible to consider different cases separately. In these
1081 # other cases we currently just leave it to the user to correctly
1082 # resolve an impossible merge using a wrong ancestor.
1083 #
1084 # xx, p1 could be -4, and both parents could probably be -4...
1085 for p in repo[rev].parents():
1086 if state.get(p.rev()) == p1:
1087 base = p.rev()
1088 break
1089 else: # fallback when base not found
1090 base = None
1106
1107 # Check if the merge will contain unwanted changes. That may happen if
1108 # there are multiple special (non-changelog ancestor) merge bases, which
1109 # cannot be handled well by the 3-way merge algorithm. For example:
1110 #
1111 # F
1112 # /|
1113 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1114 # | | # as merge base, the difference between D and F will include
1115 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1116 # |/ # chosen, the rebased F will contain B.
1117 # A Z
1118 #
1119 # But our merge base candidates (D and E in above case) could still be
1120 # better than the default (ancestor(F, Z) == null). Therefore still
1121 # pick one (so choose p1 above).
1122 if sum(1 for b in bases if b != nullrev) > 1:
1123 assert base is not None
1091 1124
1092 # Raise because this function is called wrong (see issue 4106)
1093 raise AssertionError('no base found to rebase on '
1094 '(defineparents called wrong)')
1095 return rp1 or p1, p2, base
1125 # Revisions in the side (not chosen as merge base) branch that might
1126 # contain "surprising" contents
1127 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1128 bases, base, base, dest))
1129
1130 # If those revisions are covered by rebaseset, the result is good.
1131 # A merge in rebaseset would be considered to cover its ancestors.
1132 if siderevs:
1133 rebaseset = [r for r, d in state.items() if d > 0]
1134 merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev]
1135 unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld',
1136 siderevs, merges, rebaseset))
1137
1138 # For revs not covered, it is worth a warning.
1139 if unwantedrevs:
1140 repo.ui.warn(
1141 _('warning: rebasing %d:%s may include unwanted changes '
1142 'from %s\n')
1143 % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r])
1144 for r in unwantedrevs)))
1145
1146 return newps[0], newps[1], base
1096 1147
1097 1148 def isagitpatch(repo, patchname):
1098 1149 'Return true if the given patch is in git format'
@@ -31,8 +31,8 b' Source looks like "N"'
31 31 AD: A':Z D':Z
32 32 BD: B':Z D':B'
33 33 ABD: A':Z B':Z D':B'
34 CD: CRASH: revlog index out of range
35 ACD: A':Z C':A'A' D':Z
34 CD: ABORT: cannot use revision 3 as base, result would have 3 parents
35 ACD: A':Z C':A'B D':Z
36 36 BCD: B':Z C':B'A D':B'
37 37 ABCD: A':Z B':Z C':A'B' D':B'
38 38
@@ -52,4 +52,4 b' Moving backwards'
52 52 C: ABORT: cannot use revision 3 as base, result would have 3 parents
53 53 BC: B':Z C':B'A
54 54 AC:
55 BAC: ABORT: nothing to merge
55 BAC: B':Z C':B'A
@@ -3,7 +3,7 b''
3 3 > usegeneraldelta=yes
4 4 > [extensions]
5 5 > rebase=
6 >
6 > drawdag=$TESTDIR/drawdag.py
7 7 > [alias]
8 8 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
9 9 > EOF
@@ -334,3 +334,101 b' rebase of merge of ancestors'
334 334 |/
335 335 o 0: 'common'
336 336
337 Due to the limitation of 3-way merge algorithm (1 merge base), rebasing a merge
338 may include unwanted content:
339
340 $ hg init $TESTTMP/dual-merge-base1
341 $ cd $TESTTMP/dual-merge-base1
342 $ hg debugdrawdag <<'EOS'
343 > F
344 > /|
345 > D E
346 > | |
347 > B C
348 > |/
349 > A Z
350 > |/
351 > R
352 > EOS
353 $ hg rebase -r D+E+F -d Z
354 rebasing 5:5f2c926dfecf "D" (D)
355 rebasing 6:b296604d9846 "E" (E)
356 rebasing 7:caa9781e507d "F" (F tip)
357 warning: rebasing 7:caa9781e507d may include unwanted changes from 4:d6003a550c2c
358 saved backup bundle to $TESTTMP/dual-merge-base1/.hg/strip-backup/b296604d9846-0516f6d2-rebase.hg (glob)
359 $ hg log -r 4 -T '{files}\n'
360 C
361 $ hg manifest -r 'desc(F)'
362 C
363 D
364 E
365 R
366 Z
367
368 The warning does not get printed if there is no unwanted change detected:
369
370 $ hg init $TESTTMP/dual-merge-base2
371 $ cd $TESTTMP/dual-merge-base2
372 $ hg debugdrawdag <<'EOS'
373 > D
374 > /|
375 > B C
376 > |/
377 > A Z
378 > |/
379 > R
380 > EOS
381 $ hg rebase -r B+C+D -d Z
382 rebasing 3:c1e6b162678d "B" (B)
383 rebasing 4:d6003a550c2c "C" (C)
384 rebasing 5:c8f78076273e "D" (D tip)
385 saved backup bundle to $TESTTMP/dual-merge-base2/.hg/strip-backup/d6003a550c2c-6f1424b6-rebase.hg (glob)
386 $ hg manifest -r 'desc(D)'
387 B
388 C
389 R
390 Z
391
392 The merge base could be different from old p1 (changed parent becomes new p1):
393
394 $ hg init $TESTTMP/chosen-merge-base1
395 $ cd $TESTTMP/chosen-merge-base1
396 $ hg debugdrawdag <<'EOS'
397 > F
398 > /|
399 > D E
400 > | |
401 > B C Z
402 > EOS
403 $ hg rebase -r D+F -d Z
404 rebasing 3:004dc1679908 "D" (D)
405 rebasing 5:4be4cbf6f206 "F" (F tip)
406 saved backup bundle to $TESTTMP/chosen-merge-base1/.hg/strip-backup/004dc1679908-06a66a3c-rebase.hg (glob)
407 $ hg manifest -r 'desc(F)'
408 C
409 D
410 E
411 Z
412 $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
413 D
414
415 $ hg init $TESTTMP/chosen-merge-base2
416 $ cd $TESTTMP/chosen-merge-base2
417 $ hg debugdrawdag <<'EOS'
418 > F
419 > /|
420 > D E
421 > | |
422 > B C Z
423 > EOS
424 $ hg rebase -r E+F -d Z
425 rebasing 4:974e4943c210 "E" (E)
426 rebasing 5:4be4cbf6f206 "F" (F tip)
427 saved backup bundle to $TESTTMP/chosen-merge-base2/.hg/strip-backup/974e4943c210-b2874da5-rebase.hg (glob)
428 $ hg manifest -r 'desc(F)'
429 B
430 D
431 E
432 Z
433 $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
434 E
@@ -488,9 +488,14 b' Detach both parents'
488 488 > A
489 489 > EOF
490 490
491 BROKEN: This raises an exception
492 $ hg rebase -d G -r 'B + D + F' 2>&1 | grep '^AssertionError'
493 AssertionError: no base found to rebase on (defineparents called wrong)
491 $ hg rebase -d G -r 'B + D + F'
492 rebasing 1:112478962961 "B" (B)
493 rebasing 2:b18e25de2cf5 "D" (D)
494 not rebasing ignored 4:26805aba1e60 "C" (C)
495 not rebasing ignored 5:4b61ff5c62e2 "E" (E)
496 rebasing 6:f15c3adaf214 "F" (F tip)
497 abort: cannot use revision 6 as base, result would have 3 parents
498 [255]
494 499
495 500 $ cd ..
496 501
@@ -962,17 +967,19 b' Rebase merge where successor of other pa'
962 967 > A
963 968 > EOF
964 969
965 BROKEN: Raises an exception
966 $ hg rebase -d B -s E 2>&1 | grep AssertionError:
967 AssertionError: no base found to rebase on (defineparents called wrong)
970 $ hg rebase -d B -s E
971 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
972 rebasing 4:66f1a38021c9 "F" (F tip)
968 973 $ hg log -G
969 o 4:66f1a38021c9 F
974 o 5:aae1787dacee F
970 975 |\
971 | x 3:7fb047a69f22 E
972 | |
973 o | 2:b18e25de2cf5 D
974 |/
975 | o 1:112478962961 B
976 | | x 4:66f1a38021c9 F
977 | |/|
978 | | x 3:7fb047a69f22 E
979 | | |
980 | o | 2:b18e25de2cf5 D
981 | |/
982 o / 1:112478962961 B
976 983 |/
977 984 o 0:426bada5c675 A
978 985
@@ -994,19 +1001,19 b' Rebase merge where successor of one pare'
994 1001 $ hg rebase -d C -s D
995 1002 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
996 1003 rebasing 5:66f1a38021c9 "F" (F tip)
997 BROKEN: not rebased on top of requested destination (C)
1004
998 1005 $ hg log -G
999 o 6:50e9d60b99c6 F
1006 o 6:0913febf6439 F
1000 1007 |\
1001 | | x 5:66f1a38021c9 F
1002 | |/|
1003 +-----o 4:26805aba1e60 C
1008 +---x 5:66f1a38021c9 F
1009 | | |
1010 | o | 4:26805aba1e60 C
1004 1011 | | |
1005 | o | 3:7fb047a69f22 E
1012 o | | 3:7fb047a69f22 E
1006 1013 | | |
1007 | | x 2:b18e25de2cf5 D
1008 | |/
1009 o | 1:112478962961 B
1014 +---x 2:b18e25de2cf5 D
1015 | |
1016 | o 1:112478962961 B
1010 1017 |/
1011 1018 o 0:426bada5c675 A
1012 1019
@@ -1025,19 +1032,21 b' Rebase merge where successor of other pa'
1025 1032 > A
1026 1033 > EOF
1027 1034
1028 BROKEN: Raises an exception
1029 $ hg rebase -d C -s E 2>&1 | grep AssertionError:
1030 AssertionError: no base found to rebase on (defineparents called wrong)
1035 $ hg rebase -d C -s E
1036 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
1037 rebasing 5:66f1a38021c9 "F" (F tip)
1031 1038 $ hg log -G
1032 o 5:66f1a38021c9 F
1039 o 6:c6ab0cc6d220 F
1033 1040 |\
1034 | | o 4:26805aba1e60 C
1041 +---x 5:66f1a38021c9 F
1035 1042 | | |
1036 | x | 3:7fb047a69f22 E
1043 | o | 4:26805aba1e60 C
1037 1044 | | |
1038 o | | 2:b18e25de2cf5 D
1039 |/ /
1040 | o 1:112478962961 B
1045 | | x 3:7fb047a69f22 E
1046 | | |
1047 o---+ 2:b18e25de2cf5 D
1048 / /
1049 o / 1:112478962961 B
1041 1050 |/
1042 1051 o 0:426bada5c675 A
1043 1052
@@ -1060,6 +1069,7 b' Rebase merge where successor of one pare'
1060 1069 rebasing 2:b18e25de2cf5 "D" (D)
1061 1070 note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
1062 1071 rebasing 5:66f1a38021c9 "F" (F tip)
1072 warning: rebasing 5:66f1a38021c9 may include unwanted changes from 3:7fb047a69f22
1063 1073 $ hg log -G
1064 1074 o 7:9ed45af61fa0 F
1065 1075 |
@@ -1096,13 +1106,13 b' Rebase merge where successor of other pa'
1096 1106 note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
1097 1107 rebasing 3:7fb047a69f22 "E" (E)
1098 1108 rebasing 5:66f1a38021c9 "F" (F tip)
1099 BROKEN: This should have resulted in a rebased F with one parent, just like in
1100 the test case above
1109 warning: rebasing 5:66f1a38021c9 may include unwanted changes from 2:b18e25de2cf5
1110
1101 1111 $ hg log -G
1102 o 7:c1e6f26e339d F
1103 |\
1104 | o 6:533690786a86 E
1105 |/
1112 o 7:502540f44880 F
1113 |
1114 o 6:533690786a86 E
1115 |
1106 1116 | x 5:66f1a38021c9 F
1107 1117 | |\
1108 1118 o | | 4:26805aba1e60 C
General Comments 0
You need to be logged in to leave comments. Login now