diff --git a/mercurial/manifest.py b/mercurial/manifest.py --- a/mercurial/manifest.py +++ b/mercurial/manifest.py @@ -11,8 +11,7 @@ import array, struct propertycache = util.propertycache -def _parse(data): - """Generates (path, node, flags) tuples from a manifest text""" +def _parsev1(data): # This method does a little bit of excessive-looking # precondition checking. This is so that the behavior of this # class exactly matches its C counterpart to try and help @@ -31,6 +30,34 @@ def _parse(data): else: yield f, revlog.bin(n), '' +def _parsev2(data): + metadataend = data.find('\n') + # Just ignore metadata for now + pos = metadataend + 1 + prevf = '' + while pos < len(data): + end = data.find('\n', pos + 1) # +1 to skip stem length byte + if end == -1: + raise ValueError('Manifest ended with incomplete file entry.') + stemlen = ord(data[pos]) + items = data[pos + 1:end].split('\0') + f = prevf[:stemlen] + items[0] + if prevf > f: + raise ValueError('Manifest entries not in sorted order.') + fl = items[1] + # Just ignore metadata (items[2:] for now) + n = data[end + 1:end + 21] + yield f, n, fl + pos = end + 22 + prevf = f + +def _parse(data): + """Generates (path, node, flags) tuples from a manifest text""" + if data.startswith('\0'): + return iter(_parsev2(data)) + else: + return iter(_parsev1(data)) + def _text(it): """Given an iterator over (path, node, flags) tuples, returns a manifest text""" @@ -116,7 +143,13 @@ except AttributeError: class manifestdict(object): def __init__(self, data=''): - self._lm = _lazymanifest(data) + if data.startswith('\0'): + #_lazymanifest can not parse v2 + self._lm = _lazymanifest('') + for f, n, fl in _parsev2(data): + self._lm[f] = n, fl + else: + self._lm = _lazymanifest(data) def __getitem__(self, key): return self._lm[key][0] diff --git a/tests/test-manifest.py b/tests/test-manifest.py --- a/tests/test-manifest.py +++ b/tests/test-manifest.py @@ -8,6 +8,7 @@ from mercurial import manifest as manife from mercurial import match as matchmod EMTPY_MANIFEST = '' +EMTPY_MANIFEST_V2 = '\0\n' HASH_1 = '1' * 40 BIN_HASH_1 = binascii.unhexlify(HASH_1) @@ -24,6 +25,42 @@ A_SHORT_MANIFEST = ( 'flag2': 'l', } +# Same data as A_SHORT_MANIFEST +A_SHORT_MANIFEST_V2 = ( + '\0\n' + '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n' + '\x00foo\0%(flag1)s\n%(hash1)s\n' + ) % {'hash1': BIN_HASH_1, + 'flag1': '', + 'hash2': BIN_HASH_2, + 'flag2': 'l', + } + +# Same data as A_SHORT_MANIFEST +A_METADATA_MANIFEST = ( + '\0foo\0bar\n' + '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata + '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata + ) % {'hash1': BIN_HASH_1, + 'flag1': '', + 'hash2': BIN_HASH_2, + 'flag2': 'l', + } + +A_STEM_COMPRESSED_MANIFEST = ( + '\0\n' + '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n' + '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars + '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters + '\x00%(verylongdir)sx/x\0\n%(hash1)s\n' + '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars + ) % {'hash1': BIN_HASH_1, + 'flag1': '', + 'hash2': BIN_HASH_2, + 'flag2': 'l', + 'verylongdir': 255 * 'x', + } + A_DEEPER_MANIFEST = ( 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n' 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n' @@ -77,6 +114,11 @@ class testmanifest(unittest.TestCase): self.assertEqual(0, len(m)) self.assertEqual([], list(m)) + def testEmptyManifestv2(self): + m = parsemanifest(EMTPY_MANIFEST_V2) + self.assertEqual(0, len(m)) + self.assertEqual([], list(m)) + def testManifest(self): m = parsemanifest(A_SHORT_MANIFEST) self.assertEqual(['bar/baz/qux.py', 'foo'], list(m)) @@ -86,6 +128,25 @@ class testmanifest(unittest.TestCase): self.assertEqual('', m.flags('foo')) self.assertRaises(KeyError, lambda : m['wat']) + def testParseManifestV2(self): + m1 = parsemanifest(A_SHORT_MANIFEST) + m2 = parsemanifest(A_SHORT_MANIFEST_V2) + # Should have same content as A_SHORT_MANIFEST + self.assertEqual(m1.text(), m2.text()) + + def testParseManifestMetadata(self): + # Metadata is for future-proofing and should be accepted but ignored + m = parsemanifest(A_METADATA_MANIFEST) + self.assertEqual(A_SHORT_MANIFEST, m.text()) + + def testParseManifestStemCompression(self): + m = parsemanifest(A_STEM_COMPRESSED_MANIFEST) + self.assertIn('bar/baz/qux.py', m) + self.assertIn('bar/qux/foo.py', m) + self.assertIn('bar/qux/foz.py', m) + self.assertIn(256 * 'x' + '/x', m) + self.assertIn(256 * 'x' + '/y', m) + def testSetItem(self): want = BIN_HASH_1