branchcache: add a "pure topological head" fast path
In a narrow but actually quick common case, all topological heads are all on
the same branch and all open. In this case, computing the branch map is very
simple. We can quickly detect situation where this situation will not change.
So we update the V3 format to be able to express this situation and upgrade the
update code to detect we remains in that mode.
The branch cache is populated with the actual value when the branch map is
accessed, but the update_disk method can do the update without needing to
populate it.
import collections
import struct
import unittest
from mercurial import mdiff
class diffreplace(collections.namedtuple('diffreplace', 'start end from_ to')):
def __repr__(self):
return 'diffreplace(%r, %r, %r, %r)' % self
class BdiffTests(unittest.TestCase):
def assert_bdiff_applies(self, a, b):
d = mdiff.textdiff(a, b)
c = a
if d:
c = mdiff.patches(a, [d])
self.assertEqual(
c,
b,
(
"bad diff+patch result from\n %r to\n "
"%r: \nbdiff: %r\npatched: %r" % (a, b, d, c[:200])
),
)
def assert_bdiff(self, a, b):
self.assert_bdiff_applies(a, b)
self.assert_bdiff_applies(b, a)
def test_bdiff_basic(self):
cases = [
(b"a\nc\n\n\n\n", b"a\nb\n\n\n"),
(b"a\nb\nc\n", b"a\nc\n"),
(b"", b""),
(b"a\nb\nc", b"a\nb\nc"),
(b"a\nb\nc\nd\n", b"a\nd\n"),
(b"a\nb\nc\nd\n", b"a\nc\ne\n"),
(b"a\nb\nc\n", b"a\nc\n"),
(b"a\n", b"c\na\nb\n"),
(b"a\n", b""),
(b"a\n", b"b\nc\n"),
(b"a\n", b"c\na\n"),
(b"", b"adjfkjdjksdhfksj"),
(b"", b"ab"),
(b"", b"abc"),
(b"a", b"a"),
(b"ab", b"ab"),
(b"abc", b"abc"),
(b"a\n", b"a\n"),
(b"a\nb", b"a\nb"),
]
for a, b in cases:
self.assert_bdiff(a, b)
def showdiff(self, a, b):
bin = mdiff.textdiff(a, b)
pos = 0
q = 0
actions = []
while pos < len(bin):
p1, p2, l = struct.unpack(">lll", bin[pos : pos + 12])
pos += 12
if p1:
actions.append(a[q:p1])
actions.append(diffreplace(p1, p2, a[p1:p2], bin[pos : pos + l]))
pos += l
q = p2
if q < len(a):
actions.append(a[q:])
return actions
def test_issue1295(self):
cases = [
(
b"x\n\nx\n\nx\n\nx\n\nz\n",
b"x\n\nx\n\ny\n\nx\n\nx\n\nz\n",
[
b'x\n\nx\n\n',
diffreplace(6, 6, b'', b'y\n\n'),
b'x\n\nx\n\nz\n',
],
),
(
b"x\n\nx\n\nx\n\nx\n\nz\n",
b"x\n\nx\n\ny\n\nx\n\ny\n\nx\n\nz\n",
[
b'x\n\nx\n\n',
diffreplace(6, 6, b'', b'y\n\n'),
b'x\n\n',
diffreplace(9, 9, b'', b'y\n\n'),
b'x\n\nz\n',
],
),
]
for old, new, want in cases:
self.assertEqual(self.showdiff(old, new), want)
def test_issue1295_varies_on_pure(self):
# we should pick up abbbc. rather than bc.de as the longest match
got = self.showdiff(
b"a\nb\nb\nb\nc\n.\nd\ne\n.\nf\n",
b"a\nb\nb\na\nb\nb\nb\nc\n.\nb\nc\n.\nd\ne\nf\n",
)
want_c = [
b'a\nb\nb\n',
diffreplace(6, 6, b'', b'a\nb\nb\nb\nc\n.\n'),
b'b\nc\n.\nd\ne\n',
diffreplace(16, 18, b'.\n', b''),
b'f\n',
]
want_pure = [
diffreplace(0, 0, b'', b'a\nb\nb\n'),
b'a\nb\nb\nb\nc\n.\n',
diffreplace(12, 12, b'', b'b\nc\n.\n'),
b'd\ne\n',
diffreplace(16, 18, b'.\n', b''),
b'f\n',
]
self.assertTrue(
got in (want_c, want_pure),
'got: %r, wanted either %r or %r' % (got, want_c, want_pure),
)
def test_fixws(self):
cases = [
(b" \ta\r b\t\n", b"ab\n", 1),
(b" \ta\r b\t\n", b" a b\n", 0),
(b"", b"", 1),
(b"", b"", 0),
]
for a, b, allws in cases:
c = mdiff.fixws(a, allws)
self.assertEqual(
c, b, 'fixws(%r) want %r got %r (allws=%r)' % (a, b, c, allws)
)
def test_nice_diff_for_trivial_change(self):
self.assertEqual(
self.showdiff(
b''.join(b'<%d\n-\n' % i for i in range(5)),
b''.join(b'>%d\n-\n' % i for i in range(5)),
),
[
diffreplace(0, 3, b'<0\n', b'>0\n'),
b'-\n',
diffreplace(5, 8, b'<1\n', b'>1\n'),
b'-\n',
diffreplace(10, 13, b'<2\n', b'>2\n'),
b'-\n',
diffreplace(15, 18, b'<3\n', b'>3\n'),
b'-\n',
diffreplace(20, 23, b'<4\n', b'>4\n'),
b'-\n',
],
)
def test_prefer_appending(self):
# 1 line to 3 lines
self.assertEqual(
self.showdiff(b'a\n', b'a\n' * 3),
[b'a\n', diffreplace(2, 2, b'', b'a\na\n')],
)
# 1 line to 5 lines
self.assertEqual(
self.showdiff(b'a\n', b'a\n' * 5),
[b'a\n', diffreplace(2, 2, b'', b'a\na\na\na\n')],
)
def test_prefer_removing_trailing(self):
# 3 lines to 1 line
self.assertEqual(
self.showdiff(b'a\n' * 3, b'a\n'),
[b'a\n', diffreplace(2, 6, b'a\na\n', b'')],
)
# 5 lines to 1 line
self.assertEqual(
self.showdiff(b'a\n' * 5, b'a\n'),
[b'a\n', diffreplace(2, 10, b'a\na\na\na\n', b'')],
)
if __name__ == '__main__':
import silenttestrunner
silenttestrunner.main(__name__)