destutil: allow to specify an explicit source for the merge
We can now specify from where the merge is performed. The experimental revset
is updated to take revisions as argument, allowing to test the feature.
This will become very useful for pick the 'rebase' default destination. For this
reason, we also exclude all descendants from the rebased set from the candidate
destinations. This descendants exclusion was not necessary for merge as default
destination would not be picked from anything else than a head.
I'm not super excited with the current error messages, but I would prefer to
delay an overall messages rework once 'hg rebase' is done getting a default
destination aligned with 'hg merge'.
--- a/mercurial/destutil.py Mon Feb 08 18:12:06 2016 +0100
+++ b/mercurial/destutil.py Mon Feb 08 19:32:29 2016 +0100
@@ -185,9 +185,19 @@
(_('working directory not at a head revision'),
_("use 'hg update' or merge with an explicit revision"))
},
+ 'emptysourceset':
+ {'merge':
+ (_('source set is empty'),
+ None)
+ },
+ 'multiplebranchessourceset':
+ {'merge':
+ (_('source set is rooted in multiple branches'),
+ None)
+ },
}
-def _destmergebook(repo, action='merge'):
+def _destmergebook(repo, action='merge', sourceset=None):
"""find merge destination in the active bookmark case"""
node = None
bmheads = repo.bookmarkheads(repo._activebookmark)
@@ -206,15 +216,27 @@
assert node is not None
return node
-def _destmergebranch(repo, action='merge'):
+def _destmergebranch(repo, action='merge', sourceset=None):
"""find merge destination based on branch heads"""
node = None
- parent = repo.dirstate.p1()
- branch = repo.dirstate.branch()
+
+ if sourceset is None:
+ sourceset = [repo[repo.dirstate.p1()].rev()]
+ branch = repo.dirstate.branch()
+ elif not sourceset:
+ msg, hint = msgdestmerge['emptysourceset'][action]
+ raise error.Abort(msg, hint=hint)
+ else:
+ branch = None
+ for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset):
+ if branch is not None and ctx.branch() != branch:
+ msg, hint = msgdestmerge['multiplebranchessourceset'][action]
+ raise error.Abort(msg, hint=hint)
+ branch = ctx.branch()
+
bheads = repo.branchheads(branch)
-
- if parent not in bheads:
- # Case A: working copy if not on a head.
+ if not repo.revs('%ld and %ln', sourceset, bheads):
+ # Case A: working copy if not on a head. (merge only)
#
# This is probably a user mistake We bailout pointing at 'hg update'
if len(repo.heads()) <= 1:
@@ -222,10 +244,10 @@
else:
msg, hint = msgdestmerge['notatheads'][action]
raise error.Abort(msg, hint=hint)
- # remove current head from the set
- bheads = [bh for bh in bheads if bh != parent]
+ # remove heads descendants of source from the set
+ bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset))
# filters out bookmarked heads
- nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
+ nbhs = list(repo.revs('%ld - bookmark()', bheads))
if len(nbhs) > 1:
# Case B: There is more than 1 other anonymous heads
#
@@ -253,7 +275,7 @@
assert node is not None
return node
-def destmerge(repo, action='merge'):
+def destmerge(repo, action='merge', sourceset=None):
"""return the default destination for a merge
(or raise exception about why it can't pick one)
@@ -261,9 +283,9 @@
:action: the action being performed, controls emitted error message
"""
if repo._activebookmark:
- node = _destmergebook(repo, action=action)
+ node = _destmergebook(repo, action=action, sourceset=sourceset)
else:
- node = _destmergebranch(repo, action=action)
+ node = _destmergebranch(repo, action=action, sourceset=sourceset)
return repo[node].rev()
histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
--- a/mercurial/revset.py Mon Feb 08 18:12:06 2016 +0100
+++ b/mercurial/revset.py Mon Feb 08 19:32:29 2016 +0100
@@ -541,8 +541,10 @@
@predicate('_destmerge')
def _destmerge(repo, subset, x):
# experimental revset for merge destination
- getargs(x, 0, 0, _("_mergedefaultdest takes no arguments"))
- return subset & baseset([destutil.destmerge(repo)])
+ sourceset = None
+ if x is not None:
+ sourceset = getset(repo, fullreposet(repo), x)
+ return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
@predicate('adds(pattern)', safe=True)
def adds(repo, subset, x):
--- a/tests/test-merge-default.t Mon Feb 08 18:12:06 2016 +0100
+++ b/tests/test-merge-default.t Mon Feb 08 19:32:29 2016 +0100
@@ -116,3 +116,36 @@
(run 'hg heads' to see all heads)
[255]
+(on a branch with a two heads)
+
+ $ hg up 5
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo f >> a
+ $ hg commit -mf
+ created new head
+ $ hg log -r '_destmerge()'
+ changeset: 6:e88e33f3bf62
+ parent: 5:a431fabd6039
+ parent: 3:ea9ff125ff88
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: m2
+
+
+(from the other head)
+
+ $ hg log -r '_destmerge(e88e33f3bf62)'
+ changeset: 8:b613918999e2
+ tag: tip
+ parent: 5:a431fabd6039
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: f
+
+
+(from unrelated branch)
+
+ $ hg log -r '_destmerge(foobranch)'
+ abort: branch 'foobranch' has one head - please merge with an explicit rev
+ (run 'hg heads' to see all heads)
+ [255]