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