[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