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