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