##// END OF EJS Templates
match: remove unnecessary optimization where visitdir() returns 'all'...
Drew Gottlieb -
r25188:2773540c default
parent child Browse files
Show More
@@ -1,961 +1,947 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
9 import mdiff, parsers, error, revlog, util
10 import array, struct
10 import array, struct
11 import os
11 import os
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 def _parsev1(data):
15 def _parsev1(data):
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 _parsev2(data):
34 def _parsev2(data):
35 metadataend = data.find('\n')
35 metadataend = data.find('\n')
36 # Just ignore metadata for now
36 # Just ignore metadata for now
37 pos = metadataend + 1
37 pos = metadataend + 1
38 prevf = ''
38 prevf = ''
39 while pos < len(data):
39 while pos < len(data):
40 end = data.find('\n', pos + 1) # +1 to skip stem length byte
40 end = data.find('\n', pos + 1) # +1 to skip stem length byte
41 if end == -1:
41 if end == -1:
42 raise ValueError('Manifest ended with incomplete file entry.')
42 raise ValueError('Manifest ended with incomplete file entry.')
43 stemlen = ord(data[pos])
43 stemlen = ord(data[pos])
44 items = data[pos + 1:end].split('\0')
44 items = data[pos + 1:end].split('\0')
45 f = prevf[:stemlen] + items[0]
45 f = prevf[:stemlen] + items[0]
46 if prevf > f:
46 if prevf > f:
47 raise ValueError('Manifest entries not in sorted order.')
47 raise ValueError('Manifest entries not in sorted order.')
48 fl = items[1]
48 fl = items[1]
49 # Just ignore metadata (items[2:] for now)
49 # Just ignore metadata (items[2:] for now)
50 n = data[end + 1:end + 21]
50 n = data[end + 1:end + 21]
51 yield f, n, fl
51 yield f, n, fl
52 pos = end + 22
52 pos = end + 22
53 prevf = f
53 prevf = f
54
54
55 def _parse(data):
55 def _parse(data):
56 """Generates (path, node, flags) tuples from a manifest text"""
56 """Generates (path, node, flags) tuples from a manifest text"""
57 if data.startswith('\0'):
57 if data.startswith('\0'):
58 return iter(_parsev2(data))
58 return iter(_parsev2(data))
59 else:
59 else:
60 return iter(_parsev1(data))
60 return iter(_parsev1(data))
61
61
62 def _text(it, usemanifestv2):
62 def _text(it, usemanifestv2):
63 """Given an iterator over (path, node, flags) tuples, returns a manifest
63 """Given an iterator over (path, node, flags) tuples, returns a manifest
64 text"""
64 text"""
65 if usemanifestv2:
65 if usemanifestv2:
66 return _textv2(it)
66 return _textv2(it)
67 else:
67 else:
68 return _textv1(it)
68 return _textv1(it)
69
69
70 def _textv1(it):
70 def _textv1(it):
71 files = []
71 files = []
72 lines = []
72 lines = []
73 _hex = revlog.hex
73 _hex = revlog.hex
74 for f, n, fl in it:
74 for f, n, fl in it:
75 files.append(f)
75 files.append(f)
76 # if this is changed to support newlines in filenames,
76 # if this is changed to support newlines in filenames,
77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
78 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
78 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
79
79
80 _checkforbidden(files)
80 _checkforbidden(files)
81 return ''.join(lines)
81 return ''.join(lines)
82
82
83 def _textv2(it):
83 def _textv2(it):
84 files = []
84 files = []
85 lines = ['\0\n']
85 lines = ['\0\n']
86 prevf = ''
86 prevf = ''
87 for f, n, fl in it:
87 for f, n, fl in it:
88 files.append(f)
88 files.append(f)
89 stem = os.path.commonprefix([prevf, f])
89 stem = os.path.commonprefix([prevf, f])
90 stemlen = min(len(stem), 255)
90 stemlen = min(len(stem), 255)
91 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
91 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
92 prevf = f
92 prevf = f
93 _checkforbidden(files)
93 _checkforbidden(files)
94 return ''.join(lines)
94 return ''.join(lines)
95
95
96 class _lazymanifest(dict):
96 class _lazymanifest(dict):
97 """This is the pure implementation of lazymanifest.
97 """This is the pure implementation of lazymanifest.
98
98
99 It has not been optimized *at all* and is not lazy.
99 It has not been optimized *at all* and is not lazy.
100 """
100 """
101
101
102 def __init__(self, data):
102 def __init__(self, data):
103 dict.__init__(self)
103 dict.__init__(self)
104 for f, n, fl in _parse(data):
104 for f, n, fl in _parse(data):
105 self[f] = n, fl
105 self[f] = n, fl
106
106
107 def __setitem__(self, k, v):
107 def __setitem__(self, k, v):
108 node, flag = v
108 node, flag = v
109 assert node is not None
109 assert node is not None
110 if len(node) > 21:
110 if len(node) > 21:
111 node = node[:21] # match c implementation behavior
111 node = node[:21] # match c implementation behavior
112 dict.__setitem__(self, k, (node, flag))
112 dict.__setitem__(self, k, (node, flag))
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return iter(sorted(dict.keys(self)))
115 return iter(sorted(dict.keys(self)))
116
116
117 def iterkeys(self):
117 def iterkeys(self):
118 return iter(sorted(dict.keys(self)))
118 return iter(sorted(dict.keys(self)))
119
119
120 def iterentries(self):
120 def iterentries(self):
121 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
121 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
122
122
123 def copy(self):
123 def copy(self):
124 c = _lazymanifest('')
124 c = _lazymanifest('')
125 c.update(self)
125 c.update(self)
126 return c
126 return c
127
127
128 def diff(self, m2, clean=False):
128 def diff(self, m2, clean=False):
129 '''Finds changes between the current manifest and m2.'''
129 '''Finds changes between the current manifest and m2.'''
130 diff = {}
130 diff = {}
131
131
132 for fn, e1 in self.iteritems():
132 for fn, e1 in self.iteritems():
133 if fn not in m2:
133 if fn not in m2:
134 diff[fn] = e1, (None, '')
134 diff[fn] = e1, (None, '')
135 else:
135 else:
136 e2 = m2[fn]
136 e2 = m2[fn]
137 if e1 != e2:
137 if e1 != e2:
138 diff[fn] = e1, e2
138 diff[fn] = e1, e2
139 elif clean:
139 elif clean:
140 diff[fn] = None
140 diff[fn] = None
141
141
142 for fn, e2 in m2.iteritems():
142 for fn, e2 in m2.iteritems():
143 if fn not in self:
143 if fn not in self:
144 diff[fn] = (None, ''), e2
144 diff[fn] = (None, ''), e2
145
145
146 return diff
146 return diff
147
147
148 def filtercopy(self, filterfn):
148 def filtercopy(self, filterfn):
149 c = _lazymanifest('')
149 c = _lazymanifest('')
150 for f, n, fl in self.iterentries():
150 for f, n, fl in self.iterentries():
151 if filterfn(f):
151 if filterfn(f):
152 c[f] = n, fl
152 c[f] = n, fl
153 return c
153 return c
154
154
155 def text(self):
155 def text(self):
156 """Get the full data of this manifest as a bytestring."""
156 """Get the full data of this manifest as a bytestring."""
157 return _textv1(self.iterentries())
157 return _textv1(self.iterentries())
158
158
159 try:
159 try:
160 _lazymanifest = parsers.lazymanifest
160 _lazymanifest = parsers.lazymanifest
161 except AttributeError:
161 except AttributeError:
162 pass
162 pass
163
163
164 class manifestdict(object):
164 class manifestdict(object):
165 def __init__(self, data=''):
165 def __init__(self, data=''):
166 if data.startswith('\0'):
166 if data.startswith('\0'):
167 #_lazymanifest can not parse v2
167 #_lazymanifest can not parse v2
168 self._lm = _lazymanifest('')
168 self._lm = _lazymanifest('')
169 for f, n, fl in _parsev2(data):
169 for f, n, fl in _parsev2(data):
170 self._lm[f] = n, fl
170 self._lm[f] = n, fl
171 else:
171 else:
172 self._lm = _lazymanifest(data)
172 self._lm = _lazymanifest(data)
173
173
174 def __getitem__(self, key):
174 def __getitem__(self, key):
175 return self._lm[key][0]
175 return self._lm[key][0]
176
176
177 def find(self, key):
177 def find(self, key):
178 return self._lm[key]
178 return self._lm[key]
179
179
180 def __len__(self):
180 def __len__(self):
181 return len(self._lm)
181 return len(self._lm)
182
182
183 def __setitem__(self, key, node):
183 def __setitem__(self, key, node):
184 self._lm[key] = node, self.flags(key, '')
184 self._lm[key] = node, self.flags(key, '')
185
185
186 def __contains__(self, key):
186 def __contains__(self, key):
187 return key in self._lm
187 return key in self._lm
188
188
189 def __delitem__(self, key):
189 def __delitem__(self, key):
190 del self._lm[key]
190 del self._lm[key]
191
191
192 def __iter__(self):
192 def __iter__(self):
193 return self._lm.__iter__()
193 return self._lm.__iter__()
194
194
195 def iterkeys(self):
195 def iterkeys(self):
196 return self._lm.iterkeys()
196 return self._lm.iterkeys()
197
197
198 def keys(self):
198 def keys(self):
199 return list(self.iterkeys())
199 return list(self.iterkeys())
200
200
201 def filesnotin(self, m2):
201 def filesnotin(self, m2):
202 '''Set of files in this manifest that are not in the other'''
202 '''Set of files in this manifest that are not in the other'''
203 files = set(self)
203 files = set(self)
204 files.difference_update(m2)
204 files.difference_update(m2)
205 return files
205 return files
206
206
207 @propertycache
207 @propertycache
208 def _dirs(self):
208 def _dirs(self):
209 return util.dirs(self)
209 return util.dirs(self)
210
210
211 def dirs(self):
211 def dirs(self):
212 return self._dirs
212 return self._dirs
213
213
214 def hasdir(self, dir):
214 def hasdir(self, dir):
215 return dir in self._dirs
215 return dir in self._dirs
216
216
217 def _filesfastpath(self, match):
217 def _filesfastpath(self, match):
218 '''Checks whether we can correctly and quickly iterate over matcher
218 '''Checks whether we can correctly and quickly iterate over matcher
219 files instead of over manifest files.'''
219 files instead of over manifest files.'''
220 files = match.files()
220 files = match.files()
221 return (len(files) < 100 and (match.isexact() or
221 return (len(files) < 100 and (match.isexact() or
222 (not match.anypats() and all(fn in self for fn in files))))
222 (not match.anypats() and all(fn in self for fn in files))))
223
223
224 def walk(self, match):
224 def walk(self, match):
225 '''Generates matching file names.
225 '''Generates matching file names.
226
226
227 Equivalent to manifest.matches(match).iterkeys(), but without creating
227 Equivalent to manifest.matches(match).iterkeys(), but without creating
228 an entirely new manifest.
228 an entirely new manifest.
229
229
230 It also reports nonexistent files by marking them bad with match.bad().
230 It also reports nonexistent files by marking them bad with match.bad().
231 '''
231 '''
232 if match.always():
232 if match.always():
233 for f in iter(self):
233 for f in iter(self):
234 yield f
234 yield f
235 return
235 return
236
236
237 fset = set(match.files())
237 fset = set(match.files())
238
238
239 # avoid the entire walk if we're only looking for specific files
239 # avoid the entire walk if we're only looking for specific files
240 if self._filesfastpath(match):
240 if self._filesfastpath(match):
241 for fn in sorted(fset):
241 for fn in sorted(fset):
242 yield fn
242 yield fn
243 return
243 return
244
244
245 for fn in self:
245 for fn in self:
246 if fn in fset:
246 if fn in fset:
247 # specified pattern is the exact name
247 # specified pattern is the exact name
248 fset.remove(fn)
248 fset.remove(fn)
249 if match(fn):
249 if match(fn):
250 yield fn
250 yield fn
251
251
252 # for dirstate.walk, files=['.'] means "walk the whole tree".
252 # for dirstate.walk, files=['.'] means "walk the whole tree".
253 # follow that here, too
253 # follow that here, too
254 fset.discard('.')
254 fset.discard('.')
255
255
256 for fn in sorted(fset):
256 for fn in sorted(fset):
257 if not self.hasdir(fn):
257 if not self.hasdir(fn):
258 match.bad(fn, None)
258 match.bad(fn, None)
259
259
260 def matches(self, match):
260 def matches(self, match):
261 '''generate a new manifest filtered by the match argument'''
261 '''generate a new manifest filtered by the match argument'''
262 if match.always():
262 if match.always():
263 return self.copy()
263 return self.copy()
264
264
265 if self._filesfastpath(match):
265 if self._filesfastpath(match):
266 m = manifestdict()
266 m = manifestdict()
267 lm = self._lm
267 lm = self._lm
268 for fn in match.files():
268 for fn in match.files():
269 if fn in lm:
269 if fn in lm:
270 m._lm[fn] = lm[fn]
270 m._lm[fn] = lm[fn]
271 return m
271 return m
272
272
273 m = manifestdict()
273 m = manifestdict()
274 m._lm = self._lm.filtercopy(match)
274 m._lm = self._lm.filtercopy(match)
275 return m
275 return m
276
276
277 def diff(self, m2, clean=False):
277 def diff(self, m2, clean=False):
278 '''Finds changes between the current manifest and m2.
278 '''Finds changes between the current manifest and m2.
279
279
280 Args:
280 Args:
281 m2: the manifest to which this manifest should be compared.
281 m2: the manifest to which this manifest should be compared.
282 clean: if true, include files unchanged between these manifests
282 clean: if true, include files unchanged between these manifests
283 with a None value in the returned dictionary.
283 with a None value in the returned dictionary.
284
284
285 The result is returned as a dict with filename as key and
285 The result is returned as a dict with filename as key and
286 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
286 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
287 nodeid in the current/other manifest and fl1/fl2 is the flag
287 nodeid in the current/other manifest and fl1/fl2 is the flag
288 in the current/other manifest. Where the file does not exist,
288 in the current/other manifest. Where the file does not exist,
289 the nodeid will be None and the flags will be the empty
289 the nodeid will be None and the flags will be the empty
290 string.
290 string.
291 '''
291 '''
292 return self._lm.diff(m2._lm, clean)
292 return self._lm.diff(m2._lm, clean)
293
293
294 def setflag(self, key, flag):
294 def setflag(self, key, flag):
295 self._lm[key] = self[key], flag
295 self._lm[key] = self[key], flag
296
296
297 def get(self, key, default=None):
297 def get(self, key, default=None):
298 try:
298 try:
299 return self._lm[key][0]
299 return self._lm[key][0]
300 except KeyError:
300 except KeyError:
301 return default
301 return default
302
302
303 def flags(self, key, default=''):
303 def flags(self, key, default=''):
304 try:
304 try:
305 return self._lm[key][1]
305 return self._lm[key][1]
306 except KeyError:
306 except KeyError:
307 return default
307 return default
308
308
309 def copy(self):
309 def copy(self):
310 c = manifestdict()
310 c = manifestdict()
311 c._lm = self._lm.copy()
311 c._lm = self._lm.copy()
312 return c
312 return c
313
313
314 def iteritems(self):
314 def iteritems(self):
315 return (x[:2] for x in self._lm.iterentries())
315 return (x[:2] for x in self._lm.iterentries())
316
316
317 def text(self, usemanifestv2=False):
317 def text(self, usemanifestv2=False):
318 if usemanifestv2:
318 if usemanifestv2:
319 return _textv2(self._lm.iterentries())
319 return _textv2(self._lm.iterentries())
320 else:
320 else:
321 # use (probably) native version for v1
321 # use (probably) native version for v1
322 return self._lm.text()
322 return self._lm.text()
323
323
324 def fastdelta(self, base, changes):
324 def fastdelta(self, base, changes):
325 """Given a base manifest text as an array.array and a list of changes
325 """Given a base manifest text as an array.array and a list of changes
326 relative to that text, compute a delta that can be used by revlog.
326 relative to that text, compute a delta that can be used by revlog.
327 """
327 """
328 delta = []
328 delta = []
329 dstart = None
329 dstart = None
330 dend = None
330 dend = None
331 dline = [""]
331 dline = [""]
332 start = 0
332 start = 0
333 # zero copy representation of base as a buffer
333 # zero copy representation of base as a buffer
334 addbuf = util.buffer(base)
334 addbuf = util.buffer(base)
335
335
336 # start with a readonly loop that finds the offset of
336 # start with a readonly loop that finds the offset of
337 # each line and creates the deltas
337 # each line and creates the deltas
338 for f, todelete in changes:
338 for f, todelete in changes:
339 # bs will either be the index of the item or the insert point
339 # bs will either be the index of the item or the insert point
340 start, end = _msearch(addbuf, f, start)
340 start, end = _msearch(addbuf, f, start)
341 if not todelete:
341 if not todelete:
342 h, fl = self._lm[f]
342 h, fl = self._lm[f]
343 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
343 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
344 else:
344 else:
345 if start == end:
345 if start == end:
346 # item we want to delete was not found, error out
346 # item we want to delete was not found, error out
347 raise AssertionError(
347 raise AssertionError(
348 _("failed to remove %s from manifest") % f)
348 _("failed to remove %s from manifest") % f)
349 l = ""
349 l = ""
350 if dstart is not None and dstart <= start and dend >= start:
350 if dstart is not None and dstart <= start and dend >= start:
351 if dend < end:
351 if dend < end:
352 dend = end
352 dend = end
353 if l:
353 if l:
354 dline.append(l)
354 dline.append(l)
355 else:
355 else:
356 if dstart is not None:
356 if dstart is not None:
357 delta.append([dstart, dend, "".join(dline)])
357 delta.append([dstart, dend, "".join(dline)])
358 dstart = start
358 dstart = start
359 dend = end
359 dend = end
360 dline = [l]
360 dline = [l]
361
361
362 if dstart is not None:
362 if dstart is not None:
363 delta.append([dstart, dend, "".join(dline)])
363 delta.append([dstart, dend, "".join(dline)])
364 # apply the delta to the base, and get a delta for addrevision
364 # apply the delta to the base, and get a delta for addrevision
365 deltatext, arraytext = _addlistdelta(base, delta)
365 deltatext, arraytext = _addlistdelta(base, delta)
366 return arraytext, deltatext
366 return arraytext, deltatext
367
367
368 def _msearch(m, s, lo=0, hi=None):
368 def _msearch(m, s, lo=0, hi=None):
369 '''return a tuple (start, end) that says where to find s within m.
369 '''return a tuple (start, end) that says where to find s within m.
370
370
371 If the string is found m[start:end] are the line containing
371 If the string is found m[start:end] are the line containing
372 that string. If start == end the string was not found and
372 that string. If start == end the string was not found and
373 they indicate the proper sorted insertion point.
373 they indicate the proper sorted insertion point.
374
374
375 m should be a buffer or a string
375 m should be a buffer or a string
376 s is a string'''
376 s is a string'''
377 def advance(i, c):
377 def advance(i, c):
378 while i < lenm and m[i] != c:
378 while i < lenm and m[i] != c:
379 i += 1
379 i += 1
380 return i
380 return i
381 if not s:
381 if not s:
382 return (lo, lo)
382 return (lo, lo)
383 lenm = len(m)
383 lenm = len(m)
384 if not hi:
384 if not hi:
385 hi = lenm
385 hi = lenm
386 while lo < hi:
386 while lo < hi:
387 mid = (lo + hi) // 2
387 mid = (lo + hi) // 2
388 start = mid
388 start = mid
389 while start > 0 and m[start - 1] != '\n':
389 while start > 0 and m[start - 1] != '\n':
390 start -= 1
390 start -= 1
391 end = advance(start, '\0')
391 end = advance(start, '\0')
392 if m[start:end] < s:
392 if m[start:end] < s:
393 # we know that after the null there are 40 bytes of sha1
393 # we know that after the null there are 40 bytes of sha1
394 # this translates to the bisect lo = mid + 1
394 # this translates to the bisect lo = mid + 1
395 lo = advance(end + 40, '\n') + 1
395 lo = advance(end + 40, '\n') + 1
396 else:
396 else:
397 # this translates to the bisect hi = mid
397 # this translates to the bisect hi = mid
398 hi = start
398 hi = start
399 end = advance(lo, '\0')
399 end = advance(lo, '\0')
400 found = m[lo:end]
400 found = m[lo:end]
401 if s == found:
401 if s == found:
402 # we know that after the null there are 40 bytes of sha1
402 # we know that after the null there are 40 bytes of sha1
403 end = advance(end + 40, '\n')
403 end = advance(end + 40, '\n')
404 return (lo, end + 1)
404 return (lo, end + 1)
405 else:
405 else:
406 return (lo, lo)
406 return (lo, lo)
407
407
408 def _checkforbidden(l):
408 def _checkforbidden(l):
409 """Check filenames for illegal characters."""
409 """Check filenames for illegal characters."""
410 for f in l:
410 for f in l:
411 if '\n' in f or '\r' in f:
411 if '\n' in f or '\r' in f:
412 raise error.RevlogError(
412 raise error.RevlogError(
413 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
413 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
414
414
415
415
416 # apply the changes collected during the bisect loop to our addlist
416 # apply the changes collected during the bisect loop to our addlist
417 # return a delta suitable for addrevision
417 # return a delta suitable for addrevision
418 def _addlistdelta(addlist, x):
418 def _addlistdelta(addlist, x):
419 # for large addlist arrays, building a new array is cheaper
419 # for large addlist arrays, building a new array is cheaper
420 # than repeatedly modifying the existing one
420 # than repeatedly modifying the existing one
421 currentposition = 0
421 currentposition = 0
422 newaddlist = array.array('c')
422 newaddlist = array.array('c')
423
423
424 for start, end, content in x:
424 for start, end, content in x:
425 newaddlist += addlist[currentposition:start]
425 newaddlist += addlist[currentposition:start]
426 if content:
426 if content:
427 newaddlist += array.array('c', content)
427 newaddlist += array.array('c', content)
428
428
429 currentposition = end
429 currentposition = end
430
430
431 newaddlist += addlist[currentposition:]
431 newaddlist += addlist[currentposition:]
432
432
433 deltatext = "".join(struct.pack(">lll", start, end, len(content))
433 deltatext = "".join(struct.pack(">lll", start, end, len(content))
434 + content for start, end, content in x)
434 + content for start, end, content in x)
435 return deltatext, newaddlist
435 return deltatext, newaddlist
436
436
437 def _splittopdir(f):
437 def _splittopdir(f):
438 if '/' in f:
438 if '/' in f:
439 dir, subpath = f.split('/', 1)
439 dir, subpath = f.split('/', 1)
440 return dir + '/', subpath
440 return dir + '/', subpath
441 else:
441 else:
442 return '', f
442 return '', f
443
443
444 class treemanifest(object):
444 class treemanifest(object):
445 def __init__(self, dir='', text=''):
445 def __init__(self, dir='', text=''):
446 self._dir = dir
446 self._dir = dir
447 self._node = revlog.nullid
447 self._node = revlog.nullid
448 self._dirs = {}
448 self._dirs = {}
449 # Using _lazymanifest here is a little slower than plain old dicts
449 # Using _lazymanifest here is a little slower than plain old dicts
450 self._files = {}
450 self._files = {}
451 self._flags = {}
451 self._flags = {}
452 def readsubtree(subdir, subm):
452 def readsubtree(subdir, subm):
453 raise AssertionError('treemanifest constructor only accepts '
453 raise AssertionError('treemanifest constructor only accepts '
454 'flat manifests')
454 'flat manifests')
455 self.parse(text, readsubtree)
455 self.parse(text, readsubtree)
456
456
457 def _subpath(self, path):
457 def _subpath(self, path):
458 return self._dir + path
458 return self._dir + path
459
459
460 def __len__(self):
460 def __len__(self):
461 size = len(self._files)
461 size = len(self._files)
462 for m in self._dirs.values():
462 for m in self._dirs.values():
463 size += m.__len__()
463 size += m.__len__()
464 return size
464 return size
465
465
466 def _isempty(self):
466 def _isempty(self):
467 return (not self._files and (not self._dirs or
467 return (not self._files and (not self._dirs or
468 all(m._isempty() for m in self._dirs.values())))
468 all(m._isempty() for m in self._dirs.values())))
469
469
470 def __str__(self):
470 def __str__(self):
471 return ('<treemanifest dir=%s, node=%s>' %
471 return ('<treemanifest dir=%s, node=%s>' %
472 (self._dir, revlog.hex(self._node)))
472 (self._dir, revlog.hex(self._node)))
473
473
474 def dir(self):
474 def dir(self):
475 '''The directory that this tree manifest represents, including a
475 '''The directory that this tree manifest represents, including a
476 trailing '/'. Empty string for the repo root directory.'''
476 trailing '/'. Empty string for the repo root directory.'''
477 return self._dir
477 return self._dir
478
478
479 def node(self):
479 def node(self):
480 '''This node of this instance. nullid for unsaved instances. Should
480 '''This node of this instance. nullid for unsaved instances. Should
481 be updated when the instance is read or written from a revlog.
481 be updated when the instance is read or written from a revlog.
482 '''
482 '''
483 return self._node
483 return self._node
484
484
485 def setnode(self, node):
485 def setnode(self, node):
486 self._node = node
486 self._node = node
487
487
488 def iteritems(self):
488 def iteritems(self):
489 for p, n in sorted(self._dirs.items() + self._files.items()):
489 for p, n in sorted(self._dirs.items() + self._files.items()):
490 if p in self._files:
490 if p in self._files:
491 yield self._subpath(p), n
491 yield self._subpath(p), n
492 else:
492 else:
493 for f, sn in n.iteritems():
493 for f, sn in n.iteritems():
494 yield f, sn
494 yield f, sn
495
495
496 def iterkeys(self):
496 def iterkeys(self):
497 for p in sorted(self._dirs.keys() + self._files.keys()):
497 for p in sorted(self._dirs.keys() + self._files.keys()):
498 if p in self._files:
498 if p in self._files:
499 yield self._subpath(p)
499 yield self._subpath(p)
500 else:
500 else:
501 for f in self._dirs[p].iterkeys():
501 for f in self._dirs[p].iterkeys():
502 yield f
502 yield f
503
503
504 def keys(self):
504 def keys(self):
505 return list(self.iterkeys())
505 return list(self.iterkeys())
506
506
507 def __iter__(self):
507 def __iter__(self):
508 return self.iterkeys()
508 return self.iterkeys()
509
509
510 def __contains__(self, f):
510 def __contains__(self, f):
511 if f is None:
511 if f is None:
512 return False
512 return False
513 dir, subpath = _splittopdir(f)
513 dir, subpath = _splittopdir(f)
514 if dir:
514 if dir:
515 if dir not in self._dirs:
515 if dir not in self._dirs:
516 return False
516 return False
517 return self._dirs[dir].__contains__(subpath)
517 return self._dirs[dir].__contains__(subpath)
518 else:
518 else:
519 return f in self._files
519 return f in self._files
520
520
521 def get(self, f, default=None):
521 def get(self, f, default=None):
522 dir, subpath = _splittopdir(f)
522 dir, subpath = _splittopdir(f)
523 if dir:
523 if dir:
524 if dir not in self._dirs:
524 if dir not in self._dirs:
525 return default
525 return default
526 return self._dirs[dir].get(subpath, default)
526 return self._dirs[dir].get(subpath, default)
527 else:
527 else:
528 return self._files.get(f, default)
528 return self._files.get(f, default)
529
529
530 def __getitem__(self, f):
530 def __getitem__(self, f):
531 dir, subpath = _splittopdir(f)
531 dir, subpath = _splittopdir(f)
532 if dir:
532 if dir:
533 return self._dirs[dir].__getitem__(subpath)
533 return self._dirs[dir].__getitem__(subpath)
534 else:
534 else:
535 return self._files[f]
535 return self._files[f]
536
536
537 def flags(self, f):
537 def flags(self, f):
538 dir, subpath = _splittopdir(f)
538 dir, subpath = _splittopdir(f)
539 if dir:
539 if dir:
540 if dir not in self._dirs:
540 if dir not in self._dirs:
541 return ''
541 return ''
542 return self._dirs[dir].flags(subpath)
542 return self._dirs[dir].flags(subpath)
543 else:
543 else:
544 if f in self._dirs:
544 if f in self._dirs:
545 return ''
545 return ''
546 return self._flags.get(f, '')
546 return self._flags.get(f, '')
547
547
548 def find(self, f):
548 def find(self, f):
549 dir, subpath = _splittopdir(f)
549 dir, subpath = _splittopdir(f)
550 if dir:
550 if dir:
551 return self._dirs[dir].find(subpath)
551 return self._dirs[dir].find(subpath)
552 else:
552 else:
553 return self._files[f], self._flags.get(f, '')
553 return self._files[f], self._flags.get(f, '')
554
554
555 def __delitem__(self, f):
555 def __delitem__(self, f):
556 dir, subpath = _splittopdir(f)
556 dir, subpath = _splittopdir(f)
557 if dir:
557 if dir:
558 self._dirs[dir].__delitem__(subpath)
558 self._dirs[dir].__delitem__(subpath)
559 # If the directory is now empty, remove it
559 # If the directory is now empty, remove it
560 if self._dirs[dir]._isempty():
560 if self._dirs[dir]._isempty():
561 del self._dirs[dir]
561 del self._dirs[dir]
562 else:
562 else:
563 del self._files[f]
563 del self._files[f]
564 if f in self._flags:
564 if f in self._flags:
565 del self._flags[f]
565 del self._flags[f]
566
566
567 def __setitem__(self, f, n):
567 def __setitem__(self, f, n):
568 assert n is not None
568 assert n is not None
569 dir, subpath = _splittopdir(f)
569 dir, subpath = _splittopdir(f)
570 if dir:
570 if dir:
571 if dir not in self._dirs:
571 if dir not in self._dirs:
572 self._dirs[dir] = treemanifest(self._subpath(dir))
572 self._dirs[dir] = treemanifest(self._subpath(dir))
573 self._dirs[dir].__setitem__(subpath, n)
573 self._dirs[dir].__setitem__(subpath, n)
574 else:
574 else:
575 self._files[f] = n[:21] # to match manifestdict's behavior
575 self._files[f] = n[:21] # to match manifestdict's behavior
576
576
577 def setflag(self, f, flags):
577 def setflag(self, f, flags):
578 """Set the flags (symlink, executable) for path f."""
578 """Set the flags (symlink, executable) for path f."""
579 assert 'd' not in flags
579 assert 'd' not in flags
580 dir, subpath = _splittopdir(f)
580 dir, subpath = _splittopdir(f)
581 if dir:
581 if dir:
582 if dir not in self._dirs:
582 if dir not in self._dirs:
583 self._dirs[dir] = treemanifest(self._subpath(dir))
583 self._dirs[dir] = treemanifest(self._subpath(dir))
584 self._dirs[dir].setflag(subpath, flags)
584 self._dirs[dir].setflag(subpath, flags)
585 else:
585 else:
586 self._flags[f] = flags
586 self._flags[f] = flags
587
587
588 def copy(self):
588 def copy(self):
589 copy = treemanifest(self._dir)
589 copy = treemanifest(self._dir)
590 copy._node = self._node
590 copy._node = self._node
591 for d in self._dirs:
591 for d in self._dirs:
592 copy._dirs[d] = self._dirs[d].copy()
592 copy._dirs[d] = self._dirs[d].copy()
593 copy._files = dict.copy(self._files)
593 copy._files = dict.copy(self._files)
594 copy._flags = dict.copy(self._flags)
594 copy._flags = dict.copy(self._flags)
595 return copy
595 return copy
596
596
597 def filesnotin(self, m2):
597 def filesnotin(self, m2):
598 '''Set of files in this manifest that are not in the other'''
598 '''Set of files in this manifest that are not in the other'''
599 files = set()
599 files = set()
600 def _filesnotin(t1, t2):
600 def _filesnotin(t1, t2):
601 for d, m1 in t1._dirs.iteritems():
601 for d, m1 in t1._dirs.iteritems():
602 if d in t2._dirs:
602 if d in t2._dirs:
603 m2 = t2._dirs[d]
603 m2 = t2._dirs[d]
604 _filesnotin(m1, m2)
604 _filesnotin(m1, m2)
605 else:
605 else:
606 files.update(m1.iterkeys())
606 files.update(m1.iterkeys())
607
607
608 for fn in t1._files.iterkeys():
608 for fn in t1._files.iterkeys():
609 if fn not in t2._files:
609 if fn not in t2._files:
610 files.add(t1._subpath(fn))
610 files.add(t1._subpath(fn))
611
611
612 _filesnotin(self, m2)
612 _filesnotin(self, m2)
613 return files
613 return files
614
614
615 @propertycache
615 @propertycache
616 def _alldirs(self):
616 def _alldirs(self):
617 return util.dirs(self)
617 return util.dirs(self)
618
618
619 def dirs(self):
619 def dirs(self):
620 return self._alldirs
620 return self._alldirs
621
621
622 def hasdir(self, dir):
622 def hasdir(self, dir):
623 topdir, subdir = _splittopdir(dir)
623 topdir, subdir = _splittopdir(dir)
624 if topdir:
624 if topdir:
625 if topdir in self._dirs:
625 if topdir in self._dirs:
626 return self._dirs[topdir].hasdir(subdir)
626 return self._dirs[topdir].hasdir(subdir)
627 return False
627 return False
628 return (dir + '/') in self._dirs
628 return (dir + '/') in self._dirs
629
629
630 def walk(self, match):
630 def walk(self, match):
631 '''Generates matching file names.
631 '''Generates matching file names.
632
632
633 Equivalent to manifest.matches(match).iterkeys(), but without creating
633 Equivalent to manifest.matches(match).iterkeys(), but without creating
634 an entirely new manifest.
634 an entirely new manifest.
635
635
636 It also reports nonexistent files by marking them bad with match.bad().
636 It also reports nonexistent files by marking them bad with match.bad().
637 '''
637 '''
638 if match.always():
638 if match.always():
639 for f in iter(self):
639 for f in iter(self):
640 yield f
640 yield f
641 return
641 return
642
642
643 fset = set(match.files())
643 fset = set(match.files())
644
644
645 for fn in self._walk(match):
645 for fn in self._walk(match):
646 if fn in fset:
646 if fn in fset:
647 # specified pattern is the exact name
647 # specified pattern is the exact name
648 fset.remove(fn)
648 fset.remove(fn)
649 yield fn
649 yield fn
650
650
651 # for dirstate.walk, files=['.'] means "walk the whole tree".
651 # for dirstate.walk, files=['.'] means "walk the whole tree".
652 # follow that here, too
652 # follow that here, too
653 fset.discard('.')
653 fset.discard('.')
654
654
655 for fn in sorted(fset):
655 for fn in sorted(fset):
656 if not self.hasdir(fn):
656 if not self.hasdir(fn):
657 match.bad(fn, None)
657 match.bad(fn, None)
658
658
659 def _walk(self, match, alldirs=False):
659 def _walk(self, match):
660 '''Recursively generates matching file names for walk().
660 '''Recursively generates matching file names for walk().'''
661
661 if not match.visitdir(self._dir[:-1] or '.'):
662 Will visit all subdirectories if alldirs is True, otherwise it will
663 only visit subdirectories for which match.visitdir is True.'''
664
665 if not alldirs:
666 # substring to strip trailing slash
667 visit = match.visitdir(self._dir[:-1] or '.')
668 if not visit:
669 return
662 return
670 alldirs = (visit == 'all')
671
663
672 # yield this dir's files and walk its submanifests
664 # yield this dir's files and walk its submanifests
673 for p in sorted(self._dirs.keys() + self._files.keys()):
665 for p in sorted(self._dirs.keys() + self._files.keys()):
674 if p in self._files:
666 if p in self._files:
675 fullp = self._subpath(p)
667 fullp = self._subpath(p)
676 if match(fullp):
668 if match(fullp):
677 yield fullp
669 yield fullp
678 else:
670 else:
679 for f in self._dirs[p]._walk(match, alldirs):
671 for f in self._dirs[p]._walk(match):
680 yield f
672 yield f
681
673
682 def matches(self, match):
674 def matches(self, match):
683 '''generate a new manifest filtered by the match argument'''
675 '''generate a new manifest filtered by the match argument'''
684 if match.always():
676 if match.always():
685 return self.copy()
677 return self.copy()
686
678
687 return self._matches(match)
679 return self._matches(match)
688
680
689 def _matches(self, match, alldirs=False):
681 def _matches(self, match):
690 '''recursively generate a new manifest filtered by the match argument.
682 '''recursively generate a new manifest filtered by the match argument.
691
683 '''
692 Will visit all subdirectories if alldirs is True, otherwise it will
693 only visit subdirectories for which match.visitdir is True.'''
694
695 ret = treemanifest(self._dir)
684 ret = treemanifest(self._dir)
696 if not alldirs:
685
697 # substring to strip trailing slash
686 if not match.visitdir(self._dir[:-1] or '.'):
698 visit = match.visitdir(self._dir[:-1] or '.')
699 if not visit:
700 return ret
687 return ret
701 alldirs = (visit == 'all')
702
688
703 for fn in self._files:
689 for fn in self._files:
704 fullp = self._subpath(fn)
690 fullp = self._subpath(fn)
705 if not match(fullp):
691 if not match(fullp):
706 continue
692 continue
707 ret._files[fn] = self._files[fn]
693 ret._files[fn] = self._files[fn]
708 if fn in self._flags:
694 if fn in self._flags:
709 ret._flags[fn] = self._flags[fn]
695 ret._flags[fn] = self._flags[fn]
710
696
711 for dir, subm in self._dirs.iteritems():
697 for dir, subm in self._dirs.iteritems():
712 m = subm._matches(match, alldirs)
698 m = subm._matches(match)
713 if not m._isempty():
699 if not m._isempty():
714 ret._dirs[dir] = m
700 ret._dirs[dir] = m
715
701
716 return ret
702 return ret
717
703
718 def diff(self, m2, clean=False):
704 def diff(self, m2, clean=False):
719 '''Finds changes between the current manifest and m2.
705 '''Finds changes between the current manifest and m2.
720
706
721 Args:
707 Args:
722 m2: the manifest to which this manifest should be compared.
708 m2: the manifest to which this manifest should be compared.
723 clean: if true, include files unchanged between these manifests
709 clean: if true, include files unchanged between these manifests
724 with a None value in the returned dictionary.
710 with a None value in the returned dictionary.
725
711
726 The result is returned as a dict with filename as key and
712 The result is returned as a dict with filename as key and
727 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
713 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
728 nodeid in the current/other manifest and fl1/fl2 is the flag
714 nodeid in the current/other manifest and fl1/fl2 is the flag
729 in the current/other manifest. Where the file does not exist,
715 in the current/other manifest. Where the file does not exist,
730 the nodeid will be None and the flags will be the empty
716 the nodeid will be None and the flags will be the empty
731 string.
717 string.
732 '''
718 '''
733 result = {}
719 result = {}
734 emptytree = treemanifest()
720 emptytree = treemanifest()
735 def _diff(t1, t2):
721 def _diff(t1, t2):
736 for d, m1 in t1._dirs.iteritems():
722 for d, m1 in t1._dirs.iteritems():
737 m2 = t2._dirs.get(d, emptytree)
723 m2 = t2._dirs.get(d, emptytree)
738 _diff(m1, m2)
724 _diff(m1, m2)
739
725
740 for d, m2 in t2._dirs.iteritems():
726 for d, m2 in t2._dirs.iteritems():
741 if d not in t1._dirs:
727 if d not in t1._dirs:
742 _diff(emptytree, m2)
728 _diff(emptytree, m2)
743
729
744 for fn, n1 in t1._files.iteritems():
730 for fn, n1 in t1._files.iteritems():
745 fl1 = t1._flags.get(fn, '')
731 fl1 = t1._flags.get(fn, '')
746 n2 = t2._files.get(fn, None)
732 n2 = t2._files.get(fn, None)
747 fl2 = t2._flags.get(fn, '')
733 fl2 = t2._flags.get(fn, '')
748 if n1 != n2 or fl1 != fl2:
734 if n1 != n2 or fl1 != fl2:
749 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
735 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
750 elif clean:
736 elif clean:
751 result[t1._subpath(fn)] = None
737 result[t1._subpath(fn)] = None
752
738
753 for fn, n2 in t2._files.iteritems():
739 for fn, n2 in t2._files.iteritems():
754 if fn not in t1._files:
740 if fn not in t1._files:
755 fl2 = t2._flags.get(fn, '')
741 fl2 = t2._flags.get(fn, '')
756 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
742 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
757
743
758 _diff(self, m2)
744 _diff(self, m2)
759 return result
745 return result
760
746
761 def parse(self, text, readsubtree):
747 def parse(self, text, readsubtree):
762 for f, n, fl in _parse(text):
748 for f, n, fl in _parse(text):
763 if fl == 'd':
749 if fl == 'd':
764 f = f + '/'
750 f = f + '/'
765 self._dirs[f] = readsubtree(self._subpath(f), n)
751 self._dirs[f] = readsubtree(self._subpath(f), n)
766 else:
752 else:
767 # Use __setitem__ and setflag rather than assigning directly
753 # Use __setitem__ and setflag rather than assigning directly
768 # to _files and _flags, thereby letting us parse flat manifests
754 # to _files and _flags, thereby letting us parse flat manifests
769 # as well as tree manifests.
755 # as well as tree manifests.
770 self[f] = n
756 self[f] = n
771 if fl:
757 if fl:
772 self.setflag(f, fl)
758 self.setflag(f, fl)
773
759
774 def text(self, usemanifestv2=False):
760 def text(self, usemanifestv2=False):
775 """Get the full data of this manifest as a bytestring."""
761 """Get the full data of this manifest as a bytestring."""
776 flags = self.flags
762 flags = self.flags
777 return _text(((f, self[f], flags(f)) for f in self.keys()),
763 return _text(((f, self[f], flags(f)) for f in self.keys()),
778 usemanifestv2)
764 usemanifestv2)
779
765
780 def dirtext(self, usemanifestv2=False):
766 def dirtext(self, usemanifestv2=False):
781 """Get the full data of this directory as a bytestring. Make sure that
767 """Get the full data of this directory as a bytestring. Make sure that
782 any submanifests have been written first, so their nodeids are correct.
768 any submanifests have been written first, so their nodeids are correct.
783 """
769 """
784 flags = self.flags
770 flags = self.flags
785 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
771 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
786 files = [(f, self._files[f], flags(f)) for f in self._files]
772 files = [(f, self._files[f], flags(f)) for f in self._files]
787 return _text(sorted(dirs + files), usemanifestv2)
773 return _text(sorted(dirs + files), usemanifestv2)
788
774
789 def writesubtrees(self, m1, m2, writesubtree):
775 def writesubtrees(self, m1, m2, writesubtree):
790 emptytree = treemanifest()
776 emptytree = treemanifest()
791 for d, subm in self._dirs.iteritems():
777 for d, subm in self._dirs.iteritems():
792 subp1 = m1._dirs.get(d, emptytree)._node
778 subp1 = m1._dirs.get(d, emptytree)._node
793 subp2 = m2._dirs.get(d, emptytree)._node
779 subp2 = m2._dirs.get(d, emptytree)._node
794 if subp1 == revlog.nullid:
780 if subp1 == revlog.nullid:
795 subp1, subp2 = subp2, subp1
781 subp1, subp2 = subp2, subp1
796 writesubtree(subm, subp1, subp2)
782 writesubtree(subm, subp1, subp2)
797
783
798 class manifest(revlog.revlog):
784 class manifest(revlog.revlog):
799 def __init__(self, opener, dir='', dirlogcache=None):
785 def __init__(self, opener, dir='', dirlogcache=None):
800 '''The 'dir' and 'dirlogcache' arguments are for internal use by
786 '''The 'dir' and 'dirlogcache' arguments are for internal use by
801 manifest.manifest only. External users should create a root manifest
787 manifest.manifest only. External users should create a root manifest
802 log with manifest.manifest(opener) and call dirlog() on it.
788 log with manifest.manifest(opener) and call dirlog() on it.
803 '''
789 '''
804 # During normal operations, we expect to deal with not more than four
790 # During normal operations, we expect to deal with not more than four
805 # revs at a time (such as during commit --amend). When rebasing large
791 # revs at a time (such as during commit --amend). When rebasing large
806 # stacks of commits, the number can go up, hence the config knob below.
792 # stacks of commits, the number can go up, hence the config knob below.
807 cachesize = 4
793 cachesize = 4
808 usetreemanifest = False
794 usetreemanifest = False
809 usemanifestv2 = False
795 usemanifestv2 = False
810 opts = getattr(opener, 'options', None)
796 opts = getattr(opener, 'options', None)
811 if opts is not None:
797 if opts is not None:
812 cachesize = opts.get('manifestcachesize', cachesize)
798 cachesize = opts.get('manifestcachesize', cachesize)
813 usetreemanifest = opts.get('treemanifest', usetreemanifest)
799 usetreemanifest = opts.get('treemanifest', usetreemanifest)
814 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
800 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
815 self._mancache = util.lrucachedict(cachesize)
801 self._mancache = util.lrucachedict(cachesize)
816 self._treeinmem = usetreemanifest
802 self._treeinmem = usetreemanifest
817 self._treeondisk = usetreemanifest
803 self._treeondisk = usetreemanifest
818 self._usemanifestv2 = usemanifestv2
804 self._usemanifestv2 = usemanifestv2
819 indexfile = "00manifest.i"
805 indexfile = "00manifest.i"
820 if dir:
806 if dir:
821 assert self._treeondisk
807 assert self._treeondisk
822 if not dir.endswith('/'):
808 if not dir.endswith('/'):
823 dir = dir + '/'
809 dir = dir + '/'
824 indexfile = "meta/" + dir + "00manifest.i"
810 indexfile = "meta/" + dir + "00manifest.i"
825 revlog.revlog.__init__(self, opener, indexfile)
811 revlog.revlog.__init__(self, opener, indexfile)
826 self._dir = dir
812 self._dir = dir
827 # The dirlogcache is kept on the root manifest log
813 # The dirlogcache is kept on the root manifest log
828 if dir:
814 if dir:
829 self._dirlogcache = dirlogcache
815 self._dirlogcache = dirlogcache
830 else:
816 else:
831 self._dirlogcache = {'': self}
817 self._dirlogcache = {'': self}
832
818
833 def _newmanifest(self, data=''):
819 def _newmanifest(self, data=''):
834 if self._treeinmem:
820 if self._treeinmem:
835 return treemanifest(self._dir, data)
821 return treemanifest(self._dir, data)
836 return manifestdict(data)
822 return manifestdict(data)
837
823
838 def dirlog(self, dir):
824 def dirlog(self, dir):
839 assert self._treeondisk
825 assert self._treeondisk
840 if dir not in self._dirlogcache:
826 if dir not in self._dirlogcache:
841 self._dirlogcache[dir] = manifest(self.opener, dir,
827 self._dirlogcache[dir] = manifest(self.opener, dir,
842 self._dirlogcache)
828 self._dirlogcache)
843 return self._dirlogcache[dir]
829 return self._dirlogcache[dir]
844
830
845 def _slowreaddelta(self, node):
831 def _slowreaddelta(self, node):
846 r0 = self.deltaparent(self.rev(node))
832 r0 = self.deltaparent(self.rev(node))
847 m0 = self.read(self.node(r0))
833 m0 = self.read(self.node(r0))
848 m1 = self.read(node)
834 m1 = self.read(node)
849 md = self._newmanifest()
835 md = self._newmanifest()
850 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
836 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
851 if n1:
837 if n1:
852 md[f] = n1
838 md[f] = n1
853 if fl1:
839 if fl1:
854 md.setflag(f, fl1)
840 md.setflag(f, fl1)
855 return md
841 return md
856
842
857 def readdelta(self, node):
843 def readdelta(self, node):
858 if self._usemanifestv2 or self._treeondisk:
844 if self._usemanifestv2 or self._treeondisk:
859 return self._slowreaddelta(node)
845 return self._slowreaddelta(node)
860 r = self.rev(node)
846 r = self.rev(node)
861 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
847 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
862 return self._newmanifest(d)
848 return self._newmanifest(d)
863
849
864 def readfast(self, node):
850 def readfast(self, node):
865 '''use the faster of readdelta or read
851 '''use the faster of readdelta or read
866
852
867 This will return a manifest which is either only the files
853 This will return a manifest which is either only the files
868 added/modified relative to p1, or all files in the
854 added/modified relative to p1, or all files in the
869 manifest. Which one is returned depends on the codepath used
855 manifest. Which one is returned depends on the codepath used
870 to retrieve the data.
856 to retrieve the data.
871 '''
857 '''
872 r = self.rev(node)
858 r = self.rev(node)
873 deltaparent = self.deltaparent(r)
859 deltaparent = self.deltaparent(r)
874 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
860 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
875 return self.readdelta(node)
861 return self.readdelta(node)
876 return self.read(node)
862 return self.read(node)
877
863
878 def read(self, node):
864 def read(self, node):
879 if node == revlog.nullid:
865 if node == revlog.nullid:
880 return self._newmanifest() # don't upset local cache
866 return self._newmanifest() # don't upset local cache
881 if node in self._mancache:
867 if node in self._mancache:
882 return self._mancache[node][0]
868 return self._mancache[node][0]
883 text = self.revision(node)
869 text = self.revision(node)
884 if self._treeondisk:
870 if self._treeondisk:
885 def readsubtree(dir, subm):
871 def readsubtree(dir, subm):
886 return self.dirlog(dir).read(subm)
872 return self.dirlog(dir).read(subm)
887 m = self._newmanifest()
873 m = self._newmanifest()
888 m.parse(text, readsubtree)
874 m.parse(text, readsubtree)
889 m.setnode(node)
875 m.setnode(node)
890 arraytext = None
876 arraytext = None
891 else:
877 else:
892 m = self._newmanifest(text)
878 m = self._newmanifest(text)
893 arraytext = array.array('c', text)
879 arraytext = array.array('c', text)
894 self._mancache[node] = (m, arraytext)
880 self._mancache[node] = (m, arraytext)
895 return m
881 return m
896
882
897 def find(self, node, f):
883 def find(self, node, f):
898 '''look up entry for a single file efficiently.
884 '''look up entry for a single file efficiently.
899 return (node, flags) pair if found, (None, None) if not.'''
885 return (node, flags) pair if found, (None, None) if not.'''
900 m = self.read(node)
886 m = self.read(node)
901 try:
887 try:
902 return m.find(f)
888 return m.find(f)
903 except KeyError:
889 except KeyError:
904 return None, None
890 return None, None
905
891
906 def add(self, m, transaction, link, p1, p2, added, removed):
892 def add(self, m, transaction, link, p1, p2, added, removed):
907 if (p1 in self._mancache and not self._treeinmem
893 if (p1 in self._mancache and not self._treeinmem
908 and not self._usemanifestv2):
894 and not self._usemanifestv2):
909 # If our first parent is in the manifest cache, we can
895 # If our first parent is in the manifest cache, we can
910 # compute a delta here using properties we know about the
896 # compute a delta here using properties we know about the
911 # manifest up-front, which may save time later for the
897 # manifest up-front, which may save time later for the
912 # revlog layer.
898 # revlog layer.
913
899
914 _checkforbidden(added)
900 _checkforbidden(added)
915 # combine the changed lists into one list for sorting
901 # combine the changed lists into one list for sorting
916 work = [(x, False) for x in added]
902 work = [(x, False) for x in added]
917 work.extend((x, True) for x in removed)
903 work.extend((x, True) for x in removed)
918 # this could use heapq.merge() (from Python 2.6+) or equivalent
904 # this could use heapq.merge() (from Python 2.6+) or equivalent
919 # since the lists are already sorted
905 # since the lists are already sorted
920 work.sort()
906 work.sort()
921
907
922 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
908 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
923 cachedelta = self.rev(p1), deltatext
909 cachedelta = self.rev(p1), deltatext
924 text = util.buffer(arraytext)
910 text = util.buffer(arraytext)
925 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
911 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
926 else:
912 else:
927 # The first parent manifest isn't already loaded, so we'll
913 # The first parent manifest isn't already loaded, so we'll
928 # just encode a fulltext of the manifest and pass that
914 # just encode a fulltext of the manifest and pass that
929 # through to the revlog layer, and let it handle the delta
915 # through to the revlog layer, and let it handle the delta
930 # process.
916 # process.
931 if self._treeondisk:
917 if self._treeondisk:
932 m1 = self.read(p1)
918 m1 = self.read(p1)
933 m2 = self.read(p2)
919 m2 = self.read(p2)
934 n = self._addtree(m, transaction, link, m1, m2)
920 n = self._addtree(m, transaction, link, m1, m2)
935 arraytext = None
921 arraytext = None
936 else:
922 else:
937 text = m.text(self._usemanifestv2)
923 text = m.text(self._usemanifestv2)
938 n = self.addrevision(text, transaction, link, p1, p2)
924 n = self.addrevision(text, transaction, link, p1, p2)
939 arraytext = array.array('c', text)
925 arraytext = array.array('c', text)
940
926
941 self._mancache[n] = (m, arraytext)
927 self._mancache[n] = (m, arraytext)
942
928
943 return n
929 return n
944
930
945 def _addtree(self, m, transaction, link, m1, m2):
931 def _addtree(self, m, transaction, link, m1, m2):
946 def writesubtree(subm, subp1, subp2):
932 def writesubtree(subm, subp1, subp2):
947 sublog = self.dirlog(subm.dir())
933 sublog = self.dirlog(subm.dir())
948 sublog.add(subm, transaction, link, subp1, subp2, None, None)
934 sublog.add(subm, transaction, link, subp1, subp2, None, None)
949 m.writesubtrees(m1, m2, writesubtree)
935 m.writesubtrees(m1, m2, writesubtree)
950 text = m.dirtext(self._usemanifestv2)
936 text = m.dirtext(self._usemanifestv2)
951 # If the manifest is unchanged compared to one parent,
937 # If the manifest is unchanged compared to one parent,
952 # don't write a new revision
938 # don't write a new revision
953 if text == m1.dirtext(self._usemanifestv2):
939 if text == m1.dirtext(self._usemanifestv2):
954 n = m1.node()
940 n = m1.node()
955 elif text == m2.dirtext(self._usemanifestv2):
941 elif text == m2.dirtext(self._usemanifestv2):
956 n = m2.node()
942 n = m2.node()
957 else:
943 else:
958 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
944 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
959 # Save nodeid so parent manifest can calculate its nodeid
945 # Save nodeid so parent manifest can calculate its nodeid
960 m.setnode(n)
946 m.setnode(n)
961 return n
947 return n
@@ -1,546 +1,542 b''
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
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 import re
8 import re
9 import util, pathutil
9 import util, pathutil
10 from i18n import _
10 from i18n import _
11
11
12 propertycache = util.propertycache
12 propertycache = util.propertycache
13
13
14 def _rematcher(regex):
14 def _rematcher(regex):
15 '''compile the regexp with the best available regexp engine and return a
15 '''compile the regexp with the best available regexp engine and return a
16 matcher function'''
16 matcher function'''
17 m = util.re.compile(regex)
17 m = util.re.compile(regex)
18 try:
18 try:
19 # slightly faster, provided by facebook's re2 bindings
19 # slightly faster, provided by facebook's re2 bindings
20 return m.test_match
20 return m.test_match
21 except AttributeError:
21 except AttributeError:
22 return m.match
22 return m.match
23
23
24 def _expandsets(kindpats, ctx, listsubrepos):
24 def _expandsets(kindpats, ctx, listsubrepos):
25 '''Returns the kindpats list with the 'set' patterns expanded.'''
25 '''Returns the kindpats list with the 'set' patterns expanded.'''
26 fset = set()
26 fset = set()
27 other = []
27 other = []
28
28
29 for kind, pat in kindpats:
29 for kind, pat in kindpats:
30 if kind == 'set':
30 if kind == 'set':
31 if not ctx:
31 if not ctx:
32 raise util.Abort("fileset expression with no context")
32 raise util.Abort("fileset expression with no context")
33 s = ctx.getfileset(pat)
33 s = ctx.getfileset(pat)
34 fset.update(s)
34 fset.update(s)
35
35
36 if listsubrepos:
36 if listsubrepos:
37 for subpath in ctx.substate:
37 for subpath in ctx.substate:
38 s = ctx.sub(subpath).getfileset(pat)
38 s = ctx.sub(subpath).getfileset(pat)
39 fset.update(subpath + '/' + f for f in s)
39 fset.update(subpath + '/' + f for f in s)
40
40
41 continue
41 continue
42 other.append((kind, pat))
42 other.append((kind, pat))
43 return fset, other
43 return fset, other
44
44
45 def _kindpatsalwaysmatch(kindpats):
45 def _kindpatsalwaysmatch(kindpats):
46 """"Checks whether the kindspats match everything, as e.g.
46 """"Checks whether the kindspats match everything, as e.g.
47 'relpath:.' does.
47 'relpath:.' does.
48 """
48 """
49 for kind, pat in kindpats:
49 for kind, pat in kindpats:
50 if pat != '' or kind not in ['relpath', 'glob']:
50 if pat != '' or kind not in ['relpath', 'glob']:
51 return False
51 return False
52 return True
52 return True
53
53
54 class match(object):
54 class match(object):
55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
56 default='glob', exact=False, auditor=None, ctx=None,
56 default='glob', exact=False, auditor=None, ctx=None,
57 listsubrepos=False):
57 listsubrepos=False):
58 """build an object to match a set of file patterns
58 """build an object to match a set of file patterns
59
59
60 arguments:
60 arguments:
61 root - the canonical root of the tree you're matching against
61 root - the canonical root of the tree you're matching against
62 cwd - the current working directory, if relevant
62 cwd - the current working directory, if relevant
63 patterns - patterns to find
63 patterns - patterns to find
64 include - patterns to include (unless they are excluded)
64 include - patterns to include (unless they are excluded)
65 exclude - patterns to exclude (even if they are included)
65 exclude - patterns to exclude (even if they are included)
66 default - if a pattern in patterns has no explicit type, assume this one
66 default - if a pattern in patterns has no explicit type, assume this one
67 exact - patterns are actually filenames (include/exclude still apply)
67 exact - patterns are actually filenames (include/exclude still apply)
68
68
69 a pattern is one of:
69 a pattern is one of:
70 'glob:<glob>' - a glob relative to cwd
70 'glob:<glob>' - a glob relative to cwd
71 're:<regexp>' - a regular expression
71 're:<regexp>' - a regular expression
72 'path:<path>' - a path relative to repository root
72 'path:<path>' - a path relative to repository root
73 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
73 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
74 'relpath:<path>' - a path relative to cwd
74 'relpath:<path>' - a path relative to cwd
75 'relre:<regexp>' - a regexp that needn't match the start of a name
75 'relre:<regexp>' - a regexp that needn't match the start of a name
76 'set:<fileset>' - a fileset expression
76 'set:<fileset>' - a fileset expression
77 '<something>' - a pattern of the specified default type
77 '<something>' - a pattern of the specified default type
78 """
78 """
79
79
80 self._root = root
80 self._root = root
81 self._cwd = cwd
81 self._cwd = cwd
82 self._files = [] # exact files and roots of patterns
82 self._files = [] # exact files and roots of patterns
83 self._anypats = bool(include or exclude)
83 self._anypats = bool(include or exclude)
84 self._always = False
84 self._always = False
85 self._pathrestricted = bool(include or exclude or patterns)
85 self._pathrestricted = bool(include or exclude or patterns)
86
86
87 matchfns = []
87 matchfns = []
88 if include:
88 if include:
89 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
89 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
90 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
90 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
91 listsubrepos)
91 listsubrepos)
92 matchfns.append(im)
92 matchfns.append(im)
93 if exclude:
93 if exclude:
94 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
94 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
95 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
95 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
96 listsubrepos)
96 listsubrepos)
97 matchfns.append(lambda f: not em(f))
97 matchfns.append(lambda f: not em(f))
98 if exact:
98 if exact:
99 if isinstance(patterns, list):
99 if isinstance(patterns, list):
100 self._files = patterns
100 self._files = patterns
101 else:
101 else:
102 self._files = list(patterns)
102 self._files = list(patterns)
103 matchfns.append(self.exact)
103 matchfns.append(self.exact)
104 elif patterns:
104 elif patterns:
105 kindpats = self._normalize(patterns, default, root, cwd, auditor)
105 kindpats = self._normalize(patterns, default, root, cwd, auditor)
106 if not _kindpatsalwaysmatch(kindpats):
106 if not _kindpatsalwaysmatch(kindpats):
107 self._files = _roots(kindpats)
107 self._files = _roots(kindpats)
108 self._anypats = self._anypats or _anypats(kindpats)
108 self._anypats = self._anypats or _anypats(kindpats)
109 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
109 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
110 listsubrepos)
110 listsubrepos)
111 matchfns.append(pm)
111 matchfns.append(pm)
112
112
113 if not matchfns:
113 if not matchfns:
114 m = util.always
114 m = util.always
115 self._always = True
115 self._always = True
116 elif len(matchfns) == 1:
116 elif len(matchfns) == 1:
117 m = matchfns[0]
117 m = matchfns[0]
118 else:
118 else:
119 def m(f):
119 def m(f):
120 for matchfn in matchfns:
120 for matchfn in matchfns:
121 if not matchfn(f):
121 if not matchfn(f):
122 return False
122 return False
123 return True
123 return True
124
124
125 self.matchfn = m
125 self.matchfn = m
126 self._fmap = set(self._files)
126 self._fmap = set(self._files)
127
127
128 def __call__(self, fn):
128 def __call__(self, fn):
129 return self.matchfn(fn)
129 return self.matchfn(fn)
130 def __iter__(self):
130 def __iter__(self):
131 for f in self._files:
131 for f in self._files:
132 yield f
132 yield f
133
133
134 # Callbacks related to how the matcher is used by dirstate.walk.
134 # Callbacks related to how the matcher is used by dirstate.walk.
135 # Subscribers to these events must monkeypatch the matcher object.
135 # Subscribers to these events must monkeypatch the matcher object.
136 def bad(self, f, msg):
136 def bad(self, f, msg):
137 '''Callback from dirstate.walk for each explicit file that can't be
137 '''Callback from dirstate.walk for each explicit file that can't be
138 found/accessed, with an error message.'''
138 found/accessed, with an error message.'''
139 pass
139 pass
140
140
141 # If an explicitdir is set, it will be called when an explicitly listed
141 # If an explicitdir is set, it will be called when an explicitly listed
142 # directory is visited.
142 # directory is visited.
143 explicitdir = None
143 explicitdir = None
144
144
145 # If an traversedir is set, it will be called when a directory discovered
145 # If an traversedir is set, it will be called when a directory discovered
146 # by recursive traversal is visited.
146 # by recursive traversal is visited.
147 traversedir = None
147 traversedir = None
148
148
149 def abs(self, f):
149 def abs(self, f):
150 '''Convert a repo path back to path that is relative to the root of the
150 '''Convert a repo path back to path that is relative to the root of the
151 matcher.'''
151 matcher.'''
152 return f
152 return f
153
153
154 def rel(self, f):
154 def rel(self, f):
155 '''Convert repo path back to path that is relative to cwd of matcher.'''
155 '''Convert repo path back to path that is relative to cwd of matcher.'''
156 return util.pathto(self._root, self._cwd, f)
156 return util.pathto(self._root, self._cwd, f)
157
157
158 def uipath(self, f):
158 def uipath(self, f):
159 '''Convert repo path to a display path. If patterns or -I/-X were used
159 '''Convert repo path to a display path. If patterns or -I/-X were used
160 to create this matcher, the display path will be relative to cwd.
160 to create this matcher, the display path will be relative to cwd.
161 Otherwise it is relative to the root of the repo.'''
161 Otherwise it is relative to the root of the repo.'''
162 return (self._pathrestricted and self.rel(f)) or self.abs(f)
162 return (self._pathrestricted and self.rel(f)) or self.abs(f)
163
163
164 def files(self):
164 def files(self):
165 '''Explicitly listed files or patterns or roots:
165 '''Explicitly listed files or patterns or roots:
166 if no patterns or .always(): empty list,
166 if no patterns or .always(): empty list,
167 if exact: list exact files,
167 if exact: list exact files,
168 if not .anypats(): list all files and dirs,
168 if not .anypats(): list all files and dirs,
169 else: optimal roots'''
169 else: optimal roots'''
170 return self._files
170 return self._files
171
171
172 @propertycache
172 @propertycache
173 def _dirs(self):
173 def _dirs(self):
174 return set(util.dirs(self._fmap)) | set(['.'])
174 return set(util.dirs(self._fmap)) | set(['.'])
175
175
176 def visitdir(self, dir):
176 def visitdir(self, dir):
177 '''Helps while traversing a directory tree. Returns the string 'all' if
177 return (not self._fmap or '.' in self._fmap or
178 the given directory and all subdirectories should be visited. Otherwise
178 dir in self._fmap or dir in self._dirs or
179 returns True or False indicating whether the given directory should be
179 any(parentdir in self._fmap
180 visited. If 'all' is returned, calling this method on a subdirectory
180 for parentdir in util.finddirs(dir)))
181 gives an undefined result.'''
182 if not self._fmap or self.exact(dir):
183 return 'all'
184 return dir in self._dirs
185
181
186 def exact(self, f):
182 def exact(self, f):
187 '''Returns True if f is in .files().'''
183 '''Returns True if f is in .files().'''
188 return f in self._fmap
184 return f in self._fmap
189
185
190 def anypats(self):
186 def anypats(self):
191 '''Matcher uses patterns or include/exclude.'''
187 '''Matcher uses patterns or include/exclude.'''
192 return self._anypats
188 return self._anypats
193
189
194 def always(self):
190 def always(self):
195 '''Matcher will match everything and .files() will be empty
191 '''Matcher will match everything and .files() will be empty
196 - optimization might be possible and necessary.'''
192 - optimization might be possible and necessary.'''
197 return self._always
193 return self._always
198
194
199 def ispartial(self):
195 def ispartial(self):
200 '''True if the matcher won't always match.
196 '''True if the matcher won't always match.
201
197
202 Although it's just the inverse of _always in this implementation,
198 Although it's just the inverse of _always in this implementation,
203 an extenion such as narrowhg might make it return something
199 an extenion such as narrowhg might make it return something
204 slightly different.'''
200 slightly different.'''
205 return not self._always
201 return not self._always
206
202
207 def isexact(self):
203 def isexact(self):
208 return self.matchfn == self.exact
204 return self.matchfn == self.exact
209
205
210 def _normalize(self, patterns, default, root, cwd, auditor):
206 def _normalize(self, patterns, default, root, cwd, auditor):
211 '''Convert 'kind:pat' from the patterns list to tuples with kind and
207 '''Convert 'kind:pat' from the patterns list to tuples with kind and
212 normalized and rooted patterns and with listfiles expanded.'''
208 normalized and rooted patterns and with listfiles expanded.'''
213 kindpats = []
209 kindpats = []
214 for kind, pat in [_patsplit(p, default) for p in patterns]:
210 for kind, pat in [_patsplit(p, default) for p in patterns]:
215 if kind in ('glob', 'relpath'):
211 if kind in ('glob', 'relpath'):
216 pat = pathutil.canonpath(root, cwd, pat, auditor)
212 pat = pathutil.canonpath(root, cwd, pat, auditor)
217 elif kind in ('relglob', 'path'):
213 elif kind in ('relglob', 'path'):
218 pat = util.normpath(pat)
214 pat = util.normpath(pat)
219 elif kind in ('listfile', 'listfile0'):
215 elif kind in ('listfile', 'listfile0'):
220 try:
216 try:
221 files = util.readfile(pat)
217 files = util.readfile(pat)
222 if kind == 'listfile0':
218 if kind == 'listfile0':
223 files = files.split('\0')
219 files = files.split('\0')
224 else:
220 else:
225 files = files.splitlines()
221 files = files.splitlines()
226 files = [f for f in files if f]
222 files = [f for f in files if f]
227 except EnvironmentError:
223 except EnvironmentError:
228 raise util.Abort(_("unable to read file list (%s)") % pat)
224 raise util.Abort(_("unable to read file list (%s)") % pat)
229 kindpats += self._normalize(files, default, root, cwd, auditor)
225 kindpats += self._normalize(files, default, root, cwd, auditor)
230 continue
226 continue
231 # else: re or relre - which cannot be normalized
227 # else: re or relre - which cannot be normalized
232 kindpats.append((kind, pat))
228 kindpats.append((kind, pat))
233 return kindpats
229 return kindpats
234
230
235 def exact(root, cwd, files):
231 def exact(root, cwd, files):
236 return match(root, cwd, files, exact=True)
232 return match(root, cwd, files, exact=True)
237
233
238 def always(root, cwd):
234 def always(root, cwd):
239 return match(root, cwd, [])
235 return match(root, cwd, [])
240
236
241 class narrowmatcher(match):
237 class narrowmatcher(match):
242 """Adapt a matcher to work on a subdirectory only.
238 """Adapt a matcher to work on a subdirectory only.
243
239
244 The paths are remapped to remove/insert the path as needed:
240 The paths are remapped to remove/insert the path as needed:
245
241
246 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
242 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
247 >>> m2 = narrowmatcher('sub', m1)
243 >>> m2 = narrowmatcher('sub', m1)
248 >>> bool(m2('a.txt'))
244 >>> bool(m2('a.txt'))
249 False
245 False
250 >>> bool(m2('b.txt'))
246 >>> bool(m2('b.txt'))
251 True
247 True
252 >>> bool(m2.matchfn('a.txt'))
248 >>> bool(m2.matchfn('a.txt'))
253 False
249 False
254 >>> bool(m2.matchfn('b.txt'))
250 >>> bool(m2.matchfn('b.txt'))
255 True
251 True
256 >>> m2.files()
252 >>> m2.files()
257 ['b.txt']
253 ['b.txt']
258 >>> m2.exact('b.txt')
254 >>> m2.exact('b.txt')
259 True
255 True
260 >>> util.pconvert(m2.rel('b.txt'))
256 >>> util.pconvert(m2.rel('b.txt'))
261 'sub/b.txt'
257 'sub/b.txt'
262 >>> def bad(f, msg):
258 >>> def bad(f, msg):
263 ... print "%s: %s" % (f, msg)
259 ... print "%s: %s" % (f, msg)
264 >>> m1.bad = bad
260 >>> m1.bad = bad
265 >>> m2.bad('x.txt', 'No such file')
261 >>> m2.bad('x.txt', 'No such file')
266 sub/x.txt: No such file
262 sub/x.txt: No such file
267 >>> m2.abs('c.txt')
263 >>> m2.abs('c.txt')
268 'sub/c.txt'
264 'sub/c.txt'
269 """
265 """
270
266
271 def __init__(self, path, matcher):
267 def __init__(self, path, matcher):
272 self._root = matcher._root
268 self._root = matcher._root
273 self._cwd = matcher._cwd
269 self._cwd = matcher._cwd
274 self._path = path
270 self._path = path
275 self._matcher = matcher
271 self._matcher = matcher
276 self._always = matcher._always
272 self._always = matcher._always
277 self._pathrestricted = matcher._pathrestricted
273 self._pathrestricted = matcher._pathrestricted
278
274
279 self._files = [f[len(path) + 1:] for f in matcher._files
275 self._files = [f[len(path) + 1:] for f in matcher._files
280 if f.startswith(path + "/")]
276 if f.startswith(path + "/")]
281 self._anypats = matcher._anypats
277 self._anypats = matcher._anypats
282 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
278 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
283 self._fmap = set(self._files)
279 self._fmap = set(self._files)
284
280
285 def abs(self, f):
281 def abs(self, f):
286 return self._matcher.abs(self._path + "/" + f)
282 return self._matcher.abs(self._path + "/" + f)
287
283
288 def bad(self, f, msg):
284 def bad(self, f, msg):
289 self._matcher.bad(self._path + "/" + f, msg)
285 self._matcher.bad(self._path + "/" + f, msg)
290
286
291 def rel(self, f):
287 def rel(self, f):
292 return self._matcher.rel(self._path + "/" + f)
288 return self._matcher.rel(self._path + "/" + f)
293
289
294 class icasefsmatcher(match):
290 class icasefsmatcher(match):
295 """A matcher for wdir on case insensitive filesystems, which normalizes the
291 """A matcher for wdir on case insensitive filesystems, which normalizes the
296 given patterns to the case in the filesystem.
292 given patterns to the case in the filesystem.
297 """
293 """
298
294
299 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
295 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
300 ctx, listsubrepos=False):
296 ctx, listsubrepos=False):
301 init = super(icasefsmatcher, self).__init__
297 init = super(icasefsmatcher, self).__init__
302 self._dsnormalize = ctx.repo().dirstate.normalize
298 self._dsnormalize = ctx.repo().dirstate.normalize
303
299
304 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
300 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
305 ctx=ctx, listsubrepos=listsubrepos)
301 ctx=ctx, listsubrepos=listsubrepos)
306
302
307 # m.exact(file) must be based off of the actual user input, otherwise
303 # m.exact(file) must be based off of the actual user input, otherwise
308 # inexact case matches are treated as exact, and not noted without -v.
304 # inexact case matches are treated as exact, and not noted without -v.
309 if self._files:
305 if self._files:
310 self._fmap = set(_roots(self._kp))
306 self._fmap = set(_roots(self._kp))
311
307
312 def _normalize(self, patterns, default, root, cwd, auditor):
308 def _normalize(self, patterns, default, root, cwd, auditor):
313 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
309 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
314 root, cwd, auditor)
310 root, cwd, auditor)
315 kindpats = []
311 kindpats = []
316 for kind, pats in self._kp:
312 for kind, pats in self._kp:
317 if kind not in ('re', 'relre'): # regex can't be normalized
313 if kind not in ('re', 'relre'): # regex can't be normalized
318 pats = self._dsnormalize(pats)
314 pats = self._dsnormalize(pats)
319 kindpats.append((kind, pats))
315 kindpats.append((kind, pats))
320 return kindpats
316 return kindpats
321
317
322 def patkind(pattern, default=None):
318 def patkind(pattern, default=None):
323 '''If pattern is 'kind:pat' with a known kind, return kind.'''
319 '''If pattern is 'kind:pat' with a known kind, return kind.'''
324 return _patsplit(pattern, default)[0]
320 return _patsplit(pattern, default)[0]
325
321
326 def _patsplit(pattern, default):
322 def _patsplit(pattern, default):
327 """Split a string into the optional pattern kind prefix and the actual
323 """Split a string into the optional pattern kind prefix and the actual
328 pattern."""
324 pattern."""
329 if ':' in pattern:
325 if ':' in pattern:
330 kind, pat = pattern.split(':', 1)
326 kind, pat = pattern.split(':', 1)
331 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
327 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
332 'listfile', 'listfile0', 'set'):
328 'listfile', 'listfile0', 'set'):
333 return kind, pat
329 return kind, pat
334 return default, pattern
330 return default, pattern
335
331
336 def _globre(pat):
332 def _globre(pat):
337 r'''Convert an extended glob string to a regexp string.
333 r'''Convert an extended glob string to a regexp string.
338
334
339 >>> print _globre(r'?')
335 >>> print _globre(r'?')
340 .
336 .
341 >>> print _globre(r'*')
337 >>> print _globre(r'*')
342 [^/]*
338 [^/]*
343 >>> print _globre(r'**')
339 >>> print _globre(r'**')
344 .*
340 .*
345 >>> print _globre(r'**/a')
341 >>> print _globre(r'**/a')
346 (?:.*/)?a
342 (?:.*/)?a
347 >>> print _globre(r'a/**/b')
343 >>> print _globre(r'a/**/b')
348 a\/(?:.*/)?b
344 a\/(?:.*/)?b
349 >>> print _globre(r'[a*?!^][^b][!c]')
345 >>> print _globre(r'[a*?!^][^b][!c]')
350 [a*?!^][\^b][^c]
346 [a*?!^][\^b][^c]
351 >>> print _globre(r'{a,b}')
347 >>> print _globre(r'{a,b}')
352 (?:a|b)
348 (?:a|b)
353 >>> print _globre(r'.\*\?')
349 >>> print _globre(r'.\*\?')
354 \.\*\?
350 \.\*\?
355 '''
351 '''
356 i, n = 0, len(pat)
352 i, n = 0, len(pat)
357 res = ''
353 res = ''
358 group = 0
354 group = 0
359 escape = util.re.escape
355 escape = util.re.escape
360 def peek():
356 def peek():
361 return i < n and pat[i]
357 return i < n and pat[i]
362 while i < n:
358 while i < n:
363 c = pat[i]
359 c = pat[i]
364 i += 1
360 i += 1
365 if c not in '*?[{},\\':
361 if c not in '*?[{},\\':
366 res += escape(c)
362 res += escape(c)
367 elif c == '*':
363 elif c == '*':
368 if peek() == '*':
364 if peek() == '*':
369 i += 1
365 i += 1
370 if peek() == '/':
366 if peek() == '/':
371 i += 1
367 i += 1
372 res += '(?:.*/)?'
368 res += '(?:.*/)?'
373 else:
369 else:
374 res += '.*'
370 res += '.*'
375 else:
371 else:
376 res += '[^/]*'
372 res += '[^/]*'
377 elif c == '?':
373 elif c == '?':
378 res += '.'
374 res += '.'
379 elif c == '[':
375 elif c == '[':
380 j = i
376 j = i
381 if j < n and pat[j] in '!]':
377 if j < n and pat[j] in '!]':
382 j += 1
378 j += 1
383 while j < n and pat[j] != ']':
379 while j < n and pat[j] != ']':
384 j += 1
380 j += 1
385 if j >= n:
381 if j >= n:
386 res += '\\['
382 res += '\\['
387 else:
383 else:
388 stuff = pat[i:j].replace('\\','\\\\')
384 stuff = pat[i:j].replace('\\','\\\\')
389 i = j + 1
385 i = j + 1
390 if stuff[0] == '!':
386 if stuff[0] == '!':
391 stuff = '^' + stuff[1:]
387 stuff = '^' + stuff[1:]
392 elif stuff[0] == '^':
388 elif stuff[0] == '^':
393 stuff = '\\' + stuff
389 stuff = '\\' + stuff
394 res = '%s[%s]' % (res, stuff)
390 res = '%s[%s]' % (res, stuff)
395 elif c == '{':
391 elif c == '{':
396 group += 1
392 group += 1
397 res += '(?:'
393 res += '(?:'
398 elif c == '}' and group:
394 elif c == '}' and group:
399 res += ')'
395 res += ')'
400 group -= 1
396 group -= 1
401 elif c == ',' and group:
397 elif c == ',' and group:
402 res += '|'
398 res += '|'
403 elif c == '\\':
399 elif c == '\\':
404 p = peek()
400 p = peek()
405 if p:
401 if p:
406 i += 1
402 i += 1
407 res += escape(p)
403 res += escape(p)
408 else:
404 else:
409 res += escape(c)
405 res += escape(c)
410 else:
406 else:
411 res += escape(c)
407 res += escape(c)
412 return res
408 return res
413
409
414 def _regex(kind, pat, globsuffix):
410 def _regex(kind, pat, globsuffix):
415 '''Convert a (normalized) pattern of any kind into a regular expression.
411 '''Convert a (normalized) pattern of any kind into a regular expression.
416 globsuffix is appended to the regexp of globs.'''
412 globsuffix is appended to the regexp of globs.'''
417 if not pat:
413 if not pat:
418 return ''
414 return ''
419 if kind == 're':
415 if kind == 're':
420 return pat
416 return pat
421 if kind == 'path':
417 if kind == 'path':
422 return '^' + util.re.escape(pat) + '(?:/|$)'
418 return '^' + util.re.escape(pat) + '(?:/|$)'
423 if kind == 'relglob':
419 if kind == 'relglob':
424 return '(?:|.*/)' + _globre(pat) + globsuffix
420 return '(?:|.*/)' + _globre(pat) + globsuffix
425 if kind == 'relpath':
421 if kind == 'relpath':
426 return util.re.escape(pat) + '(?:/|$)'
422 return util.re.escape(pat) + '(?:/|$)'
427 if kind == 'relre':
423 if kind == 'relre':
428 if pat.startswith('^'):
424 if pat.startswith('^'):
429 return pat
425 return pat
430 return '.*' + pat
426 return '.*' + pat
431 return _globre(pat) + globsuffix
427 return _globre(pat) + globsuffix
432
428
433 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
429 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
434 '''Return regexp string and a matcher function for kindpats.
430 '''Return regexp string and a matcher function for kindpats.
435 globsuffix is appended to the regexp of globs.'''
431 globsuffix is appended to the regexp of globs.'''
436 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
432 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
437 if not kindpats:
433 if not kindpats:
438 return "", fset.__contains__
434 return "", fset.__contains__
439
435
440 regex, mf = _buildregexmatch(kindpats, globsuffix)
436 regex, mf = _buildregexmatch(kindpats, globsuffix)
441 if fset:
437 if fset:
442 return regex, lambda f: f in fset or mf(f)
438 return regex, lambda f: f in fset or mf(f)
443 return regex, mf
439 return regex, mf
444
440
445 def _buildregexmatch(kindpats, globsuffix):
441 def _buildregexmatch(kindpats, globsuffix):
446 """Build a match function from a list of kinds and kindpats,
442 """Build a match function from a list of kinds and kindpats,
447 return regexp string and a matcher function."""
443 return regexp string and a matcher function."""
448 try:
444 try:
449 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
445 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
450 for (k, p) in kindpats])
446 for (k, p) in kindpats])
451 if len(regex) > 20000:
447 if len(regex) > 20000:
452 raise OverflowError
448 raise OverflowError
453 return regex, _rematcher(regex)
449 return regex, _rematcher(regex)
454 except OverflowError:
450 except OverflowError:
455 # We're using a Python with a tiny regex engine and we
451 # We're using a Python with a tiny regex engine and we
456 # made it explode, so we'll divide the pattern list in two
452 # made it explode, so we'll divide the pattern list in two
457 # until it works
453 # until it works
458 l = len(kindpats)
454 l = len(kindpats)
459 if l < 2:
455 if l < 2:
460 raise
456 raise
461 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
457 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
462 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
458 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
463 return regex, lambda s: a(s) or b(s)
459 return regex, lambda s: a(s) or b(s)
464 except re.error:
460 except re.error:
465 for k, p in kindpats:
461 for k, p in kindpats:
466 try:
462 try:
467 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
463 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
468 except re.error:
464 except re.error:
469 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
465 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
470 raise util.Abort(_("invalid pattern"))
466 raise util.Abort(_("invalid pattern"))
471
467
472 def _roots(kindpats):
468 def _roots(kindpats):
473 '''return roots and exact explicitly listed files from patterns
469 '''return roots and exact explicitly listed files from patterns
474
470
475 >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')])
471 >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')])
476 ['g', 'g', '.']
472 ['g', 'g', '.']
477 >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')])
473 >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')])
478 ['r', 'p/p', '.']
474 ['r', 'p/p', '.']
479 >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')])
475 >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')])
480 ['.', '.', '.']
476 ['.', '.', '.']
481 '''
477 '''
482 r = []
478 r = []
483 for kind, pat in kindpats:
479 for kind, pat in kindpats:
484 if kind == 'glob': # find the non-glob prefix
480 if kind == 'glob': # find the non-glob prefix
485 root = []
481 root = []
486 for p in pat.split('/'):
482 for p in pat.split('/'):
487 if '[' in p or '{' in p or '*' in p or '?' in p:
483 if '[' in p or '{' in p or '*' in p or '?' in p:
488 break
484 break
489 root.append(p)
485 root.append(p)
490 r.append('/'.join(root) or '.')
486 r.append('/'.join(root) or '.')
491 elif kind in ('relpath', 'path'):
487 elif kind in ('relpath', 'path'):
492 r.append(pat or '.')
488 r.append(pat or '.')
493 else: # relglob, re, relre
489 else: # relglob, re, relre
494 r.append('.')
490 r.append('.')
495 return r
491 return r
496
492
497 def _anypats(kindpats):
493 def _anypats(kindpats):
498 for kind, pat in kindpats:
494 for kind, pat in kindpats:
499 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
495 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
500 return True
496 return True
501
497
502 _commentre = None
498 _commentre = None
503
499
504 def readpatternfile(filepath, warn):
500 def readpatternfile(filepath, warn):
505 '''parse a pattern file, returning a list of
501 '''parse a pattern file, returning a list of
506 patterns. These patterns should be given to compile()
502 patterns. These patterns should be given to compile()
507 to be validated and converted into a match function.'''
503 to be validated and converted into a match function.'''
508 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
504 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
509 syntax = 'relre:'
505 syntax = 'relre:'
510 patterns = []
506 patterns = []
511
507
512 fp = open(filepath)
508 fp = open(filepath)
513 for line in fp:
509 for line in fp:
514 if "#" in line:
510 if "#" in line:
515 global _commentre
511 global _commentre
516 if not _commentre:
512 if not _commentre:
517 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
513 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
518 # remove comments prefixed by an even number of escapes
514 # remove comments prefixed by an even number of escapes
519 line = _commentre.sub(r'\1', line)
515 line = _commentre.sub(r'\1', line)
520 # fixup properly escaped comments that survived the above
516 # fixup properly escaped comments that survived the above
521 line = line.replace("\\#", "#")
517 line = line.replace("\\#", "#")
522 line = line.rstrip()
518 line = line.rstrip()
523 if not line:
519 if not line:
524 continue
520 continue
525
521
526 if line.startswith('syntax:'):
522 if line.startswith('syntax:'):
527 s = line[7:].strip()
523 s = line[7:].strip()
528 try:
524 try:
529 syntax = syntaxes[s]
525 syntax = syntaxes[s]
530 except KeyError:
526 except KeyError:
531 warn(_("%s: ignoring invalid syntax '%s'\n") % (filepath, s))
527 warn(_("%s: ignoring invalid syntax '%s'\n") % (filepath, s))
532 continue
528 continue
533
529
534 linesyntax = syntax
530 linesyntax = syntax
535 for s, rels in syntaxes.iteritems():
531 for s, rels in syntaxes.iteritems():
536 if line.startswith(rels):
532 if line.startswith(rels):
537 linesyntax = rels
533 linesyntax = rels
538 line = line[len(rels):]
534 line = line[len(rels):]
539 break
535 break
540 elif line.startswith(s+':'):
536 elif line.startswith(s+':'):
541 linesyntax = rels
537 linesyntax = rels
542 line = line[len(s) + 1:]
538 line = line[len(s) + 1:]
543 break
539 break
544 patterns.append(linesyntax + line)
540 patterns.append(linesyntax + line)
545 fp.close()
541 fp.close()
546 return patterns
542 return patterns
General Comments 0
You need to be logged in to leave comments. Login now