##// END OF EJS Templates
manifest: throw LookupError if node not in revlog...
Durham Goode -
r30290:1a0c1ad5 default
parent child Browse files
Show More
@@ -1,1535 +1,1538
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class lazymanifestiter(object):
107 class lazymanifestiter(object):
108 def __init__(self, lm):
108 def __init__(self, lm):
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self):
112 def __iter__(self):
113 return self
113 return self
114
114
115 def next(self):
115 def next(self):
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 self.pos += 1
121 self.pos += 1
122 return data[0]
122 return data[0]
123 self.pos += 1
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
125 return data[pos:zeropos]
126
126
127 class lazymanifestiterentries(object):
127 class lazymanifestiterentries(object):
128 def __init__(self, lm):
128 def __init__(self, lm):
129 self.lm = lm
129 self.lm = lm
130 self.pos = 0
130 self.pos = 0
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return self
133 return self
134
134
135 def next(self):
135 def next(self):
136 try:
136 try:
137 data, pos = self.lm._get(self.pos)
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
138 except IndexError:
139 raise StopIteration
139 raise StopIteration
140 if pos == -1:
140 if pos == -1:
141 self.pos += 1
141 self.pos += 1
142 return data
142 return data
143 zeropos = data.find('\x00', pos)
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
148 return (data[pos:zeropos], hashval, flags)
149
149
150 def unhexlify(data, extra, pos, length):
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
151 s = data[pos:pos + length].decode('hex')
152 if extra:
152 if extra:
153 s += chr(extra & 0xff)
153 s += chr(extra & 0xff)
154 return s
154 return s
155
155
156 def _cmp(a, b):
156 def _cmp(a, b):
157 return (a > b) - (a < b)
157 return (a > b) - (a < b)
158
158
159 class _lazymanifest(object):
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
161 if positions is None:
162 self.positions = self.findlines(data)
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
164 self.data = data
165 self.extradata = []
165 self.extradata = []
166 else:
166 else:
167 self.positions = positions[:]
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
169 self.extradata = extradata[:]
170 self.data = data
170 self.data = data
171
171
172 def findlines(self, data):
172 def findlines(self, data):
173 if not data:
173 if not data:
174 return []
174 return []
175 pos = data.find("\n")
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
178 positions = [0]
179 prev = data[:data.find('\x00')]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
185 prev = nexts
186 pos = data.find("\n", pos + 1)
186 pos = data.find("\n", pos + 1)
187 return positions
187 return positions
188
188
189 def _get(self, index):
189 def _get(self, index):
190 # get the position encoded in pos:
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
192 # negative number is in extrapieces
193 pos = self.positions[index]
193 pos = self.positions[index]
194 if pos >= 0:
194 if pos >= 0:
195 return self.data, pos
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
196 return self.extradata[-pos - 1], -1
197
197
198 def _getkey(self, pos):
198 def _getkey(self, pos):
199 if pos >= 0:
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
201 return self.extradata[-pos - 1][0]
202
202
203 def bsearch(self, key):
203 def bsearch(self, key):
204 first = 0
204 first = 0
205 last = len(self.positions) - 1
205 last = len(self.positions) - 1
206
206
207 while first <= last:
207 while first <= last:
208 midpoint = (first + last)//2
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
211 r = _cmp(key, candidate)
212 if r == 0:
212 if r == 0:
213 return midpoint
213 return midpoint
214 else:
214 else:
215 if r < 0:
215 if r < 0:
216 last = midpoint - 1
216 last = midpoint - 1
217 else:
217 else:
218 first = midpoint + 1
218 first = midpoint + 1
219 return -1
219 return -1
220
220
221 def bsearch2(self, key):
221 def bsearch2(self, key):
222 # same as the above, but will always return the position
222 # same as the above, but will always return the position
223 # done for performance reasons
223 # done for performance reasons
224 first = 0
224 first = 0
225 last = len(self.positions) - 1
225 last = len(self.positions) - 1
226
226
227 while first <= last:
227 while first <= last:
228 midpoint = (first + last)//2
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
231 r = _cmp(key, candidate)
232 if r == 0:
232 if r == 0:
233 return (midpoint, True)
233 return (midpoint, True)
234 else:
234 else:
235 if r < 0:
235 if r < 0:
236 last = midpoint - 1
236 last = midpoint - 1
237 else:
237 else:
238 first = midpoint + 1
238 first = midpoint + 1
239 return (first, False)
239 return (first, False)
240
240
241 def __contains__(self, key):
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
242 return self.bsearch(key) != -1
243
243
244 def _getflags(self, data, needle, pos):
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
245 start = pos + 41
246 end = data.find("\n", start)
246 end = data.find("\n", start)
247 if end == -1:
247 if end == -1:
248 end = len(data) - 1
248 end = len(data) - 1
249 if start == end:
249 if start == end:
250 return ''
250 return ''
251 return self.data[start:end]
251 return self.data[start:end]
252
252
253 def __getitem__(self, key):
253 def __getitem__(self, key):
254 if not isinstance(key, str):
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
256 needle = self.bsearch(key)
257 if needle == -1:
257 if needle == -1:
258 raise KeyError
258 raise KeyError
259 data, pos = self._get(needle)
259 data, pos = self._get(needle)
260 if pos == -1:
260 if pos == -1:
261 return (data[1], data[2])
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
267 return (hashval, flags)
268
268
269 def __delitem__(self, key):
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
270 needle, found = self.bsearch2(key)
271 if not found:
271 if not found:
272 raise KeyError
272 raise KeyError
273 cur = self.positions[needle]
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
278
279 def __setitem__(self, key, value):
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
287 flags = value[1]
288 if len(hashval) == 22:
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
292 needle, found = self.bsearch2(key)
293 if found:
293 if found:
294 # put the item
294 # put the item
295 pos = self.positions[needle]
295 pos = self.positions[needle]
296 if pos < 0:
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
298 else:
299 # just don't bother
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
301 self.positions[needle] = -len(self.extradata)
302 else:
302 else:
303 # not found, put it in with extra positions
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
308 self.extrainfo[needle:])
309
309
310 def copy(self):
310 def copy(self):
311 # XXX call _compact like in C?
311 # XXX call _compact like in C?
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 self.extradata)
313 self.extradata)
314
314
315 def _compact(self):
315 def _compact(self):
316 # hopefully not called TOO often
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
317 if len(self.extradata) == 0:
318 return
318 return
319 l = []
319 l = []
320 last_cut = 0
320 last_cut = 0
321 i = 0
321 i = 0
322 offset = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
326 cur = self.positions[i]
327 last_cut = cur
327 last_cut = cur
328 while True:
328 while True:
329 self.positions[i] = offset
329 self.positions[i] = offset
330 i += 1
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
332 break
333 offset += self.positions[i] - cur
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
336 if end_cut != -1:
337 end_cut += 1
337 end_cut += 1
338 offset += end_cut - cur
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
339 l.append(self.data[last_cut:end_cut])
340 else:
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
344 l.append(self._pack(t))
345 self.positions[i] = offset
345 self.positions[i] = offset
346 if len(t[1]) > 20:
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
348 offset += len(l[-1])
349 i += 1
349 i += 1
350 self.data = ''.join(l)
350 self.data = ''.join(l)
351 self.extradata = []
351 self.extradata = []
352
352
353 def _pack(self, d):
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
355
356 def text(self):
356 def text(self):
357 self._compact()
357 self._compact()
358 return self.data
358 return self.data
359
359
360 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
361 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
362 # XXX think whether efficiency matters here
363 diff = {}
363 diff = {}
364
364
365 for fn, e1, flags in self.iterentries():
365 for fn, e1, flags in self.iterentries():
366 if fn not in m2:
366 if fn not in m2:
367 diff[fn] = (e1, flags), (None, '')
367 diff[fn] = (e1, flags), (None, '')
368 else:
368 else:
369 e2 = m2[fn]
369 e2 = m2[fn]
370 if (e1, flags) != e2:
370 if (e1, flags) != e2:
371 diff[fn] = (e1, flags), e2
371 diff[fn] = (e1, flags), e2
372 elif clean:
372 elif clean:
373 diff[fn] = None
373 diff[fn] = None
374
374
375 for fn, e2, flags in m2.iterentries():
375 for fn, e2, flags in m2.iterentries():
376 if fn not in self:
376 if fn not in self:
377 diff[fn] = (None, ''), (e2, flags)
377 diff[fn] = (None, ''), (e2, flags)
378
378
379 return diff
379 return diff
380
380
381 def iterentries(self):
381 def iterentries(self):
382 return lazymanifestiterentries(self)
382 return lazymanifestiterentries(self)
383
383
384 def iterkeys(self):
384 def iterkeys(self):
385 return lazymanifestiter(self)
385 return lazymanifestiter(self)
386
386
387 def __iter__(self):
387 def __iter__(self):
388 return lazymanifestiter(self)
388 return lazymanifestiter(self)
389
389
390 def __len__(self):
390 def __len__(self):
391 return len(self.positions)
391 return len(self.positions)
392
392
393 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
394 # XXX should be optimized
395 c = _lazymanifest('')
395 c = _lazymanifest('')
396 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
397 if filterfn(f):
397 if filterfn(f):
398 c[f] = n, fl
398 c[f] = n, fl
399 return c
399 return c
400
400
401 try:
401 try:
402 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
403 except AttributeError:
403 except AttributeError:
404 pass
404 pass
405
405
406 class manifestdict(object):
406 class manifestdict(object):
407 def __init__(self, data=''):
407 def __init__(self, data=''):
408 if data.startswith('\0'):
408 if data.startswith('\0'):
409 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
410 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
411 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
412 self._lm[f] = n, fl
412 self._lm[f] = n, fl
413 else:
413 else:
414 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
415
415
416 def __getitem__(self, key):
416 def __getitem__(self, key):
417 return self._lm[key][0]
417 return self._lm[key][0]
418
418
419 def find(self, key):
419 def find(self, key):
420 return self._lm[key]
420 return self._lm[key]
421
421
422 def __len__(self):
422 def __len__(self):
423 return len(self._lm)
423 return len(self._lm)
424
424
425 def __setitem__(self, key, node):
425 def __setitem__(self, key, node):
426 self._lm[key] = node, self.flags(key, '')
426 self._lm[key] = node, self.flags(key, '')
427
427
428 def __contains__(self, key):
428 def __contains__(self, key):
429 return key in self._lm
429 return key in self._lm
430
430
431 def __delitem__(self, key):
431 def __delitem__(self, key):
432 del self._lm[key]
432 del self._lm[key]
433
433
434 def __iter__(self):
434 def __iter__(self):
435 return self._lm.__iter__()
435 return self._lm.__iter__()
436
436
437 def iterkeys(self):
437 def iterkeys(self):
438 return self._lm.iterkeys()
438 return self._lm.iterkeys()
439
439
440 def keys(self):
440 def keys(self):
441 return list(self.iterkeys())
441 return list(self.iterkeys())
442
442
443 def filesnotin(self, m2):
443 def filesnotin(self, m2):
444 '''Set of files in this manifest that are not in the other'''
444 '''Set of files in this manifest that are not in the other'''
445 diff = self.diff(m2)
445 diff = self.diff(m2)
446 files = set(filepath
446 files = set(filepath
447 for filepath, hashflags in diff.iteritems()
447 for filepath, hashflags in diff.iteritems()
448 if hashflags[1][0] is None)
448 if hashflags[1][0] is None)
449 return files
449 return files
450
450
451 @propertycache
451 @propertycache
452 def _dirs(self):
452 def _dirs(self):
453 return util.dirs(self)
453 return util.dirs(self)
454
454
455 def dirs(self):
455 def dirs(self):
456 return self._dirs
456 return self._dirs
457
457
458 def hasdir(self, dir):
458 def hasdir(self, dir):
459 return dir in self._dirs
459 return dir in self._dirs
460
460
461 def _filesfastpath(self, match):
461 def _filesfastpath(self, match):
462 '''Checks whether we can correctly and quickly iterate over matcher
462 '''Checks whether we can correctly and quickly iterate over matcher
463 files instead of over manifest files.'''
463 files instead of over manifest files.'''
464 files = match.files()
464 files = match.files()
465 return (len(files) < 100 and (match.isexact() or
465 return (len(files) < 100 and (match.isexact() or
466 (match.prefix() and all(fn in self for fn in files))))
466 (match.prefix() and all(fn in self for fn in files))))
467
467
468 def walk(self, match):
468 def walk(self, match):
469 '''Generates matching file names.
469 '''Generates matching file names.
470
470
471 Equivalent to manifest.matches(match).iterkeys(), but without creating
471 Equivalent to manifest.matches(match).iterkeys(), but without creating
472 an entirely new manifest.
472 an entirely new manifest.
473
473
474 It also reports nonexistent files by marking them bad with match.bad().
474 It also reports nonexistent files by marking them bad with match.bad().
475 '''
475 '''
476 if match.always():
476 if match.always():
477 for f in iter(self):
477 for f in iter(self):
478 yield f
478 yield f
479 return
479 return
480
480
481 fset = set(match.files())
481 fset = set(match.files())
482
482
483 # avoid the entire walk if we're only looking for specific files
483 # avoid the entire walk if we're only looking for specific files
484 if self._filesfastpath(match):
484 if self._filesfastpath(match):
485 for fn in sorted(fset):
485 for fn in sorted(fset):
486 yield fn
486 yield fn
487 return
487 return
488
488
489 for fn in self:
489 for fn in self:
490 if fn in fset:
490 if fn in fset:
491 # specified pattern is the exact name
491 # specified pattern is the exact name
492 fset.remove(fn)
492 fset.remove(fn)
493 if match(fn):
493 if match(fn):
494 yield fn
494 yield fn
495
495
496 # for dirstate.walk, files=['.'] means "walk the whole tree".
496 # for dirstate.walk, files=['.'] means "walk the whole tree".
497 # follow that here, too
497 # follow that here, too
498 fset.discard('.')
498 fset.discard('.')
499
499
500 for fn in sorted(fset):
500 for fn in sorted(fset):
501 if not self.hasdir(fn):
501 if not self.hasdir(fn):
502 match.bad(fn, None)
502 match.bad(fn, None)
503
503
504 def matches(self, match):
504 def matches(self, match):
505 '''generate a new manifest filtered by the match argument'''
505 '''generate a new manifest filtered by the match argument'''
506 if match.always():
506 if match.always():
507 return self.copy()
507 return self.copy()
508
508
509 if self._filesfastpath(match):
509 if self._filesfastpath(match):
510 m = manifestdict()
510 m = manifestdict()
511 lm = self._lm
511 lm = self._lm
512 for fn in match.files():
512 for fn in match.files():
513 if fn in lm:
513 if fn in lm:
514 m._lm[fn] = lm[fn]
514 m._lm[fn] = lm[fn]
515 return m
515 return m
516
516
517 m = manifestdict()
517 m = manifestdict()
518 m._lm = self._lm.filtercopy(match)
518 m._lm = self._lm.filtercopy(match)
519 return m
519 return m
520
520
521 def diff(self, m2, clean=False):
521 def diff(self, m2, clean=False):
522 '''Finds changes between the current manifest and m2.
522 '''Finds changes between the current manifest and m2.
523
523
524 Args:
524 Args:
525 m2: the manifest to which this manifest should be compared.
525 m2: the manifest to which this manifest should be compared.
526 clean: if true, include files unchanged between these manifests
526 clean: if true, include files unchanged between these manifests
527 with a None value in the returned dictionary.
527 with a None value in the returned dictionary.
528
528
529 The result is returned as a dict with filename as key and
529 The result is returned as a dict with filename as key and
530 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
530 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
531 nodeid in the current/other manifest and fl1/fl2 is the flag
531 nodeid in the current/other manifest and fl1/fl2 is the flag
532 in the current/other manifest. Where the file does not exist,
532 in the current/other manifest. Where the file does not exist,
533 the nodeid will be None and the flags will be the empty
533 the nodeid will be None and the flags will be the empty
534 string.
534 string.
535 '''
535 '''
536 return self._lm.diff(m2._lm, clean)
536 return self._lm.diff(m2._lm, clean)
537
537
538 def setflag(self, key, flag):
538 def setflag(self, key, flag):
539 self._lm[key] = self[key], flag
539 self._lm[key] = self[key], flag
540
540
541 def get(self, key, default=None):
541 def get(self, key, default=None):
542 try:
542 try:
543 return self._lm[key][0]
543 return self._lm[key][0]
544 except KeyError:
544 except KeyError:
545 return default
545 return default
546
546
547 def flags(self, key, default=''):
547 def flags(self, key, default=''):
548 try:
548 try:
549 return self._lm[key][1]
549 return self._lm[key][1]
550 except KeyError:
550 except KeyError:
551 return default
551 return default
552
552
553 def copy(self):
553 def copy(self):
554 c = manifestdict()
554 c = manifestdict()
555 c._lm = self._lm.copy()
555 c._lm = self._lm.copy()
556 return c
556 return c
557
557
558 def iteritems(self):
558 def iteritems(self):
559 return (x[:2] for x in self._lm.iterentries())
559 return (x[:2] for x in self._lm.iterentries())
560
560
561 def iterentries(self):
561 def iterentries(self):
562 return self._lm.iterentries()
562 return self._lm.iterentries()
563
563
564 def text(self, usemanifestv2=False):
564 def text(self, usemanifestv2=False):
565 if usemanifestv2:
565 if usemanifestv2:
566 return _textv2(self._lm.iterentries())
566 return _textv2(self._lm.iterentries())
567 else:
567 else:
568 # use (probably) native version for v1
568 # use (probably) native version for v1
569 return self._lm.text()
569 return self._lm.text()
570
570
571 def fastdelta(self, base, changes):
571 def fastdelta(self, base, changes):
572 """Given a base manifest text as an array.array and a list of changes
572 """Given a base manifest text as an array.array and a list of changes
573 relative to that text, compute a delta that can be used by revlog.
573 relative to that text, compute a delta that can be used by revlog.
574 """
574 """
575 delta = []
575 delta = []
576 dstart = None
576 dstart = None
577 dend = None
577 dend = None
578 dline = [""]
578 dline = [""]
579 start = 0
579 start = 0
580 # zero copy representation of base as a buffer
580 # zero copy representation of base as a buffer
581 addbuf = util.buffer(base)
581 addbuf = util.buffer(base)
582
582
583 changes = list(changes)
583 changes = list(changes)
584 if len(changes) < 1000:
584 if len(changes) < 1000:
585 # start with a readonly loop that finds the offset of
585 # start with a readonly loop that finds the offset of
586 # each line and creates the deltas
586 # each line and creates the deltas
587 for f, todelete in changes:
587 for f, todelete in changes:
588 # bs will either be the index of the item or the insert point
588 # bs will either be the index of the item or the insert point
589 start, end = _msearch(addbuf, f, start)
589 start, end = _msearch(addbuf, f, start)
590 if not todelete:
590 if not todelete:
591 h, fl = self._lm[f]
591 h, fl = self._lm[f]
592 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
592 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
593 else:
593 else:
594 if start == end:
594 if start == end:
595 # item we want to delete was not found, error out
595 # item we want to delete was not found, error out
596 raise AssertionError(
596 raise AssertionError(
597 _("failed to remove %s from manifest") % f)
597 _("failed to remove %s from manifest") % f)
598 l = ""
598 l = ""
599 if dstart is not None and dstart <= start and dend >= start:
599 if dstart is not None and dstart <= start and dend >= start:
600 if dend < end:
600 if dend < end:
601 dend = end
601 dend = end
602 if l:
602 if l:
603 dline.append(l)
603 dline.append(l)
604 else:
604 else:
605 if dstart is not None:
605 if dstart is not None:
606 delta.append([dstart, dend, "".join(dline)])
606 delta.append([dstart, dend, "".join(dline)])
607 dstart = start
607 dstart = start
608 dend = end
608 dend = end
609 dline = [l]
609 dline = [l]
610
610
611 if dstart is not None:
611 if dstart is not None:
612 delta.append([dstart, dend, "".join(dline)])
612 delta.append([dstart, dend, "".join(dline)])
613 # apply the delta to the base, and get a delta for addrevision
613 # apply the delta to the base, and get a delta for addrevision
614 deltatext, arraytext = _addlistdelta(base, delta)
614 deltatext, arraytext = _addlistdelta(base, delta)
615 else:
615 else:
616 # For large changes, it's much cheaper to just build the text and
616 # For large changes, it's much cheaper to just build the text and
617 # diff it.
617 # diff it.
618 arraytext = array.array('c', self.text())
618 arraytext = array.array('c', self.text())
619 deltatext = mdiff.textdiff(base, arraytext)
619 deltatext = mdiff.textdiff(base, arraytext)
620
620
621 return arraytext, deltatext
621 return arraytext, deltatext
622
622
623 def _msearch(m, s, lo=0, hi=None):
623 def _msearch(m, s, lo=0, hi=None):
624 '''return a tuple (start, end) that says where to find s within m.
624 '''return a tuple (start, end) that says where to find s within m.
625
625
626 If the string is found m[start:end] are the line containing
626 If the string is found m[start:end] are the line containing
627 that string. If start == end the string was not found and
627 that string. If start == end the string was not found and
628 they indicate the proper sorted insertion point.
628 they indicate the proper sorted insertion point.
629
629
630 m should be a buffer or a string
630 m should be a buffer or a string
631 s is a string'''
631 s is a string'''
632 def advance(i, c):
632 def advance(i, c):
633 while i < lenm and m[i] != c:
633 while i < lenm and m[i] != c:
634 i += 1
634 i += 1
635 return i
635 return i
636 if not s:
636 if not s:
637 return (lo, lo)
637 return (lo, lo)
638 lenm = len(m)
638 lenm = len(m)
639 if not hi:
639 if not hi:
640 hi = lenm
640 hi = lenm
641 while lo < hi:
641 while lo < hi:
642 mid = (lo + hi) // 2
642 mid = (lo + hi) // 2
643 start = mid
643 start = mid
644 while start > 0 and m[start - 1] != '\n':
644 while start > 0 and m[start - 1] != '\n':
645 start -= 1
645 start -= 1
646 end = advance(start, '\0')
646 end = advance(start, '\0')
647 if m[start:end] < s:
647 if m[start:end] < s:
648 # we know that after the null there are 40 bytes of sha1
648 # we know that after the null there are 40 bytes of sha1
649 # this translates to the bisect lo = mid + 1
649 # this translates to the bisect lo = mid + 1
650 lo = advance(end + 40, '\n') + 1
650 lo = advance(end + 40, '\n') + 1
651 else:
651 else:
652 # this translates to the bisect hi = mid
652 # this translates to the bisect hi = mid
653 hi = start
653 hi = start
654 end = advance(lo, '\0')
654 end = advance(lo, '\0')
655 found = m[lo:end]
655 found = m[lo:end]
656 if s == found:
656 if s == found:
657 # we know that after the null there are 40 bytes of sha1
657 # we know that after the null there are 40 bytes of sha1
658 end = advance(end + 40, '\n')
658 end = advance(end + 40, '\n')
659 return (lo, end + 1)
659 return (lo, end + 1)
660 else:
660 else:
661 return (lo, lo)
661 return (lo, lo)
662
662
663 def _checkforbidden(l):
663 def _checkforbidden(l):
664 """Check filenames for illegal characters."""
664 """Check filenames for illegal characters."""
665 for f in l:
665 for f in l:
666 if '\n' in f or '\r' in f:
666 if '\n' in f or '\r' in f:
667 raise error.RevlogError(
667 raise error.RevlogError(
668 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
668 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
669
669
670
670
671 # apply the changes collected during the bisect loop to our addlist
671 # apply the changes collected during the bisect loop to our addlist
672 # return a delta suitable for addrevision
672 # return a delta suitable for addrevision
673 def _addlistdelta(addlist, x):
673 def _addlistdelta(addlist, x):
674 # for large addlist arrays, building a new array is cheaper
674 # for large addlist arrays, building a new array is cheaper
675 # than repeatedly modifying the existing one
675 # than repeatedly modifying the existing one
676 currentposition = 0
676 currentposition = 0
677 newaddlist = array.array('c')
677 newaddlist = array.array('c')
678
678
679 for start, end, content in x:
679 for start, end, content in x:
680 newaddlist += addlist[currentposition:start]
680 newaddlist += addlist[currentposition:start]
681 if content:
681 if content:
682 newaddlist += array.array('c', content)
682 newaddlist += array.array('c', content)
683
683
684 currentposition = end
684 currentposition = end
685
685
686 newaddlist += addlist[currentposition:]
686 newaddlist += addlist[currentposition:]
687
687
688 deltatext = "".join(struct.pack(">lll", start, end, len(content))
688 deltatext = "".join(struct.pack(">lll", start, end, len(content))
689 + content for start, end, content in x)
689 + content for start, end, content in x)
690 return deltatext, newaddlist
690 return deltatext, newaddlist
691
691
692 def _splittopdir(f):
692 def _splittopdir(f):
693 if '/' in f:
693 if '/' in f:
694 dir, subpath = f.split('/', 1)
694 dir, subpath = f.split('/', 1)
695 return dir + '/', subpath
695 return dir + '/', subpath
696 else:
696 else:
697 return '', f
697 return '', f
698
698
699 _noop = lambda s: None
699 _noop = lambda s: None
700
700
701 class treemanifest(object):
701 class treemanifest(object):
702 def __init__(self, dir='', text=''):
702 def __init__(self, dir='', text=''):
703 self._dir = dir
703 self._dir = dir
704 self._node = revlog.nullid
704 self._node = revlog.nullid
705 self._loadfunc = _noop
705 self._loadfunc = _noop
706 self._copyfunc = _noop
706 self._copyfunc = _noop
707 self._dirty = False
707 self._dirty = False
708 self._dirs = {}
708 self._dirs = {}
709 # Using _lazymanifest here is a little slower than plain old dicts
709 # Using _lazymanifest here is a little slower than plain old dicts
710 self._files = {}
710 self._files = {}
711 self._flags = {}
711 self._flags = {}
712 if text:
712 if text:
713 def readsubtree(subdir, subm):
713 def readsubtree(subdir, subm):
714 raise AssertionError('treemanifest constructor only accepts '
714 raise AssertionError('treemanifest constructor only accepts '
715 'flat manifests')
715 'flat manifests')
716 self.parse(text, readsubtree)
716 self.parse(text, readsubtree)
717 self._dirty = True # Mark flat manifest dirty after parsing
717 self._dirty = True # Mark flat manifest dirty after parsing
718
718
719 def _subpath(self, path):
719 def _subpath(self, path):
720 return self._dir + path
720 return self._dir + path
721
721
722 def __len__(self):
722 def __len__(self):
723 self._load()
723 self._load()
724 size = len(self._files)
724 size = len(self._files)
725 for m in self._dirs.values():
725 for m in self._dirs.values():
726 size += m.__len__()
726 size += m.__len__()
727 return size
727 return size
728
728
729 def _isempty(self):
729 def _isempty(self):
730 self._load() # for consistency; already loaded by all callers
730 self._load() # for consistency; already loaded by all callers
731 return (not self._files and (not self._dirs or
731 return (not self._files and (not self._dirs or
732 all(m._isempty() for m in self._dirs.values())))
732 all(m._isempty() for m in self._dirs.values())))
733
733
734 def __repr__(self):
734 def __repr__(self):
735 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
735 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
736 (self._dir, revlog.hex(self._node),
736 (self._dir, revlog.hex(self._node),
737 bool(self._loadfunc is _noop),
737 bool(self._loadfunc is _noop),
738 self._dirty, id(self)))
738 self._dirty, id(self)))
739
739
740 def dir(self):
740 def dir(self):
741 '''The directory that this tree manifest represents, including a
741 '''The directory that this tree manifest represents, including a
742 trailing '/'. Empty string for the repo root directory.'''
742 trailing '/'. Empty string for the repo root directory.'''
743 return self._dir
743 return self._dir
744
744
745 def node(self):
745 def node(self):
746 '''This node of this instance. nullid for unsaved instances. Should
746 '''This node of this instance. nullid for unsaved instances. Should
747 be updated when the instance is read or written from a revlog.
747 be updated when the instance is read or written from a revlog.
748 '''
748 '''
749 assert not self._dirty
749 assert not self._dirty
750 return self._node
750 return self._node
751
751
752 def setnode(self, node):
752 def setnode(self, node):
753 self._node = node
753 self._node = node
754 self._dirty = False
754 self._dirty = False
755
755
756 def iterentries(self):
756 def iterentries(self):
757 self._load()
757 self._load()
758 for p, n in sorted(self._dirs.items() + self._files.items()):
758 for p, n in sorted(self._dirs.items() + self._files.items()):
759 if p in self._files:
759 if p in self._files:
760 yield self._subpath(p), n, self._flags.get(p, '')
760 yield self._subpath(p), n, self._flags.get(p, '')
761 else:
761 else:
762 for x in n.iterentries():
762 for x in n.iterentries():
763 yield x
763 yield x
764
764
765 def iteritems(self):
765 def iteritems(self):
766 self._load()
766 self._load()
767 for p, n in sorted(self._dirs.items() + self._files.items()):
767 for p, n in sorted(self._dirs.items() + self._files.items()):
768 if p in self._files:
768 if p in self._files:
769 yield self._subpath(p), n
769 yield self._subpath(p), n
770 else:
770 else:
771 for f, sn in n.iteritems():
771 for f, sn in n.iteritems():
772 yield f, sn
772 yield f, sn
773
773
774 def iterkeys(self):
774 def iterkeys(self):
775 self._load()
775 self._load()
776 for p in sorted(self._dirs.keys() + self._files.keys()):
776 for p in sorted(self._dirs.keys() + self._files.keys()):
777 if p in self._files:
777 if p in self._files:
778 yield self._subpath(p)
778 yield self._subpath(p)
779 else:
779 else:
780 for f in self._dirs[p].iterkeys():
780 for f in self._dirs[p].iterkeys():
781 yield f
781 yield f
782
782
783 def keys(self):
783 def keys(self):
784 return list(self.iterkeys())
784 return list(self.iterkeys())
785
785
786 def __iter__(self):
786 def __iter__(self):
787 return self.iterkeys()
787 return self.iterkeys()
788
788
789 def __contains__(self, f):
789 def __contains__(self, f):
790 if f is None:
790 if f is None:
791 return False
791 return False
792 self._load()
792 self._load()
793 dir, subpath = _splittopdir(f)
793 dir, subpath = _splittopdir(f)
794 if dir:
794 if dir:
795 if dir not in self._dirs:
795 if dir not in self._dirs:
796 return False
796 return False
797 return self._dirs[dir].__contains__(subpath)
797 return self._dirs[dir].__contains__(subpath)
798 else:
798 else:
799 return f in self._files
799 return f in self._files
800
800
801 def get(self, f, default=None):
801 def get(self, f, default=None):
802 self._load()
802 self._load()
803 dir, subpath = _splittopdir(f)
803 dir, subpath = _splittopdir(f)
804 if dir:
804 if dir:
805 if dir not in self._dirs:
805 if dir not in self._dirs:
806 return default
806 return default
807 return self._dirs[dir].get(subpath, default)
807 return self._dirs[dir].get(subpath, default)
808 else:
808 else:
809 return self._files.get(f, default)
809 return self._files.get(f, default)
810
810
811 def __getitem__(self, f):
811 def __getitem__(self, f):
812 self._load()
812 self._load()
813 dir, subpath = _splittopdir(f)
813 dir, subpath = _splittopdir(f)
814 if dir:
814 if dir:
815 return self._dirs[dir].__getitem__(subpath)
815 return self._dirs[dir].__getitem__(subpath)
816 else:
816 else:
817 return self._files[f]
817 return self._files[f]
818
818
819 def flags(self, f):
819 def flags(self, f):
820 self._load()
820 self._load()
821 dir, subpath = _splittopdir(f)
821 dir, subpath = _splittopdir(f)
822 if dir:
822 if dir:
823 if dir not in self._dirs:
823 if dir not in self._dirs:
824 return ''
824 return ''
825 return self._dirs[dir].flags(subpath)
825 return self._dirs[dir].flags(subpath)
826 else:
826 else:
827 if f in self._dirs:
827 if f in self._dirs:
828 return ''
828 return ''
829 return self._flags.get(f, '')
829 return self._flags.get(f, '')
830
830
831 def find(self, f):
831 def find(self, f):
832 self._load()
832 self._load()
833 dir, subpath = _splittopdir(f)
833 dir, subpath = _splittopdir(f)
834 if dir:
834 if dir:
835 return self._dirs[dir].find(subpath)
835 return self._dirs[dir].find(subpath)
836 else:
836 else:
837 return self._files[f], self._flags.get(f, '')
837 return self._files[f], self._flags.get(f, '')
838
838
839 def __delitem__(self, f):
839 def __delitem__(self, f):
840 self._load()
840 self._load()
841 dir, subpath = _splittopdir(f)
841 dir, subpath = _splittopdir(f)
842 if dir:
842 if dir:
843 self._dirs[dir].__delitem__(subpath)
843 self._dirs[dir].__delitem__(subpath)
844 # If the directory is now empty, remove it
844 # If the directory is now empty, remove it
845 if self._dirs[dir]._isempty():
845 if self._dirs[dir]._isempty():
846 del self._dirs[dir]
846 del self._dirs[dir]
847 else:
847 else:
848 del self._files[f]
848 del self._files[f]
849 if f in self._flags:
849 if f in self._flags:
850 del self._flags[f]
850 del self._flags[f]
851 self._dirty = True
851 self._dirty = True
852
852
853 def __setitem__(self, f, n):
853 def __setitem__(self, f, n):
854 assert n is not None
854 assert n is not None
855 self._load()
855 self._load()
856 dir, subpath = _splittopdir(f)
856 dir, subpath = _splittopdir(f)
857 if dir:
857 if dir:
858 if dir not in self._dirs:
858 if dir not in self._dirs:
859 self._dirs[dir] = treemanifest(self._subpath(dir))
859 self._dirs[dir] = treemanifest(self._subpath(dir))
860 self._dirs[dir].__setitem__(subpath, n)
860 self._dirs[dir].__setitem__(subpath, n)
861 else:
861 else:
862 self._files[f] = n[:21] # to match manifestdict's behavior
862 self._files[f] = n[:21] # to match manifestdict's behavior
863 self._dirty = True
863 self._dirty = True
864
864
865 def _load(self):
865 def _load(self):
866 if self._loadfunc is not _noop:
866 if self._loadfunc is not _noop:
867 lf, self._loadfunc = self._loadfunc, _noop
867 lf, self._loadfunc = self._loadfunc, _noop
868 lf(self)
868 lf(self)
869 elif self._copyfunc is not _noop:
869 elif self._copyfunc is not _noop:
870 cf, self._copyfunc = self._copyfunc, _noop
870 cf, self._copyfunc = self._copyfunc, _noop
871 cf(self)
871 cf(self)
872
872
873 def setflag(self, f, flags):
873 def setflag(self, f, flags):
874 """Set the flags (symlink, executable) for path f."""
874 """Set the flags (symlink, executable) for path f."""
875 self._load()
875 self._load()
876 dir, subpath = _splittopdir(f)
876 dir, subpath = _splittopdir(f)
877 if dir:
877 if dir:
878 if dir not in self._dirs:
878 if dir not in self._dirs:
879 self._dirs[dir] = treemanifest(self._subpath(dir))
879 self._dirs[dir] = treemanifest(self._subpath(dir))
880 self._dirs[dir].setflag(subpath, flags)
880 self._dirs[dir].setflag(subpath, flags)
881 else:
881 else:
882 self._flags[f] = flags
882 self._flags[f] = flags
883 self._dirty = True
883 self._dirty = True
884
884
885 def copy(self):
885 def copy(self):
886 copy = treemanifest(self._dir)
886 copy = treemanifest(self._dir)
887 copy._node = self._node
887 copy._node = self._node
888 copy._dirty = self._dirty
888 copy._dirty = self._dirty
889 if self._copyfunc is _noop:
889 if self._copyfunc is _noop:
890 def _copyfunc(s):
890 def _copyfunc(s):
891 self._load()
891 self._load()
892 for d in self._dirs:
892 for d in self._dirs:
893 s._dirs[d] = self._dirs[d].copy()
893 s._dirs[d] = self._dirs[d].copy()
894 s._files = dict.copy(self._files)
894 s._files = dict.copy(self._files)
895 s._flags = dict.copy(self._flags)
895 s._flags = dict.copy(self._flags)
896 if self._loadfunc is _noop:
896 if self._loadfunc is _noop:
897 _copyfunc(copy)
897 _copyfunc(copy)
898 else:
898 else:
899 copy._copyfunc = _copyfunc
899 copy._copyfunc = _copyfunc
900 else:
900 else:
901 copy._copyfunc = self._copyfunc
901 copy._copyfunc = self._copyfunc
902 return copy
902 return copy
903
903
904 def filesnotin(self, m2):
904 def filesnotin(self, m2):
905 '''Set of files in this manifest that are not in the other'''
905 '''Set of files in this manifest that are not in the other'''
906 files = set()
906 files = set()
907 def _filesnotin(t1, t2):
907 def _filesnotin(t1, t2):
908 if t1._node == t2._node and not t1._dirty and not t2._dirty:
908 if t1._node == t2._node and not t1._dirty and not t2._dirty:
909 return
909 return
910 t1._load()
910 t1._load()
911 t2._load()
911 t2._load()
912 for d, m1 in t1._dirs.iteritems():
912 for d, m1 in t1._dirs.iteritems():
913 if d in t2._dirs:
913 if d in t2._dirs:
914 m2 = t2._dirs[d]
914 m2 = t2._dirs[d]
915 _filesnotin(m1, m2)
915 _filesnotin(m1, m2)
916 else:
916 else:
917 files.update(m1.iterkeys())
917 files.update(m1.iterkeys())
918
918
919 for fn in t1._files.iterkeys():
919 for fn in t1._files.iterkeys():
920 if fn not in t2._files:
920 if fn not in t2._files:
921 files.add(t1._subpath(fn))
921 files.add(t1._subpath(fn))
922
922
923 _filesnotin(self, m2)
923 _filesnotin(self, m2)
924 return files
924 return files
925
925
926 @propertycache
926 @propertycache
927 def _alldirs(self):
927 def _alldirs(self):
928 return util.dirs(self)
928 return util.dirs(self)
929
929
930 def dirs(self):
930 def dirs(self):
931 return self._alldirs
931 return self._alldirs
932
932
933 def hasdir(self, dir):
933 def hasdir(self, dir):
934 self._load()
934 self._load()
935 topdir, subdir = _splittopdir(dir)
935 topdir, subdir = _splittopdir(dir)
936 if topdir:
936 if topdir:
937 if topdir in self._dirs:
937 if topdir in self._dirs:
938 return self._dirs[topdir].hasdir(subdir)
938 return self._dirs[topdir].hasdir(subdir)
939 return False
939 return False
940 return (dir + '/') in self._dirs
940 return (dir + '/') in self._dirs
941
941
942 def walk(self, match):
942 def walk(self, match):
943 '''Generates matching file names.
943 '''Generates matching file names.
944
944
945 Equivalent to manifest.matches(match).iterkeys(), but without creating
945 Equivalent to manifest.matches(match).iterkeys(), but without creating
946 an entirely new manifest.
946 an entirely new manifest.
947
947
948 It also reports nonexistent files by marking them bad with match.bad().
948 It also reports nonexistent files by marking them bad with match.bad().
949 '''
949 '''
950 if match.always():
950 if match.always():
951 for f in iter(self):
951 for f in iter(self):
952 yield f
952 yield f
953 return
953 return
954
954
955 fset = set(match.files())
955 fset = set(match.files())
956
956
957 for fn in self._walk(match):
957 for fn in self._walk(match):
958 if fn in fset:
958 if fn in fset:
959 # specified pattern is the exact name
959 # specified pattern is the exact name
960 fset.remove(fn)
960 fset.remove(fn)
961 yield fn
961 yield fn
962
962
963 # for dirstate.walk, files=['.'] means "walk the whole tree".
963 # for dirstate.walk, files=['.'] means "walk the whole tree".
964 # follow that here, too
964 # follow that here, too
965 fset.discard('.')
965 fset.discard('.')
966
966
967 for fn in sorted(fset):
967 for fn in sorted(fset):
968 if not self.hasdir(fn):
968 if not self.hasdir(fn):
969 match.bad(fn, None)
969 match.bad(fn, None)
970
970
971 def _walk(self, match):
971 def _walk(self, match):
972 '''Recursively generates matching file names for walk().'''
972 '''Recursively generates matching file names for walk().'''
973 if not match.visitdir(self._dir[:-1] or '.'):
973 if not match.visitdir(self._dir[:-1] or '.'):
974 return
974 return
975
975
976 # yield this dir's files and walk its submanifests
976 # yield this dir's files and walk its submanifests
977 self._load()
977 self._load()
978 for p in sorted(self._dirs.keys() + self._files.keys()):
978 for p in sorted(self._dirs.keys() + self._files.keys()):
979 if p in self._files:
979 if p in self._files:
980 fullp = self._subpath(p)
980 fullp = self._subpath(p)
981 if match(fullp):
981 if match(fullp):
982 yield fullp
982 yield fullp
983 else:
983 else:
984 for f in self._dirs[p]._walk(match):
984 for f in self._dirs[p]._walk(match):
985 yield f
985 yield f
986
986
987 def matches(self, match):
987 def matches(self, match):
988 '''generate a new manifest filtered by the match argument'''
988 '''generate a new manifest filtered by the match argument'''
989 if match.always():
989 if match.always():
990 return self.copy()
990 return self.copy()
991
991
992 return self._matches(match)
992 return self._matches(match)
993
993
994 def _matches(self, match):
994 def _matches(self, match):
995 '''recursively generate a new manifest filtered by the match argument.
995 '''recursively generate a new manifest filtered by the match argument.
996 '''
996 '''
997
997
998 visit = match.visitdir(self._dir[:-1] or '.')
998 visit = match.visitdir(self._dir[:-1] or '.')
999 if visit == 'all':
999 if visit == 'all':
1000 return self.copy()
1000 return self.copy()
1001 ret = treemanifest(self._dir)
1001 ret = treemanifest(self._dir)
1002 if not visit:
1002 if not visit:
1003 return ret
1003 return ret
1004
1004
1005 self._load()
1005 self._load()
1006 for fn in self._files:
1006 for fn in self._files:
1007 fullp = self._subpath(fn)
1007 fullp = self._subpath(fn)
1008 if not match(fullp):
1008 if not match(fullp):
1009 continue
1009 continue
1010 ret._files[fn] = self._files[fn]
1010 ret._files[fn] = self._files[fn]
1011 if fn in self._flags:
1011 if fn in self._flags:
1012 ret._flags[fn] = self._flags[fn]
1012 ret._flags[fn] = self._flags[fn]
1013
1013
1014 for dir, subm in self._dirs.iteritems():
1014 for dir, subm in self._dirs.iteritems():
1015 m = subm._matches(match)
1015 m = subm._matches(match)
1016 if not m._isempty():
1016 if not m._isempty():
1017 ret._dirs[dir] = m
1017 ret._dirs[dir] = m
1018
1018
1019 if not ret._isempty():
1019 if not ret._isempty():
1020 ret._dirty = True
1020 ret._dirty = True
1021 return ret
1021 return ret
1022
1022
1023 def diff(self, m2, clean=False):
1023 def diff(self, m2, clean=False):
1024 '''Finds changes between the current manifest and m2.
1024 '''Finds changes between the current manifest and m2.
1025
1025
1026 Args:
1026 Args:
1027 m2: the manifest to which this manifest should be compared.
1027 m2: the manifest to which this manifest should be compared.
1028 clean: if true, include files unchanged between these manifests
1028 clean: if true, include files unchanged between these manifests
1029 with a None value in the returned dictionary.
1029 with a None value in the returned dictionary.
1030
1030
1031 The result is returned as a dict with filename as key and
1031 The result is returned as a dict with filename as key and
1032 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1032 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1033 nodeid in the current/other manifest and fl1/fl2 is the flag
1033 nodeid in the current/other manifest and fl1/fl2 is the flag
1034 in the current/other manifest. Where the file does not exist,
1034 in the current/other manifest. Where the file does not exist,
1035 the nodeid will be None and the flags will be the empty
1035 the nodeid will be None and the flags will be the empty
1036 string.
1036 string.
1037 '''
1037 '''
1038 result = {}
1038 result = {}
1039 emptytree = treemanifest()
1039 emptytree = treemanifest()
1040 def _diff(t1, t2):
1040 def _diff(t1, t2):
1041 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1041 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1042 return
1042 return
1043 t1._load()
1043 t1._load()
1044 t2._load()
1044 t2._load()
1045 for d, m1 in t1._dirs.iteritems():
1045 for d, m1 in t1._dirs.iteritems():
1046 m2 = t2._dirs.get(d, emptytree)
1046 m2 = t2._dirs.get(d, emptytree)
1047 _diff(m1, m2)
1047 _diff(m1, m2)
1048
1048
1049 for d, m2 in t2._dirs.iteritems():
1049 for d, m2 in t2._dirs.iteritems():
1050 if d not in t1._dirs:
1050 if d not in t1._dirs:
1051 _diff(emptytree, m2)
1051 _diff(emptytree, m2)
1052
1052
1053 for fn, n1 in t1._files.iteritems():
1053 for fn, n1 in t1._files.iteritems():
1054 fl1 = t1._flags.get(fn, '')
1054 fl1 = t1._flags.get(fn, '')
1055 n2 = t2._files.get(fn, None)
1055 n2 = t2._files.get(fn, None)
1056 fl2 = t2._flags.get(fn, '')
1056 fl2 = t2._flags.get(fn, '')
1057 if n1 != n2 or fl1 != fl2:
1057 if n1 != n2 or fl1 != fl2:
1058 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1058 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1059 elif clean:
1059 elif clean:
1060 result[t1._subpath(fn)] = None
1060 result[t1._subpath(fn)] = None
1061
1061
1062 for fn, n2 in t2._files.iteritems():
1062 for fn, n2 in t2._files.iteritems():
1063 if fn not in t1._files:
1063 if fn not in t1._files:
1064 fl2 = t2._flags.get(fn, '')
1064 fl2 = t2._flags.get(fn, '')
1065 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1065 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1066
1066
1067 _diff(self, m2)
1067 _diff(self, m2)
1068 return result
1068 return result
1069
1069
1070 def unmodifiedsince(self, m2):
1070 def unmodifiedsince(self, m2):
1071 return not self._dirty and not m2._dirty and self._node == m2._node
1071 return not self._dirty and not m2._dirty and self._node == m2._node
1072
1072
1073 def parse(self, text, readsubtree):
1073 def parse(self, text, readsubtree):
1074 for f, n, fl in _parse(text):
1074 for f, n, fl in _parse(text):
1075 if fl == 't':
1075 if fl == 't':
1076 f = f + '/'
1076 f = f + '/'
1077 self._dirs[f] = readsubtree(self._subpath(f), n)
1077 self._dirs[f] = readsubtree(self._subpath(f), n)
1078 elif '/' in f:
1078 elif '/' in f:
1079 # This is a flat manifest, so use __setitem__ and setflag rather
1079 # This is a flat manifest, so use __setitem__ and setflag rather
1080 # than assigning directly to _files and _flags, so we can
1080 # than assigning directly to _files and _flags, so we can
1081 # assign a path in a subdirectory, and to mark dirty (compared
1081 # assign a path in a subdirectory, and to mark dirty (compared
1082 # to nullid).
1082 # to nullid).
1083 self[f] = n
1083 self[f] = n
1084 if fl:
1084 if fl:
1085 self.setflag(f, fl)
1085 self.setflag(f, fl)
1086 else:
1086 else:
1087 # Assigning to _files and _flags avoids marking as dirty,
1087 # Assigning to _files and _flags avoids marking as dirty,
1088 # and should be a little faster.
1088 # and should be a little faster.
1089 self._files[f] = n
1089 self._files[f] = n
1090 if fl:
1090 if fl:
1091 self._flags[f] = fl
1091 self._flags[f] = fl
1092
1092
1093 def text(self, usemanifestv2=False):
1093 def text(self, usemanifestv2=False):
1094 """Get the full data of this manifest as a bytestring."""
1094 """Get the full data of this manifest as a bytestring."""
1095 self._load()
1095 self._load()
1096 return _text(self.iterentries(), usemanifestv2)
1096 return _text(self.iterentries(), usemanifestv2)
1097
1097
1098 def dirtext(self, usemanifestv2=False):
1098 def dirtext(self, usemanifestv2=False):
1099 """Get the full data of this directory as a bytestring. Make sure that
1099 """Get the full data of this directory as a bytestring. Make sure that
1100 any submanifests have been written first, so their nodeids are correct.
1100 any submanifests have been written first, so their nodeids are correct.
1101 """
1101 """
1102 self._load()
1102 self._load()
1103 flags = self.flags
1103 flags = self.flags
1104 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1104 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1105 files = [(f, self._files[f], flags(f)) for f in self._files]
1105 files = [(f, self._files[f], flags(f)) for f in self._files]
1106 return _text(sorted(dirs + files), usemanifestv2)
1106 return _text(sorted(dirs + files), usemanifestv2)
1107
1107
1108 def read(self, gettext, readsubtree):
1108 def read(self, gettext, readsubtree):
1109 def _load_for_read(s):
1109 def _load_for_read(s):
1110 s.parse(gettext(), readsubtree)
1110 s.parse(gettext(), readsubtree)
1111 s._dirty = False
1111 s._dirty = False
1112 self._loadfunc = _load_for_read
1112 self._loadfunc = _load_for_read
1113
1113
1114 def writesubtrees(self, m1, m2, writesubtree):
1114 def writesubtrees(self, m1, m2, writesubtree):
1115 self._load() # for consistency; should never have any effect here
1115 self._load() # for consistency; should never have any effect here
1116 m1._load()
1116 m1._load()
1117 m2._load()
1117 m2._load()
1118 emptytree = treemanifest()
1118 emptytree = treemanifest()
1119 for d, subm in self._dirs.iteritems():
1119 for d, subm in self._dirs.iteritems():
1120 subp1 = m1._dirs.get(d, emptytree)._node
1120 subp1 = m1._dirs.get(d, emptytree)._node
1121 subp2 = m2._dirs.get(d, emptytree)._node
1121 subp2 = m2._dirs.get(d, emptytree)._node
1122 if subp1 == revlog.nullid:
1122 if subp1 == revlog.nullid:
1123 subp1, subp2 = subp2, subp1
1123 subp1, subp2 = subp2, subp1
1124 writesubtree(subm, subp1, subp2)
1124 writesubtree(subm, subp1, subp2)
1125
1125
1126 class manifestrevlog(revlog.revlog):
1126 class manifestrevlog(revlog.revlog):
1127 '''A revlog that stores manifest texts. This is responsible for caching the
1127 '''A revlog that stores manifest texts. This is responsible for caching the
1128 full-text manifest contents.
1128 full-text manifest contents.
1129 '''
1129 '''
1130 def __init__(self, opener, dir='', dirlogcache=None):
1130 def __init__(self, opener, dir='', dirlogcache=None):
1131 # During normal operations, we expect to deal with not more than four
1131 # During normal operations, we expect to deal with not more than four
1132 # revs at a time (such as during commit --amend). When rebasing large
1132 # revs at a time (such as during commit --amend). When rebasing large
1133 # stacks of commits, the number can go up, hence the config knob below.
1133 # stacks of commits, the number can go up, hence the config knob below.
1134 cachesize = 4
1134 cachesize = 4
1135 usetreemanifest = False
1135 usetreemanifest = False
1136 usemanifestv2 = False
1136 usemanifestv2 = False
1137 opts = getattr(opener, 'options', None)
1137 opts = getattr(opener, 'options', None)
1138 if opts is not None:
1138 if opts is not None:
1139 cachesize = opts.get('manifestcachesize', cachesize)
1139 cachesize = opts.get('manifestcachesize', cachesize)
1140 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1140 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1141 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1141 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1142
1142
1143 self._treeondisk = usetreemanifest
1143 self._treeondisk = usetreemanifest
1144 self._usemanifestv2 = usemanifestv2
1144 self._usemanifestv2 = usemanifestv2
1145
1145
1146 self._fulltextcache = util.lrucachedict(cachesize)
1146 self._fulltextcache = util.lrucachedict(cachesize)
1147
1147
1148 indexfile = "00manifest.i"
1148 indexfile = "00manifest.i"
1149 if dir:
1149 if dir:
1150 assert self._treeondisk, 'opts is %r' % opts
1150 assert self._treeondisk, 'opts is %r' % opts
1151 if not dir.endswith('/'):
1151 if not dir.endswith('/'):
1152 dir = dir + '/'
1152 dir = dir + '/'
1153 indexfile = "meta/" + dir + "00manifest.i"
1153 indexfile = "meta/" + dir + "00manifest.i"
1154 self._dir = dir
1154 self._dir = dir
1155 # The dirlogcache is kept on the root manifest log
1155 # The dirlogcache is kept on the root manifest log
1156 if dir:
1156 if dir:
1157 self._dirlogcache = dirlogcache
1157 self._dirlogcache = dirlogcache
1158 else:
1158 else:
1159 self._dirlogcache = {'': self}
1159 self._dirlogcache = {'': self}
1160
1160
1161 super(manifestrevlog, self).__init__(opener, indexfile,
1161 super(manifestrevlog, self).__init__(opener, indexfile,
1162 checkambig=bool(dir))
1162 checkambig=bool(dir))
1163
1163
1164 @property
1164 @property
1165 def fulltextcache(self):
1165 def fulltextcache(self):
1166 return self._fulltextcache
1166 return self._fulltextcache
1167
1167
1168 def clearcaches(self):
1168 def clearcaches(self):
1169 super(manifestrevlog, self).clearcaches()
1169 super(manifestrevlog, self).clearcaches()
1170 self._fulltextcache.clear()
1170 self._fulltextcache.clear()
1171 self._dirlogcache = {'': self}
1171 self._dirlogcache = {'': self}
1172
1172
1173 def dirlog(self, dir):
1173 def dirlog(self, dir):
1174 if dir:
1174 if dir:
1175 assert self._treeondisk
1175 assert self._treeondisk
1176 if dir not in self._dirlogcache:
1176 if dir not in self._dirlogcache:
1177 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1177 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1178 self._dirlogcache)
1178 self._dirlogcache)
1179 return self._dirlogcache[dir]
1179 return self._dirlogcache[dir]
1180
1180
1181 def add(self, m, transaction, link, p1, p2, added, removed):
1181 def add(self, m, transaction, link, p1, p2, added, removed):
1182 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1182 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1183 and not self._usemanifestv2):
1183 and not self._usemanifestv2):
1184 # If our first parent is in the manifest cache, we can
1184 # If our first parent is in the manifest cache, we can
1185 # compute a delta here using properties we know about the
1185 # compute a delta here using properties we know about the
1186 # manifest up-front, which may save time later for the
1186 # manifest up-front, which may save time later for the
1187 # revlog layer.
1187 # revlog layer.
1188
1188
1189 _checkforbidden(added)
1189 _checkforbidden(added)
1190 # combine the changed lists into one sorted iterator
1190 # combine the changed lists into one sorted iterator
1191 work = heapq.merge([(x, False) for x in added],
1191 work = heapq.merge([(x, False) for x in added],
1192 [(x, True) for x in removed])
1192 [(x, True) for x in removed])
1193
1193
1194 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1194 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1195 cachedelta = self.rev(p1), deltatext
1195 cachedelta = self.rev(p1), deltatext
1196 text = util.buffer(arraytext)
1196 text = util.buffer(arraytext)
1197 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1197 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1198 else:
1198 else:
1199 # The first parent manifest isn't already loaded, so we'll
1199 # The first parent manifest isn't already loaded, so we'll
1200 # just encode a fulltext of the manifest and pass that
1200 # just encode a fulltext of the manifest and pass that
1201 # through to the revlog layer, and let it handle the delta
1201 # through to the revlog layer, and let it handle the delta
1202 # process.
1202 # process.
1203 if self._treeondisk:
1203 if self._treeondisk:
1204 m1 = self.read(p1)
1204 m1 = self.read(p1)
1205 m2 = self.read(p2)
1205 m2 = self.read(p2)
1206 n = self._addtree(m, transaction, link, m1, m2)
1206 n = self._addtree(m, transaction, link, m1, m2)
1207 arraytext = None
1207 arraytext = None
1208 else:
1208 else:
1209 text = m.text(self._usemanifestv2)
1209 text = m.text(self._usemanifestv2)
1210 n = self.addrevision(text, transaction, link, p1, p2)
1210 n = self.addrevision(text, transaction, link, p1, p2)
1211 arraytext = array.array('c', text)
1211 arraytext = array.array('c', text)
1212
1212
1213 if arraytext is not None:
1213 if arraytext is not None:
1214 self.fulltextcache[n] = arraytext
1214 self.fulltextcache[n] = arraytext
1215
1215
1216 return n
1216 return n
1217
1217
1218 def _addtree(self, m, transaction, link, m1, m2):
1218 def _addtree(self, m, transaction, link, m1, m2):
1219 # If the manifest is unchanged compared to one parent,
1219 # If the manifest is unchanged compared to one parent,
1220 # don't write a new revision
1220 # don't write a new revision
1221 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1221 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1222 return m.node()
1222 return m.node()
1223 def writesubtree(subm, subp1, subp2):
1223 def writesubtree(subm, subp1, subp2):
1224 sublog = self.dirlog(subm.dir())
1224 sublog = self.dirlog(subm.dir())
1225 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1225 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1226 m.writesubtrees(m1, m2, writesubtree)
1226 m.writesubtrees(m1, m2, writesubtree)
1227 text = m.dirtext(self._usemanifestv2)
1227 text = m.dirtext(self._usemanifestv2)
1228 # Double-check whether contents are unchanged to one parent
1228 # Double-check whether contents are unchanged to one parent
1229 if text == m1.dirtext(self._usemanifestv2):
1229 if text == m1.dirtext(self._usemanifestv2):
1230 n = m1.node()
1230 n = m1.node()
1231 elif text == m2.dirtext(self._usemanifestv2):
1231 elif text == m2.dirtext(self._usemanifestv2):
1232 n = m2.node()
1232 n = m2.node()
1233 else:
1233 else:
1234 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1234 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1235 # Save nodeid so parent manifest can calculate its nodeid
1235 # Save nodeid so parent manifest can calculate its nodeid
1236 m.setnode(n)
1236 m.setnode(n)
1237 return n
1237 return n
1238
1238
1239 class manifestlog(object):
1239 class manifestlog(object):
1240 """A collection class representing the collection of manifest snapshots
1240 """A collection class representing the collection of manifest snapshots
1241 referenced by commits in the repository.
1241 referenced by commits in the repository.
1242
1242
1243 In this situation, 'manifest' refers to the abstract concept of a snapshot
1243 In this situation, 'manifest' refers to the abstract concept of a snapshot
1244 of the list of files in the given commit. Consumers of the output of this
1244 of the list of files in the given commit. Consumers of the output of this
1245 class do not care about the implementation details of the actual manifests
1245 class do not care about the implementation details of the actual manifests
1246 they receive (i.e. tree or flat or lazily loaded, etc)."""
1246 they receive (i.e. tree or flat or lazily loaded, etc)."""
1247 def __init__(self, opener, repo):
1247 def __init__(self, opener, repo):
1248 self._repo = repo
1248 self._repo = repo
1249
1249
1250 usetreemanifest = False
1250 usetreemanifest = False
1251
1251
1252 opts = getattr(opener, 'options', None)
1252 opts = getattr(opener, 'options', None)
1253 if opts is not None:
1253 if opts is not None:
1254 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1254 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1255 self._treeinmem = usetreemanifest
1255 self._treeinmem = usetreemanifest
1256
1256
1257 self._oldmanifest = repo._constructmanifest()
1257 self._oldmanifest = repo._constructmanifest()
1258 self._revlog = self._oldmanifest
1258 self._revlog = self._oldmanifest
1259
1259
1260 # We'll separate this into it's own cache once oldmanifest is no longer
1260 # We'll separate this into it's own cache once oldmanifest is no longer
1261 # used
1261 # used
1262 self._mancache = self._oldmanifest._mancache
1262 self._mancache = self._oldmanifest._mancache
1263
1263
1264 def __getitem__(self, node):
1264 def __getitem__(self, node):
1265 """Retrieves the manifest instance for the given node. Throws a KeyError
1265 """Retrieves the manifest instance for the given node. Throws a
1266 if not found.
1266 LookupError if not found.
1267 """
1267 """
1268 if node in self._mancache:
1268 if node in self._mancache:
1269 cachemf = self._mancache[node]
1269 cachemf = self._mancache[node]
1270 # The old manifest may put non-ctx manifests in the cache, so skip
1270 # The old manifest may put non-ctx manifests in the cache, so skip
1271 # those since they don't implement the full api.
1271 # those since they don't implement the full api.
1272 if (isinstance(cachemf, manifestctx) or
1272 if (isinstance(cachemf, manifestctx) or
1273 isinstance(cachemf, treemanifestctx)):
1273 isinstance(cachemf, treemanifestctx)):
1274 return cachemf
1274 return cachemf
1275
1275
1276 if node not in self._revlog.nodemap:
1277 raise LookupError(node, self._revlog.indexfile,
1278 _('no node'))
1276 if self._treeinmem:
1279 if self._treeinmem:
1277 m = treemanifestctx(self._repo, '', node)
1280 m = treemanifestctx(self._repo, '', node)
1278 else:
1281 else:
1279 m = manifestctx(self._repo, node)
1282 m = manifestctx(self._repo, node)
1280 if node != revlog.nullid:
1283 if node != revlog.nullid:
1281 self._mancache[node] = m
1284 self._mancache[node] = m
1282 return m
1285 return m
1283
1286
1284 def add(self, m, transaction, link, p1, p2, added, removed):
1287 def add(self, m, transaction, link, p1, p2, added, removed):
1285 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1288 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1286
1289
1287 class manifestctx(object):
1290 class manifestctx(object):
1288 """A class representing a single revision of a manifest, including its
1291 """A class representing a single revision of a manifest, including its
1289 contents, its parent revs, and its linkrev.
1292 contents, its parent revs, and its linkrev.
1290 """
1293 """
1291 def __init__(self, repo, node):
1294 def __init__(self, repo, node):
1292 self._repo = repo
1295 self._repo = repo
1293 self._data = None
1296 self._data = None
1294
1297
1295 self._node = node
1298 self._node = node
1296
1299
1297 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1300 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1298 # but let's add it later when something needs it and we can load it
1301 # but let's add it later when something needs it and we can load it
1299 # lazily.
1302 # lazily.
1300 #self.p1, self.p2 = revlog.parents(node)
1303 #self.p1, self.p2 = revlog.parents(node)
1301 #rev = revlog.rev(node)
1304 #rev = revlog.rev(node)
1302 #self.linkrev = revlog.linkrev(rev)
1305 #self.linkrev = revlog.linkrev(rev)
1303
1306
1304 def node(self):
1307 def node(self):
1305 return self._node
1308 return self._node
1306
1309
1307 def read(self):
1310 def read(self):
1308 if not self._data:
1311 if not self._data:
1309 if self._node == revlog.nullid:
1312 if self._node == revlog.nullid:
1310 self._data = manifestdict()
1313 self._data = manifestdict()
1311 else:
1314 else:
1312 rl = self._repo.manifestlog._revlog
1315 rl = self._repo.manifestlog._revlog
1313 text = rl.revision(self._node)
1316 text = rl.revision(self._node)
1314 arraytext = array.array('c', text)
1317 arraytext = array.array('c', text)
1315 rl._fulltextcache[self._node] = arraytext
1318 rl._fulltextcache[self._node] = arraytext
1316 self._data = manifestdict(text)
1319 self._data = manifestdict(text)
1317 return self._data
1320 return self._data
1318
1321
1319 def readfast(self):
1322 def readfast(self):
1320 rl = self._repo.manifestlog._revlog
1323 rl = self._repo.manifestlog._revlog
1321 r = rl.rev(self._node)
1324 r = rl.rev(self._node)
1322 deltaparent = rl.deltaparent(r)
1325 deltaparent = rl.deltaparent(r)
1323 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1326 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1324 return self.readdelta()
1327 return self.readdelta()
1325 return self.read()
1328 return self.read()
1326
1329
1327 def readdelta(self):
1330 def readdelta(self):
1328 revlog = self._repo.manifestlog._revlog
1331 revlog = self._repo.manifestlog._revlog
1329 if revlog._usemanifestv2:
1332 if revlog._usemanifestv2:
1330 # Need to perform a slow delta
1333 # Need to perform a slow delta
1331 r0 = revlog.deltaparent(revlog.rev(self._node))
1334 r0 = revlog.deltaparent(revlog.rev(self._node))
1332 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1335 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1333 m1 = self.read()
1336 m1 = self.read()
1334 md = manifestdict()
1337 md = manifestdict()
1335 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1338 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1336 if n1:
1339 if n1:
1337 md[f] = n1
1340 md[f] = n1
1338 if fl1:
1341 if fl1:
1339 md.setflag(f, fl1)
1342 md.setflag(f, fl1)
1340 return md
1343 return md
1341
1344
1342 r = revlog.rev(self._node)
1345 r = revlog.rev(self._node)
1343 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1346 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1344 return manifestdict(d)
1347 return manifestdict(d)
1345
1348
1346 class treemanifestctx(object):
1349 class treemanifestctx(object):
1347 def __init__(self, repo, dir, node):
1350 def __init__(self, repo, dir, node):
1348 self._repo = repo
1351 self._repo = repo
1349 self._dir = dir
1352 self._dir = dir
1350 self._data = None
1353 self._data = None
1351
1354
1352 self._node = node
1355 self._node = node
1353
1356
1354 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1357 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1355 # we can instantiate treemanifestctx objects for directories we don't
1358 # we can instantiate treemanifestctx objects for directories we don't
1356 # have on disk.
1359 # have on disk.
1357 #self.p1, self.p2 = revlog.parents(node)
1360 #self.p1, self.p2 = revlog.parents(node)
1358 #rev = revlog.rev(node)
1361 #rev = revlog.rev(node)
1359 #self.linkrev = revlog.linkrev(rev)
1362 #self.linkrev = revlog.linkrev(rev)
1360
1363
1361 def _revlog(self):
1364 def _revlog(self):
1362 return self._repo.manifestlog._revlog.dirlog(self._dir)
1365 return self._repo.manifestlog._revlog.dirlog(self._dir)
1363
1366
1364 def read(self):
1367 def read(self):
1365 if not self._data:
1368 if not self._data:
1366 rl = self._revlog()
1369 rl = self._revlog()
1367 if self._node == revlog.nullid:
1370 if self._node == revlog.nullid:
1368 self._data = treemanifest()
1371 self._data = treemanifest()
1369 elif rl._treeondisk:
1372 elif rl._treeondisk:
1370 m = treemanifest(dir=self._dir)
1373 m = treemanifest(dir=self._dir)
1371 def gettext():
1374 def gettext():
1372 return rl.revision(self._node)
1375 return rl.revision(self._node)
1373 def readsubtree(dir, subm):
1376 def readsubtree(dir, subm):
1374 return treemanifestctx(self._repo, dir, subm).read()
1377 return treemanifestctx(self._repo, dir, subm).read()
1375 m.read(gettext, readsubtree)
1378 m.read(gettext, readsubtree)
1376 m.setnode(self._node)
1379 m.setnode(self._node)
1377 self._data = m
1380 self._data = m
1378 else:
1381 else:
1379 text = revlog.revision(self._node)
1382 text = revlog.revision(self._node)
1380 arraytext = array.array('c', text)
1383 arraytext = array.array('c', text)
1381 rl.fulltextcache[self._node] = arraytext
1384 rl.fulltextcache[self._node] = arraytext
1382 self._data = treemanifest(dir=self._dir, text=text)
1385 self._data = treemanifest(dir=self._dir, text=text)
1383
1386
1384 return self._data
1387 return self._data
1385
1388
1386 def node(self):
1389 def node(self):
1387 return self._node
1390 return self._node
1388
1391
1389 def readdelta(self):
1392 def readdelta(self):
1390 # Need to perform a slow delta
1393 # Need to perform a slow delta
1391 revlog = self._revlog()
1394 revlog = self._revlog()
1392 r0 = revlog.deltaparent(revlog.rev(self._node))
1395 r0 = revlog.deltaparent(revlog.rev(self._node))
1393 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1396 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1394 m1 = self.read()
1397 m1 = self.read()
1395 md = treemanifest(dir=self._dir)
1398 md = treemanifest(dir=self._dir)
1396 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1399 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1397 if n1:
1400 if n1:
1398 md[f] = n1
1401 md[f] = n1
1399 if fl1:
1402 if fl1:
1400 md.setflag(f, fl1)
1403 md.setflag(f, fl1)
1401 return md
1404 return md
1402
1405
1403 def readfast(self):
1406 def readfast(self):
1404 rl = self._revlog()
1407 rl = self._revlog()
1405 r = rl.rev(self._node)
1408 r = rl.rev(self._node)
1406 deltaparent = rl.deltaparent(r)
1409 deltaparent = rl.deltaparent(r)
1407 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1410 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1408 return self.readdelta()
1411 return self.readdelta()
1409 return self.read()
1412 return self.read()
1410
1413
1411 class manifest(manifestrevlog):
1414 class manifest(manifestrevlog):
1412 def __init__(self, opener, dir='', dirlogcache=None):
1415 def __init__(self, opener, dir='', dirlogcache=None):
1413 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1416 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1414 manifest.manifest only. External users should create a root manifest
1417 manifest.manifest only. External users should create a root manifest
1415 log with manifest.manifest(opener) and call dirlog() on it.
1418 log with manifest.manifest(opener) and call dirlog() on it.
1416 '''
1419 '''
1417 # During normal operations, we expect to deal with not more than four
1420 # During normal operations, we expect to deal with not more than four
1418 # revs at a time (such as during commit --amend). When rebasing large
1421 # revs at a time (such as during commit --amend). When rebasing large
1419 # stacks of commits, the number can go up, hence the config knob below.
1422 # stacks of commits, the number can go up, hence the config knob below.
1420 cachesize = 4
1423 cachesize = 4
1421 usetreemanifest = False
1424 usetreemanifest = False
1422 opts = getattr(opener, 'options', None)
1425 opts = getattr(opener, 'options', None)
1423 if opts is not None:
1426 if opts is not None:
1424 cachesize = opts.get('manifestcachesize', cachesize)
1427 cachesize = opts.get('manifestcachesize', cachesize)
1425 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1428 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1426 self._mancache = util.lrucachedict(cachesize)
1429 self._mancache = util.lrucachedict(cachesize)
1427 self._treeinmem = usetreemanifest
1430 self._treeinmem = usetreemanifest
1428 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1431 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1429
1432
1430 def _newmanifest(self, data=''):
1433 def _newmanifest(self, data=''):
1431 if self._treeinmem:
1434 if self._treeinmem:
1432 return treemanifest(self._dir, data)
1435 return treemanifest(self._dir, data)
1433 return manifestdict(data)
1436 return manifestdict(data)
1434
1437
1435 def dirlog(self, dir):
1438 def dirlog(self, dir):
1436 """This overrides the base revlog implementation to allow construction
1439 """This overrides the base revlog implementation to allow construction
1437 'manifest' types instead of manifestrevlog types. This is only needed
1440 'manifest' types instead of manifestrevlog types. This is only needed
1438 until we migrate off the 'manifest' type."""
1441 until we migrate off the 'manifest' type."""
1439 if dir:
1442 if dir:
1440 assert self._treeondisk
1443 assert self._treeondisk
1441 if dir not in self._dirlogcache:
1444 if dir not in self._dirlogcache:
1442 self._dirlogcache[dir] = manifest(self.opener, dir,
1445 self._dirlogcache[dir] = manifest(self.opener, dir,
1443 self._dirlogcache)
1446 self._dirlogcache)
1444 return self._dirlogcache[dir]
1447 return self._dirlogcache[dir]
1445
1448
1446 def _slowreaddelta(self, node):
1449 def _slowreaddelta(self, node):
1447 r0 = self.deltaparent(self.rev(node))
1450 r0 = self.deltaparent(self.rev(node))
1448 m0 = self.read(self.node(r0))
1451 m0 = self.read(self.node(r0))
1449 m1 = self.read(node)
1452 m1 = self.read(node)
1450 md = self._newmanifest()
1453 md = self._newmanifest()
1451 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1454 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1452 if n1:
1455 if n1:
1453 md[f] = n1
1456 md[f] = n1
1454 if fl1:
1457 if fl1:
1455 md.setflag(f, fl1)
1458 md.setflag(f, fl1)
1456 return md
1459 return md
1457
1460
1458 def readdelta(self, node):
1461 def readdelta(self, node):
1459 if self._usemanifestv2 or self._treeondisk:
1462 if self._usemanifestv2 or self._treeondisk:
1460 return self._slowreaddelta(node)
1463 return self._slowreaddelta(node)
1461 r = self.rev(node)
1464 r = self.rev(node)
1462 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1465 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1463 return self._newmanifest(d)
1466 return self._newmanifest(d)
1464
1467
1465 def readshallowdelta(self, node):
1468 def readshallowdelta(self, node):
1466 '''For flat manifests, this is the same as readdelta(). For
1469 '''For flat manifests, this is the same as readdelta(). For
1467 treemanifests, this will read the delta for this revlog's directory,
1470 treemanifests, this will read the delta for this revlog's directory,
1468 without recursively reading subdirectory manifests. Instead, any
1471 without recursively reading subdirectory manifests. Instead, any
1469 subdirectory entry will be reported as it appears in the manifests, i.e.
1472 subdirectory entry will be reported as it appears in the manifests, i.e.
1470 the subdirectory will be reported among files and distinguished only by
1473 the subdirectory will be reported among files and distinguished only by
1471 its 't' flag.'''
1474 its 't' flag.'''
1472 if not self._treeondisk:
1475 if not self._treeondisk:
1473 return self.readdelta(node)
1476 return self.readdelta(node)
1474 if self._usemanifestv2:
1477 if self._usemanifestv2:
1475 raise error.Abort(
1478 raise error.Abort(
1476 _("readshallowdelta() not implemented for manifestv2"))
1479 _("readshallowdelta() not implemented for manifestv2"))
1477 r = self.rev(node)
1480 r = self.rev(node)
1478 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1481 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1479 return manifestdict(d)
1482 return manifestdict(d)
1480
1483
1481 def readshallowfast(self, node):
1484 def readshallowfast(self, node):
1482 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1485 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1483 '''
1486 '''
1484 r = self.rev(node)
1487 r = self.rev(node)
1485 deltaparent = self.deltaparent(r)
1488 deltaparent = self.deltaparent(r)
1486 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1489 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1487 return self.readshallowdelta(node)
1490 return self.readshallowdelta(node)
1488 return self.readshallow(node)
1491 return self.readshallow(node)
1489
1492
1490 def read(self, node):
1493 def read(self, node):
1491 if node == revlog.nullid:
1494 if node == revlog.nullid:
1492 return self._newmanifest() # don't upset local cache
1495 return self._newmanifest() # don't upset local cache
1493 if node in self._mancache:
1496 if node in self._mancache:
1494 cached = self._mancache[node]
1497 cached = self._mancache[node]
1495 if (isinstance(cached, manifestctx) or
1498 if (isinstance(cached, manifestctx) or
1496 isinstance(cached, treemanifestctx)):
1499 isinstance(cached, treemanifestctx)):
1497 cached = cached.read()
1500 cached = cached.read()
1498 return cached
1501 return cached
1499 if self._treeondisk:
1502 if self._treeondisk:
1500 def gettext():
1503 def gettext():
1501 return self.revision(node)
1504 return self.revision(node)
1502 def readsubtree(dir, subm):
1505 def readsubtree(dir, subm):
1503 return self.dirlog(dir).read(subm)
1506 return self.dirlog(dir).read(subm)
1504 m = self._newmanifest()
1507 m = self._newmanifest()
1505 m.read(gettext, readsubtree)
1508 m.read(gettext, readsubtree)
1506 m.setnode(node)
1509 m.setnode(node)
1507 arraytext = None
1510 arraytext = None
1508 else:
1511 else:
1509 text = self.revision(node)
1512 text = self.revision(node)
1510 m = self._newmanifest(text)
1513 m = self._newmanifest(text)
1511 arraytext = array.array('c', text)
1514 arraytext = array.array('c', text)
1512 self._mancache[node] = m
1515 self._mancache[node] = m
1513 if arraytext is not None:
1516 if arraytext is not None:
1514 self.fulltextcache[node] = arraytext
1517 self.fulltextcache[node] = arraytext
1515 return m
1518 return m
1516
1519
1517 def readshallow(self, node):
1520 def readshallow(self, node):
1518 '''Reads the manifest in this directory. When using flat manifests,
1521 '''Reads the manifest in this directory. When using flat manifests,
1519 this manifest will generally have files in subdirectories in it. Does
1522 this manifest will generally have files in subdirectories in it. Does
1520 not cache the manifest as the callers generally do not read the same
1523 not cache the manifest as the callers generally do not read the same
1521 version twice.'''
1524 version twice.'''
1522 return manifestdict(self.revision(node))
1525 return manifestdict(self.revision(node))
1523
1526
1524 def find(self, node, f):
1527 def find(self, node, f):
1525 '''look up entry for a single file efficiently.
1528 '''look up entry for a single file efficiently.
1526 return (node, flags) pair if found, (None, None) if not.'''
1529 return (node, flags) pair if found, (None, None) if not.'''
1527 m = self.read(node)
1530 m = self.read(node)
1528 try:
1531 try:
1529 return m.find(f)
1532 return m.find(f)
1530 except KeyError:
1533 except KeyError:
1531 return None, None
1534 return None, None
1532
1535
1533 def clearcaches(self):
1536 def clearcaches(self):
1534 super(manifest, self).clearcaches()
1537 super(manifest, self).clearcaches()
1535 self._mancache.clear()
1538 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now