Mercurial > hg
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 = () |