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