##// END OF EJS Templates
lazymanifest: write a more efficient, pypy friendly version of lazymanifest
Maciej Fijalkowski -
r30042:d24e03da default
parent child Browse files
Show More
@@ -1,1299 +1,1530 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class _lazymanifest(dict):
107 class lazymanifestiter(object):
108 """This is the pure implementation of lazymanifest.
108 def __init__(self, lm):
109 self.pos = 0
110 self.lm = lm
109
111
110 It has not been optimized *at all* and is not lazy.
112 def __iter__(self):
111 """
113 return self
112
114
113 def __init__(self, data):
115 def next(self):
114 dict.__init__(self)
116 try:
115 for f, n, fl in _parse(data):
117 data, pos = self.lm._get(self.pos)
116 self[f] = n, fl
118 except IndexError:
119 raise StopIteration
120 if pos == -1:
121 self.pos += 1
122 return data[0]
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
117
126
118 def __setitem__(self, k, v):
127 class lazymanifestiterentries(object):
119 node, flag = v
128 def __init__(self, lm):
120 assert node is not None
129 self.lm = lm
121 if len(node) > 21:
130 self.pos = 0
122 node = node[:21] # match c implementation behavior
123 dict.__setitem__(self, k, (node, flag))
124
131
125 def __iter__(self):
132 def __iter__(self):
126 return iter(sorted(dict.keys(self)))
133 return self
134
135 def next(self):
136 try:
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
139 raise StopIteration
140 if pos == -1:
141 self.pos += 1
142 return data
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
149
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
152 if extra:
153 s += chr(extra & 0xff)
154 return s
155
156 def _cmp(a, b):
157 return (a > b) - (a < b)
158
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
165 self.extradata = []
166 else:
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
170 self.data = data
171
172 def findlines(self, data):
173 if not data:
174 return []
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
186 pos = data.find("\n", pos + 1)
187 return positions
188
189 def _get(self, index):
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
193 pos = self.positions[index]
194 if pos >= 0:
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
197
198 def _getkey(self, pos):
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
202
203 def bsearch(self, key):
204 first = 0
205 last = len(self.positions) - 1
206
207 while first <= last:
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
212 if r == 0:
213 return midpoint
214 else:
215 if r < 0:
216 last = midpoint - 1
217 else:
218 first = midpoint + 1
219 return -1
127
220
128 def iterkeys(self):
221 def bsearch2(self, key):
129 return iter(sorted(dict.keys(self)))
222 # same as the above, but will always return the position
223 # done for performance reasons
224 first = 0
225 last = len(self.positions) - 1
226
227 while first <= last:
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
232 if r == 0:
233 return (midpoint, True)
234 else:
235 if r < 0:
236 last = midpoint - 1
237 else:
238 first = midpoint + 1
239 return (first, False)
240
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
243
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
246 end = data.find("\n", start)
247 if end == -1:
248 end = len(data) - 1
249 if start == end:
250 return ''
251 return self.data[start:end]
130
252
131 def iterentries(self):
253 def __getitem__(self, key):
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
257 if needle == -1:
258 raise KeyError
259 data, pos = self._get(needle)
260 if pos == -1:
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
268
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
271 if not found:
272 raise KeyError
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
293 if found:
294 # put the item
295 pos = self.positions[needle]
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
302 else:
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
133
309
134 def copy(self):
310 def copy(self):
135 c = _lazymanifest('')
311 # XXX call _compact like in C?
136 c.update(self)
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
137 return c
313 self.extradata)
314
315 def _compact(self):
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
318 return
319 l = []
320 last_cut = 0
321 i = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
327 last_cut = cur
328 while True:
329 self.positions[i] = offset
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
337 end_cut += 1
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
345 self.positions[i] = offset
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
349 i += 1
350 self.data = ''.join(l)
351 self.extradata = []
352
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
356 def text(self):
357 self._compact()
358 return self.data
138
359
139 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
140 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
141 diff = {}
363 diff = {}
142
364
143 for fn, e1 in self.iteritems():
365 for fn, e1, flags in self.iterentries():
144 if fn not in m2:
366 if fn not in m2:
145 diff[fn] = e1, (None, '')
367 diff[fn] = (e1, flags), (None, '')
146 else:
368 else:
147 e2 = m2[fn]
369 e2 = m2[fn]
148 if e1 != e2:
370 if (e1, flags) != e2:
149 diff[fn] = e1, e2
371 diff[fn] = (e1, flags), e2
150 elif clean:
372 elif clean:
151 diff[fn] = None
373 diff[fn] = None
152
374
153 for fn, e2 in m2.iteritems():
375 for fn, e2, flags in m2.iterentries():
154 if fn not in self:
376 if fn not in self:
155 diff[fn] = (None, ''), e2
377 diff[fn] = (None, ''), (e2, flags)
156
378
157 return diff
379 return diff
158
380
381 def iterentries(self):
382 return lazymanifestiterentries(self)
383
384 def iterkeys(self):
385 return lazymanifestiter(self)
386
387 def __iter__(self):
388 return lazymanifestiter(self)
389
390 def __len__(self):
391 return len(self.positions)
392
159 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
160 c = _lazymanifest('')
395 c = _lazymanifest('')
161 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
162 if filterfn(f):
397 if filterfn(f):
163 c[f] = n, fl
398 c[f] = n, fl
164 return c
399 return c
165
400
166 def text(self):
167 """Get the full data of this manifest as a bytestring."""
168 return _textv1(self.iterentries())
169
170 try:
401 try:
171 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
172 except AttributeError:
403 except AttributeError:
173 pass
404 pass
174
405
175 class manifestdict(object):
406 class manifestdict(object):
176 def __init__(self, data=''):
407 def __init__(self, data=''):
177 if data.startswith('\0'):
408 if data.startswith('\0'):
178 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
179 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
180 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
181 self._lm[f] = n, fl
412 self._lm[f] = n, fl
182 else:
413 else:
183 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
184
415
185 def __getitem__(self, key):
416 def __getitem__(self, key):
186 return self._lm[key][0]
417 return self._lm[key][0]
187
418
188 def find(self, key):
419 def find(self, key):
189 return self._lm[key]
420 return self._lm[key]
190
421
191 def __len__(self):
422 def __len__(self):
192 return len(self._lm)
423 return len(self._lm)
193
424
194 def __setitem__(self, key, node):
425 def __setitem__(self, key, node):
195 self._lm[key] = node, self.flags(key, '')
426 self._lm[key] = node, self.flags(key, '')
196
427
197 def __contains__(self, key):
428 def __contains__(self, key):
198 return key in self._lm
429 return key in self._lm
199
430
200 def __delitem__(self, key):
431 def __delitem__(self, key):
201 del self._lm[key]
432 del self._lm[key]
202
433
203 def __iter__(self):
434 def __iter__(self):
204 return self._lm.__iter__()
435 return self._lm.__iter__()
205
436
206 def iterkeys(self):
437 def iterkeys(self):
207 return self._lm.iterkeys()
438 return self._lm.iterkeys()
208
439
209 def keys(self):
440 def keys(self):
210 return list(self.iterkeys())
441 return list(self.iterkeys())
211
442
212 def filesnotin(self, m2):
443 def filesnotin(self, m2):
213 '''Set of files in this manifest that are not in the other'''
444 '''Set of files in this manifest that are not in the other'''
214 diff = self.diff(m2)
445 diff = self.diff(m2)
215 files = set(filepath
446 files = set(filepath
216 for filepath, hashflags in diff.iteritems()
447 for filepath, hashflags in diff.iteritems()
217 if hashflags[1][0] is None)
448 if hashflags[1][0] is None)
218 return files
449 return files
219
450
220 @propertycache
451 @propertycache
221 def _dirs(self):
452 def _dirs(self):
222 return util.dirs(self)
453 return util.dirs(self)
223
454
224 def dirs(self):
455 def dirs(self):
225 return self._dirs
456 return self._dirs
226
457
227 def hasdir(self, dir):
458 def hasdir(self, dir):
228 return dir in self._dirs
459 return dir in self._dirs
229
460
230 def _filesfastpath(self, match):
461 def _filesfastpath(self, match):
231 '''Checks whether we can correctly and quickly iterate over matcher
462 '''Checks whether we can correctly and quickly iterate over matcher
232 files instead of over manifest files.'''
463 files instead of over manifest files.'''
233 files = match.files()
464 files = match.files()
234 return (len(files) < 100 and (match.isexact() or
465 return (len(files) < 100 and (match.isexact() or
235 (match.prefix() and all(fn in self for fn in files))))
466 (match.prefix() and all(fn in self for fn in files))))
236
467
237 def walk(self, match):
468 def walk(self, match):
238 '''Generates matching file names.
469 '''Generates matching file names.
239
470
240 Equivalent to manifest.matches(match).iterkeys(), but without creating
471 Equivalent to manifest.matches(match).iterkeys(), but without creating
241 an entirely new manifest.
472 an entirely new manifest.
242
473
243 It also reports nonexistent files by marking them bad with match.bad().
474 It also reports nonexistent files by marking them bad with match.bad().
244 '''
475 '''
245 if match.always():
476 if match.always():
246 for f in iter(self):
477 for f in iter(self):
247 yield f
478 yield f
248 return
479 return
249
480
250 fset = set(match.files())
481 fset = set(match.files())
251
482
252 # avoid the entire walk if we're only looking for specific files
483 # avoid the entire walk if we're only looking for specific files
253 if self._filesfastpath(match):
484 if self._filesfastpath(match):
254 for fn in sorted(fset):
485 for fn in sorted(fset):
255 yield fn
486 yield fn
256 return
487 return
257
488
258 for fn in self:
489 for fn in self:
259 if fn in fset:
490 if fn in fset:
260 # specified pattern is the exact name
491 # specified pattern is the exact name
261 fset.remove(fn)
492 fset.remove(fn)
262 if match(fn):
493 if match(fn):
263 yield fn
494 yield fn
264
495
265 # for dirstate.walk, files=['.'] means "walk the whole tree".
496 # for dirstate.walk, files=['.'] means "walk the whole tree".
266 # follow that here, too
497 # follow that here, too
267 fset.discard('.')
498 fset.discard('.')
268
499
269 for fn in sorted(fset):
500 for fn in sorted(fset):
270 if not self.hasdir(fn):
501 if not self.hasdir(fn):
271 match.bad(fn, None)
502 match.bad(fn, None)
272
503
273 def matches(self, match):
504 def matches(self, match):
274 '''generate a new manifest filtered by the match argument'''
505 '''generate a new manifest filtered by the match argument'''
275 if match.always():
506 if match.always():
276 return self.copy()
507 return self.copy()
277
508
278 if self._filesfastpath(match):
509 if self._filesfastpath(match):
279 m = manifestdict()
510 m = manifestdict()
280 lm = self._lm
511 lm = self._lm
281 for fn in match.files():
512 for fn in match.files():
282 if fn in lm:
513 if fn in lm:
283 m._lm[fn] = lm[fn]
514 m._lm[fn] = lm[fn]
284 return m
515 return m
285
516
286 m = manifestdict()
517 m = manifestdict()
287 m._lm = self._lm.filtercopy(match)
518 m._lm = self._lm.filtercopy(match)
288 return m
519 return m
289
520
290 def diff(self, m2, clean=False):
521 def diff(self, m2, clean=False):
291 '''Finds changes between the current manifest and m2.
522 '''Finds changes between the current manifest and m2.
292
523
293 Args:
524 Args:
294 m2: the manifest to which this manifest should be compared.
525 m2: the manifest to which this manifest should be compared.
295 clean: if true, include files unchanged between these manifests
526 clean: if true, include files unchanged between these manifests
296 with a None value in the returned dictionary.
527 with a None value in the returned dictionary.
297
528
298 The result is returned as a dict with filename as key and
529 The result is returned as a dict with filename as key and
299 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
530 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
300 nodeid in the current/other manifest and fl1/fl2 is the flag
531 nodeid in the current/other manifest and fl1/fl2 is the flag
301 in the current/other manifest. Where the file does not exist,
532 in the current/other manifest. Where the file does not exist,
302 the nodeid will be None and the flags will be the empty
533 the nodeid will be None and the flags will be the empty
303 string.
534 string.
304 '''
535 '''
305 return self._lm.diff(m2._lm, clean)
536 return self._lm.diff(m2._lm, clean)
306
537
307 def setflag(self, key, flag):
538 def setflag(self, key, flag):
308 self._lm[key] = self[key], flag
539 self._lm[key] = self[key], flag
309
540
310 def get(self, key, default=None):
541 def get(self, key, default=None):
311 try:
542 try:
312 return self._lm[key][0]
543 return self._lm[key][0]
313 except KeyError:
544 except KeyError:
314 return default
545 return default
315
546
316 def flags(self, key, default=''):
547 def flags(self, key, default=''):
317 try:
548 try:
318 return self._lm[key][1]
549 return self._lm[key][1]
319 except KeyError:
550 except KeyError:
320 return default
551 return default
321
552
322 def copy(self):
553 def copy(self):
323 c = manifestdict()
554 c = manifestdict()
324 c._lm = self._lm.copy()
555 c._lm = self._lm.copy()
325 return c
556 return c
326
557
327 def iteritems(self):
558 def iteritems(self):
328 return (x[:2] for x in self._lm.iterentries())
559 return (x[:2] for x in self._lm.iterentries())
329
560
330 def iterentries(self):
561 def iterentries(self):
331 return self._lm.iterentries()
562 return self._lm.iterentries()
332
563
333 def text(self, usemanifestv2=False):
564 def text(self, usemanifestv2=False):
334 if usemanifestv2:
565 if usemanifestv2:
335 return _textv2(self._lm.iterentries())
566 return _textv2(self._lm.iterentries())
336 else:
567 else:
337 # use (probably) native version for v1
568 # use (probably) native version for v1
338 return self._lm.text()
569 return self._lm.text()
339
570
340 def fastdelta(self, base, changes):
571 def fastdelta(self, base, changes):
341 """Given a base manifest text as an array.array and a list of changes
572 """Given a base manifest text as an array.array and a list of changes
342 relative to that text, compute a delta that can be used by revlog.
573 relative to that text, compute a delta that can be used by revlog.
343 """
574 """
344 delta = []
575 delta = []
345 dstart = None
576 dstart = None
346 dend = None
577 dend = None
347 dline = [""]
578 dline = [""]
348 start = 0
579 start = 0
349 # zero copy representation of base as a buffer
580 # zero copy representation of base as a buffer
350 addbuf = util.buffer(base)
581 addbuf = util.buffer(base)
351
582
352 changes = list(changes)
583 changes = list(changes)
353 if len(changes) < 1000:
584 if len(changes) < 1000:
354 # start with a readonly loop that finds the offset of
585 # start with a readonly loop that finds the offset of
355 # each line and creates the deltas
586 # each line and creates the deltas
356 for f, todelete in changes:
587 for f, todelete in changes:
357 # bs will either be the index of the item or the insert point
588 # bs will either be the index of the item or the insert point
358 start, end = _msearch(addbuf, f, start)
589 start, end = _msearch(addbuf, f, start)
359 if not todelete:
590 if not todelete:
360 h, fl = self._lm[f]
591 h, fl = self._lm[f]
361 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
592 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
362 else:
593 else:
363 if start == end:
594 if start == end:
364 # item we want to delete was not found, error out
595 # item we want to delete was not found, error out
365 raise AssertionError(
596 raise AssertionError(
366 _("failed to remove %s from manifest") % f)
597 _("failed to remove %s from manifest") % f)
367 l = ""
598 l = ""
368 if dstart is not None and dstart <= start and dend >= start:
599 if dstart is not None and dstart <= start and dend >= start:
369 if dend < end:
600 if dend < end:
370 dend = end
601 dend = end
371 if l:
602 if l:
372 dline.append(l)
603 dline.append(l)
373 else:
604 else:
374 if dstart is not None:
605 if dstart is not None:
375 delta.append([dstart, dend, "".join(dline)])
606 delta.append([dstart, dend, "".join(dline)])
376 dstart = start
607 dstart = start
377 dend = end
608 dend = end
378 dline = [l]
609 dline = [l]
379
610
380 if dstart is not None:
611 if dstart is not None:
381 delta.append([dstart, dend, "".join(dline)])
612 delta.append([dstart, dend, "".join(dline)])
382 # apply the delta to the base, and get a delta for addrevision
613 # apply the delta to the base, and get a delta for addrevision
383 deltatext, arraytext = _addlistdelta(base, delta)
614 deltatext, arraytext = _addlistdelta(base, delta)
384 else:
615 else:
385 # For large changes, it's much cheaper to just build the text and
616 # For large changes, it's much cheaper to just build the text and
386 # diff it.
617 # diff it.
387 arraytext = array.array('c', self.text())
618 arraytext = array.array('c', self.text())
388 deltatext = mdiff.textdiff(base, arraytext)
619 deltatext = mdiff.textdiff(base, arraytext)
389
620
390 return arraytext, deltatext
621 return arraytext, deltatext
391
622
392 def _msearch(m, s, lo=0, hi=None):
623 def _msearch(m, s, lo=0, hi=None):
393 '''return a tuple (start, end) that says where to find s within m.
624 '''return a tuple (start, end) that says where to find s within m.
394
625
395 If the string is found m[start:end] are the line containing
626 If the string is found m[start:end] are the line containing
396 that string. If start == end the string was not found and
627 that string. If start == end the string was not found and
397 they indicate the proper sorted insertion point.
628 they indicate the proper sorted insertion point.
398
629
399 m should be a buffer or a string
630 m should be a buffer or a string
400 s is a string'''
631 s is a string'''
401 def advance(i, c):
632 def advance(i, c):
402 while i < lenm and m[i] != c:
633 while i < lenm and m[i] != c:
403 i += 1
634 i += 1
404 return i
635 return i
405 if not s:
636 if not s:
406 return (lo, lo)
637 return (lo, lo)
407 lenm = len(m)
638 lenm = len(m)
408 if not hi:
639 if not hi:
409 hi = lenm
640 hi = lenm
410 while lo < hi:
641 while lo < hi:
411 mid = (lo + hi) // 2
642 mid = (lo + hi) // 2
412 start = mid
643 start = mid
413 while start > 0 and m[start - 1] != '\n':
644 while start > 0 and m[start - 1] != '\n':
414 start -= 1
645 start -= 1
415 end = advance(start, '\0')
646 end = advance(start, '\0')
416 if m[start:end] < s:
647 if m[start:end] < s:
417 # we know that after the null there are 40 bytes of sha1
648 # we know that after the null there are 40 bytes of sha1
418 # this translates to the bisect lo = mid + 1
649 # this translates to the bisect lo = mid + 1
419 lo = advance(end + 40, '\n') + 1
650 lo = advance(end + 40, '\n') + 1
420 else:
651 else:
421 # this translates to the bisect hi = mid
652 # this translates to the bisect hi = mid
422 hi = start
653 hi = start
423 end = advance(lo, '\0')
654 end = advance(lo, '\0')
424 found = m[lo:end]
655 found = m[lo:end]
425 if s == found:
656 if s == found:
426 # we know that after the null there are 40 bytes of sha1
657 # we know that after the null there are 40 bytes of sha1
427 end = advance(end + 40, '\n')
658 end = advance(end + 40, '\n')
428 return (lo, end + 1)
659 return (lo, end + 1)
429 else:
660 else:
430 return (lo, lo)
661 return (lo, lo)
431
662
432 def _checkforbidden(l):
663 def _checkforbidden(l):
433 """Check filenames for illegal characters."""
664 """Check filenames for illegal characters."""
434 for f in l:
665 for f in l:
435 if '\n' in f or '\r' in f:
666 if '\n' in f or '\r' in f:
436 raise error.RevlogError(
667 raise error.RevlogError(
437 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
668 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
438
669
439
670
440 # apply the changes collected during the bisect loop to our addlist
671 # apply the changes collected during the bisect loop to our addlist
441 # return a delta suitable for addrevision
672 # return a delta suitable for addrevision
442 def _addlistdelta(addlist, x):
673 def _addlistdelta(addlist, x):
443 # for large addlist arrays, building a new array is cheaper
674 # for large addlist arrays, building a new array is cheaper
444 # than repeatedly modifying the existing one
675 # than repeatedly modifying the existing one
445 currentposition = 0
676 currentposition = 0
446 newaddlist = array.array('c')
677 newaddlist = array.array('c')
447
678
448 for start, end, content in x:
679 for start, end, content in x:
449 newaddlist += addlist[currentposition:start]
680 newaddlist += addlist[currentposition:start]
450 if content:
681 if content:
451 newaddlist += array.array('c', content)
682 newaddlist += array.array('c', content)
452
683
453 currentposition = end
684 currentposition = end
454
685
455 newaddlist += addlist[currentposition:]
686 newaddlist += addlist[currentposition:]
456
687
457 deltatext = "".join(struct.pack(">lll", start, end, len(content))
688 deltatext = "".join(struct.pack(">lll", start, end, len(content))
458 + content for start, end, content in x)
689 + content for start, end, content in x)
459 return deltatext, newaddlist
690 return deltatext, newaddlist
460
691
461 def _splittopdir(f):
692 def _splittopdir(f):
462 if '/' in f:
693 if '/' in f:
463 dir, subpath = f.split('/', 1)
694 dir, subpath = f.split('/', 1)
464 return dir + '/', subpath
695 return dir + '/', subpath
465 else:
696 else:
466 return '', f
697 return '', f
467
698
468 _noop = lambda s: None
699 _noop = lambda s: None
469
700
470 class treemanifest(object):
701 class treemanifest(object):
471 def __init__(self, dir='', text=''):
702 def __init__(self, dir='', text=''):
472 self._dir = dir
703 self._dir = dir
473 self._node = revlog.nullid
704 self._node = revlog.nullid
474 self._loadfunc = _noop
705 self._loadfunc = _noop
475 self._copyfunc = _noop
706 self._copyfunc = _noop
476 self._dirty = False
707 self._dirty = False
477 self._dirs = {}
708 self._dirs = {}
478 # Using _lazymanifest here is a little slower than plain old dicts
709 # Using _lazymanifest here is a little slower than plain old dicts
479 self._files = {}
710 self._files = {}
480 self._flags = {}
711 self._flags = {}
481 if text:
712 if text:
482 def readsubtree(subdir, subm):
713 def readsubtree(subdir, subm):
483 raise AssertionError('treemanifest constructor only accepts '
714 raise AssertionError('treemanifest constructor only accepts '
484 'flat manifests')
715 'flat manifests')
485 self.parse(text, readsubtree)
716 self.parse(text, readsubtree)
486 self._dirty = True # Mark flat manifest dirty after parsing
717 self._dirty = True # Mark flat manifest dirty after parsing
487
718
488 def _subpath(self, path):
719 def _subpath(self, path):
489 return self._dir + path
720 return self._dir + path
490
721
491 def __len__(self):
722 def __len__(self):
492 self._load()
723 self._load()
493 size = len(self._files)
724 size = len(self._files)
494 for m in self._dirs.values():
725 for m in self._dirs.values():
495 size += m.__len__()
726 size += m.__len__()
496 return size
727 return size
497
728
498 def _isempty(self):
729 def _isempty(self):
499 self._load() # for consistency; already loaded by all callers
730 self._load() # for consistency; already loaded by all callers
500 return (not self._files and (not self._dirs or
731 return (not self._files and (not self._dirs or
501 all(m._isempty() for m in self._dirs.values())))
732 all(m._isempty() for m in self._dirs.values())))
502
733
503 def __repr__(self):
734 def __repr__(self):
504 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
735 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
505 (self._dir, revlog.hex(self._node),
736 (self._dir, revlog.hex(self._node),
506 bool(self._loadfunc is _noop),
737 bool(self._loadfunc is _noop),
507 self._dirty, id(self)))
738 self._dirty, id(self)))
508
739
509 def dir(self):
740 def dir(self):
510 '''The directory that this tree manifest represents, including a
741 '''The directory that this tree manifest represents, including a
511 trailing '/'. Empty string for the repo root directory.'''
742 trailing '/'. Empty string for the repo root directory.'''
512 return self._dir
743 return self._dir
513
744
514 def node(self):
745 def node(self):
515 '''This node of this instance. nullid for unsaved instances. Should
746 '''This node of this instance. nullid for unsaved instances. Should
516 be updated when the instance is read or written from a revlog.
747 be updated when the instance is read or written from a revlog.
517 '''
748 '''
518 assert not self._dirty
749 assert not self._dirty
519 return self._node
750 return self._node
520
751
521 def setnode(self, node):
752 def setnode(self, node):
522 self._node = node
753 self._node = node
523 self._dirty = False
754 self._dirty = False
524
755
525 def iterentries(self):
756 def iterentries(self):
526 self._load()
757 self._load()
527 for p, n in sorted(self._dirs.items() + self._files.items()):
758 for p, n in sorted(self._dirs.items() + self._files.items()):
528 if p in self._files:
759 if p in self._files:
529 yield self._subpath(p), n, self._flags.get(p, '')
760 yield self._subpath(p), n, self._flags.get(p, '')
530 else:
761 else:
531 for x in n.iterentries():
762 for x in n.iterentries():
532 yield x
763 yield x
533
764
534 def iteritems(self):
765 def iteritems(self):
535 self._load()
766 self._load()
536 for p, n in sorted(self._dirs.items() + self._files.items()):
767 for p, n in sorted(self._dirs.items() + self._files.items()):
537 if p in self._files:
768 if p in self._files:
538 yield self._subpath(p), n
769 yield self._subpath(p), n
539 else:
770 else:
540 for f, sn in n.iteritems():
771 for f, sn in n.iteritems():
541 yield f, sn
772 yield f, sn
542
773
543 def iterkeys(self):
774 def iterkeys(self):
544 self._load()
775 self._load()
545 for p in sorted(self._dirs.keys() + self._files.keys()):
776 for p in sorted(self._dirs.keys() + self._files.keys()):
546 if p in self._files:
777 if p in self._files:
547 yield self._subpath(p)
778 yield self._subpath(p)
548 else:
779 else:
549 for f in self._dirs[p].iterkeys():
780 for f in self._dirs[p].iterkeys():
550 yield f
781 yield f
551
782
552 def keys(self):
783 def keys(self):
553 return list(self.iterkeys())
784 return list(self.iterkeys())
554
785
555 def __iter__(self):
786 def __iter__(self):
556 return self.iterkeys()
787 return self.iterkeys()
557
788
558 def __contains__(self, f):
789 def __contains__(self, f):
559 if f is None:
790 if f is None:
560 return False
791 return False
561 self._load()
792 self._load()
562 dir, subpath = _splittopdir(f)
793 dir, subpath = _splittopdir(f)
563 if dir:
794 if dir:
564 if dir not in self._dirs:
795 if dir not in self._dirs:
565 return False
796 return False
566 return self._dirs[dir].__contains__(subpath)
797 return self._dirs[dir].__contains__(subpath)
567 else:
798 else:
568 return f in self._files
799 return f in self._files
569
800
570 def get(self, f, default=None):
801 def get(self, f, default=None):
571 self._load()
802 self._load()
572 dir, subpath = _splittopdir(f)
803 dir, subpath = _splittopdir(f)
573 if dir:
804 if dir:
574 if dir not in self._dirs:
805 if dir not in self._dirs:
575 return default
806 return default
576 return self._dirs[dir].get(subpath, default)
807 return self._dirs[dir].get(subpath, default)
577 else:
808 else:
578 return self._files.get(f, default)
809 return self._files.get(f, default)
579
810
580 def __getitem__(self, f):
811 def __getitem__(self, f):
581 self._load()
812 self._load()
582 dir, subpath = _splittopdir(f)
813 dir, subpath = _splittopdir(f)
583 if dir:
814 if dir:
584 return self._dirs[dir].__getitem__(subpath)
815 return self._dirs[dir].__getitem__(subpath)
585 else:
816 else:
586 return self._files[f]
817 return self._files[f]
587
818
588 def flags(self, f):
819 def flags(self, f):
589 self._load()
820 self._load()
590 dir, subpath = _splittopdir(f)
821 dir, subpath = _splittopdir(f)
591 if dir:
822 if dir:
592 if dir not in self._dirs:
823 if dir not in self._dirs:
593 return ''
824 return ''
594 return self._dirs[dir].flags(subpath)
825 return self._dirs[dir].flags(subpath)
595 else:
826 else:
596 if f in self._dirs:
827 if f in self._dirs:
597 return ''
828 return ''
598 return self._flags.get(f, '')
829 return self._flags.get(f, '')
599
830
600 def find(self, f):
831 def find(self, f):
601 self._load()
832 self._load()
602 dir, subpath = _splittopdir(f)
833 dir, subpath = _splittopdir(f)
603 if dir:
834 if dir:
604 return self._dirs[dir].find(subpath)
835 return self._dirs[dir].find(subpath)
605 else:
836 else:
606 return self._files[f], self._flags.get(f, '')
837 return self._files[f], self._flags.get(f, '')
607
838
608 def __delitem__(self, f):
839 def __delitem__(self, f):
609 self._load()
840 self._load()
610 dir, subpath = _splittopdir(f)
841 dir, subpath = _splittopdir(f)
611 if dir:
842 if dir:
612 self._dirs[dir].__delitem__(subpath)
843 self._dirs[dir].__delitem__(subpath)
613 # If the directory is now empty, remove it
844 # If the directory is now empty, remove it
614 if self._dirs[dir]._isempty():
845 if self._dirs[dir]._isempty():
615 del self._dirs[dir]
846 del self._dirs[dir]
616 else:
847 else:
617 del self._files[f]
848 del self._files[f]
618 if f in self._flags:
849 if f in self._flags:
619 del self._flags[f]
850 del self._flags[f]
620 self._dirty = True
851 self._dirty = True
621
852
622 def __setitem__(self, f, n):
853 def __setitem__(self, f, n):
623 assert n is not None
854 assert n is not None
624 self._load()
855 self._load()
625 dir, subpath = _splittopdir(f)
856 dir, subpath = _splittopdir(f)
626 if dir:
857 if dir:
627 if dir not in self._dirs:
858 if dir not in self._dirs:
628 self._dirs[dir] = treemanifest(self._subpath(dir))
859 self._dirs[dir] = treemanifest(self._subpath(dir))
629 self._dirs[dir].__setitem__(subpath, n)
860 self._dirs[dir].__setitem__(subpath, n)
630 else:
861 else:
631 self._files[f] = n[:21] # to match manifestdict's behavior
862 self._files[f] = n[:21] # to match manifestdict's behavior
632 self._dirty = True
863 self._dirty = True
633
864
634 def _load(self):
865 def _load(self):
635 if self._loadfunc is not _noop:
866 if self._loadfunc is not _noop:
636 lf, self._loadfunc = self._loadfunc, _noop
867 lf, self._loadfunc = self._loadfunc, _noop
637 lf(self)
868 lf(self)
638 elif self._copyfunc is not _noop:
869 elif self._copyfunc is not _noop:
639 cf, self._copyfunc = self._copyfunc, _noop
870 cf, self._copyfunc = self._copyfunc, _noop
640 cf(self)
871 cf(self)
641
872
642 def setflag(self, f, flags):
873 def setflag(self, f, flags):
643 """Set the flags (symlink, executable) for path f."""
874 """Set the flags (symlink, executable) for path f."""
644 self._load()
875 self._load()
645 dir, subpath = _splittopdir(f)
876 dir, subpath = _splittopdir(f)
646 if dir:
877 if dir:
647 if dir not in self._dirs:
878 if dir not in self._dirs:
648 self._dirs[dir] = treemanifest(self._subpath(dir))
879 self._dirs[dir] = treemanifest(self._subpath(dir))
649 self._dirs[dir].setflag(subpath, flags)
880 self._dirs[dir].setflag(subpath, flags)
650 else:
881 else:
651 self._flags[f] = flags
882 self._flags[f] = flags
652 self._dirty = True
883 self._dirty = True
653
884
654 def copy(self):
885 def copy(self):
655 copy = treemanifest(self._dir)
886 copy = treemanifest(self._dir)
656 copy._node = self._node
887 copy._node = self._node
657 copy._dirty = self._dirty
888 copy._dirty = self._dirty
658 if self._copyfunc is _noop:
889 if self._copyfunc is _noop:
659 def _copyfunc(s):
890 def _copyfunc(s):
660 self._load()
891 self._load()
661 for d in self._dirs:
892 for d in self._dirs:
662 s._dirs[d] = self._dirs[d].copy()
893 s._dirs[d] = self._dirs[d].copy()
663 s._files = dict.copy(self._files)
894 s._files = dict.copy(self._files)
664 s._flags = dict.copy(self._flags)
895 s._flags = dict.copy(self._flags)
665 if self._loadfunc is _noop:
896 if self._loadfunc is _noop:
666 _copyfunc(copy)
897 _copyfunc(copy)
667 else:
898 else:
668 copy._copyfunc = _copyfunc
899 copy._copyfunc = _copyfunc
669 else:
900 else:
670 copy._copyfunc = self._copyfunc
901 copy._copyfunc = self._copyfunc
671 return copy
902 return copy
672
903
673 def filesnotin(self, m2):
904 def filesnotin(self, m2):
674 '''Set of files in this manifest that are not in the other'''
905 '''Set of files in this manifest that are not in the other'''
675 files = set()
906 files = set()
676 def _filesnotin(t1, t2):
907 def _filesnotin(t1, t2):
677 if t1._node == t2._node and not t1._dirty and not t2._dirty:
908 if t1._node == t2._node and not t1._dirty and not t2._dirty:
678 return
909 return
679 t1._load()
910 t1._load()
680 t2._load()
911 t2._load()
681 for d, m1 in t1._dirs.iteritems():
912 for d, m1 in t1._dirs.iteritems():
682 if d in t2._dirs:
913 if d in t2._dirs:
683 m2 = t2._dirs[d]
914 m2 = t2._dirs[d]
684 _filesnotin(m1, m2)
915 _filesnotin(m1, m2)
685 else:
916 else:
686 files.update(m1.iterkeys())
917 files.update(m1.iterkeys())
687
918
688 for fn in t1._files.iterkeys():
919 for fn in t1._files.iterkeys():
689 if fn not in t2._files:
920 if fn not in t2._files:
690 files.add(t1._subpath(fn))
921 files.add(t1._subpath(fn))
691
922
692 _filesnotin(self, m2)
923 _filesnotin(self, m2)
693 return files
924 return files
694
925
695 @propertycache
926 @propertycache
696 def _alldirs(self):
927 def _alldirs(self):
697 return util.dirs(self)
928 return util.dirs(self)
698
929
699 def dirs(self):
930 def dirs(self):
700 return self._alldirs
931 return self._alldirs
701
932
702 def hasdir(self, dir):
933 def hasdir(self, dir):
703 self._load()
934 self._load()
704 topdir, subdir = _splittopdir(dir)
935 topdir, subdir = _splittopdir(dir)
705 if topdir:
936 if topdir:
706 if topdir in self._dirs:
937 if topdir in self._dirs:
707 return self._dirs[topdir].hasdir(subdir)
938 return self._dirs[topdir].hasdir(subdir)
708 return False
939 return False
709 return (dir + '/') in self._dirs
940 return (dir + '/') in self._dirs
710
941
711 def walk(self, match):
942 def walk(self, match):
712 '''Generates matching file names.
943 '''Generates matching file names.
713
944
714 Equivalent to manifest.matches(match).iterkeys(), but without creating
945 Equivalent to manifest.matches(match).iterkeys(), but without creating
715 an entirely new manifest.
946 an entirely new manifest.
716
947
717 It also reports nonexistent files by marking them bad with match.bad().
948 It also reports nonexistent files by marking them bad with match.bad().
718 '''
949 '''
719 if match.always():
950 if match.always():
720 for f in iter(self):
951 for f in iter(self):
721 yield f
952 yield f
722 return
953 return
723
954
724 fset = set(match.files())
955 fset = set(match.files())
725
956
726 for fn in self._walk(match):
957 for fn in self._walk(match):
727 if fn in fset:
958 if fn in fset:
728 # specified pattern is the exact name
959 # specified pattern is the exact name
729 fset.remove(fn)
960 fset.remove(fn)
730 yield fn
961 yield fn
731
962
732 # for dirstate.walk, files=['.'] means "walk the whole tree".
963 # for dirstate.walk, files=['.'] means "walk the whole tree".
733 # follow that here, too
964 # follow that here, too
734 fset.discard('.')
965 fset.discard('.')
735
966
736 for fn in sorted(fset):
967 for fn in sorted(fset):
737 if not self.hasdir(fn):
968 if not self.hasdir(fn):
738 match.bad(fn, None)
969 match.bad(fn, None)
739
970
740 def _walk(self, match):
971 def _walk(self, match):
741 '''Recursively generates matching file names for walk().'''
972 '''Recursively generates matching file names for walk().'''
742 if not match.visitdir(self._dir[:-1] or '.'):
973 if not match.visitdir(self._dir[:-1] or '.'):
743 return
974 return
744
975
745 # yield this dir's files and walk its submanifests
976 # yield this dir's files and walk its submanifests
746 self._load()
977 self._load()
747 for p in sorted(self._dirs.keys() + self._files.keys()):
978 for p in sorted(self._dirs.keys() + self._files.keys()):
748 if p in self._files:
979 if p in self._files:
749 fullp = self._subpath(p)
980 fullp = self._subpath(p)
750 if match(fullp):
981 if match(fullp):
751 yield fullp
982 yield fullp
752 else:
983 else:
753 for f in self._dirs[p]._walk(match):
984 for f in self._dirs[p]._walk(match):
754 yield f
985 yield f
755
986
756 def matches(self, match):
987 def matches(self, match):
757 '''generate a new manifest filtered by the match argument'''
988 '''generate a new manifest filtered by the match argument'''
758 if match.always():
989 if match.always():
759 return self.copy()
990 return self.copy()
760
991
761 return self._matches(match)
992 return self._matches(match)
762
993
763 def _matches(self, match):
994 def _matches(self, match):
764 '''recursively generate a new manifest filtered by the match argument.
995 '''recursively generate a new manifest filtered by the match argument.
765 '''
996 '''
766
997
767 visit = match.visitdir(self._dir[:-1] or '.')
998 visit = match.visitdir(self._dir[:-1] or '.')
768 if visit == 'all':
999 if visit == 'all':
769 return self.copy()
1000 return self.copy()
770 ret = treemanifest(self._dir)
1001 ret = treemanifest(self._dir)
771 if not visit:
1002 if not visit:
772 return ret
1003 return ret
773
1004
774 self._load()
1005 self._load()
775 for fn in self._files:
1006 for fn in self._files:
776 fullp = self._subpath(fn)
1007 fullp = self._subpath(fn)
777 if not match(fullp):
1008 if not match(fullp):
778 continue
1009 continue
779 ret._files[fn] = self._files[fn]
1010 ret._files[fn] = self._files[fn]
780 if fn in self._flags:
1011 if fn in self._flags:
781 ret._flags[fn] = self._flags[fn]
1012 ret._flags[fn] = self._flags[fn]
782
1013
783 for dir, subm in self._dirs.iteritems():
1014 for dir, subm in self._dirs.iteritems():
784 m = subm._matches(match)
1015 m = subm._matches(match)
785 if not m._isempty():
1016 if not m._isempty():
786 ret._dirs[dir] = m
1017 ret._dirs[dir] = m
787
1018
788 if not ret._isempty():
1019 if not ret._isempty():
789 ret._dirty = True
1020 ret._dirty = True
790 return ret
1021 return ret
791
1022
792 def diff(self, m2, clean=False):
1023 def diff(self, m2, clean=False):
793 '''Finds changes between the current manifest and m2.
1024 '''Finds changes between the current manifest and m2.
794
1025
795 Args:
1026 Args:
796 m2: the manifest to which this manifest should be compared.
1027 m2: the manifest to which this manifest should be compared.
797 clean: if true, include files unchanged between these manifests
1028 clean: if true, include files unchanged between these manifests
798 with a None value in the returned dictionary.
1029 with a None value in the returned dictionary.
799
1030
800 The result is returned as a dict with filename as key and
1031 The result is returned as a dict with filename as key and
801 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1032 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
802 nodeid in the current/other manifest and fl1/fl2 is the flag
1033 nodeid in the current/other manifest and fl1/fl2 is the flag
803 in the current/other manifest. Where the file does not exist,
1034 in the current/other manifest. Where the file does not exist,
804 the nodeid will be None and the flags will be the empty
1035 the nodeid will be None and the flags will be the empty
805 string.
1036 string.
806 '''
1037 '''
807 result = {}
1038 result = {}
808 emptytree = treemanifest()
1039 emptytree = treemanifest()
809 def _diff(t1, t2):
1040 def _diff(t1, t2):
810 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1041 if t1._node == t2._node and not t1._dirty and not t2._dirty:
811 return
1042 return
812 t1._load()
1043 t1._load()
813 t2._load()
1044 t2._load()
814 for d, m1 in t1._dirs.iteritems():
1045 for d, m1 in t1._dirs.iteritems():
815 m2 = t2._dirs.get(d, emptytree)
1046 m2 = t2._dirs.get(d, emptytree)
816 _diff(m1, m2)
1047 _diff(m1, m2)
817
1048
818 for d, m2 in t2._dirs.iteritems():
1049 for d, m2 in t2._dirs.iteritems():
819 if d not in t1._dirs:
1050 if d not in t1._dirs:
820 _diff(emptytree, m2)
1051 _diff(emptytree, m2)
821
1052
822 for fn, n1 in t1._files.iteritems():
1053 for fn, n1 in t1._files.iteritems():
823 fl1 = t1._flags.get(fn, '')
1054 fl1 = t1._flags.get(fn, '')
824 n2 = t2._files.get(fn, None)
1055 n2 = t2._files.get(fn, None)
825 fl2 = t2._flags.get(fn, '')
1056 fl2 = t2._flags.get(fn, '')
826 if n1 != n2 or fl1 != fl2:
1057 if n1 != n2 or fl1 != fl2:
827 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1058 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
828 elif clean:
1059 elif clean:
829 result[t1._subpath(fn)] = None
1060 result[t1._subpath(fn)] = None
830
1061
831 for fn, n2 in t2._files.iteritems():
1062 for fn, n2 in t2._files.iteritems():
832 if fn not in t1._files:
1063 if fn not in t1._files:
833 fl2 = t2._flags.get(fn, '')
1064 fl2 = t2._flags.get(fn, '')
834 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1065 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
835
1066
836 _diff(self, m2)
1067 _diff(self, m2)
837 return result
1068 return result
838
1069
839 def unmodifiedsince(self, m2):
1070 def unmodifiedsince(self, m2):
840 return not self._dirty and not m2._dirty and self._node == m2._node
1071 return not self._dirty and not m2._dirty and self._node == m2._node
841
1072
842 def parse(self, text, readsubtree):
1073 def parse(self, text, readsubtree):
843 for f, n, fl in _parse(text):
1074 for f, n, fl in _parse(text):
844 if fl == 't':
1075 if fl == 't':
845 f = f + '/'
1076 f = f + '/'
846 self._dirs[f] = readsubtree(self._subpath(f), n)
1077 self._dirs[f] = readsubtree(self._subpath(f), n)
847 elif '/' in f:
1078 elif '/' in f:
848 # This is a flat manifest, so use __setitem__ and setflag rather
1079 # This is a flat manifest, so use __setitem__ and setflag rather
849 # than assigning directly to _files and _flags, so we can
1080 # than assigning directly to _files and _flags, so we can
850 # assign a path in a subdirectory, and to mark dirty (compared
1081 # assign a path in a subdirectory, and to mark dirty (compared
851 # to nullid).
1082 # to nullid).
852 self[f] = n
1083 self[f] = n
853 if fl:
1084 if fl:
854 self.setflag(f, fl)
1085 self.setflag(f, fl)
855 else:
1086 else:
856 # Assigning to _files and _flags avoids marking as dirty,
1087 # Assigning to _files and _flags avoids marking as dirty,
857 # and should be a little faster.
1088 # and should be a little faster.
858 self._files[f] = n
1089 self._files[f] = n
859 if fl:
1090 if fl:
860 self._flags[f] = fl
1091 self._flags[f] = fl
861
1092
862 def text(self, usemanifestv2=False):
1093 def text(self, usemanifestv2=False):
863 """Get the full data of this manifest as a bytestring."""
1094 """Get the full data of this manifest as a bytestring."""
864 self._load()
1095 self._load()
865 return _text(self.iterentries(), usemanifestv2)
1096 return _text(self.iterentries(), usemanifestv2)
866
1097
867 def dirtext(self, usemanifestv2=False):
1098 def dirtext(self, usemanifestv2=False):
868 """Get the full data of this directory as a bytestring. Make sure that
1099 """Get the full data of this directory as a bytestring. Make sure that
869 any submanifests have been written first, so their nodeids are correct.
1100 any submanifests have been written first, so their nodeids are correct.
870 """
1101 """
871 self._load()
1102 self._load()
872 flags = self.flags
1103 flags = self.flags
873 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1104 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
874 files = [(f, self._files[f], flags(f)) for f in self._files]
1105 files = [(f, self._files[f], flags(f)) for f in self._files]
875 return _text(sorted(dirs + files), usemanifestv2)
1106 return _text(sorted(dirs + files), usemanifestv2)
876
1107
877 def read(self, gettext, readsubtree):
1108 def read(self, gettext, readsubtree):
878 def _load_for_read(s):
1109 def _load_for_read(s):
879 s.parse(gettext(), readsubtree)
1110 s.parse(gettext(), readsubtree)
880 s._dirty = False
1111 s._dirty = False
881 self._loadfunc = _load_for_read
1112 self._loadfunc = _load_for_read
882
1113
883 def writesubtrees(self, m1, m2, writesubtree):
1114 def writesubtrees(self, m1, m2, writesubtree):
884 self._load() # for consistency; should never have any effect here
1115 self._load() # for consistency; should never have any effect here
885 m1._load()
1116 m1._load()
886 m2._load()
1117 m2._load()
887 emptytree = treemanifest()
1118 emptytree = treemanifest()
888 for d, subm in self._dirs.iteritems():
1119 for d, subm in self._dirs.iteritems():
889 subp1 = m1._dirs.get(d, emptytree)._node
1120 subp1 = m1._dirs.get(d, emptytree)._node
890 subp2 = m2._dirs.get(d, emptytree)._node
1121 subp2 = m2._dirs.get(d, emptytree)._node
891 if subp1 == revlog.nullid:
1122 if subp1 == revlog.nullid:
892 subp1, subp2 = subp2, subp1
1123 subp1, subp2 = subp2, subp1
893 writesubtree(subm, subp1, subp2)
1124 writesubtree(subm, subp1, subp2)
894
1125
895 class manifestrevlog(revlog.revlog):
1126 class manifestrevlog(revlog.revlog):
896 '''A revlog that stores manifest texts. This is responsible for caching the
1127 '''A revlog that stores manifest texts. This is responsible for caching the
897 full-text manifest contents.
1128 full-text manifest contents.
898 '''
1129 '''
899 def __init__(self, opener, dir='', dirlogcache=None):
1130 def __init__(self, opener, dir='', dirlogcache=None):
900 # During normal operations, we expect to deal with not more than four
1131 # During normal operations, we expect to deal with not more than four
901 # revs at a time (such as during commit --amend). When rebasing large
1132 # revs at a time (such as during commit --amend). When rebasing large
902 # stacks of commits, the number can go up, hence the config knob below.
1133 # stacks of commits, the number can go up, hence the config knob below.
903 cachesize = 4
1134 cachesize = 4
904 usetreemanifest = False
1135 usetreemanifest = False
905 usemanifestv2 = False
1136 usemanifestv2 = False
906 opts = getattr(opener, 'options', None)
1137 opts = getattr(opener, 'options', None)
907 if opts is not None:
1138 if opts is not None:
908 cachesize = opts.get('manifestcachesize', cachesize)
1139 cachesize = opts.get('manifestcachesize', cachesize)
909 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1140 usetreemanifest = opts.get('treemanifest', usetreemanifest)
910 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1141 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
911
1142
912 self._treeondisk = usetreemanifest
1143 self._treeondisk = usetreemanifest
913 self._usemanifestv2 = usemanifestv2
1144 self._usemanifestv2 = usemanifestv2
914
1145
915 self._fulltextcache = util.lrucachedict(cachesize)
1146 self._fulltextcache = util.lrucachedict(cachesize)
916
1147
917 indexfile = "00manifest.i"
1148 indexfile = "00manifest.i"
918 if dir:
1149 if dir:
919 assert self._treeondisk, 'opts is %r' % opts
1150 assert self._treeondisk, 'opts is %r' % opts
920 if not dir.endswith('/'):
1151 if not dir.endswith('/'):
921 dir = dir + '/'
1152 dir = dir + '/'
922 indexfile = "meta/" + dir + "00manifest.i"
1153 indexfile = "meta/" + dir + "00manifest.i"
923 self._dir = dir
1154 self._dir = dir
924 # The dirlogcache is kept on the root manifest log
1155 # The dirlogcache is kept on the root manifest log
925 if dir:
1156 if dir:
926 self._dirlogcache = dirlogcache
1157 self._dirlogcache = dirlogcache
927 else:
1158 else:
928 self._dirlogcache = {'': self}
1159 self._dirlogcache = {'': self}
929
1160
930 super(manifestrevlog, self).__init__(opener, indexfile,
1161 super(manifestrevlog, self).__init__(opener, indexfile,
931 checkambig=bool(dir))
1162 checkambig=bool(dir))
932
1163
933 @property
1164 @property
934 def fulltextcache(self):
1165 def fulltextcache(self):
935 return self._fulltextcache
1166 return self._fulltextcache
936
1167
937 def clearcaches(self):
1168 def clearcaches(self):
938 super(manifestrevlog, self).clearcaches()
1169 super(manifestrevlog, self).clearcaches()
939 self._fulltextcache.clear()
1170 self._fulltextcache.clear()
940 self._dirlogcache = {'': self}
1171 self._dirlogcache = {'': self}
941
1172
942 def dirlog(self, dir):
1173 def dirlog(self, dir):
943 if dir:
1174 if dir:
944 assert self._treeondisk
1175 assert self._treeondisk
945 if dir not in self._dirlogcache:
1176 if dir not in self._dirlogcache:
946 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1177 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
947 self._dirlogcache)
1178 self._dirlogcache)
948 return self._dirlogcache[dir]
1179 return self._dirlogcache[dir]
949
1180
950 def add(self, m, transaction, link, p1, p2, added, removed):
1181 def add(self, m, transaction, link, p1, p2, added, removed):
951 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1182 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
952 and not self._usemanifestv2):
1183 and not self._usemanifestv2):
953 # If our first parent is in the manifest cache, we can
1184 # If our first parent is in the manifest cache, we can
954 # compute a delta here using properties we know about the
1185 # compute a delta here using properties we know about the
955 # manifest up-front, which may save time later for the
1186 # manifest up-front, which may save time later for the
956 # revlog layer.
1187 # revlog layer.
957
1188
958 _checkforbidden(added)
1189 _checkforbidden(added)
959 # combine the changed lists into one sorted iterator
1190 # combine the changed lists into one sorted iterator
960 work = heapq.merge([(x, False) for x in added],
1191 work = heapq.merge([(x, False) for x in added],
961 [(x, True) for x in removed])
1192 [(x, True) for x in removed])
962
1193
963 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1194 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
964 cachedelta = self.rev(p1), deltatext
1195 cachedelta = self.rev(p1), deltatext
965 text = util.buffer(arraytext)
1196 text = util.buffer(arraytext)
966 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1197 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
967 else:
1198 else:
968 # The first parent manifest isn't already loaded, so we'll
1199 # The first parent manifest isn't already loaded, so we'll
969 # just encode a fulltext of the manifest and pass that
1200 # just encode a fulltext of the manifest and pass that
970 # through to the revlog layer, and let it handle the delta
1201 # through to the revlog layer, and let it handle the delta
971 # process.
1202 # process.
972 if self._treeondisk:
1203 if self._treeondisk:
973 m1 = self.read(p1)
1204 m1 = self.read(p1)
974 m2 = self.read(p2)
1205 m2 = self.read(p2)
975 n = self._addtree(m, transaction, link, m1, m2)
1206 n = self._addtree(m, transaction, link, m1, m2)
976 arraytext = None
1207 arraytext = None
977 else:
1208 else:
978 text = m.text(self._usemanifestv2)
1209 text = m.text(self._usemanifestv2)
979 n = self.addrevision(text, transaction, link, p1, p2)
1210 n = self.addrevision(text, transaction, link, p1, p2)
980 arraytext = array.array('c', text)
1211 arraytext = array.array('c', text)
981
1212
982 self.fulltextcache[n] = arraytext
1213 self.fulltextcache[n] = arraytext
983
1214
984 return n
1215 return n
985
1216
986 def _addtree(self, m, transaction, link, m1, m2):
1217 def _addtree(self, m, transaction, link, m1, m2):
987 # If the manifest is unchanged compared to one parent,
1218 # If the manifest is unchanged compared to one parent,
988 # don't write a new revision
1219 # don't write a new revision
989 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1220 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
990 return m.node()
1221 return m.node()
991 def writesubtree(subm, subp1, subp2):
1222 def writesubtree(subm, subp1, subp2):
992 sublog = self.dirlog(subm.dir())
1223 sublog = self.dirlog(subm.dir())
993 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1224 sublog.add(subm, transaction, link, subp1, subp2, None, None)
994 m.writesubtrees(m1, m2, writesubtree)
1225 m.writesubtrees(m1, m2, writesubtree)
995 text = m.dirtext(self._usemanifestv2)
1226 text = m.dirtext(self._usemanifestv2)
996 # Double-check whether contents are unchanged to one parent
1227 # Double-check whether contents are unchanged to one parent
997 if text == m1.dirtext(self._usemanifestv2):
1228 if text == m1.dirtext(self._usemanifestv2):
998 n = m1.node()
1229 n = m1.node()
999 elif text == m2.dirtext(self._usemanifestv2):
1230 elif text == m2.dirtext(self._usemanifestv2):
1000 n = m2.node()
1231 n = m2.node()
1001 else:
1232 else:
1002 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1233 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1003 # Save nodeid so parent manifest can calculate its nodeid
1234 # Save nodeid so parent manifest can calculate its nodeid
1004 m.setnode(n)
1235 m.setnode(n)
1005 return n
1236 return n
1006
1237
1007 class manifestlog(object):
1238 class manifestlog(object):
1008 """A collection class representing the collection of manifest snapshots
1239 """A collection class representing the collection of manifest snapshots
1009 referenced by commits in the repository.
1240 referenced by commits in the repository.
1010
1241
1011 In this situation, 'manifest' refers to the abstract concept of a snapshot
1242 In this situation, 'manifest' refers to the abstract concept of a snapshot
1012 of the list of files in the given commit. Consumers of the output of this
1243 of the list of files in the given commit. Consumers of the output of this
1013 class do not care about the implementation details of the actual manifests
1244 class do not care about the implementation details of the actual manifests
1014 they receive (i.e. tree or flat or lazily loaded, etc)."""
1245 they receive (i.e. tree or flat or lazily loaded, etc)."""
1015 def __init__(self, opener, repo):
1246 def __init__(self, opener, repo):
1016 self._repo = repo
1247 self._repo = repo
1017
1248
1018 usetreemanifest = False
1249 usetreemanifest = False
1019
1250
1020 opts = getattr(opener, 'options', None)
1251 opts = getattr(opener, 'options', None)
1021 if opts is not None:
1252 if opts is not None:
1022 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1253 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1023 self._treeinmem = usetreemanifest
1254 self._treeinmem = usetreemanifest
1024
1255
1025 # We'll separate this into it's own cache once oldmanifest is no longer
1256 # We'll separate this into it's own cache once oldmanifest is no longer
1026 # used
1257 # used
1027 self._mancache = repo.manifest._mancache
1258 self._mancache = repo.manifest._mancache
1028
1259
1029 @property
1260 @property
1030 def _revlog(self):
1261 def _revlog(self):
1031 return self._repo.manifest
1262 return self._repo.manifest
1032
1263
1033 def __getitem__(self, node):
1264 def __getitem__(self, node):
1034 """Retrieves the manifest instance for the given node. Throws a KeyError
1265 """Retrieves the manifest instance for the given node. Throws a KeyError
1035 if not found.
1266 if not found.
1036 """
1267 """
1037 if node in self._mancache:
1268 if node in self._mancache:
1038 cachemf = self._mancache[node]
1269 cachemf = self._mancache[node]
1039 # The old manifest may put non-ctx manifests in the cache, so skip
1270 # The old manifest may put non-ctx manifests in the cache, so skip
1040 # those since they don't implement the full api.
1271 # those since they don't implement the full api.
1041 if (isinstance(cachemf, manifestctx) or
1272 if (isinstance(cachemf, manifestctx) or
1042 isinstance(cachemf, treemanifestctx)):
1273 isinstance(cachemf, treemanifestctx)):
1043 return cachemf
1274 return cachemf
1044
1275
1045 if self._treeinmem:
1276 if self._treeinmem:
1046 m = treemanifestctx(self._revlog, '', node)
1277 m = treemanifestctx(self._revlog, '', node)
1047 else:
1278 else:
1048 m = manifestctx(self._revlog, node)
1279 m = manifestctx(self._revlog, node)
1049 if node != revlog.nullid:
1280 if node != revlog.nullid:
1050 self._mancache[node] = m
1281 self._mancache[node] = m
1051 return m
1282 return m
1052
1283
1053 def add(self, m, transaction, link, p1, p2, added, removed):
1284 def add(self, m, transaction, link, p1, p2, added, removed):
1054 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1285 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1055
1286
1056 class manifestctx(object):
1287 class manifestctx(object):
1057 """A class representing a single revision of a manifest, including its
1288 """A class representing a single revision of a manifest, including its
1058 contents, its parent revs, and its linkrev.
1289 contents, its parent revs, and its linkrev.
1059 """
1290 """
1060 def __init__(self, revlog, node):
1291 def __init__(self, revlog, node):
1061 self._revlog = revlog
1292 self._revlog = revlog
1062 self._data = None
1293 self._data = None
1063
1294
1064 self._node = node
1295 self._node = node
1065
1296
1066 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1297 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1067 # but let's add it later when something needs it and we can load it
1298 # but let's add it later when something needs it and we can load it
1068 # lazily.
1299 # lazily.
1069 #self.p1, self.p2 = revlog.parents(node)
1300 #self.p1, self.p2 = revlog.parents(node)
1070 #rev = revlog.rev(node)
1301 #rev = revlog.rev(node)
1071 #self.linkrev = revlog.linkrev(rev)
1302 #self.linkrev = revlog.linkrev(rev)
1072
1303
1073 def node(self):
1304 def node(self):
1074 return self._node
1305 return self._node
1075
1306
1076 def read(self):
1307 def read(self):
1077 if not self._data:
1308 if not self._data:
1078 if self._node == revlog.nullid:
1309 if self._node == revlog.nullid:
1079 self._data = manifestdict()
1310 self._data = manifestdict()
1080 else:
1311 else:
1081 text = self._revlog.revision(self._node)
1312 text = self._revlog.revision(self._node)
1082 arraytext = array.array('c', text)
1313 arraytext = array.array('c', text)
1083 self._revlog._fulltextcache[self._node] = arraytext
1314 self._revlog._fulltextcache[self._node] = arraytext
1084 self._data = manifestdict(text)
1315 self._data = manifestdict(text)
1085 return self._data
1316 return self._data
1086
1317
1087 def readfast(self):
1318 def readfast(self):
1088 rl = self._revlog
1319 rl = self._revlog
1089 r = rl.rev(self._node)
1320 r = rl.rev(self._node)
1090 deltaparent = rl.deltaparent(r)
1321 deltaparent = rl.deltaparent(r)
1091 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1322 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1092 return self.readdelta()
1323 return self.readdelta()
1093 return self.read()
1324 return self.read()
1094
1325
1095 def readdelta(self):
1326 def readdelta(self):
1096 revlog = self._revlog
1327 revlog = self._revlog
1097 if revlog._usemanifestv2:
1328 if revlog._usemanifestv2:
1098 # Need to perform a slow delta
1329 # Need to perform a slow delta
1099 r0 = revlog.deltaparent(revlog.rev(self._node))
1330 r0 = revlog.deltaparent(revlog.rev(self._node))
1100 m0 = manifestctx(revlog, revlog.node(r0)).read()
1331 m0 = manifestctx(revlog, revlog.node(r0)).read()
1101 m1 = self.read()
1332 m1 = self.read()
1102 md = manifestdict()
1333 md = manifestdict()
1103 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1334 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1104 if n1:
1335 if n1:
1105 md[f] = n1
1336 md[f] = n1
1106 if fl1:
1337 if fl1:
1107 md.setflag(f, fl1)
1338 md.setflag(f, fl1)
1108 return md
1339 return md
1109
1340
1110 r = revlog.rev(self._node)
1341 r = revlog.rev(self._node)
1111 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1342 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1112 return manifestdict(d)
1343 return manifestdict(d)
1113
1344
1114 class treemanifestctx(object):
1345 class treemanifestctx(object):
1115 def __init__(self, revlog, dir, node):
1346 def __init__(self, revlog, dir, node):
1116 revlog = revlog.dirlog(dir)
1347 revlog = revlog.dirlog(dir)
1117 self._revlog = revlog
1348 self._revlog = revlog
1118 self._dir = dir
1349 self._dir = dir
1119 self._data = None
1350 self._data = None
1120
1351
1121 self._node = node
1352 self._node = node
1122
1353
1123 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1354 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1124 # we can instantiate treemanifestctx objects for directories we don't
1355 # we can instantiate treemanifestctx objects for directories we don't
1125 # have on disk.
1356 # have on disk.
1126 #self.p1, self.p2 = revlog.parents(node)
1357 #self.p1, self.p2 = revlog.parents(node)
1127 #rev = revlog.rev(node)
1358 #rev = revlog.rev(node)
1128 #self.linkrev = revlog.linkrev(rev)
1359 #self.linkrev = revlog.linkrev(rev)
1129
1360
1130 def read(self):
1361 def read(self):
1131 if not self._data:
1362 if not self._data:
1132 if self._node == revlog.nullid:
1363 if self._node == revlog.nullid:
1133 self._data = treemanifest()
1364 self._data = treemanifest()
1134 elif self._revlog._treeondisk:
1365 elif self._revlog._treeondisk:
1135 m = treemanifest(dir=self._dir)
1366 m = treemanifest(dir=self._dir)
1136 def gettext():
1367 def gettext():
1137 return self._revlog.revision(self._node)
1368 return self._revlog.revision(self._node)
1138 def readsubtree(dir, subm):
1369 def readsubtree(dir, subm):
1139 return treemanifestctx(self._revlog, dir, subm).read()
1370 return treemanifestctx(self._revlog, dir, subm).read()
1140 m.read(gettext, readsubtree)
1371 m.read(gettext, readsubtree)
1141 m.setnode(self._node)
1372 m.setnode(self._node)
1142 self._data = m
1373 self._data = m
1143 else:
1374 else:
1144 text = self._revlog.revision(self._node)
1375 text = self._revlog.revision(self._node)
1145 arraytext = array.array('c', text)
1376 arraytext = array.array('c', text)
1146 self._revlog.fulltextcache[self._node] = arraytext
1377 self._revlog.fulltextcache[self._node] = arraytext
1147 self._data = treemanifest(dir=self._dir, text=text)
1378 self._data = treemanifest(dir=self._dir, text=text)
1148
1379
1149 return self._data
1380 return self._data
1150
1381
1151 def node(self):
1382 def node(self):
1152 return self._node
1383 return self._node
1153
1384
1154 def readdelta(self):
1385 def readdelta(self):
1155 # Need to perform a slow delta
1386 # Need to perform a slow delta
1156 revlog = self._revlog
1387 revlog = self._revlog
1157 r0 = revlog.deltaparent(revlog.rev(self._node))
1388 r0 = revlog.deltaparent(revlog.rev(self._node))
1158 m0 = treemanifestctx(revlog, revlog.node(r0), dir=self._dir).read()
1389 m0 = treemanifestctx(revlog, revlog.node(r0), dir=self._dir).read()
1159 m1 = self.read()
1390 m1 = self.read()
1160 md = treemanifest(dir=self._dir)
1391 md = treemanifest(dir=self._dir)
1161 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1392 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1162 if n1:
1393 if n1:
1163 md[f] = n1
1394 md[f] = n1
1164 if fl1:
1395 if fl1:
1165 md.setflag(f, fl1)
1396 md.setflag(f, fl1)
1166 return md
1397 return md
1167
1398
1168 def readfast(self):
1399 def readfast(self):
1169 rl = self._revlog
1400 rl = self._revlog
1170 r = rl.rev(self._node)
1401 r = rl.rev(self._node)
1171 deltaparent = rl.deltaparent(r)
1402 deltaparent = rl.deltaparent(r)
1172 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1403 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1173 return self.readdelta()
1404 return self.readdelta()
1174 return self.read()
1405 return self.read()
1175
1406
1176 class manifest(manifestrevlog):
1407 class manifest(manifestrevlog):
1177 def __init__(self, opener, dir='', dirlogcache=None):
1408 def __init__(self, opener, dir='', dirlogcache=None):
1178 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1409 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1179 manifest.manifest only. External users should create a root manifest
1410 manifest.manifest only. External users should create a root manifest
1180 log with manifest.manifest(opener) and call dirlog() on it.
1411 log with manifest.manifest(opener) and call dirlog() on it.
1181 '''
1412 '''
1182 # During normal operations, we expect to deal with not more than four
1413 # During normal operations, we expect to deal with not more than four
1183 # revs at a time (such as during commit --amend). When rebasing large
1414 # revs at a time (such as during commit --amend). When rebasing large
1184 # stacks of commits, the number can go up, hence the config knob below.
1415 # stacks of commits, the number can go up, hence the config knob below.
1185 cachesize = 4
1416 cachesize = 4
1186 usetreemanifest = False
1417 usetreemanifest = False
1187 opts = getattr(opener, 'options', None)
1418 opts = getattr(opener, 'options', None)
1188 if opts is not None:
1419 if opts is not None:
1189 cachesize = opts.get('manifestcachesize', cachesize)
1420 cachesize = opts.get('manifestcachesize', cachesize)
1190 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1421 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1191 self._mancache = util.lrucachedict(cachesize)
1422 self._mancache = util.lrucachedict(cachesize)
1192 self._treeinmem = usetreemanifest
1423 self._treeinmem = usetreemanifest
1193 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1424 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1194
1425
1195 def _newmanifest(self, data=''):
1426 def _newmanifest(self, data=''):
1196 if self._treeinmem:
1427 if self._treeinmem:
1197 return treemanifest(self._dir, data)
1428 return treemanifest(self._dir, data)
1198 return manifestdict(data)
1429 return manifestdict(data)
1199
1430
1200 def dirlog(self, dir):
1431 def dirlog(self, dir):
1201 """This overrides the base revlog implementation to allow construction
1432 """This overrides the base revlog implementation to allow construction
1202 'manifest' types instead of manifestrevlog types. This is only needed
1433 'manifest' types instead of manifestrevlog types. This is only needed
1203 until we migrate off the 'manifest' type."""
1434 until we migrate off the 'manifest' type."""
1204 if dir:
1435 if dir:
1205 assert self._treeondisk
1436 assert self._treeondisk
1206 if dir not in self._dirlogcache:
1437 if dir not in self._dirlogcache:
1207 self._dirlogcache[dir] = manifest(self.opener, dir,
1438 self._dirlogcache[dir] = manifest(self.opener, dir,
1208 self._dirlogcache)
1439 self._dirlogcache)
1209 return self._dirlogcache[dir]
1440 return self._dirlogcache[dir]
1210
1441
1211 def _slowreaddelta(self, node):
1442 def _slowreaddelta(self, node):
1212 r0 = self.deltaparent(self.rev(node))
1443 r0 = self.deltaparent(self.rev(node))
1213 m0 = self.read(self.node(r0))
1444 m0 = self.read(self.node(r0))
1214 m1 = self.read(node)
1445 m1 = self.read(node)
1215 md = self._newmanifest()
1446 md = self._newmanifest()
1216 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1447 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1217 if n1:
1448 if n1:
1218 md[f] = n1
1449 md[f] = n1
1219 if fl1:
1450 if fl1:
1220 md.setflag(f, fl1)
1451 md.setflag(f, fl1)
1221 return md
1452 return md
1222
1453
1223 def readdelta(self, node):
1454 def readdelta(self, node):
1224 if self._usemanifestv2 or self._treeondisk:
1455 if self._usemanifestv2 or self._treeondisk:
1225 return self._slowreaddelta(node)
1456 return self._slowreaddelta(node)
1226 r = self.rev(node)
1457 r = self.rev(node)
1227 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1458 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1228 return self._newmanifest(d)
1459 return self._newmanifest(d)
1229
1460
1230 def readshallowdelta(self, node):
1461 def readshallowdelta(self, node):
1231 '''For flat manifests, this is the same as readdelta(). For
1462 '''For flat manifests, this is the same as readdelta(). For
1232 treemanifests, this will read the delta for this revlog's directory,
1463 treemanifests, this will read the delta for this revlog's directory,
1233 without recursively reading subdirectory manifests. Instead, any
1464 without recursively reading subdirectory manifests. Instead, any
1234 subdirectory entry will be reported as it appears in the manifests, i.e.
1465 subdirectory entry will be reported as it appears in the manifests, i.e.
1235 the subdirectory will be reported among files and distinguished only by
1466 the subdirectory will be reported among files and distinguished only by
1236 its 't' flag.'''
1467 its 't' flag.'''
1237 if not self._treeondisk:
1468 if not self._treeondisk:
1238 return self.readdelta(node)
1469 return self.readdelta(node)
1239 if self._usemanifestv2:
1470 if self._usemanifestv2:
1240 raise error.Abort(
1471 raise error.Abort(
1241 _("readshallowdelta() not implemented for manifestv2"))
1472 _("readshallowdelta() not implemented for manifestv2"))
1242 r = self.rev(node)
1473 r = self.rev(node)
1243 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1474 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1244 return manifestdict(d)
1475 return manifestdict(d)
1245
1476
1246 def readshallowfast(self, node):
1477 def readshallowfast(self, node):
1247 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1478 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1248 '''
1479 '''
1249 r = self.rev(node)
1480 r = self.rev(node)
1250 deltaparent = self.deltaparent(r)
1481 deltaparent = self.deltaparent(r)
1251 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1482 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1252 return self.readshallowdelta(node)
1483 return self.readshallowdelta(node)
1253 return self.readshallow(node)
1484 return self.readshallow(node)
1254
1485
1255 def read(self, node):
1486 def read(self, node):
1256 if node == revlog.nullid:
1487 if node == revlog.nullid:
1257 return self._newmanifest() # don't upset local cache
1488 return self._newmanifest() # don't upset local cache
1258 if node in self._mancache:
1489 if node in self._mancache:
1259 cached = self._mancache[node]
1490 cached = self._mancache[node]
1260 if (isinstance(cached, manifestctx) or
1491 if (isinstance(cached, manifestctx) or
1261 isinstance(cached, treemanifestctx)):
1492 isinstance(cached, treemanifestctx)):
1262 cached = cached.read()
1493 cached = cached.read()
1263 return cached
1494 return cached
1264 if self._treeondisk:
1495 if self._treeondisk:
1265 def gettext():
1496 def gettext():
1266 return self.revision(node)
1497 return self.revision(node)
1267 def readsubtree(dir, subm):
1498 def readsubtree(dir, subm):
1268 return self.dirlog(dir).read(subm)
1499 return self.dirlog(dir).read(subm)
1269 m = self._newmanifest()
1500 m = self._newmanifest()
1270 m.read(gettext, readsubtree)
1501 m.read(gettext, readsubtree)
1271 m.setnode(node)
1502 m.setnode(node)
1272 arraytext = None
1503 arraytext = None
1273 else:
1504 else:
1274 text = self.revision(node)
1505 text = self.revision(node)
1275 m = self._newmanifest(text)
1506 m = self._newmanifest(text)
1276 arraytext = array.array('c', text)
1507 arraytext = array.array('c', text)
1277 self._mancache[node] = m
1508 self._mancache[node] = m
1278 self.fulltextcache[node] = arraytext
1509 self.fulltextcache[node] = arraytext
1279 return m
1510 return m
1280
1511
1281 def readshallow(self, node):
1512 def readshallow(self, node):
1282 '''Reads the manifest in this directory. When using flat manifests,
1513 '''Reads the manifest in this directory. When using flat manifests,
1283 this manifest will generally have files in subdirectories in it. Does
1514 this manifest will generally have files in subdirectories in it. Does
1284 not cache the manifest as the callers generally do not read the same
1515 not cache the manifest as the callers generally do not read the same
1285 version twice.'''
1516 version twice.'''
1286 return manifestdict(self.revision(node))
1517 return manifestdict(self.revision(node))
1287
1518
1288 def find(self, node, f):
1519 def find(self, node, f):
1289 '''look up entry for a single file efficiently.
1520 '''look up entry for a single file efficiently.
1290 return (node, flags) pair if found, (None, None) if not.'''
1521 return (node, flags) pair if found, (None, None) if not.'''
1291 m = self.read(node)
1522 m = self.read(node)
1292 try:
1523 try:
1293 return m.find(f)
1524 return m.find(f)
1294 except KeyError:
1525 except KeyError:
1295 return None, None
1526 return None, None
1296
1527
1297 def clearcaches(self):
1528 def clearcaches(self):
1298 super(manifest, self).clearcaches()
1529 super(manifest, self).clearcaches()
1299 self._mancache.clear()
1530 self._mancache.clear()
@@ -1,169 +1,169 b''
1 # bdiff.py - Python implementation of bdiff.c
1 # bdiff.py - Python implementation of bdiff.c
2 #
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import difflib
11 import difflib
12 import re
12 import re
13 import struct
13 import struct
14
14
15 from . import policy
15 from . import policy
16 policynocffi = policy.policynocffi
16 policynocffi = policy.policynocffi
17 modulepolicy = policy.policy
17 modulepolicy = policy.policy
18
18
19 def splitnewlines(text):
19 def splitnewlines(text):
20 '''like str.splitlines, but only split on newlines.'''
20 '''like str.splitlines, but only split on newlines.'''
21 lines = [l + '\n' for l in text.split('\n')]
21 lines = [l + '\n' for l in text.split('\n')]
22 if lines:
22 if lines:
23 if lines[-1] == '\n':
23 if lines[-1] == '\n':
24 lines.pop()
24 lines.pop()
25 else:
25 else:
26 lines[-1] = lines[-1][:-1]
26 lines[-1] = lines[-1][:-1]
27 return lines
27 return lines
28
28
29 def _normalizeblocks(a, b, blocks):
29 def _normalizeblocks(a, b, blocks):
30 prev = None
30 prev = None
31 r = []
31 r = []
32 for curr in blocks:
32 for curr in blocks:
33 if prev is None:
33 if prev is None:
34 prev = curr
34 prev = curr
35 continue
35 continue
36 shift = 0
36 shift = 0
37
37
38 a1, b1, l1 = prev
38 a1, b1, l1 = prev
39 a1end = a1 + l1
39 a1end = a1 + l1
40 b1end = b1 + l1
40 b1end = b1 + l1
41
41
42 a2, b2, l2 = curr
42 a2, b2, l2 = curr
43 a2end = a2 + l2
43 a2end = a2 + l2
44 b2end = b2 + l2
44 b2end = b2 + l2
45 if a1end == a2:
45 if a1end == a2:
46 while (a1end + shift < a2end and
46 while (a1end + shift < a2end and
47 a[a1end + shift] == b[b1end + shift]):
47 a[a1end + shift] == b[b1end + shift]):
48 shift += 1
48 shift += 1
49 elif b1end == b2:
49 elif b1end == b2:
50 while (b1end + shift < b2end and
50 while (b1end + shift < b2end and
51 a[a1end + shift] == b[b1end + shift]):
51 a[a1end + shift] == b[b1end + shift]):
52 shift += 1
52 shift += 1
53 r.append((a1, b1, l1 + shift))
53 r.append((a1, b1, l1 + shift))
54 prev = a2 + shift, b2 + shift, l2 - shift
54 prev = a2 + shift, b2 + shift, l2 - shift
55 r.append(prev)
55 r.append(prev)
56 return r
56 return r
57
57
58 def _tostring(c):
58 def _tostring(c):
59 if type(c) is array.array:
59 if type(c) is array.array:
60 # this copy overhead isn't ideal
60 # this copy overhead isn't ideal
61 return c.tostring()
61 return c.tostring()
62 return str(c)
62 return str(c)
63
63
64 def bdiff(a, b):
64 def bdiff(a, b):
65 a = _tostring(a).splitlines(True)
65 a = _tostring(a).splitlines(True)
66 b = _tostring(b).splitlines(True)
66 b = _tostring(b).splitlines(True)
67
67
68 if not a:
68 if not a:
69 s = "".join(b)
69 s = "".join(b)
70 return s and (struct.pack(">lll", 0, 0, len(s)) + s)
70 return s and (struct.pack(">lll", 0, 0, len(s)) + s)
71
71
72 bin = []
72 bin = []
73 p = [0]
73 p = [0]
74 for i in a: p.append(p[-1] + len(i))
74 for i in a: p.append(p[-1] + len(i))
75
75
76 d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
76 d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
77 d = _normalizeblocks(a, b, d)
77 d = _normalizeblocks(a, b, d)
78 la = 0
78 la = 0
79 lb = 0
79 lb = 0
80 for am, bm, size in d:
80 for am, bm, size in d:
81 s = "".join(b[lb:bm])
81 s = "".join(b[lb:bm])
82 if am > la or s:
82 if am > la or s:
83 bin.append(struct.pack(">lll", p[la], p[am], len(s)) + s)
83 bin.append(struct.pack(">lll", p[la], p[am], len(s)) + s)
84 la = am + size
84 la = am + size
85 lb = bm + size
85 lb = bm + size
86
86
87 return "".join(bin)
87 return "".join(bin)
88
88
89 def blocks(a, b):
89 def blocks(a, b):
90 an = splitnewlines(a)
90 an = splitnewlines(a)
91 bn = splitnewlines(b)
91 bn = splitnewlines(b)
92 d = difflib.SequenceMatcher(None, an, bn).get_matching_blocks()
92 d = difflib.SequenceMatcher(None, an, bn).get_matching_blocks()
93 d = _normalizeblocks(an, bn, d)
93 d = _normalizeblocks(an, bn, d)
94 return [(i, i + n, j, j + n) for (i, j, n) in d]
94 return [(i, i + n, j, j + n) for (i, j, n) in d]
95
95
96 def fixws(text, allws):
96 def fixws(text, allws):
97 if allws:
97 if allws:
98 text = re.sub('[ \t\r]+', '', text)
98 text = re.sub('[ \t\r]+', '', text)
99 else:
99 else:
100 text = re.sub('[ \t\r]+', ' ', text)
100 text = re.sub('[ \t\r]+', ' ', text)
101 text = text.replace(' \n', '\n')
101 text = text.replace(' \n', '\n')
102 return text
102 return text
103
103
104 if modulepolicy not in policynocffi:
104 if modulepolicy not in policynocffi:
105 try:
105 try:
106 from _bdiff_cffi import ffi, lib
106 from _bdiff_cffi import ffi, lib
107 except ImportError:
107 except ImportError:
108 if modulepolicy == 'cffi': # strict cffi import
108 if modulepolicy == 'cffi': # strict cffi import
109 raise
109 raise
110 else:
110 else:
111 def blocks(sa, sb):
111 def blocks(sa, sb):
112 a = ffi.new("struct bdiff_line**")
112 a = ffi.new("struct bdiff_line**")
113 b = ffi.new("struct bdiff_line**")
113 b = ffi.new("struct bdiff_line**")
114 ac = ffi.new("char[]", sa)
114 ac = ffi.new("char[]", str(sa))
115 bc = ffi.new("char[]", sb)
115 bc = ffi.new("char[]", str(sb))
116 l = ffi.new("struct bdiff_hunk*")
116 l = ffi.new("struct bdiff_hunk*")
117 try:
117 try:
118 an = lib.bdiff_splitlines(ac, len(sa), a)
118 an = lib.bdiff_splitlines(ac, len(sa), a)
119 bn = lib.bdiff_splitlines(bc, len(sb), b)
119 bn = lib.bdiff_splitlines(bc, len(sb), b)
120 if not a[0] or not b[0]:
120 if not a[0] or not b[0]:
121 raise MemoryError
121 raise MemoryError
122 count = lib.bdiff_diff(a[0], an, b[0], bn, l)
122 count = lib.bdiff_diff(a[0], an, b[0], bn, l)
123 if count < 0:
123 if count < 0:
124 raise MemoryError
124 raise MemoryError
125 rl = [None] * count
125 rl = [None] * count
126 h = l.next
126 h = l.next
127 i = 0
127 i = 0
128 while h:
128 while h:
129 rl[i] = (h.a1, h.a2, h.b1, h.b2)
129 rl[i] = (h.a1, h.a2, h.b1, h.b2)
130 h = h.next
130 h = h.next
131 i += 1
131 i += 1
132 finally:
132 finally:
133 lib.free(a[0])
133 lib.free(a[0])
134 lib.free(b[0])
134 lib.free(b[0])
135 lib.bdiff_freehunks(l.next)
135 lib.bdiff_freehunks(l.next)
136 return rl
136 return rl
137
137
138 def bdiff(sa, sb):
138 def bdiff(sa, sb):
139 a = ffi.new("struct bdiff_line**")
139 a = ffi.new("struct bdiff_line**")
140 b = ffi.new("struct bdiff_line**")
140 b = ffi.new("struct bdiff_line**")
141 ac = ffi.new("char[]", sa)
141 ac = ffi.new("char[]", str(sa))
142 bc = ffi.new("char[]", sb)
142 bc = ffi.new("char[]", str(sb))
143 l = ffi.new("struct bdiff_hunk*")
143 l = ffi.new("struct bdiff_hunk*")
144 try:
144 try:
145 an = lib.bdiff_splitlines(ac, len(sa), a)
145 an = lib.bdiff_splitlines(ac, len(sa), a)
146 bn = lib.bdiff_splitlines(bc, len(sb), b)
146 bn = lib.bdiff_splitlines(bc, len(sb), b)
147 if not a[0] or not b[0]:
147 if not a[0] or not b[0]:
148 raise MemoryError
148 raise MemoryError
149 count = lib.bdiff_diff(a[0], an, b[0], bn, l)
149 count = lib.bdiff_diff(a[0], an, b[0], bn, l)
150 if count < 0:
150 if count < 0:
151 raise MemoryError
151 raise MemoryError
152 rl = []
152 rl = []
153 h = l.next
153 h = l.next
154 la = lb = 0
154 la = lb = 0
155 while h:
155 while h:
156 if h.a1 != la or h.b1 != lb:
156 if h.a1 != la or h.b1 != lb:
157 lgt = (b[0] + h.b1).l - (b[0] + lb).l
157 lgt = (b[0] + h.b1).l - (b[0] + lb).l
158 rl.append(struct.pack(">lll", (a[0] + la).l - a[0].l,
158 rl.append(struct.pack(">lll", (a[0] + la).l - a[0].l,
159 (a[0] + h.a1).l - a[0].l, lgt))
159 (a[0] + h.a1).l - a[0].l, lgt))
160 rl.append(str(ffi.buffer((b[0] + lb).l, lgt)))
160 rl.append(str(ffi.buffer((b[0] + lb).l, lgt)))
161 la = h.a2
161 la = h.a2
162 lb = h.b2
162 lb = h.b2
163 h = h.next
163 h = h.next
164
164
165 finally:
165 finally:
166 lib.free(a[0])
166 lib.free(a[0])
167 lib.free(b[0])
167 lib.free(b[0])
168 lib.bdiff_freehunks(l.next)
168 lib.bdiff_freehunks(l.next)
169 return "".join(rl)
169 return "".join(rl)
General Comments 0
You need to be logged in to leave comments. Login now