[PATCH 2 of 3 V3] context: add a `blockancestors(fctx, fromline, toline)` function

Denis Laxalde denis at laxalde.org
Fri Dec 30 09:01:30 EST 2016

# HG changeset patch
# User Denis Laxalde <denis.laxalde at logilab.fr>
# Date 1482962617 -3600
#      Wed Dec 28 23:03:37 2016 +0100
# Node ID 5e2582bb20e2328f296a415834acdd401164283a
# Parent  17eee60ccbafe46f539a75484ebf110377781fb1
# EXP-Topic linerange-log/revset
context: add a `blockancestors(fctx, fromline, toline)` function

This yields ancestors of `fctx` by only keeping changesets touching the file
within specified linerange = (fromline, toline).

The algorithm is currently limited to following only the first parent in case
of merge (might be improved later).

Matching revisions are found by inspecting the result of `mdiff.allblocks()`,
filtered by `mdiff.blocksinrange()`, to find out if there are blocks of type
"!" within specified line range.

If, at some iteration, an ancestor with an empty line range is encountered,
the algorithm stops as it means that the considered block of lines actually
has been introduced in the revision of this iteration. Otherwise, we finally
yield the initial revision of the file as the block originates from it.

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1153,6 +1153,35 @@ class filectx(basefilectx):
         return [filectx(self._repo, self._path, fileid=x,
                         filelog=self._filelog) for x in c]
+def blockancestors(fctx, fromline, toline):
+    """Yield ancestors of `fctx` with respect to the block of lines within
+    `fromline`-`toline` range.
+    """
+    def changesrange(fctx1, fctx2, linerange2):
+        """Return `(diffinrange, linerange1)` where `diffinrange` is True
+        if diff from fctx2 to fctx1 has changes in linerange2 and
+        `linerange1` is the new line range for fctx1.
+        """
+        diffopts = patch.diffopts(fctx._repo.ui)
+        blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
+        filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
+        diffinrange = any(stype == '!' for _, stype in filteredblocks)
+        return diffinrange, linerange1
+    c, linerange2 = fctx, (fromline, toline)
+    for p in fctx.ancestors(followfirst=True):  # TODO handle followfirst
+        inrangep, linerange1 = changesrange(p, c, linerange2)
+        if inrangep:
+            yield c
+        if linerange1[0] == linerange1[1]:
+            # Ancestor's linerange is empty, meaning that the block got
+            # introduced in this revision; no need to go futher.
+            break
+        c, linerange2 = p, linerange1
+    else:
+        # The block originates from the initial revision.
+        yield c
 class committablectx(basectx):
     """A committablectx object provides common functionality for a context that
     wants the ability to commit, e.g. workingctx or memctx."""

More information about the Mercurial-devel mailing list