##// END OF EJS Templates
manifest: make manifestctx store the repo...
Durham Goode -
r30220:acc8885a stable
parent child Browse files
Show More
@@ -1,1531 +1,1532 b''
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 KeyError
1266 if not found.
1266 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 self._treeinmem:
1276 if self._treeinmem:
1277 m = treemanifestctx(self._revlog, '', node)
1277 m = treemanifestctx(self._revlog, '', node)
1278 else:
1278 else:
1279 m = manifestctx(self._revlog, node)
1279 m = manifestctx(self._repo, node)
1280 if node != revlog.nullid:
1280 if node != revlog.nullid:
1281 self._mancache[node] = m
1281 self._mancache[node] = m
1282 return m
1282 return m
1283
1283
1284 def add(self, m, transaction, link, p1, p2, added, removed):
1284 def add(self, m, transaction, link, p1, p2, added, removed):
1285 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1285 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1286
1286
1287 class manifestctx(object):
1287 class manifestctx(object):
1288 """A class representing a single revision of a manifest, including its
1288 """A class representing a single revision of a manifest, including its
1289 contents, its parent revs, and its linkrev.
1289 contents, its parent revs, and its linkrev.
1290 """
1290 """
1291 def __init__(self, revlog, node):
1291 def __init__(self, repo, node):
1292 self._revlog = revlog
1292 self._repo = repo
1293 self._data = None
1293 self._data = None
1294
1294
1295 self._node = node
1295 self._node = node
1296
1296
1297 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1297 # 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
1298 # but let's add it later when something needs it and we can load it
1299 # lazily.
1299 # lazily.
1300 #self.p1, self.p2 = revlog.parents(node)
1300 #self.p1, self.p2 = revlog.parents(node)
1301 #rev = revlog.rev(node)
1301 #rev = revlog.rev(node)
1302 #self.linkrev = revlog.linkrev(rev)
1302 #self.linkrev = revlog.linkrev(rev)
1303
1303
1304 def node(self):
1304 def node(self):
1305 return self._node
1305 return self._node
1306
1306
1307 def read(self):
1307 def read(self):
1308 if not self._data:
1308 if not self._data:
1309 if self._node == revlog.nullid:
1309 if self._node == revlog.nullid:
1310 self._data = manifestdict()
1310 self._data = manifestdict()
1311 else:
1311 else:
1312 text = self._revlog.revision(self._node)
1312 rl = self._repo.manifestlog._revlog
1313 text = rl.revision(self._node)
1313 arraytext = array.array('c', text)
1314 arraytext = array.array('c', text)
1314 self._revlog._fulltextcache[self._node] = arraytext
1315 rl._fulltextcache[self._node] = arraytext
1315 self._data = manifestdict(text)
1316 self._data = manifestdict(text)
1316 return self._data
1317 return self._data
1317
1318
1318 def readfast(self):
1319 def readfast(self):
1319 rl = self._revlog
1320 rl = self._repo.manifestlog._revlog
1320 r = rl.rev(self._node)
1321 r = rl.rev(self._node)
1321 deltaparent = rl.deltaparent(r)
1322 deltaparent = rl.deltaparent(r)
1322 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1323 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1323 return self.readdelta()
1324 return self.readdelta()
1324 return self.read()
1325 return self.read()
1325
1326
1326 def readdelta(self):
1327 def readdelta(self):
1327 revlog = self._revlog
1328 revlog = self._repo.manifestlog._revlog
1328 if revlog._usemanifestv2:
1329 if revlog._usemanifestv2:
1329 # Need to perform a slow delta
1330 # Need to perform a slow delta
1330 r0 = revlog.deltaparent(revlog.rev(self._node))
1331 r0 = revlog.deltaparent(revlog.rev(self._node))
1331 m0 = manifestctx(revlog, revlog.node(r0)).read()
1332 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1332 m1 = self.read()
1333 m1 = self.read()
1333 md = manifestdict()
1334 md = manifestdict()
1334 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1335 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1335 if n1:
1336 if n1:
1336 md[f] = n1
1337 md[f] = n1
1337 if fl1:
1338 if fl1:
1338 md.setflag(f, fl1)
1339 md.setflag(f, fl1)
1339 return md
1340 return md
1340
1341
1341 r = revlog.rev(self._node)
1342 r = revlog.rev(self._node)
1342 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1343 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1343 return manifestdict(d)
1344 return manifestdict(d)
1344
1345
1345 class treemanifestctx(object):
1346 class treemanifestctx(object):
1346 def __init__(self, revlog, dir, node):
1347 def __init__(self, revlog, dir, node):
1347 revlog = revlog.dirlog(dir)
1348 revlog = revlog.dirlog(dir)
1348 self._revlog = revlog
1349 self._revlog = revlog
1349 self._dir = dir
1350 self._dir = dir
1350 self._data = None
1351 self._data = None
1351
1352
1352 self._node = node
1353 self._node = node
1353
1354
1354 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1355 # 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
1356 # we can instantiate treemanifestctx objects for directories we don't
1356 # have on disk.
1357 # have on disk.
1357 #self.p1, self.p2 = revlog.parents(node)
1358 #self.p1, self.p2 = revlog.parents(node)
1358 #rev = revlog.rev(node)
1359 #rev = revlog.rev(node)
1359 #self.linkrev = revlog.linkrev(rev)
1360 #self.linkrev = revlog.linkrev(rev)
1360
1361
1361 def read(self):
1362 def read(self):
1362 if not self._data:
1363 if not self._data:
1363 if self._node == revlog.nullid:
1364 if self._node == revlog.nullid:
1364 self._data = treemanifest()
1365 self._data = treemanifest()
1365 elif self._revlog._treeondisk:
1366 elif self._revlog._treeondisk:
1366 m = treemanifest(dir=self._dir)
1367 m = treemanifest(dir=self._dir)
1367 def gettext():
1368 def gettext():
1368 return self._revlog.revision(self._node)
1369 return self._revlog.revision(self._node)
1369 def readsubtree(dir, subm):
1370 def readsubtree(dir, subm):
1370 return treemanifestctx(self._revlog, dir, subm).read()
1371 return treemanifestctx(self._revlog, dir, subm).read()
1371 m.read(gettext, readsubtree)
1372 m.read(gettext, readsubtree)
1372 m.setnode(self._node)
1373 m.setnode(self._node)
1373 self._data = m
1374 self._data = m
1374 else:
1375 else:
1375 text = self._revlog.revision(self._node)
1376 text = self._revlog.revision(self._node)
1376 arraytext = array.array('c', text)
1377 arraytext = array.array('c', text)
1377 self._revlog.fulltextcache[self._node] = arraytext
1378 self._revlog.fulltextcache[self._node] = arraytext
1378 self._data = treemanifest(dir=self._dir, text=text)
1379 self._data = treemanifest(dir=self._dir, text=text)
1379
1380
1380 return self._data
1381 return self._data
1381
1382
1382 def node(self):
1383 def node(self):
1383 return self._node
1384 return self._node
1384
1385
1385 def readdelta(self):
1386 def readdelta(self):
1386 # Need to perform a slow delta
1387 # Need to perform a slow delta
1387 revlog = self._revlog
1388 revlog = self._revlog
1388 r0 = revlog.deltaparent(revlog.rev(self._node))
1389 r0 = revlog.deltaparent(revlog.rev(self._node))
1389 m0 = treemanifestctx(revlog, self._dir, revlog.node(r0)).read()
1390 m0 = treemanifestctx(revlog, self._dir, revlog.node(r0)).read()
1390 m1 = self.read()
1391 m1 = self.read()
1391 md = treemanifest(dir=self._dir)
1392 md = treemanifest(dir=self._dir)
1392 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1393 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1393 if n1:
1394 if n1:
1394 md[f] = n1
1395 md[f] = n1
1395 if fl1:
1396 if fl1:
1396 md.setflag(f, fl1)
1397 md.setflag(f, fl1)
1397 return md
1398 return md
1398
1399
1399 def readfast(self):
1400 def readfast(self):
1400 rl = self._revlog
1401 rl = self._revlog
1401 r = rl.rev(self._node)
1402 r = rl.rev(self._node)
1402 deltaparent = rl.deltaparent(r)
1403 deltaparent = rl.deltaparent(r)
1403 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1404 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1404 return self.readdelta()
1405 return self.readdelta()
1405 return self.read()
1406 return self.read()
1406
1407
1407 class manifest(manifestrevlog):
1408 class manifest(manifestrevlog):
1408 def __init__(self, opener, dir='', dirlogcache=None):
1409 def __init__(self, opener, dir='', dirlogcache=None):
1409 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1410 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1410 manifest.manifest only. External users should create a root manifest
1411 manifest.manifest only. External users should create a root manifest
1411 log with manifest.manifest(opener) and call dirlog() on it.
1412 log with manifest.manifest(opener) and call dirlog() on it.
1412 '''
1413 '''
1413 # During normal operations, we expect to deal with not more than four
1414 # During normal operations, we expect to deal with not more than four
1414 # revs at a time (such as during commit --amend). When rebasing large
1415 # revs at a time (such as during commit --amend). When rebasing large
1415 # stacks of commits, the number can go up, hence the config knob below.
1416 # stacks of commits, the number can go up, hence the config knob below.
1416 cachesize = 4
1417 cachesize = 4
1417 usetreemanifest = False
1418 usetreemanifest = False
1418 opts = getattr(opener, 'options', None)
1419 opts = getattr(opener, 'options', None)
1419 if opts is not None:
1420 if opts is not None:
1420 cachesize = opts.get('manifestcachesize', cachesize)
1421 cachesize = opts.get('manifestcachesize', cachesize)
1421 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1422 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1422 self._mancache = util.lrucachedict(cachesize)
1423 self._mancache = util.lrucachedict(cachesize)
1423 self._treeinmem = usetreemanifest
1424 self._treeinmem = usetreemanifest
1424 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1425 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1425
1426
1426 def _newmanifest(self, data=''):
1427 def _newmanifest(self, data=''):
1427 if self._treeinmem:
1428 if self._treeinmem:
1428 return treemanifest(self._dir, data)
1429 return treemanifest(self._dir, data)
1429 return manifestdict(data)
1430 return manifestdict(data)
1430
1431
1431 def dirlog(self, dir):
1432 def dirlog(self, dir):
1432 """This overrides the base revlog implementation to allow construction
1433 """This overrides the base revlog implementation to allow construction
1433 'manifest' types instead of manifestrevlog types. This is only needed
1434 'manifest' types instead of manifestrevlog types. This is only needed
1434 until we migrate off the 'manifest' type."""
1435 until we migrate off the 'manifest' type."""
1435 if dir:
1436 if dir:
1436 assert self._treeondisk
1437 assert self._treeondisk
1437 if dir not in self._dirlogcache:
1438 if dir not in self._dirlogcache:
1438 self._dirlogcache[dir] = manifest(self.opener, dir,
1439 self._dirlogcache[dir] = manifest(self.opener, dir,
1439 self._dirlogcache)
1440 self._dirlogcache)
1440 return self._dirlogcache[dir]
1441 return self._dirlogcache[dir]
1441
1442
1442 def _slowreaddelta(self, node):
1443 def _slowreaddelta(self, node):
1443 r0 = self.deltaparent(self.rev(node))
1444 r0 = self.deltaparent(self.rev(node))
1444 m0 = self.read(self.node(r0))
1445 m0 = self.read(self.node(r0))
1445 m1 = self.read(node)
1446 m1 = self.read(node)
1446 md = self._newmanifest()
1447 md = self._newmanifest()
1447 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1448 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1448 if n1:
1449 if n1:
1449 md[f] = n1
1450 md[f] = n1
1450 if fl1:
1451 if fl1:
1451 md.setflag(f, fl1)
1452 md.setflag(f, fl1)
1452 return md
1453 return md
1453
1454
1454 def readdelta(self, node):
1455 def readdelta(self, node):
1455 if self._usemanifestv2 or self._treeondisk:
1456 if self._usemanifestv2 or self._treeondisk:
1456 return self._slowreaddelta(node)
1457 return self._slowreaddelta(node)
1457 r = self.rev(node)
1458 r = self.rev(node)
1458 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1459 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1459 return self._newmanifest(d)
1460 return self._newmanifest(d)
1460
1461
1461 def readshallowdelta(self, node):
1462 def readshallowdelta(self, node):
1462 '''For flat manifests, this is the same as readdelta(). For
1463 '''For flat manifests, this is the same as readdelta(). For
1463 treemanifests, this will read the delta for this revlog's directory,
1464 treemanifests, this will read the delta for this revlog's directory,
1464 without recursively reading subdirectory manifests. Instead, any
1465 without recursively reading subdirectory manifests. Instead, any
1465 subdirectory entry will be reported as it appears in the manifests, i.e.
1466 subdirectory entry will be reported as it appears in the manifests, i.e.
1466 the subdirectory will be reported among files and distinguished only by
1467 the subdirectory will be reported among files and distinguished only by
1467 its 't' flag.'''
1468 its 't' flag.'''
1468 if not self._treeondisk:
1469 if not self._treeondisk:
1469 return self.readdelta(node)
1470 return self.readdelta(node)
1470 if self._usemanifestv2:
1471 if self._usemanifestv2:
1471 raise error.Abort(
1472 raise error.Abort(
1472 _("readshallowdelta() not implemented for manifestv2"))
1473 _("readshallowdelta() not implemented for manifestv2"))
1473 r = self.rev(node)
1474 r = self.rev(node)
1474 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1475 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1475 return manifestdict(d)
1476 return manifestdict(d)
1476
1477
1477 def readshallowfast(self, node):
1478 def readshallowfast(self, node):
1478 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1479 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1479 '''
1480 '''
1480 r = self.rev(node)
1481 r = self.rev(node)
1481 deltaparent = self.deltaparent(r)
1482 deltaparent = self.deltaparent(r)
1482 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1483 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1483 return self.readshallowdelta(node)
1484 return self.readshallowdelta(node)
1484 return self.readshallow(node)
1485 return self.readshallow(node)
1485
1486
1486 def read(self, node):
1487 def read(self, node):
1487 if node == revlog.nullid:
1488 if node == revlog.nullid:
1488 return self._newmanifest() # don't upset local cache
1489 return self._newmanifest() # don't upset local cache
1489 if node in self._mancache:
1490 if node in self._mancache:
1490 cached = self._mancache[node]
1491 cached = self._mancache[node]
1491 if (isinstance(cached, manifestctx) or
1492 if (isinstance(cached, manifestctx) or
1492 isinstance(cached, treemanifestctx)):
1493 isinstance(cached, treemanifestctx)):
1493 cached = cached.read()
1494 cached = cached.read()
1494 return cached
1495 return cached
1495 if self._treeondisk:
1496 if self._treeondisk:
1496 def gettext():
1497 def gettext():
1497 return self.revision(node)
1498 return self.revision(node)
1498 def readsubtree(dir, subm):
1499 def readsubtree(dir, subm):
1499 return self.dirlog(dir).read(subm)
1500 return self.dirlog(dir).read(subm)
1500 m = self._newmanifest()
1501 m = self._newmanifest()
1501 m.read(gettext, readsubtree)
1502 m.read(gettext, readsubtree)
1502 m.setnode(node)
1503 m.setnode(node)
1503 arraytext = None
1504 arraytext = None
1504 else:
1505 else:
1505 text = self.revision(node)
1506 text = self.revision(node)
1506 m = self._newmanifest(text)
1507 m = self._newmanifest(text)
1507 arraytext = array.array('c', text)
1508 arraytext = array.array('c', text)
1508 self._mancache[node] = m
1509 self._mancache[node] = m
1509 if arraytext is not None:
1510 if arraytext is not None:
1510 self.fulltextcache[node] = arraytext
1511 self.fulltextcache[node] = arraytext
1511 return m
1512 return m
1512
1513
1513 def readshallow(self, node):
1514 def readshallow(self, node):
1514 '''Reads the manifest in this directory. When using flat manifests,
1515 '''Reads the manifest in this directory. When using flat manifests,
1515 this manifest will generally have files in subdirectories in it. Does
1516 this manifest will generally have files in subdirectories in it. Does
1516 not cache the manifest as the callers generally do not read the same
1517 not cache the manifest as the callers generally do not read the same
1517 version twice.'''
1518 version twice.'''
1518 return manifestdict(self.revision(node))
1519 return manifestdict(self.revision(node))
1519
1520
1520 def find(self, node, f):
1521 def find(self, node, f):
1521 '''look up entry for a single file efficiently.
1522 '''look up entry for a single file efficiently.
1522 return (node, flags) pair if found, (None, None) if not.'''
1523 return (node, flags) pair if found, (None, None) if not.'''
1523 m = self.read(node)
1524 m = self.read(node)
1524 try:
1525 try:
1525 return m.find(f)
1526 return m.find(f)
1526 except KeyError:
1527 except KeyError:
1527 return None, None
1528 return None, None
1528
1529
1529 def clearcaches(self):
1530 def clearcaches(self):
1530 super(manifest, self).clearcaches()
1531 super(manifest, self).clearcaches()
1531 self._mancache.clear()
1532 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now