[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