D750: rebase: move bookmarks with --keep (issue5682)
martinvonz (Martin von Zweigbergk)
phabricator at mercurial-scm.org
Wed Sep 20 16:55:21 UTC 2017
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.
REVISION SUMMARY
This is my version of Jun's https://phab.mercurial-scm.org/D727. Not meant to be accepted, but
instead possibly incorporated into Jun's patch.
REPOSITORY
rHG Mercurial
REVISION DETAIL
https://phab.mercurial-scm.org/D750
AFFECTED FILES
hgext/rebase.py
mercurial/scmutil.py
tests/test-rebase-bookmarks.t
tests/test-rebase-emptycommit.t
CHANGE DETAILS
diff --git a/tests/test-rebase-emptycommit.t b/tests/test-rebase-emptycommit.t
--- a/tests/test-rebase-emptycommit.t
+++ b/tests/test-rebase-emptycommit.t
@@ -47,30 +47,35 @@
|/
o 0 A
-With --keep, bookmark should not move
+With --keep, bookmark should move
$ hg rebase -r 3+4 -d E --keep
rebasing 3:e7b3f00ed42e "D" (BOOK-D)
note: rebase of 3:e7b3f00ed42e created no changes to commit
rebasing 4:69a34c08022a "E" (BOOK-E)
note: rebase of 4:69a34c08022a created no changes to commit
$ hg log -G -T '{rev} {desc} {bookmarks}'
- o 7 E
+ o 7 E BOOK-D BOOK-E
|
o 6 D
|
| o 5 F BOOK-F
| |
- | o 4 E BOOK-E
+ | o 4 E
| |
- | o 3 D BOOK-D
+ | o 3 D
| |
| o 2 C BOOK-C
| |
o | 1 B
|/
o 0 A
+Move D and E back for the next test
+
+ $ hg bookmark BOOK-D -fqir 3
+ $ hg bookmark BOOK-E -fqir 4
+
Bookmark is usually an indication of a head. For changes that are introduced by
an ancestor of bookmark B, after moving B to B-NEW, the changes are ideally
still introduced by an ancestor of changeset on B-NEW. In the below case,
diff --git a/tests/test-rebase-bookmarks.t b/tests/test-rebase-bookmarks.t
--- a/tests/test-rebase-bookmarks.t
+++ b/tests/test-rebase-bookmarks.t
@@ -1,6 +1,7 @@
$ cat >> $HGRCPATH <<EOF
> [extensions]
> rebase=
+ > drawdag=$TESTDIR/drawdag.py
>
> [phases]
> publish=False
@@ -210,3 +211,35 @@
rebasing 6:f677a2907404 "bisect2"
rebasing 7:325c16001345 "bisect3" (tip bisect)
saved backup bundle to $TESTTMP/a3/.hg/strip-backup/345c90f326a4-b4840586-rebase.hg (glob)
+
+Bookmark and working parent get moved even if --keep is set (issue5682)
+
+ $ hg init $TESTTMP/book-keep
+ $ cd $TESTTMP/book-keep
+ $ hg debugdrawdag <<'EOS'
+ > B C
+ > |/
+ > A
+ > EOS
+ $ eval `hg tags -T 'hg bookmark -ir {node} {tag};\n' | grep -v tip`
+ $ rm .hg/localtags
+ $ hg up -q B
+ $ hg tglog
+ o 2: 'C' bookmarks: C
+ |
+ | @ 1: 'B' bookmarks: B
+ |/
+ o 0: 'A' bookmarks: A
+
+ $ hg rebase -r B -d C --keep
+ rebasing 1:112478962961 "B" (B)
+ $ hg tglog
+ @ 3: 'B' bookmarks: B
+ |
+ o 2: 'C' bookmarks: C
+ |
+ | o 1: 'B' bookmarks:
+ |/
+ o 0: 'A' bookmarks: A
+
+
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -579,22 +579,32 @@
def __contains__(self, node):
return self._revcontains(self._torev(node))
-def cleanupnodes(repo, replacements, operation):
+def cleanupnodes(repo, replacements, operation, moves=None):
"""do common cleanups when old nodes are replaced by new nodes
That includes writing obsmarkers or stripping nodes, and moving bookmarks.
(we might also want to move working directory parent in the future)
+
+ By default, bookmark moves are calculated automatically from 'replacements',
+ but 'moves' can be used to override that.
replacements is {oldnode: [newnode]} or a iterable of nodes if they do not have
replacements. operation is a string, like "rebase".
"""
+ if not replacements and not moves:
+ return
+
+ # translate mapping's other forms
if not util.safehasattr(replacements, 'items'):
replacements = {n: () for n in replacements}
# Calculate bookmark movements
- moves = {}
+ if moves is None:
+ moves = {}
unfi = repo.unfiltered()
for oldnode, newnodes in replacements.items():
+ if oldnode in moves:
+ continue
if len(newnodes) > 1:
# usually a split, take the one with biggest rev number
newnode = next(unfi.set('max(%ln)', newnodes)).node()
@@ -648,13 +658,16 @@
isobs = unfi.obsstore.successors.__contains__
torev = unfi.changelog.rev
sortfunc = lambda ns: torev(ns[0])
- rels = [(unfi[n], tuple(unfi[m] for m in s))
- for n, s in sorted(replacements.items(), key=sortfunc)
- if s or not isobs(n)]
- obsolete.createmarkers(repo, rels, operation=operation)
+ rels = [(unfi[n], tuple(unfi[m] for m in v))
+ for n, v in sorted(replacements.items(), key=sortfunc)
+ if v or not isobs(n)]
+ if rels:
+ obsolete.createmarkers(repo, rels, operation=operation)
else:
from . import repair # avoid import cycle
- repair.delayedstrip(repo.ui, repo, list(replacements), operation)
+ tostrip = [n for n, v in replacements.items()]
+ if tostrip:
+ repair.delayedstrip(repo.ui, repo, tostrip, operation)
def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
if opts is None:
diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -543,12 +543,12 @@
ui.note(_("update back to initial working directory parent\n"))
hg.updaterepo(repo, newwd, False)
+ collapsedas = None
if not self.keepf:
- collapsedas = None
if self.collapsef:
collapsedas = newnode
- clearrebased(ui, repo, self.destmap, self.state, self.skipped,
- collapsedas)
+ clearrebased(ui, repo, self.destmap, self.state, self.skipped,
+ collapsedas, self.keepf)
clearstatus(repo)
clearcollapsemsg(repo)
@@ -1512,32 +1512,31 @@
state[rev] = rev
return originalwd, destmap, state
-def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
+def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
+ keepf=False):
"""dispose of rebased revision at the end of the rebase
If `collapsedas` is not None, the rebase was a collapse whose result if the
- `collapsedas` node."""
+ `collapsedas` node.
+
+ If `keepf` is not True, the rebase has --keep set and no nodes should be
+ removed (but bookmarks still need to be moved).
+ """
tonode = repo.changelog.node
- # Move bookmark of skipped nodes to destination. This cannot be handled
- # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
- # and move bookmark backwards.
- bmchanges = [(name, tonode(state[rev]))
- for rev in skipped
- for name in repo.nodebookmarks(tonode(rev))]
- if bmchanges:
- with repo.transaction('rebase') as tr:
- repo._bookmarks.applychanges(repo, tr, bmchanges)
- mapping = {}
+ replacements = {}
+ moves = {}
for rev, newrev in sorted(state.items()):
if newrev >= 0 and newrev != rev:
- if rev in skipped:
- succs = ()
- elif collapsedas is not None:
- succs = (collapsedas,)
- else:
- succs = (tonode(newrev),)
- mapping[tonode(rev)] = succs
- scmutil.cleanupnodes(repo, mapping, 'rebase')
+ oldnode = tonode(rev)
+ newnode = collapsedas or tonode(newrev)
+ moves[oldnode] = newnode
+ if not keepf:
+ if rev in skipped:
+ succs = ()
+ else:
+ succs = (newnode,)
+ replacements[oldnode] = succs
+ scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
def pullrebase(orig, ui, repo, *args, **opts):
'Call rebase after pull if the latter has been invoked with --rebase'
To: martinvonz, #hg-reviewers
Cc: mercurial-devel
More information about the Mercurial-devel
mailing list