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