##// END OF EJS Templates
treemanifest: rework lazy-copying code (issue4840)...
Augie Fackler -
r26402:05871262 default
parent child Browse files
Show More
@@ -1,1020 +1,1026 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 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 # start with a readonly loop that finds the offset of
337 # start with a readonly loop that finds the offset of
338 # each line and creates the deltas
338 # each line and creates the deltas
339 for f, todelete in changes:
339 for f, todelete in changes:
340 # bs will either be the index of the item or the insert point
340 # bs will either be the index of the item or the insert point
341 start, end = _msearch(addbuf, f, start)
341 start, end = _msearch(addbuf, f, start)
342 if not todelete:
342 if not todelete:
343 h, fl = self._lm[f]
343 h, fl = self._lm[f]
344 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
344 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
345 else:
345 else:
346 if start == end:
346 if start == end:
347 # item we want to delete was not found, error out
347 # item we want to delete was not found, error out
348 raise AssertionError(
348 raise AssertionError(
349 _("failed to remove %s from manifest") % f)
349 _("failed to remove %s from manifest") % f)
350 l = ""
350 l = ""
351 if dstart is not None and dstart <= start and dend >= start:
351 if dstart is not None and dstart <= start and dend >= start:
352 if dend < end:
352 if dend < end:
353 dend = end
353 dend = end
354 if l:
354 if l:
355 dline.append(l)
355 dline.append(l)
356 else:
356 else:
357 if dstart is not None:
357 if dstart is not None:
358 delta.append([dstart, dend, "".join(dline)])
358 delta.append([dstart, dend, "".join(dline)])
359 dstart = start
359 dstart = start
360 dend = end
360 dend = end
361 dline = [l]
361 dline = [l]
362
362
363 if dstart is not None:
363 if dstart is not None:
364 delta.append([dstart, dend, "".join(dline)])
364 delta.append([dstart, dend, "".join(dline)])
365 # apply the delta to the base, and get a delta for addrevision
365 # apply the delta to the base, and get a delta for addrevision
366 deltatext, arraytext = _addlistdelta(base, delta)
366 deltatext, arraytext = _addlistdelta(base, delta)
367 return arraytext, deltatext
367 return arraytext, deltatext
368
368
369 def _msearch(m, s, lo=0, hi=None):
369 def _msearch(m, s, lo=0, hi=None):
370 '''return a tuple (start, end) that says where to find s within m.
370 '''return a tuple (start, end) that says where to find s within m.
371
371
372 If the string is found m[start:end] are the line containing
372 If the string is found m[start:end] are the line containing
373 that string. If start == end the string was not found and
373 that string. If start == end the string was not found and
374 they indicate the proper sorted insertion point.
374 they indicate the proper sorted insertion point.
375
375
376 m should be a buffer or a string
376 m should be a buffer or a string
377 s is a string'''
377 s is a string'''
378 def advance(i, c):
378 def advance(i, c):
379 while i < lenm and m[i] != c:
379 while i < lenm and m[i] != c:
380 i += 1
380 i += 1
381 return i
381 return i
382 if not s:
382 if not s:
383 return (lo, lo)
383 return (lo, lo)
384 lenm = len(m)
384 lenm = len(m)
385 if not hi:
385 if not hi:
386 hi = lenm
386 hi = lenm
387 while lo < hi:
387 while lo < hi:
388 mid = (lo + hi) // 2
388 mid = (lo + hi) // 2
389 start = mid
389 start = mid
390 while start > 0 and m[start - 1] != '\n':
390 while start > 0 and m[start - 1] != '\n':
391 start -= 1
391 start -= 1
392 end = advance(start, '\0')
392 end = advance(start, '\0')
393 if m[start:end] < s:
393 if m[start:end] < s:
394 # we know that after the null there are 40 bytes of sha1
394 # we know that after the null there are 40 bytes of sha1
395 # this translates to the bisect lo = mid + 1
395 # this translates to the bisect lo = mid + 1
396 lo = advance(end + 40, '\n') + 1
396 lo = advance(end + 40, '\n') + 1
397 else:
397 else:
398 # this translates to the bisect hi = mid
398 # this translates to the bisect hi = mid
399 hi = start
399 hi = start
400 end = advance(lo, '\0')
400 end = advance(lo, '\0')
401 found = m[lo:end]
401 found = m[lo:end]
402 if s == found:
402 if s == found:
403 # we know that after the null there are 40 bytes of sha1
403 # we know that after the null there are 40 bytes of sha1
404 end = advance(end + 40, '\n')
404 end = advance(end + 40, '\n')
405 return (lo, end + 1)
405 return (lo, end + 1)
406 else:
406 else:
407 return (lo, lo)
407 return (lo, lo)
408
408
409 def _checkforbidden(l):
409 def _checkforbidden(l):
410 """Check filenames for illegal characters."""
410 """Check filenames for illegal characters."""
411 for f in l:
411 for f in l:
412 if '\n' in f or '\r' in f:
412 if '\n' in f or '\r' in f:
413 raise error.RevlogError(
413 raise error.RevlogError(
414 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
414 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
415
415
416
416
417 # apply the changes collected during the bisect loop to our addlist
417 # apply the changes collected during the bisect loop to our addlist
418 # return a delta suitable for addrevision
418 # return a delta suitable for addrevision
419 def _addlistdelta(addlist, x):
419 def _addlistdelta(addlist, x):
420 # for large addlist arrays, building a new array is cheaper
420 # for large addlist arrays, building a new array is cheaper
421 # than repeatedly modifying the existing one
421 # than repeatedly modifying the existing one
422 currentposition = 0
422 currentposition = 0
423 newaddlist = array.array('c')
423 newaddlist = array.array('c')
424
424
425 for start, end, content in x:
425 for start, end, content in x:
426 newaddlist += addlist[currentposition:start]
426 newaddlist += addlist[currentposition:start]
427 if content:
427 if content:
428 newaddlist += array.array('c', content)
428 newaddlist += array.array('c', content)
429
429
430 currentposition = end
430 currentposition = end
431
431
432 newaddlist += addlist[currentposition:]
432 newaddlist += addlist[currentposition:]
433
433
434 deltatext = "".join(struct.pack(">lll", start, end, len(content))
434 deltatext = "".join(struct.pack(">lll", start, end, len(content))
435 + content for start, end, content in x)
435 + content for start, end, content in x)
436 return deltatext, newaddlist
436 return deltatext, newaddlist
437
437
438 def _splittopdir(f):
438 def _splittopdir(f):
439 if '/' in f:
439 if '/' in f:
440 dir, subpath = f.split('/', 1)
440 dir, subpath = f.split('/', 1)
441 return dir + '/', subpath
441 return dir + '/', subpath
442 else:
442 else:
443 return '', f
443 return '', f
444
444
445 _noop = lambda: None
445 _noop = lambda s: None
446
446
447 class treemanifest(object):
447 class treemanifest(object):
448 def __init__(self, dir='', text=''):
448 def __init__(self, dir='', text=''):
449 self._dir = dir
449 self._dir = dir
450 self._node = revlog.nullid
450 self._node = revlog.nullid
451 self._load = _noop
451 self._loadfunc = _noop
452 self._copyfunc = _noop
452 self._dirty = False
453 self._dirty = False
453 self._dirs = {}
454 self._dirs = {}
454 # Using _lazymanifest here is a little slower than plain old dicts
455 # Using _lazymanifest here is a little slower than plain old dicts
455 self._files = {}
456 self._files = {}
456 self._flags = {}
457 self._flags = {}
457 if text:
458 if text:
458 def readsubtree(subdir, subm):
459 def readsubtree(subdir, subm):
459 raise AssertionError('treemanifest constructor only accepts '
460 raise AssertionError('treemanifest constructor only accepts '
460 'flat manifests')
461 'flat manifests')
461 self.parse(text, readsubtree)
462 self.parse(text, readsubtree)
462 self._dirty = True # Mark flat manifest dirty after parsing
463 self._dirty = True # Mark flat manifest dirty after parsing
463
464
464 def _subpath(self, path):
465 def _subpath(self, path):
465 return self._dir + path
466 return self._dir + path
466
467
467 def __len__(self):
468 def __len__(self):
468 self._load()
469 self._load()
469 size = len(self._files)
470 size = len(self._files)
470 for m in self._dirs.values():
471 for m in self._dirs.values():
471 size += m.__len__()
472 size += m.__len__()
472 return size
473 return size
473
474
474 def _isempty(self):
475 def _isempty(self):
475 self._load() # for consistency; already loaded by all callers
476 self._load() # for consistency; already loaded by all callers
476 return (not self._files and (not self._dirs or
477 return (not self._files and (not self._dirs or
477 all(m._isempty() for m in self._dirs.values())))
478 all(m._isempty() for m in self._dirs.values())))
478
479
479 def __repr__(self):
480 def __repr__(self):
480 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
481 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
481 (self._dir, revlog.hex(self._node),
482 (self._dir, revlog.hex(self._node),
482 bool(self._load is _noop),
483 bool(self._loadfunc is _noop),
483 self._dirty, id(self)))
484 self._dirty, id(self)))
484
485
485 def dir(self):
486 def dir(self):
486 '''The directory that this tree manifest represents, including a
487 '''The directory that this tree manifest represents, including a
487 trailing '/'. Empty string for the repo root directory.'''
488 trailing '/'. Empty string for the repo root directory.'''
488 return self._dir
489 return self._dir
489
490
490 def node(self):
491 def node(self):
491 '''This node of this instance. nullid for unsaved instances. Should
492 '''This node of this instance. nullid for unsaved instances. Should
492 be updated when the instance is read or written from a revlog.
493 be updated when the instance is read or written from a revlog.
493 '''
494 '''
494 assert not self._dirty
495 assert not self._dirty
495 return self._node
496 return self._node
496
497
497 def setnode(self, node):
498 def setnode(self, node):
498 self._node = node
499 self._node = node
499 self._dirty = False
500 self._dirty = False
500
501
501 def iteritems(self):
502 def iteritems(self):
502 self._load()
503 self._load()
503 for p, n in sorted(self._dirs.items() + self._files.items()):
504 for p, n in sorted(self._dirs.items() + self._files.items()):
504 if p in self._files:
505 if p in self._files:
505 yield self._subpath(p), n
506 yield self._subpath(p), n
506 else:
507 else:
507 for f, sn in n.iteritems():
508 for f, sn in n.iteritems():
508 yield f, sn
509 yield f, sn
509
510
510 def iterkeys(self):
511 def iterkeys(self):
511 self._load()
512 self._load()
512 for p in sorted(self._dirs.keys() + self._files.keys()):
513 for p in sorted(self._dirs.keys() + self._files.keys()):
513 if p in self._files:
514 if p in self._files:
514 yield self._subpath(p)
515 yield self._subpath(p)
515 else:
516 else:
516 for f in self._dirs[p].iterkeys():
517 for f in self._dirs[p].iterkeys():
517 yield f
518 yield f
518
519
519 def keys(self):
520 def keys(self):
520 return list(self.iterkeys())
521 return list(self.iterkeys())
521
522
522 def __iter__(self):
523 def __iter__(self):
523 return self.iterkeys()
524 return self.iterkeys()
524
525
525 def __contains__(self, f):
526 def __contains__(self, f):
526 if f is None:
527 if f is None:
527 return False
528 return False
528 self._load()
529 self._load()
529 dir, subpath = _splittopdir(f)
530 dir, subpath = _splittopdir(f)
530 if dir:
531 if dir:
531 if dir not in self._dirs:
532 if dir not in self._dirs:
532 return False
533 return False
533 return self._dirs[dir].__contains__(subpath)
534 return self._dirs[dir].__contains__(subpath)
534 else:
535 else:
535 return f in self._files
536 return f in self._files
536
537
537 def get(self, f, default=None):
538 def get(self, f, default=None):
538 self._load()
539 self._load()
539 dir, subpath = _splittopdir(f)
540 dir, subpath = _splittopdir(f)
540 if dir:
541 if dir:
541 if dir not in self._dirs:
542 if dir not in self._dirs:
542 return default
543 return default
543 return self._dirs[dir].get(subpath, default)
544 return self._dirs[dir].get(subpath, default)
544 else:
545 else:
545 return self._files.get(f, default)
546 return self._files.get(f, default)
546
547
547 def __getitem__(self, f):
548 def __getitem__(self, f):
548 self._load()
549 self._load()
549 dir, subpath = _splittopdir(f)
550 dir, subpath = _splittopdir(f)
550 if dir:
551 if dir:
551 return self._dirs[dir].__getitem__(subpath)
552 return self._dirs[dir].__getitem__(subpath)
552 else:
553 else:
553 return self._files[f]
554 return self._files[f]
554
555
555 def flags(self, f):
556 def flags(self, f):
556 self._load()
557 self._load()
557 dir, subpath = _splittopdir(f)
558 dir, subpath = _splittopdir(f)
558 if dir:
559 if dir:
559 if dir not in self._dirs:
560 if dir not in self._dirs:
560 return ''
561 return ''
561 return self._dirs[dir].flags(subpath)
562 return self._dirs[dir].flags(subpath)
562 else:
563 else:
563 if f in self._dirs:
564 if f in self._dirs:
564 return ''
565 return ''
565 return self._flags.get(f, '')
566 return self._flags.get(f, '')
566
567
567 def find(self, f):
568 def find(self, f):
568 self._load()
569 self._load()
569 dir, subpath = _splittopdir(f)
570 dir, subpath = _splittopdir(f)
570 if dir:
571 if dir:
571 return self._dirs[dir].find(subpath)
572 return self._dirs[dir].find(subpath)
572 else:
573 else:
573 return self._files[f], self._flags.get(f, '')
574 return self._files[f], self._flags.get(f, '')
574
575
575 def __delitem__(self, f):
576 def __delitem__(self, f):
576 self._load()
577 self._load()
577 dir, subpath = _splittopdir(f)
578 dir, subpath = _splittopdir(f)
578 if dir:
579 if dir:
579 self._dirs[dir].__delitem__(subpath)
580 self._dirs[dir].__delitem__(subpath)
580 # If the directory is now empty, remove it
581 # If the directory is now empty, remove it
581 if self._dirs[dir]._isempty():
582 if self._dirs[dir]._isempty():
582 del self._dirs[dir]
583 del self._dirs[dir]
583 else:
584 else:
584 del self._files[f]
585 del self._files[f]
585 if f in self._flags:
586 if f in self._flags:
586 del self._flags[f]
587 del self._flags[f]
587 self._dirty = True
588 self._dirty = True
588
589
589 def __setitem__(self, f, n):
590 def __setitem__(self, f, n):
590 assert n is not None
591 assert n is not None
591 self._load()
592 self._load()
592 dir, subpath = _splittopdir(f)
593 dir, subpath = _splittopdir(f)
593 if dir:
594 if dir:
594 if dir not in self._dirs:
595 if dir not in self._dirs:
595 self._dirs[dir] = treemanifest(self._subpath(dir))
596 self._dirs[dir] = treemanifest(self._subpath(dir))
596 self._dirs[dir].__setitem__(subpath, n)
597 self._dirs[dir].__setitem__(subpath, n)
597 else:
598 else:
598 self._files[f] = n[:21] # to match manifestdict's behavior
599 self._files[f] = n[:21] # to match manifestdict's behavior
599 self._dirty = True
600 self._dirty = True
600
601
602 def _load(self):
603 if self._loadfunc is not _noop:
604 lf, self._loadfunc = self._loadfunc, _noop
605 lf(self)
606 elif self._copyfunc is not _noop:
607 cf, self._copyfunc = self._copyfunc, _noop
608 cf(self)
609
601 def setflag(self, f, flags):
610 def setflag(self, f, flags):
602 """Set the flags (symlink, executable) for path f."""
611 """Set the flags (symlink, executable) for path f."""
603 assert 'd' not in flags
612 assert 'd' not in flags
604 self._load()
613 self._load()
605 dir, subpath = _splittopdir(f)
614 dir, subpath = _splittopdir(f)
606 if dir:
615 if dir:
607 if dir not in self._dirs:
616 if dir not in self._dirs:
608 self._dirs[dir] = treemanifest(self._subpath(dir))
617 self._dirs[dir] = treemanifest(self._subpath(dir))
609 self._dirs[dir].setflag(subpath, flags)
618 self._dirs[dir].setflag(subpath, flags)
610 else:
619 else:
611 self._flags[f] = flags
620 self._flags[f] = flags
612 self._dirty = True
621 self._dirty = True
613
622
614 def copy(self):
623 def copy(self):
615 copy = treemanifest(self._dir)
624 copy = treemanifest(self._dir)
616 copy._node = self._node
625 copy._node = self._node
617 copy._dirty = self._dirty
626 copy._dirty = self._dirty
618 def _load_for_copy():
627 if self._copyfunc is _noop:
619 self._load()
628 def _copyfunc(s):
620 for d in self._dirs:
629 self._load()
621 copy._dirs[d] = self._dirs[d].copy()
630 for d in self._dirs:
622 copy._files = dict.copy(self._files)
631 s._dirs[d] = self._dirs[d].copy()
623 copy._flags = dict.copy(self._flags)
632 s._files = dict.copy(self._files)
624 copy._load = _noop
633 s._flags = dict.copy(self._flags)
625 copy._load = _load_for_copy
634 if self._loadfunc is _noop:
626 if self._load == _noop:
635 _copyfunc(copy)
627 # Chaining _load if it's _noop is functionally correct, but the
636 else:
628 # chain may end up excessively long (stack overflow), and
637 copy._copyfunc = _copyfunc
629 # will prevent garbage collection of 'self'.
638 else:
630 copy._load()
639 copy._copyfunc = self._copyfunc
631 return copy
640 return copy
632
641
633 def filesnotin(self, m2):
642 def filesnotin(self, m2):
634 '''Set of files in this manifest that are not in the other'''
643 '''Set of files in this manifest that are not in the other'''
635 files = set()
644 files = set()
636 def _filesnotin(t1, t2):
645 def _filesnotin(t1, t2):
637 if t1._node == t2._node and not t1._dirty and not t2._dirty:
646 if t1._node == t2._node and not t1._dirty and not t2._dirty:
638 return
647 return
639 t1._load()
648 t1._load()
640 t2._load()
649 t2._load()
641 for d, m1 in t1._dirs.iteritems():
650 for d, m1 in t1._dirs.iteritems():
642 if d in t2._dirs:
651 if d in t2._dirs:
643 m2 = t2._dirs[d]
652 m2 = t2._dirs[d]
644 _filesnotin(m1, m2)
653 _filesnotin(m1, m2)
645 else:
654 else:
646 files.update(m1.iterkeys())
655 files.update(m1.iterkeys())
647
656
648 for fn in t1._files.iterkeys():
657 for fn in t1._files.iterkeys():
649 if fn not in t2._files:
658 if fn not in t2._files:
650 files.add(t1._subpath(fn))
659 files.add(t1._subpath(fn))
651
660
652 _filesnotin(self, m2)
661 _filesnotin(self, m2)
653 return files
662 return files
654
663
655 @propertycache
664 @propertycache
656 def _alldirs(self):
665 def _alldirs(self):
657 return util.dirs(self)
666 return util.dirs(self)
658
667
659 def dirs(self):
668 def dirs(self):
660 return self._alldirs
669 return self._alldirs
661
670
662 def hasdir(self, dir):
671 def hasdir(self, dir):
663 self._load()
672 self._load()
664 topdir, subdir = _splittopdir(dir)
673 topdir, subdir = _splittopdir(dir)
665 if topdir:
674 if topdir:
666 if topdir in self._dirs:
675 if topdir in self._dirs:
667 return self._dirs[topdir].hasdir(subdir)
676 return self._dirs[topdir].hasdir(subdir)
668 return False
677 return False
669 return (dir + '/') in self._dirs
678 return (dir + '/') in self._dirs
670
679
671 def walk(self, match):
680 def walk(self, match):
672 '''Generates matching file names.
681 '''Generates matching file names.
673
682
674 Equivalent to manifest.matches(match).iterkeys(), but without creating
683 Equivalent to manifest.matches(match).iterkeys(), but without creating
675 an entirely new manifest.
684 an entirely new manifest.
676
685
677 It also reports nonexistent files by marking them bad with match.bad().
686 It also reports nonexistent files by marking them bad with match.bad().
678 '''
687 '''
679 if match.always():
688 if match.always():
680 for f in iter(self):
689 for f in iter(self):
681 yield f
690 yield f
682 return
691 return
683
692
684 fset = set(match.files())
693 fset = set(match.files())
685
694
686 for fn in self._walk(match):
695 for fn in self._walk(match):
687 if fn in fset:
696 if fn in fset:
688 # specified pattern is the exact name
697 # specified pattern is the exact name
689 fset.remove(fn)
698 fset.remove(fn)
690 yield fn
699 yield fn
691
700
692 # for dirstate.walk, files=['.'] means "walk the whole tree".
701 # for dirstate.walk, files=['.'] means "walk the whole tree".
693 # follow that here, too
702 # follow that here, too
694 fset.discard('.')
703 fset.discard('.')
695
704
696 for fn in sorted(fset):
705 for fn in sorted(fset):
697 if not self.hasdir(fn):
706 if not self.hasdir(fn):
698 match.bad(fn, None)
707 match.bad(fn, None)
699
708
700 def _walk(self, match):
709 def _walk(self, match):
701 '''Recursively generates matching file names for walk().'''
710 '''Recursively generates matching file names for walk().'''
702 if not match.visitdir(self._dir[:-1] or '.'):
711 if not match.visitdir(self._dir[:-1] or '.'):
703 return
712 return
704
713
705 # yield this dir's files and walk its submanifests
714 # yield this dir's files and walk its submanifests
706 self._load()
715 self._load()
707 for p in sorted(self._dirs.keys() + self._files.keys()):
716 for p in sorted(self._dirs.keys() + self._files.keys()):
708 if p in self._files:
717 if p in self._files:
709 fullp = self._subpath(p)
718 fullp = self._subpath(p)
710 if match(fullp):
719 if match(fullp):
711 yield fullp
720 yield fullp
712 else:
721 else:
713 for f in self._dirs[p]._walk(match):
722 for f in self._dirs[p]._walk(match):
714 yield f
723 yield f
715
724
716 def matches(self, match):
725 def matches(self, match):
717 '''generate a new manifest filtered by the match argument'''
726 '''generate a new manifest filtered by the match argument'''
718 if match.always():
727 if match.always():
719 return self.copy()
728 return self.copy()
720
729
721 return self._matches(match)
730 return self._matches(match)
722
731
723 def _matches(self, match):
732 def _matches(self, match):
724 '''recursively generate a new manifest filtered by the match argument.
733 '''recursively generate a new manifest filtered by the match argument.
725 '''
734 '''
726 ret = treemanifest(self._dir)
735 ret = treemanifest(self._dir)
727
736
728 if not match.visitdir(self._dir[:-1] or '.'):
737 if not match.visitdir(self._dir[:-1] or '.'):
729 return ret
738 return ret
730
739
731 self._load()
740 self._load()
732 for fn in self._files:
741 for fn in self._files:
733 fullp = self._subpath(fn)
742 fullp = self._subpath(fn)
734 if not match(fullp):
743 if not match(fullp):
735 continue
744 continue
736 ret._files[fn] = self._files[fn]
745 ret._files[fn] = self._files[fn]
737 if fn in self._flags:
746 if fn in self._flags:
738 ret._flags[fn] = self._flags[fn]
747 ret._flags[fn] = self._flags[fn]
739
748
740 for dir, subm in self._dirs.iteritems():
749 for dir, subm in self._dirs.iteritems():
741 m = subm._matches(match)
750 m = subm._matches(match)
742 if not m._isempty():
751 if not m._isempty():
743 ret._dirs[dir] = m
752 ret._dirs[dir] = m
744
753
745 if not ret._isempty():
754 if not ret._isempty():
746 ret._dirty = True
755 ret._dirty = True
747 return ret
756 return ret
748
757
749 def diff(self, m2, clean=False):
758 def diff(self, m2, clean=False):
750 '''Finds changes between the current manifest and m2.
759 '''Finds changes between the current manifest and m2.
751
760
752 Args:
761 Args:
753 m2: the manifest to which this manifest should be compared.
762 m2: the manifest to which this manifest should be compared.
754 clean: if true, include files unchanged between these manifests
763 clean: if true, include files unchanged between these manifests
755 with a None value in the returned dictionary.
764 with a None value in the returned dictionary.
756
765
757 The result is returned as a dict with filename as key and
766 The result is returned as a dict with filename as key and
758 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
767 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
759 nodeid in the current/other manifest and fl1/fl2 is the flag
768 nodeid in the current/other manifest and fl1/fl2 is the flag
760 in the current/other manifest. Where the file does not exist,
769 in the current/other manifest. Where the file does not exist,
761 the nodeid will be None and the flags will be the empty
770 the nodeid will be None and the flags will be the empty
762 string.
771 string.
763 '''
772 '''
764 result = {}
773 result = {}
765 emptytree = treemanifest()
774 emptytree = treemanifest()
766 def _diff(t1, t2):
775 def _diff(t1, t2):
767 if t1._node == t2._node and not t1._dirty and not t2._dirty:
776 if t1._node == t2._node and not t1._dirty and not t2._dirty:
768 return
777 return
769 t1._load()
778 t1._load()
770 t2._load()
779 t2._load()
771 for d, m1 in t1._dirs.iteritems():
780 for d, m1 in t1._dirs.iteritems():
772 m2 = t2._dirs.get(d, emptytree)
781 m2 = t2._dirs.get(d, emptytree)
773 _diff(m1, m2)
782 _diff(m1, m2)
774
783
775 for d, m2 in t2._dirs.iteritems():
784 for d, m2 in t2._dirs.iteritems():
776 if d not in t1._dirs:
785 if d not in t1._dirs:
777 _diff(emptytree, m2)
786 _diff(emptytree, m2)
778
787
779 for fn, n1 in t1._files.iteritems():
788 for fn, n1 in t1._files.iteritems():
780 fl1 = t1._flags.get(fn, '')
789 fl1 = t1._flags.get(fn, '')
781 n2 = t2._files.get(fn, None)
790 n2 = t2._files.get(fn, None)
782 fl2 = t2._flags.get(fn, '')
791 fl2 = t2._flags.get(fn, '')
783 if n1 != n2 or fl1 != fl2:
792 if n1 != n2 or fl1 != fl2:
784 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
793 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
785 elif clean:
794 elif clean:
786 result[t1._subpath(fn)] = None
795 result[t1._subpath(fn)] = None
787
796
788 for fn, n2 in t2._files.iteritems():
797 for fn, n2 in t2._files.iteritems():
789 if fn not in t1._files:
798 if fn not in t1._files:
790 fl2 = t2._flags.get(fn, '')
799 fl2 = t2._flags.get(fn, '')
791 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
800 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
792
801
793 _diff(self, m2)
802 _diff(self, m2)
794 return result
803 return result
795
804
796 def unmodifiedsince(self, m2):
805 def unmodifiedsince(self, m2):
797 return not self._dirty and not m2._dirty and self._node == m2._node
806 return not self._dirty and not m2._dirty and self._node == m2._node
798
807
799 def parse(self, text, readsubtree):
808 def parse(self, text, readsubtree):
800 for f, n, fl in _parse(text):
809 for f, n, fl in _parse(text):
801 if fl == 'd':
810 if fl == 'd':
802 f = f + '/'
811 f = f + '/'
803 self._dirs[f] = readsubtree(self._subpath(f), n)
812 self._dirs[f] = readsubtree(self._subpath(f), n)
804 elif '/' in f:
813 elif '/' in f:
805 # This is a flat manifest, so use __setitem__ and setflag rather
814 # This is a flat manifest, so use __setitem__ and setflag rather
806 # than assigning directly to _files and _flags, so we can
815 # than assigning directly to _files and _flags, so we can
807 # assign a path in a subdirectory, and to mark dirty (compared
816 # assign a path in a subdirectory, and to mark dirty (compared
808 # to nullid).
817 # to nullid).
809 self[f] = n
818 self[f] = n
810 if fl:
819 if fl:
811 self.setflag(f, fl)
820 self.setflag(f, fl)
812 else:
821 else:
813 # Assigning to _files and _flags avoids marking as dirty,
822 # Assigning to _files and _flags avoids marking as dirty,
814 # and should be a little faster.
823 # and should be a little faster.
815 self._files[f] = n
824 self._files[f] = n
816 if fl:
825 if fl:
817 self._flags[f] = fl
826 self._flags[f] = fl
818
827
819 def text(self, usemanifestv2=False):
828 def text(self, usemanifestv2=False):
820 """Get the full data of this manifest as a bytestring."""
829 """Get the full data of this manifest as a bytestring."""
821 self._load()
830 self._load()
822 flags = self.flags
831 flags = self.flags
823 return _text(((f, self[f], flags(f)) for f in self.keys()),
832 return _text(((f, self[f], flags(f)) for f in self.keys()),
824 usemanifestv2)
833 usemanifestv2)
825
834
826 def dirtext(self, usemanifestv2=False):
835 def dirtext(self, usemanifestv2=False):
827 """Get the full data of this directory as a bytestring. Make sure that
836 """Get the full data of this directory as a bytestring. Make sure that
828 any submanifests have been written first, so their nodeids are correct.
837 any submanifests have been written first, so their nodeids are correct.
829 """
838 """
830 self._load()
839 self._load()
831 flags = self.flags
840 flags = self.flags
832 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
841 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
833 files = [(f, self._files[f], flags(f)) for f in self._files]
842 files = [(f, self._files[f], flags(f)) for f in self._files]
834 return _text(sorted(dirs + files), usemanifestv2)
843 return _text(sorted(dirs + files), usemanifestv2)
835
844
836 def read(self, gettext, readsubtree):
845 def read(self, gettext, readsubtree):
837 def _load_for_read():
846 def _load_for_read(s):
838 # Mark as loaded already here, so __setitem__ and setflag() don't
847 s.parse(gettext(), readsubtree)
839 # cause infinite loops when they try to load.
848 s._dirty = False
840 self._load = _noop
849 self._loadfunc = _load_for_read
841 self.parse(gettext(), readsubtree)
842 self._dirty = False
843 self._load = _load_for_read
844
850
845 def writesubtrees(self, m1, m2, writesubtree):
851 def writesubtrees(self, m1, m2, writesubtree):
846 self._load() # for consistency; should never have any effect here
852 self._load() # for consistency; should never have any effect here
847 emptytree = treemanifest()
853 emptytree = treemanifest()
848 for d, subm in self._dirs.iteritems():
854 for d, subm in self._dirs.iteritems():
849 subp1 = m1._dirs.get(d, emptytree)._node
855 subp1 = m1._dirs.get(d, emptytree)._node
850 subp2 = m2._dirs.get(d, emptytree)._node
856 subp2 = m2._dirs.get(d, emptytree)._node
851 if subp1 == revlog.nullid:
857 if subp1 == revlog.nullid:
852 subp1, subp2 = subp2, subp1
858 subp1, subp2 = subp2, subp1
853 writesubtree(subm, subp1, subp2)
859 writesubtree(subm, subp1, subp2)
854
860
855 class manifest(revlog.revlog):
861 class manifest(revlog.revlog):
856 def __init__(self, opener, dir='', dirlogcache=None):
862 def __init__(self, opener, dir='', dirlogcache=None):
857 '''The 'dir' and 'dirlogcache' arguments are for internal use by
863 '''The 'dir' and 'dirlogcache' arguments are for internal use by
858 manifest.manifest only. External users should create a root manifest
864 manifest.manifest only. External users should create a root manifest
859 log with manifest.manifest(opener) and call dirlog() on it.
865 log with manifest.manifest(opener) and call dirlog() on it.
860 '''
866 '''
861 # During normal operations, we expect to deal with not more than four
867 # During normal operations, we expect to deal with not more than four
862 # revs at a time (such as during commit --amend). When rebasing large
868 # revs at a time (such as during commit --amend). When rebasing large
863 # stacks of commits, the number can go up, hence the config knob below.
869 # stacks of commits, the number can go up, hence the config knob below.
864 cachesize = 4
870 cachesize = 4
865 usetreemanifest = False
871 usetreemanifest = False
866 usemanifestv2 = False
872 usemanifestv2 = False
867 opts = getattr(opener, 'options', None)
873 opts = getattr(opener, 'options', None)
868 if opts is not None:
874 if opts is not None:
869 cachesize = opts.get('manifestcachesize', cachesize)
875 cachesize = opts.get('manifestcachesize', cachesize)
870 usetreemanifest = opts.get('treemanifest', usetreemanifest)
876 usetreemanifest = opts.get('treemanifest', usetreemanifest)
871 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
877 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
872 self._mancache = util.lrucachedict(cachesize)
878 self._mancache = util.lrucachedict(cachesize)
873 self._treeinmem = usetreemanifest
879 self._treeinmem = usetreemanifest
874 self._treeondisk = usetreemanifest
880 self._treeondisk = usetreemanifest
875 self._usemanifestv2 = usemanifestv2
881 self._usemanifestv2 = usemanifestv2
876 indexfile = "00manifest.i"
882 indexfile = "00manifest.i"
877 if dir:
883 if dir:
878 assert self._treeondisk
884 assert self._treeondisk
879 if not dir.endswith('/'):
885 if not dir.endswith('/'):
880 dir = dir + '/'
886 dir = dir + '/'
881 indexfile = "meta/" + dir + "00manifest.i"
887 indexfile = "meta/" + dir + "00manifest.i"
882 revlog.revlog.__init__(self, opener, indexfile)
888 revlog.revlog.__init__(self, opener, indexfile)
883 self._dir = dir
889 self._dir = dir
884 # The dirlogcache is kept on the root manifest log
890 # The dirlogcache is kept on the root manifest log
885 if dir:
891 if dir:
886 self._dirlogcache = dirlogcache
892 self._dirlogcache = dirlogcache
887 else:
893 else:
888 self._dirlogcache = {'': self}
894 self._dirlogcache = {'': self}
889
895
890 def _newmanifest(self, data=''):
896 def _newmanifest(self, data=''):
891 if self._treeinmem:
897 if self._treeinmem:
892 return treemanifest(self._dir, data)
898 return treemanifest(self._dir, data)
893 return manifestdict(data)
899 return manifestdict(data)
894
900
895 def dirlog(self, dir):
901 def dirlog(self, dir):
896 assert self._treeondisk
902 assert self._treeondisk
897 if dir not in self._dirlogcache:
903 if dir not in self._dirlogcache:
898 self._dirlogcache[dir] = manifest(self.opener, dir,
904 self._dirlogcache[dir] = manifest(self.opener, dir,
899 self._dirlogcache)
905 self._dirlogcache)
900 return self._dirlogcache[dir]
906 return self._dirlogcache[dir]
901
907
902 def _slowreaddelta(self, node):
908 def _slowreaddelta(self, node):
903 r0 = self.deltaparent(self.rev(node))
909 r0 = self.deltaparent(self.rev(node))
904 m0 = self.read(self.node(r0))
910 m0 = self.read(self.node(r0))
905 m1 = self.read(node)
911 m1 = self.read(node)
906 md = self._newmanifest()
912 md = self._newmanifest()
907 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
913 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
908 if n1:
914 if n1:
909 md[f] = n1
915 md[f] = n1
910 if fl1:
916 if fl1:
911 md.setflag(f, fl1)
917 md.setflag(f, fl1)
912 return md
918 return md
913
919
914 def readdelta(self, node):
920 def readdelta(self, node):
915 if self._usemanifestv2 or self._treeondisk:
921 if self._usemanifestv2 or self._treeondisk:
916 return self._slowreaddelta(node)
922 return self._slowreaddelta(node)
917 r = self.rev(node)
923 r = self.rev(node)
918 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
924 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
919 return self._newmanifest(d)
925 return self._newmanifest(d)
920
926
921 def readfast(self, node):
927 def readfast(self, node):
922 '''use the faster of readdelta or read
928 '''use the faster of readdelta or read
923
929
924 This will return a manifest which is either only the files
930 This will return a manifest which is either only the files
925 added/modified relative to p1, or all files in the
931 added/modified relative to p1, or all files in the
926 manifest. Which one is returned depends on the codepath used
932 manifest. Which one is returned depends on the codepath used
927 to retrieve the data.
933 to retrieve the data.
928 '''
934 '''
929 r = self.rev(node)
935 r = self.rev(node)
930 deltaparent = self.deltaparent(r)
936 deltaparent = self.deltaparent(r)
931 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
937 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
932 return self.readdelta(node)
938 return self.readdelta(node)
933 return self.read(node)
939 return self.read(node)
934
940
935 def read(self, node):
941 def read(self, node):
936 if node == revlog.nullid:
942 if node == revlog.nullid:
937 return self._newmanifest() # don't upset local cache
943 return self._newmanifest() # don't upset local cache
938 if node in self._mancache:
944 if node in self._mancache:
939 return self._mancache[node][0]
945 return self._mancache[node][0]
940 if self._treeondisk:
946 if self._treeondisk:
941 def gettext():
947 def gettext():
942 return self.revision(node)
948 return self.revision(node)
943 def readsubtree(dir, subm):
949 def readsubtree(dir, subm):
944 return self.dirlog(dir).read(subm)
950 return self.dirlog(dir).read(subm)
945 m = self._newmanifest()
951 m = self._newmanifest()
946 m.read(gettext, readsubtree)
952 m.read(gettext, readsubtree)
947 m.setnode(node)
953 m.setnode(node)
948 arraytext = None
954 arraytext = None
949 else:
955 else:
950 text = self.revision(node)
956 text = self.revision(node)
951 m = self._newmanifest(text)
957 m = self._newmanifest(text)
952 arraytext = array.array('c', text)
958 arraytext = array.array('c', text)
953 self._mancache[node] = (m, arraytext)
959 self._mancache[node] = (m, arraytext)
954 return m
960 return m
955
961
956 def find(self, node, f):
962 def find(self, node, f):
957 '''look up entry for a single file efficiently.
963 '''look up entry for a single file efficiently.
958 return (node, flags) pair if found, (None, None) if not.'''
964 return (node, flags) pair if found, (None, None) if not.'''
959 m = self.read(node)
965 m = self.read(node)
960 try:
966 try:
961 return m.find(f)
967 return m.find(f)
962 except KeyError:
968 except KeyError:
963 return None, None
969 return None, None
964
970
965 def add(self, m, transaction, link, p1, p2, added, removed):
971 def add(self, m, transaction, link, p1, p2, added, removed):
966 if (p1 in self._mancache and not self._treeinmem
972 if (p1 in self._mancache and not self._treeinmem
967 and not self._usemanifestv2):
973 and not self._usemanifestv2):
968 # If our first parent is in the manifest cache, we can
974 # If our first parent is in the manifest cache, we can
969 # compute a delta here using properties we know about the
975 # compute a delta here using properties we know about the
970 # manifest up-front, which may save time later for the
976 # manifest up-front, which may save time later for the
971 # revlog layer.
977 # revlog layer.
972
978
973 _checkforbidden(added)
979 _checkforbidden(added)
974 # combine the changed lists into one sorted iterator
980 # combine the changed lists into one sorted iterator
975 work = heapq.merge([(x, False) for x in added],
981 work = heapq.merge([(x, False) for x in added],
976 [(x, True) for x in removed])
982 [(x, True) for x in removed])
977
983
978 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
984 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
979 cachedelta = self.rev(p1), deltatext
985 cachedelta = self.rev(p1), deltatext
980 text = util.buffer(arraytext)
986 text = util.buffer(arraytext)
981 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
987 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
982 else:
988 else:
983 # The first parent manifest isn't already loaded, so we'll
989 # The first parent manifest isn't already loaded, so we'll
984 # just encode a fulltext of the manifest and pass that
990 # just encode a fulltext of the manifest and pass that
985 # through to the revlog layer, and let it handle the delta
991 # through to the revlog layer, and let it handle the delta
986 # process.
992 # process.
987 if self._treeondisk:
993 if self._treeondisk:
988 m1 = self.read(p1)
994 m1 = self.read(p1)
989 m2 = self.read(p2)
995 m2 = self.read(p2)
990 n = self._addtree(m, transaction, link, m1, m2)
996 n = self._addtree(m, transaction, link, m1, m2)
991 arraytext = None
997 arraytext = None
992 else:
998 else:
993 text = m.text(self._usemanifestv2)
999 text = m.text(self._usemanifestv2)
994 n = self.addrevision(text, transaction, link, p1, p2)
1000 n = self.addrevision(text, transaction, link, p1, p2)
995 arraytext = array.array('c', text)
1001 arraytext = array.array('c', text)
996
1002
997 self._mancache[n] = (m, arraytext)
1003 self._mancache[n] = (m, arraytext)
998
1004
999 return n
1005 return n
1000
1006
1001 def _addtree(self, m, transaction, link, m1, m2):
1007 def _addtree(self, m, transaction, link, m1, m2):
1002 # If the manifest is unchanged compared to one parent,
1008 # If the manifest is unchanged compared to one parent,
1003 # don't write a new revision
1009 # don't write a new revision
1004 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1010 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1005 return m.node()
1011 return m.node()
1006 def writesubtree(subm, subp1, subp2):
1012 def writesubtree(subm, subp1, subp2):
1007 sublog = self.dirlog(subm.dir())
1013 sublog = self.dirlog(subm.dir())
1008 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1014 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1009 m.writesubtrees(m1, m2, writesubtree)
1015 m.writesubtrees(m1, m2, writesubtree)
1010 text = m.dirtext(self._usemanifestv2)
1016 text = m.dirtext(self._usemanifestv2)
1011 # Double-check whether contents are unchanged to one parent
1017 # Double-check whether contents are unchanged to one parent
1012 if text == m1.dirtext(self._usemanifestv2):
1018 if text == m1.dirtext(self._usemanifestv2):
1013 n = m1.node()
1019 n = m1.node()
1014 elif text == m2.dirtext(self._usemanifestv2):
1020 elif text == m2.dirtext(self._usemanifestv2):
1015 n = m2.node()
1021 n = m2.node()
1016 else:
1022 else:
1017 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1023 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1018 # Save nodeid so parent manifest can calculate its nodeid
1024 # Save nodeid so parent manifest can calculate its nodeid
1019 m.setnode(n)
1025 m.setnode(n)
1020 return n
1026 return n
General Comments 0
You need to be logged in to leave comments. Login now