##// END OF EJS Templates
manifest: implement clearcaches()...
Gregory Szorc -
r27466:f888676a default
parent child Browse files
Show More
@@ -1,1037 +1,1042
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 i18n import _
8 from i18n import _
9 import mdiff, parsers, error, revlog, util
9 import mdiff, parsers, error, revlog, util
10 import array, struct
10 import array, struct
11 import os
11 import os
12 import heapq
12 import heapq
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15
15
16 def _parsev1(data):
16 def _parsev1(data):
17 # This method does a little bit of excessive-looking
17 # This method does a little bit of excessive-looking
18 # precondition checking. This is so that the behavior of this
18 # precondition checking. This is so that the behavior of this
19 # class exactly matches its C counterpart to try and help
19 # class exactly matches its C counterpart to try and help
20 # prevent surprise breakage for anyone that develops against
20 # prevent surprise breakage for anyone that develops against
21 # the pure version.
21 # the pure version.
22 if data and data[-1] != '\n':
22 if data and data[-1] != '\n':
23 raise ValueError('Manifest did not end in a newline.')
23 raise ValueError('Manifest did not end in a newline.')
24 prev = None
24 prev = None
25 for l in data.splitlines():
25 for l in data.splitlines():
26 if prev is not None and prev > l:
26 if prev is not None and prev > l:
27 raise ValueError('Manifest lines not in sorted order.')
27 raise ValueError('Manifest lines not in sorted order.')
28 prev = l
28 prev = l
29 f, n = l.split('\0')
29 f, n = l.split('\0')
30 if len(n) > 40:
30 if len(n) > 40:
31 yield f, revlog.bin(n[:40]), n[40:]
31 yield f, revlog.bin(n[:40]), n[40:]
32 else:
32 else:
33 yield f, revlog.bin(n), ''
33 yield f, revlog.bin(n), ''
34
34
35 def _parsev2(data):
35 def _parsev2(data):
36 metadataend = data.find('\n')
36 metadataend = data.find('\n')
37 # Just ignore metadata for now
37 # Just ignore metadata for now
38 pos = metadataend + 1
38 pos = metadataend + 1
39 prevf = ''
39 prevf = ''
40 while pos < len(data):
40 while pos < len(data):
41 end = data.find('\n', pos + 1) # +1 to skip stem length byte
41 end = data.find('\n', pos + 1) # +1 to skip stem length byte
42 if end == -1:
42 if end == -1:
43 raise ValueError('Manifest ended with incomplete file entry.')
43 raise ValueError('Manifest ended with incomplete file entry.')
44 stemlen = ord(data[pos])
44 stemlen = ord(data[pos])
45 items = data[pos + 1:end].split('\0')
45 items = data[pos + 1:end].split('\0')
46 f = prevf[:stemlen] + items[0]
46 f = prevf[:stemlen] + items[0]
47 if prevf > f:
47 if prevf > f:
48 raise ValueError('Manifest entries not in sorted order.')
48 raise ValueError('Manifest entries not in sorted order.')
49 fl = items[1]
49 fl = items[1]
50 # Just ignore metadata (items[2:] for now)
50 # Just ignore metadata (items[2:] for now)
51 n = data[end + 1:end + 21]
51 n = data[end + 1:end + 21]
52 yield f, n, fl
52 yield f, n, fl
53 pos = end + 22
53 pos = end + 22
54 prevf = f
54 prevf = f
55
55
56 def _parse(data):
56 def _parse(data):
57 """Generates (path, node, flags) tuples from a manifest text"""
57 """Generates (path, node, flags) tuples from a manifest text"""
58 if data.startswith('\0'):
58 if data.startswith('\0'):
59 return iter(_parsev2(data))
59 return iter(_parsev2(data))
60 else:
60 else:
61 return iter(_parsev1(data))
61 return iter(_parsev1(data))
62
62
63 def _text(it, usemanifestv2):
63 def _text(it, usemanifestv2):
64 """Given an iterator over (path, node, flags) tuples, returns a manifest
64 """Given an iterator over (path, node, flags) tuples, returns a manifest
65 text"""
65 text"""
66 if usemanifestv2:
66 if usemanifestv2:
67 return _textv2(it)
67 return _textv2(it)
68 else:
68 else:
69 return _textv1(it)
69 return _textv1(it)
70
70
71 def _textv1(it):
71 def _textv1(it):
72 files = []
72 files = []
73 lines = []
73 lines = []
74 _hex = revlog.hex
74 _hex = revlog.hex
75 for f, n, fl in it:
75 for f, n, fl in it:
76 files.append(f)
76 files.append(f)
77 # if this is changed to support newlines in filenames,
77 # if this is changed to support newlines in filenames,
78 # be sure to check the templates/ dir again (especially *-raw.tmpl)
78 # be sure to check the templates/ dir again (especially *-raw.tmpl)
79 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
79 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
80
80
81 _checkforbidden(files)
81 _checkforbidden(files)
82 return ''.join(lines)
82 return ''.join(lines)
83
83
84 def _textv2(it):
84 def _textv2(it):
85 files = []
85 files = []
86 lines = ['\0\n']
86 lines = ['\0\n']
87 prevf = ''
87 prevf = ''
88 for f, n, fl in it:
88 for f, n, fl in it:
89 files.append(f)
89 files.append(f)
90 stem = os.path.commonprefix([prevf, f])
90 stem = os.path.commonprefix([prevf, f])
91 stemlen = min(len(stem), 255)
91 stemlen = min(len(stem), 255)
92 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
92 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
93 prevf = f
93 prevf = f
94 _checkforbidden(files)
94 _checkforbidden(files)
95 return ''.join(lines)
95 return ''.join(lines)
96
96
97 class _lazymanifest(dict):
97 class _lazymanifest(dict):
98 """This is the pure implementation of lazymanifest.
98 """This is the pure implementation of lazymanifest.
99
99
100 It has not been optimized *at all* and is not lazy.
100 It has not been optimized *at all* and is not lazy.
101 """
101 """
102
102
103 def __init__(self, data):
103 def __init__(self, data):
104 dict.__init__(self)
104 dict.__init__(self)
105 for f, n, fl in _parse(data):
105 for f, n, fl in _parse(data):
106 self[f] = n, fl
106 self[f] = n, fl
107
107
108 def __setitem__(self, k, v):
108 def __setitem__(self, k, v):
109 node, flag = v
109 node, flag = v
110 assert node is not None
110 assert node is not None
111 if len(node) > 21:
111 if len(node) > 21:
112 node = node[:21] # match c implementation behavior
112 node = node[:21] # match c implementation behavior
113 dict.__setitem__(self, k, (node, flag))
113 dict.__setitem__(self, k, (node, flag))
114
114
115 def __iter__(self):
115 def __iter__(self):
116 return iter(sorted(dict.keys(self)))
116 return iter(sorted(dict.keys(self)))
117
117
118 def iterkeys(self):
118 def iterkeys(self):
119 return iter(sorted(dict.keys(self)))
119 return iter(sorted(dict.keys(self)))
120
120
121 def iterentries(self):
121 def iterentries(self):
122 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
122 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
123
123
124 def copy(self):
124 def copy(self):
125 c = _lazymanifest('')
125 c = _lazymanifest('')
126 c.update(self)
126 c.update(self)
127 return c
127 return c
128
128
129 def diff(self, m2, clean=False):
129 def diff(self, m2, clean=False):
130 '''Finds changes between the current manifest and m2.'''
130 '''Finds changes between the current manifest and m2.'''
131 diff = {}
131 diff = {}
132
132
133 for fn, e1 in self.iteritems():
133 for fn, e1 in self.iteritems():
134 if fn not in m2:
134 if fn not in m2:
135 diff[fn] = e1, (None, '')
135 diff[fn] = e1, (None, '')
136 else:
136 else:
137 e2 = m2[fn]
137 e2 = m2[fn]
138 if e1 != e2:
138 if e1 != e2:
139 diff[fn] = e1, e2
139 diff[fn] = e1, e2
140 elif clean:
140 elif clean:
141 diff[fn] = None
141 diff[fn] = None
142
142
143 for fn, e2 in m2.iteritems():
143 for fn, e2 in m2.iteritems():
144 if fn not in self:
144 if fn not in self:
145 diff[fn] = (None, ''), e2
145 diff[fn] = (None, ''), e2
146
146
147 return diff
147 return diff
148
148
149 def filtercopy(self, filterfn):
149 def filtercopy(self, filterfn):
150 c = _lazymanifest('')
150 c = _lazymanifest('')
151 for f, n, fl in self.iterentries():
151 for f, n, fl in self.iterentries():
152 if filterfn(f):
152 if filterfn(f):
153 c[f] = n, fl
153 c[f] = n, fl
154 return c
154 return c
155
155
156 def text(self):
156 def text(self):
157 """Get the full data of this manifest as a bytestring."""
157 """Get the full data of this manifest as a bytestring."""
158 return _textv1(self.iterentries())
158 return _textv1(self.iterentries())
159
159
160 try:
160 try:
161 _lazymanifest = parsers.lazymanifest
161 _lazymanifest = parsers.lazymanifest
162 except AttributeError:
162 except AttributeError:
163 pass
163 pass
164
164
165 class manifestdict(object):
165 class manifestdict(object):
166 def __init__(self, data=''):
166 def __init__(self, data=''):
167 if data.startswith('\0'):
167 if data.startswith('\0'):
168 #_lazymanifest can not parse v2
168 #_lazymanifest can not parse v2
169 self._lm = _lazymanifest('')
169 self._lm = _lazymanifest('')
170 for f, n, fl in _parsev2(data):
170 for f, n, fl in _parsev2(data):
171 self._lm[f] = n, fl
171 self._lm[f] = n, fl
172 else:
172 else:
173 self._lm = _lazymanifest(data)
173 self._lm = _lazymanifest(data)
174
174
175 def __getitem__(self, key):
175 def __getitem__(self, key):
176 return self._lm[key][0]
176 return self._lm[key][0]
177
177
178 def find(self, key):
178 def find(self, key):
179 return self._lm[key]
179 return self._lm[key]
180
180
181 def __len__(self):
181 def __len__(self):
182 return len(self._lm)
182 return len(self._lm)
183
183
184 def __setitem__(self, key, node):
184 def __setitem__(self, key, node):
185 self._lm[key] = node, self.flags(key, '')
185 self._lm[key] = node, self.flags(key, '')
186
186
187 def __contains__(self, key):
187 def __contains__(self, key):
188 return key in self._lm
188 return key in self._lm
189
189
190 def __delitem__(self, key):
190 def __delitem__(self, key):
191 del self._lm[key]
191 del self._lm[key]
192
192
193 def __iter__(self):
193 def __iter__(self):
194 return self._lm.__iter__()
194 return self._lm.__iter__()
195
195
196 def iterkeys(self):
196 def iterkeys(self):
197 return self._lm.iterkeys()
197 return self._lm.iterkeys()
198
198
199 def keys(self):
199 def keys(self):
200 return list(self.iterkeys())
200 return list(self.iterkeys())
201
201
202 def filesnotin(self, m2):
202 def filesnotin(self, m2):
203 '''Set of files in this manifest that are not in the other'''
203 '''Set of files in this manifest that are not in the other'''
204 files = set(self)
204 files = set(self)
205 files.difference_update(m2)
205 files.difference_update(m2)
206 return files
206 return files
207
207
208 @propertycache
208 @propertycache
209 def _dirs(self):
209 def _dirs(self):
210 return util.dirs(self)
210 return util.dirs(self)
211
211
212 def dirs(self):
212 def dirs(self):
213 return self._dirs
213 return self._dirs
214
214
215 def hasdir(self, dir):
215 def hasdir(self, dir):
216 return dir in self._dirs
216 return dir in self._dirs
217
217
218 def _filesfastpath(self, match):
218 def _filesfastpath(self, match):
219 '''Checks whether we can correctly and quickly iterate over matcher
219 '''Checks whether we can correctly and quickly iterate over matcher
220 files instead of over manifest files.'''
220 files instead of over manifest files.'''
221 files = match.files()
221 files = match.files()
222 return (len(files) < 100 and (match.isexact() or
222 return (len(files) < 100 and (match.isexact() or
223 (match.prefix() and all(fn in self for fn in files))))
223 (match.prefix() and all(fn in self for fn in files))))
224
224
225 def walk(self, match):
225 def walk(self, match):
226 '''Generates matching file names.
226 '''Generates matching file names.
227
227
228 Equivalent to manifest.matches(match).iterkeys(), but without creating
228 Equivalent to manifest.matches(match).iterkeys(), but without creating
229 an entirely new manifest.
229 an entirely new manifest.
230
230
231 It also reports nonexistent files by marking them bad with match.bad().
231 It also reports nonexistent files by marking them bad with match.bad().
232 '''
232 '''
233 if match.always():
233 if match.always():
234 for f in iter(self):
234 for f in iter(self):
235 yield f
235 yield f
236 return
236 return
237
237
238 fset = set(match.files())
238 fset = set(match.files())
239
239
240 # avoid the entire walk if we're only looking for specific files
240 # avoid the entire walk if we're only looking for specific files
241 if self._filesfastpath(match):
241 if self._filesfastpath(match):
242 for fn in sorted(fset):
242 for fn in sorted(fset):
243 yield fn
243 yield fn
244 return
244 return
245
245
246 for fn in self:
246 for fn in self:
247 if fn in fset:
247 if fn in fset:
248 # specified pattern is the exact name
248 # specified pattern is the exact name
249 fset.remove(fn)
249 fset.remove(fn)
250 if match(fn):
250 if match(fn):
251 yield fn
251 yield fn
252
252
253 # for dirstate.walk, files=['.'] means "walk the whole tree".
253 # for dirstate.walk, files=['.'] means "walk the whole tree".
254 # follow that here, too
254 # follow that here, too
255 fset.discard('.')
255 fset.discard('.')
256
256
257 for fn in sorted(fset):
257 for fn in sorted(fset):
258 if not self.hasdir(fn):
258 if not self.hasdir(fn):
259 match.bad(fn, None)
259 match.bad(fn, None)
260
260
261 def matches(self, match):
261 def matches(self, match):
262 '''generate a new manifest filtered by the match argument'''
262 '''generate a new manifest filtered by the match argument'''
263 if match.always():
263 if match.always():
264 return self.copy()
264 return self.copy()
265
265
266 if self._filesfastpath(match):
266 if self._filesfastpath(match):
267 m = manifestdict()
267 m = manifestdict()
268 lm = self._lm
268 lm = self._lm
269 for fn in match.files():
269 for fn in match.files():
270 if fn in lm:
270 if fn in lm:
271 m._lm[fn] = lm[fn]
271 m._lm[fn] = lm[fn]
272 return m
272 return m
273
273
274 m = manifestdict()
274 m = manifestdict()
275 m._lm = self._lm.filtercopy(match)
275 m._lm = self._lm.filtercopy(match)
276 return m
276 return m
277
277
278 def diff(self, m2, clean=False):
278 def diff(self, m2, clean=False):
279 '''Finds changes between the current manifest and m2.
279 '''Finds changes between the current manifest and m2.
280
280
281 Args:
281 Args:
282 m2: the manifest to which this manifest should be compared.
282 m2: the manifest to which this manifest should be compared.
283 clean: if true, include files unchanged between these manifests
283 clean: if true, include files unchanged between these manifests
284 with a None value in the returned dictionary.
284 with a None value in the returned dictionary.
285
285
286 The result is returned as a dict with filename as key and
286 The result is returned as a dict with filename as key and
287 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
287 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
288 nodeid in the current/other manifest and fl1/fl2 is the flag
288 nodeid in the current/other manifest and fl1/fl2 is the flag
289 in the current/other manifest. Where the file does not exist,
289 in the current/other manifest. Where the file does not exist,
290 the nodeid will be None and the flags will be the empty
290 the nodeid will be None and the flags will be the empty
291 string.
291 string.
292 '''
292 '''
293 return self._lm.diff(m2._lm, clean)
293 return self._lm.diff(m2._lm, clean)
294
294
295 def setflag(self, key, flag):
295 def setflag(self, key, flag):
296 self._lm[key] = self[key], flag
296 self._lm[key] = self[key], flag
297
297
298 def get(self, key, default=None):
298 def get(self, key, default=None):
299 try:
299 try:
300 return self._lm[key][0]
300 return self._lm[key][0]
301 except KeyError:
301 except KeyError:
302 return default
302 return default
303
303
304 def flags(self, key, default=''):
304 def flags(self, key, default=''):
305 try:
305 try:
306 return self._lm[key][1]
306 return self._lm[key][1]
307 except KeyError:
307 except KeyError:
308 return default
308 return default
309
309
310 def copy(self):
310 def copy(self):
311 c = manifestdict()
311 c = manifestdict()
312 c._lm = self._lm.copy()
312 c._lm = self._lm.copy()
313 return c
313 return c
314
314
315 def iteritems(self):
315 def iteritems(self):
316 return (x[:2] for x in self._lm.iterentries())
316 return (x[:2] for x in self._lm.iterentries())
317
317
318 def text(self, usemanifestv2=False):
318 def text(self, usemanifestv2=False):
319 if usemanifestv2:
319 if usemanifestv2:
320 return _textv2(self._lm.iterentries())
320 return _textv2(self._lm.iterentries())
321 else:
321 else:
322 # use (probably) native version for v1
322 # use (probably) native version for v1
323 return self._lm.text()
323 return self._lm.text()
324
324
325 def fastdelta(self, base, changes):
325 def fastdelta(self, base, changes):
326 """Given a base manifest text as an array.array and a list of changes
326 """Given a base manifest text as an array.array and a list of changes
327 relative to that text, compute a delta that can be used by revlog.
327 relative to that text, compute a delta that can be used by revlog.
328 """
328 """
329 delta = []
329 delta = []
330 dstart = None
330 dstart = None
331 dend = None
331 dend = None
332 dline = [""]
332 dline = [""]
333 start = 0
333 start = 0
334 # zero copy representation of base as a buffer
334 # zero copy representation of base as a buffer
335 addbuf = util.buffer(base)
335 addbuf = util.buffer(base)
336
336
337 changes = list(changes)
337 changes = list(changes)
338 if len(changes) < 1000:
338 if len(changes) < 1000:
339 # start with a readonly loop that finds the offset of
339 # start with a readonly loop that finds the offset of
340 # each line and creates the deltas
340 # each line and creates the deltas
341 for f, todelete in changes:
341 for f, todelete in changes:
342 # bs will either be the index of the item or the insert point
342 # bs will either be the index of the item or the insert point
343 start, end = _msearch(addbuf, f, start)
343 start, end = _msearch(addbuf, f, start)
344 if not todelete:
344 if not todelete:
345 h, fl = self._lm[f]
345 h, fl = self._lm[f]
346 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
346 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
347 else:
347 else:
348 if start == end:
348 if start == end:
349 # item we want to delete was not found, error out
349 # item we want to delete was not found, error out
350 raise AssertionError(
350 raise AssertionError(
351 _("failed to remove %s from manifest") % f)
351 _("failed to remove %s from manifest") % f)
352 l = ""
352 l = ""
353 if dstart is not None and dstart <= start and dend >= start:
353 if dstart is not None and dstart <= start and dend >= start:
354 if dend < end:
354 if dend < end:
355 dend = end
355 dend = end
356 if l:
356 if l:
357 dline.append(l)
357 dline.append(l)
358 else:
358 else:
359 if dstart is not None:
359 if dstart is not None:
360 delta.append([dstart, dend, "".join(dline)])
360 delta.append([dstart, dend, "".join(dline)])
361 dstart = start
361 dstart = start
362 dend = end
362 dend = end
363 dline = [l]
363 dline = [l]
364
364
365 if dstart is not None:
365 if dstart is not None:
366 delta.append([dstart, dend, "".join(dline)])
366 delta.append([dstart, dend, "".join(dline)])
367 # apply the delta to the base, and get a delta for addrevision
367 # apply the delta to the base, and get a delta for addrevision
368 deltatext, arraytext = _addlistdelta(base, delta)
368 deltatext, arraytext = _addlistdelta(base, delta)
369 else:
369 else:
370 # For large changes, it's much cheaper to just build the text and
370 # For large changes, it's much cheaper to just build the text and
371 # diff it.
371 # diff it.
372 arraytext = array.array('c', self.text())
372 arraytext = array.array('c', self.text())
373 deltatext = mdiff.textdiff(base, arraytext)
373 deltatext = mdiff.textdiff(base, arraytext)
374
374
375 return arraytext, deltatext
375 return arraytext, deltatext
376
376
377 def _msearch(m, s, lo=0, hi=None):
377 def _msearch(m, s, lo=0, hi=None):
378 '''return a tuple (start, end) that says where to find s within m.
378 '''return a tuple (start, end) that says where to find s within m.
379
379
380 If the string is found m[start:end] are the line containing
380 If the string is found m[start:end] are the line containing
381 that string. If start == end the string was not found and
381 that string. If start == end the string was not found and
382 they indicate the proper sorted insertion point.
382 they indicate the proper sorted insertion point.
383
383
384 m should be a buffer or a string
384 m should be a buffer or a string
385 s is a string'''
385 s is a string'''
386 def advance(i, c):
386 def advance(i, c):
387 while i < lenm and m[i] != c:
387 while i < lenm and m[i] != c:
388 i += 1
388 i += 1
389 return i
389 return i
390 if not s:
390 if not s:
391 return (lo, lo)
391 return (lo, lo)
392 lenm = len(m)
392 lenm = len(m)
393 if not hi:
393 if not hi:
394 hi = lenm
394 hi = lenm
395 while lo < hi:
395 while lo < hi:
396 mid = (lo + hi) // 2
396 mid = (lo + hi) // 2
397 start = mid
397 start = mid
398 while start > 0 and m[start - 1] != '\n':
398 while start > 0 and m[start - 1] != '\n':
399 start -= 1
399 start -= 1
400 end = advance(start, '\0')
400 end = advance(start, '\0')
401 if m[start:end] < s:
401 if m[start:end] < s:
402 # we know that after the null there are 40 bytes of sha1
402 # we know that after the null there are 40 bytes of sha1
403 # this translates to the bisect lo = mid + 1
403 # this translates to the bisect lo = mid + 1
404 lo = advance(end + 40, '\n') + 1
404 lo = advance(end + 40, '\n') + 1
405 else:
405 else:
406 # this translates to the bisect hi = mid
406 # this translates to the bisect hi = mid
407 hi = start
407 hi = start
408 end = advance(lo, '\0')
408 end = advance(lo, '\0')
409 found = m[lo:end]
409 found = m[lo:end]
410 if s == found:
410 if s == found:
411 # we know that after the null there are 40 bytes of sha1
411 # we know that after the null there are 40 bytes of sha1
412 end = advance(end + 40, '\n')
412 end = advance(end + 40, '\n')
413 return (lo, end + 1)
413 return (lo, end + 1)
414 else:
414 else:
415 return (lo, lo)
415 return (lo, lo)
416
416
417 def _checkforbidden(l):
417 def _checkforbidden(l):
418 """Check filenames for illegal characters."""
418 """Check filenames for illegal characters."""
419 for f in l:
419 for f in l:
420 if '\n' in f or '\r' in f:
420 if '\n' in f or '\r' in f:
421 raise error.RevlogError(
421 raise error.RevlogError(
422 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
422 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
423
423
424
424
425 # apply the changes collected during the bisect loop to our addlist
425 # apply the changes collected during the bisect loop to our addlist
426 # return a delta suitable for addrevision
426 # return a delta suitable for addrevision
427 def _addlistdelta(addlist, x):
427 def _addlistdelta(addlist, x):
428 # for large addlist arrays, building a new array is cheaper
428 # for large addlist arrays, building a new array is cheaper
429 # than repeatedly modifying the existing one
429 # than repeatedly modifying the existing one
430 currentposition = 0
430 currentposition = 0
431 newaddlist = array.array('c')
431 newaddlist = array.array('c')
432
432
433 for start, end, content in x:
433 for start, end, content in x:
434 newaddlist += addlist[currentposition:start]
434 newaddlist += addlist[currentposition:start]
435 if content:
435 if content:
436 newaddlist += array.array('c', content)
436 newaddlist += array.array('c', content)
437
437
438 currentposition = end
438 currentposition = end
439
439
440 newaddlist += addlist[currentposition:]
440 newaddlist += addlist[currentposition:]
441
441
442 deltatext = "".join(struct.pack(">lll", start, end, len(content))
442 deltatext = "".join(struct.pack(">lll", start, end, len(content))
443 + content for start, end, content in x)
443 + content for start, end, content in x)
444 return deltatext, newaddlist
444 return deltatext, newaddlist
445
445
446 def _splittopdir(f):
446 def _splittopdir(f):
447 if '/' in f:
447 if '/' in f:
448 dir, subpath = f.split('/', 1)
448 dir, subpath = f.split('/', 1)
449 return dir + '/', subpath
449 return dir + '/', subpath
450 else:
450 else:
451 return '', f
451 return '', f
452
452
453 _noop = lambda s: None
453 _noop = lambda s: None
454
454
455 class treemanifest(object):
455 class treemanifest(object):
456 def __init__(self, dir='', text=''):
456 def __init__(self, dir='', text=''):
457 self._dir = dir
457 self._dir = dir
458 self._node = revlog.nullid
458 self._node = revlog.nullid
459 self._loadfunc = _noop
459 self._loadfunc = _noop
460 self._copyfunc = _noop
460 self._copyfunc = _noop
461 self._dirty = False
461 self._dirty = False
462 self._dirs = {}
462 self._dirs = {}
463 # Using _lazymanifest here is a little slower than plain old dicts
463 # Using _lazymanifest here is a little slower than plain old dicts
464 self._files = {}
464 self._files = {}
465 self._flags = {}
465 self._flags = {}
466 if text:
466 if text:
467 def readsubtree(subdir, subm):
467 def readsubtree(subdir, subm):
468 raise AssertionError('treemanifest constructor only accepts '
468 raise AssertionError('treemanifest constructor only accepts '
469 'flat manifests')
469 'flat manifests')
470 self.parse(text, readsubtree)
470 self.parse(text, readsubtree)
471 self._dirty = True # Mark flat manifest dirty after parsing
471 self._dirty = True # Mark flat manifest dirty after parsing
472
472
473 def _subpath(self, path):
473 def _subpath(self, path):
474 return self._dir + path
474 return self._dir + path
475
475
476 def __len__(self):
476 def __len__(self):
477 self._load()
477 self._load()
478 size = len(self._files)
478 size = len(self._files)
479 for m in self._dirs.values():
479 for m in self._dirs.values():
480 size += m.__len__()
480 size += m.__len__()
481 return size
481 return size
482
482
483 def _isempty(self):
483 def _isempty(self):
484 self._load() # for consistency; already loaded by all callers
484 self._load() # for consistency; already loaded by all callers
485 return (not self._files and (not self._dirs or
485 return (not self._files and (not self._dirs or
486 all(m._isempty() for m in self._dirs.values())))
486 all(m._isempty() for m in self._dirs.values())))
487
487
488 def __repr__(self):
488 def __repr__(self):
489 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
489 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
490 (self._dir, revlog.hex(self._node),
490 (self._dir, revlog.hex(self._node),
491 bool(self._loadfunc is _noop),
491 bool(self._loadfunc is _noop),
492 self._dirty, id(self)))
492 self._dirty, id(self)))
493
493
494 def dir(self):
494 def dir(self):
495 '''The directory that this tree manifest represents, including a
495 '''The directory that this tree manifest represents, including a
496 trailing '/'. Empty string for the repo root directory.'''
496 trailing '/'. Empty string for the repo root directory.'''
497 return self._dir
497 return self._dir
498
498
499 def node(self):
499 def node(self):
500 '''This node of this instance. nullid for unsaved instances. Should
500 '''This node of this instance. nullid for unsaved instances. Should
501 be updated when the instance is read or written from a revlog.
501 be updated when the instance is read or written from a revlog.
502 '''
502 '''
503 assert not self._dirty
503 assert not self._dirty
504 return self._node
504 return self._node
505
505
506 def setnode(self, node):
506 def setnode(self, node):
507 self._node = node
507 self._node = node
508 self._dirty = False
508 self._dirty = False
509
509
510 def iteritems(self):
510 def iteritems(self):
511 self._load()
511 self._load()
512 for p, n in sorted(self._dirs.items() + self._files.items()):
512 for p, n in sorted(self._dirs.items() + self._files.items()):
513 if p in self._files:
513 if p in self._files:
514 yield self._subpath(p), n
514 yield self._subpath(p), n
515 else:
515 else:
516 for f, sn in n.iteritems():
516 for f, sn in n.iteritems():
517 yield f, sn
517 yield f, sn
518
518
519 def iterkeys(self):
519 def iterkeys(self):
520 self._load()
520 self._load()
521 for p in sorted(self._dirs.keys() + self._files.keys()):
521 for p in sorted(self._dirs.keys() + self._files.keys()):
522 if p in self._files:
522 if p in self._files:
523 yield self._subpath(p)
523 yield self._subpath(p)
524 else:
524 else:
525 for f in self._dirs[p].iterkeys():
525 for f in self._dirs[p].iterkeys():
526 yield f
526 yield f
527
527
528 def keys(self):
528 def keys(self):
529 return list(self.iterkeys())
529 return list(self.iterkeys())
530
530
531 def __iter__(self):
531 def __iter__(self):
532 return self.iterkeys()
532 return self.iterkeys()
533
533
534 def __contains__(self, f):
534 def __contains__(self, f):
535 if f is None:
535 if f is None:
536 return False
536 return False
537 self._load()
537 self._load()
538 dir, subpath = _splittopdir(f)
538 dir, subpath = _splittopdir(f)
539 if dir:
539 if dir:
540 if dir not in self._dirs:
540 if dir not in self._dirs:
541 return False
541 return False
542 return self._dirs[dir].__contains__(subpath)
542 return self._dirs[dir].__contains__(subpath)
543 else:
543 else:
544 return f in self._files
544 return f in self._files
545
545
546 def get(self, f, default=None):
546 def get(self, f, default=None):
547 self._load()
547 self._load()
548 dir, subpath = _splittopdir(f)
548 dir, subpath = _splittopdir(f)
549 if dir:
549 if dir:
550 if dir not in self._dirs:
550 if dir not in self._dirs:
551 return default
551 return default
552 return self._dirs[dir].get(subpath, default)
552 return self._dirs[dir].get(subpath, default)
553 else:
553 else:
554 return self._files.get(f, default)
554 return self._files.get(f, default)
555
555
556 def __getitem__(self, f):
556 def __getitem__(self, f):
557 self._load()
557 self._load()
558 dir, subpath = _splittopdir(f)
558 dir, subpath = _splittopdir(f)
559 if dir:
559 if dir:
560 return self._dirs[dir].__getitem__(subpath)
560 return self._dirs[dir].__getitem__(subpath)
561 else:
561 else:
562 return self._files[f]
562 return self._files[f]
563
563
564 def flags(self, f):
564 def flags(self, f):
565 self._load()
565 self._load()
566 dir, subpath = _splittopdir(f)
566 dir, subpath = _splittopdir(f)
567 if dir:
567 if dir:
568 if dir not in self._dirs:
568 if dir not in self._dirs:
569 return ''
569 return ''
570 return self._dirs[dir].flags(subpath)
570 return self._dirs[dir].flags(subpath)
571 else:
571 else:
572 if f in self._dirs:
572 if f in self._dirs:
573 return ''
573 return ''
574 return self._flags.get(f, '')
574 return self._flags.get(f, '')
575
575
576 def find(self, f):
576 def find(self, f):
577 self._load()
577 self._load()
578 dir, subpath = _splittopdir(f)
578 dir, subpath = _splittopdir(f)
579 if dir:
579 if dir:
580 return self._dirs[dir].find(subpath)
580 return self._dirs[dir].find(subpath)
581 else:
581 else:
582 return self._files[f], self._flags.get(f, '')
582 return self._files[f], self._flags.get(f, '')
583
583
584 def __delitem__(self, f):
584 def __delitem__(self, f):
585 self._load()
585 self._load()
586 dir, subpath = _splittopdir(f)
586 dir, subpath = _splittopdir(f)
587 if dir:
587 if dir:
588 self._dirs[dir].__delitem__(subpath)
588 self._dirs[dir].__delitem__(subpath)
589 # If the directory is now empty, remove it
589 # If the directory is now empty, remove it
590 if self._dirs[dir]._isempty():
590 if self._dirs[dir]._isempty():
591 del self._dirs[dir]
591 del self._dirs[dir]
592 else:
592 else:
593 del self._files[f]
593 del self._files[f]
594 if f in self._flags:
594 if f in self._flags:
595 del self._flags[f]
595 del self._flags[f]
596 self._dirty = True
596 self._dirty = True
597
597
598 def __setitem__(self, f, n):
598 def __setitem__(self, f, n):
599 assert n is not None
599 assert n is not None
600 self._load()
600 self._load()
601 dir, subpath = _splittopdir(f)
601 dir, subpath = _splittopdir(f)
602 if dir:
602 if dir:
603 if dir not in self._dirs:
603 if dir not in self._dirs:
604 self._dirs[dir] = treemanifest(self._subpath(dir))
604 self._dirs[dir] = treemanifest(self._subpath(dir))
605 self._dirs[dir].__setitem__(subpath, n)
605 self._dirs[dir].__setitem__(subpath, n)
606 else:
606 else:
607 self._files[f] = n[:21] # to match manifestdict's behavior
607 self._files[f] = n[:21] # to match manifestdict's behavior
608 self._dirty = True
608 self._dirty = True
609
609
610 def _load(self):
610 def _load(self):
611 if self._loadfunc is not _noop:
611 if self._loadfunc is not _noop:
612 lf, self._loadfunc = self._loadfunc, _noop
612 lf, self._loadfunc = self._loadfunc, _noop
613 lf(self)
613 lf(self)
614 elif self._copyfunc is not _noop:
614 elif self._copyfunc is not _noop:
615 cf, self._copyfunc = self._copyfunc, _noop
615 cf, self._copyfunc = self._copyfunc, _noop
616 cf(self)
616 cf(self)
617
617
618 def setflag(self, f, flags):
618 def setflag(self, f, flags):
619 """Set the flags (symlink, executable) for path f."""
619 """Set the flags (symlink, executable) for path f."""
620 assert 't' not in flags
620 assert 't' not in flags
621 self._load()
621 self._load()
622 dir, subpath = _splittopdir(f)
622 dir, subpath = _splittopdir(f)
623 if dir:
623 if dir:
624 if dir not in self._dirs:
624 if dir not in self._dirs:
625 self._dirs[dir] = treemanifest(self._subpath(dir))
625 self._dirs[dir] = treemanifest(self._subpath(dir))
626 self._dirs[dir].setflag(subpath, flags)
626 self._dirs[dir].setflag(subpath, flags)
627 else:
627 else:
628 self._flags[f] = flags
628 self._flags[f] = flags
629 self._dirty = True
629 self._dirty = True
630
630
631 def copy(self):
631 def copy(self):
632 copy = treemanifest(self._dir)
632 copy = treemanifest(self._dir)
633 copy._node = self._node
633 copy._node = self._node
634 copy._dirty = self._dirty
634 copy._dirty = self._dirty
635 if self._copyfunc is _noop:
635 if self._copyfunc is _noop:
636 def _copyfunc(s):
636 def _copyfunc(s):
637 self._load()
637 self._load()
638 for d in self._dirs:
638 for d in self._dirs:
639 s._dirs[d] = self._dirs[d].copy()
639 s._dirs[d] = self._dirs[d].copy()
640 s._files = dict.copy(self._files)
640 s._files = dict.copy(self._files)
641 s._flags = dict.copy(self._flags)
641 s._flags = dict.copy(self._flags)
642 if self._loadfunc is _noop:
642 if self._loadfunc is _noop:
643 _copyfunc(copy)
643 _copyfunc(copy)
644 else:
644 else:
645 copy._copyfunc = _copyfunc
645 copy._copyfunc = _copyfunc
646 else:
646 else:
647 copy._copyfunc = self._copyfunc
647 copy._copyfunc = self._copyfunc
648 return copy
648 return copy
649
649
650 def filesnotin(self, m2):
650 def filesnotin(self, m2):
651 '''Set of files in this manifest that are not in the other'''
651 '''Set of files in this manifest that are not in the other'''
652 files = set()
652 files = set()
653 def _filesnotin(t1, t2):
653 def _filesnotin(t1, t2):
654 if t1._node == t2._node and not t1._dirty and not t2._dirty:
654 if t1._node == t2._node and not t1._dirty and not t2._dirty:
655 return
655 return
656 t1._load()
656 t1._load()
657 t2._load()
657 t2._load()
658 for d, m1 in t1._dirs.iteritems():
658 for d, m1 in t1._dirs.iteritems():
659 if d in t2._dirs:
659 if d in t2._dirs:
660 m2 = t2._dirs[d]
660 m2 = t2._dirs[d]
661 _filesnotin(m1, m2)
661 _filesnotin(m1, m2)
662 else:
662 else:
663 files.update(m1.iterkeys())
663 files.update(m1.iterkeys())
664
664
665 for fn in t1._files.iterkeys():
665 for fn in t1._files.iterkeys():
666 if fn not in t2._files:
666 if fn not in t2._files:
667 files.add(t1._subpath(fn))
667 files.add(t1._subpath(fn))
668
668
669 _filesnotin(self, m2)
669 _filesnotin(self, m2)
670 return files
670 return files
671
671
672 @propertycache
672 @propertycache
673 def _alldirs(self):
673 def _alldirs(self):
674 return util.dirs(self)
674 return util.dirs(self)
675
675
676 def dirs(self):
676 def dirs(self):
677 return self._alldirs
677 return self._alldirs
678
678
679 def hasdir(self, dir):
679 def hasdir(self, dir):
680 self._load()
680 self._load()
681 topdir, subdir = _splittopdir(dir)
681 topdir, subdir = _splittopdir(dir)
682 if topdir:
682 if topdir:
683 if topdir in self._dirs:
683 if topdir in self._dirs:
684 return self._dirs[topdir].hasdir(subdir)
684 return self._dirs[topdir].hasdir(subdir)
685 return False
685 return False
686 return (dir + '/') in self._dirs
686 return (dir + '/') in self._dirs
687
687
688 def walk(self, match):
688 def walk(self, match):
689 '''Generates matching file names.
689 '''Generates matching file names.
690
690
691 Equivalent to manifest.matches(match).iterkeys(), but without creating
691 Equivalent to manifest.matches(match).iterkeys(), but without creating
692 an entirely new manifest.
692 an entirely new manifest.
693
693
694 It also reports nonexistent files by marking them bad with match.bad().
694 It also reports nonexistent files by marking them bad with match.bad().
695 '''
695 '''
696 if match.always():
696 if match.always():
697 for f in iter(self):
697 for f in iter(self):
698 yield f
698 yield f
699 return
699 return
700
700
701 fset = set(match.files())
701 fset = set(match.files())
702
702
703 for fn in self._walk(match):
703 for fn in self._walk(match):
704 if fn in fset:
704 if fn in fset:
705 # specified pattern is the exact name
705 # specified pattern is the exact name
706 fset.remove(fn)
706 fset.remove(fn)
707 yield fn
707 yield fn
708
708
709 # for dirstate.walk, files=['.'] means "walk the whole tree".
709 # for dirstate.walk, files=['.'] means "walk the whole tree".
710 # follow that here, too
710 # follow that here, too
711 fset.discard('.')
711 fset.discard('.')
712
712
713 for fn in sorted(fset):
713 for fn in sorted(fset):
714 if not self.hasdir(fn):
714 if not self.hasdir(fn):
715 match.bad(fn, None)
715 match.bad(fn, None)
716
716
717 def _walk(self, match):
717 def _walk(self, match):
718 '''Recursively generates matching file names for walk().'''
718 '''Recursively generates matching file names for walk().'''
719 if not match.visitdir(self._dir[:-1] or '.'):
719 if not match.visitdir(self._dir[:-1] or '.'):
720 return
720 return
721
721
722 # yield this dir's files and walk its submanifests
722 # yield this dir's files and walk its submanifests
723 self._load()
723 self._load()
724 for p in sorted(self._dirs.keys() + self._files.keys()):
724 for p in sorted(self._dirs.keys() + self._files.keys()):
725 if p in self._files:
725 if p in self._files:
726 fullp = self._subpath(p)
726 fullp = self._subpath(p)
727 if match(fullp):
727 if match(fullp):
728 yield fullp
728 yield fullp
729 else:
729 else:
730 for f in self._dirs[p]._walk(match):
730 for f in self._dirs[p]._walk(match):
731 yield f
731 yield f
732
732
733 def matches(self, match):
733 def matches(self, match):
734 '''generate a new manifest filtered by the match argument'''
734 '''generate a new manifest filtered by the match argument'''
735 if match.always():
735 if match.always():
736 return self.copy()
736 return self.copy()
737
737
738 return self._matches(match)
738 return self._matches(match)
739
739
740 def _matches(self, match):
740 def _matches(self, match):
741 '''recursively generate a new manifest filtered by the match argument.
741 '''recursively generate a new manifest filtered by the match argument.
742 '''
742 '''
743
743
744 visit = match.visitdir(self._dir[:-1] or '.')
744 visit = match.visitdir(self._dir[:-1] or '.')
745 if visit == 'all':
745 if visit == 'all':
746 return self.copy()
746 return self.copy()
747 ret = treemanifest(self._dir)
747 ret = treemanifest(self._dir)
748 if not visit:
748 if not visit:
749 return ret
749 return ret
750
750
751 self._load()
751 self._load()
752 for fn in self._files:
752 for fn in self._files:
753 fullp = self._subpath(fn)
753 fullp = self._subpath(fn)
754 if not match(fullp):
754 if not match(fullp):
755 continue
755 continue
756 ret._files[fn] = self._files[fn]
756 ret._files[fn] = self._files[fn]
757 if fn in self._flags:
757 if fn in self._flags:
758 ret._flags[fn] = self._flags[fn]
758 ret._flags[fn] = self._flags[fn]
759
759
760 for dir, subm in self._dirs.iteritems():
760 for dir, subm in self._dirs.iteritems():
761 m = subm._matches(match)
761 m = subm._matches(match)
762 if not m._isempty():
762 if not m._isempty():
763 ret._dirs[dir] = m
763 ret._dirs[dir] = m
764
764
765 if not ret._isempty():
765 if not ret._isempty():
766 ret._dirty = True
766 ret._dirty = True
767 return ret
767 return ret
768
768
769 def diff(self, m2, clean=False):
769 def diff(self, m2, clean=False):
770 '''Finds changes between the current manifest and m2.
770 '''Finds changes between the current manifest and m2.
771
771
772 Args:
772 Args:
773 m2: the manifest to which this manifest should be compared.
773 m2: the manifest to which this manifest should be compared.
774 clean: if true, include files unchanged between these manifests
774 clean: if true, include files unchanged between these manifests
775 with a None value in the returned dictionary.
775 with a None value in the returned dictionary.
776
776
777 The result is returned as a dict with filename as key and
777 The result is returned as a dict with filename as key and
778 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
778 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
779 nodeid in the current/other manifest and fl1/fl2 is the flag
779 nodeid in the current/other manifest and fl1/fl2 is the flag
780 in the current/other manifest. Where the file does not exist,
780 in the current/other manifest. Where the file does not exist,
781 the nodeid will be None and the flags will be the empty
781 the nodeid will be None and the flags will be the empty
782 string.
782 string.
783 '''
783 '''
784 result = {}
784 result = {}
785 emptytree = treemanifest()
785 emptytree = treemanifest()
786 def _diff(t1, t2):
786 def _diff(t1, t2):
787 if t1._node == t2._node and not t1._dirty and not t2._dirty:
787 if t1._node == t2._node and not t1._dirty and not t2._dirty:
788 return
788 return
789 t1._load()
789 t1._load()
790 t2._load()
790 t2._load()
791 for d, m1 in t1._dirs.iteritems():
791 for d, m1 in t1._dirs.iteritems():
792 m2 = t2._dirs.get(d, emptytree)
792 m2 = t2._dirs.get(d, emptytree)
793 _diff(m1, m2)
793 _diff(m1, m2)
794
794
795 for d, m2 in t2._dirs.iteritems():
795 for d, m2 in t2._dirs.iteritems():
796 if d not in t1._dirs:
796 if d not in t1._dirs:
797 _diff(emptytree, m2)
797 _diff(emptytree, m2)
798
798
799 for fn, n1 in t1._files.iteritems():
799 for fn, n1 in t1._files.iteritems():
800 fl1 = t1._flags.get(fn, '')
800 fl1 = t1._flags.get(fn, '')
801 n2 = t2._files.get(fn, None)
801 n2 = t2._files.get(fn, None)
802 fl2 = t2._flags.get(fn, '')
802 fl2 = t2._flags.get(fn, '')
803 if n1 != n2 or fl1 != fl2:
803 if n1 != n2 or fl1 != fl2:
804 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
804 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
805 elif clean:
805 elif clean:
806 result[t1._subpath(fn)] = None
806 result[t1._subpath(fn)] = None
807
807
808 for fn, n2 in t2._files.iteritems():
808 for fn, n2 in t2._files.iteritems():
809 if fn not in t1._files:
809 if fn not in t1._files:
810 fl2 = t2._flags.get(fn, '')
810 fl2 = t2._flags.get(fn, '')
811 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
811 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
812
812
813 _diff(self, m2)
813 _diff(self, m2)
814 return result
814 return result
815
815
816 def unmodifiedsince(self, m2):
816 def unmodifiedsince(self, m2):
817 return not self._dirty and not m2._dirty and self._node == m2._node
817 return not self._dirty and not m2._dirty and self._node == m2._node
818
818
819 def parse(self, text, readsubtree):
819 def parse(self, text, readsubtree):
820 for f, n, fl in _parse(text):
820 for f, n, fl in _parse(text):
821 if fl == 't':
821 if fl == 't':
822 f = f + '/'
822 f = f + '/'
823 self._dirs[f] = readsubtree(self._subpath(f), n)
823 self._dirs[f] = readsubtree(self._subpath(f), n)
824 elif '/' in f:
824 elif '/' in f:
825 # This is a flat manifest, so use __setitem__ and setflag rather
825 # This is a flat manifest, so use __setitem__ and setflag rather
826 # than assigning directly to _files and _flags, so we can
826 # than assigning directly to _files and _flags, so we can
827 # assign a path in a subdirectory, and to mark dirty (compared
827 # assign a path in a subdirectory, and to mark dirty (compared
828 # to nullid).
828 # to nullid).
829 self[f] = n
829 self[f] = n
830 if fl:
830 if fl:
831 self.setflag(f, fl)
831 self.setflag(f, fl)
832 else:
832 else:
833 # Assigning to _files and _flags avoids marking as dirty,
833 # Assigning to _files and _flags avoids marking as dirty,
834 # and should be a little faster.
834 # and should be a little faster.
835 self._files[f] = n
835 self._files[f] = n
836 if fl:
836 if fl:
837 self._flags[f] = fl
837 self._flags[f] = fl
838
838
839 def text(self, usemanifestv2=False):
839 def text(self, usemanifestv2=False):
840 """Get the full data of this manifest as a bytestring."""
840 """Get the full data of this manifest as a bytestring."""
841 self._load()
841 self._load()
842 flags = self.flags
842 flags = self.flags
843 return _text(((f, self[f], flags(f)) for f in self.keys()),
843 return _text(((f, self[f], flags(f)) for f in self.keys()),
844 usemanifestv2)
844 usemanifestv2)
845
845
846 def dirtext(self, usemanifestv2=False):
846 def dirtext(self, usemanifestv2=False):
847 """Get the full data of this directory as a bytestring. Make sure that
847 """Get the full data of this directory as a bytestring. Make sure that
848 any submanifests have been written first, so their nodeids are correct.
848 any submanifests have been written first, so their nodeids are correct.
849 """
849 """
850 self._load()
850 self._load()
851 flags = self.flags
851 flags = self.flags
852 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
852 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
853 files = [(f, self._files[f], flags(f)) for f in self._files]
853 files = [(f, self._files[f], flags(f)) for f in self._files]
854 return _text(sorted(dirs + files), usemanifestv2)
854 return _text(sorted(dirs + files), usemanifestv2)
855
855
856 def read(self, gettext, readsubtree):
856 def read(self, gettext, readsubtree):
857 def _load_for_read(s):
857 def _load_for_read(s):
858 s.parse(gettext(), readsubtree)
858 s.parse(gettext(), readsubtree)
859 s._dirty = False
859 s._dirty = False
860 self._loadfunc = _load_for_read
860 self._loadfunc = _load_for_read
861
861
862 def writesubtrees(self, m1, m2, writesubtree):
862 def writesubtrees(self, m1, m2, writesubtree):
863 self._load() # for consistency; should never have any effect here
863 self._load() # for consistency; should never have any effect here
864 emptytree = treemanifest()
864 emptytree = treemanifest()
865 for d, subm in self._dirs.iteritems():
865 for d, subm in self._dirs.iteritems():
866 subp1 = m1._dirs.get(d, emptytree)._node
866 subp1 = m1._dirs.get(d, emptytree)._node
867 subp2 = m2._dirs.get(d, emptytree)._node
867 subp2 = m2._dirs.get(d, emptytree)._node
868 if subp1 == revlog.nullid:
868 if subp1 == revlog.nullid:
869 subp1, subp2 = subp2, subp1
869 subp1, subp2 = subp2, subp1
870 writesubtree(subm, subp1, subp2)
870 writesubtree(subm, subp1, subp2)
871
871
872 class manifest(revlog.revlog):
872 class manifest(revlog.revlog):
873 def __init__(self, opener, dir='', dirlogcache=None):
873 def __init__(self, opener, dir='', dirlogcache=None):
874 '''The 'dir' and 'dirlogcache' arguments are for internal use by
874 '''The 'dir' and 'dirlogcache' arguments are for internal use by
875 manifest.manifest only. External users should create a root manifest
875 manifest.manifest only. External users should create a root manifest
876 log with manifest.manifest(opener) and call dirlog() on it.
876 log with manifest.manifest(opener) and call dirlog() on it.
877 '''
877 '''
878 # During normal operations, we expect to deal with not more than four
878 # During normal operations, we expect to deal with not more than four
879 # revs at a time (such as during commit --amend). When rebasing large
879 # revs at a time (such as during commit --amend). When rebasing large
880 # stacks of commits, the number can go up, hence the config knob below.
880 # stacks of commits, the number can go up, hence the config knob below.
881 cachesize = 4
881 cachesize = 4
882 usetreemanifest = False
882 usetreemanifest = False
883 usemanifestv2 = False
883 usemanifestv2 = False
884 opts = getattr(opener, 'options', None)
884 opts = getattr(opener, 'options', None)
885 if opts is not None:
885 if opts is not None:
886 cachesize = opts.get('manifestcachesize', cachesize)
886 cachesize = opts.get('manifestcachesize', cachesize)
887 usetreemanifest = opts.get('treemanifest', usetreemanifest)
887 usetreemanifest = opts.get('treemanifest', usetreemanifest)
888 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
888 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
889 self._mancache = util.lrucachedict(cachesize)
889 self._mancache = util.lrucachedict(cachesize)
890 self._treeinmem = usetreemanifest
890 self._treeinmem = usetreemanifest
891 self._treeondisk = usetreemanifest
891 self._treeondisk = usetreemanifest
892 self._usemanifestv2 = usemanifestv2
892 self._usemanifestv2 = usemanifestv2
893 indexfile = "00manifest.i"
893 indexfile = "00manifest.i"
894 if dir:
894 if dir:
895 assert self._treeondisk
895 assert self._treeondisk
896 if not dir.endswith('/'):
896 if not dir.endswith('/'):
897 dir = dir + '/'
897 dir = dir + '/'
898 indexfile = "meta/" + dir + "00manifest.i"
898 indexfile = "meta/" + dir + "00manifest.i"
899 revlog.revlog.__init__(self, opener, indexfile)
899 revlog.revlog.__init__(self, opener, indexfile)
900 self._dir = dir
900 self._dir = dir
901 # The dirlogcache is kept on the root manifest log
901 # The dirlogcache is kept on the root manifest log
902 if dir:
902 if dir:
903 self._dirlogcache = dirlogcache
903 self._dirlogcache = dirlogcache
904 else:
904 else:
905 self._dirlogcache = {'': self}
905 self._dirlogcache = {'': self}
906
906
907 def _newmanifest(self, data=''):
907 def _newmanifest(self, data=''):
908 if self._treeinmem:
908 if self._treeinmem:
909 return treemanifest(self._dir, data)
909 return treemanifest(self._dir, data)
910 return manifestdict(data)
910 return manifestdict(data)
911
911
912 def dirlog(self, dir):
912 def dirlog(self, dir):
913 assert self._treeondisk
913 assert self._treeondisk
914 if dir not in self._dirlogcache:
914 if dir not in self._dirlogcache:
915 self._dirlogcache[dir] = manifest(self.opener, dir,
915 self._dirlogcache[dir] = manifest(self.opener, dir,
916 self._dirlogcache)
916 self._dirlogcache)
917 return self._dirlogcache[dir]
917 return self._dirlogcache[dir]
918
918
919 def _slowreaddelta(self, node):
919 def _slowreaddelta(self, node):
920 r0 = self.deltaparent(self.rev(node))
920 r0 = self.deltaparent(self.rev(node))
921 m0 = self.read(self.node(r0))
921 m0 = self.read(self.node(r0))
922 m1 = self.read(node)
922 m1 = self.read(node)
923 md = self._newmanifest()
923 md = self._newmanifest()
924 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
924 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
925 if n1:
925 if n1:
926 md[f] = n1
926 md[f] = n1
927 if fl1:
927 if fl1:
928 md.setflag(f, fl1)
928 md.setflag(f, fl1)
929 return md
929 return md
930
930
931 def readdelta(self, node):
931 def readdelta(self, node):
932 if self._usemanifestv2 or self._treeondisk:
932 if self._usemanifestv2 or self._treeondisk:
933 return self._slowreaddelta(node)
933 return self._slowreaddelta(node)
934 r = self.rev(node)
934 r = self.rev(node)
935 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
935 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
936 return self._newmanifest(d)
936 return self._newmanifest(d)
937
937
938 def readfast(self, node):
938 def readfast(self, node):
939 '''use the faster of readdelta or read
939 '''use the faster of readdelta or read
940
940
941 This will return a manifest which is either only the files
941 This will return a manifest which is either only the files
942 added/modified relative to p1, or all files in the
942 added/modified relative to p1, or all files in the
943 manifest. Which one is returned depends on the codepath used
943 manifest. Which one is returned depends on the codepath used
944 to retrieve the data.
944 to retrieve the data.
945 '''
945 '''
946 r = self.rev(node)
946 r = self.rev(node)
947 deltaparent = self.deltaparent(r)
947 deltaparent = self.deltaparent(r)
948 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
948 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
949 return self.readdelta(node)
949 return self.readdelta(node)
950 return self.read(node)
950 return self.read(node)
951
951
952 def read(self, node):
952 def read(self, node):
953 if node == revlog.nullid:
953 if node == revlog.nullid:
954 return self._newmanifest() # don't upset local cache
954 return self._newmanifest() # don't upset local cache
955 if node in self._mancache:
955 if node in self._mancache:
956 return self._mancache[node][0]
956 return self._mancache[node][0]
957 if self._treeondisk:
957 if self._treeondisk:
958 def gettext():
958 def gettext():
959 return self.revision(node)
959 return self.revision(node)
960 def readsubtree(dir, subm):
960 def readsubtree(dir, subm):
961 return self.dirlog(dir).read(subm)
961 return self.dirlog(dir).read(subm)
962 m = self._newmanifest()
962 m = self._newmanifest()
963 m.read(gettext, readsubtree)
963 m.read(gettext, readsubtree)
964 m.setnode(node)
964 m.setnode(node)
965 arraytext = None
965 arraytext = None
966 else:
966 else:
967 text = self.revision(node)
967 text = self.revision(node)
968 m = self._newmanifest(text)
968 m = self._newmanifest(text)
969 arraytext = array.array('c', text)
969 arraytext = array.array('c', text)
970 self._mancache[node] = (m, arraytext)
970 self._mancache[node] = (m, arraytext)
971 return m
971 return m
972
972
973 def find(self, node, f):
973 def find(self, node, f):
974 '''look up entry for a single file efficiently.
974 '''look up entry for a single file efficiently.
975 return (node, flags) pair if found, (None, None) if not.'''
975 return (node, flags) pair if found, (None, None) if not.'''
976 m = self.read(node)
976 m = self.read(node)
977 try:
977 try:
978 return m.find(f)
978 return m.find(f)
979 except KeyError:
979 except KeyError:
980 return None, None
980 return None, None
981
981
982 def add(self, m, transaction, link, p1, p2, added, removed):
982 def add(self, m, transaction, link, p1, p2, added, removed):
983 if (p1 in self._mancache and not self._treeinmem
983 if (p1 in self._mancache and not self._treeinmem
984 and not self._usemanifestv2):
984 and not self._usemanifestv2):
985 # If our first parent is in the manifest cache, we can
985 # If our first parent is in the manifest cache, we can
986 # compute a delta here using properties we know about the
986 # compute a delta here using properties we know about the
987 # manifest up-front, which may save time later for the
987 # manifest up-front, which may save time later for the
988 # revlog layer.
988 # revlog layer.
989
989
990 _checkforbidden(added)
990 _checkforbidden(added)
991 # combine the changed lists into one sorted iterator
991 # combine the changed lists into one sorted iterator
992 work = heapq.merge([(x, False) for x in added],
992 work = heapq.merge([(x, False) for x in added],
993 [(x, True) for x in removed])
993 [(x, True) for x in removed])
994
994
995 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
995 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
996 cachedelta = self.rev(p1), deltatext
996 cachedelta = self.rev(p1), deltatext
997 text = util.buffer(arraytext)
997 text = util.buffer(arraytext)
998 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
998 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
999 else:
999 else:
1000 # The first parent manifest isn't already loaded, so we'll
1000 # The first parent manifest isn't already loaded, so we'll
1001 # just encode a fulltext of the manifest and pass that
1001 # just encode a fulltext of the manifest and pass that
1002 # through to the revlog layer, and let it handle the delta
1002 # through to the revlog layer, and let it handle the delta
1003 # process.
1003 # process.
1004 if self._treeondisk:
1004 if self._treeondisk:
1005 m1 = self.read(p1)
1005 m1 = self.read(p1)
1006 m2 = self.read(p2)
1006 m2 = self.read(p2)
1007 n = self._addtree(m, transaction, link, m1, m2)
1007 n = self._addtree(m, transaction, link, m1, m2)
1008 arraytext = None
1008 arraytext = None
1009 else:
1009 else:
1010 text = m.text(self._usemanifestv2)
1010 text = m.text(self._usemanifestv2)
1011 n = self.addrevision(text, transaction, link, p1, p2)
1011 n = self.addrevision(text, transaction, link, p1, p2)
1012 arraytext = array.array('c', text)
1012 arraytext = array.array('c', text)
1013
1013
1014 self._mancache[n] = (m, arraytext)
1014 self._mancache[n] = (m, arraytext)
1015
1015
1016 return n
1016 return n
1017
1017
1018 def _addtree(self, m, transaction, link, m1, m2):
1018 def _addtree(self, m, transaction, link, m1, m2):
1019 # If the manifest is unchanged compared to one parent,
1019 # If the manifest is unchanged compared to one parent,
1020 # don't write a new revision
1020 # don't write a new revision
1021 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1021 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1022 return m.node()
1022 return m.node()
1023 def writesubtree(subm, subp1, subp2):
1023 def writesubtree(subm, subp1, subp2):
1024 sublog = self.dirlog(subm.dir())
1024 sublog = self.dirlog(subm.dir())
1025 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1025 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1026 m.writesubtrees(m1, m2, writesubtree)
1026 m.writesubtrees(m1, m2, writesubtree)
1027 text = m.dirtext(self._usemanifestv2)
1027 text = m.dirtext(self._usemanifestv2)
1028 # Double-check whether contents are unchanged to one parent
1028 # Double-check whether contents are unchanged to one parent
1029 if text == m1.dirtext(self._usemanifestv2):
1029 if text == m1.dirtext(self._usemanifestv2):
1030 n = m1.node()
1030 n = m1.node()
1031 elif text == m2.dirtext(self._usemanifestv2):
1031 elif text == m2.dirtext(self._usemanifestv2):
1032 n = m2.node()
1032 n = m2.node()
1033 else:
1033 else:
1034 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1034 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1035 # Save nodeid so parent manifest can calculate its nodeid
1035 # Save nodeid so parent manifest can calculate its nodeid
1036 m.setnode(n)
1036 m.setnode(n)
1037 return n
1037 return n
1038
1039 def clearcaches(self):
1040 super(manifest, self).clearcaches()
1041 self._mancache.clear()
1042 self._dirlogcache = {'': self}
General Comments 0
You need to be logged in to leave comments. Login now