##// END OF EJS Templates
manifest: split manifestdict into high-level and low-level logic...
Augie Fackler -
r24225:3e5c4af6 default
parent child Browse files
Show More
@@ -9,53 +9,119 b' from i18n import _'
9 import mdiff, parsers, error, revlog, util
9 import mdiff, parsers, error, revlog, util
10 import array, struct
10 import array, struct
11
11
12 # Pure Python fallback
12
13 def _parsemanifest(mfdict, fdict, lines):
13 class _lazymanifest(dict):
14 bin = revlog.bin
14 """This is the pure implementation of lazymanifest.
15 for l in lines.splitlines():
15
16 f, n = l.split('\0')
16 It has not been optimized *at all* and is not lazy.
17 if len(n) > 40:
17 """
18 fdict[f] = n[40:]
19 mfdict[f] = bin(n[:40])
20 else:
21 mfdict[f] = bin(n)
22
18
23 def _parse(lines, mfdict, flags):
19 def __init__(self, data):
24 try:
20 # This init method does a little bit of excessive-looking
25 parsers.parse_manifest(mfdict, flags, lines)
21 # precondition checking. This is so that the behavior of this
26 except AttributeError:
22 # class exactly matches its C counterpart to try and help
27 _parsemanifest(mfdict, flags, lines)
23 # prevent surprise breakage for anyone that develops against
28 return mfdict
24 # the pure version.
29
25 if data and data[-1] != '\n':
30 class manifestdict(dict):
26 raise ValueError('Manifest did not end in a newline.')
31 def __init__(self, data=''):
27 dict.__init__(self)
32 self._flags = {}
28 prev = None
33 _parse(data, self, self._flags)
29 for l in data.splitlines():
30 if prev is not None and prev > l:
31 raise ValueError('Manifest lines not in sorted order.')
32 prev = l
33 f, n = l.split('\0')
34 if len(n) > 40:
35 self[f] = revlog.bin(n[:40]), n[40:]
36 else:
37 self[f] = revlog.bin(n), ''
34
38
35 def __setitem__(self, k, v):
39 def __setitem__(self, k, v):
36 assert v is not None
40 node, flag = v
37 dict.__setitem__(self, k, v)
41 assert node is not None
38 def flags(self, f):
42 if len(node) > 21:
39 return self._flags.get(f, "")
43 node = node[:21] # match c implementation behavior
40 def setflag(self, f, flags):
44 dict.__setitem__(self, k, (node, flag))
41 """Set the flags (symlink, executable) for path f."""
45
42 self._flags[f] = flags
46 def __iter__(self):
47 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
48
43 def copy(self):
49 def copy(self):
44 copy = manifestdict()
50 c = _lazymanifest('')
45 dict.__init__(copy, self)
51 c.update(self)
46 copy._flags = dict.copy(self._flags)
52 return c
47 return copy
53
54 def diff(self, m2, clean=False):
55 '''Finds changes between the current manifest and m2.'''
56 diff = {}
57
58 for fn, e1 in self.iteritems():
59 if fn not in m2:
60 diff[fn] = e1, (None, '')
61 else:
62 e2 = m2[fn]
63 if e1 != e2:
64 diff[fn] = e1, e2
65 elif clean:
66 diff[fn] = None
67
68 for fn, e2 in m2.iteritems():
69 if fn not in self:
70 diff[fn] = (None, ''), e2
71
72 return diff
73
74 def filtercopy(self, filterfn):
75 c = _lazymanifest('')
76 for f, n, fl in self:
77 if filterfn(f):
78 c[f] = n, fl
79 return c
80
81 def text(self):
82 """Get the full data of this manifest as a bytestring."""
83 fl = sorted(self)
84
85 _hex = revlog.hex
86 # if this is changed to support newlines in filenames,
87 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 return ''.join("%s\0%s%s\n" % (
89 f, _hex(n[:20]), flag) for f, n, flag in fl)
90
91 class manifestdict(object):
92 def __init__(self, data=''):
93 self._lm = _lazymanifest(data)
94
95 def __getitem__(self, key):
96 return self._lm[key][0]
97
98 def __len__(self):
99 return len(self._lm)
100
101 def __setitem__(self, key, node):
102 self._lm[key] = node, self.flags(key, '')
103
104 def __contains__(self, key):
105 return key in self._lm
106
107 def __delitem__(self, key):
108 del self._lm[key]
109
110 def keys(self):
111 return [x[0] for x in self._lm]
112
113 def iterkeys(self):
114 return iter(self.keys())
115
48 def intersectfiles(self, files):
116 def intersectfiles(self, files):
49 '''make a new manifestdict with the intersection of self with files
117 '''make a new lazymanifest with the intersection of self with files
50
118
51 The algorithm assumes that files is much smaller than self.'''
119 The algorithm assumes that files is much smaller than self.'''
52 ret = manifestdict()
120 ret = manifestdict()
121 lm = self._lm
53 for fn in files:
122 for fn in files:
54 if fn in self:
123 if fn in lm:
55 ret[fn] = self[fn]
124 ret._lm[fn] = self._lm[fn]
56 flags = self._flags.get(fn, None)
57 if flags:
58 ret._flags[fn] = flags
59 return ret
125 return ret
60
126
61 def filesnotin(self, m2):
127 def filesnotin(self, m2):
@@ -74,11 +140,9 b' class manifestdict(dict):'
74 (not match.anypats() and util.all(fn in self for fn in files))):
140 (not match.anypats() and util.all(fn in self for fn in files))):
75 return self.intersectfiles(files)
141 return self.intersectfiles(files)
76
142
77 m = self.copy()
143 lm = manifestdict('')
78 for fn in m.keys():
144 lm._lm = self._lm.filtercopy(match)
79 if not match(fn):
145 return lm
80 del m[fn]
81 return m
82
146
83 def diff(self, m2, clean=False):
147 def diff(self, m2, clean=False):
84 '''Finds changes between the current manifest and m2.
148 '''Finds changes between the current manifest and m2.
@@ -95,35 +159,36 b' class manifestdict(dict):'
95 the nodeid will be None and the flags will be the empty
159 the nodeid will be None and the flags will be the empty
96 string.
160 string.
97 '''
161 '''
98 diff = {}
162 return self._lm.diff(m2._lm, clean)
163
164 def setflag(self, key, flag):
165 self._lm[key] = self[key], flag
166
167 def get(self, key, default=None):
168 try:
169 return self._lm[key][0]
170 except KeyError:
171 return default
99
172
100 for fn, n1 in self.iteritems():
173 def flags(self, key, default=''):
101 fl1 = self._flags.get(fn, '')
174 try:
102 n2 = m2.get(fn, None)
175 return self._lm[key][1]
103 fl2 = m2._flags.get(fn, '')
176 except KeyError:
104 if n2 is None:
177 return default
105 fl2 = ''
106 if n1 != n2 or fl1 != fl2:
107 diff[fn] = ((n1, fl1), (n2, fl2))
108 elif clean:
109 diff[fn] = None
110
178
111 for fn, n2 in m2.iteritems():
179 def __iter__(self):
112 if fn not in self:
180 return (x[0] for x in self._lm)
113 fl2 = m2._flags.get(fn, '')
114 diff[fn] = ((None, ''), (n2, fl2))
115
181
116 return diff
182 def copy(self):
183 c = manifestdict('')
184 c._lm = self._lm.copy()
185 return c
186
187 def iteritems(self):
188 return (x[:2] for x in self._lm)
117
189
118 def text(self):
190 def text(self):
119 """Get the full data of this manifest as a bytestring."""
191 return self._lm.text()
120 fl = sorted(self)
121 _checkforbidden(fl)
122
123 hex, flags = revlog.hex, self.flags
124 # if this is changed to support newlines in filenames,
125 # be sure to check the templates/ dir again (especially *-raw.tmpl)
126 return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
127
192
128 def fastdelta(self, base, changes):
193 def fastdelta(self, base, changes):
129 """Given a base manifest text as an array.array and a list of changes
194 """Given a base manifest text as an array.array and a list of changes
@@ -143,7 +208,8 b' class manifestdict(dict):'
143 # bs will either be the index of the item or the insert point
208 # bs will either be the index of the item or the insert point
144 start, end = _msearch(addbuf, f, start)
209 start, end = _msearch(addbuf, f, start)
145 if not todelete:
210 if not todelete:
146 l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
211 h, fl = self._lm[f]
212 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
147 else:
213 else:
148 if start == end:
214 if start == end:
149 # item we want to delete was not found, error out
215 # item we want to delete was not found, error out
@@ -280,12 +346,10 b' class manifest(revlog.revlog):'
280 m = self._mancache[node][0]
346 m = self._mancache[node][0]
281 return m.get(f), m.flags(f)
347 return m.get(f), m.flags(f)
282 text = self.revision(node)
348 text = self.revision(node)
283 start, end = _msearch(text, f)
349 try:
284 if start == end:
350 return manifestdict(text)._lm[f]
351 except KeyError:
285 return None, None
352 return None, None
286 l = text[start:end]
287 f, n = l.split('\0')
288 return revlog.bin(n[:40]), n[40:-1]
289
353
290 def add(self, m, transaction, link, p1, p2, added, removed):
354 def add(self, m, transaction, link, p1, p2, added, removed):
291 if p1 in self._mancache:
355 if p1 in self._mancache:
@@ -4,7 +4,7 b' import itertools'
4
4
5 import silenttestrunner
5 import silenttestrunner
6
6
7 from mercurial import parsers
7 from mercurial import manifest as manifestmod
8
8
9 HASH_1 = '1' * 40
9 HASH_1 = '1' * 40
10 HASH_2 = 'f' * 40
10 HASH_2 = 'f' * 40
@@ -38,12 +38,12 b' class testmanifest(unittest.TestCase):'
38 self.assert_(thing in container, msg)
38 self.assert_(thing in container, msg)
39
39
40 def testEmptyManifest(self):
40 def testEmptyManifest(self):
41 m = parsers.lazymanifest('')
41 m = manifestmod._lazymanifest('')
42 self.assertEqual(0, len(m))
42 self.assertEqual(0, len(m))
43 self.assertEqual([], list(m))
43 self.assertEqual([], list(m))
44
44
45 def testManifest(self):
45 def testManifest(self):
46 m = parsers.lazymanifest(A_SHORT_MANIFEST)
46 m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
47 want = [
47 want = [
48 ('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
48 ('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
49 ('foo', binascii.unhexlify(HASH_1), ''),
49 ('foo', binascii.unhexlify(HASH_1), ''),
@@ -58,13 +58,13 b' class testmanifest(unittest.TestCase):'
58 def testSetItem(self):
58 def testSetItem(self):
59 want = binascii.unhexlify(HASH_1), ''
59 want = binascii.unhexlify(HASH_1), ''
60
60
61 m = parsers.lazymanifest('')
61 m = manifestmod._lazymanifest('')
62 m['a'] = want
62 m['a'] = want
63 self.assertIn('a', m)
63 self.assertIn('a', m)
64 self.assertEqual(want, m['a'])
64 self.assertEqual(want, m['a'])
65 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
65 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
66
66
67 m = parsers.lazymanifest(A_SHORT_MANIFEST)
67 m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
68 m['a'] = want
68 m['a'] = want
69 self.assertEqual(want, m['a'])
69 self.assertEqual(want, m['a'])
70 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
70 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
@@ -76,7 +76,7 b' class testmanifest(unittest.TestCase):'
76 def testCompaction(self):
76 def testCompaction(self):
77 unhex = binascii.unhexlify
77 unhex = binascii.unhexlify
78 h1, h2 = unhex(HASH_1), unhex(HASH_2)
78 h1, h2 = unhex(HASH_1), unhex(HASH_2)
79 m = parsers.lazymanifest(A_SHORT_MANIFEST)
79 m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
80 m['alpha'] = h1, ''
80 m['alpha'] = h1, ''
81 m['beta'] = h2, ''
81 m['beta'] = h2, ''
82 del m['foo']
82 del m['foo']
@@ -91,8 +91,8 b' class testmanifest(unittest.TestCase):'
91 self.assertEqual(w, list(m))
91 self.assertEqual(w, list(m))
92
92
93 def testSetGetNodeSuffix(self):
93 def testSetGetNodeSuffix(self):
94 clean = parsers.lazymanifest(A_SHORT_MANIFEST)
94 clean = manifestmod._lazymanifest(A_SHORT_MANIFEST)
95 m = parsers.lazymanifest(A_SHORT_MANIFEST)
95 m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
96 h, f = m['foo']
96 h, f = m['foo']
97 want = h + 'a', f
97 want = h + 'a', f
98 # Merge code wants to set 21-byte fake hashes at times
98 # Merge code wants to set 21-byte fake hashes at times
@@ -120,7 +120,7 b' class testmanifest(unittest.TestCase):'
120 self.assertEqual({'foo': ((h, ''), want)}, clean.diff(m))
120 self.assertEqual({'foo': ((h, ''), want)}, clean.diff(m))
121
121
122 def testFilterCopyException(self):
122 def testFilterCopyException(self):
123 m = parsers.lazymanifest(A_SHORT_MANIFEST)
123 m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
124 def filt(path):
124 def filt(path):
125 if path == 'foo':
125 if path == 'foo':
126 assert False
126 assert False
@@ -128,7 +128,7 b' class testmanifest(unittest.TestCase):'
128 self.assertRaises(AssertionError, m.filtercopy, filt)
128 self.assertRaises(AssertionError, m.filtercopy, filt)
129
129
130 def testRemoveItem(self):
130 def testRemoveItem(self):
131 m = parsers.lazymanifest(A_SHORT_MANIFEST)
131 m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
132 del m['foo']
132 del m['foo']
133 self.assertRaises(KeyError, lambda : m['foo'])
133 self.assertRaises(KeyError, lambda : m['foo'])
134 self.assertEqual(1, len(m))
134 self.assertEqual(1, len(m))
@@ -138,9 +138,9 b' class testmanifest(unittest.TestCase):'
138 MISSING = (None, '')
138 MISSING = (None, '')
139 addl = 'z-only-in-left\0' + HASH_1 + '\n'
139 addl = 'z-only-in-left\0' + HASH_1 + '\n'
140 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
140 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
141 left = parsers.lazymanifest(
141 left = manifestmod._lazymanifest(
142 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
142 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
143 right = parsers.lazymanifest(A_SHORT_MANIFEST + addr)
143 right = manifestmod._lazymanifest(A_SHORT_MANIFEST + addr)
144 want = {
144 want = {
145 'foo': ((binascii.unhexlify(HASH_3), 'x'),
145 'foo': ((binascii.unhexlify(HASH_3), 'x'),
146 (binascii.unhexlify(HASH_1), '')),
146 (binascii.unhexlify(HASH_1), '')),
@@ -154,14 +154,14 b' class testmanifest(unittest.TestCase):'
154 'foo': (MISSING, (binascii.unhexlify(HASH_3), 'x')),
154 'foo': (MISSING, (binascii.unhexlify(HASH_3), 'x')),
155 'z-only-in-left': (MISSING, (binascii.unhexlify(HASH_1), '')),
155 'z-only-in-left': (MISSING, (binascii.unhexlify(HASH_1), '')),
156 }
156 }
157 self.assertEqual(want, parsers.lazymanifest('').diff(left))
157 self.assertEqual(want, manifestmod._lazymanifest('').diff(left))
158
158
159 want = {
159 want = {
160 'bar/baz/qux.py': ((binascii.unhexlify(HASH_2), 'l'), MISSING),
160 'bar/baz/qux.py': ((binascii.unhexlify(HASH_2), 'l'), MISSING),
161 'foo': ((binascii.unhexlify(HASH_3), 'x'), MISSING),
161 'foo': ((binascii.unhexlify(HASH_3), 'x'), MISSING),
162 'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
162 'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
163 }
163 }
164 self.assertEqual(want, left.diff(parsers.lazymanifest('')))
164 self.assertEqual(want, left.diff(manifestmod._lazymanifest('')))
165 copy = right.copy()
165 copy = right.copy()
166 del copy['z-only-in-right']
166 del copy['z-only-in-right']
167 del right['foo']
167 del right['foo']
@@ -171,7 +171,7 b' class testmanifest(unittest.TestCase):'
171 }
171 }
172 self.assertEqual(want, right.diff(copy))
172 self.assertEqual(want, right.diff(copy))
173
173
174 short = parsers.lazymanifest(A_SHORT_MANIFEST)
174 short = manifestmod._lazymanifest(A_SHORT_MANIFEST)
175 pruned = short.copy()
175 pruned = short.copy()
176 del pruned['foo']
176 del pruned['foo']
177 want = {
177 want = {
@@ -192,30 +192,37 b' class testmanifest(unittest.TestCase):'
192 backwards = ''.join(
192 backwards = ''.join(
193 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
193 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
194 try:
194 try:
195 parsers.lazymanifest(backwards)
195 manifestmod._lazymanifest(backwards)
196 self.fail('Should have raised ValueError')
196 self.fail('Should have raised ValueError')
197 except ValueError, v:
197 except ValueError, v:
198 self.assertIn('Manifest lines not in sorted order.', str(v))
198 self.assertIn('Manifest lines not in sorted order.', str(v))
199
199
200 def testNoTerminalNewline(self):
200 def testNoTerminalNewline(self):
201 try:
201 try:
202 parsers.lazymanifest(A_SHORT_MANIFEST + 'wat')
202 manifestmod._lazymanifest(A_SHORT_MANIFEST + 'wat')
203 self.fail('Should have raised ValueError')
203 self.fail('Should have raised ValueError')
204 except ValueError, v:
204 except ValueError, v:
205 self.assertIn('Manifest did not end in a newline.', str(v))
205 self.assertIn('Manifest did not end in a newline.', str(v))
206
206
207 def testNoNewLineAtAll(self):
207 def testNoNewLineAtAll(self):
208 try:
208 try:
209 parsers.lazymanifest('wat')
209 manifestmod._lazymanifest('wat')
210 self.fail('Should have raised ValueError')
210 self.fail('Should have raised ValueError')
211 except ValueError, v:
211 except ValueError, v:
212 self.assertIn('Manifest did not end in a newline.', str(v))
212 self.assertIn('Manifest did not end in a newline.', str(v))
213
213
214 def testHugeManifest(self):
214 def testHugeManifest(self):
215 m = parsers.lazymanifest(A_HUGE_MANIFEST)
215 m = manifestmod._lazymanifest(A_HUGE_MANIFEST)
216 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
216 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
217 self.assertEqual(len(m), len(list(m)))
217 self.assertEqual(len(m), len(list(m)))
218
218
219 def testIntersectFiles(self):
220 m = manifestmod.manifestdict(A_HUGE_MANIFEST)
221 m2 = m.intersectfiles(['file1', 'file200', 'file300'])
222 w = ('file1\0%sx\n'
223 'file200\0%sl\n'
224 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
225 self.assertEqual(w, m2.text())
219
226
220 if __name__ == '__main__':
227 if __name__ == '__main__':
221 silenttestrunner.main(__name__)
228 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now