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