[PATCH RFC] revset: add new predicates for finding merge revisions

Simon Farnsworth simonfar at fb.com
Mon Feb 29 13:19:09 EST 2016


This patch isn't safe to apply as-is, because it introduces import loops.

I can fix most of them by code motion, but there's one I'm stuck on, so sending this out for advice.

Specifically, I can't work out a good way to break "Import cycle: mercurial.filemerge -> mercurial.templater -> mercurial.revset -> mercurial.merge -> mercurial.filemerge".



filemerge needs templater because it uses templater to write conflict markers. In turn, templater needs revset because there's a revset template function. I want revset to pull in merge so that it can reuse merge's code for reading merge state.

Am I best off creating a new mergestate.py file, and pulling merge state into there to break the loop? If not, have I missed something extremely obvious?

Simon

On 29/02/2016, 18:14, "Mercurial-devel on behalf of Simon Farnsworth" <mercurial-devel-bounces at mercurial-scm.org on behalf of simonfar at fb.com> wrote:

># HG changeset patch
># User Simon Farnsworth <simonfar at fb.com>
># Date 1456564536 0
>#      Sat Feb 27 09:15:36 2016 +0000
># Node ID fbb5d06a6aed2d2e0d9af3a63d3c251dcb72dbcb
># Parent  41dcd754526612c43b9695df8851557c851828ef
>revset: add new predicates for finding merge revisions
>
>When you've merged a large stack of changesets in a fast moving repository,
>the conflict markers can be apparent nonsense, as they're based on the heads
>of the two trees that were merged.
>
>Provide three new revset predicates (conflictbase, conflictother and
>conflictlocal) to help you explore the relevant parts of history; these are
>the introducing revisions of the "base", "other" and "local" commits for
>each file that has conflicts (restricted by a pattern). The user can build
>appropriate views of history from these three predicates for their
>particular situation.
>
>diff --git a/mercurial/merge.py b/mercurial/merge.py
>--- a/mercurial/merge.py
>+++ b/mercurial/merge.py
>@@ -434,6 +434,25 @@
>             if entry[0] == 'u':
>                 yield f
> 
>+    def ancestorfilectx(self, dfile):
>+        extras = self.extras(dfile)
>+        anccommitnode = extras.get('ancestorlinknode')
>+        if anccommitnode:
>+            actx = self._repo[anccommitnode]
>+        else:
>+            actx = None
>+
>+        entry = self._state[dfile]
>+        return self._repo.filectx(entry[3], fileid=entry[4], changeid=actx)
>+
>+    def otherfilectx(self, dfile):
>+        entry = self._state[dfile]
>+        return self.otherctx.filectx(entry[5], fileid=entry[6])
>+
>+    def localfilectx(self, dfile):
>+        entry = self._state[dfile]
>+        return self.localctx.filectx(entry[2])
>+
>     def driverresolved(self):
>         """Obtain the paths of driver-resolved files."""
> 
>diff --git a/mercurial/revset.py b/mercurial/revset.py
>--- a/mercurial/revset.py
>+++ b/mercurial/revset.py
>@@ -17,6 +17,7 @@
>     error,
>     hbisect,
>     match as matchmod,
>+    merge as mergemod,
>     node,
>     obsolete as obsmod,
>     parser,
>@@ -816,6 +817,93 @@
>     getargs(x, 0, 0, _("closed takes no arguments"))
>     return subset.filter(lambda r: repo[r].closesbranch())
> 
>+ at predicate('conflictbase(pattern)')
>+def conflictbase(repo, subset, x):
>+    """The base revision for any merge conflict matching pattern.
>+    See :hg:`help patterns` for information about file patterns.
>+
>+    The pattern without explicit kind like ``glob:`` is expected to be
>+    relative to the current directory and match against a file exactly
>+    for efficiency.
>+    """
>+    # i18n: "conflictbase" is a keyword
>+    pat = getstring(x, _("conflictbase requires a pattern"))
>+    ms = mergemod.mergestate.read(repo)
>+
>+    if not matchmod.patkind(pat):
>+        f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
>+        if f in ms:
>+            files = [f]
>+        else:
>+            files = []
>+    else:
>+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
>+        files = (f for f in ms if m(f))
>+
>+    s = set()
>+    for f in files:
>+        s.add(ms.ancestorfilectx(f).introrev())
>+
>+    return subset & s
>+
>+ at predicate('conflictlocal(pattern)')
>+def conflictlocal(repo, subset, x):
>+    """The local revision for any merge conflict matching pattern.
>+    See :hg:`help patterns` for information about file patterns.
>+
>+    The pattern without explicit kind like ``glob:`` is expected to be
>+    relative to the current directory and match against a file exactly
>+    for efficiency.
>+    """
>+    # i18n: "conflictlocal" is a keyword
>+    pat = getstring(x, _("conflictlocal requires a pattern"))
>+    ms = mergemod.mergestate.read(repo)
>+
>+    if not matchmod.patkind(pat):
>+        f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
>+        if f in ms:
>+            files = [f]
>+        else:
>+            files = []
>+    else:
>+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
>+        files = (f for f in ms if m(f))
>+
>+    s = set()
>+    for f in files:
>+        s.add(ms.localfilectx(f).introrev())
>+
>+    return subset & s
>+
>+ at predicate('conflictother(pattern)')
>+def conflictother(repo, subset, x):
>+    """The other revision for any merge conflict matching pattern.
>+    See :hg:`help patterns` for information about file patterns.
>+
>+    The pattern without explicit kind like ``glob:`` is expected to be
>+    relative to the current directory and match against a file exactly
>+    for efficiency.
>+    """
>+    # i18n: "conflictother" is a keyword
>+    pat = getstring(x, _("conflictother requires a pattern"))
>+    ms = mergemod.mergestate.read(repo)
>+
>+    if not matchmod.patkind(pat):
>+        f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
>+        if f in ms:
>+            files = [f]
>+        else:
>+            files = []
>+    else:
>+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
>+        files = (f for f in ms if m(f))
>+
>+    s = set()
>+    for f in files:
>+        s.add(ms.otherfilectx(f).introrev())
>+
>+    return subset & s
>+
> @predicate('contains(pattern)')
> def contains(repo, subset, x):
>     """The revision's manifest contains a file matching pattern (but might not
>diff --git a/tests/test-revset.t b/tests/test-revset.t
>--- a/tests/test-revset.t
>+++ b/tests/test-revset.t
>@@ -2230,3 +2230,62 @@
>   2
> 
>   $ cd ..
>+
>+Test merge conflict predicates
>+
>+  $ hg init conflictrepo
>+  $ cd conflictrepo
>+  $ echo file1 > file1
>+  $ echo file2 > file2
>+  $ hg commit -qAm first
>+  $ echo line2 >> file1
>+  $ hg commit -qAm second
>+  $ hg bookmark base
>+  $ hg bookmark tree1
>+  $ echo line1 > file1
>+  $ hg commit -qAm tree1-file1
>+  $ echo tree1-file2 > file2
>+  $ hg commit -qAm tree1-file2
>+  $ echo file3 > file3
>+  $ hg commit -qAm tree1-file3
>+  $ hg update base
>+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
>+  (activating bookmark base)
>+  $ hg bookmark tree2
>+  $ echo lineA > file1
>+  $ echo line2 >> file1
>+  $ hg commit -qAm tree2
>+  $ hg bookmark tree2
>+  $ echo tree2-file2 > file2
>+  $ hg commit -qAm tree2-file2
>+  $ echo file4 > file4
>+  $ hg commit -qAm tree2-file4
>+  $ hg merge --rev tree1 --tool :fail
>+  1 files updated, 0 files merged, 0 files removed, 2 files unresolved
>+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
>+  [1]
>+  $ hg resolve --list
>+  U file1
>+  U file2
>+  $ hg debugrevspec 'conflictbase("glob:*")'
>+  0
>+  1
>+  $ hg debugrevspec 'conflictlocal("glob:*")'
>+  5
>+  6
>+  $ hg debugrevspec 'conflictother("glob:*")'
>+  2
>+  3
>+  $ hg debugrevspec 'conflictbase("file1")'
>+  1
>+  $ hg debugrevspec 'conflictlocal("file1")'
>+  5
>+  $ hg debugrevspec 'conflictother("file1")'
>+  2
>+  $ hg debugrevspec 'conflictbase("file2")'
>+  0
>+  $ hg debugrevspec 'conflictlocal("file2")'
>+  6
>+  $ hg debugrevspec 'conflictother("file2")'
>+  3
>+  $ cd ..
>_______________________________________________
>Mercurial-devel mailing list
>Mercurial-devel at mercurial-scm.org
>https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=CwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=MfnfL18BLlALJ5-H51eJzbhtGOy9GWUJnFcSF-MfQoQ&s=wvLikMa9gCtpYuQCRpdbKVBKdFKOVGtB05Vi3pT3x_4&e= 


More information about the Mercurial-devel mailing list