comparison hgext/rebase.py @ 33590:52f82e7d6a7e stable

rebase: move bookmark to destination for commits becoming empty (issue5627) When rebasing a changeset X and that changeset becomes empty, we should move the bookmark on X to rebase destination. This is a regression caused by the scmutil.cleanupnodes refactoring for rebase. The `adjustdest` function calculates the destination of bookmark movement. It was back-ported from https://phab.mercurial-scm.org/D21. It might be slightly more powerful than the minimal requirement to solve this issue. For example, it's impossible for a merge changeset to become empty while any of its ancestors does not become empty, but the code could handle that case. Since the code is reasonably short and clean, and helps the upcoming D21 series, I'd like to check-in `adjustdest` now. Thanks Martin von Zweigbergk for spotting corner cases (-k and descendant with bookmarks) in this area!
author Jun Wu <quark@fb.com>
date Mon, 24 Jul 2017 23:52:56 -0700
parents d341677d667d
children 609606d21765 5a5f600b06ad
comparison
equal deleted inserted replaced
33589:a0bfcd08f5fe 33590:52f82e7d6a7e
510 510
511 if not self.keepf: 511 if not self.keepf:
512 collapsedas = None 512 collapsedas = None
513 if self.collapsef: 513 if self.collapsef:
514 collapsedas = newnode 514 collapsedas = newnode
515 clearrebased(ui, repo, self.state, self.skipped, collapsedas) 515 clearrebased(ui, repo, self.dest, self.state, self.skipped,
516 collapsedas)
516 517
517 clearstatus(repo) 518 clearstatus(repo)
518 clearcollapsemsg(repo) 519 clearcollapsemsg(repo)
519 520
520 ui.note(_("rebase completed\n")) 521 ui.note(_("rebase completed\n"))
894 # duplicate any copies that have already been 895 # duplicate any copies that have already been
895 # performed in the destination. 896 # performed in the destination.
896 p1rev = repo[rev].p1().rev() 897 p1rev = repo[rev].p1().rev()
897 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest) 898 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
898 return stats 899 return stats
900
901 def adjustdest(repo, rev, dest, state):
902 """adjust rebase destination given the current rebase state
903
904 rev is what is being rebased. Return a list of two revs, which are the
905 adjusted destinations for rev's p1 and p2, respectively. If a parent is
906 nullrev, return dest without adjustment for it.
907
908 For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
909 and E's destination will be adjusted from F to B1.
910
911 B1 <- written during rebasing B
912 |
913 F <- original destination of B, E
914 |
915 | E <- rev, which is being rebased
916 | |
917 | D <- prev, one parent of rev being checked
918 | |
919 | x <- skipped, ex. no successor or successor in (::dest)
920 | |
921 | C
922 | |
923 | B <- rebased as B1
924 |/
925 A
926
927 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
928 first move C to C1, G to G1, and when it's checking H, the adjusted
929 destinations will be [C1, G1].
930
931 H C1 G1
932 /| | /
933 F G |/
934 K | | -> K
935 | C D |
936 | |/ |
937 | B | ...
938 |/ |/
939 A A
940 """
941 result = []
942 for prev in repo.changelog.parentrevs(rev):
943 adjusted = dest
944 if prev != nullrev:
945 # pick already rebased revs from state
946 source = [s for s, d in state.items() if d > 0]
947 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
948 if candidate is not None:
949 adjusted = state[candidate]
950 result.append(adjusted)
951 return result
899 952
900 def nearestrebased(repo, rev, state): 953 def nearestrebased(repo, rev, state):
901 """return the nearest ancestors of rev in the rebase result""" 954 """return the nearest ancestors of rev in the rebase result"""
902 rebased = [r for r in state if state[r] > nullmerge] 955 rebased = [r for r in state if state[r] > nullmerge]
903 candidates = repo.revs('max(%ld and (::%d))', rebased, rev) 956 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
1299 state[r] = revpruned 1352 state[r] = revpruned
1300 else: 1353 else:
1301 state[r] = revprecursor 1354 state[r] = revprecursor
1302 return originalwd, dest.rev(), state 1355 return originalwd, dest.rev(), state
1303 1356
1304 def clearrebased(ui, repo, state, skipped, collapsedas=None): 1357 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
1305 """dispose of rebased revision at the end of the rebase 1358 """dispose of rebased revision at the end of the rebase
1306 1359
1307 If `collapsedas` is not None, the rebase was a collapse whose result if the 1360 If `collapsedas` is not None, the rebase was a collapse whose result if the
1308 `collapsedas` node.""" 1361 `collapsedas` node."""
1309 tonode = repo.changelog.node 1362 tonode = repo.changelog.node
1363 # Move bookmark of skipped nodes to destination. This cannot be handled
1364 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1365 # and move bookmark backwards.
1366 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
1367 for rev in skipped
1368 for name in repo.nodebookmarks(tonode(rev))]
1369 if bmchanges:
1370 with repo.transaction('rebase') as tr:
1371 repo._bookmarks.applychanges(repo, tr, bmchanges)
1310 mapping = {} 1372 mapping = {}
1311 for rev, newrev in sorted(state.items()): 1373 for rev, newrev in sorted(state.items()):
1312 if newrev >= 0 and newrev != rev: 1374 if newrev >= 0 and newrev != rev:
1313 if rev in skipped: 1375 if rev in skipped:
1314 succs = () 1376 succs = ()