##// END OF EJS Templates
treemanifest: make treemanifest.matches() faster...
Drew Gottlieb -
r24552:a2292da6 default
parent child Browse files
Show More
@@ -1,680 +1,697
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import mdiff, parsers, error, revlog, util, scmutil
9 import mdiff, parsers, error, revlog, util, scmutil
10 import array, struct
10 import array, struct
11
11
12 propertycache = util.propertycache
12 propertycache = util.propertycache
13
13
14 def _parse(data):
14 def _parse(data):
15 """Generates (path, node, flags) tuples from a manifest text"""
15 """Generates (path, node, flags) tuples from a manifest text"""
16 # This method does a little bit of excessive-looking
16 # This method does a little bit of excessive-looking
17 # precondition checking. This is so that the behavior of this
17 # precondition checking. This is so that the behavior of this
18 # class exactly matches its C counterpart to try and help
18 # class exactly matches its C counterpart to try and help
19 # prevent surprise breakage for anyone that develops against
19 # prevent surprise breakage for anyone that develops against
20 # the pure version.
20 # the pure version.
21 if data and data[-1] != '\n':
21 if data and data[-1] != '\n':
22 raise ValueError('Manifest did not end in a newline.')
22 raise ValueError('Manifest did not end in a newline.')
23 prev = None
23 prev = None
24 for l in data.splitlines():
24 for l in data.splitlines():
25 if prev is not None and prev > l:
25 if prev is not None and prev > l:
26 raise ValueError('Manifest lines not in sorted order.')
26 raise ValueError('Manifest lines not in sorted order.')
27 prev = l
27 prev = l
28 f, n = l.split('\0')
28 f, n = l.split('\0')
29 if len(n) > 40:
29 if len(n) > 40:
30 yield f, revlog.bin(n[:40]), n[40:]
30 yield f, revlog.bin(n[:40]), n[40:]
31 else:
31 else:
32 yield f, revlog.bin(n), ''
32 yield f, revlog.bin(n), ''
33
33
34 def _text(it):
34 def _text(it):
35 """Given an iterator over (path, node, flags) tuples, returns a manifest
35 """Given an iterator over (path, node, flags) tuples, returns a manifest
36 text"""
36 text"""
37 files = []
37 files = []
38 lines = []
38 lines = []
39 _hex = revlog.hex
39 _hex = revlog.hex
40 for f, n, fl in it:
40 for f, n, fl in it:
41 files.append(f)
41 files.append(f)
42 # if this is changed to support newlines in filenames,
42 # if this is changed to support newlines in filenames,
43 # be sure to check the templates/ dir again (especially *-raw.tmpl)
43 # be sure to check the templates/ dir again (especially *-raw.tmpl)
44 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
44 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
45
45
46 _checkforbidden(files)
46 _checkforbidden(files)
47 return ''.join(lines)
47 return ''.join(lines)
48
48
49 class _lazymanifest(dict):
49 class _lazymanifest(dict):
50 """This is the pure implementation of lazymanifest.
50 """This is the pure implementation of lazymanifest.
51
51
52 It has not been optimized *at all* and is not lazy.
52 It has not been optimized *at all* and is not lazy.
53 """
53 """
54
54
55 def __init__(self, data):
55 def __init__(self, data):
56 dict.__init__(self)
56 dict.__init__(self)
57 for f, n, fl in _parse(data):
57 for f, n, fl in _parse(data):
58 self[f] = n, fl
58 self[f] = n, fl
59
59
60 def __setitem__(self, k, v):
60 def __setitem__(self, k, v):
61 node, flag = v
61 node, flag = v
62 assert node is not None
62 assert node is not None
63 if len(node) > 21:
63 if len(node) > 21:
64 node = node[:21] # match c implementation behavior
64 node = node[:21] # match c implementation behavior
65 dict.__setitem__(self, k, (node, flag))
65 dict.__setitem__(self, k, (node, flag))
66
66
67 def __iter__(self):
67 def __iter__(self):
68 return iter(sorted(dict.keys(self)))
68 return iter(sorted(dict.keys(self)))
69
69
70 def iterkeys(self):
70 def iterkeys(self):
71 return iter(sorted(dict.keys(self)))
71 return iter(sorted(dict.keys(self)))
72
72
73 def iterentries(self):
73 def iterentries(self):
74 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
74 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
75
75
76 def copy(self):
76 def copy(self):
77 c = _lazymanifest('')
77 c = _lazymanifest('')
78 c.update(self)
78 c.update(self)
79 return c
79 return c
80
80
81 def diff(self, m2, clean=False):
81 def diff(self, m2, clean=False):
82 '''Finds changes between the current manifest and m2.'''
82 '''Finds changes between the current manifest and m2.'''
83 diff = {}
83 diff = {}
84
84
85 for fn, e1 in self.iteritems():
85 for fn, e1 in self.iteritems():
86 if fn not in m2:
86 if fn not in m2:
87 diff[fn] = e1, (None, '')
87 diff[fn] = e1, (None, '')
88 else:
88 else:
89 e2 = m2[fn]
89 e2 = m2[fn]
90 if e1 != e2:
90 if e1 != e2:
91 diff[fn] = e1, e2
91 diff[fn] = e1, e2
92 elif clean:
92 elif clean:
93 diff[fn] = None
93 diff[fn] = None
94
94
95 for fn, e2 in m2.iteritems():
95 for fn, e2 in m2.iteritems():
96 if fn not in self:
96 if fn not in self:
97 diff[fn] = (None, ''), e2
97 diff[fn] = (None, ''), e2
98
98
99 return diff
99 return diff
100
100
101 def filtercopy(self, filterfn):
101 def filtercopy(self, filterfn):
102 c = _lazymanifest('')
102 c = _lazymanifest('')
103 for f, n, fl in self.iterentries():
103 for f, n, fl in self.iterentries():
104 if filterfn(f):
104 if filterfn(f):
105 c[f] = n, fl
105 c[f] = n, fl
106 return c
106 return c
107
107
108 def text(self):
108 def text(self):
109 """Get the full data of this manifest as a bytestring."""
109 """Get the full data of this manifest as a bytestring."""
110 return _text(self.iterentries())
110 return _text(self.iterentries())
111
111
112 try:
112 try:
113 _lazymanifest = parsers.lazymanifest
113 _lazymanifest = parsers.lazymanifest
114 except AttributeError:
114 except AttributeError:
115 pass
115 pass
116
116
117 class manifestdict(object):
117 class manifestdict(object):
118 def __init__(self, data=''):
118 def __init__(self, data=''):
119 self._lm = _lazymanifest(data)
119 self._lm = _lazymanifest(data)
120
120
121 def __getitem__(self, key):
121 def __getitem__(self, key):
122 return self._lm[key][0]
122 return self._lm[key][0]
123
123
124 def find(self, key):
124 def find(self, key):
125 return self._lm[key]
125 return self._lm[key]
126
126
127 def __len__(self):
127 def __len__(self):
128 return len(self._lm)
128 return len(self._lm)
129
129
130 def __setitem__(self, key, node):
130 def __setitem__(self, key, node):
131 self._lm[key] = node, self.flags(key, '')
131 self._lm[key] = node, self.flags(key, '')
132
132
133 def __contains__(self, key):
133 def __contains__(self, key):
134 return key in self._lm
134 return key in self._lm
135
135
136 def __delitem__(self, key):
136 def __delitem__(self, key):
137 del self._lm[key]
137 del self._lm[key]
138
138
139 def __iter__(self):
139 def __iter__(self):
140 return self._lm.__iter__()
140 return self._lm.__iter__()
141
141
142 def iterkeys(self):
142 def iterkeys(self):
143 return self._lm.iterkeys()
143 return self._lm.iterkeys()
144
144
145 def keys(self):
145 def keys(self):
146 return list(self.iterkeys())
146 return list(self.iterkeys())
147
147
148 def _intersectfiles(self, files):
148 def _intersectfiles(self, files):
149 '''make a new lazymanifest with the intersection of self with files
149 '''make a new lazymanifest with the intersection of self with files
150
150
151 The algorithm assumes that files is much smaller than self.'''
151 The algorithm assumes that files is much smaller than self.'''
152 ret = manifestdict()
152 ret = manifestdict()
153 lm = self._lm
153 lm = self._lm
154 for fn in files:
154 for fn in files:
155 if fn in lm:
155 if fn in lm:
156 ret._lm[fn] = self._lm[fn]
156 ret._lm[fn] = self._lm[fn]
157 return ret
157 return ret
158
158
159 def filesnotin(self, m2):
159 def filesnotin(self, m2):
160 '''Set of files in this manifest that are not in the other'''
160 '''Set of files in this manifest that are not in the other'''
161 files = set(self)
161 files = set(self)
162 files.difference_update(m2)
162 files.difference_update(m2)
163 return files
163 return files
164
164
165 @propertycache
165 @propertycache
166 def _dirs(self):
166 def _dirs(self):
167 return scmutil.dirs(self)
167 return scmutil.dirs(self)
168
168
169 def dirs(self):
169 def dirs(self):
170 return self._dirs
170 return self._dirs
171
171
172 def hasdir(self, dir):
172 def hasdir(self, dir):
173 return dir in self._dirs
173 return dir in self._dirs
174
174
175 def matches(self, match):
175 def matches(self, match):
176 '''generate a new manifest filtered by the match argument'''
176 '''generate a new manifest filtered by the match argument'''
177 if match.always():
177 if match.always():
178 return self.copy()
178 return self.copy()
179
179
180 files = match.files()
180 files = match.files()
181 if (len(files) < 100 and (match.isexact() or
181 if (len(files) < 100 and (match.isexact() or
182 (not match.anypats() and util.all(fn in self for fn in files)))):
182 (not match.anypats() and util.all(fn in self for fn in files)))):
183 return self._intersectfiles(files)
183 return self._intersectfiles(files)
184
184
185 lm = manifestdict('')
185 lm = manifestdict('')
186 lm._lm = self._lm.filtercopy(match)
186 lm._lm = self._lm.filtercopy(match)
187 return lm
187 return lm
188
188
189 def diff(self, m2, clean=False):
189 def diff(self, m2, clean=False):
190 '''Finds changes between the current manifest and m2.
190 '''Finds changes between the current manifest and m2.
191
191
192 Args:
192 Args:
193 m2: the manifest to which this manifest should be compared.
193 m2: the manifest to which this manifest should be compared.
194 clean: if true, include files unchanged between these manifests
194 clean: if true, include files unchanged between these manifests
195 with a None value in the returned dictionary.
195 with a None value in the returned dictionary.
196
196
197 The result is returned as a dict with filename as key and
197 The result is returned as a dict with filename as key and
198 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
198 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
199 nodeid in the current/other manifest and fl1/fl2 is the flag
199 nodeid in the current/other manifest and fl1/fl2 is the flag
200 in the current/other manifest. Where the file does not exist,
200 in the current/other manifest. Where the file does not exist,
201 the nodeid will be None and the flags will be the empty
201 the nodeid will be None and the flags will be the empty
202 string.
202 string.
203 '''
203 '''
204 return self._lm.diff(m2._lm, clean)
204 return self._lm.diff(m2._lm, clean)
205
205
206 def setflag(self, key, flag):
206 def setflag(self, key, flag):
207 self._lm[key] = self[key], flag
207 self._lm[key] = self[key], flag
208
208
209 def get(self, key, default=None):
209 def get(self, key, default=None):
210 try:
210 try:
211 return self._lm[key][0]
211 return self._lm[key][0]
212 except KeyError:
212 except KeyError:
213 return default
213 return default
214
214
215 def flags(self, key, default=''):
215 def flags(self, key, default=''):
216 try:
216 try:
217 return self._lm[key][1]
217 return self._lm[key][1]
218 except KeyError:
218 except KeyError:
219 return default
219 return default
220
220
221 def copy(self):
221 def copy(self):
222 c = manifestdict('')
222 c = manifestdict('')
223 c._lm = self._lm.copy()
223 c._lm = self._lm.copy()
224 return c
224 return c
225
225
226 def iteritems(self):
226 def iteritems(self):
227 return (x[:2] for x in self._lm.iterentries())
227 return (x[:2] for x in self._lm.iterentries())
228
228
229 def text(self):
229 def text(self):
230 return self._lm.text()
230 return self._lm.text()
231
231
232 def fastdelta(self, base, changes):
232 def fastdelta(self, base, changes):
233 """Given a base manifest text as an array.array and a list of changes
233 """Given a base manifest text as an array.array and a list of changes
234 relative to that text, compute a delta that can be used by revlog.
234 relative to that text, compute a delta that can be used by revlog.
235 """
235 """
236 delta = []
236 delta = []
237 dstart = None
237 dstart = None
238 dend = None
238 dend = None
239 dline = [""]
239 dline = [""]
240 start = 0
240 start = 0
241 # zero copy representation of base as a buffer
241 # zero copy representation of base as a buffer
242 addbuf = util.buffer(base)
242 addbuf = util.buffer(base)
243
243
244 # start with a readonly loop that finds the offset of
244 # start with a readonly loop that finds the offset of
245 # each line and creates the deltas
245 # each line and creates the deltas
246 for f, todelete in changes:
246 for f, todelete in changes:
247 # bs will either be the index of the item or the insert point
247 # bs will either be the index of the item or the insert point
248 start, end = _msearch(addbuf, f, start)
248 start, end = _msearch(addbuf, f, start)
249 if not todelete:
249 if not todelete:
250 h, fl = self._lm[f]
250 h, fl = self._lm[f]
251 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
251 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
252 else:
252 else:
253 if start == end:
253 if start == end:
254 # item we want to delete was not found, error out
254 # item we want to delete was not found, error out
255 raise AssertionError(
255 raise AssertionError(
256 _("failed to remove %s from manifest") % f)
256 _("failed to remove %s from manifest") % f)
257 l = ""
257 l = ""
258 if dstart is not None and dstart <= start and dend >= start:
258 if dstart is not None and dstart <= start and dend >= start:
259 if dend < end:
259 if dend < end:
260 dend = end
260 dend = end
261 if l:
261 if l:
262 dline.append(l)
262 dline.append(l)
263 else:
263 else:
264 if dstart is not None:
264 if dstart is not None:
265 delta.append([dstart, dend, "".join(dline)])
265 delta.append([dstart, dend, "".join(dline)])
266 dstart = start
266 dstart = start
267 dend = end
267 dend = end
268 dline = [l]
268 dline = [l]
269
269
270 if dstart is not None:
270 if dstart is not None:
271 delta.append([dstart, dend, "".join(dline)])
271 delta.append([dstart, dend, "".join(dline)])
272 # apply the delta to the base, and get a delta for addrevision
272 # apply the delta to the base, and get a delta for addrevision
273 deltatext, arraytext = _addlistdelta(base, delta)
273 deltatext, arraytext = _addlistdelta(base, delta)
274 return arraytext, deltatext
274 return arraytext, deltatext
275
275
276 def _msearch(m, s, lo=0, hi=None):
276 def _msearch(m, s, lo=0, hi=None):
277 '''return a tuple (start, end) that says where to find s within m.
277 '''return a tuple (start, end) that says where to find s within m.
278
278
279 If the string is found m[start:end] are the line containing
279 If the string is found m[start:end] are the line containing
280 that string. If start == end the string was not found and
280 that string. If start == end the string was not found and
281 they indicate the proper sorted insertion point.
281 they indicate the proper sorted insertion point.
282
282
283 m should be a buffer or a string
283 m should be a buffer or a string
284 s is a string'''
284 s is a string'''
285 def advance(i, c):
285 def advance(i, c):
286 while i < lenm and m[i] != c:
286 while i < lenm and m[i] != c:
287 i += 1
287 i += 1
288 return i
288 return i
289 if not s:
289 if not s:
290 return (lo, lo)
290 return (lo, lo)
291 lenm = len(m)
291 lenm = len(m)
292 if not hi:
292 if not hi:
293 hi = lenm
293 hi = lenm
294 while lo < hi:
294 while lo < hi:
295 mid = (lo + hi) // 2
295 mid = (lo + hi) // 2
296 start = mid
296 start = mid
297 while start > 0 and m[start - 1] != '\n':
297 while start > 0 and m[start - 1] != '\n':
298 start -= 1
298 start -= 1
299 end = advance(start, '\0')
299 end = advance(start, '\0')
300 if m[start:end] < s:
300 if m[start:end] < s:
301 # we know that after the null there are 40 bytes of sha1
301 # we know that after the null there are 40 bytes of sha1
302 # this translates to the bisect lo = mid + 1
302 # this translates to the bisect lo = mid + 1
303 lo = advance(end + 40, '\n') + 1
303 lo = advance(end + 40, '\n') + 1
304 else:
304 else:
305 # this translates to the bisect hi = mid
305 # this translates to the bisect hi = mid
306 hi = start
306 hi = start
307 end = advance(lo, '\0')
307 end = advance(lo, '\0')
308 found = m[lo:end]
308 found = m[lo:end]
309 if s == found:
309 if s == found:
310 # we know that after the null there are 40 bytes of sha1
310 # we know that after the null there are 40 bytes of sha1
311 end = advance(end + 40, '\n')
311 end = advance(end + 40, '\n')
312 return (lo, end + 1)
312 return (lo, end + 1)
313 else:
313 else:
314 return (lo, lo)
314 return (lo, lo)
315
315
316 def _checkforbidden(l):
316 def _checkforbidden(l):
317 """Check filenames for illegal characters."""
317 """Check filenames for illegal characters."""
318 for f in l:
318 for f in l:
319 if '\n' in f or '\r' in f:
319 if '\n' in f or '\r' in f:
320 raise error.RevlogError(
320 raise error.RevlogError(
321 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
321 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
322
322
323
323
324 # apply the changes collected during the bisect loop to our addlist
324 # apply the changes collected during the bisect loop to our addlist
325 # return a delta suitable for addrevision
325 # return a delta suitable for addrevision
326 def _addlistdelta(addlist, x):
326 def _addlistdelta(addlist, x):
327 # for large addlist arrays, building a new array is cheaper
327 # for large addlist arrays, building a new array is cheaper
328 # than repeatedly modifying the existing one
328 # than repeatedly modifying the existing one
329 currentposition = 0
329 currentposition = 0
330 newaddlist = array.array('c')
330 newaddlist = array.array('c')
331
331
332 for start, end, content in x:
332 for start, end, content in x:
333 newaddlist += addlist[currentposition:start]
333 newaddlist += addlist[currentposition:start]
334 if content:
334 if content:
335 newaddlist += array.array('c', content)
335 newaddlist += array.array('c', content)
336
336
337 currentposition = end
337 currentposition = end
338
338
339 newaddlist += addlist[currentposition:]
339 newaddlist += addlist[currentposition:]
340
340
341 deltatext = "".join(struct.pack(">lll", start, end, len(content))
341 deltatext = "".join(struct.pack(">lll", start, end, len(content))
342 + content for start, end, content in x)
342 + content for start, end, content in x)
343 return deltatext, newaddlist
343 return deltatext, newaddlist
344
344
345 def _splittopdir(f):
345 def _splittopdir(f):
346 if '/' in f:
346 if '/' in f:
347 dir, subpath = f.split('/', 1)
347 dir, subpath = f.split('/', 1)
348 return dir + '/', subpath
348 return dir + '/', subpath
349 else:
349 else:
350 return '', f
350 return '', f
351
351
352 class treemanifest(object):
352 class treemanifest(object):
353 def __init__(self, dir='', text=''):
353 def __init__(self, dir='', text=''):
354 self._dir = dir
354 self._dir = dir
355 self._dirs = {}
355 self._dirs = {}
356 # Using _lazymanifest here is a little slower than plain old dicts
356 # Using _lazymanifest here is a little slower than plain old dicts
357 self._files = {}
357 self._files = {}
358 self._flags = {}
358 self._flags = {}
359 for f, n, fl in _parse(text):
359 for f, n, fl in _parse(text):
360 self[f] = n
360 self[f] = n
361 if fl:
361 if fl:
362 self.setflag(f, fl)
362 self.setflag(f, fl)
363
363
364 def _subpath(self, path):
364 def _subpath(self, path):
365 return self._dir + path
365 return self._dir + path
366
366
367 def __len__(self):
367 def __len__(self):
368 size = len(self._files)
368 size = len(self._files)
369 for m in self._dirs.values():
369 for m in self._dirs.values():
370 size += m.__len__()
370 size += m.__len__()
371 return size
371 return size
372
372
373 def _isempty(self):
373 def _isempty(self):
374 return (not self._files and (not self._dirs or
374 return (not self._files and (not self._dirs or
375 util.all(m._isempty() for m in self._dirs.values())))
375 util.all(m._isempty() for m in self._dirs.values())))
376
376
377 def __str__(self):
377 def __str__(self):
378 return '<treemanifest dir=%s>' % self._dir
378 return '<treemanifest dir=%s>' % self._dir
379
379
380 def iteritems(self):
380 def iteritems(self):
381 for p, n in sorted(self._dirs.items() + self._files.items()):
381 for p, n in sorted(self._dirs.items() + self._files.items()):
382 if p in self._files:
382 if p in self._files:
383 yield self._subpath(p), n
383 yield self._subpath(p), n
384 else:
384 else:
385 for f, sn in n.iteritems():
385 for f, sn in n.iteritems():
386 yield f, sn
386 yield f, sn
387
387
388 def iterkeys(self):
388 def iterkeys(self):
389 for p in sorted(self._dirs.keys() + self._files.keys()):
389 for p in sorted(self._dirs.keys() + self._files.keys()):
390 if p in self._files:
390 if p in self._files:
391 yield self._subpath(p)
391 yield self._subpath(p)
392 else:
392 else:
393 for f in self._dirs[p].iterkeys():
393 for f in self._dirs[p].iterkeys():
394 yield f
394 yield f
395
395
396 def keys(self):
396 def keys(self):
397 return list(self.iterkeys())
397 return list(self.iterkeys())
398
398
399 def __iter__(self):
399 def __iter__(self):
400 return self.iterkeys()
400 return self.iterkeys()
401
401
402 def __contains__(self, f):
402 def __contains__(self, f):
403 if f is None:
403 if f is None:
404 return False
404 return False
405 dir, subpath = _splittopdir(f)
405 dir, subpath = _splittopdir(f)
406 if dir:
406 if dir:
407 if dir not in self._dirs:
407 if dir not in self._dirs:
408 return False
408 return False
409 return self._dirs[dir].__contains__(subpath)
409 return self._dirs[dir].__contains__(subpath)
410 else:
410 else:
411 return f in self._files
411 return f in self._files
412
412
413 def get(self, f, default=None):
413 def get(self, f, default=None):
414 dir, subpath = _splittopdir(f)
414 dir, subpath = _splittopdir(f)
415 if dir:
415 if dir:
416 if dir not in self._dirs:
416 if dir not in self._dirs:
417 return default
417 return default
418 return self._dirs[dir].get(subpath, default)
418 return self._dirs[dir].get(subpath, default)
419 else:
419 else:
420 return self._files.get(f, default)
420 return self._files.get(f, default)
421
421
422 def __getitem__(self, f):
422 def __getitem__(self, f):
423 dir, subpath = _splittopdir(f)
423 dir, subpath = _splittopdir(f)
424 if dir:
424 if dir:
425 return self._dirs[dir].__getitem__(subpath)
425 return self._dirs[dir].__getitem__(subpath)
426 else:
426 else:
427 return self._files[f]
427 return self._files[f]
428
428
429 def flags(self, f):
429 def flags(self, f):
430 dir, subpath = _splittopdir(f)
430 dir, subpath = _splittopdir(f)
431 if dir:
431 if dir:
432 if dir not in self._dirs:
432 if dir not in self._dirs:
433 return ''
433 return ''
434 return self._dirs[dir].flags(subpath)
434 return self._dirs[dir].flags(subpath)
435 else:
435 else:
436 if f in self._dirs:
436 if f in self._dirs:
437 return ''
437 return ''
438 return self._flags.get(f, '')
438 return self._flags.get(f, '')
439
439
440 def find(self, f):
440 def find(self, f):
441 dir, subpath = _splittopdir(f)
441 dir, subpath = _splittopdir(f)
442 if dir:
442 if dir:
443 return self._dirs[dir].find(subpath)
443 return self._dirs[dir].find(subpath)
444 else:
444 else:
445 return self._files[f], self._flags.get(f, '')
445 return self._files[f], self._flags.get(f, '')
446
446
447 def __delitem__(self, f):
447 def __delitem__(self, f):
448 dir, subpath = _splittopdir(f)
448 dir, subpath = _splittopdir(f)
449 if dir:
449 if dir:
450 self._dirs[dir].__delitem__(subpath)
450 self._dirs[dir].__delitem__(subpath)
451 # If the directory is now empty, remove it
451 # If the directory is now empty, remove it
452 if self._dirs[dir]._isempty():
452 if self._dirs[dir]._isempty():
453 del self._dirs[dir]
453 del self._dirs[dir]
454 else:
454 else:
455 del self._files[f]
455 del self._files[f]
456 if f in self._flags:
456 if f in self._flags:
457 del self._flags[f]
457 del self._flags[f]
458
458
459 def __setitem__(self, f, n):
459 def __setitem__(self, f, n):
460 assert n is not None
460 assert n is not None
461 dir, subpath = _splittopdir(f)
461 dir, subpath = _splittopdir(f)
462 if dir:
462 if dir:
463 if dir not in self._dirs:
463 if dir not in self._dirs:
464 self._dirs[dir] = treemanifest(self._subpath(dir))
464 self._dirs[dir] = treemanifest(self._subpath(dir))
465 self._dirs[dir].__setitem__(subpath, n)
465 self._dirs[dir].__setitem__(subpath, n)
466 else:
466 else:
467 self._files[f] = n[:21] # to match manifestdict's behavior
467 self._files[f] = n[:21] # to match manifestdict's behavior
468
468
469 def setflag(self, f, flags):
469 def setflag(self, f, flags):
470 """Set the flags (symlink, executable) for path f."""
470 """Set the flags (symlink, executable) for path f."""
471 dir, subpath = _splittopdir(f)
471 dir, subpath = _splittopdir(f)
472 if dir:
472 if dir:
473 if dir not in self._dirs:
473 if dir not in self._dirs:
474 self._dirs[dir] = treemanifest(self._subpath(dir))
474 self._dirs[dir] = treemanifest(self._subpath(dir))
475 self._dirs[dir].setflag(subpath, flags)
475 self._dirs[dir].setflag(subpath, flags)
476 else:
476 else:
477 self._flags[f] = flags
477 self._flags[f] = flags
478
478
479 def copy(self):
479 def copy(self):
480 copy = treemanifest(self._dir)
480 copy = treemanifest(self._dir)
481 for d in self._dirs:
481 for d in self._dirs:
482 copy._dirs[d] = self._dirs[d].copy()
482 copy._dirs[d] = self._dirs[d].copy()
483 copy._files = dict.copy(self._files)
483 copy._files = dict.copy(self._files)
484 copy._flags = dict.copy(self._flags)
484 copy._flags = dict.copy(self._flags)
485 return copy
485 return copy
486
486
487 def filesnotin(self, m2):
487 def filesnotin(self, m2):
488 '''Set of files in this manifest that are not in the other'''
488 '''Set of files in this manifest that are not in the other'''
489 files = set()
489 files = set()
490 def _filesnotin(t1, t2):
490 def _filesnotin(t1, t2):
491 for d, m1 in t1._dirs.iteritems():
491 for d, m1 in t1._dirs.iteritems():
492 if d in t2._dirs:
492 if d in t2._dirs:
493 m2 = t2._dirs[d]
493 m2 = t2._dirs[d]
494 _filesnotin(m1, m2)
494 _filesnotin(m1, m2)
495 else:
495 else:
496 files.update(m1.iterkeys())
496 files.update(m1.iterkeys())
497
497
498 for fn in t1._files.iterkeys():
498 for fn in t1._files.iterkeys():
499 if fn not in t2._files:
499 if fn not in t2._files:
500 files.add(t1._subpath(fn))
500 files.add(t1._subpath(fn))
501
501
502 _filesnotin(self, m2)
502 _filesnotin(self, m2)
503 return files
503 return files
504
504
505 @propertycache
505 @propertycache
506 def _alldirs(self):
506 def _alldirs(self):
507 return scmutil.dirs(self)
507 return scmutil.dirs(self)
508
508
509 def dirs(self):
509 def dirs(self):
510 return self._alldirs
510 return self._alldirs
511
511
512 def hasdir(self, dir):
512 def hasdir(self, dir):
513 topdir, subdir = _splittopdir(dir)
513 topdir, subdir = _splittopdir(dir)
514 if topdir:
514 if topdir:
515 if topdir in self._dirs:
515 if topdir in self._dirs:
516 return self._dirs[topdir].hasdir(subdir)
516 return self._dirs[topdir].hasdir(subdir)
517 return False
517 return False
518 return (dir + '/') in self._dirs
518 return (dir + '/') in self._dirs
519
519
520 def matches(self, match):
520 def matches(self, match):
521 '''generate a new manifest filtered by the match argument'''
521 '''generate a new manifest filtered by the match argument'''
522 if match.always():
522 if match.always():
523 return self.copy()
523 return self.copy()
524
524
525 m = self.copy()
525 return self._matches(match)
526 for fn in m.keys():
526
527 if not match(fn):
527 def _matches(self, match):
528 del m[fn]
528 '''recursively generate a new manifest filtered by the match argument.
529 return m
529 '''
530
531 ret = treemanifest(self._dir)
532
533 for fn in self._files:
534 fullp = self._subpath(fn)
535 if not match(fullp):
536 continue
537 ret._files[fn] = self._files[fn]
538 if fn in self._flags:
539 ret._flags[fn] = self._flags[fn]
540
541 for dir, subm in self._dirs.iteritems():
542 m = subm._matches(match)
543 if not m._isempty():
544 ret._dirs[dir] = m
545
546 return ret
530
547
531 def diff(self, m2, clean=False):
548 def diff(self, m2, clean=False):
532 '''Finds changes between the current manifest and m2.
549 '''Finds changes between the current manifest and m2.
533
550
534 Args:
551 Args:
535 m2: the manifest to which this manifest should be compared.
552 m2: the manifest to which this manifest should be compared.
536 clean: if true, include files unchanged between these manifests
553 clean: if true, include files unchanged between these manifests
537 with a None value in the returned dictionary.
554 with a None value in the returned dictionary.
538
555
539 The result is returned as a dict with filename as key and
556 The result is returned as a dict with filename as key and
540 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
557 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
541 nodeid in the current/other manifest and fl1/fl2 is the flag
558 nodeid in the current/other manifest and fl1/fl2 is the flag
542 in the current/other manifest. Where the file does not exist,
559 in the current/other manifest. Where the file does not exist,
543 the nodeid will be None and the flags will be the empty
560 the nodeid will be None and the flags will be the empty
544 string.
561 string.
545 '''
562 '''
546 result = {}
563 result = {}
547 emptytree = treemanifest()
564 emptytree = treemanifest()
548 def _diff(t1, t2):
565 def _diff(t1, t2):
549 for d, m1 in t1._dirs.iteritems():
566 for d, m1 in t1._dirs.iteritems():
550 m2 = t2._dirs.get(d, emptytree)
567 m2 = t2._dirs.get(d, emptytree)
551 _diff(m1, m2)
568 _diff(m1, m2)
552
569
553 for d, m2 in t2._dirs.iteritems():
570 for d, m2 in t2._dirs.iteritems():
554 if d not in t1._dirs:
571 if d not in t1._dirs:
555 _diff(emptytree, m2)
572 _diff(emptytree, m2)
556
573
557 for fn, n1 in t1._files.iteritems():
574 for fn, n1 in t1._files.iteritems():
558 fl1 = t1._flags.get(fn, '')
575 fl1 = t1._flags.get(fn, '')
559 n2 = t2._files.get(fn, None)
576 n2 = t2._files.get(fn, None)
560 fl2 = t2._flags.get(fn, '')
577 fl2 = t2._flags.get(fn, '')
561 if n1 != n2 or fl1 != fl2:
578 if n1 != n2 or fl1 != fl2:
562 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
579 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
563 elif clean:
580 elif clean:
564 result[t1._subpath(fn)] = None
581 result[t1._subpath(fn)] = None
565
582
566 for fn, n2 in t2._files.iteritems():
583 for fn, n2 in t2._files.iteritems():
567 if fn not in t1._files:
584 if fn not in t1._files:
568 fl2 = t2._flags.get(fn, '')
585 fl2 = t2._flags.get(fn, '')
569 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
586 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
570
587
571 _diff(self, m2)
588 _diff(self, m2)
572 return result
589 return result
573
590
574 def text(self):
591 def text(self):
575 """Get the full data of this manifest as a bytestring."""
592 """Get the full data of this manifest as a bytestring."""
576 flags = self.flags
593 flags = self.flags
577 return _text((f, self[f], flags(f)) for f in self.keys())
594 return _text((f, self[f], flags(f)) for f in self.keys())
578
595
579 class manifest(revlog.revlog):
596 class manifest(revlog.revlog):
580 def __init__(self, opener):
597 def __init__(self, opener):
581 # During normal operations, we expect to deal with not more than four
598 # During normal operations, we expect to deal with not more than four
582 # revs at a time (such as during commit --amend). When rebasing large
599 # revs at a time (such as during commit --amend). When rebasing large
583 # stacks of commits, the number can go up, hence the config knob below.
600 # stacks of commits, the number can go up, hence the config knob below.
584 cachesize = 4
601 cachesize = 4
585 usetreemanifest = False
602 usetreemanifest = False
586 usemanifestv2 = False
603 usemanifestv2 = False
587 opts = getattr(opener, 'options', None)
604 opts = getattr(opener, 'options', None)
588 if opts is not None:
605 if opts is not None:
589 cachesize = opts.get('manifestcachesize', cachesize)
606 cachesize = opts.get('manifestcachesize', cachesize)
590 usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
607 usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
591 usemanifestv2 = opts.get('usemanifestv2', usemanifestv2)
608 usemanifestv2 = opts.get('usemanifestv2', usemanifestv2)
592 self._mancache = util.lrucachedict(cachesize)
609 self._mancache = util.lrucachedict(cachesize)
593 revlog.revlog.__init__(self, opener, "00manifest.i")
610 revlog.revlog.__init__(self, opener, "00manifest.i")
594 self._usetreemanifest = usetreemanifest
611 self._usetreemanifest = usetreemanifest
595 self._usemanifestv2 = usemanifestv2
612 self._usemanifestv2 = usemanifestv2
596
613
597 def _newmanifest(self, data=''):
614 def _newmanifest(self, data=''):
598 if self._usetreemanifest:
615 if self._usetreemanifest:
599 return treemanifest('', data)
616 return treemanifest('', data)
600 return manifestdict(data)
617 return manifestdict(data)
601
618
602 def _slowreaddelta(self, node):
619 def _slowreaddelta(self, node):
603 r0 = self.deltaparent(self.rev(node))
620 r0 = self.deltaparent(self.rev(node))
604 m0 = self.read(self.node(r0))
621 m0 = self.read(self.node(r0))
605 m1 = self.read(node)
622 m1 = self.read(node)
606 md = self._newmanifest()
623 md = self._newmanifest()
607 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
624 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
608 if n1:
625 if n1:
609 md[f] = n1
626 md[f] = n1
610 if fl1:
627 if fl1:
611 md.setflag(f, fl1)
628 md.setflag(f, fl1)
612 return md
629 return md
613
630
614 def readdelta(self, node):
631 def readdelta(self, node):
615 if self._usemanifestv2:
632 if self._usemanifestv2:
616 return self._slowreaddelta(node)
633 return self._slowreaddelta(node)
617 r = self.rev(node)
634 r = self.rev(node)
618 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
635 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
619 return self._newmanifest(d)
636 return self._newmanifest(d)
620
637
621 def readfast(self, node):
638 def readfast(self, node):
622 '''use the faster of readdelta or read'''
639 '''use the faster of readdelta or read'''
623 r = self.rev(node)
640 r = self.rev(node)
624 deltaparent = self.deltaparent(r)
641 deltaparent = self.deltaparent(r)
625 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
642 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
626 return self.readdelta(node)
643 return self.readdelta(node)
627 return self.read(node)
644 return self.read(node)
628
645
629 def read(self, node):
646 def read(self, node):
630 if node == revlog.nullid:
647 if node == revlog.nullid:
631 return self._newmanifest() # don't upset local cache
648 return self._newmanifest() # don't upset local cache
632 if node in self._mancache:
649 if node in self._mancache:
633 return self._mancache[node][0]
650 return self._mancache[node][0]
634 text = self.revision(node)
651 text = self.revision(node)
635 arraytext = array.array('c', text)
652 arraytext = array.array('c', text)
636 m = self._newmanifest(text)
653 m = self._newmanifest(text)
637 self._mancache[node] = (m, arraytext)
654 self._mancache[node] = (m, arraytext)
638 return m
655 return m
639
656
640 def find(self, node, f):
657 def find(self, node, f):
641 '''look up entry for a single file efficiently.
658 '''look up entry for a single file efficiently.
642 return (node, flags) pair if found, (None, None) if not.'''
659 return (node, flags) pair if found, (None, None) if not.'''
643 m = self.read(node)
660 m = self.read(node)
644 try:
661 try:
645 return m.find(f)
662 return m.find(f)
646 except KeyError:
663 except KeyError:
647 return None, None
664 return None, None
648
665
649 def add(self, m, transaction, link, p1, p2, added, removed):
666 def add(self, m, transaction, link, p1, p2, added, removed):
650 if (p1 in self._mancache and not self._usetreemanifest
667 if (p1 in self._mancache and not self._usetreemanifest
651 and not self._usemanifestv2):
668 and not self._usemanifestv2):
652 # If our first parent is in the manifest cache, we can
669 # If our first parent is in the manifest cache, we can
653 # compute a delta here using properties we know about the
670 # compute a delta here using properties we know about the
654 # manifest up-front, which may save time later for the
671 # manifest up-front, which may save time later for the
655 # revlog layer.
672 # revlog layer.
656
673
657 _checkforbidden(added)
674 _checkforbidden(added)
658 # combine the changed lists into one list for sorting
675 # combine the changed lists into one list for sorting
659 work = [(x, False) for x in added]
676 work = [(x, False) for x in added]
660 work.extend((x, True) for x in removed)
677 work.extend((x, True) for x in removed)
661 # this could use heapq.merge() (from Python 2.6+) or equivalent
678 # this could use heapq.merge() (from Python 2.6+) or equivalent
662 # since the lists are already sorted
679 # since the lists are already sorted
663 work.sort()
680 work.sort()
664
681
665 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
682 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
666 cachedelta = self.rev(p1), deltatext
683 cachedelta = self.rev(p1), deltatext
667 text = util.buffer(arraytext)
684 text = util.buffer(arraytext)
668 else:
685 else:
669 # The first parent manifest isn't already loaded, so we'll
686 # The first parent manifest isn't already loaded, so we'll
670 # just encode a fulltext of the manifest and pass that
687 # just encode a fulltext of the manifest and pass that
671 # through to the revlog layer, and let it handle the delta
688 # through to the revlog layer, and let it handle the delta
672 # process.
689 # process.
673 text = m.text()
690 text = m.text()
674 arraytext = array.array('c', text)
691 arraytext = array.array('c', text)
675 cachedelta = None
692 cachedelta = None
676
693
677 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
694 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
678 self._mancache[n] = (m, arraytext)
695 self._mancache[n] = (m, arraytext)
679
696
680 return n
697 return n
General Comments 0
You need to be logged in to leave comments. Login now