##// END OF EJS Templates
manifest: add manifestdict.hasdir() method...
Drew Gottlieb -
r24324:149cc171 default
parent child Browse files
Show More
@@ -1,403 +1,406 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from 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):
159 return dir in self._dirs
160
158 def matches(self, match):
161 def matches(self, match):
159 '''generate a new manifest filtered by the match argument'''
162 '''generate a new manifest filtered by the match argument'''
160 if match.always():
163 if match.always():
161 return self.copy()
164 return self.copy()
162
165
163 files = match.files()
166 files = match.files()
164 if (match.matchfn == match.exact or
167 if (match.matchfn == match.exact or
165 (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))):
166 return self.intersectfiles(files)
169 return self.intersectfiles(files)
167
170
168 lm = manifestdict('')
171 lm = manifestdict('')
169 lm._lm = self._lm.filtercopy(match)
172 lm._lm = self._lm.filtercopy(match)
170 return lm
173 return lm
171
174
172 def diff(self, m2, clean=False):
175 def diff(self, m2, clean=False):
173 '''Finds changes between the current manifest and m2.
176 '''Finds changes between the current manifest and m2.
174
177
175 Args:
178 Args:
176 m2: the manifest to which this manifest should be compared.
179 m2: the manifest to which this manifest should be compared.
177 clean: if true, include files unchanged between these manifests
180 clean: if true, include files unchanged between these manifests
178 with a None value in the returned dictionary.
181 with a None value in the returned dictionary.
179
182
180 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
181 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
182 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
183 in the current/other manifest. Where the file does not exist,
186 in the current/other manifest. Where the file does not exist,
184 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
185 string.
188 string.
186 '''
189 '''
187 return self._lm.diff(m2._lm, clean)
190 return self._lm.diff(m2._lm, clean)
188
191
189 def setflag(self, key, flag):
192 def setflag(self, key, flag):
190 self._lm[key] = self[key], flag
193 self._lm[key] = self[key], flag
191
194
192 def get(self, key, default=None):
195 def get(self, key, default=None):
193 try:
196 try:
194 return self._lm[key][0]
197 return self._lm[key][0]
195 except KeyError:
198 except KeyError:
196 return default
199 return default
197
200
198 def flags(self, key, default=''):
201 def flags(self, key, default=''):
199 try:
202 try:
200 return self._lm[key][1]
203 return self._lm[key][1]
201 except KeyError:
204 except KeyError:
202 return default
205 return default
203
206
204 def copy(self):
207 def copy(self):
205 c = manifestdict('')
208 c = manifestdict('')
206 c._lm = self._lm.copy()
209 c._lm = self._lm.copy()
207 return c
210 return c
208
211
209 def iteritems(self):
212 def iteritems(self):
210 return (x[:2] for x in self._lm.iterentries())
213 return (x[:2] for x in self._lm.iterentries())
211
214
212 def text(self):
215 def text(self):
213 return self._lm.text()
216 return self._lm.text()
214
217
215 def fastdelta(self, base, changes):
218 def fastdelta(self, base, changes):
216 """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
217 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.
218 """
221 """
219 delta = []
222 delta = []
220 dstart = None
223 dstart = None
221 dend = None
224 dend = None
222 dline = [""]
225 dline = [""]
223 start = 0
226 start = 0
224 # zero copy representation of base as a buffer
227 # zero copy representation of base as a buffer
225 addbuf = util.buffer(base)
228 addbuf = util.buffer(base)
226
229
227 # start with a readonly loop that finds the offset of
230 # start with a readonly loop that finds the offset of
228 # each line and creates the deltas
231 # each line and creates the deltas
229 for f, todelete in changes:
232 for f, todelete in changes:
230 # 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
231 start, end = _msearch(addbuf, f, start)
234 start, end = _msearch(addbuf, f, start)
232 if not todelete:
235 if not todelete:
233 h, fl = self._lm[f]
236 h, fl = self._lm[f]
234 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
237 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
235 else:
238 else:
236 if start == end:
239 if start == end:
237 # item we want to delete was not found, error out
240 # item we want to delete was not found, error out
238 raise AssertionError(
241 raise AssertionError(
239 _("failed to remove %s from manifest") % f)
242 _("failed to remove %s from manifest") % f)
240 l = ""
243 l = ""
241 if dstart is not None and dstart <= start and dend >= start:
244 if dstart is not None and dstart <= start and dend >= start:
242 if dend < end:
245 if dend < end:
243 dend = end
246 dend = end
244 if l:
247 if l:
245 dline.append(l)
248 dline.append(l)
246 else:
249 else:
247 if dstart is not None:
250 if dstart is not None:
248 delta.append([dstart, dend, "".join(dline)])
251 delta.append([dstart, dend, "".join(dline)])
249 dstart = start
252 dstart = start
250 dend = end
253 dend = end
251 dline = [l]
254 dline = [l]
252
255
253 if dstart is not None:
256 if dstart is not None:
254 delta.append([dstart, dend, "".join(dline)])
257 delta.append([dstart, dend, "".join(dline)])
255 # 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
256 deltatext, arraytext = _addlistdelta(base, delta)
259 deltatext, arraytext = _addlistdelta(base, delta)
257 return arraytext, deltatext
260 return arraytext, deltatext
258
261
259 def _msearch(m, s, lo=0, hi=None):
262 def _msearch(m, s, lo=0, hi=None):
260 '''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.
261
264
262 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
263 that string. If start == end the string was not found and
266 that string. If start == end the string was not found and
264 they indicate the proper sorted insertion point.
267 they indicate the proper sorted insertion point.
265
268
266 m should be a buffer or a string
269 m should be a buffer or a string
267 s is a string'''
270 s is a string'''
268 def advance(i, c):
271 def advance(i, c):
269 while i < lenm and m[i] != c:
272 while i < lenm and m[i] != c:
270 i += 1
273 i += 1
271 return i
274 return i
272 if not s:
275 if not s:
273 return (lo, lo)
276 return (lo, lo)
274 lenm = len(m)
277 lenm = len(m)
275 if not hi:
278 if not hi:
276 hi = lenm
279 hi = lenm
277 while lo < hi:
280 while lo < hi:
278 mid = (lo + hi) // 2
281 mid = (lo + hi) // 2
279 start = mid
282 start = mid
280 while start > 0 and m[start - 1] != '\n':
283 while start > 0 and m[start - 1] != '\n':
281 start -= 1
284 start -= 1
282 end = advance(start, '\0')
285 end = advance(start, '\0')
283 if m[start:end] < s:
286 if m[start:end] < s:
284 # 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
285 # this translates to the bisect lo = mid + 1
288 # this translates to the bisect lo = mid + 1
286 lo = advance(end + 40, '\n') + 1
289 lo = advance(end + 40, '\n') + 1
287 else:
290 else:
288 # this translates to the bisect hi = mid
291 # this translates to the bisect hi = mid
289 hi = start
292 hi = start
290 end = advance(lo, '\0')
293 end = advance(lo, '\0')
291 found = m[lo:end]
294 found = m[lo:end]
292 if s == found:
295 if s == found:
293 # 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
294 end = advance(end + 40, '\n')
297 end = advance(end + 40, '\n')
295 return (lo, end + 1)
298 return (lo, end + 1)
296 else:
299 else:
297 return (lo, lo)
300 return (lo, lo)
298
301
299 def _checkforbidden(l):
302 def _checkforbidden(l):
300 """Check filenames for illegal characters."""
303 """Check filenames for illegal characters."""
301 for f in l:
304 for f in l:
302 if '\n' in f or '\r' in f:
305 if '\n' in f or '\r' in f:
303 raise error.RevlogError(
306 raise error.RevlogError(
304 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
307 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
305
308
306
309
307 # apply the changes collected during the bisect loop to our addlist
310 # apply the changes collected during the bisect loop to our addlist
308 # return a delta suitable for addrevision
311 # return a delta suitable for addrevision
309 def _addlistdelta(addlist, x):
312 def _addlistdelta(addlist, x):
310 # for large addlist arrays, building a new array is cheaper
313 # for large addlist arrays, building a new array is cheaper
311 # than repeatedly modifying the existing one
314 # than repeatedly modifying the existing one
312 currentposition = 0
315 currentposition = 0
313 newaddlist = array.array('c')
316 newaddlist = array.array('c')
314
317
315 for start, end, content in x:
318 for start, end, content in x:
316 newaddlist += addlist[currentposition:start]
319 newaddlist += addlist[currentposition:start]
317 if content:
320 if content:
318 newaddlist += array.array('c', content)
321 newaddlist += array.array('c', content)
319
322
320 currentposition = end
323 currentposition = end
321
324
322 newaddlist += addlist[currentposition:]
325 newaddlist += addlist[currentposition:]
323
326
324 deltatext = "".join(struct.pack(">lll", start, end, len(content))
327 deltatext = "".join(struct.pack(">lll", start, end, len(content))
325 + content for start, end, content in x)
328 + content for start, end, content in x)
326 return deltatext, newaddlist
329 return deltatext, newaddlist
327
330
328 class manifest(revlog.revlog):
331 class manifest(revlog.revlog):
329 def __init__(self, opener):
332 def __init__(self, opener):
330 # During normal operations, we expect to deal with not more than four
333 # During normal operations, we expect to deal with not more than four
331 # revs at a time (such as during commit --amend). When rebasing large
334 # revs at a time (such as during commit --amend). When rebasing large
332 # stacks of commits, the number can go up, hence the config knob below.
335 # stacks of commits, the number can go up, hence the config knob below.
333 cachesize = 4
336 cachesize = 4
334 opts = getattr(opener, 'options', None)
337 opts = getattr(opener, 'options', None)
335 if opts is not None:
338 if opts is not None:
336 cachesize = opts.get('manifestcachesize', cachesize)
339 cachesize = opts.get('manifestcachesize', cachesize)
337 self._mancache = util.lrucachedict(cachesize)
340 self._mancache = util.lrucachedict(cachesize)
338 revlog.revlog.__init__(self, opener, "00manifest.i")
341 revlog.revlog.__init__(self, opener, "00manifest.i")
339
342
340 def readdelta(self, node):
343 def readdelta(self, node):
341 r = self.rev(node)
344 r = self.rev(node)
342 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
345 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
343 return manifestdict(d)
346 return manifestdict(d)
344
347
345 def readfast(self, node):
348 def readfast(self, node):
346 '''use the faster of readdelta or read'''
349 '''use the faster of readdelta or read'''
347 r = self.rev(node)
350 r = self.rev(node)
348 deltaparent = self.deltaparent(r)
351 deltaparent = self.deltaparent(r)
349 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
352 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
350 return self.readdelta(node)
353 return self.readdelta(node)
351 return self.read(node)
354 return self.read(node)
352
355
353 def read(self, node):
356 def read(self, node):
354 if node == revlog.nullid:
357 if node == revlog.nullid:
355 return manifestdict() # don't upset local cache
358 return manifestdict() # don't upset local cache
356 if node in self._mancache:
359 if node in self._mancache:
357 return self._mancache[node][0]
360 return self._mancache[node][0]
358 text = self.revision(node)
361 text = self.revision(node)
359 arraytext = array.array('c', text)
362 arraytext = array.array('c', text)
360 m = manifestdict(text)
363 m = manifestdict(text)
361 self._mancache[node] = (m, arraytext)
364 self._mancache[node] = (m, arraytext)
362 return m
365 return m
363
366
364 def find(self, node, f):
367 def find(self, node, f):
365 '''look up entry for a single file efficiently.
368 '''look up entry for a single file efficiently.
366 return (node, flags) pair if found, (None, None) if not.'''
369 return (node, flags) pair if found, (None, None) if not.'''
367 m = self.read(node)
370 m = self.read(node)
368 try:
371 try:
369 return m.find(f)
372 return m.find(f)
370 except KeyError:
373 except KeyError:
371 return None, None
374 return None, None
372
375
373 def add(self, m, transaction, link, p1, p2, added, removed):
376 def add(self, m, transaction, link, p1, p2, added, removed):
374 if p1 in self._mancache:
377 if p1 in self._mancache:
375 # If our first parent is in the manifest cache, we can
378 # If our first parent is in the manifest cache, we can
376 # compute a delta here using properties we know about the
379 # compute a delta here using properties we know about the
377 # manifest up-front, which may save time later for the
380 # manifest up-front, which may save time later for the
378 # revlog layer.
381 # revlog layer.
379
382
380 _checkforbidden(added)
383 _checkforbidden(added)
381 # combine the changed lists into one list for sorting
384 # combine the changed lists into one list for sorting
382 work = [(x, False) for x in added]
385 work = [(x, False) for x in added]
383 work.extend((x, True) for x in removed)
386 work.extend((x, True) for x in removed)
384 # this could use heapq.merge() (from Python 2.6+) or equivalent
387 # this could use heapq.merge() (from Python 2.6+) or equivalent
385 # since the lists are already sorted
388 # since the lists are already sorted
386 work.sort()
389 work.sort()
387
390
388 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
391 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
389 cachedelta = self.rev(p1), deltatext
392 cachedelta = self.rev(p1), deltatext
390 text = util.buffer(arraytext)
393 text = util.buffer(arraytext)
391 else:
394 else:
392 # The first parent manifest isn't already loaded, so we'll
395 # The first parent manifest isn't already loaded, so we'll
393 # just encode a fulltext of the manifest and pass that
396 # just encode a fulltext of the manifest and pass that
394 # through to the revlog layer, and let it handle the delta
397 # through to the revlog layer, and let it handle the delta
395 # process.
398 # process.
396 text = m.text()
399 text = m.text()
397 arraytext = array.array('c', text)
400 arraytext = array.array('c', text)
398 cachedelta = None
401 cachedelta = None
399
402
400 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
403 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
401 self._mancache[n] = (m, arraytext)
404 self._mancache[n] = (m, arraytext)
402
405
403 return n
406 return n
General Comments 0
You need to be logged in to leave comments. Login now