Patchwork [5,of,7] destutil: allow to specify an explicit source for the merge

login
register
mail settings
Submitter Pierre-Yves David
Date Feb. 14, 2016, 2:16 a.m.
Message ID <6386ab5b42f3742c47f2.1455416187@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/13177/
State Superseded
Headers show

Comments

Pierre-Yves David - Feb. 14, 2016, 2:16 a.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1454956349 -3600
#      Mon Feb 08 19:32:29 2016 +0100
# Node ID 6386ab5b42f3742c47f265ea66149a7229bb50d0
# Parent  4cccc4e95970f32885a3e896c668f3e00b11bd45
# EXP-Topic destination
# Available At http://hg.netv6.net/marmoute-wip/mercurial/
#              hg pull http://hg.netv6.net/marmoute-wip/mercurial/ -r 6386ab5b42f3
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 this feature.

This will become very useful for pick the 'rebase' default destination.

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'.

Patch

diff --git a/mercurial/destutil.py b/mercurial/destutil.py
--- a/mercurial/destutil.py
+++ b/mercurial/destutil.py
@@ -183,13 +183,23 @@  msgdestmerge = {
     'notatheads':
         {'merge':
             (_('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)
     curhead = repo[repo._activebookmark].node()
     if len(bmheads) == 2:
@@ -204,30 +214,42 @@  def _destmergebook(repo, action='merge')
         msg, hint = msgdestmerge['nootherbookmarks'][action]
         raise error.Abort(msg, hint=hint)
     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:
             msg, hint = msgdestmerge['nootherheadsbehind'][action]
         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]
+    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
         #
         # This means that there will be more than 1 candidate. This is
         # ambiguous. We abort asking the user to pick as explicit destination
@@ -251,21 +273,21 @@  def _destmergebranch(repo, action='merge
     else:
         node = nbhs[0]
     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)
 
     :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())'
 
 def desthistedit(ui, repo):
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -539,12 +539,14 @@  def _destupdate(repo, subset, x):
     return subset & baseset([destutil.destupdate(repo, **args)[0]])
 
 @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):
     """Changesets that add a file matching pattern.
 
diff --git a/tests/test-merge-default.t b/tests/test-merge-default.t
--- a/tests/test-merge-default.t
+++ b/tests/test-merge-default.t
@@ -114,5 +114,38 @@  Test experimental destination revset
   $ hg log -r '_destmerge()'
   abort: branch 'foobranch' has one head - please merge with an explicit rev
   (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]