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