##// 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 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import mdiff, parsers, error, revlog, util, scmutil
10 10 import array, struct
11 11
12 12 propertycache = util.propertycache
13 13
14 14 class _lazymanifest(dict):
15 15 """This is the pure implementation of lazymanifest.
16 16
17 17 It has not been optimized *at all* and is not lazy.
18 18 """
19 19
20 20 def __init__(self, data):
21 21 # This init method does a little bit of excessive-looking
22 22 # precondition checking. This is so that the behavior of this
23 23 # class exactly matches its C counterpart to try and help
24 24 # prevent surprise breakage for anyone that develops against
25 25 # the pure version.
26 26 if data and data[-1] != '\n':
27 27 raise ValueError('Manifest did not end in a newline.')
28 28 dict.__init__(self)
29 29 prev = None
30 30 for l in data.splitlines():
31 31 if prev is not None and prev > l:
32 32 raise ValueError('Manifest lines not in sorted order.')
33 33 prev = l
34 34 f, n = l.split('\0')
35 35 if len(n) > 40:
36 36 self[f] = revlog.bin(n[:40]), n[40:]
37 37 else:
38 38 self[f] = revlog.bin(n), ''
39 39
40 40 def __setitem__(self, k, v):
41 41 node, flag = v
42 42 assert node is not None
43 43 if len(node) > 21:
44 44 node = node[:21] # match c implementation behavior
45 45 dict.__setitem__(self, k, (node, flag))
46 46
47 47 def __iter__(self):
48 48 return iter(sorted(dict.keys(self)))
49 49
50 50 def iterkeys(self):
51 51 return iter(sorted(dict.keys(self)))
52 52
53 53 def iterentries(self):
54 54 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
55 55
56 56 def copy(self):
57 57 c = _lazymanifest('')
58 58 c.update(self)
59 59 return c
60 60
61 61 def diff(self, m2, clean=False):
62 62 '''Finds changes between the current manifest and m2.'''
63 63 diff = {}
64 64
65 65 for fn, e1 in self.iteritems():
66 66 if fn not in m2:
67 67 diff[fn] = e1, (None, '')
68 68 else:
69 69 e2 = m2[fn]
70 70 if e1 != e2:
71 71 diff[fn] = e1, e2
72 72 elif clean:
73 73 diff[fn] = None
74 74
75 75 for fn, e2 in m2.iteritems():
76 76 if fn not in self:
77 77 diff[fn] = (None, ''), e2
78 78
79 79 return diff
80 80
81 81 def filtercopy(self, filterfn):
82 82 c = _lazymanifest('')
83 83 for f, n, fl in self.iterentries():
84 84 if filterfn(f):
85 85 c[f] = n, fl
86 86 return c
87 87
88 88 def text(self):
89 89 """Get the full data of this manifest as a bytestring."""
90 90 fl = sorted(self.iterentries())
91 91
92 92 _hex = revlog.hex
93 93 # if this is changed to support newlines in filenames,
94 94 # be sure to check the templates/ dir again (especially *-raw.tmpl)
95 95 return ''.join("%s\0%s%s\n" % (
96 96 f, _hex(n[:20]), flag) for f, n, flag in fl)
97 97
98 98 try:
99 99 _lazymanifest = parsers.lazymanifest
100 100 except AttributeError:
101 101 pass
102 102
103 103 class manifestdict(object):
104 104 def __init__(self, data=''):
105 105 self._lm = _lazymanifest(data)
106 106
107 107 def __getitem__(self, key):
108 108 return self._lm[key][0]
109 109
110 110 def find(self, key):
111 111 return self._lm[key]
112 112
113 113 def __len__(self):
114 114 return len(self._lm)
115 115
116 116 def __setitem__(self, key, node):
117 117 self._lm[key] = node, self.flags(key, '')
118 118
119 119 def __contains__(self, key):
120 120 return key in self._lm
121 121
122 122 def __delitem__(self, key):
123 123 del self._lm[key]
124 124
125 125 def __iter__(self):
126 126 return self._lm.__iter__()
127 127
128 128 def iterkeys(self):
129 129 return self._lm.iterkeys()
130 130
131 131 def keys(self):
132 132 return list(self.iterkeys())
133 133
134 134 def intersectfiles(self, files):
135 135 '''make a new lazymanifest with the intersection of self with files
136 136
137 137 The algorithm assumes that files is much smaller than self.'''
138 138 ret = manifestdict()
139 139 lm = self._lm
140 140 for fn in files:
141 141 if fn in lm:
142 142 ret._lm[fn] = self._lm[fn]
143 143 return ret
144 144
145 145 def filesnotin(self, m2):
146 146 '''Set of files in this manifest that are not in the other'''
147 147 files = set(self)
148 148 files.difference_update(m2)
149 149 return files
150 150
151 151 @propertycache
152 152 def _dirs(self):
153 153 return scmutil.dirs(self)
154 154
155 155 def dirs(self):
156 156 return self._dirs
157 157
158 def hasdir(self, dir):
159 return dir in self._dirs
160
158 161 def matches(self, match):
159 162 '''generate a new manifest filtered by the match argument'''
160 163 if match.always():
161 164 return self.copy()
162 165
163 166 files = match.files()
164 167 if (match.matchfn == match.exact or
165 168 (not match.anypats() and util.all(fn in self for fn in files))):
166 169 return self.intersectfiles(files)
167 170
168 171 lm = manifestdict('')
169 172 lm._lm = self._lm.filtercopy(match)
170 173 return lm
171 174
172 175 def diff(self, m2, clean=False):
173 176 '''Finds changes between the current manifest and m2.
174 177
175 178 Args:
176 179 m2: the manifest to which this manifest should be compared.
177 180 clean: if true, include files unchanged between these manifests
178 181 with a None value in the returned dictionary.
179 182
180 183 The result is returned as a dict with filename as key and
181 184 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
182 185 nodeid in the current/other manifest and fl1/fl2 is the flag
183 186 in the current/other manifest. Where the file does not exist,
184 187 the nodeid will be None and the flags will be the empty
185 188 string.
186 189 '''
187 190 return self._lm.diff(m2._lm, clean)
188 191
189 192 def setflag(self, key, flag):
190 193 self._lm[key] = self[key], flag
191 194
192 195 def get(self, key, default=None):
193 196 try:
194 197 return self._lm[key][0]
195 198 except KeyError:
196 199 return default
197 200
198 201 def flags(self, key, default=''):
199 202 try:
200 203 return self._lm[key][1]
201 204 except KeyError:
202 205 return default
203 206
204 207 def copy(self):
205 208 c = manifestdict('')
206 209 c._lm = self._lm.copy()
207 210 return c
208 211
209 212 def iteritems(self):
210 213 return (x[:2] for x in self._lm.iterentries())
211 214
212 215 def text(self):
213 216 return self._lm.text()
214 217
215 218 def fastdelta(self, base, changes):
216 219 """Given a base manifest text as an array.array and a list of changes
217 220 relative to that text, compute a delta that can be used by revlog.
218 221 """
219 222 delta = []
220 223 dstart = None
221 224 dend = None
222 225 dline = [""]
223 226 start = 0
224 227 # zero copy representation of base as a buffer
225 228 addbuf = util.buffer(base)
226 229
227 230 # start with a readonly loop that finds the offset of
228 231 # each line and creates the deltas
229 232 for f, todelete in changes:
230 233 # bs will either be the index of the item or the insert point
231 234 start, end = _msearch(addbuf, f, start)
232 235 if not todelete:
233 236 h, fl = self._lm[f]
234 237 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
235 238 else:
236 239 if start == end:
237 240 # item we want to delete was not found, error out
238 241 raise AssertionError(
239 242 _("failed to remove %s from manifest") % f)
240 243 l = ""
241 244 if dstart is not None and dstart <= start and dend >= start:
242 245 if dend < end:
243 246 dend = end
244 247 if l:
245 248 dline.append(l)
246 249 else:
247 250 if dstart is not None:
248 251 delta.append([dstart, dend, "".join(dline)])
249 252 dstart = start
250 253 dend = end
251 254 dline = [l]
252 255
253 256 if dstart is not None:
254 257 delta.append([dstart, dend, "".join(dline)])
255 258 # apply the delta to the base, and get a delta for addrevision
256 259 deltatext, arraytext = _addlistdelta(base, delta)
257 260 return arraytext, deltatext
258 261
259 262 def _msearch(m, s, lo=0, hi=None):
260 263 '''return a tuple (start, end) that says where to find s within m.
261 264
262 265 If the string is found m[start:end] are the line containing
263 266 that string. If start == end the string was not found and
264 267 they indicate the proper sorted insertion point.
265 268
266 269 m should be a buffer or a string
267 270 s is a string'''
268 271 def advance(i, c):
269 272 while i < lenm and m[i] != c:
270 273 i += 1
271 274 return i
272 275 if not s:
273 276 return (lo, lo)
274 277 lenm = len(m)
275 278 if not hi:
276 279 hi = lenm
277 280 while lo < hi:
278 281 mid = (lo + hi) // 2
279 282 start = mid
280 283 while start > 0 and m[start - 1] != '\n':
281 284 start -= 1
282 285 end = advance(start, '\0')
283 286 if m[start:end] < s:
284 287 # we know that after the null there are 40 bytes of sha1
285 288 # this translates to the bisect lo = mid + 1
286 289 lo = advance(end + 40, '\n') + 1
287 290 else:
288 291 # this translates to the bisect hi = mid
289 292 hi = start
290 293 end = advance(lo, '\0')
291 294 found = m[lo:end]
292 295 if s == found:
293 296 # we know that after the null there are 40 bytes of sha1
294 297 end = advance(end + 40, '\n')
295 298 return (lo, end + 1)
296 299 else:
297 300 return (lo, lo)
298 301
299 302 def _checkforbidden(l):
300 303 """Check filenames for illegal characters."""
301 304 for f in l:
302 305 if '\n' in f or '\r' in f:
303 306 raise error.RevlogError(
304 307 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
305 308
306 309
307 310 # apply the changes collected during the bisect loop to our addlist
308 311 # return a delta suitable for addrevision
309 312 def _addlistdelta(addlist, x):
310 313 # for large addlist arrays, building a new array is cheaper
311 314 # than repeatedly modifying the existing one
312 315 currentposition = 0
313 316 newaddlist = array.array('c')
314 317
315 318 for start, end, content in x:
316 319 newaddlist += addlist[currentposition:start]
317 320 if content:
318 321 newaddlist += array.array('c', content)
319 322
320 323 currentposition = end
321 324
322 325 newaddlist += addlist[currentposition:]
323 326
324 327 deltatext = "".join(struct.pack(">lll", start, end, len(content))
325 328 + content for start, end, content in x)
326 329 return deltatext, newaddlist
327 330
328 331 class manifest(revlog.revlog):
329 332 def __init__(self, opener):
330 333 # During normal operations, we expect to deal with not more than four
331 334 # revs at a time (such as during commit --amend). When rebasing large
332 335 # stacks of commits, the number can go up, hence the config knob below.
333 336 cachesize = 4
334 337 opts = getattr(opener, 'options', None)
335 338 if opts is not None:
336 339 cachesize = opts.get('manifestcachesize', cachesize)
337 340 self._mancache = util.lrucachedict(cachesize)
338 341 revlog.revlog.__init__(self, opener, "00manifest.i")
339 342
340 343 def readdelta(self, node):
341 344 r = self.rev(node)
342 345 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
343 346 return manifestdict(d)
344 347
345 348 def readfast(self, node):
346 349 '''use the faster of readdelta or read'''
347 350 r = self.rev(node)
348 351 deltaparent = self.deltaparent(r)
349 352 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
350 353 return self.readdelta(node)
351 354 return self.read(node)
352 355
353 356 def read(self, node):
354 357 if node == revlog.nullid:
355 358 return manifestdict() # don't upset local cache
356 359 if node in self._mancache:
357 360 return self._mancache[node][0]
358 361 text = self.revision(node)
359 362 arraytext = array.array('c', text)
360 363 m = manifestdict(text)
361 364 self._mancache[node] = (m, arraytext)
362 365 return m
363 366
364 367 def find(self, node, f):
365 368 '''look up entry for a single file efficiently.
366 369 return (node, flags) pair if found, (None, None) if not.'''
367 370 m = self.read(node)
368 371 try:
369 372 return m.find(f)
370 373 except KeyError:
371 374 return None, None
372 375
373 376 def add(self, m, transaction, link, p1, p2, added, removed):
374 377 if p1 in self._mancache:
375 378 # If our first parent is in the manifest cache, we can
376 379 # compute a delta here using properties we know about the
377 380 # manifest up-front, which may save time later for the
378 381 # revlog layer.
379 382
380 383 _checkforbidden(added)
381 384 # combine the changed lists into one list for sorting
382 385 work = [(x, False) for x in added]
383 386 work.extend((x, True) for x in removed)
384 387 # this could use heapq.merge() (from Python 2.6+) or equivalent
385 388 # since the lists are already sorted
386 389 work.sort()
387 390
388 391 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
389 392 cachedelta = self.rev(p1), deltatext
390 393 text = util.buffer(arraytext)
391 394 else:
392 395 # The first parent manifest isn't already loaded, so we'll
393 396 # just encode a fulltext of the manifest and pass that
394 397 # through to the revlog layer, and let it handle the delta
395 398 # process.
396 399 text = m.text()
397 400 arraytext = array.array('c', text)
398 401 cachedelta = None
399 402
400 403 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
401 404 self._mancache[n] = (m, arraytext)
402 405
403 406 return n
General Comments 0
You need to be logged in to leave comments. Login now