comparison tests/test-manifest.py @ 24572:b83679eb5f86

manifestv2: add support for reading new manifest format The new manifest format is designed to be smaller, in particular to produce smaller deltas. It stores hashes in binary and puts the hash on a new line (for smaller deltas). It also uses stem compression to save space for long paths. The format has room for metadata, but that's there only for future-proofing. The parser thus accepts any metadata and throws it away. For more information, see http://mercurial.selenic.com/wiki/ManifestV2Plan. The current manifest format doesn't allow an empty filename, so we use an empty filename on the first line to tell a manifest of the new format from the old. Since we still never write manifests in the new format, the added code is unused, but it is tested by test-manifest.py.
author Martin von Zweigbergk <martinvonz@google.com>
date Fri, 27 Mar 2015 22:26:41 -0700
parents 487245cbf1ab
children 701d3554de0e
comparison
equal deleted inserted replaced
24571:919f8ce040be 24572:b83679eb5f86
6 6
7 from mercurial import manifest as manifestmod 7 from mercurial import manifest as manifestmod
8 from mercurial import match as matchmod 8 from mercurial import match as matchmod
9 9
10 EMTPY_MANIFEST = '' 10 EMTPY_MANIFEST = ''
11 EMTPY_MANIFEST_V2 = '\0\n'
11 12
12 HASH_1 = '1' * 40 13 HASH_1 = '1' * 40
13 BIN_HASH_1 = binascii.unhexlify(HASH_1) 14 BIN_HASH_1 = binascii.unhexlify(HASH_1)
14 HASH_2 = 'f' * 40 15 HASH_2 = 'f' * 40
15 BIN_HASH_2 = binascii.unhexlify(HASH_2) 16 BIN_HASH_2 = binascii.unhexlify(HASH_2)
20 'foo\0%(hash1)s%(flag1)s\n' 21 'foo\0%(hash1)s%(flag1)s\n'
21 ) % {'hash1': HASH_1, 22 ) % {'hash1': HASH_1,
22 'flag1': '', 23 'flag1': '',
23 'hash2': HASH_2, 24 'hash2': HASH_2,
24 'flag2': 'l', 25 'flag2': 'l',
26 }
27
28 # Same data as A_SHORT_MANIFEST
29 A_SHORT_MANIFEST_V2 = (
30 '\0\n'
31 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
32 '\x00foo\0%(flag1)s\n%(hash1)s\n'
33 ) % {'hash1': BIN_HASH_1,
34 'flag1': '',
35 'hash2': BIN_HASH_2,
36 'flag2': 'l',
37 }
38
39 # Same data as A_SHORT_MANIFEST
40 A_METADATA_MANIFEST = (
41 '\0foo\0bar\n'
42 '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata
43 '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata
44 ) % {'hash1': BIN_HASH_1,
45 'flag1': '',
46 'hash2': BIN_HASH_2,
47 'flag2': 'l',
48 }
49
50 A_STEM_COMPRESSED_MANIFEST = (
51 '\0\n'
52 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
53 '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars
54 '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters
55 '\x00%(verylongdir)sx/x\0\n%(hash1)s\n'
56 '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars
57 ) % {'hash1': BIN_HASH_1,
58 'flag1': '',
59 'hash2': BIN_HASH_2,
60 'flag2': 'l',
61 'verylongdir': 255 * 'x',
25 } 62 }
26 63
27 A_DEEPER_MANIFEST = ( 64 A_DEEPER_MANIFEST = (
28 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n' 65 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
29 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n' 66 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
75 def testEmptyManifest(self): 112 def testEmptyManifest(self):
76 m = parsemanifest(EMTPY_MANIFEST) 113 m = parsemanifest(EMTPY_MANIFEST)
77 self.assertEqual(0, len(m)) 114 self.assertEqual(0, len(m))
78 self.assertEqual([], list(m)) 115 self.assertEqual([], list(m))
79 116
117 def testEmptyManifestv2(self):
118 m = parsemanifest(EMTPY_MANIFEST_V2)
119 self.assertEqual(0, len(m))
120 self.assertEqual([], list(m))
121
80 def testManifest(self): 122 def testManifest(self):
81 m = parsemanifest(A_SHORT_MANIFEST) 123 m = parsemanifest(A_SHORT_MANIFEST)
82 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m)) 124 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
83 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py']) 125 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
84 self.assertEqual('l', m.flags('bar/baz/qux.py')) 126 self.assertEqual('l', m.flags('bar/baz/qux.py'))
85 self.assertEqual(BIN_HASH_1, m['foo']) 127 self.assertEqual(BIN_HASH_1, m['foo'])
86 self.assertEqual('', m.flags('foo')) 128 self.assertEqual('', m.flags('foo'))
87 self.assertRaises(KeyError, lambda : m['wat']) 129 self.assertRaises(KeyError, lambda : m['wat'])
130
131 def testParseManifestV2(self):
132 m1 = parsemanifest(A_SHORT_MANIFEST)
133 m2 = parsemanifest(A_SHORT_MANIFEST_V2)
134 # Should have same content as A_SHORT_MANIFEST
135 self.assertEqual(m1.text(), m2.text())
136
137 def testParseManifestMetadata(self):
138 # Metadata is for future-proofing and should be accepted but ignored
139 m = parsemanifest(A_METADATA_MANIFEST)
140 self.assertEqual(A_SHORT_MANIFEST, m.text())
141
142 def testParseManifestStemCompression(self):
143 m = parsemanifest(A_STEM_COMPRESSED_MANIFEST)
144 self.assertIn('bar/baz/qux.py', m)
145 self.assertIn('bar/qux/foo.py', m)
146 self.assertIn('bar/qux/foz.py', m)
147 self.assertIn(256 * 'x' + '/x', m)
148 self.assertIn(256 * 'x' + '/y', m)
88 149
89 def testSetItem(self): 150 def testSetItem(self):
90 want = BIN_HASH_1 151 want = BIN_HASH_1
91 152
92 m = parsemanifest(EMTPY_MANIFEST) 153 m = parsemanifest(EMTPY_MANIFEST)