##// END OF EJS Templates
manifest: add match argument to diff and filesnotin...
Durham Goode -
r31255:959ebff3 default
parent child Browse files
Show More
@@ -1,1582 +1,1599 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class lazymanifestiter(object):
107 class lazymanifestiter(object):
108 def __init__(self, lm):
108 def __init__(self, lm):
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self):
112 def __iter__(self):
113 return self
113 return self
114
114
115 def next(self):
115 def next(self):
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 self.pos += 1
121 self.pos += 1
122 return data[0]
122 return data[0]
123 self.pos += 1
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
125 return data[pos:zeropos]
126
126
127 class lazymanifestiterentries(object):
127 class lazymanifestiterentries(object):
128 def __init__(self, lm):
128 def __init__(self, lm):
129 self.lm = lm
129 self.lm = lm
130 self.pos = 0
130 self.pos = 0
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return self
133 return self
134
134
135 def next(self):
135 def next(self):
136 try:
136 try:
137 data, pos = self.lm._get(self.pos)
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
138 except IndexError:
139 raise StopIteration
139 raise StopIteration
140 if pos == -1:
140 if pos == -1:
141 self.pos += 1
141 self.pos += 1
142 return data
142 return data
143 zeropos = data.find('\x00', pos)
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
148 return (data[pos:zeropos], hashval, flags)
149
149
150 def unhexlify(data, extra, pos, length):
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
151 s = data[pos:pos + length].decode('hex')
152 if extra:
152 if extra:
153 s += chr(extra & 0xff)
153 s += chr(extra & 0xff)
154 return s
154 return s
155
155
156 def _cmp(a, b):
156 def _cmp(a, b):
157 return (a > b) - (a < b)
157 return (a > b) - (a < b)
158
158
159 class _lazymanifest(object):
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
161 if positions is None:
162 self.positions = self.findlines(data)
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
164 self.data = data
165 self.extradata = []
165 self.extradata = []
166 else:
166 else:
167 self.positions = positions[:]
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
169 self.extradata = extradata[:]
170 self.data = data
170 self.data = data
171
171
172 def findlines(self, data):
172 def findlines(self, data):
173 if not data:
173 if not data:
174 return []
174 return []
175 pos = data.find("\n")
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
178 positions = [0]
179 prev = data[:data.find('\x00')]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
185 prev = nexts
186 pos = data.find("\n", pos + 1)
186 pos = data.find("\n", pos + 1)
187 return positions
187 return positions
188
188
189 def _get(self, index):
189 def _get(self, index):
190 # get the position encoded in pos:
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
192 # negative number is in extrapieces
193 pos = self.positions[index]
193 pos = self.positions[index]
194 if pos >= 0:
194 if pos >= 0:
195 return self.data, pos
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
196 return self.extradata[-pos - 1], -1
197
197
198 def _getkey(self, pos):
198 def _getkey(self, pos):
199 if pos >= 0:
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
201 return self.extradata[-pos - 1][0]
202
202
203 def bsearch(self, key):
203 def bsearch(self, key):
204 first = 0
204 first = 0
205 last = len(self.positions) - 1
205 last = len(self.positions) - 1
206
206
207 while first <= last:
207 while first <= last:
208 midpoint = (first + last)//2
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
211 r = _cmp(key, candidate)
212 if r == 0:
212 if r == 0:
213 return midpoint
213 return midpoint
214 else:
214 else:
215 if r < 0:
215 if r < 0:
216 last = midpoint - 1
216 last = midpoint - 1
217 else:
217 else:
218 first = midpoint + 1
218 first = midpoint + 1
219 return -1
219 return -1
220
220
221 def bsearch2(self, key):
221 def bsearch2(self, key):
222 # same as the above, but will always return the position
222 # same as the above, but will always return the position
223 # done for performance reasons
223 # done for performance reasons
224 first = 0
224 first = 0
225 last = len(self.positions) - 1
225 last = len(self.positions) - 1
226
226
227 while first <= last:
227 while first <= last:
228 midpoint = (first + last)//2
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
231 r = _cmp(key, candidate)
232 if r == 0:
232 if r == 0:
233 return (midpoint, True)
233 return (midpoint, True)
234 else:
234 else:
235 if r < 0:
235 if r < 0:
236 last = midpoint - 1
236 last = midpoint - 1
237 else:
237 else:
238 first = midpoint + 1
238 first = midpoint + 1
239 return (first, False)
239 return (first, False)
240
240
241 def __contains__(self, key):
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
242 return self.bsearch(key) != -1
243
243
244 def _getflags(self, data, needle, pos):
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
245 start = pos + 41
246 end = data.find("\n", start)
246 end = data.find("\n", start)
247 if end == -1:
247 if end == -1:
248 end = len(data) - 1
248 end = len(data) - 1
249 if start == end:
249 if start == end:
250 return ''
250 return ''
251 return self.data[start:end]
251 return self.data[start:end]
252
252
253 def __getitem__(self, key):
253 def __getitem__(self, key):
254 if not isinstance(key, str):
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
256 needle = self.bsearch(key)
257 if needle == -1:
257 if needle == -1:
258 raise KeyError
258 raise KeyError
259 data, pos = self._get(needle)
259 data, pos = self._get(needle)
260 if pos == -1:
260 if pos == -1:
261 return (data[1], data[2])
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
267 return (hashval, flags)
268
268
269 def __delitem__(self, key):
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
270 needle, found = self.bsearch2(key)
271 if not found:
271 if not found:
272 raise KeyError
272 raise KeyError
273 cur = self.positions[needle]
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
278
279 def __setitem__(self, key, value):
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
287 flags = value[1]
288 if len(hashval) == 22:
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
292 needle, found = self.bsearch2(key)
293 if found:
293 if found:
294 # put the item
294 # put the item
295 pos = self.positions[needle]
295 pos = self.positions[needle]
296 if pos < 0:
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
298 else:
299 # just don't bother
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
301 self.positions[needle] = -len(self.extradata)
302 else:
302 else:
303 # not found, put it in with extra positions
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
308 self.extrainfo[needle:])
309
309
310 def copy(self):
310 def copy(self):
311 # XXX call _compact like in C?
311 # XXX call _compact like in C?
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 self.extradata)
313 self.extradata)
314
314
315 def _compact(self):
315 def _compact(self):
316 # hopefully not called TOO often
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
317 if len(self.extradata) == 0:
318 return
318 return
319 l = []
319 l = []
320 last_cut = 0
320 last_cut = 0
321 i = 0
321 i = 0
322 offset = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
326 cur = self.positions[i]
327 last_cut = cur
327 last_cut = cur
328 while True:
328 while True:
329 self.positions[i] = offset
329 self.positions[i] = offset
330 i += 1
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
332 break
333 offset += self.positions[i] - cur
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
336 if end_cut != -1:
337 end_cut += 1
337 end_cut += 1
338 offset += end_cut - cur
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
339 l.append(self.data[last_cut:end_cut])
340 else:
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
344 l.append(self._pack(t))
345 self.positions[i] = offset
345 self.positions[i] = offset
346 if len(t[1]) > 20:
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
348 offset += len(l[-1])
349 i += 1
349 i += 1
350 self.data = ''.join(l)
350 self.data = ''.join(l)
351 self.extradata = []
351 self.extradata = []
352
352
353 def _pack(self, d):
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
355
356 def text(self):
356 def text(self):
357 self._compact()
357 self._compact()
358 return self.data
358 return self.data
359
359
360 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
361 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
362 # XXX think whether efficiency matters here
363 diff = {}
363 diff = {}
364
364
365 for fn, e1, flags in self.iterentries():
365 for fn, e1, flags in self.iterentries():
366 if fn not in m2:
366 if fn not in m2:
367 diff[fn] = (e1, flags), (None, '')
367 diff[fn] = (e1, flags), (None, '')
368 else:
368 else:
369 e2 = m2[fn]
369 e2 = m2[fn]
370 if (e1, flags) != e2:
370 if (e1, flags) != e2:
371 diff[fn] = (e1, flags), e2
371 diff[fn] = (e1, flags), e2
372 elif clean:
372 elif clean:
373 diff[fn] = None
373 diff[fn] = None
374
374
375 for fn, e2, flags in m2.iterentries():
375 for fn, e2, flags in m2.iterentries():
376 if fn not in self:
376 if fn not in self:
377 diff[fn] = (None, ''), (e2, flags)
377 diff[fn] = (None, ''), (e2, flags)
378
378
379 return diff
379 return diff
380
380
381 def iterentries(self):
381 def iterentries(self):
382 return lazymanifestiterentries(self)
382 return lazymanifestiterentries(self)
383
383
384 def iterkeys(self):
384 def iterkeys(self):
385 return lazymanifestiter(self)
385 return lazymanifestiter(self)
386
386
387 def __iter__(self):
387 def __iter__(self):
388 return lazymanifestiter(self)
388 return lazymanifestiter(self)
389
389
390 def __len__(self):
390 def __len__(self):
391 return len(self.positions)
391 return len(self.positions)
392
392
393 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
394 # XXX should be optimized
395 c = _lazymanifest('')
395 c = _lazymanifest('')
396 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
397 if filterfn(f):
397 if filterfn(f):
398 c[f] = n, fl
398 c[f] = n, fl
399 return c
399 return c
400
400
401 try:
401 try:
402 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
403 except AttributeError:
403 except AttributeError:
404 pass
404 pass
405
405
406 class manifestdict(object):
406 class manifestdict(object):
407 def __init__(self, data=''):
407 def __init__(self, data=''):
408 if data.startswith('\0'):
408 if data.startswith('\0'):
409 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
410 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
411 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
412 self._lm[f] = n, fl
412 self._lm[f] = n, fl
413 else:
413 else:
414 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
415
415
416 def __getitem__(self, key):
416 def __getitem__(self, key):
417 return self._lm[key][0]
417 return self._lm[key][0]
418
418
419 def find(self, key):
419 def find(self, key):
420 return self._lm[key]
420 return self._lm[key]
421
421
422 def __len__(self):
422 def __len__(self):
423 return len(self._lm)
423 return len(self._lm)
424
424
425 def __nonzero__(self):
425 def __nonzero__(self):
426 # nonzero is covered by the __len__ function, but implementing it here
426 # nonzero is covered by the __len__ function, but implementing it here
427 # makes it easier for extensions to override.
427 # makes it easier for extensions to override.
428 return len(self._lm) != 0
428 return len(self._lm) != 0
429
429
430 def __setitem__(self, key, node):
430 def __setitem__(self, key, node):
431 self._lm[key] = node, self.flags(key, '')
431 self._lm[key] = node, self.flags(key, '')
432
432
433 def __contains__(self, key):
433 def __contains__(self, key):
434 return key in self._lm
434 return key in self._lm
435
435
436 def __delitem__(self, key):
436 def __delitem__(self, key):
437 del self._lm[key]
437 del self._lm[key]
438
438
439 def __iter__(self):
439 def __iter__(self):
440 return self._lm.__iter__()
440 return self._lm.__iter__()
441
441
442 def iterkeys(self):
442 def iterkeys(self):
443 return self._lm.iterkeys()
443 return self._lm.iterkeys()
444
444
445 def keys(self):
445 def keys(self):
446 return list(self.iterkeys())
446 return list(self.iterkeys())
447
447
448 def filesnotin(self, m2):
448 def filesnotin(self, m2, match=None):
449 '''Set of files in this manifest that are not in the other'''
449 '''Set of files in this manifest that are not in the other'''
450 if match:
451 m1 = self.matches(match)
452 m2 = m2.matches(match)
453 return m1.filesnotin(m2)
450 diff = self.diff(m2)
454 diff = self.diff(m2)
451 files = set(filepath
455 files = set(filepath
452 for filepath, hashflags in diff.iteritems()
456 for filepath, hashflags in diff.iteritems()
453 if hashflags[1][0] is None)
457 if hashflags[1][0] is None)
454 return files
458 return files
455
459
456 @propertycache
460 @propertycache
457 def _dirs(self):
461 def _dirs(self):
458 return util.dirs(self)
462 return util.dirs(self)
459
463
460 def dirs(self):
464 def dirs(self):
461 return self._dirs
465 return self._dirs
462
466
463 def hasdir(self, dir):
467 def hasdir(self, dir):
464 return dir in self._dirs
468 return dir in self._dirs
465
469
466 def _filesfastpath(self, match):
470 def _filesfastpath(self, match):
467 '''Checks whether we can correctly and quickly iterate over matcher
471 '''Checks whether we can correctly and quickly iterate over matcher
468 files instead of over manifest files.'''
472 files instead of over manifest files.'''
469 files = match.files()
473 files = match.files()
470 return (len(files) < 100 and (match.isexact() or
474 return (len(files) < 100 and (match.isexact() or
471 (match.prefix() and all(fn in self for fn in files))))
475 (match.prefix() and all(fn in self for fn in files))))
472
476
473 def walk(self, match):
477 def walk(self, match):
474 '''Generates matching file names.
478 '''Generates matching file names.
475
479
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
480 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 an entirely new manifest.
481 an entirely new manifest.
478
482
479 It also reports nonexistent files by marking them bad with match.bad().
483 It also reports nonexistent files by marking them bad with match.bad().
480 '''
484 '''
481 if match.always():
485 if match.always():
482 for f in iter(self):
486 for f in iter(self):
483 yield f
487 yield f
484 return
488 return
485
489
486 fset = set(match.files())
490 fset = set(match.files())
487
491
488 # avoid the entire walk if we're only looking for specific files
492 # avoid the entire walk if we're only looking for specific files
489 if self._filesfastpath(match):
493 if self._filesfastpath(match):
490 for fn in sorted(fset):
494 for fn in sorted(fset):
491 yield fn
495 yield fn
492 return
496 return
493
497
494 for fn in self:
498 for fn in self:
495 if fn in fset:
499 if fn in fset:
496 # specified pattern is the exact name
500 # specified pattern is the exact name
497 fset.remove(fn)
501 fset.remove(fn)
498 if match(fn):
502 if match(fn):
499 yield fn
503 yield fn
500
504
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
505 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 # follow that here, too
506 # follow that here, too
503 fset.discard('.')
507 fset.discard('.')
504
508
505 for fn in sorted(fset):
509 for fn in sorted(fset):
506 if not self.hasdir(fn):
510 if not self.hasdir(fn):
507 match.bad(fn, None)
511 match.bad(fn, None)
508
512
509 def matches(self, match):
513 def matches(self, match):
510 '''generate a new manifest filtered by the match argument'''
514 '''generate a new manifest filtered by the match argument'''
511 if match.always():
515 if match.always():
512 return self.copy()
516 return self.copy()
513
517
514 if self._filesfastpath(match):
518 if self._filesfastpath(match):
515 m = manifestdict()
519 m = manifestdict()
516 lm = self._lm
520 lm = self._lm
517 for fn in match.files():
521 for fn in match.files():
518 if fn in lm:
522 if fn in lm:
519 m._lm[fn] = lm[fn]
523 m._lm[fn] = lm[fn]
520 return m
524 return m
521
525
522 m = manifestdict()
526 m = manifestdict()
523 m._lm = self._lm.filtercopy(match)
527 m._lm = self._lm.filtercopy(match)
524 return m
528 return m
525
529
526 def diff(self, m2, clean=False):
530 def diff(self, m2, match=None, clean=False):
527 '''Finds changes between the current manifest and m2.
531 '''Finds changes between the current manifest and m2.
528
532
529 Args:
533 Args:
530 m2: the manifest to which this manifest should be compared.
534 m2: the manifest to which this manifest should be compared.
531 clean: if true, include files unchanged between these manifests
535 clean: if true, include files unchanged between these manifests
532 with a None value in the returned dictionary.
536 with a None value in the returned dictionary.
533
537
534 The result is returned as a dict with filename as key and
538 The result is returned as a dict with filename as key and
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
539 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 nodeid in the current/other manifest and fl1/fl2 is the flag
540 nodeid in the current/other manifest and fl1/fl2 is the flag
537 in the current/other manifest. Where the file does not exist,
541 in the current/other manifest. Where the file does not exist,
538 the nodeid will be None and the flags will be the empty
542 the nodeid will be None and the flags will be the empty
539 string.
543 string.
540 '''
544 '''
545 if match:
546 m1 = self.matches(match)
547 m2 = m2.matches(match)
548 return m1.diff(m2, clean=clean)
541 return self._lm.diff(m2._lm, clean)
549 return self._lm.diff(m2._lm, clean)
542
550
543 def setflag(self, key, flag):
551 def setflag(self, key, flag):
544 self._lm[key] = self[key], flag
552 self._lm[key] = self[key], flag
545
553
546 def get(self, key, default=None):
554 def get(self, key, default=None):
547 try:
555 try:
548 return self._lm[key][0]
556 return self._lm[key][0]
549 except KeyError:
557 except KeyError:
550 return default
558 return default
551
559
552 def flags(self, key, default=''):
560 def flags(self, key, default=''):
553 try:
561 try:
554 return self._lm[key][1]
562 return self._lm[key][1]
555 except KeyError:
563 except KeyError:
556 return default
564 return default
557
565
558 def copy(self):
566 def copy(self):
559 c = manifestdict()
567 c = manifestdict()
560 c._lm = self._lm.copy()
568 c._lm = self._lm.copy()
561 return c
569 return c
562
570
563 def iteritems(self):
571 def iteritems(self):
564 return (x[:2] for x in self._lm.iterentries())
572 return (x[:2] for x in self._lm.iterentries())
565
573
566 def iterentries(self):
574 def iterentries(self):
567 return self._lm.iterentries()
575 return self._lm.iterentries()
568
576
569 def text(self, usemanifestv2=False):
577 def text(self, usemanifestv2=False):
570 if usemanifestv2:
578 if usemanifestv2:
571 return _textv2(self._lm.iterentries())
579 return _textv2(self._lm.iterentries())
572 else:
580 else:
573 # use (probably) native version for v1
581 # use (probably) native version for v1
574 return self._lm.text()
582 return self._lm.text()
575
583
576 def fastdelta(self, base, changes):
584 def fastdelta(self, base, changes):
577 """Given a base manifest text as an array.array and a list of changes
585 """Given a base manifest text as an array.array and a list of changes
578 relative to that text, compute a delta that can be used by revlog.
586 relative to that text, compute a delta that can be used by revlog.
579 """
587 """
580 delta = []
588 delta = []
581 dstart = None
589 dstart = None
582 dend = None
590 dend = None
583 dline = [""]
591 dline = [""]
584 start = 0
592 start = 0
585 # zero copy representation of base as a buffer
593 # zero copy representation of base as a buffer
586 addbuf = util.buffer(base)
594 addbuf = util.buffer(base)
587
595
588 changes = list(changes)
596 changes = list(changes)
589 if len(changes) < 1000:
597 if len(changes) < 1000:
590 # start with a readonly loop that finds the offset of
598 # start with a readonly loop that finds the offset of
591 # each line and creates the deltas
599 # each line and creates the deltas
592 for f, todelete in changes:
600 for f, todelete in changes:
593 # bs will either be the index of the item or the insert point
601 # bs will either be the index of the item or the insert point
594 start, end = _msearch(addbuf, f, start)
602 start, end = _msearch(addbuf, f, start)
595 if not todelete:
603 if not todelete:
596 h, fl = self._lm[f]
604 h, fl = self._lm[f]
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
605 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 else:
606 else:
599 if start == end:
607 if start == end:
600 # item we want to delete was not found, error out
608 # item we want to delete was not found, error out
601 raise AssertionError(
609 raise AssertionError(
602 _("failed to remove %s from manifest") % f)
610 _("failed to remove %s from manifest") % f)
603 l = ""
611 l = ""
604 if dstart is not None and dstart <= start and dend >= start:
612 if dstart is not None and dstart <= start and dend >= start:
605 if dend < end:
613 if dend < end:
606 dend = end
614 dend = end
607 if l:
615 if l:
608 dline.append(l)
616 dline.append(l)
609 else:
617 else:
610 if dstart is not None:
618 if dstart is not None:
611 delta.append([dstart, dend, "".join(dline)])
619 delta.append([dstart, dend, "".join(dline)])
612 dstart = start
620 dstart = start
613 dend = end
621 dend = end
614 dline = [l]
622 dline = [l]
615
623
616 if dstart is not None:
624 if dstart is not None:
617 delta.append([dstart, dend, "".join(dline)])
625 delta.append([dstart, dend, "".join(dline)])
618 # apply the delta to the base, and get a delta for addrevision
626 # apply the delta to the base, and get a delta for addrevision
619 deltatext, arraytext = _addlistdelta(base, delta)
627 deltatext, arraytext = _addlistdelta(base, delta)
620 else:
628 else:
621 # For large changes, it's much cheaper to just build the text and
629 # For large changes, it's much cheaper to just build the text and
622 # diff it.
630 # diff it.
623 arraytext = array.array('c', self.text())
631 arraytext = array.array('c', self.text())
624 deltatext = mdiff.textdiff(base, arraytext)
632 deltatext = mdiff.textdiff(base, arraytext)
625
633
626 return arraytext, deltatext
634 return arraytext, deltatext
627
635
628 def _msearch(m, s, lo=0, hi=None):
636 def _msearch(m, s, lo=0, hi=None):
629 '''return a tuple (start, end) that says where to find s within m.
637 '''return a tuple (start, end) that says where to find s within m.
630
638
631 If the string is found m[start:end] are the line containing
639 If the string is found m[start:end] are the line containing
632 that string. If start == end the string was not found and
640 that string. If start == end the string was not found and
633 they indicate the proper sorted insertion point.
641 they indicate the proper sorted insertion point.
634
642
635 m should be a buffer or a string
643 m should be a buffer or a string
636 s is a string'''
644 s is a string'''
637 def advance(i, c):
645 def advance(i, c):
638 while i < lenm and m[i] != c:
646 while i < lenm and m[i] != c:
639 i += 1
647 i += 1
640 return i
648 return i
641 if not s:
649 if not s:
642 return (lo, lo)
650 return (lo, lo)
643 lenm = len(m)
651 lenm = len(m)
644 if not hi:
652 if not hi:
645 hi = lenm
653 hi = lenm
646 while lo < hi:
654 while lo < hi:
647 mid = (lo + hi) // 2
655 mid = (lo + hi) // 2
648 start = mid
656 start = mid
649 while start > 0 and m[start - 1] != '\n':
657 while start > 0 and m[start - 1] != '\n':
650 start -= 1
658 start -= 1
651 end = advance(start, '\0')
659 end = advance(start, '\0')
652 if m[start:end] < s:
660 if m[start:end] < s:
653 # we know that after the null there are 40 bytes of sha1
661 # we know that after the null there are 40 bytes of sha1
654 # this translates to the bisect lo = mid + 1
662 # this translates to the bisect lo = mid + 1
655 lo = advance(end + 40, '\n') + 1
663 lo = advance(end + 40, '\n') + 1
656 else:
664 else:
657 # this translates to the bisect hi = mid
665 # this translates to the bisect hi = mid
658 hi = start
666 hi = start
659 end = advance(lo, '\0')
667 end = advance(lo, '\0')
660 found = m[lo:end]
668 found = m[lo:end]
661 if s == found:
669 if s == found:
662 # we know that after the null there are 40 bytes of sha1
670 # we know that after the null there are 40 bytes of sha1
663 end = advance(end + 40, '\n')
671 end = advance(end + 40, '\n')
664 return (lo, end + 1)
672 return (lo, end + 1)
665 else:
673 else:
666 return (lo, lo)
674 return (lo, lo)
667
675
668 def _checkforbidden(l):
676 def _checkforbidden(l):
669 """Check filenames for illegal characters."""
677 """Check filenames for illegal characters."""
670 for f in l:
678 for f in l:
671 if '\n' in f or '\r' in f:
679 if '\n' in f or '\r' in f:
672 raise error.RevlogError(
680 raise error.RevlogError(
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
681 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674
682
675
683
676 # apply the changes collected during the bisect loop to our addlist
684 # apply the changes collected during the bisect loop to our addlist
677 # return a delta suitable for addrevision
685 # return a delta suitable for addrevision
678 def _addlistdelta(addlist, x):
686 def _addlistdelta(addlist, x):
679 # for large addlist arrays, building a new array is cheaper
687 # for large addlist arrays, building a new array is cheaper
680 # than repeatedly modifying the existing one
688 # than repeatedly modifying the existing one
681 currentposition = 0
689 currentposition = 0
682 newaddlist = array.array('c')
690 newaddlist = array.array('c')
683
691
684 for start, end, content in x:
692 for start, end, content in x:
685 newaddlist += addlist[currentposition:start]
693 newaddlist += addlist[currentposition:start]
686 if content:
694 if content:
687 newaddlist += array.array('c', content)
695 newaddlist += array.array('c', content)
688
696
689 currentposition = end
697 currentposition = end
690
698
691 newaddlist += addlist[currentposition:]
699 newaddlist += addlist[currentposition:]
692
700
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
701 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 + content for start, end, content in x)
702 + content for start, end, content in x)
695 return deltatext, newaddlist
703 return deltatext, newaddlist
696
704
697 def _splittopdir(f):
705 def _splittopdir(f):
698 if '/' in f:
706 if '/' in f:
699 dir, subpath = f.split('/', 1)
707 dir, subpath = f.split('/', 1)
700 return dir + '/', subpath
708 return dir + '/', subpath
701 else:
709 else:
702 return '', f
710 return '', f
703
711
704 _noop = lambda s: None
712 _noop = lambda s: None
705
713
706 class treemanifest(object):
714 class treemanifest(object):
707 def __init__(self, dir='', text=''):
715 def __init__(self, dir='', text=''):
708 self._dir = dir
716 self._dir = dir
709 self._node = revlog.nullid
717 self._node = revlog.nullid
710 self._loadfunc = _noop
718 self._loadfunc = _noop
711 self._copyfunc = _noop
719 self._copyfunc = _noop
712 self._dirty = False
720 self._dirty = False
713 self._dirs = {}
721 self._dirs = {}
714 # Using _lazymanifest here is a little slower than plain old dicts
722 # Using _lazymanifest here is a little slower than plain old dicts
715 self._files = {}
723 self._files = {}
716 self._flags = {}
724 self._flags = {}
717 if text:
725 if text:
718 def readsubtree(subdir, subm):
726 def readsubtree(subdir, subm):
719 raise AssertionError('treemanifest constructor only accepts '
727 raise AssertionError('treemanifest constructor only accepts '
720 'flat manifests')
728 'flat manifests')
721 self.parse(text, readsubtree)
729 self.parse(text, readsubtree)
722 self._dirty = True # Mark flat manifest dirty after parsing
730 self._dirty = True # Mark flat manifest dirty after parsing
723
731
724 def _subpath(self, path):
732 def _subpath(self, path):
725 return self._dir + path
733 return self._dir + path
726
734
727 def __len__(self):
735 def __len__(self):
728 self._load()
736 self._load()
729 size = len(self._files)
737 size = len(self._files)
730 for m in self._dirs.values():
738 for m in self._dirs.values():
731 size += m.__len__()
739 size += m.__len__()
732 return size
740 return size
733
741
734 def _isempty(self):
742 def _isempty(self):
735 self._load() # for consistency; already loaded by all callers
743 self._load() # for consistency; already loaded by all callers
736 return (not self._files and (not self._dirs or
744 return (not self._files and (not self._dirs or
737 all(m._isempty() for m in self._dirs.values())))
745 all(m._isempty() for m in self._dirs.values())))
738
746
739 def __repr__(self):
747 def __repr__(self):
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
748 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 (self._dir, revlog.hex(self._node),
749 (self._dir, revlog.hex(self._node),
742 bool(self._loadfunc is _noop),
750 bool(self._loadfunc is _noop),
743 self._dirty, id(self)))
751 self._dirty, id(self)))
744
752
745 def dir(self):
753 def dir(self):
746 '''The directory that this tree manifest represents, including a
754 '''The directory that this tree manifest represents, including a
747 trailing '/'. Empty string for the repo root directory.'''
755 trailing '/'. Empty string for the repo root directory.'''
748 return self._dir
756 return self._dir
749
757
750 def node(self):
758 def node(self):
751 '''This node of this instance. nullid for unsaved instances. Should
759 '''This node of this instance. nullid for unsaved instances. Should
752 be updated when the instance is read or written from a revlog.
760 be updated when the instance is read or written from a revlog.
753 '''
761 '''
754 assert not self._dirty
762 assert not self._dirty
755 return self._node
763 return self._node
756
764
757 def setnode(self, node):
765 def setnode(self, node):
758 self._node = node
766 self._node = node
759 self._dirty = False
767 self._dirty = False
760
768
761 def iterentries(self):
769 def iterentries(self):
762 self._load()
770 self._load()
763 for p, n in sorted(self._dirs.items() + self._files.items()):
771 for p, n in sorted(self._dirs.items() + self._files.items()):
764 if p in self._files:
772 if p in self._files:
765 yield self._subpath(p), n, self._flags.get(p, '')
773 yield self._subpath(p), n, self._flags.get(p, '')
766 else:
774 else:
767 for x in n.iterentries():
775 for x in n.iterentries():
768 yield x
776 yield x
769
777
770 def iteritems(self):
778 def iteritems(self):
771 self._load()
779 self._load()
772 for p, n in sorted(self._dirs.items() + self._files.items()):
780 for p, n in sorted(self._dirs.items() + self._files.items()):
773 if p in self._files:
781 if p in self._files:
774 yield self._subpath(p), n
782 yield self._subpath(p), n
775 else:
783 else:
776 for f, sn in n.iteritems():
784 for f, sn in n.iteritems():
777 yield f, sn
785 yield f, sn
778
786
779 def iterkeys(self):
787 def iterkeys(self):
780 self._load()
788 self._load()
781 for p in sorted(self._dirs.keys() + self._files.keys()):
789 for p in sorted(self._dirs.keys() + self._files.keys()):
782 if p in self._files:
790 if p in self._files:
783 yield self._subpath(p)
791 yield self._subpath(p)
784 else:
792 else:
785 for f in self._dirs[p].iterkeys():
793 for f in self._dirs[p].iterkeys():
786 yield f
794 yield f
787
795
788 def keys(self):
796 def keys(self):
789 return list(self.iterkeys())
797 return list(self.iterkeys())
790
798
791 def __iter__(self):
799 def __iter__(self):
792 return self.iterkeys()
800 return self.iterkeys()
793
801
794 def __contains__(self, f):
802 def __contains__(self, f):
795 if f is None:
803 if f is None:
796 return False
804 return False
797 self._load()
805 self._load()
798 dir, subpath = _splittopdir(f)
806 dir, subpath = _splittopdir(f)
799 if dir:
807 if dir:
800 if dir not in self._dirs:
808 if dir not in self._dirs:
801 return False
809 return False
802 return self._dirs[dir].__contains__(subpath)
810 return self._dirs[dir].__contains__(subpath)
803 else:
811 else:
804 return f in self._files
812 return f in self._files
805
813
806 def get(self, f, default=None):
814 def get(self, f, default=None):
807 self._load()
815 self._load()
808 dir, subpath = _splittopdir(f)
816 dir, subpath = _splittopdir(f)
809 if dir:
817 if dir:
810 if dir not in self._dirs:
818 if dir not in self._dirs:
811 return default
819 return default
812 return self._dirs[dir].get(subpath, default)
820 return self._dirs[dir].get(subpath, default)
813 else:
821 else:
814 return self._files.get(f, default)
822 return self._files.get(f, default)
815
823
816 def __getitem__(self, f):
824 def __getitem__(self, f):
817 self._load()
825 self._load()
818 dir, subpath = _splittopdir(f)
826 dir, subpath = _splittopdir(f)
819 if dir:
827 if dir:
820 return self._dirs[dir].__getitem__(subpath)
828 return self._dirs[dir].__getitem__(subpath)
821 else:
829 else:
822 return self._files[f]
830 return self._files[f]
823
831
824 def flags(self, f):
832 def flags(self, f):
825 self._load()
833 self._load()
826 dir, subpath = _splittopdir(f)
834 dir, subpath = _splittopdir(f)
827 if dir:
835 if dir:
828 if dir not in self._dirs:
836 if dir not in self._dirs:
829 return ''
837 return ''
830 return self._dirs[dir].flags(subpath)
838 return self._dirs[dir].flags(subpath)
831 else:
839 else:
832 if f in self._dirs:
840 if f in self._dirs:
833 return ''
841 return ''
834 return self._flags.get(f, '')
842 return self._flags.get(f, '')
835
843
836 def find(self, f):
844 def find(self, f):
837 self._load()
845 self._load()
838 dir, subpath = _splittopdir(f)
846 dir, subpath = _splittopdir(f)
839 if dir:
847 if dir:
840 return self._dirs[dir].find(subpath)
848 return self._dirs[dir].find(subpath)
841 else:
849 else:
842 return self._files[f], self._flags.get(f, '')
850 return self._files[f], self._flags.get(f, '')
843
851
844 def __delitem__(self, f):
852 def __delitem__(self, f):
845 self._load()
853 self._load()
846 dir, subpath = _splittopdir(f)
854 dir, subpath = _splittopdir(f)
847 if dir:
855 if dir:
848 self._dirs[dir].__delitem__(subpath)
856 self._dirs[dir].__delitem__(subpath)
849 # If the directory is now empty, remove it
857 # If the directory is now empty, remove it
850 if self._dirs[dir]._isempty():
858 if self._dirs[dir]._isempty():
851 del self._dirs[dir]
859 del self._dirs[dir]
852 else:
860 else:
853 del self._files[f]
861 del self._files[f]
854 if f in self._flags:
862 if f in self._flags:
855 del self._flags[f]
863 del self._flags[f]
856 self._dirty = True
864 self._dirty = True
857
865
858 def __setitem__(self, f, n):
866 def __setitem__(self, f, n):
859 assert n is not None
867 assert n is not None
860 self._load()
868 self._load()
861 dir, subpath = _splittopdir(f)
869 dir, subpath = _splittopdir(f)
862 if dir:
870 if dir:
863 if dir not in self._dirs:
871 if dir not in self._dirs:
864 self._dirs[dir] = treemanifest(self._subpath(dir))
872 self._dirs[dir] = treemanifest(self._subpath(dir))
865 self._dirs[dir].__setitem__(subpath, n)
873 self._dirs[dir].__setitem__(subpath, n)
866 else:
874 else:
867 self._files[f] = n[:21] # to match manifestdict's behavior
875 self._files[f] = n[:21] # to match manifestdict's behavior
868 self._dirty = True
876 self._dirty = True
869
877
870 def _load(self):
878 def _load(self):
871 if self._loadfunc is not _noop:
879 if self._loadfunc is not _noop:
872 lf, self._loadfunc = self._loadfunc, _noop
880 lf, self._loadfunc = self._loadfunc, _noop
873 lf(self)
881 lf(self)
874 elif self._copyfunc is not _noop:
882 elif self._copyfunc is not _noop:
875 cf, self._copyfunc = self._copyfunc, _noop
883 cf, self._copyfunc = self._copyfunc, _noop
876 cf(self)
884 cf(self)
877
885
878 def setflag(self, f, flags):
886 def setflag(self, f, flags):
879 """Set the flags (symlink, executable) for path f."""
887 """Set the flags (symlink, executable) for path f."""
880 self._load()
888 self._load()
881 dir, subpath = _splittopdir(f)
889 dir, subpath = _splittopdir(f)
882 if dir:
890 if dir:
883 if dir not in self._dirs:
891 if dir not in self._dirs:
884 self._dirs[dir] = treemanifest(self._subpath(dir))
892 self._dirs[dir] = treemanifest(self._subpath(dir))
885 self._dirs[dir].setflag(subpath, flags)
893 self._dirs[dir].setflag(subpath, flags)
886 else:
894 else:
887 self._flags[f] = flags
895 self._flags[f] = flags
888 self._dirty = True
896 self._dirty = True
889
897
890 def copy(self):
898 def copy(self):
891 copy = treemanifest(self._dir)
899 copy = treemanifest(self._dir)
892 copy._node = self._node
900 copy._node = self._node
893 copy._dirty = self._dirty
901 copy._dirty = self._dirty
894 if self._copyfunc is _noop:
902 if self._copyfunc is _noop:
895 def _copyfunc(s):
903 def _copyfunc(s):
896 self._load()
904 self._load()
897 for d in self._dirs:
905 for d in self._dirs:
898 s._dirs[d] = self._dirs[d].copy()
906 s._dirs[d] = self._dirs[d].copy()
899 s._files = dict.copy(self._files)
907 s._files = dict.copy(self._files)
900 s._flags = dict.copy(self._flags)
908 s._flags = dict.copy(self._flags)
901 if self._loadfunc is _noop:
909 if self._loadfunc is _noop:
902 _copyfunc(copy)
910 _copyfunc(copy)
903 else:
911 else:
904 copy._copyfunc = _copyfunc
912 copy._copyfunc = _copyfunc
905 else:
913 else:
906 copy._copyfunc = self._copyfunc
914 copy._copyfunc = self._copyfunc
907 return copy
915 return copy
908
916
909 def filesnotin(self, m2):
917 def filesnotin(self, m2, match=None):
910 '''Set of files in this manifest that are not in the other'''
918 '''Set of files in this manifest that are not in the other'''
919 if match:
920 m1 = self.matches(match)
921 m2 = m2.matches(match)
922 return m1.filesnotin(m2)
923
911 files = set()
924 files = set()
912 def _filesnotin(t1, t2):
925 def _filesnotin(t1, t2):
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
926 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 return
927 return
915 t1._load()
928 t1._load()
916 t2._load()
929 t2._load()
917 for d, m1 in t1._dirs.iteritems():
930 for d, m1 in t1._dirs.iteritems():
918 if d in t2._dirs:
931 if d in t2._dirs:
919 m2 = t2._dirs[d]
932 m2 = t2._dirs[d]
920 _filesnotin(m1, m2)
933 _filesnotin(m1, m2)
921 else:
934 else:
922 files.update(m1.iterkeys())
935 files.update(m1.iterkeys())
923
936
924 for fn in t1._files.iterkeys():
937 for fn in t1._files.iterkeys():
925 if fn not in t2._files:
938 if fn not in t2._files:
926 files.add(t1._subpath(fn))
939 files.add(t1._subpath(fn))
927
940
928 _filesnotin(self, m2)
941 _filesnotin(self, m2)
929 return files
942 return files
930
943
931 @propertycache
944 @propertycache
932 def _alldirs(self):
945 def _alldirs(self):
933 return util.dirs(self)
946 return util.dirs(self)
934
947
935 def dirs(self):
948 def dirs(self):
936 return self._alldirs
949 return self._alldirs
937
950
938 def hasdir(self, dir):
951 def hasdir(self, dir):
939 self._load()
952 self._load()
940 topdir, subdir = _splittopdir(dir)
953 topdir, subdir = _splittopdir(dir)
941 if topdir:
954 if topdir:
942 if topdir in self._dirs:
955 if topdir in self._dirs:
943 return self._dirs[topdir].hasdir(subdir)
956 return self._dirs[topdir].hasdir(subdir)
944 return False
957 return False
945 return (dir + '/') in self._dirs
958 return (dir + '/') in self._dirs
946
959
947 def walk(self, match):
960 def walk(self, match):
948 '''Generates matching file names.
961 '''Generates matching file names.
949
962
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
963 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 an entirely new manifest.
964 an entirely new manifest.
952
965
953 It also reports nonexistent files by marking them bad with match.bad().
966 It also reports nonexistent files by marking them bad with match.bad().
954 '''
967 '''
955 if match.always():
968 if match.always():
956 for f in iter(self):
969 for f in iter(self):
957 yield f
970 yield f
958 return
971 return
959
972
960 fset = set(match.files())
973 fset = set(match.files())
961
974
962 for fn in self._walk(match):
975 for fn in self._walk(match):
963 if fn in fset:
976 if fn in fset:
964 # specified pattern is the exact name
977 # specified pattern is the exact name
965 fset.remove(fn)
978 fset.remove(fn)
966 yield fn
979 yield fn
967
980
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
981 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 # follow that here, too
982 # follow that here, too
970 fset.discard('.')
983 fset.discard('.')
971
984
972 for fn in sorted(fset):
985 for fn in sorted(fset):
973 if not self.hasdir(fn):
986 if not self.hasdir(fn):
974 match.bad(fn, None)
987 match.bad(fn, None)
975
988
976 def _walk(self, match):
989 def _walk(self, match):
977 '''Recursively generates matching file names for walk().'''
990 '''Recursively generates matching file names for walk().'''
978 if not match.visitdir(self._dir[:-1] or '.'):
991 if not match.visitdir(self._dir[:-1] or '.'):
979 return
992 return
980
993
981 # yield this dir's files and walk its submanifests
994 # yield this dir's files and walk its submanifests
982 self._load()
995 self._load()
983 for p in sorted(self._dirs.keys() + self._files.keys()):
996 for p in sorted(self._dirs.keys() + self._files.keys()):
984 if p in self._files:
997 if p in self._files:
985 fullp = self._subpath(p)
998 fullp = self._subpath(p)
986 if match(fullp):
999 if match(fullp):
987 yield fullp
1000 yield fullp
988 else:
1001 else:
989 for f in self._dirs[p]._walk(match):
1002 for f in self._dirs[p]._walk(match):
990 yield f
1003 yield f
991
1004
992 def matches(self, match):
1005 def matches(self, match):
993 '''generate a new manifest filtered by the match argument'''
1006 '''generate a new manifest filtered by the match argument'''
994 if match.always():
1007 if match.always():
995 return self.copy()
1008 return self.copy()
996
1009
997 return self._matches(match)
1010 return self._matches(match)
998
1011
999 def _matches(self, match):
1012 def _matches(self, match):
1000 '''recursively generate a new manifest filtered by the match argument.
1013 '''recursively generate a new manifest filtered by the match argument.
1001 '''
1014 '''
1002
1015
1003 visit = match.visitdir(self._dir[:-1] or '.')
1016 visit = match.visitdir(self._dir[:-1] or '.')
1004 if visit == 'all':
1017 if visit == 'all':
1005 return self.copy()
1018 return self.copy()
1006 ret = treemanifest(self._dir)
1019 ret = treemanifest(self._dir)
1007 if not visit:
1020 if not visit:
1008 return ret
1021 return ret
1009
1022
1010 self._load()
1023 self._load()
1011 for fn in self._files:
1024 for fn in self._files:
1012 fullp = self._subpath(fn)
1025 fullp = self._subpath(fn)
1013 if not match(fullp):
1026 if not match(fullp):
1014 continue
1027 continue
1015 ret._files[fn] = self._files[fn]
1028 ret._files[fn] = self._files[fn]
1016 if fn in self._flags:
1029 if fn in self._flags:
1017 ret._flags[fn] = self._flags[fn]
1030 ret._flags[fn] = self._flags[fn]
1018
1031
1019 for dir, subm in self._dirs.iteritems():
1032 for dir, subm in self._dirs.iteritems():
1020 m = subm._matches(match)
1033 m = subm._matches(match)
1021 if not m._isempty():
1034 if not m._isempty():
1022 ret._dirs[dir] = m
1035 ret._dirs[dir] = m
1023
1036
1024 if not ret._isempty():
1037 if not ret._isempty():
1025 ret._dirty = True
1038 ret._dirty = True
1026 return ret
1039 return ret
1027
1040
1028 def diff(self, m2, clean=False):
1041 def diff(self, m2, match=None, clean=False):
1029 '''Finds changes between the current manifest and m2.
1042 '''Finds changes between the current manifest and m2.
1030
1043
1031 Args:
1044 Args:
1032 m2: the manifest to which this manifest should be compared.
1045 m2: the manifest to which this manifest should be compared.
1033 clean: if true, include files unchanged between these manifests
1046 clean: if true, include files unchanged between these manifests
1034 with a None value in the returned dictionary.
1047 with a None value in the returned dictionary.
1035
1048
1036 The result is returned as a dict with filename as key and
1049 The result is returned as a dict with filename as key and
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1050 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1051 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 in the current/other manifest. Where the file does not exist,
1052 in the current/other manifest. Where the file does not exist,
1040 the nodeid will be None and the flags will be the empty
1053 the nodeid will be None and the flags will be the empty
1041 string.
1054 string.
1042 '''
1055 '''
1056 if match:
1057 m1 = self.matches(match)
1058 m2 = m2.matches(match)
1059 return m1.diff(m2, clean=clean)
1043 result = {}
1060 result = {}
1044 emptytree = treemanifest()
1061 emptytree = treemanifest()
1045 def _diff(t1, t2):
1062 def _diff(t1, t2):
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1063 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 return
1064 return
1048 t1._load()
1065 t1._load()
1049 t2._load()
1066 t2._load()
1050 for d, m1 in t1._dirs.iteritems():
1067 for d, m1 in t1._dirs.iteritems():
1051 m2 = t2._dirs.get(d, emptytree)
1068 m2 = t2._dirs.get(d, emptytree)
1052 _diff(m1, m2)
1069 _diff(m1, m2)
1053
1070
1054 for d, m2 in t2._dirs.iteritems():
1071 for d, m2 in t2._dirs.iteritems():
1055 if d not in t1._dirs:
1072 if d not in t1._dirs:
1056 _diff(emptytree, m2)
1073 _diff(emptytree, m2)
1057
1074
1058 for fn, n1 in t1._files.iteritems():
1075 for fn, n1 in t1._files.iteritems():
1059 fl1 = t1._flags.get(fn, '')
1076 fl1 = t1._flags.get(fn, '')
1060 n2 = t2._files.get(fn, None)
1077 n2 = t2._files.get(fn, None)
1061 fl2 = t2._flags.get(fn, '')
1078 fl2 = t2._flags.get(fn, '')
1062 if n1 != n2 or fl1 != fl2:
1079 if n1 != n2 or fl1 != fl2:
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1080 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 elif clean:
1081 elif clean:
1065 result[t1._subpath(fn)] = None
1082 result[t1._subpath(fn)] = None
1066
1083
1067 for fn, n2 in t2._files.iteritems():
1084 for fn, n2 in t2._files.iteritems():
1068 if fn not in t1._files:
1085 if fn not in t1._files:
1069 fl2 = t2._flags.get(fn, '')
1086 fl2 = t2._flags.get(fn, '')
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1087 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071
1088
1072 _diff(self, m2)
1089 _diff(self, m2)
1073 return result
1090 return result
1074
1091
1075 def unmodifiedsince(self, m2):
1092 def unmodifiedsince(self, m2):
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1093 return not self._dirty and not m2._dirty and self._node == m2._node
1077
1094
1078 def parse(self, text, readsubtree):
1095 def parse(self, text, readsubtree):
1079 for f, n, fl in _parse(text):
1096 for f, n, fl in _parse(text):
1080 if fl == 't':
1097 if fl == 't':
1081 f = f + '/'
1098 f = f + '/'
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1099 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 elif '/' in f:
1100 elif '/' in f:
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1101 # This is a flat manifest, so use __setitem__ and setflag rather
1085 # than assigning directly to _files and _flags, so we can
1102 # than assigning directly to _files and _flags, so we can
1086 # assign a path in a subdirectory, and to mark dirty (compared
1103 # assign a path in a subdirectory, and to mark dirty (compared
1087 # to nullid).
1104 # to nullid).
1088 self[f] = n
1105 self[f] = n
1089 if fl:
1106 if fl:
1090 self.setflag(f, fl)
1107 self.setflag(f, fl)
1091 else:
1108 else:
1092 # Assigning to _files and _flags avoids marking as dirty,
1109 # Assigning to _files and _flags avoids marking as dirty,
1093 # and should be a little faster.
1110 # and should be a little faster.
1094 self._files[f] = n
1111 self._files[f] = n
1095 if fl:
1112 if fl:
1096 self._flags[f] = fl
1113 self._flags[f] = fl
1097
1114
1098 def text(self, usemanifestv2=False):
1115 def text(self, usemanifestv2=False):
1099 """Get the full data of this manifest as a bytestring."""
1116 """Get the full data of this manifest as a bytestring."""
1100 self._load()
1117 self._load()
1101 return _text(self.iterentries(), usemanifestv2)
1118 return _text(self.iterentries(), usemanifestv2)
1102
1119
1103 def dirtext(self, usemanifestv2=False):
1120 def dirtext(self, usemanifestv2=False):
1104 """Get the full data of this directory as a bytestring. Make sure that
1121 """Get the full data of this directory as a bytestring. Make sure that
1105 any submanifests have been written first, so their nodeids are correct.
1122 any submanifests have been written first, so their nodeids are correct.
1106 """
1123 """
1107 self._load()
1124 self._load()
1108 flags = self.flags
1125 flags = self.flags
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1126 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1127 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 return _text(sorted(dirs + files), usemanifestv2)
1128 return _text(sorted(dirs + files), usemanifestv2)
1112
1129
1113 def read(self, gettext, readsubtree):
1130 def read(self, gettext, readsubtree):
1114 def _load_for_read(s):
1131 def _load_for_read(s):
1115 s.parse(gettext(), readsubtree)
1132 s.parse(gettext(), readsubtree)
1116 s._dirty = False
1133 s._dirty = False
1117 self._loadfunc = _load_for_read
1134 self._loadfunc = _load_for_read
1118
1135
1119 def writesubtrees(self, m1, m2, writesubtree):
1136 def writesubtrees(self, m1, m2, writesubtree):
1120 self._load() # for consistency; should never have any effect here
1137 self._load() # for consistency; should never have any effect here
1121 m1._load()
1138 m1._load()
1122 m2._load()
1139 m2._load()
1123 emptytree = treemanifest()
1140 emptytree = treemanifest()
1124 for d, subm in self._dirs.iteritems():
1141 for d, subm in self._dirs.iteritems():
1125 subp1 = m1._dirs.get(d, emptytree)._node
1142 subp1 = m1._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1143 subp2 = m2._dirs.get(d, emptytree)._node
1127 if subp1 == revlog.nullid:
1144 if subp1 == revlog.nullid:
1128 subp1, subp2 = subp2, subp1
1145 subp1, subp2 = subp2, subp1
1129 writesubtree(subm, subp1, subp2)
1146 writesubtree(subm, subp1, subp2)
1130
1147
1131 class manifestrevlog(revlog.revlog):
1148 class manifestrevlog(revlog.revlog):
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1149 '''A revlog that stores manifest texts. This is responsible for caching the
1133 full-text manifest contents.
1150 full-text manifest contents.
1134 '''
1151 '''
1135 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None):
1152 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None):
1136 """Constructs a new manifest revlog
1153 """Constructs a new manifest revlog
1137
1154
1138 `indexfile` - used by extensions to have two manifests at once, like
1155 `indexfile` - used by extensions to have two manifests at once, like
1139 when transitioning between flatmanifeset and treemanifests.
1156 when transitioning between flatmanifeset and treemanifests.
1140 """
1157 """
1141 # During normal operations, we expect to deal with not more than four
1158 # During normal operations, we expect to deal with not more than four
1142 # revs at a time (such as during commit --amend). When rebasing large
1159 # revs at a time (such as during commit --amend). When rebasing large
1143 # stacks of commits, the number can go up, hence the config knob below.
1160 # stacks of commits, the number can go up, hence the config knob below.
1144 cachesize = 4
1161 cachesize = 4
1145 usetreemanifest = False
1162 usetreemanifest = False
1146 usemanifestv2 = False
1163 usemanifestv2 = False
1147 opts = getattr(opener, 'options', None)
1164 opts = getattr(opener, 'options', None)
1148 if opts is not None:
1165 if opts is not None:
1149 cachesize = opts.get('manifestcachesize', cachesize)
1166 cachesize = opts.get('manifestcachesize', cachesize)
1150 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1167 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1151 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1168 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1152
1169
1153 self._treeondisk = usetreemanifest
1170 self._treeondisk = usetreemanifest
1154 self._usemanifestv2 = usemanifestv2
1171 self._usemanifestv2 = usemanifestv2
1155
1172
1156 self._fulltextcache = util.lrucachedict(cachesize)
1173 self._fulltextcache = util.lrucachedict(cachesize)
1157
1174
1158 if dir:
1175 if dir:
1159 assert self._treeondisk, 'opts is %r' % opts
1176 assert self._treeondisk, 'opts is %r' % opts
1160 if not dir.endswith('/'):
1177 if not dir.endswith('/'):
1161 dir = dir + '/'
1178 dir = dir + '/'
1162
1179
1163 if indexfile is None:
1180 if indexfile is None:
1164 indexfile = '00manifest.i'
1181 indexfile = '00manifest.i'
1165 if dir:
1182 if dir:
1166 indexfile = "meta/" + dir + indexfile
1183 indexfile = "meta/" + dir + indexfile
1167
1184
1168 self._dir = dir
1185 self._dir = dir
1169 # The dirlogcache is kept on the root manifest log
1186 # The dirlogcache is kept on the root manifest log
1170 if dir:
1187 if dir:
1171 self._dirlogcache = dirlogcache
1188 self._dirlogcache = dirlogcache
1172 else:
1189 else:
1173 self._dirlogcache = {'': self}
1190 self._dirlogcache = {'': self}
1174
1191
1175 super(manifestrevlog, self).__init__(opener, indexfile,
1192 super(manifestrevlog, self).__init__(opener, indexfile,
1176 checkambig=bool(dir))
1193 checkambig=bool(dir))
1177
1194
1178 @property
1195 @property
1179 def fulltextcache(self):
1196 def fulltextcache(self):
1180 return self._fulltextcache
1197 return self._fulltextcache
1181
1198
1182 def clearcaches(self):
1199 def clearcaches(self):
1183 super(manifestrevlog, self).clearcaches()
1200 super(manifestrevlog, self).clearcaches()
1184 self._fulltextcache.clear()
1201 self._fulltextcache.clear()
1185 self._dirlogcache = {'': self}
1202 self._dirlogcache = {'': self}
1186
1203
1187 def dirlog(self, dir):
1204 def dirlog(self, dir):
1188 if dir:
1205 if dir:
1189 assert self._treeondisk
1206 assert self._treeondisk
1190 if dir not in self._dirlogcache:
1207 if dir not in self._dirlogcache:
1191 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1208 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1192 self._dirlogcache)
1209 self._dirlogcache)
1193 return self._dirlogcache[dir]
1210 return self._dirlogcache[dir]
1194
1211
1195 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1212 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1196 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1213 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1197 and not self._usemanifestv2):
1214 and not self._usemanifestv2):
1198 # If our first parent is in the manifest cache, we can
1215 # If our first parent is in the manifest cache, we can
1199 # compute a delta here using properties we know about the
1216 # compute a delta here using properties we know about the
1200 # manifest up-front, which may save time later for the
1217 # manifest up-front, which may save time later for the
1201 # revlog layer.
1218 # revlog layer.
1202
1219
1203 _checkforbidden(added)
1220 _checkforbidden(added)
1204 # combine the changed lists into one sorted iterator
1221 # combine the changed lists into one sorted iterator
1205 work = heapq.merge([(x, False) for x in added],
1222 work = heapq.merge([(x, False) for x in added],
1206 [(x, True) for x in removed])
1223 [(x, True) for x in removed])
1207
1224
1208 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1225 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1209 cachedelta = self.rev(p1), deltatext
1226 cachedelta = self.rev(p1), deltatext
1210 text = util.buffer(arraytext)
1227 text = util.buffer(arraytext)
1211 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1228 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1212 else:
1229 else:
1213 # The first parent manifest isn't already loaded, so we'll
1230 # The first parent manifest isn't already loaded, so we'll
1214 # just encode a fulltext of the manifest and pass that
1231 # just encode a fulltext of the manifest and pass that
1215 # through to the revlog layer, and let it handle the delta
1232 # through to the revlog layer, and let it handle the delta
1216 # process.
1233 # process.
1217 if self._treeondisk:
1234 if self._treeondisk:
1218 assert readtree, "readtree must be set for treemanifest writes"
1235 assert readtree, "readtree must be set for treemanifest writes"
1219 m1 = readtree(self._dir, p1)
1236 m1 = readtree(self._dir, p1)
1220 m2 = readtree(self._dir, p2)
1237 m2 = readtree(self._dir, p2)
1221 n = self._addtree(m, transaction, link, m1, m2, readtree)
1238 n = self._addtree(m, transaction, link, m1, m2, readtree)
1222 arraytext = None
1239 arraytext = None
1223 else:
1240 else:
1224 text = m.text(self._usemanifestv2)
1241 text = m.text(self._usemanifestv2)
1225 n = self.addrevision(text, transaction, link, p1, p2)
1242 n = self.addrevision(text, transaction, link, p1, p2)
1226 arraytext = array.array('c', text)
1243 arraytext = array.array('c', text)
1227
1244
1228 if arraytext is not None:
1245 if arraytext is not None:
1229 self.fulltextcache[n] = arraytext
1246 self.fulltextcache[n] = arraytext
1230
1247
1231 return n
1248 return n
1232
1249
1233 def _addtree(self, m, transaction, link, m1, m2, readtree):
1250 def _addtree(self, m, transaction, link, m1, m2, readtree):
1234 # If the manifest is unchanged compared to one parent,
1251 # If the manifest is unchanged compared to one parent,
1235 # don't write a new revision
1252 # don't write a new revision
1236 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1253 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1237 return m.node()
1254 return m.node()
1238 def writesubtree(subm, subp1, subp2):
1255 def writesubtree(subm, subp1, subp2):
1239 sublog = self.dirlog(subm.dir())
1256 sublog = self.dirlog(subm.dir())
1240 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1257 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1241 readtree=readtree)
1258 readtree=readtree)
1242 m.writesubtrees(m1, m2, writesubtree)
1259 m.writesubtrees(m1, m2, writesubtree)
1243 text = m.dirtext(self._usemanifestv2)
1260 text = m.dirtext(self._usemanifestv2)
1244 # Double-check whether contents are unchanged to one parent
1261 # Double-check whether contents are unchanged to one parent
1245 if text == m1.dirtext(self._usemanifestv2):
1262 if text == m1.dirtext(self._usemanifestv2):
1246 n = m1.node()
1263 n = m1.node()
1247 elif text == m2.dirtext(self._usemanifestv2):
1264 elif text == m2.dirtext(self._usemanifestv2):
1248 n = m2.node()
1265 n = m2.node()
1249 else:
1266 else:
1250 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1267 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1251 # Save nodeid so parent manifest can calculate its nodeid
1268 # Save nodeid so parent manifest can calculate its nodeid
1252 m.setnode(n)
1269 m.setnode(n)
1253 return n
1270 return n
1254
1271
1255 class manifestlog(object):
1272 class manifestlog(object):
1256 """A collection class representing the collection of manifest snapshots
1273 """A collection class representing the collection of manifest snapshots
1257 referenced by commits in the repository.
1274 referenced by commits in the repository.
1258
1275
1259 In this situation, 'manifest' refers to the abstract concept of a snapshot
1276 In this situation, 'manifest' refers to the abstract concept of a snapshot
1260 of the list of files in the given commit. Consumers of the output of this
1277 of the list of files in the given commit. Consumers of the output of this
1261 class do not care about the implementation details of the actual manifests
1278 class do not care about the implementation details of the actual manifests
1262 they receive (i.e. tree or flat or lazily loaded, etc)."""
1279 they receive (i.e. tree or flat or lazily loaded, etc)."""
1263 def __init__(self, opener, repo):
1280 def __init__(self, opener, repo):
1264 usetreemanifest = False
1281 usetreemanifest = False
1265 cachesize = 4
1282 cachesize = 4
1266
1283
1267 opts = getattr(opener, 'options', None)
1284 opts = getattr(opener, 'options', None)
1268 if opts is not None:
1285 if opts is not None:
1269 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1286 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1270 cachesize = opts.get('manifestcachesize', cachesize)
1287 cachesize = opts.get('manifestcachesize', cachesize)
1271 self._treeinmem = usetreemanifest
1288 self._treeinmem = usetreemanifest
1272
1289
1273 self._oldmanifest = repo._constructmanifest()
1290 self._oldmanifest = repo._constructmanifest()
1274 self._revlog = self._oldmanifest
1291 self._revlog = self._oldmanifest
1275
1292
1276 # A cache of the manifestctx or treemanifestctx for each directory
1293 # A cache of the manifestctx or treemanifestctx for each directory
1277 self._dirmancache = {}
1294 self._dirmancache = {}
1278 self._dirmancache[''] = util.lrucachedict(cachesize)
1295 self._dirmancache[''] = util.lrucachedict(cachesize)
1279
1296
1280 self.cachesize = cachesize
1297 self.cachesize = cachesize
1281
1298
1282 def __getitem__(self, node):
1299 def __getitem__(self, node):
1283 """Retrieves the manifest instance for the given node. Throws a
1300 """Retrieves the manifest instance for the given node. Throws a
1284 LookupError if not found.
1301 LookupError if not found.
1285 """
1302 """
1286 return self.get('', node)
1303 return self.get('', node)
1287
1304
1288 def get(self, dir, node, verify=True):
1305 def get(self, dir, node, verify=True):
1289 """Retrieves the manifest instance for the given node. Throws a
1306 """Retrieves the manifest instance for the given node. Throws a
1290 LookupError if not found.
1307 LookupError if not found.
1291
1308
1292 `verify` - if True an exception will be thrown if the node is not in
1309 `verify` - if True an exception will be thrown if the node is not in
1293 the revlog
1310 the revlog
1294 """
1311 """
1295 if node in self._dirmancache.get(dir, ()):
1312 if node in self._dirmancache.get(dir, ()):
1296 cachemf = self._dirmancache[dir][node]
1313 cachemf = self._dirmancache[dir][node]
1297 # The old manifest may put non-ctx manifests in the cache, so
1314 # The old manifest may put non-ctx manifests in the cache, so
1298 # skip those since they don't implement the full api.
1315 # skip those since they don't implement the full api.
1299 if (isinstance(cachemf, manifestctx) or
1316 if (isinstance(cachemf, manifestctx) or
1300 isinstance(cachemf, treemanifestctx)):
1317 isinstance(cachemf, treemanifestctx)):
1301 return cachemf
1318 return cachemf
1302
1319
1303 if dir:
1320 if dir:
1304 if self._revlog._treeondisk:
1321 if self._revlog._treeondisk:
1305 if verify:
1322 if verify:
1306 dirlog = self._revlog.dirlog(dir)
1323 dirlog = self._revlog.dirlog(dir)
1307 if node not in dirlog.nodemap:
1324 if node not in dirlog.nodemap:
1308 raise LookupError(node, dirlog.indexfile,
1325 raise LookupError(node, dirlog.indexfile,
1309 _('no node'))
1326 _('no node'))
1310 m = treemanifestctx(self, dir, node)
1327 m = treemanifestctx(self, dir, node)
1311 else:
1328 else:
1312 raise error.Abort(
1329 raise error.Abort(
1313 _("cannot ask for manifest directory '%s' in a flat "
1330 _("cannot ask for manifest directory '%s' in a flat "
1314 "manifest") % dir)
1331 "manifest") % dir)
1315 else:
1332 else:
1316 if verify:
1333 if verify:
1317 if node not in self._revlog.nodemap:
1334 if node not in self._revlog.nodemap:
1318 raise LookupError(node, self._revlog.indexfile,
1335 raise LookupError(node, self._revlog.indexfile,
1319 _('no node'))
1336 _('no node'))
1320 if self._treeinmem:
1337 if self._treeinmem:
1321 m = treemanifestctx(self, '', node)
1338 m = treemanifestctx(self, '', node)
1322 else:
1339 else:
1323 m = manifestctx(self, node)
1340 m = manifestctx(self, node)
1324
1341
1325 if node != revlog.nullid:
1342 if node != revlog.nullid:
1326 mancache = self._dirmancache.get(dir)
1343 mancache = self._dirmancache.get(dir)
1327 if not mancache:
1344 if not mancache:
1328 mancache = util.lrucachedict(self.cachesize)
1345 mancache = util.lrucachedict(self.cachesize)
1329 self._dirmancache[dir] = mancache
1346 self._dirmancache[dir] = mancache
1330 mancache[node] = m
1347 mancache[node] = m
1331 return m
1348 return m
1332
1349
1333 def clearcaches(self):
1350 def clearcaches(self):
1334 self._dirmancache.clear()
1351 self._dirmancache.clear()
1335 self._revlog.clearcaches()
1352 self._revlog.clearcaches()
1336
1353
1337 class memmanifestctx(object):
1354 class memmanifestctx(object):
1338 def __init__(self, manifestlog):
1355 def __init__(self, manifestlog):
1339 self._manifestlog = manifestlog
1356 self._manifestlog = manifestlog
1340 self._manifestdict = manifestdict()
1357 self._manifestdict = manifestdict()
1341
1358
1342 def _revlog(self):
1359 def _revlog(self):
1343 return self._manifestlog._revlog
1360 return self._manifestlog._revlog
1344
1361
1345 def new(self):
1362 def new(self):
1346 return memmanifestctx(self._manifestlog)
1363 return memmanifestctx(self._manifestlog)
1347
1364
1348 def copy(self):
1365 def copy(self):
1349 memmf = memmanifestctx(self._manifestlog)
1366 memmf = memmanifestctx(self._manifestlog)
1350 memmf._manifestdict = self.read().copy()
1367 memmf._manifestdict = self.read().copy()
1351 return memmf
1368 return memmf
1352
1369
1353 def read(self):
1370 def read(self):
1354 return self._manifestdict
1371 return self._manifestdict
1355
1372
1356 def write(self, transaction, link, p1, p2, added, removed):
1373 def write(self, transaction, link, p1, p2, added, removed):
1357 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1374 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1358 added, removed)
1375 added, removed)
1359
1376
1360 class manifestctx(object):
1377 class manifestctx(object):
1361 """A class representing a single revision of a manifest, including its
1378 """A class representing a single revision of a manifest, including its
1362 contents, its parent revs, and its linkrev.
1379 contents, its parent revs, and its linkrev.
1363 """
1380 """
1364 def __init__(self, manifestlog, node):
1381 def __init__(self, manifestlog, node):
1365 self._manifestlog = manifestlog
1382 self._manifestlog = manifestlog
1366 self._data = None
1383 self._data = None
1367
1384
1368 self._node = node
1385 self._node = node
1369
1386
1370 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1387 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1371 # but let's add it later when something needs it and we can load it
1388 # but let's add it later when something needs it and we can load it
1372 # lazily.
1389 # lazily.
1373 #self.p1, self.p2 = revlog.parents(node)
1390 #self.p1, self.p2 = revlog.parents(node)
1374 #rev = revlog.rev(node)
1391 #rev = revlog.rev(node)
1375 #self.linkrev = revlog.linkrev(rev)
1392 #self.linkrev = revlog.linkrev(rev)
1376
1393
1377 def _revlog(self):
1394 def _revlog(self):
1378 return self._manifestlog._revlog
1395 return self._manifestlog._revlog
1379
1396
1380 def node(self):
1397 def node(self):
1381 return self._node
1398 return self._node
1382
1399
1383 def new(self):
1400 def new(self):
1384 return memmanifestctx(self._manifestlog)
1401 return memmanifestctx(self._manifestlog)
1385
1402
1386 def copy(self):
1403 def copy(self):
1387 memmf = memmanifestctx(self._manifestlog)
1404 memmf = memmanifestctx(self._manifestlog)
1388 memmf._manifestdict = self.read().copy()
1405 memmf._manifestdict = self.read().copy()
1389 return memmf
1406 return memmf
1390
1407
1391 @propertycache
1408 @propertycache
1392 def parents(self):
1409 def parents(self):
1393 return self._revlog().parents(self._node)
1410 return self._revlog().parents(self._node)
1394
1411
1395 def read(self):
1412 def read(self):
1396 if self._data is None:
1413 if self._data is None:
1397 if self._node == revlog.nullid:
1414 if self._node == revlog.nullid:
1398 self._data = manifestdict()
1415 self._data = manifestdict()
1399 else:
1416 else:
1400 rl = self._revlog()
1417 rl = self._revlog()
1401 text = rl.revision(self._node)
1418 text = rl.revision(self._node)
1402 arraytext = array.array('c', text)
1419 arraytext = array.array('c', text)
1403 rl._fulltextcache[self._node] = arraytext
1420 rl._fulltextcache[self._node] = arraytext
1404 self._data = manifestdict(text)
1421 self._data = manifestdict(text)
1405 return self._data
1422 return self._data
1406
1423
1407 def readfast(self, shallow=False):
1424 def readfast(self, shallow=False):
1408 '''Calls either readdelta or read, based on which would be less work.
1425 '''Calls either readdelta or read, based on which would be less work.
1409 readdelta is called if the delta is against the p1, and therefore can be
1426 readdelta is called if the delta is against the p1, and therefore can be
1410 read quickly.
1427 read quickly.
1411
1428
1412 If `shallow` is True, nothing changes since this is a flat manifest.
1429 If `shallow` is True, nothing changes since this is a flat manifest.
1413 '''
1430 '''
1414 rl = self._revlog()
1431 rl = self._revlog()
1415 r = rl.rev(self._node)
1432 r = rl.rev(self._node)
1416 deltaparent = rl.deltaparent(r)
1433 deltaparent = rl.deltaparent(r)
1417 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1434 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1418 return self.readdelta()
1435 return self.readdelta()
1419 return self.read()
1436 return self.read()
1420
1437
1421 def readdelta(self, shallow=False):
1438 def readdelta(self, shallow=False):
1422 '''Returns a manifest containing just the entries that are present
1439 '''Returns a manifest containing just the entries that are present
1423 in this manifest, but not in its p1 manifest. This is efficient to read
1440 in this manifest, but not in its p1 manifest. This is efficient to read
1424 if the revlog delta is already p1.
1441 if the revlog delta is already p1.
1425
1442
1426 Changing the value of `shallow` has no effect on flat manifests.
1443 Changing the value of `shallow` has no effect on flat manifests.
1427 '''
1444 '''
1428 revlog = self._revlog()
1445 revlog = self._revlog()
1429 if revlog._usemanifestv2:
1446 if revlog._usemanifestv2:
1430 # Need to perform a slow delta
1447 # Need to perform a slow delta
1431 r0 = revlog.deltaparent(revlog.rev(self._node))
1448 r0 = revlog.deltaparent(revlog.rev(self._node))
1432 m0 = self._manifestlog[revlog.node(r0)].read()
1449 m0 = self._manifestlog[revlog.node(r0)].read()
1433 m1 = self.read()
1450 m1 = self.read()
1434 md = manifestdict()
1451 md = manifestdict()
1435 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1452 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1436 if n1:
1453 if n1:
1437 md[f] = n1
1454 md[f] = n1
1438 if fl1:
1455 if fl1:
1439 md.setflag(f, fl1)
1456 md.setflag(f, fl1)
1440 return md
1457 return md
1441
1458
1442 r = revlog.rev(self._node)
1459 r = revlog.rev(self._node)
1443 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1460 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1444 return manifestdict(d)
1461 return manifestdict(d)
1445
1462
1446 def find(self, key):
1463 def find(self, key):
1447 return self.read().find(key)
1464 return self.read().find(key)
1448
1465
1449 class memtreemanifestctx(object):
1466 class memtreemanifestctx(object):
1450 def __init__(self, manifestlog, dir=''):
1467 def __init__(self, manifestlog, dir=''):
1451 self._manifestlog = manifestlog
1468 self._manifestlog = manifestlog
1452 self._dir = dir
1469 self._dir = dir
1453 self._treemanifest = treemanifest()
1470 self._treemanifest = treemanifest()
1454
1471
1455 def _revlog(self):
1472 def _revlog(self):
1456 return self._manifestlog._revlog
1473 return self._manifestlog._revlog
1457
1474
1458 def new(self, dir=''):
1475 def new(self, dir=''):
1459 return memtreemanifestctx(self._manifestlog, dir=dir)
1476 return memtreemanifestctx(self._manifestlog, dir=dir)
1460
1477
1461 def copy(self):
1478 def copy(self):
1462 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1479 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1463 memmf._treemanifest = self._treemanifest.copy()
1480 memmf._treemanifest = self._treemanifest.copy()
1464 return memmf
1481 return memmf
1465
1482
1466 def read(self):
1483 def read(self):
1467 return self._treemanifest
1484 return self._treemanifest
1468
1485
1469 def write(self, transaction, link, p1, p2, added, removed):
1486 def write(self, transaction, link, p1, p2, added, removed):
1470 def readtree(dir, node):
1487 def readtree(dir, node):
1471 return self._manifestlog.get(dir, node).read()
1488 return self._manifestlog.get(dir, node).read()
1472 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1489 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1473 added, removed, readtree=readtree)
1490 added, removed, readtree=readtree)
1474
1491
1475 class treemanifestctx(object):
1492 class treemanifestctx(object):
1476 def __init__(self, manifestlog, dir, node):
1493 def __init__(self, manifestlog, dir, node):
1477 self._manifestlog = manifestlog
1494 self._manifestlog = manifestlog
1478 self._dir = dir
1495 self._dir = dir
1479 self._data = None
1496 self._data = None
1480
1497
1481 self._node = node
1498 self._node = node
1482
1499
1483 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1500 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1484 # we can instantiate treemanifestctx objects for directories we don't
1501 # we can instantiate treemanifestctx objects for directories we don't
1485 # have on disk.
1502 # have on disk.
1486 #self.p1, self.p2 = revlog.parents(node)
1503 #self.p1, self.p2 = revlog.parents(node)
1487 #rev = revlog.rev(node)
1504 #rev = revlog.rev(node)
1488 #self.linkrev = revlog.linkrev(rev)
1505 #self.linkrev = revlog.linkrev(rev)
1489
1506
1490 def _revlog(self):
1507 def _revlog(self):
1491 return self._manifestlog._revlog.dirlog(self._dir)
1508 return self._manifestlog._revlog.dirlog(self._dir)
1492
1509
1493 def read(self):
1510 def read(self):
1494 if self._data is None:
1511 if self._data is None:
1495 rl = self._revlog()
1512 rl = self._revlog()
1496 if self._node == revlog.nullid:
1513 if self._node == revlog.nullid:
1497 self._data = treemanifest()
1514 self._data = treemanifest()
1498 elif rl._treeondisk:
1515 elif rl._treeondisk:
1499 m = treemanifest(dir=self._dir)
1516 m = treemanifest(dir=self._dir)
1500 def gettext():
1517 def gettext():
1501 return rl.revision(self._node)
1518 return rl.revision(self._node)
1502 def readsubtree(dir, subm):
1519 def readsubtree(dir, subm):
1503 # Set verify to False since we need to be able to create
1520 # Set verify to False since we need to be able to create
1504 # subtrees for trees that don't exist on disk.
1521 # subtrees for trees that don't exist on disk.
1505 return self._manifestlog.get(dir, subm, verify=False).read()
1522 return self._manifestlog.get(dir, subm, verify=False).read()
1506 m.read(gettext, readsubtree)
1523 m.read(gettext, readsubtree)
1507 m.setnode(self._node)
1524 m.setnode(self._node)
1508 self._data = m
1525 self._data = m
1509 else:
1526 else:
1510 text = rl.revision(self._node)
1527 text = rl.revision(self._node)
1511 arraytext = array.array('c', text)
1528 arraytext = array.array('c', text)
1512 rl.fulltextcache[self._node] = arraytext
1529 rl.fulltextcache[self._node] = arraytext
1513 self._data = treemanifest(dir=self._dir, text=text)
1530 self._data = treemanifest(dir=self._dir, text=text)
1514
1531
1515 return self._data
1532 return self._data
1516
1533
1517 def node(self):
1534 def node(self):
1518 return self._node
1535 return self._node
1519
1536
1520 def new(self, dir=''):
1537 def new(self, dir=''):
1521 return memtreemanifestctx(self._manifestlog, dir=dir)
1538 return memtreemanifestctx(self._manifestlog, dir=dir)
1522
1539
1523 def copy(self):
1540 def copy(self):
1524 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1541 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1525 memmf._treemanifest = self.read().copy()
1542 memmf._treemanifest = self.read().copy()
1526 return memmf
1543 return memmf
1527
1544
1528 @propertycache
1545 @propertycache
1529 def parents(self):
1546 def parents(self):
1530 return self._revlog().parents(self._node)
1547 return self._revlog().parents(self._node)
1531
1548
1532 def readdelta(self, shallow=False):
1549 def readdelta(self, shallow=False):
1533 '''Returns a manifest containing just the entries that are present
1550 '''Returns a manifest containing just the entries that are present
1534 in this manifest, but not in its p1 manifest. This is efficient to read
1551 in this manifest, but not in its p1 manifest. This is efficient to read
1535 if the revlog delta is already p1.
1552 if the revlog delta is already p1.
1536
1553
1537 If `shallow` is True, this will read the delta for this directory,
1554 If `shallow` is True, this will read the delta for this directory,
1538 without recursively reading subdirectory manifests. Instead, any
1555 without recursively reading subdirectory manifests. Instead, any
1539 subdirectory entry will be reported as it appears in the manifest, i.e.
1556 subdirectory entry will be reported as it appears in the manifest, i.e.
1540 the subdirectory will be reported among files and distinguished only by
1557 the subdirectory will be reported among files and distinguished only by
1541 its 't' flag.
1558 its 't' flag.
1542 '''
1559 '''
1543 revlog = self._revlog()
1560 revlog = self._revlog()
1544 if shallow and not revlog._usemanifestv2:
1561 if shallow and not revlog._usemanifestv2:
1545 r = revlog.rev(self._node)
1562 r = revlog.rev(self._node)
1546 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1563 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1547 return manifestdict(d)
1564 return manifestdict(d)
1548 else:
1565 else:
1549 # Need to perform a slow delta
1566 # Need to perform a slow delta
1550 r0 = revlog.deltaparent(revlog.rev(self._node))
1567 r0 = revlog.deltaparent(revlog.rev(self._node))
1551 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1568 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1552 m1 = self.read()
1569 m1 = self.read()
1553 md = treemanifest(dir=self._dir)
1570 md = treemanifest(dir=self._dir)
1554 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1571 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1555 if n1:
1572 if n1:
1556 md[f] = n1
1573 md[f] = n1
1557 if fl1:
1574 if fl1:
1558 md.setflag(f, fl1)
1575 md.setflag(f, fl1)
1559 return md
1576 return md
1560
1577
1561 def readfast(self, shallow=False):
1578 def readfast(self, shallow=False):
1562 '''Calls either readdelta or read, based on which would be less work.
1579 '''Calls either readdelta or read, based on which would be less work.
1563 readdelta is called if the delta is against the p1, and therefore can be
1580 readdelta is called if the delta is against the p1, and therefore can be
1564 read quickly.
1581 read quickly.
1565
1582
1566 If `shallow` is True, it only returns the entries from this manifest,
1583 If `shallow` is True, it only returns the entries from this manifest,
1567 and not any submanifests.
1584 and not any submanifests.
1568 '''
1585 '''
1569 rl = self._revlog()
1586 rl = self._revlog()
1570 r = rl.rev(self._node)
1587 r = rl.rev(self._node)
1571 deltaparent = rl.deltaparent(r)
1588 deltaparent = rl.deltaparent(r)
1572 if (deltaparent != revlog.nullrev and
1589 if (deltaparent != revlog.nullrev and
1573 deltaparent in rl.parentrevs(r)):
1590 deltaparent in rl.parentrevs(r)):
1574 return self.readdelta(shallow=shallow)
1591 return self.readdelta(shallow=shallow)
1575
1592
1576 if shallow:
1593 if shallow:
1577 return manifestdict(rl.revision(self._node))
1594 return manifestdict(rl.revision(self._node))
1578 else:
1595 else:
1579 return self.read()
1596 return self.read()
1580
1597
1581 def find(self, key):
1598 def find(self, key):
1582 return self.read().find(key)
1599 return self.read().find(key)
@@ -1,471 +1,471 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import binascii
3 import binascii
4 import itertools
4 import itertools
5 import silenttestrunner
5 import silenttestrunner
6 import unittest
6 import unittest
7
7
8 from mercurial import (
8 from mercurial import (
9 manifest as manifestmod,
9 manifest as manifestmod,
10 match as matchmod,
10 match as matchmod,
11 )
11 )
12
12
13 EMTPY_MANIFEST = ''
13 EMTPY_MANIFEST = ''
14 EMTPY_MANIFEST_V2 = '\0\n'
14 EMTPY_MANIFEST_V2 = '\0\n'
15
15
16 HASH_1 = '1' * 40
16 HASH_1 = '1' * 40
17 BIN_HASH_1 = binascii.unhexlify(HASH_1)
17 BIN_HASH_1 = binascii.unhexlify(HASH_1)
18 HASH_2 = 'f' * 40
18 HASH_2 = 'f' * 40
19 BIN_HASH_2 = binascii.unhexlify(HASH_2)
19 BIN_HASH_2 = binascii.unhexlify(HASH_2)
20 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
20 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
21 BIN_HASH_3 = binascii.unhexlify(HASH_3)
21 BIN_HASH_3 = binascii.unhexlify(HASH_3)
22 A_SHORT_MANIFEST = (
22 A_SHORT_MANIFEST = (
23 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
23 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
24 'foo\0%(hash1)s%(flag1)s\n'
24 'foo\0%(hash1)s%(flag1)s\n'
25 ) % {'hash1': HASH_1,
25 ) % {'hash1': HASH_1,
26 'flag1': '',
26 'flag1': '',
27 'hash2': HASH_2,
27 'hash2': HASH_2,
28 'flag2': 'l',
28 'flag2': 'l',
29 }
29 }
30
30
31 # Same data as A_SHORT_MANIFEST
31 # Same data as A_SHORT_MANIFEST
32 A_SHORT_MANIFEST_V2 = (
32 A_SHORT_MANIFEST_V2 = (
33 '\0\n'
33 '\0\n'
34 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
34 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
35 '\x00foo\0%(flag1)s\n%(hash1)s\n'
35 '\x00foo\0%(flag1)s\n%(hash1)s\n'
36 ) % {'hash1': BIN_HASH_1,
36 ) % {'hash1': BIN_HASH_1,
37 'flag1': '',
37 'flag1': '',
38 'hash2': BIN_HASH_2,
38 'hash2': BIN_HASH_2,
39 'flag2': 'l',
39 'flag2': 'l',
40 }
40 }
41
41
42 # Same data as A_SHORT_MANIFEST
42 # Same data as A_SHORT_MANIFEST
43 A_METADATA_MANIFEST = (
43 A_METADATA_MANIFEST = (
44 '\0foo\0bar\n'
44 '\0foo\0bar\n'
45 '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata
45 '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata
46 '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata
46 '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata
47 ) % {'hash1': BIN_HASH_1,
47 ) % {'hash1': BIN_HASH_1,
48 'flag1': '',
48 'flag1': '',
49 'hash2': BIN_HASH_2,
49 'hash2': BIN_HASH_2,
50 'flag2': 'l',
50 'flag2': 'l',
51 }
51 }
52
52
53 A_STEM_COMPRESSED_MANIFEST = (
53 A_STEM_COMPRESSED_MANIFEST = (
54 '\0\n'
54 '\0\n'
55 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
55 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
56 '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars
56 '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars
57 '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters
57 '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters
58 '\x00%(verylongdir)sx/x\0\n%(hash1)s\n'
58 '\x00%(verylongdir)sx/x\0\n%(hash1)s\n'
59 '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars
59 '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars
60 ) % {'hash1': BIN_HASH_1,
60 ) % {'hash1': BIN_HASH_1,
61 'flag1': '',
61 'flag1': '',
62 'hash2': BIN_HASH_2,
62 'hash2': BIN_HASH_2,
63 'flag2': 'l',
63 'flag2': 'l',
64 'verylongdir': 255 * 'x',
64 'verylongdir': 255 * 'x',
65 }
65 }
66
66
67 A_DEEPER_MANIFEST = (
67 A_DEEPER_MANIFEST = (
68 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
68 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
69 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
69 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
70 'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
70 'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
71 'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
71 'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
72 'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
72 'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
73 'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
73 'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
74 'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
74 'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
75 'a/b/dog.py\0%(hash3)s%(flag1)s\n'
75 'a/b/dog.py\0%(hash3)s%(flag1)s\n'
76 'a/b/fish.py\0%(hash2)s%(flag1)s\n'
76 'a/b/fish.py\0%(hash2)s%(flag1)s\n'
77 'a/c/london.py\0%(hash3)s%(flag2)s\n'
77 'a/c/london.py\0%(hash3)s%(flag2)s\n'
78 'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
78 'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
79 'a/c/paris.py\0%(hash2)s%(flag1)s\n'
79 'a/c/paris.py\0%(hash2)s%(flag1)s\n'
80 'a/d/apple.py\0%(hash3)s%(flag1)s\n'
80 'a/d/apple.py\0%(hash3)s%(flag1)s\n'
81 'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
81 'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
82 'a/green.py\0%(hash1)s%(flag2)s\n'
82 'a/green.py\0%(hash1)s%(flag2)s\n'
83 'a/purple.py\0%(hash2)s%(flag1)s\n'
83 'a/purple.py\0%(hash2)s%(flag1)s\n'
84 'app.py\0%(hash3)s%(flag1)s\n'
84 'app.py\0%(hash3)s%(flag1)s\n'
85 'readme.txt\0%(hash2)s%(flag1)s\n'
85 'readme.txt\0%(hash2)s%(flag1)s\n'
86 ) % {'hash1': HASH_1,
86 ) % {'hash1': HASH_1,
87 'flag1': '',
87 'flag1': '',
88 'hash2': HASH_2,
88 'hash2': HASH_2,
89 'flag2': 'l',
89 'flag2': 'l',
90 'hash3': HASH_3,
90 'hash3': HASH_3,
91 }
91 }
92
92
93 HUGE_MANIFEST_ENTRIES = 200001
93 HUGE_MANIFEST_ENTRIES = 200001
94
94
95 A_HUGE_MANIFEST = ''.join(sorted(
95 A_HUGE_MANIFEST = ''.join(sorted(
96 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
96 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
97 itertools.izip(xrange(200001),
97 itertools.izip(xrange(200001),
98 itertools.cycle((HASH_1, HASH_2)),
98 itertools.cycle((HASH_1, HASH_2)),
99 itertools.cycle(('', 'x', 'l')))))
99 itertools.cycle(('', 'x', 'l')))))
100
100
101 class basemanifesttests(object):
101 class basemanifesttests(object):
102 def parsemanifest(self, text):
102 def parsemanifest(self, text):
103 raise NotImplementedError('parsemanifest not implemented by test case')
103 raise NotImplementedError('parsemanifest not implemented by test case')
104
104
105 def assertIn(self, thing, container, msg=None):
105 def assertIn(self, thing, container, msg=None):
106 # assertIn new in 2.7, use it if available, otherwise polyfill
106 # assertIn new in 2.7, use it if available, otherwise polyfill
107 sup = getattr(unittest.TestCase, 'assertIn', False)
107 sup = getattr(unittest.TestCase, 'assertIn', False)
108 if sup:
108 if sup:
109 return sup(self, thing, container, msg=msg)
109 return sup(self, thing, container, msg=msg)
110 if not msg:
110 if not msg:
111 msg = 'Expected %r in %r' % (thing, container)
111 msg = 'Expected %r in %r' % (thing, container)
112 self.assert_(thing in container, msg)
112 self.assert_(thing in container, msg)
113
113
114 def testEmptyManifest(self):
114 def testEmptyManifest(self):
115 m = self.parsemanifest(EMTPY_MANIFEST)
115 m = self.parsemanifest(EMTPY_MANIFEST)
116 self.assertEqual(0, len(m))
116 self.assertEqual(0, len(m))
117 self.assertEqual([], list(m))
117 self.assertEqual([], list(m))
118
118
119 def testEmptyManifestv2(self):
119 def testEmptyManifestv2(self):
120 m = self.parsemanifest(EMTPY_MANIFEST_V2)
120 m = self.parsemanifest(EMTPY_MANIFEST_V2)
121 self.assertEqual(0, len(m))
121 self.assertEqual(0, len(m))
122 self.assertEqual([], list(m))
122 self.assertEqual([], list(m))
123
123
124 def testManifest(self):
124 def testManifest(self):
125 m = self.parsemanifest(A_SHORT_MANIFEST)
125 m = self.parsemanifest(A_SHORT_MANIFEST)
126 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
126 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
127 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
127 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
128 self.assertEqual('l', m.flags('bar/baz/qux.py'))
128 self.assertEqual('l', m.flags('bar/baz/qux.py'))
129 self.assertEqual(BIN_HASH_1, m['foo'])
129 self.assertEqual(BIN_HASH_1, m['foo'])
130 self.assertEqual('', m.flags('foo'))
130 self.assertEqual('', m.flags('foo'))
131 self.assertRaises(KeyError, lambda : m['wat'])
131 self.assertRaises(KeyError, lambda : m['wat'])
132
132
133 def testParseManifestV2(self):
133 def testParseManifestV2(self):
134 m1 = self.parsemanifest(A_SHORT_MANIFEST)
134 m1 = self.parsemanifest(A_SHORT_MANIFEST)
135 m2 = self.parsemanifest(A_SHORT_MANIFEST_V2)
135 m2 = self.parsemanifest(A_SHORT_MANIFEST_V2)
136 # Should have same content as A_SHORT_MANIFEST
136 # Should have same content as A_SHORT_MANIFEST
137 self.assertEqual(m1.text(), m2.text())
137 self.assertEqual(m1.text(), m2.text())
138
138
139 def testParseManifestMetadata(self):
139 def testParseManifestMetadata(self):
140 # Metadata is for future-proofing and should be accepted but ignored
140 # Metadata is for future-proofing and should be accepted but ignored
141 m = self.parsemanifest(A_METADATA_MANIFEST)
141 m = self.parsemanifest(A_METADATA_MANIFEST)
142 self.assertEqual(A_SHORT_MANIFEST, m.text())
142 self.assertEqual(A_SHORT_MANIFEST, m.text())
143
143
144 def testParseManifestStemCompression(self):
144 def testParseManifestStemCompression(self):
145 m = self.parsemanifest(A_STEM_COMPRESSED_MANIFEST)
145 m = self.parsemanifest(A_STEM_COMPRESSED_MANIFEST)
146 self.assertIn('bar/baz/qux.py', m)
146 self.assertIn('bar/baz/qux.py', m)
147 self.assertIn('bar/qux/foo.py', m)
147 self.assertIn('bar/qux/foo.py', m)
148 self.assertIn('bar/qux/foz.py', m)
148 self.assertIn('bar/qux/foz.py', m)
149 self.assertIn(256 * 'x' + '/x', m)
149 self.assertIn(256 * 'x' + '/x', m)
150 self.assertIn(256 * 'x' + '/y', m)
150 self.assertIn(256 * 'x' + '/y', m)
151 self.assertEqual(A_STEM_COMPRESSED_MANIFEST, m.text(usemanifestv2=True))
151 self.assertEqual(A_STEM_COMPRESSED_MANIFEST, m.text(usemanifestv2=True))
152
152
153 def testTextV2(self):
153 def testTextV2(self):
154 m1 = self.parsemanifest(A_SHORT_MANIFEST)
154 m1 = self.parsemanifest(A_SHORT_MANIFEST)
155 v2text = m1.text(usemanifestv2=True)
155 v2text = m1.text(usemanifestv2=True)
156 self.assertEqual(A_SHORT_MANIFEST_V2, v2text)
156 self.assertEqual(A_SHORT_MANIFEST_V2, v2text)
157
157
158 def testSetItem(self):
158 def testSetItem(self):
159 want = BIN_HASH_1
159 want = BIN_HASH_1
160
160
161 m = self.parsemanifest(EMTPY_MANIFEST)
161 m = self.parsemanifest(EMTPY_MANIFEST)
162 m['a'] = want
162 m['a'] = want
163 self.assertIn('a', m)
163 self.assertIn('a', m)
164 self.assertEqual(want, m['a'])
164 self.assertEqual(want, m['a'])
165 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
165 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
166
166
167 m = self.parsemanifest(A_SHORT_MANIFEST)
167 m = self.parsemanifest(A_SHORT_MANIFEST)
168 m['a'] = want
168 m['a'] = want
169 self.assertEqual(want, m['a'])
169 self.assertEqual(want, m['a'])
170 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
170 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
171 m.text())
171 m.text())
172
172
173 def testSetFlag(self):
173 def testSetFlag(self):
174 want = 'x'
174 want = 'x'
175
175
176 m = self.parsemanifest(EMTPY_MANIFEST)
176 m = self.parsemanifest(EMTPY_MANIFEST)
177 # first add a file; a file-less flag makes no sense
177 # first add a file; a file-less flag makes no sense
178 m['a'] = BIN_HASH_1
178 m['a'] = BIN_HASH_1
179 m.setflag('a', want)
179 m.setflag('a', want)
180 self.assertEqual(want, m.flags('a'))
180 self.assertEqual(want, m.flags('a'))
181 self.assertEqual('a\0' + HASH_1 + want + '\n', m.text())
181 self.assertEqual('a\0' + HASH_1 + want + '\n', m.text())
182
182
183 m = self.parsemanifest(A_SHORT_MANIFEST)
183 m = self.parsemanifest(A_SHORT_MANIFEST)
184 # first add a file; a file-less flag makes no sense
184 # first add a file; a file-less flag makes no sense
185 m['a'] = BIN_HASH_1
185 m['a'] = BIN_HASH_1
186 m.setflag('a', want)
186 m.setflag('a', want)
187 self.assertEqual(want, m.flags('a'))
187 self.assertEqual(want, m.flags('a'))
188 self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST,
188 self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST,
189 m.text())
189 m.text())
190
190
191 def testCopy(self):
191 def testCopy(self):
192 m = self.parsemanifest(A_SHORT_MANIFEST)
192 m = self.parsemanifest(A_SHORT_MANIFEST)
193 m['a'] = BIN_HASH_1
193 m['a'] = BIN_HASH_1
194 m2 = m.copy()
194 m2 = m.copy()
195 del m
195 del m
196 del m2 # make sure we don't double free() anything
196 del m2 # make sure we don't double free() anything
197
197
198 def testCompaction(self):
198 def testCompaction(self):
199 unhex = binascii.unhexlify
199 unhex = binascii.unhexlify
200 h1, h2 = unhex(HASH_1), unhex(HASH_2)
200 h1, h2 = unhex(HASH_1), unhex(HASH_2)
201 m = self.parsemanifest(A_SHORT_MANIFEST)
201 m = self.parsemanifest(A_SHORT_MANIFEST)
202 m['alpha'] = h1
202 m['alpha'] = h1
203 m['beta'] = h2
203 m['beta'] = h2
204 del m['foo']
204 del m['foo']
205 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
205 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
206 HASH_1, HASH_2, HASH_2)
206 HASH_1, HASH_2, HASH_2)
207 self.assertEqual(want, m.text())
207 self.assertEqual(want, m.text())
208 self.assertEqual(3, len(m))
208 self.assertEqual(3, len(m))
209 self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m))
209 self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m))
210 self.assertEqual(h1, m['alpha'])
210 self.assertEqual(h1, m['alpha'])
211 self.assertEqual(h2, m['bar/baz/qux.py'])
211 self.assertEqual(h2, m['bar/baz/qux.py'])
212 self.assertEqual(h2, m['beta'])
212 self.assertEqual(h2, m['beta'])
213 self.assertEqual('', m.flags('alpha'))
213 self.assertEqual('', m.flags('alpha'))
214 self.assertEqual('l', m.flags('bar/baz/qux.py'))
214 self.assertEqual('l', m.flags('bar/baz/qux.py'))
215 self.assertEqual('', m.flags('beta'))
215 self.assertEqual('', m.flags('beta'))
216 self.assertRaises(KeyError, lambda : m['foo'])
216 self.assertRaises(KeyError, lambda : m['foo'])
217
217
218 def testSetGetNodeSuffix(self):
218 def testSetGetNodeSuffix(self):
219 clean = self.parsemanifest(A_SHORT_MANIFEST)
219 clean = self.parsemanifest(A_SHORT_MANIFEST)
220 m = self.parsemanifest(A_SHORT_MANIFEST)
220 m = self.parsemanifest(A_SHORT_MANIFEST)
221 h = m['foo']
221 h = m['foo']
222 f = m.flags('foo')
222 f = m.flags('foo')
223 want = h + 'a'
223 want = h + 'a'
224 # Merge code wants to set 21-byte fake hashes at times
224 # Merge code wants to set 21-byte fake hashes at times
225 m['foo'] = want
225 m['foo'] = want
226 self.assertEqual(want, m['foo'])
226 self.assertEqual(want, m['foo'])
227 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
227 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
228 ('foo', BIN_HASH_1 + 'a')],
228 ('foo', BIN_HASH_1 + 'a')],
229 list(m.iteritems()))
229 list(m.iteritems()))
230 # Sometimes it even tries a 22-byte fake hash, but we can
230 # Sometimes it even tries a 22-byte fake hash, but we can
231 # return 21 and it'll work out
231 # return 21 and it'll work out
232 m['foo'] = want + '+'
232 m['foo'] = want + '+'
233 self.assertEqual(want, m['foo'])
233 self.assertEqual(want, m['foo'])
234 # make sure the suffix survives a copy
234 # make sure the suffix survives a copy
235 match = matchmod.match('', '', ['re:foo'])
235 match = matchmod.match('', '', ['re:foo'])
236 m2 = m.matches(match)
236 m2 = m.matches(match)
237 self.assertEqual(want, m2['foo'])
237 self.assertEqual(want, m2['foo'])
238 self.assertEqual(1, len(m2))
238 self.assertEqual(1, len(m2))
239 m2 = m.copy()
239 m2 = m.copy()
240 self.assertEqual(want, m2['foo'])
240 self.assertEqual(want, m2['foo'])
241 # suffix with iteration
241 # suffix with iteration
242 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
242 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
243 ('foo', want)],
243 ('foo', want)],
244 list(m.iteritems()))
244 list(m.iteritems()))
245
245
246 # shows up in diff
246 # shows up in diff
247 self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean))
247 self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean))
248 self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m))
248 self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m))
249
249
250 def testMatchException(self):
250 def testMatchException(self):
251 m = self.parsemanifest(A_SHORT_MANIFEST)
251 m = self.parsemanifest(A_SHORT_MANIFEST)
252 match = matchmod.match('', '', ['re:.*'])
252 match = matchmod.match('', '', ['re:.*'])
253 def filt(path):
253 def filt(path):
254 if path == 'foo':
254 if path == 'foo':
255 assert False
255 assert False
256 return True
256 return True
257 match.matchfn = filt
257 match.matchfn = filt
258 self.assertRaises(AssertionError, m.matches, match)
258 self.assertRaises(AssertionError, m.matches, match)
259
259
260 def testRemoveItem(self):
260 def testRemoveItem(self):
261 m = self.parsemanifest(A_SHORT_MANIFEST)
261 m = self.parsemanifest(A_SHORT_MANIFEST)
262 del m['foo']
262 del m['foo']
263 self.assertRaises(KeyError, lambda : m['foo'])
263 self.assertRaises(KeyError, lambda : m['foo'])
264 self.assertEqual(1, len(m))
264 self.assertEqual(1, len(m))
265 self.assertEqual(1, len(list(m)))
265 self.assertEqual(1, len(list(m)))
266 # now restore and make sure everything works right
266 # now restore and make sure everything works right
267 m['foo'] = 'a' * 20
267 m['foo'] = 'a' * 20
268 self.assertEqual(2, len(m))
268 self.assertEqual(2, len(m))
269 self.assertEqual(2, len(list(m)))
269 self.assertEqual(2, len(list(m)))
270
270
271 def testManifestDiff(self):
271 def testManifestDiff(self):
272 MISSING = (None, '')
272 MISSING = (None, '')
273 addl = 'z-only-in-left\0' + HASH_1 + '\n'
273 addl = 'z-only-in-left\0' + HASH_1 + '\n'
274 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
274 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
275 left = self.parsemanifest(
275 left = self.parsemanifest(
276 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
276 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
277 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
277 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
278 want = {
278 want = {
279 'foo': ((BIN_HASH_3, 'x'),
279 'foo': ((BIN_HASH_3, 'x'),
280 (BIN_HASH_1, '')),
280 (BIN_HASH_1, '')),
281 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
281 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
282 'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')),
282 'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')),
283 }
283 }
284 self.assertEqual(want, left.diff(right))
284 self.assertEqual(want, left.diff(right))
285
285
286 want = {
286 want = {
287 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')),
287 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')),
288 'foo': (MISSING, (BIN_HASH_3, 'x')),
288 'foo': (MISSING, (BIN_HASH_3, 'x')),
289 'z-only-in-left': (MISSING, (BIN_HASH_1, '')),
289 'z-only-in-left': (MISSING, (BIN_HASH_1, '')),
290 }
290 }
291 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
291 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
292
292
293 want = {
293 want = {
294 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING),
294 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING),
295 'foo': ((BIN_HASH_3, 'x'), MISSING),
295 'foo': ((BIN_HASH_3, 'x'), MISSING),
296 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
296 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
297 }
297 }
298 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
298 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
299 copy = right.copy()
299 copy = right.copy()
300 del copy['z-only-in-right']
300 del copy['z-only-in-right']
301 del right['foo']
301 del right['foo']
302 want = {
302 want = {
303 'foo': (MISSING, (BIN_HASH_1, '')),
303 'foo': (MISSING, (BIN_HASH_1, '')),
304 'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING),
304 'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING),
305 }
305 }
306 self.assertEqual(want, right.diff(copy))
306 self.assertEqual(want, right.diff(copy))
307
307
308 short = self.parsemanifest(A_SHORT_MANIFEST)
308 short = self.parsemanifest(A_SHORT_MANIFEST)
309 pruned = short.copy()
309 pruned = short.copy()
310 del pruned['foo']
310 del pruned['foo']
311 want = {
311 want = {
312 'foo': ((BIN_HASH_1, ''), MISSING),
312 'foo': ((BIN_HASH_1, ''), MISSING),
313 }
313 }
314 self.assertEqual(want, short.diff(pruned))
314 self.assertEqual(want, short.diff(pruned))
315 want = {
315 want = {
316 'foo': (MISSING, (BIN_HASH_1, '')),
316 'foo': (MISSING, (BIN_HASH_1, '')),
317 }
317 }
318 self.assertEqual(want, pruned.diff(short))
318 self.assertEqual(want, pruned.diff(short))
319 want = {
319 want = {
320 'bar/baz/qux.py': None,
320 'bar/baz/qux.py': None,
321 'foo': (MISSING, (BIN_HASH_1, '')),
321 'foo': (MISSING, (BIN_HASH_1, '')),
322 }
322 }
323 self.assertEqual(want, pruned.diff(short, True))
323 self.assertEqual(want, pruned.diff(short, clean=True))
324
324
325 def testReversedLines(self):
325 def testReversedLines(self):
326 backwards = ''.join(
326 backwards = ''.join(
327 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
327 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
328 try:
328 try:
329 self.parsemanifest(backwards)
329 self.parsemanifest(backwards)
330 self.fail('Should have raised ValueError')
330 self.fail('Should have raised ValueError')
331 except ValueError as v:
331 except ValueError as v:
332 self.assertIn('Manifest lines not in sorted order.', str(v))
332 self.assertIn('Manifest lines not in sorted order.', str(v))
333
333
334 def testNoTerminalNewline(self):
334 def testNoTerminalNewline(self):
335 try:
335 try:
336 self.parsemanifest(A_SHORT_MANIFEST + 'wat')
336 self.parsemanifest(A_SHORT_MANIFEST + 'wat')
337 self.fail('Should have raised ValueError')
337 self.fail('Should have raised ValueError')
338 except ValueError as v:
338 except ValueError as v:
339 self.assertIn('Manifest did not end in a newline.', str(v))
339 self.assertIn('Manifest did not end in a newline.', str(v))
340
340
341 def testNoNewLineAtAll(self):
341 def testNoNewLineAtAll(self):
342 try:
342 try:
343 self.parsemanifest('wat')
343 self.parsemanifest('wat')
344 self.fail('Should have raised ValueError')
344 self.fail('Should have raised ValueError')
345 except ValueError as v:
345 except ValueError as v:
346 self.assertIn('Manifest did not end in a newline.', str(v))
346 self.assertIn('Manifest did not end in a newline.', str(v))
347
347
348 def testHugeManifest(self):
348 def testHugeManifest(self):
349 m = self.parsemanifest(A_HUGE_MANIFEST)
349 m = self.parsemanifest(A_HUGE_MANIFEST)
350 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
350 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
351 self.assertEqual(len(m), len(list(m)))
351 self.assertEqual(len(m), len(list(m)))
352
352
353 def testMatchesMetadata(self):
353 def testMatchesMetadata(self):
354 '''Tests matches() for a few specific files to make sure that both
354 '''Tests matches() for a few specific files to make sure that both
355 the set of files as well as their flags and nodeids are correct in
355 the set of files as well as their flags and nodeids are correct in
356 the resulting manifest.'''
356 the resulting manifest.'''
357 m = self.parsemanifest(A_HUGE_MANIFEST)
357 m = self.parsemanifest(A_HUGE_MANIFEST)
358
358
359 match = matchmod.match('/', '',
359 match = matchmod.match('/', '',
360 ['file1', 'file200', 'file300'], exact=True)
360 ['file1', 'file200', 'file300'], exact=True)
361 m2 = m.matches(match)
361 m2 = m.matches(match)
362
362
363 w = ('file1\0%sx\n'
363 w = ('file1\0%sx\n'
364 'file200\0%sl\n'
364 'file200\0%sl\n'
365 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
365 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
366 self.assertEqual(w, m2.text())
366 self.assertEqual(w, m2.text())
367
367
368 def testMatchesNonexistentFile(self):
368 def testMatchesNonexistentFile(self):
369 '''Tests matches() for a small set of specific files, including one
369 '''Tests matches() for a small set of specific files, including one
370 nonexistent file to make sure in only matches against existing files.
370 nonexistent file to make sure in only matches against existing files.
371 '''
371 '''
372 m = self.parsemanifest(A_DEEPER_MANIFEST)
372 m = self.parsemanifest(A_DEEPER_MANIFEST)
373
373
374 match = matchmod.match('/', '',
374 match = matchmod.match('/', '',
375 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'],
375 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'],
376 exact=True)
376 exact=True)
377 m2 = m.matches(match)
377 m2 = m.matches(match)
378
378
379 self.assertEqual(
379 self.assertEqual(
380 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'],
380 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'],
381 m2.keys())
381 m2.keys())
382
382
383 def testMatchesNonexistentDirectory(self):
383 def testMatchesNonexistentDirectory(self):
384 '''Tests matches() for a relpath match on a directory that doesn't
384 '''Tests matches() for a relpath match on a directory that doesn't
385 actually exist.'''
385 actually exist.'''
386 m = self.parsemanifest(A_DEEPER_MANIFEST)
386 m = self.parsemanifest(A_DEEPER_MANIFEST)
387
387
388 match = matchmod.match('/', '', ['a/f'], default='relpath')
388 match = matchmod.match('/', '', ['a/f'], default='relpath')
389 m2 = m.matches(match)
389 m2 = m.matches(match)
390
390
391 self.assertEqual([], m2.keys())
391 self.assertEqual([], m2.keys())
392
392
393 def testMatchesExactLarge(self):
393 def testMatchesExactLarge(self):
394 '''Tests matches() for files matching a large list of exact files.
394 '''Tests matches() for files matching a large list of exact files.
395 '''
395 '''
396 m = self.parsemanifest(A_HUGE_MANIFEST)
396 m = self.parsemanifest(A_HUGE_MANIFEST)
397
397
398 flist = m.keys()[80:300]
398 flist = m.keys()[80:300]
399 match = matchmod.match('/', '', flist, exact=True)
399 match = matchmod.match('/', '', flist, exact=True)
400 m2 = m.matches(match)
400 m2 = m.matches(match)
401
401
402 self.assertEqual(flist, m2.keys())
402 self.assertEqual(flist, m2.keys())
403
403
404 def testMatchesFull(self):
404 def testMatchesFull(self):
405 '''Tests matches() for what should be a full match.'''
405 '''Tests matches() for what should be a full match.'''
406 m = self.parsemanifest(A_DEEPER_MANIFEST)
406 m = self.parsemanifest(A_DEEPER_MANIFEST)
407
407
408 match = matchmod.match('/', '', [''])
408 match = matchmod.match('/', '', [''])
409 m2 = m.matches(match)
409 m2 = m.matches(match)
410
410
411 self.assertEqual(m.keys(), m2.keys())
411 self.assertEqual(m.keys(), m2.keys())
412
412
413 def testMatchesDirectory(self):
413 def testMatchesDirectory(self):
414 '''Tests matches() on a relpath match on a directory, which should
414 '''Tests matches() on a relpath match on a directory, which should
415 match against all files within said directory.'''
415 match against all files within said directory.'''
416 m = self.parsemanifest(A_DEEPER_MANIFEST)
416 m = self.parsemanifest(A_DEEPER_MANIFEST)
417
417
418 match = matchmod.match('/', '', ['a/b'], default='relpath')
418 match = matchmod.match('/', '', ['a/b'], default='relpath')
419 m2 = m.matches(match)
419 m2 = m.matches(match)
420
420
421 self.assertEqual([
421 self.assertEqual([
422 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
422 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
423 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
423 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
424 'a/b/fish.py'], m2.keys())
424 'a/b/fish.py'], m2.keys())
425
425
426 def testMatchesExactPath(self):
426 def testMatchesExactPath(self):
427 '''Tests matches() on an exact match on a directory, which should
427 '''Tests matches() on an exact match on a directory, which should
428 result in an empty manifest because you can't perform an exact match
428 result in an empty manifest because you can't perform an exact match
429 against a directory.'''
429 against a directory.'''
430 m = self.parsemanifest(A_DEEPER_MANIFEST)
430 m = self.parsemanifest(A_DEEPER_MANIFEST)
431
431
432 match = matchmod.match('/', '', ['a/b'], exact=True)
432 match = matchmod.match('/', '', ['a/b'], exact=True)
433 m2 = m.matches(match)
433 m2 = m.matches(match)
434
434
435 self.assertEqual([], m2.keys())
435 self.assertEqual([], m2.keys())
436
436
437 def testMatchesCwd(self):
437 def testMatchesCwd(self):
438 '''Tests matches() on a relpath match with the current directory ('.')
438 '''Tests matches() on a relpath match with the current directory ('.')
439 when not in the root directory.'''
439 when not in the root directory.'''
440 m = self.parsemanifest(A_DEEPER_MANIFEST)
440 m = self.parsemanifest(A_DEEPER_MANIFEST)
441
441
442 match = matchmod.match('/', 'a/b', ['.'], default='relpath')
442 match = matchmod.match('/', 'a/b', ['.'], default='relpath')
443 m2 = m.matches(match)
443 m2 = m.matches(match)
444
444
445 self.assertEqual([
445 self.assertEqual([
446 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
446 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
447 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
447 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
448 'a/b/fish.py'], m2.keys())
448 'a/b/fish.py'], m2.keys())
449
449
450 def testMatchesWithPattern(self):
450 def testMatchesWithPattern(self):
451 '''Tests matches() for files matching a pattern that reside
451 '''Tests matches() for files matching a pattern that reside
452 deeper than the specified directory.'''
452 deeper than the specified directory.'''
453 m = self.parsemanifest(A_DEEPER_MANIFEST)
453 m = self.parsemanifest(A_DEEPER_MANIFEST)
454
454
455 match = matchmod.match('/', '', ['a/b/*/*.txt'])
455 match = matchmod.match('/', '', ['a/b/*/*.txt'])
456 m2 = m.matches(match)
456 m2 = m.matches(match)
457
457
458 self.assertEqual(
458 self.assertEqual(
459 ['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'],
459 ['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'],
460 m2.keys())
460 m2.keys())
461
461
462 class testmanifestdict(unittest.TestCase, basemanifesttests):
462 class testmanifestdict(unittest.TestCase, basemanifesttests):
463 def parsemanifest(self, text):
463 def parsemanifest(self, text):
464 return manifestmod.manifestdict(text)
464 return manifestmod.manifestdict(text)
465
465
466 class testtreemanifest(unittest.TestCase, basemanifesttests):
466 class testtreemanifest(unittest.TestCase, basemanifesttests):
467 def parsemanifest(self, text):
467 def parsemanifest(self, text):
468 return manifestmod.treemanifest('', text)
468 return manifestmod.treemanifest('', text)
469
469
470 if __name__ == '__main__':
470 if __name__ == '__main__':
471 silenttestrunner.main(__name__)
471 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now