##// END OF EJS Templates
manifestv2: add support for reading new manifest format...
Martin von Zweigbergk -
r24572:b83679eb default
parent child Browse files
Show More
@@ -1,697 +1,730 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import mdiff, parsers, error, revlog, util, scmutil
9 import mdiff, parsers, error, revlog, util, scmutil
10 import array, struct
10 import array, struct
11
11
12 propertycache = util.propertycache
12 propertycache = util.propertycache
13
13
14 def _parse(data):
14 def _parsev1(data):
15 """Generates (path, node, flags) tuples from a manifest text"""
16 # This method does a little bit of excessive-looking
15 # This method does a little bit of excessive-looking
17 # precondition checking. This is so that the behavior of this
16 # precondition checking. This is so that the behavior of this
18 # class exactly matches its C counterpart to try and help
17 # class exactly matches its C counterpart to try and help
19 # prevent surprise breakage for anyone that develops against
18 # prevent surprise breakage for anyone that develops against
20 # the pure version.
19 # the pure version.
21 if data and data[-1] != '\n':
20 if data and data[-1] != '\n':
22 raise ValueError('Manifest did not end in a newline.')
21 raise ValueError('Manifest did not end in a newline.')
23 prev = None
22 prev = None
24 for l in data.splitlines():
23 for l in data.splitlines():
25 if prev is not None and prev > l:
24 if prev is not None and prev > l:
26 raise ValueError('Manifest lines not in sorted order.')
25 raise ValueError('Manifest lines not in sorted order.')
27 prev = l
26 prev = l
28 f, n = l.split('\0')
27 f, n = l.split('\0')
29 if len(n) > 40:
28 if len(n) > 40:
30 yield f, revlog.bin(n[:40]), n[40:]
29 yield f, revlog.bin(n[:40]), n[40:]
31 else:
30 else:
32 yield f, revlog.bin(n), ''
31 yield f, revlog.bin(n), ''
33
32
33 def _parsev2(data):
34 metadataend = data.find('\n')
35 # Just ignore metadata for now
36 pos = metadataend + 1
37 prevf = ''
38 while pos < len(data):
39 end = data.find('\n', pos + 1) # +1 to skip stem length byte
40 if end == -1:
41 raise ValueError('Manifest ended with incomplete file entry.')
42 stemlen = ord(data[pos])
43 items = data[pos + 1:end].split('\0')
44 f = prevf[:stemlen] + items[0]
45 if prevf > f:
46 raise ValueError('Manifest entries not in sorted order.')
47 fl = items[1]
48 # Just ignore metadata (items[2:] for now)
49 n = data[end + 1:end + 21]
50 yield f, n, fl
51 pos = end + 22
52 prevf = f
53
54 def _parse(data):
55 """Generates (path, node, flags) tuples from a manifest text"""
56 if data.startswith('\0'):
57 return iter(_parsev2(data))
58 else:
59 return iter(_parsev1(data))
60
34 def _text(it):
61 def _text(it):
35 """Given an iterator over (path, node, flags) tuples, returns a manifest
62 """Given an iterator over (path, node, flags) tuples, returns a manifest
36 text"""
63 text"""
37 files = []
64 files = []
38 lines = []
65 lines = []
39 _hex = revlog.hex
66 _hex = revlog.hex
40 for f, n, fl in it:
67 for f, n, fl in it:
41 files.append(f)
68 files.append(f)
42 # if this is changed to support newlines in filenames,
69 # if this is changed to support newlines in filenames,
43 # be sure to check the templates/ dir again (especially *-raw.tmpl)
70 # be sure to check the templates/ dir again (especially *-raw.tmpl)
44 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
71 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
45
72
46 _checkforbidden(files)
73 _checkforbidden(files)
47 return ''.join(lines)
74 return ''.join(lines)
48
75
49 class _lazymanifest(dict):
76 class _lazymanifest(dict):
50 """This is the pure implementation of lazymanifest.
77 """This is the pure implementation of lazymanifest.
51
78
52 It has not been optimized *at all* and is not lazy.
79 It has not been optimized *at all* and is not lazy.
53 """
80 """
54
81
55 def __init__(self, data):
82 def __init__(self, data):
56 dict.__init__(self)
83 dict.__init__(self)
57 for f, n, fl in _parse(data):
84 for f, n, fl in _parse(data):
58 self[f] = n, fl
85 self[f] = n, fl
59
86
60 def __setitem__(self, k, v):
87 def __setitem__(self, k, v):
61 node, flag = v
88 node, flag = v
62 assert node is not None
89 assert node is not None
63 if len(node) > 21:
90 if len(node) > 21:
64 node = node[:21] # match c implementation behavior
91 node = node[:21] # match c implementation behavior
65 dict.__setitem__(self, k, (node, flag))
92 dict.__setitem__(self, k, (node, flag))
66
93
67 def __iter__(self):
94 def __iter__(self):
68 return iter(sorted(dict.keys(self)))
95 return iter(sorted(dict.keys(self)))
69
96
70 def iterkeys(self):
97 def iterkeys(self):
71 return iter(sorted(dict.keys(self)))
98 return iter(sorted(dict.keys(self)))
72
99
73 def iterentries(self):
100 def iterentries(self):
74 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
101 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
75
102
76 def copy(self):
103 def copy(self):
77 c = _lazymanifest('')
104 c = _lazymanifest('')
78 c.update(self)
105 c.update(self)
79 return c
106 return c
80
107
81 def diff(self, m2, clean=False):
108 def diff(self, m2, clean=False):
82 '''Finds changes between the current manifest and m2.'''
109 '''Finds changes between the current manifest and m2.'''
83 diff = {}
110 diff = {}
84
111
85 for fn, e1 in self.iteritems():
112 for fn, e1 in self.iteritems():
86 if fn not in m2:
113 if fn not in m2:
87 diff[fn] = e1, (None, '')
114 diff[fn] = e1, (None, '')
88 else:
115 else:
89 e2 = m2[fn]
116 e2 = m2[fn]
90 if e1 != e2:
117 if e1 != e2:
91 diff[fn] = e1, e2
118 diff[fn] = e1, e2
92 elif clean:
119 elif clean:
93 diff[fn] = None
120 diff[fn] = None
94
121
95 for fn, e2 in m2.iteritems():
122 for fn, e2 in m2.iteritems():
96 if fn not in self:
123 if fn not in self:
97 diff[fn] = (None, ''), e2
124 diff[fn] = (None, ''), e2
98
125
99 return diff
126 return diff
100
127
101 def filtercopy(self, filterfn):
128 def filtercopy(self, filterfn):
102 c = _lazymanifest('')
129 c = _lazymanifest('')
103 for f, n, fl in self.iterentries():
130 for f, n, fl in self.iterentries():
104 if filterfn(f):
131 if filterfn(f):
105 c[f] = n, fl
132 c[f] = n, fl
106 return c
133 return c
107
134
108 def text(self):
135 def text(self):
109 """Get the full data of this manifest as a bytestring."""
136 """Get the full data of this manifest as a bytestring."""
110 return _text(self.iterentries())
137 return _text(self.iterentries())
111
138
112 try:
139 try:
113 _lazymanifest = parsers.lazymanifest
140 _lazymanifest = parsers.lazymanifest
114 except AttributeError:
141 except AttributeError:
115 pass
142 pass
116
143
117 class manifestdict(object):
144 class manifestdict(object):
118 def __init__(self, data=''):
145 def __init__(self, data=''):
119 self._lm = _lazymanifest(data)
146 if data.startswith('\0'):
147 #_lazymanifest can not parse v2
148 self._lm = _lazymanifest('')
149 for f, n, fl in _parsev2(data):
150 self._lm[f] = n, fl
151 else:
152 self._lm = _lazymanifest(data)
120
153
121 def __getitem__(self, key):
154 def __getitem__(self, key):
122 return self._lm[key][0]
155 return self._lm[key][0]
123
156
124 def find(self, key):
157 def find(self, key):
125 return self._lm[key]
158 return self._lm[key]
126
159
127 def __len__(self):
160 def __len__(self):
128 return len(self._lm)
161 return len(self._lm)
129
162
130 def __setitem__(self, key, node):
163 def __setitem__(self, key, node):
131 self._lm[key] = node, self.flags(key, '')
164 self._lm[key] = node, self.flags(key, '')
132
165
133 def __contains__(self, key):
166 def __contains__(self, key):
134 return key in self._lm
167 return key in self._lm
135
168
136 def __delitem__(self, key):
169 def __delitem__(self, key):
137 del self._lm[key]
170 del self._lm[key]
138
171
139 def __iter__(self):
172 def __iter__(self):
140 return self._lm.__iter__()
173 return self._lm.__iter__()
141
174
142 def iterkeys(self):
175 def iterkeys(self):
143 return self._lm.iterkeys()
176 return self._lm.iterkeys()
144
177
145 def keys(self):
178 def keys(self):
146 return list(self.iterkeys())
179 return list(self.iterkeys())
147
180
148 def _intersectfiles(self, files):
181 def _intersectfiles(self, files):
149 '''make a new lazymanifest with the intersection of self with files
182 '''make a new lazymanifest with the intersection of self with files
150
183
151 The algorithm assumes that files is much smaller than self.'''
184 The algorithm assumes that files is much smaller than self.'''
152 ret = manifestdict()
185 ret = manifestdict()
153 lm = self._lm
186 lm = self._lm
154 for fn in files:
187 for fn in files:
155 if fn in lm:
188 if fn in lm:
156 ret._lm[fn] = self._lm[fn]
189 ret._lm[fn] = self._lm[fn]
157 return ret
190 return ret
158
191
159 def filesnotin(self, m2):
192 def filesnotin(self, m2):
160 '''Set of files in this manifest that are not in the other'''
193 '''Set of files in this manifest that are not in the other'''
161 files = set(self)
194 files = set(self)
162 files.difference_update(m2)
195 files.difference_update(m2)
163 return files
196 return files
164
197
165 @propertycache
198 @propertycache
166 def _dirs(self):
199 def _dirs(self):
167 return scmutil.dirs(self)
200 return scmutil.dirs(self)
168
201
169 def dirs(self):
202 def dirs(self):
170 return self._dirs
203 return self._dirs
171
204
172 def hasdir(self, dir):
205 def hasdir(self, dir):
173 return dir in self._dirs
206 return dir in self._dirs
174
207
175 def matches(self, match):
208 def matches(self, match):
176 '''generate a new manifest filtered by the match argument'''
209 '''generate a new manifest filtered by the match argument'''
177 if match.always():
210 if match.always():
178 return self.copy()
211 return self.copy()
179
212
180 files = match.files()
213 files = match.files()
181 if (len(files) < 100 and (match.isexact() or
214 if (len(files) < 100 and (match.isexact() or
182 (not match.anypats() and util.all(fn in self for fn in files)))):
215 (not match.anypats() and util.all(fn in self for fn in files)))):
183 return self._intersectfiles(files)
216 return self._intersectfiles(files)
184
217
185 lm = manifestdict('')
218 lm = manifestdict('')
186 lm._lm = self._lm.filtercopy(match)
219 lm._lm = self._lm.filtercopy(match)
187 return lm
220 return lm
188
221
189 def diff(self, m2, clean=False):
222 def diff(self, m2, clean=False):
190 '''Finds changes between the current manifest and m2.
223 '''Finds changes between the current manifest and m2.
191
224
192 Args:
225 Args:
193 m2: the manifest to which this manifest should be compared.
226 m2: the manifest to which this manifest should be compared.
194 clean: if true, include files unchanged between these manifests
227 clean: if true, include files unchanged between these manifests
195 with a None value in the returned dictionary.
228 with a None value in the returned dictionary.
196
229
197 The result is returned as a dict with filename as key and
230 The result is returned as a dict with filename as key and
198 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
231 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
199 nodeid in the current/other manifest and fl1/fl2 is the flag
232 nodeid in the current/other manifest and fl1/fl2 is the flag
200 in the current/other manifest. Where the file does not exist,
233 in the current/other manifest. Where the file does not exist,
201 the nodeid will be None and the flags will be the empty
234 the nodeid will be None and the flags will be the empty
202 string.
235 string.
203 '''
236 '''
204 return self._lm.diff(m2._lm, clean)
237 return self._lm.diff(m2._lm, clean)
205
238
206 def setflag(self, key, flag):
239 def setflag(self, key, flag):
207 self._lm[key] = self[key], flag
240 self._lm[key] = self[key], flag
208
241
209 def get(self, key, default=None):
242 def get(self, key, default=None):
210 try:
243 try:
211 return self._lm[key][0]
244 return self._lm[key][0]
212 except KeyError:
245 except KeyError:
213 return default
246 return default
214
247
215 def flags(self, key, default=''):
248 def flags(self, key, default=''):
216 try:
249 try:
217 return self._lm[key][1]
250 return self._lm[key][1]
218 except KeyError:
251 except KeyError:
219 return default
252 return default
220
253
221 def copy(self):
254 def copy(self):
222 c = manifestdict('')
255 c = manifestdict('')
223 c._lm = self._lm.copy()
256 c._lm = self._lm.copy()
224 return c
257 return c
225
258
226 def iteritems(self):
259 def iteritems(self):
227 return (x[:2] for x in self._lm.iterentries())
260 return (x[:2] for x in self._lm.iterentries())
228
261
229 def text(self):
262 def text(self):
230 return self._lm.text()
263 return self._lm.text()
231
264
232 def fastdelta(self, base, changes):
265 def fastdelta(self, base, changes):
233 """Given a base manifest text as an array.array and a list of changes
266 """Given a base manifest text as an array.array and a list of changes
234 relative to that text, compute a delta that can be used by revlog.
267 relative to that text, compute a delta that can be used by revlog.
235 """
268 """
236 delta = []
269 delta = []
237 dstart = None
270 dstart = None
238 dend = None
271 dend = None
239 dline = [""]
272 dline = [""]
240 start = 0
273 start = 0
241 # zero copy representation of base as a buffer
274 # zero copy representation of base as a buffer
242 addbuf = util.buffer(base)
275 addbuf = util.buffer(base)
243
276
244 # start with a readonly loop that finds the offset of
277 # start with a readonly loop that finds the offset of
245 # each line and creates the deltas
278 # each line and creates the deltas
246 for f, todelete in changes:
279 for f, todelete in changes:
247 # bs will either be the index of the item or the insert point
280 # bs will either be the index of the item or the insert point
248 start, end = _msearch(addbuf, f, start)
281 start, end = _msearch(addbuf, f, start)
249 if not todelete:
282 if not todelete:
250 h, fl = self._lm[f]
283 h, fl = self._lm[f]
251 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
284 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
252 else:
285 else:
253 if start == end:
286 if start == end:
254 # item we want to delete was not found, error out
287 # item we want to delete was not found, error out
255 raise AssertionError(
288 raise AssertionError(
256 _("failed to remove %s from manifest") % f)
289 _("failed to remove %s from manifest") % f)
257 l = ""
290 l = ""
258 if dstart is not None and dstart <= start and dend >= start:
291 if dstart is not None and dstart <= start and dend >= start:
259 if dend < end:
292 if dend < end:
260 dend = end
293 dend = end
261 if l:
294 if l:
262 dline.append(l)
295 dline.append(l)
263 else:
296 else:
264 if dstart is not None:
297 if dstart is not None:
265 delta.append([dstart, dend, "".join(dline)])
298 delta.append([dstart, dend, "".join(dline)])
266 dstart = start
299 dstart = start
267 dend = end
300 dend = end
268 dline = [l]
301 dline = [l]
269
302
270 if dstart is not None:
303 if dstart is not None:
271 delta.append([dstart, dend, "".join(dline)])
304 delta.append([dstart, dend, "".join(dline)])
272 # apply the delta to the base, and get a delta for addrevision
305 # apply the delta to the base, and get a delta for addrevision
273 deltatext, arraytext = _addlistdelta(base, delta)
306 deltatext, arraytext = _addlistdelta(base, delta)
274 return arraytext, deltatext
307 return arraytext, deltatext
275
308
276 def _msearch(m, s, lo=0, hi=None):
309 def _msearch(m, s, lo=0, hi=None):
277 '''return a tuple (start, end) that says where to find s within m.
310 '''return a tuple (start, end) that says where to find s within m.
278
311
279 If the string is found m[start:end] are the line containing
312 If the string is found m[start:end] are the line containing
280 that string. If start == end the string was not found and
313 that string. If start == end the string was not found and
281 they indicate the proper sorted insertion point.
314 they indicate the proper sorted insertion point.
282
315
283 m should be a buffer or a string
316 m should be a buffer or a string
284 s is a string'''
317 s is a string'''
285 def advance(i, c):
318 def advance(i, c):
286 while i < lenm and m[i] != c:
319 while i < lenm and m[i] != c:
287 i += 1
320 i += 1
288 return i
321 return i
289 if not s:
322 if not s:
290 return (lo, lo)
323 return (lo, lo)
291 lenm = len(m)
324 lenm = len(m)
292 if not hi:
325 if not hi:
293 hi = lenm
326 hi = lenm
294 while lo < hi:
327 while lo < hi:
295 mid = (lo + hi) // 2
328 mid = (lo + hi) // 2
296 start = mid
329 start = mid
297 while start > 0 and m[start - 1] != '\n':
330 while start > 0 and m[start - 1] != '\n':
298 start -= 1
331 start -= 1
299 end = advance(start, '\0')
332 end = advance(start, '\0')
300 if m[start:end] < s:
333 if m[start:end] < s:
301 # we know that after the null there are 40 bytes of sha1
334 # we know that after the null there are 40 bytes of sha1
302 # this translates to the bisect lo = mid + 1
335 # this translates to the bisect lo = mid + 1
303 lo = advance(end + 40, '\n') + 1
336 lo = advance(end + 40, '\n') + 1
304 else:
337 else:
305 # this translates to the bisect hi = mid
338 # this translates to the bisect hi = mid
306 hi = start
339 hi = start
307 end = advance(lo, '\0')
340 end = advance(lo, '\0')
308 found = m[lo:end]
341 found = m[lo:end]
309 if s == found:
342 if s == found:
310 # we know that after the null there are 40 bytes of sha1
343 # we know that after the null there are 40 bytes of sha1
311 end = advance(end + 40, '\n')
344 end = advance(end + 40, '\n')
312 return (lo, end + 1)
345 return (lo, end + 1)
313 else:
346 else:
314 return (lo, lo)
347 return (lo, lo)
315
348
316 def _checkforbidden(l):
349 def _checkforbidden(l):
317 """Check filenames for illegal characters."""
350 """Check filenames for illegal characters."""
318 for f in l:
351 for f in l:
319 if '\n' in f or '\r' in f:
352 if '\n' in f or '\r' in f:
320 raise error.RevlogError(
353 raise error.RevlogError(
321 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
354 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
322
355
323
356
324 # apply the changes collected during the bisect loop to our addlist
357 # apply the changes collected during the bisect loop to our addlist
325 # return a delta suitable for addrevision
358 # return a delta suitable for addrevision
326 def _addlistdelta(addlist, x):
359 def _addlistdelta(addlist, x):
327 # for large addlist arrays, building a new array is cheaper
360 # for large addlist arrays, building a new array is cheaper
328 # than repeatedly modifying the existing one
361 # than repeatedly modifying the existing one
329 currentposition = 0
362 currentposition = 0
330 newaddlist = array.array('c')
363 newaddlist = array.array('c')
331
364
332 for start, end, content in x:
365 for start, end, content in x:
333 newaddlist += addlist[currentposition:start]
366 newaddlist += addlist[currentposition:start]
334 if content:
367 if content:
335 newaddlist += array.array('c', content)
368 newaddlist += array.array('c', content)
336
369
337 currentposition = end
370 currentposition = end
338
371
339 newaddlist += addlist[currentposition:]
372 newaddlist += addlist[currentposition:]
340
373
341 deltatext = "".join(struct.pack(">lll", start, end, len(content))
374 deltatext = "".join(struct.pack(">lll", start, end, len(content))
342 + content for start, end, content in x)
375 + content for start, end, content in x)
343 return deltatext, newaddlist
376 return deltatext, newaddlist
344
377
345 def _splittopdir(f):
378 def _splittopdir(f):
346 if '/' in f:
379 if '/' in f:
347 dir, subpath = f.split('/', 1)
380 dir, subpath = f.split('/', 1)
348 return dir + '/', subpath
381 return dir + '/', subpath
349 else:
382 else:
350 return '', f
383 return '', f
351
384
352 class treemanifest(object):
385 class treemanifest(object):
353 def __init__(self, dir='', text=''):
386 def __init__(self, dir='', text=''):
354 self._dir = dir
387 self._dir = dir
355 self._dirs = {}
388 self._dirs = {}
356 # Using _lazymanifest here is a little slower than plain old dicts
389 # Using _lazymanifest here is a little slower than plain old dicts
357 self._files = {}
390 self._files = {}
358 self._flags = {}
391 self._flags = {}
359 for f, n, fl in _parse(text):
392 for f, n, fl in _parse(text):
360 self[f] = n
393 self[f] = n
361 if fl:
394 if fl:
362 self.setflag(f, fl)
395 self.setflag(f, fl)
363
396
364 def _subpath(self, path):
397 def _subpath(self, path):
365 return self._dir + path
398 return self._dir + path
366
399
367 def __len__(self):
400 def __len__(self):
368 size = len(self._files)
401 size = len(self._files)
369 for m in self._dirs.values():
402 for m in self._dirs.values():
370 size += m.__len__()
403 size += m.__len__()
371 return size
404 return size
372
405
373 def _isempty(self):
406 def _isempty(self):
374 return (not self._files and (not self._dirs or
407 return (not self._files and (not self._dirs or
375 util.all(m._isempty() for m in self._dirs.values())))
408 util.all(m._isempty() for m in self._dirs.values())))
376
409
377 def __str__(self):
410 def __str__(self):
378 return '<treemanifest dir=%s>' % self._dir
411 return '<treemanifest dir=%s>' % self._dir
379
412
380 def iteritems(self):
413 def iteritems(self):
381 for p, n in sorted(self._dirs.items() + self._files.items()):
414 for p, n in sorted(self._dirs.items() + self._files.items()):
382 if p in self._files:
415 if p in self._files:
383 yield self._subpath(p), n
416 yield self._subpath(p), n
384 else:
417 else:
385 for f, sn in n.iteritems():
418 for f, sn in n.iteritems():
386 yield f, sn
419 yield f, sn
387
420
388 def iterkeys(self):
421 def iterkeys(self):
389 for p in sorted(self._dirs.keys() + self._files.keys()):
422 for p in sorted(self._dirs.keys() + self._files.keys()):
390 if p in self._files:
423 if p in self._files:
391 yield self._subpath(p)
424 yield self._subpath(p)
392 else:
425 else:
393 for f in self._dirs[p].iterkeys():
426 for f in self._dirs[p].iterkeys():
394 yield f
427 yield f
395
428
396 def keys(self):
429 def keys(self):
397 return list(self.iterkeys())
430 return list(self.iterkeys())
398
431
399 def __iter__(self):
432 def __iter__(self):
400 return self.iterkeys()
433 return self.iterkeys()
401
434
402 def __contains__(self, f):
435 def __contains__(self, f):
403 if f is None:
436 if f is None:
404 return False
437 return False
405 dir, subpath = _splittopdir(f)
438 dir, subpath = _splittopdir(f)
406 if dir:
439 if dir:
407 if dir not in self._dirs:
440 if dir not in self._dirs:
408 return False
441 return False
409 return self._dirs[dir].__contains__(subpath)
442 return self._dirs[dir].__contains__(subpath)
410 else:
443 else:
411 return f in self._files
444 return f in self._files
412
445
413 def get(self, f, default=None):
446 def get(self, f, default=None):
414 dir, subpath = _splittopdir(f)
447 dir, subpath = _splittopdir(f)
415 if dir:
448 if dir:
416 if dir not in self._dirs:
449 if dir not in self._dirs:
417 return default
450 return default
418 return self._dirs[dir].get(subpath, default)
451 return self._dirs[dir].get(subpath, default)
419 else:
452 else:
420 return self._files.get(f, default)
453 return self._files.get(f, default)
421
454
422 def __getitem__(self, f):
455 def __getitem__(self, f):
423 dir, subpath = _splittopdir(f)
456 dir, subpath = _splittopdir(f)
424 if dir:
457 if dir:
425 return self._dirs[dir].__getitem__(subpath)
458 return self._dirs[dir].__getitem__(subpath)
426 else:
459 else:
427 return self._files[f]
460 return self._files[f]
428
461
429 def flags(self, f):
462 def flags(self, f):
430 dir, subpath = _splittopdir(f)
463 dir, subpath = _splittopdir(f)
431 if dir:
464 if dir:
432 if dir not in self._dirs:
465 if dir not in self._dirs:
433 return ''
466 return ''
434 return self._dirs[dir].flags(subpath)
467 return self._dirs[dir].flags(subpath)
435 else:
468 else:
436 if f in self._dirs:
469 if f in self._dirs:
437 return ''
470 return ''
438 return self._flags.get(f, '')
471 return self._flags.get(f, '')
439
472
440 def find(self, f):
473 def find(self, f):
441 dir, subpath = _splittopdir(f)
474 dir, subpath = _splittopdir(f)
442 if dir:
475 if dir:
443 return self._dirs[dir].find(subpath)
476 return self._dirs[dir].find(subpath)
444 else:
477 else:
445 return self._files[f], self._flags.get(f, '')
478 return self._files[f], self._flags.get(f, '')
446
479
447 def __delitem__(self, f):
480 def __delitem__(self, f):
448 dir, subpath = _splittopdir(f)
481 dir, subpath = _splittopdir(f)
449 if dir:
482 if dir:
450 self._dirs[dir].__delitem__(subpath)
483 self._dirs[dir].__delitem__(subpath)
451 # If the directory is now empty, remove it
484 # If the directory is now empty, remove it
452 if self._dirs[dir]._isempty():
485 if self._dirs[dir]._isempty():
453 del self._dirs[dir]
486 del self._dirs[dir]
454 else:
487 else:
455 del self._files[f]
488 del self._files[f]
456 if f in self._flags:
489 if f in self._flags:
457 del self._flags[f]
490 del self._flags[f]
458
491
459 def __setitem__(self, f, n):
492 def __setitem__(self, f, n):
460 assert n is not None
493 assert n is not None
461 dir, subpath = _splittopdir(f)
494 dir, subpath = _splittopdir(f)
462 if dir:
495 if dir:
463 if dir not in self._dirs:
496 if dir not in self._dirs:
464 self._dirs[dir] = treemanifest(self._subpath(dir))
497 self._dirs[dir] = treemanifest(self._subpath(dir))
465 self._dirs[dir].__setitem__(subpath, n)
498 self._dirs[dir].__setitem__(subpath, n)
466 else:
499 else:
467 self._files[f] = n[:21] # to match manifestdict's behavior
500 self._files[f] = n[:21] # to match manifestdict's behavior
468
501
469 def setflag(self, f, flags):
502 def setflag(self, f, flags):
470 """Set the flags (symlink, executable) for path f."""
503 """Set the flags (symlink, executable) for path f."""
471 dir, subpath = _splittopdir(f)
504 dir, subpath = _splittopdir(f)
472 if dir:
505 if dir:
473 if dir not in self._dirs:
506 if dir not in self._dirs:
474 self._dirs[dir] = treemanifest(self._subpath(dir))
507 self._dirs[dir] = treemanifest(self._subpath(dir))
475 self._dirs[dir].setflag(subpath, flags)
508 self._dirs[dir].setflag(subpath, flags)
476 else:
509 else:
477 self._flags[f] = flags
510 self._flags[f] = flags
478
511
479 def copy(self):
512 def copy(self):
480 copy = treemanifest(self._dir)
513 copy = treemanifest(self._dir)
481 for d in self._dirs:
514 for d in self._dirs:
482 copy._dirs[d] = self._dirs[d].copy()
515 copy._dirs[d] = self._dirs[d].copy()
483 copy._files = dict.copy(self._files)
516 copy._files = dict.copy(self._files)
484 copy._flags = dict.copy(self._flags)
517 copy._flags = dict.copy(self._flags)
485 return copy
518 return copy
486
519
487 def filesnotin(self, m2):
520 def filesnotin(self, m2):
488 '''Set of files in this manifest that are not in the other'''
521 '''Set of files in this manifest that are not in the other'''
489 files = set()
522 files = set()
490 def _filesnotin(t1, t2):
523 def _filesnotin(t1, t2):
491 for d, m1 in t1._dirs.iteritems():
524 for d, m1 in t1._dirs.iteritems():
492 if d in t2._dirs:
525 if d in t2._dirs:
493 m2 = t2._dirs[d]
526 m2 = t2._dirs[d]
494 _filesnotin(m1, m2)
527 _filesnotin(m1, m2)
495 else:
528 else:
496 files.update(m1.iterkeys())
529 files.update(m1.iterkeys())
497
530
498 for fn in t1._files.iterkeys():
531 for fn in t1._files.iterkeys():
499 if fn not in t2._files:
532 if fn not in t2._files:
500 files.add(t1._subpath(fn))
533 files.add(t1._subpath(fn))
501
534
502 _filesnotin(self, m2)
535 _filesnotin(self, m2)
503 return files
536 return files
504
537
505 @propertycache
538 @propertycache
506 def _alldirs(self):
539 def _alldirs(self):
507 return scmutil.dirs(self)
540 return scmutil.dirs(self)
508
541
509 def dirs(self):
542 def dirs(self):
510 return self._alldirs
543 return self._alldirs
511
544
512 def hasdir(self, dir):
545 def hasdir(self, dir):
513 topdir, subdir = _splittopdir(dir)
546 topdir, subdir = _splittopdir(dir)
514 if topdir:
547 if topdir:
515 if topdir in self._dirs:
548 if topdir in self._dirs:
516 return self._dirs[topdir].hasdir(subdir)
549 return self._dirs[topdir].hasdir(subdir)
517 return False
550 return False
518 return (dir + '/') in self._dirs
551 return (dir + '/') in self._dirs
519
552
520 def matches(self, match):
553 def matches(self, match):
521 '''generate a new manifest filtered by the match argument'''
554 '''generate a new manifest filtered by the match argument'''
522 if match.always():
555 if match.always():
523 return self.copy()
556 return self.copy()
524
557
525 return self._matches(match)
558 return self._matches(match)
526
559
527 def _matches(self, match):
560 def _matches(self, match):
528 '''recursively generate a new manifest filtered by the match argument.
561 '''recursively generate a new manifest filtered by the match argument.
529 '''
562 '''
530
563
531 ret = treemanifest(self._dir)
564 ret = treemanifest(self._dir)
532
565
533 for fn in self._files:
566 for fn in self._files:
534 fullp = self._subpath(fn)
567 fullp = self._subpath(fn)
535 if not match(fullp):
568 if not match(fullp):
536 continue
569 continue
537 ret._files[fn] = self._files[fn]
570 ret._files[fn] = self._files[fn]
538 if fn in self._flags:
571 if fn in self._flags:
539 ret._flags[fn] = self._flags[fn]
572 ret._flags[fn] = self._flags[fn]
540
573
541 for dir, subm in self._dirs.iteritems():
574 for dir, subm in self._dirs.iteritems():
542 m = subm._matches(match)
575 m = subm._matches(match)
543 if not m._isempty():
576 if not m._isempty():
544 ret._dirs[dir] = m
577 ret._dirs[dir] = m
545
578
546 return ret
579 return ret
547
580
548 def diff(self, m2, clean=False):
581 def diff(self, m2, clean=False):
549 '''Finds changes between the current manifest and m2.
582 '''Finds changes between the current manifest and m2.
550
583
551 Args:
584 Args:
552 m2: the manifest to which this manifest should be compared.
585 m2: the manifest to which this manifest should be compared.
553 clean: if true, include files unchanged between these manifests
586 clean: if true, include files unchanged between these manifests
554 with a None value in the returned dictionary.
587 with a None value in the returned dictionary.
555
588
556 The result is returned as a dict with filename as key and
589 The result is returned as a dict with filename as key and
557 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
590 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
558 nodeid in the current/other manifest and fl1/fl2 is the flag
591 nodeid in the current/other manifest and fl1/fl2 is the flag
559 in the current/other manifest. Where the file does not exist,
592 in the current/other manifest. Where the file does not exist,
560 the nodeid will be None and the flags will be the empty
593 the nodeid will be None and the flags will be the empty
561 string.
594 string.
562 '''
595 '''
563 result = {}
596 result = {}
564 emptytree = treemanifest()
597 emptytree = treemanifest()
565 def _diff(t1, t2):
598 def _diff(t1, t2):
566 for d, m1 in t1._dirs.iteritems():
599 for d, m1 in t1._dirs.iteritems():
567 m2 = t2._dirs.get(d, emptytree)
600 m2 = t2._dirs.get(d, emptytree)
568 _diff(m1, m2)
601 _diff(m1, m2)
569
602
570 for d, m2 in t2._dirs.iteritems():
603 for d, m2 in t2._dirs.iteritems():
571 if d not in t1._dirs:
604 if d not in t1._dirs:
572 _diff(emptytree, m2)
605 _diff(emptytree, m2)
573
606
574 for fn, n1 in t1._files.iteritems():
607 for fn, n1 in t1._files.iteritems():
575 fl1 = t1._flags.get(fn, '')
608 fl1 = t1._flags.get(fn, '')
576 n2 = t2._files.get(fn, None)
609 n2 = t2._files.get(fn, None)
577 fl2 = t2._flags.get(fn, '')
610 fl2 = t2._flags.get(fn, '')
578 if n1 != n2 or fl1 != fl2:
611 if n1 != n2 or fl1 != fl2:
579 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
612 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
580 elif clean:
613 elif clean:
581 result[t1._subpath(fn)] = None
614 result[t1._subpath(fn)] = None
582
615
583 for fn, n2 in t2._files.iteritems():
616 for fn, n2 in t2._files.iteritems():
584 if fn not in t1._files:
617 if fn not in t1._files:
585 fl2 = t2._flags.get(fn, '')
618 fl2 = t2._flags.get(fn, '')
586 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
619 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
587
620
588 _diff(self, m2)
621 _diff(self, m2)
589 return result
622 return result
590
623
591 def text(self):
624 def text(self):
592 """Get the full data of this manifest as a bytestring."""
625 """Get the full data of this manifest as a bytestring."""
593 flags = self.flags
626 flags = self.flags
594 return _text((f, self[f], flags(f)) for f in self.keys())
627 return _text((f, self[f], flags(f)) for f in self.keys())
595
628
596 class manifest(revlog.revlog):
629 class manifest(revlog.revlog):
597 def __init__(self, opener):
630 def __init__(self, opener):
598 # During normal operations, we expect to deal with not more than four
631 # During normal operations, we expect to deal with not more than four
599 # revs at a time (such as during commit --amend). When rebasing large
632 # revs at a time (such as during commit --amend). When rebasing large
600 # stacks of commits, the number can go up, hence the config knob below.
633 # stacks of commits, the number can go up, hence the config knob below.
601 cachesize = 4
634 cachesize = 4
602 usetreemanifest = False
635 usetreemanifest = False
603 usemanifestv2 = False
636 usemanifestv2 = False
604 opts = getattr(opener, 'options', None)
637 opts = getattr(opener, 'options', None)
605 if opts is not None:
638 if opts is not None:
606 cachesize = opts.get('manifestcachesize', cachesize)
639 cachesize = opts.get('manifestcachesize', cachesize)
607 usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
640 usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
608 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
641 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
609 self._mancache = util.lrucachedict(cachesize)
642 self._mancache = util.lrucachedict(cachesize)
610 revlog.revlog.__init__(self, opener, "00manifest.i")
643 revlog.revlog.__init__(self, opener, "00manifest.i")
611 self._usetreemanifest = usetreemanifest
644 self._usetreemanifest = usetreemanifest
612 self._usemanifestv2 = usemanifestv2
645 self._usemanifestv2 = usemanifestv2
613
646
614 def _newmanifest(self, data=''):
647 def _newmanifest(self, data=''):
615 if self._usetreemanifest:
648 if self._usetreemanifest:
616 return treemanifest('', data)
649 return treemanifest('', data)
617 return manifestdict(data)
650 return manifestdict(data)
618
651
619 def _slowreaddelta(self, node):
652 def _slowreaddelta(self, node):
620 r0 = self.deltaparent(self.rev(node))
653 r0 = self.deltaparent(self.rev(node))
621 m0 = self.read(self.node(r0))
654 m0 = self.read(self.node(r0))
622 m1 = self.read(node)
655 m1 = self.read(node)
623 md = self._newmanifest()
656 md = self._newmanifest()
624 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
657 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
625 if n1:
658 if n1:
626 md[f] = n1
659 md[f] = n1
627 if fl1:
660 if fl1:
628 md.setflag(f, fl1)
661 md.setflag(f, fl1)
629 return md
662 return md
630
663
631 def readdelta(self, node):
664 def readdelta(self, node):
632 if self._usemanifestv2:
665 if self._usemanifestv2:
633 return self._slowreaddelta(node)
666 return self._slowreaddelta(node)
634 r = self.rev(node)
667 r = self.rev(node)
635 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
668 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
636 return self._newmanifest(d)
669 return self._newmanifest(d)
637
670
638 def readfast(self, node):
671 def readfast(self, node):
639 '''use the faster of readdelta or read'''
672 '''use the faster of readdelta or read'''
640 r = self.rev(node)
673 r = self.rev(node)
641 deltaparent = self.deltaparent(r)
674 deltaparent = self.deltaparent(r)
642 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
675 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
643 return self.readdelta(node)
676 return self.readdelta(node)
644 return self.read(node)
677 return self.read(node)
645
678
646 def read(self, node):
679 def read(self, node):
647 if node == revlog.nullid:
680 if node == revlog.nullid:
648 return self._newmanifest() # don't upset local cache
681 return self._newmanifest() # don't upset local cache
649 if node in self._mancache:
682 if node in self._mancache:
650 return self._mancache[node][0]
683 return self._mancache[node][0]
651 text = self.revision(node)
684 text = self.revision(node)
652 arraytext = array.array('c', text)
685 arraytext = array.array('c', text)
653 m = self._newmanifest(text)
686 m = self._newmanifest(text)
654 self._mancache[node] = (m, arraytext)
687 self._mancache[node] = (m, arraytext)
655 return m
688 return m
656
689
657 def find(self, node, f):
690 def find(self, node, f):
658 '''look up entry for a single file efficiently.
691 '''look up entry for a single file efficiently.
659 return (node, flags) pair if found, (None, None) if not.'''
692 return (node, flags) pair if found, (None, None) if not.'''
660 m = self.read(node)
693 m = self.read(node)
661 try:
694 try:
662 return m.find(f)
695 return m.find(f)
663 except KeyError:
696 except KeyError:
664 return None, None
697 return None, None
665
698
666 def add(self, m, transaction, link, p1, p2, added, removed):
699 def add(self, m, transaction, link, p1, p2, added, removed):
667 if (p1 in self._mancache and not self._usetreemanifest
700 if (p1 in self._mancache and not self._usetreemanifest
668 and not self._usemanifestv2):
701 and not self._usemanifestv2):
669 # If our first parent is in the manifest cache, we can
702 # If our first parent is in the manifest cache, we can
670 # compute a delta here using properties we know about the
703 # compute a delta here using properties we know about the
671 # manifest up-front, which may save time later for the
704 # manifest up-front, which may save time later for the
672 # revlog layer.
705 # revlog layer.
673
706
674 _checkforbidden(added)
707 _checkforbidden(added)
675 # combine the changed lists into one list for sorting
708 # combine the changed lists into one list for sorting
676 work = [(x, False) for x in added]
709 work = [(x, False) for x in added]
677 work.extend((x, True) for x in removed)
710 work.extend((x, True) for x in removed)
678 # this could use heapq.merge() (from Python 2.6+) or equivalent
711 # this could use heapq.merge() (from Python 2.6+) or equivalent
679 # since the lists are already sorted
712 # since the lists are already sorted
680 work.sort()
713 work.sort()
681
714
682 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
715 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
683 cachedelta = self.rev(p1), deltatext
716 cachedelta = self.rev(p1), deltatext
684 text = util.buffer(arraytext)
717 text = util.buffer(arraytext)
685 else:
718 else:
686 # The first parent manifest isn't already loaded, so we'll
719 # The first parent manifest isn't already loaded, so we'll
687 # just encode a fulltext of the manifest and pass that
720 # just encode a fulltext of the manifest and pass that
688 # through to the revlog layer, and let it handle the delta
721 # through to the revlog layer, and let it handle the delta
689 # process.
722 # process.
690 text = m.text()
723 text = m.text()
691 arraytext = array.array('c', text)
724 arraytext = array.array('c', text)
692 cachedelta = None
725 cachedelta = None
693
726
694 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
727 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
695 self._mancache[n] = (m, arraytext)
728 self._mancache[n] = (m, arraytext)
696
729
697 return n
730 return n
@@ -1,394 +1,455 b''
1 import binascii
1 import binascii
2 import unittest
2 import unittest
3 import itertools
3 import itertools
4
4
5 import silenttestrunner
5 import silenttestrunner
6
6
7 from mercurial import manifest as manifestmod
7 from mercurial import manifest as manifestmod
8 from mercurial import match as matchmod
8 from mercurial import match as matchmod
9
9
10 EMTPY_MANIFEST = ''
10 EMTPY_MANIFEST = ''
11 EMTPY_MANIFEST_V2 = '\0\n'
11
12
12 HASH_1 = '1' * 40
13 HASH_1 = '1' * 40
13 BIN_HASH_1 = binascii.unhexlify(HASH_1)
14 BIN_HASH_1 = binascii.unhexlify(HASH_1)
14 HASH_2 = 'f' * 40
15 HASH_2 = 'f' * 40
15 BIN_HASH_2 = binascii.unhexlify(HASH_2)
16 BIN_HASH_2 = binascii.unhexlify(HASH_2)
16 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
17 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
17 BIN_HASH_3 = binascii.unhexlify(HASH_3)
18 BIN_HASH_3 = binascii.unhexlify(HASH_3)
18 A_SHORT_MANIFEST = (
19 A_SHORT_MANIFEST = (
19 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
20 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
20 'foo\0%(hash1)s%(flag1)s\n'
21 'foo\0%(hash1)s%(flag1)s\n'
21 ) % {'hash1': HASH_1,
22 ) % {'hash1': HASH_1,
22 'flag1': '',
23 'flag1': '',
23 'hash2': HASH_2,
24 'hash2': HASH_2,
24 'flag2': 'l',
25 'flag2': 'l',
25 }
26 }
26
27
28 # Same data as A_SHORT_MANIFEST
29 A_SHORT_MANIFEST_V2 = (
30 '\0\n'
31 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
32 '\x00foo\0%(flag1)s\n%(hash1)s\n'
33 ) % {'hash1': BIN_HASH_1,
34 'flag1': '',
35 'hash2': BIN_HASH_2,
36 'flag2': 'l',
37 }
38
39 # Same data as A_SHORT_MANIFEST
40 A_METADATA_MANIFEST = (
41 '\0foo\0bar\n'
42 '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata
43 '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata
44 ) % {'hash1': BIN_HASH_1,
45 'flag1': '',
46 'hash2': BIN_HASH_2,
47 'flag2': 'l',
48 }
49
50 A_STEM_COMPRESSED_MANIFEST = (
51 '\0\n'
52 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
53 '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars
54 '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters
55 '\x00%(verylongdir)sx/x\0\n%(hash1)s\n'
56 '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars
57 ) % {'hash1': BIN_HASH_1,
58 'flag1': '',
59 'hash2': BIN_HASH_2,
60 'flag2': 'l',
61 'verylongdir': 255 * 'x',
62 }
63
27 A_DEEPER_MANIFEST = (
64 A_DEEPER_MANIFEST = (
28 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
65 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
29 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
66 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
30 'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
67 'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
31 'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
68 'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
32 'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
69 'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
33 'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
70 'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
34 'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
71 'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
35 'a/b/dog.py\0%(hash3)s%(flag1)s\n'
72 'a/b/dog.py\0%(hash3)s%(flag1)s\n'
36 'a/b/fish.py\0%(hash2)s%(flag1)s\n'
73 'a/b/fish.py\0%(hash2)s%(flag1)s\n'
37 'a/c/london.py\0%(hash3)s%(flag2)s\n'
74 'a/c/london.py\0%(hash3)s%(flag2)s\n'
38 'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
75 'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
39 'a/c/paris.py\0%(hash2)s%(flag1)s\n'
76 'a/c/paris.py\0%(hash2)s%(flag1)s\n'
40 'a/d/apple.py\0%(hash3)s%(flag1)s\n'
77 'a/d/apple.py\0%(hash3)s%(flag1)s\n'
41 'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
78 'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
42 'a/green.py\0%(hash1)s%(flag2)s\n'
79 'a/green.py\0%(hash1)s%(flag2)s\n'
43 'a/purple.py\0%(hash2)s%(flag1)s\n'
80 'a/purple.py\0%(hash2)s%(flag1)s\n'
44 'app.py\0%(hash3)s%(flag1)s\n'
81 'app.py\0%(hash3)s%(flag1)s\n'
45 'readme.txt\0%(hash2)s%(flag1)s\n'
82 'readme.txt\0%(hash2)s%(flag1)s\n'
46 ) % {'hash1': HASH_1,
83 ) % {'hash1': HASH_1,
47 'flag1': '',
84 'flag1': '',
48 'hash2': HASH_2,
85 'hash2': HASH_2,
49 'flag2': 'l',
86 'flag2': 'l',
50 'hash3': HASH_3,
87 'hash3': HASH_3,
51 }
88 }
52
89
53 HUGE_MANIFEST_ENTRIES = 200001
90 HUGE_MANIFEST_ENTRIES = 200001
54
91
55 A_HUGE_MANIFEST = ''.join(sorted(
92 A_HUGE_MANIFEST = ''.join(sorted(
56 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
93 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
57 itertools.izip(xrange(200001),
94 itertools.izip(xrange(200001),
58 itertools.cycle((HASH_1, HASH_2)),
95 itertools.cycle((HASH_1, HASH_2)),
59 itertools.cycle(('', 'x', 'l')))))
96 itertools.cycle(('', 'x', 'l')))))
60
97
61 def parsemanifest(text):
98 def parsemanifest(text):
62 return manifestmod.manifestdict(text)
99 return manifestmod.manifestdict(text)
63
100
64 class testmanifest(unittest.TestCase):
101 class testmanifest(unittest.TestCase):
65
102
66 def assertIn(self, thing, container, msg=None):
103 def assertIn(self, thing, container, msg=None):
67 # assertIn new in 2.7, use it if available, otherwise polyfill
104 # assertIn new in 2.7, use it if available, otherwise polyfill
68 sup = getattr(unittest.TestCase, 'assertIn', False)
105 sup = getattr(unittest.TestCase, 'assertIn', False)
69 if sup:
106 if sup:
70 return sup(self, thing, container, msg=msg)
107 return sup(self, thing, container, msg=msg)
71 if not msg:
108 if not msg:
72 msg = 'Expected %r in %r' % (thing, container)
109 msg = 'Expected %r in %r' % (thing, container)
73 self.assert_(thing in container, msg)
110 self.assert_(thing in container, msg)
74
111
75 def testEmptyManifest(self):
112 def testEmptyManifest(self):
76 m = parsemanifest(EMTPY_MANIFEST)
113 m = parsemanifest(EMTPY_MANIFEST)
77 self.assertEqual(0, len(m))
114 self.assertEqual(0, len(m))
78 self.assertEqual([], list(m))
115 self.assertEqual([], list(m))
79
116
117 def testEmptyManifestv2(self):
118 m = parsemanifest(EMTPY_MANIFEST_V2)
119 self.assertEqual(0, len(m))
120 self.assertEqual([], list(m))
121
80 def testManifest(self):
122 def testManifest(self):
81 m = parsemanifest(A_SHORT_MANIFEST)
123 m = parsemanifest(A_SHORT_MANIFEST)
82 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
124 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
83 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
125 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
84 self.assertEqual('l', m.flags('bar/baz/qux.py'))
126 self.assertEqual('l', m.flags('bar/baz/qux.py'))
85 self.assertEqual(BIN_HASH_1, m['foo'])
127 self.assertEqual(BIN_HASH_1, m['foo'])
86 self.assertEqual('', m.flags('foo'))
128 self.assertEqual('', m.flags('foo'))
87 self.assertRaises(KeyError, lambda : m['wat'])
129 self.assertRaises(KeyError, lambda : m['wat'])
88
130
131 def testParseManifestV2(self):
132 m1 = parsemanifest(A_SHORT_MANIFEST)
133 m2 = parsemanifest(A_SHORT_MANIFEST_V2)
134 # Should have same content as A_SHORT_MANIFEST
135 self.assertEqual(m1.text(), m2.text())
136
137 def testParseManifestMetadata(self):
138 # Metadata is for future-proofing and should be accepted but ignored
139 m = parsemanifest(A_METADATA_MANIFEST)
140 self.assertEqual(A_SHORT_MANIFEST, m.text())
141
142 def testParseManifestStemCompression(self):
143 m = parsemanifest(A_STEM_COMPRESSED_MANIFEST)
144 self.assertIn('bar/baz/qux.py', m)
145 self.assertIn('bar/qux/foo.py', m)
146 self.assertIn('bar/qux/foz.py', m)
147 self.assertIn(256 * 'x' + '/x', m)
148 self.assertIn(256 * 'x' + '/y', m)
149
89 def testSetItem(self):
150 def testSetItem(self):
90 want = BIN_HASH_1
151 want = BIN_HASH_1
91
152
92 m = parsemanifest(EMTPY_MANIFEST)
153 m = parsemanifest(EMTPY_MANIFEST)
93 m['a'] = want
154 m['a'] = want
94 self.assertIn('a', m)
155 self.assertIn('a', m)
95 self.assertEqual(want, m['a'])
156 self.assertEqual(want, m['a'])
96 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
157 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
97
158
98 m = parsemanifest(A_SHORT_MANIFEST)
159 m = parsemanifest(A_SHORT_MANIFEST)
99 m['a'] = want
160 m['a'] = want
100 self.assertEqual(want, m['a'])
161 self.assertEqual(want, m['a'])
101 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
162 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
102 m.text())
163 m.text())
103
164
104 def testSetFlag(self):
165 def testSetFlag(self):
105 want = 'x'
166 want = 'x'
106
167
107 m = parsemanifest(EMTPY_MANIFEST)
168 m = parsemanifest(EMTPY_MANIFEST)
108 # first add a file; a file-less flag makes no sense
169 # first add a file; a file-less flag makes no sense
109 m['a'] = BIN_HASH_1
170 m['a'] = BIN_HASH_1
110 m.setflag('a', want)
171 m.setflag('a', want)
111 self.assertEqual(want, m.flags('a'))
172 self.assertEqual(want, m.flags('a'))
112 self.assertEqual('a\0' + HASH_1 + want + '\n', m.text())
173 self.assertEqual('a\0' + HASH_1 + want + '\n', m.text())
113
174
114 m = parsemanifest(A_SHORT_MANIFEST)
175 m = parsemanifest(A_SHORT_MANIFEST)
115 # first add a file; a file-less flag makes no sense
176 # first add a file; a file-less flag makes no sense
116 m['a'] = BIN_HASH_1
177 m['a'] = BIN_HASH_1
117 m.setflag('a', want)
178 m.setflag('a', want)
118 self.assertEqual(want, m.flags('a'))
179 self.assertEqual(want, m.flags('a'))
119 self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST,
180 self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST,
120 m.text())
181 m.text())
121
182
122 def testCopy(self):
183 def testCopy(self):
123 m = parsemanifest(A_SHORT_MANIFEST)
184 m = parsemanifest(A_SHORT_MANIFEST)
124 m['a'] = BIN_HASH_1
185 m['a'] = BIN_HASH_1
125 m2 = m.copy()
186 m2 = m.copy()
126 del m
187 del m
127 del m2 # make sure we don't double free() anything
188 del m2 # make sure we don't double free() anything
128
189
129 def testCompaction(self):
190 def testCompaction(self):
130 unhex = binascii.unhexlify
191 unhex = binascii.unhexlify
131 h1, h2 = unhex(HASH_1), unhex(HASH_2)
192 h1, h2 = unhex(HASH_1), unhex(HASH_2)
132 m = parsemanifest(A_SHORT_MANIFEST)
193 m = parsemanifest(A_SHORT_MANIFEST)
133 m['alpha'] = h1
194 m['alpha'] = h1
134 m['beta'] = h2
195 m['beta'] = h2
135 del m['foo']
196 del m['foo']
136 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
197 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
137 HASH_1, HASH_2, HASH_2)
198 HASH_1, HASH_2, HASH_2)
138 self.assertEqual(want, m.text())
199 self.assertEqual(want, m.text())
139 self.assertEqual(3, len(m))
200 self.assertEqual(3, len(m))
140 self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m))
201 self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m))
141 self.assertEqual(h1, m['alpha'])
202 self.assertEqual(h1, m['alpha'])
142 self.assertEqual(h2, m['bar/baz/qux.py'])
203 self.assertEqual(h2, m['bar/baz/qux.py'])
143 self.assertEqual(h2, m['beta'])
204 self.assertEqual(h2, m['beta'])
144 self.assertEqual('', m.flags('alpha'))
205 self.assertEqual('', m.flags('alpha'))
145 self.assertEqual('l', m.flags('bar/baz/qux.py'))
206 self.assertEqual('l', m.flags('bar/baz/qux.py'))
146 self.assertEqual('', m.flags('beta'))
207 self.assertEqual('', m.flags('beta'))
147 self.assertRaises(KeyError, lambda : m['foo'])
208 self.assertRaises(KeyError, lambda : m['foo'])
148
209
149 def testSetGetNodeSuffix(self):
210 def testSetGetNodeSuffix(self):
150 clean = parsemanifest(A_SHORT_MANIFEST)
211 clean = parsemanifest(A_SHORT_MANIFEST)
151 m = parsemanifest(A_SHORT_MANIFEST)
212 m = parsemanifest(A_SHORT_MANIFEST)
152 h = m['foo']
213 h = m['foo']
153 f = m.flags('foo')
214 f = m.flags('foo')
154 want = h + 'a'
215 want = h + 'a'
155 # Merge code wants to set 21-byte fake hashes at times
216 # Merge code wants to set 21-byte fake hashes at times
156 m['foo'] = want
217 m['foo'] = want
157 self.assertEqual(want, m['foo'])
218 self.assertEqual(want, m['foo'])
158 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
219 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
159 ('foo', BIN_HASH_1 + 'a')],
220 ('foo', BIN_HASH_1 + 'a')],
160 list(m.iteritems()))
221 list(m.iteritems()))
161 # Sometimes it even tries a 22-byte fake hash, but we can
222 # Sometimes it even tries a 22-byte fake hash, but we can
162 # return 21 and it'll work out
223 # return 21 and it'll work out
163 m['foo'] = want + '+'
224 m['foo'] = want + '+'
164 self.assertEqual(want, m['foo'])
225 self.assertEqual(want, m['foo'])
165 # make sure the suffix survives a copy
226 # make sure the suffix survives a copy
166 match = matchmod.match('', '', ['re:foo'])
227 match = matchmod.match('', '', ['re:foo'])
167 m2 = m.matches(match)
228 m2 = m.matches(match)
168 self.assertEqual(want, m2['foo'])
229 self.assertEqual(want, m2['foo'])
169 self.assertEqual(1, len(m2))
230 self.assertEqual(1, len(m2))
170 m2 = m.copy()
231 m2 = m.copy()
171 self.assertEqual(want, m2['foo'])
232 self.assertEqual(want, m2['foo'])
172 # suffix with iteration
233 # suffix with iteration
173 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
234 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
174 ('foo', want)],
235 ('foo', want)],
175 list(m.iteritems()))
236 list(m.iteritems()))
176
237
177 # shows up in diff
238 # shows up in diff
178 self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean))
239 self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean))
179 self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m))
240 self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m))
180
241
181 def testMatchException(self):
242 def testMatchException(self):
182 m = parsemanifest(A_SHORT_MANIFEST)
243 m = parsemanifest(A_SHORT_MANIFEST)
183 match = matchmod.match('', '', ['re:.*'])
244 match = matchmod.match('', '', ['re:.*'])
184 def filt(path):
245 def filt(path):
185 if path == 'foo':
246 if path == 'foo':
186 assert False
247 assert False
187 return True
248 return True
188 match.matchfn = filt
249 match.matchfn = filt
189 self.assertRaises(AssertionError, m.matches, match)
250 self.assertRaises(AssertionError, m.matches, match)
190
251
191 def testRemoveItem(self):
252 def testRemoveItem(self):
192 m = parsemanifest(A_SHORT_MANIFEST)
253 m = parsemanifest(A_SHORT_MANIFEST)
193 del m['foo']
254 del m['foo']
194 self.assertRaises(KeyError, lambda : m['foo'])
255 self.assertRaises(KeyError, lambda : m['foo'])
195 self.assertEqual(1, len(m))
256 self.assertEqual(1, len(m))
196 self.assertEqual(1, len(list(m)))
257 self.assertEqual(1, len(list(m)))
197 # now restore and make sure everything works right
258 # now restore and make sure everything works right
198 m['foo'] = 'a' * 20
259 m['foo'] = 'a' * 20
199 self.assertEqual(2, len(m))
260 self.assertEqual(2, len(m))
200 self.assertEqual(2, len(list(m)))
261 self.assertEqual(2, len(list(m)))
201
262
202 def testManifestDiff(self):
263 def testManifestDiff(self):
203 MISSING = (None, '')
264 MISSING = (None, '')
204 addl = 'z-only-in-left\0' + HASH_1 + '\n'
265 addl = 'z-only-in-left\0' + HASH_1 + '\n'
205 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
266 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
206 left = parsemanifest(
267 left = parsemanifest(
207 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
268 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
208 right = parsemanifest(A_SHORT_MANIFEST + addr)
269 right = parsemanifest(A_SHORT_MANIFEST + addr)
209 want = {
270 want = {
210 'foo': ((BIN_HASH_3, 'x'),
271 'foo': ((BIN_HASH_3, 'x'),
211 (BIN_HASH_1, '')),
272 (BIN_HASH_1, '')),
212 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
273 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
213 'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')),
274 'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')),
214 }
275 }
215 self.assertEqual(want, left.diff(right))
276 self.assertEqual(want, left.diff(right))
216
277
217 want = {
278 want = {
218 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')),
279 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')),
219 'foo': (MISSING, (BIN_HASH_3, 'x')),
280 'foo': (MISSING, (BIN_HASH_3, 'x')),
220 'z-only-in-left': (MISSING, (BIN_HASH_1, '')),
281 'z-only-in-left': (MISSING, (BIN_HASH_1, '')),
221 }
282 }
222 self.assertEqual(want, parsemanifest(EMTPY_MANIFEST).diff(left))
283 self.assertEqual(want, parsemanifest(EMTPY_MANIFEST).diff(left))
223
284
224 want = {
285 want = {
225 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING),
286 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING),
226 'foo': ((BIN_HASH_3, 'x'), MISSING),
287 'foo': ((BIN_HASH_3, 'x'), MISSING),
227 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
288 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
228 }
289 }
229 self.assertEqual(want, left.diff(parsemanifest(EMTPY_MANIFEST)))
290 self.assertEqual(want, left.diff(parsemanifest(EMTPY_MANIFEST)))
230 copy = right.copy()
291 copy = right.copy()
231 del copy['z-only-in-right']
292 del copy['z-only-in-right']
232 del right['foo']
293 del right['foo']
233 want = {
294 want = {
234 'foo': (MISSING, (BIN_HASH_1, '')),
295 'foo': (MISSING, (BIN_HASH_1, '')),
235 'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING),
296 'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING),
236 }
297 }
237 self.assertEqual(want, right.diff(copy))
298 self.assertEqual(want, right.diff(copy))
238
299
239 short = parsemanifest(A_SHORT_MANIFEST)
300 short = parsemanifest(A_SHORT_MANIFEST)
240 pruned = short.copy()
301 pruned = short.copy()
241 del pruned['foo']
302 del pruned['foo']
242 want = {
303 want = {
243 'foo': ((BIN_HASH_1, ''), MISSING),
304 'foo': ((BIN_HASH_1, ''), MISSING),
244 }
305 }
245 self.assertEqual(want, short.diff(pruned))
306 self.assertEqual(want, short.diff(pruned))
246 want = {
307 want = {
247 'foo': (MISSING, (BIN_HASH_1, '')),
308 'foo': (MISSING, (BIN_HASH_1, '')),
248 }
309 }
249 self.assertEqual(want, pruned.diff(short))
310 self.assertEqual(want, pruned.diff(short))
250 want = {
311 want = {
251 'bar/baz/qux.py': None,
312 'bar/baz/qux.py': None,
252 'foo': (MISSING, (BIN_HASH_1, '')),
313 'foo': (MISSING, (BIN_HASH_1, '')),
253 }
314 }
254 self.assertEqual(want, pruned.diff(short, True))
315 self.assertEqual(want, pruned.diff(short, True))
255
316
256 def testReversedLines(self):
317 def testReversedLines(self):
257 backwards = ''.join(
318 backwards = ''.join(
258 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
319 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
259 try:
320 try:
260 parsemanifest(backwards)
321 parsemanifest(backwards)
261 self.fail('Should have raised ValueError')
322 self.fail('Should have raised ValueError')
262 except ValueError, v:
323 except ValueError, v:
263 self.assertIn('Manifest lines not in sorted order.', str(v))
324 self.assertIn('Manifest lines not in sorted order.', str(v))
264
325
265 def testNoTerminalNewline(self):
326 def testNoTerminalNewline(self):
266 try:
327 try:
267 parsemanifest(A_SHORT_MANIFEST + 'wat')
328 parsemanifest(A_SHORT_MANIFEST + 'wat')
268 self.fail('Should have raised ValueError')
329 self.fail('Should have raised ValueError')
269 except ValueError, v:
330 except ValueError, v:
270 self.assertIn('Manifest did not end in a newline.', str(v))
331 self.assertIn('Manifest did not end in a newline.', str(v))
271
332
272 def testNoNewLineAtAll(self):
333 def testNoNewLineAtAll(self):
273 try:
334 try:
274 parsemanifest('wat')
335 parsemanifest('wat')
275 self.fail('Should have raised ValueError')
336 self.fail('Should have raised ValueError')
276 except ValueError, v:
337 except ValueError, v:
277 self.assertIn('Manifest did not end in a newline.', str(v))
338 self.assertIn('Manifest did not end in a newline.', str(v))
278
339
279 def testHugeManifest(self):
340 def testHugeManifest(self):
280 m = parsemanifest(A_HUGE_MANIFEST)
341 m = parsemanifest(A_HUGE_MANIFEST)
281 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
342 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
282 self.assertEqual(len(m), len(list(m)))
343 self.assertEqual(len(m), len(list(m)))
283
344
284 def testMatchesMetadata(self):
345 def testMatchesMetadata(self):
285 '''Tests matches() for a few specific files to make sure that both
346 '''Tests matches() for a few specific files to make sure that both
286 the set of files as well as their flags and nodeids are correct in
347 the set of files as well as their flags and nodeids are correct in
287 the resulting manifest.'''
348 the resulting manifest.'''
288 m = parsemanifest(A_HUGE_MANIFEST)
349 m = parsemanifest(A_HUGE_MANIFEST)
289
350
290 match = matchmod.match('/', '',
351 match = matchmod.match('/', '',
291 ['file1', 'file200', 'file300'], exact=True)
352 ['file1', 'file200', 'file300'], exact=True)
292 m2 = m.matches(match)
353 m2 = m.matches(match)
293
354
294 w = ('file1\0%sx\n'
355 w = ('file1\0%sx\n'
295 'file200\0%sl\n'
356 'file200\0%sl\n'
296 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
357 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
297 self.assertEqual(w, m2.text())
358 self.assertEqual(w, m2.text())
298
359
299 def testMatchesNonexistentFile(self):
360 def testMatchesNonexistentFile(self):
300 '''Tests matches() for a small set of specific files, including one
361 '''Tests matches() for a small set of specific files, including one
301 nonexistent file to make sure in only matches against existing files.
362 nonexistent file to make sure in only matches against existing files.
302 '''
363 '''
303 m = parsemanifest(A_DEEPER_MANIFEST)
364 m = parsemanifest(A_DEEPER_MANIFEST)
304
365
305 match = matchmod.match('/', '',
366 match = matchmod.match('/', '',
306 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'],
367 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'],
307 exact=True)
368 exact=True)
308 m2 = m.matches(match)
369 m2 = m.matches(match)
309
370
310 self.assertEqual(
371 self.assertEqual(
311 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'],
372 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'],
312 m2.keys())
373 m2.keys())
313
374
314 def testMatchesNonexistentDirectory(self):
375 def testMatchesNonexistentDirectory(self):
315 '''Tests matches() for a relpath match on a directory that doesn't
376 '''Tests matches() for a relpath match on a directory that doesn't
316 actually exist.'''
377 actually exist.'''
317 m = parsemanifest(A_DEEPER_MANIFEST)
378 m = parsemanifest(A_DEEPER_MANIFEST)
318
379
319 match = matchmod.match('/', '', ['a/f'], default='relpath')
380 match = matchmod.match('/', '', ['a/f'], default='relpath')
320 m2 = m.matches(match)
381 m2 = m.matches(match)
321
382
322 self.assertEqual([], m2.keys())
383 self.assertEqual([], m2.keys())
323
384
324 def testMatchesExactLarge(self):
385 def testMatchesExactLarge(self):
325 '''Tests matches() for files matching a large list of exact files.
386 '''Tests matches() for files matching a large list of exact files.
326 '''
387 '''
327 m = parsemanifest(A_HUGE_MANIFEST)
388 m = parsemanifest(A_HUGE_MANIFEST)
328
389
329 flist = m.keys()[80:300]
390 flist = m.keys()[80:300]
330 match = matchmod.match('/', '', flist, exact=True)
391 match = matchmod.match('/', '', flist, exact=True)
331 m2 = m.matches(match)
392 m2 = m.matches(match)
332
393
333 self.assertEqual(flist, m2.keys())
394 self.assertEqual(flist, m2.keys())
334
395
335 def testMatchesFull(self):
396 def testMatchesFull(self):
336 '''Tests matches() for what should be a full match.'''
397 '''Tests matches() for what should be a full match.'''
337 m = parsemanifest(A_DEEPER_MANIFEST)
398 m = parsemanifest(A_DEEPER_MANIFEST)
338
399
339 match = matchmod.match('/', '', [''])
400 match = matchmod.match('/', '', [''])
340 m2 = m.matches(match)
401 m2 = m.matches(match)
341
402
342 self.assertEqual(m.keys(), m2.keys())
403 self.assertEqual(m.keys(), m2.keys())
343
404
344 def testMatchesDirectory(self):
405 def testMatchesDirectory(self):
345 '''Tests matches() on a relpath match on a directory, which should
406 '''Tests matches() on a relpath match on a directory, which should
346 match against all files within said directory.'''
407 match against all files within said directory.'''
347 m = parsemanifest(A_DEEPER_MANIFEST)
408 m = parsemanifest(A_DEEPER_MANIFEST)
348
409
349 match = matchmod.match('/', '', ['a/b'], default='relpath')
410 match = matchmod.match('/', '', ['a/b'], default='relpath')
350 m2 = m.matches(match)
411 m2 = m.matches(match)
351
412
352 self.assertEqual([
413 self.assertEqual([
353 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
414 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
354 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
415 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
355 'a/b/fish.py'], m2.keys())
416 'a/b/fish.py'], m2.keys())
356
417
357 def testMatchesExactPath(self):
418 def testMatchesExactPath(self):
358 '''Tests matches() on an exact match on a directory, which should
419 '''Tests matches() on an exact match on a directory, which should
359 result in an empty manifest because you can't perform an exact match
420 result in an empty manifest because you can't perform an exact match
360 against a directory.'''
421 against a directory.'''
361 m = parsemanifest(A_DEEPER_MANIFEST)
422 m = parsemanifest(A_DEEPER_MANIFEST)
362
423
363 match = matchmod.match('/', '', ['a/b'], exact=True)
424 match = matchmod.match('/', '', ['a/b'], exact=True)
364 m2 = m.matches(match)
425 m2 = m.matches(match)
365
426
366 self.assertEqual([], m2.keys())
427 self.assertEqual([], m2.keys())
367
428
368 def testMatchesCwd(self):
429 def testMatchesCwd(self):
369 '''Tests matches() on a relpath match with the current directory ('.')
430 '''Tests matches() on a relpath match with the current directory ('.')
370 when not in the root directory.'''
431 when not in the root directory.'''
371 m = parsemanifest(A_DEEPER_MANIFEST)
432 m = parsemanifest(A_DEEPER_MANIFEST)
372
433
373 match = matchmod.match('/', 'a/b', ['.'], default='relpath')
434 match = matchmod.match('/', 'a/b', ['.'], default='relpath')
374 m2 = m.matches(match)
435 m2 = m.matches(match)
375
436
376 self.assertEqual([
437 self.assertEqual([
377 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
438 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
378 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
439 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
379 'a/b/fish.py'], m2.keys())
440 'a/b/fish.py'], m2.keys())
380
441
381 def testMatchesWithPattern(self):
442 def testMatchesWithPattern(self):
382 '''Tests matches() for files matching a pattern that reside
443 '''Tests matches() for files matching a pattern that reside
383 deeper than the specified directory.'''
444 deeper than the specified directory.'''
384 m = parsemanifest(A_DEEPER_MANIFEST)
445 m = parsemanifest(A_DEEPER_MANIFEST)
385
446
386 match = matchmod.match('/', '', ['a/b/*/*.txt'])
447 match = matchmod.match('/', '', ['a/b/*/*.txt'])
387 m2 = m.matches(match)
448 m2 = m.matches(match)
388
449
389 self.assertEqual(
450 self.assertEqual(
390 ['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'],
451 ['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'],
391 m2.keys())
452 m2.keys())
392
453
393 if __name__ == '__main__':
454 if __name__ == '__main__':
394 silenttestrunner.main(__name__)
455 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now