[PATCH 1 of 3] mdiff: add a "blocksinrange" function to filter diff blocks by line range

Denis Laxalde denis.laxalde at logilab.fr
Mon Nov 28 09:54:14 UTC 2016


# HG changeset patch
# User Denis Laxalde <denis.laxalde at logilab.fr>
# Date 1476279051 -7200
#      Wed Oct 12 15:30:51 2016 +0200
# Node ID 0cf70234a38e47a3f7107611885368db9d52f574
# Parent  342d0cb4f446826169a83a6e773f1c767e0c977b
# EXP-Topic linerange-log/revset
mdiff: add a "blocksinrange" function to filter diff blocks by line range

The function filters diff blocks as generated by mdiff.allblock function based
on whether they are contained in a given line range based on the "b-side" of
blocks.

diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -113,6 +113,45 @@ def splitblock(base1, lines1, base2, lin
         s1 = i1
         s2 = i2
 
+def blocksinrange(blocks, rangeb):
+    """yield ``block = (a1, a2, b1, b2), stype`` items of `blocks` that are
+    inside `rangeb` from ``(b1, b2)`` point of view; a block ``(b1, b2)``
+    being inside `rangeb` if ``rangeb[0] < b2 and b1 < rangeb[1]``.
+
+    Compute `rangea` w.r.t. to ``(a1, a2)`` parts of `blocks`, and bind it to
+    the final StopIteration exception.
+    """
+    lbb, ubb = rangeb
+    lba, uba = None, None
+    for block in blocks:
+        (a1, a2, b1, b2), stype = block
+        if lbb >= b1 and ubb <= b2 and stype == '=':
+            # rangeb is within a single "=" hunk, restrict back linerange1
+            # by offsetting rangeb
+            lba = lbb - b1 + a1
+            uba = ubb - b1 + a1
+        else:
+            if lbb == ubb and b1 <= ubb < b2:
+                # oneline range, within (b1, b2) block
+                lba = a1
+                uba = a2
+            else:
+                if b1 <= lbb < b2:
+                    if stype == '=':
+                        lba = a2 - (b2 - lbb)
+                    else:
+                        lba = a1
+                if b1 < ubb <= b2:
+                    if stype == '=':
+                        uba = a1 + (ubb - b1)
+                    else:
+                        uba = a2
+        if lbb < b2 and b1 < ubb:
+            yield block
+    if lba is None or uba is None or uba < lba:
+        raise ValueError('out of range')
+    raise StopIteration((lba, uba))
+
 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
     """Return (block, type) tuples, where block is an mdiff.blocks
     line entry. type is '=' for blocks matching exactly one another
diff --git a/tests/test-linerange.py b/tests/test-linerange.py
new file mode 100644
--- /dev/null
+++ b/tests/test-linerange.py
@@ -0,0 +1,123 @@
+from __future__ import absolute_import
+
+import unittest
+from mercurial import mdiff
+
+def filteredblocks(blocks, rangeb):
+    """return `rangea` extracted from `blocks` coming from
+    `mdiff.blocksinrange` along with the mask of blocks within rangeb.
+    """
+    filtered = mdiff.blocksinrange(blocks, rangeb)
+    skipped = [True] * len(blocks)
+    while True:
+        try:
+            skipped[blocks.index(next(filtered))] = False
+        except StopIteration as exc:
+            break
+    return exc.args[0], skipped
+
+class blocksinrangetests(unittest.TestCase):
+
+    def setUp(self):
+        self.blocks = [
+            ((0, 3, 0, 2), '!'),
+            ((3, 7, 2, 6), '='),
+            ((7, 12, 6, 12), '!'),
+            ((15, 15, 12, 12), '='),
+        ]
+
+    def testWithinEqual(self):
+        linerange2 = (3, 5)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (4, 6))
+        self.assertEqual(skipped, [True, False, True, True])
+
+    def testWithinEqualStrictly(self):
+        linerange2 = (2, 6)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (3, 7))
+        self.assertEqual(skipped, [True, False, True, True])
+
+    def testWithinEqualLowerboundOneline(self):
+        linerange2 = (2, 3)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (3, 4))
+        self.assertEqual(skipped, [True, False, True, True])
+
+    def testWithinEqualLowerbound(self):
+        linerange2 = (2, 3)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (3, 4))
+        self.assertEqual(skipped, [True, False, True, True])
+
+    def testWithinEqualUpperbound(self):
+        linerange2 = (3, 6)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (4, 7))
+        self.assertEqual(skipped, [True, False, True, True])
+
+    def testWithinFirstBlockNeq(self):
+        for linerange2 in [
+            (0, 1),
+            (1, 1),
+            (1, 2),
+            (0, 2),
+        ]:
+            linerange1, skipped = filteredblocks(self.blocks, linerange2)
+            self.assertEqual(linerange1, (0, 3))
+            self.assertEqual(skipped, [False, True, True, True])
+
+    def testWithinLastBlockNeq(self):
+        for linerange2 in [
+            (6, 7),
+            (7, 8),
+            (7, 7),
+            (6, 12),
+        ]:
+            linerange1, skipped = filteredblocks(self.blocks, linerange2)
+            self.assertEqual(linerange1, (7, 12))
+            self.assertEqual(skipped, [True, True, False, True])
+
+    def testWithinLastLine(self):
+        linerange2 = (11, 12)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (7, 12))
+        self.assertEqual(skipped, [True, True, False, True])
+
+    def testAccrossTwoBlocks(self):
+        linerange2 = (1, 7)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (0, 12))
+        self.assertEqual(skipped, [False, False, False, True])
+
+    def testCrossingSeveralBlocks(self):
+        linerange2 = (1, 8)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (0, 12))
+        self.assertEqual(skipped, [False, False, False, True])
+
+    def testStartInEqBlock(self):
+        linerange2 = (5, 9)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (6, 12))
+        self.assertEqual(skipped, [True, False, False, True])
+
+    def testEndInEqBlock(self):
+        linerange2 = (1, 3)
+        linerange1, skipped = filteredblocks(self.blocks, linerange2)
+        self.assertEqual(linerange1, (0, 4))
+        self.assertEqual(skipped, [False, False, True, True])
+
+    def testOutOfRange(self):
+        for linerange2 in [
+            (0, 34),
+            (15, 12),
+        ]:
+            blocksin = mdiff.blocksinrange(self.blocks, linerange2)
+            with self.assertRaises(ValueError) as cm:
+                list(blocksin)
+            self.assertIn('out of range', str(cm.exception))
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)


More information about the Mercurial-devel mailing list