@@ -304,6 +304,22 @@
if entry[0] == 'u':
yield f
+ def ancestorchangectx(self, dfile):
+ extras = self.extras(dfile)
+ anccommitnode = extras.get('ancestorlinknode')
+ if anccommitnode:
+ return self._repo[anccommitnode]
+ else:
+ return None
+
+ 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."""
@@ -17,6 +17,7 @@
error,
hbisect,
match as matchmod,
+ mergestate as mergestatemod,
node,
obsolete as obsmod,
parser,
@@ -816,6 +817,54 @@
getargs(x, 0, 0, _("closed takes no arguments"))
return subset.filter(lambda r: repo[r].closesbranch())
+@predicate('conflict(type,[pattern])')
+def conflict(repo, subset, x):
+ """The type revision for any merge conflict matching pattern.
+ See :hg:`help patterns` for information about file patterns.
+
+ type should be one of "ancestor", "other" or "local", for the three-way
+ merge ancestor, the "other" tree, or the "local" tree.
+
+ The pattern without explicit kind like ``glob:`` is expected to be
+ relative to the current directory and match against a file exactly
+ for efficiency. If pattern is omitted, all conflicts are included.
+ """
+ # i18n: "conflict" is a keyword
+ l = getargs(x, 1, 2, _('conflict takes one or two arguments'))
+ t = getstring(l[0], _("conflict requires a type"))
+ if t not in ["ancestor", "other", "local"]:
+ msg = _('conflict file types are "ancestor", "other" or "local"')
+ raise error.Abort(msg)
+ if len(l) == 2:
+ pat = getstring(l[1], _("conflict takes a string pattern"))
+ else:
+ pat = None
+
+ ms = mergestatemod.mergestatereadonly.read(repo)
+
+ if pat is None:
+ files = ms.files()
+ elif 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:
+ if t == "ancestor":
+ s.add(ms.ancestorchangectx(f).rev())
+ elif t == "local":
+ s.add(ms.localfilectx(f).introrev())
+ elif t == "other":
+ 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
@@ -2230,3 +2230,94 @@
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
+
+There are no markers before a merge conflict exists
+
+ $ hg debugrevspec 'conflict("ancestor","glob:*")'
+ $ hg debugrevspec 'conflict("local","glob:*")'
+ $ hg debugrevspec 'conflict("other","glob:*")'
+
+Merge and test that the expected set of markers exist and work with patterns
+
+ $ 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 'conflict("ancestor","glob:*")'
+ 1
+ $ hg debugrevspec 'conflict("local","glob:*")'
+ 5
+ 6
+ $ hg debugrevspec 'conflict("other","glob:*")'
+ 2
+ 3
+ $ hg debugrevspec 'conflict("ancestor", "file1")'
+ 1
+ $ hg debugrevspec 'conflict("local", "file1")'
+ 5
+ $ hg debugrevspec 'conflict("other", "file1")'
+ 2
+ $ hg debugrevspec 'conflict("ancestor", "file2")'
+ 1
+ $ hg debugrevspec 'conflict("local", "file2")'
+ 6
+ $ hg debugrevspec 'conflict("other", "file2")'
+ 3
+
+There are no markers on files not in conflict
+ $ hg debugrevspec 'conflict("ancestor", "file4")'
+ $ hg debugrevspec 'conflict("local", "file4")'
+ $ hg debugrevspec 'conflict("other","file4")'
+
+It's possible to get interesting sets from markers
+
+ $ hg debugrevspec 'conflict("ancestor", "glob:*"):conflict("local", "file1")'
+ 1
+ 2
+ 3
+ 4
+ 5
+ $ hg debugrevspec 'conflict("ancestor", "file2"):conflict("other", "re:file[1234]")'
+ 1
+ 2
+ 3
+
+We get a nice error if we ask for a non-existent marker
+
+ $ hg debugrevspec 'conflict("mercurial-versus-sccs", "glob:*")'
+ abort: conflict file types are "ancestor", "other" or "local"
+ [255]
+ $ cd ..