##// END OF EJS Templates
manifest: fix _very_ subtle bug with exact matchers passed to walk()...
Augie Fackler -
r44741:48a1a974 default
parent child Browse files
Show More
@@ -1,2261 +1,2262
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 heapq
10 import heapq
11 import itertools
11 import itertools
12 import struct
12 import struct
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 bin,
17 bin,
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 )
21 )
22 from .pycompat import getattr
22 from .pycompat import getattr
23 from . import (
23 from . import (
24 error,
24 error,
25 mdiff,
25 mdiff,
26 pathutil,
26 pathutil,
27 policy,
27 policy,
28 pycompat,
28 pycompat,
29 revlog,
29 revlog,
30 util,
30 util,
31 )
31 )
32 from .interfaces import (
32 from .interfaces import (
33 repository,
33 repository,
34 util as interfaceutil,
34 util as interfaceutil,
35 )
35 )
36
36
37 parsers = policy.importmod('parsers')
37 parsers = policy.importmod('parsers')
38 propertycache = util.propertycache
38 propertycache = util.propertycache
39
39
40 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
40 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
41 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
41 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
42
42
43
43
44 def _parse(data):
44 def _parse(data):
45 # This method does a little bit of excessive-looking
45 # This method does a little bit of excessive-looking
46 # precondition checking. This is so that the behavior of this
46 # precondition checking. This is so that the behavior of this
47 # class exactly matches its C counterpart to try and help
47 # class exactly matches its C counterpart to try and help
48 # prevent surprise breakage for anyone that develops against
48 # prevent surprise breakage for anyone that develops against
49 # the pure version.
49 # the pure version.
50 if data and data[-1:] != b'\n':
50 if data and data[-1:] != b'\n':
51 raise ValueError(b'Manifest did not end in a newline.')
51 raise ValueError(b'Manifest did not end in a newline.')
52 prev = None
52 prev = None
53 for l in data.splitlines():
53 for l in data.splitlines():
54 if prev is not None and prev > l:
54 if prev is not None and prev > l:
55 raise ValueError(b'Manifest lines not in sorted order.')
55 raise ValueError(b'Manifest lines not in sorted order.')
56 prev = l
56 prev = l
57 f, n = l.split(b'\0')
57 f, n = l.split(b'\0')
58 if len(n) > 40:
58 if len(n) > 40:
59 yield f, bin(n[:40]), n[40:]
59 yield f, bin(n[:40]), n[40:]
60 else:
60 else:
61 yield f, bin(n), b''
61 yield f, bin(n), b''
62
62
63
63
64 def _text(it):
64 def _text(it):
65 files = []
65 files = []
66 lines = []
66 lines = []
67 for f, n, fl in it:
67 for f, n, fl in it:
68 files.append(f)
68 files.append(f)
69 # if this is changed to support newlines in filenames,
69 # if this is changed to support newlines in filenames,
70 # be sure to check the templates/ dir again (especially *-raw.tmpl)
70 # be sure to check the templates/ dir again (especially *-raw.tmpl)
71 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
71 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
72
72
73 _checkforbidden(files)
73 _checkforbidden(files)
74 return b''.join(lines)
74 return b''.join(lines)
75
75
76
76
77 class lazymanifestiter(object):
77 class lazymanifestiter(object):
78 def __init__(self, lm):
78 def __init__(self, lm):
79 self.pos = 0
79 self.pos = 0
80 self.lm = lm
80 self.lm = lm
81
81
82 def __iter__(self):
82 def __iter__(self):
83 return self
83 return self
84
84
85 def next(self):
85 def next(self):
86 try:
86 try:
87 data, pos = self.lm._get(self.pos)
87 data, pos = self.lm._get(self.pos)
88 except IndexError:
88 except IndexError:
89 raise StopIteration
89 raise StopIteration
90 if pos == -1:
90 if pos == -1:
91 self.pos += 1
91 self.pos += 1
92 return data[0]
92 return data[0]
93 self.pos += 1
93 self.pos += 1
94 zeropos = data.find(b'\x00', pos)
94 zeropos = data.find(b'\x00', pos)
95 return data[pos:zeropos]
95 return data[pos:zeropos]
96
96
97 __next__ = next
97 __next__ = next
98
98
99
99
100 class lazymanifestiterentries(object):
100 class lazymanifestiterentries(object):
101 def __init__(self, lm):
101 def __init__(self, lm):
102 self.lm = lm
102 self.lm = lm
103 self.pos = 0
103 self.pos = 0
104
104
105 def __iter__(self):
105 def __iter__(self):
106 return self
106 return self
107
107
108 def next(self):
108 def next(self):
109 try:
109 try:
110 data, pos = self.lm._get(self.pos)
110 data, pos = self.lm._get(self.pos)
111 except IndexError:
111 except IndexError:
112 raise StopIteration
112 raise StopIteration
113 if pos == -1:
113 if pos == -1:
114 self.pos += 1
114 self.pos += 1
115 return data
115 return data
116 zeropos = data.find(b'\x00', pos)
116 zeropos = data.find(b'\x00', pos)
117 hashval = unhexlify(data, self.lm.extrainfo[self.pos], zeropos + 1, 40)
117 hashval = unhexlify(data, self.lm.extrainfo[self.pos], zeropos + 1, 40)
118 flags = self.lm._getflags(data, self.pos, zeropos)
118 flags = self.lm._getflags(data, self.pos, zeropos)
119 self.pos += 1
119 self.pos += 1
120 return (data[pos:zeropos], hashval, flags)
120 return (data[pos:zeropos], hashval, flags)
121
121
122 __next__ = next
122 __next__ = next
123
123
124
124
125 def unhexlify(data, extra, pos, length):
125 def unhexlify(data, extra, pos, length):
126 s = bin(data[pos : pos + length])
126 s = bin(data[pos : pos + length])
127 if extra:
127 if extra:
128 s += chr(extra & 0xFF)
128 s += chr(extra & 0xFF)
129 return s
129 return s
130
130
131
131
132 def _cmp(a, b):
132 def _cmp(a, b):
133 return (a > b) - (a < b)
133 return (a > b) - (a < b)
134
134
135
135
136 class _lazymanifest(object):
136 class _lazymanifest(object):
137 """A pure python manifest backed by a byte string. It is supplimented with
137 """A pure python manifest backed by a byte string. It is supplimented with
138 internal lists as it is modified, until it is compacted back to a pure byte
138 internal lists as it is modified, until it is compacted back to a pure byte
139 string.
139 string.
140
140
141 ``data`` is the initial manifest data.
141 ``data`` is the initial manifest data.
142
142
143 ``positions`` is a list of offsets, one per manifest entry. Positive
143 ``positions`` is a list of offsets, one per manifest entry. Positive
144 values are offsets into ``data``, negative values are offsets into the
144 values are offsets into ``data``, negative values are offsets into the
145 ``extradata`` list. When an entry is removed, its entry is dropped from
145 ``extradata`` list. When an entry is removed, its entry is dropped from
146 ``positions``. The values are encoded such that when walking the list and
146 ``positions``. The values are encoded such that when walking the list and
147 indexing into ``data`` or ``extradata`` as appropriate, the entries are
147 indexing into ``data`` or ``extradata`` as appropriate, the entries are
148 sorted by filename.
148 sorted by filename.
149
149
150 ``extradata`` is a list of (key, hash, flags) for entries that were added or
150 ``extradata`` is a list of (key, hash, flags) for entries that were added or
151 modified since the manifest was created or compacted.
151 modified since the manifest was created or compacted.
152 """
152 """
153
153
154 def __init__(
154 def __init__(
155 self,
155 self,
156 data,
156 data,
157 positions=None,
157 positions=None,
158 extrainfo=None,
158 extrainfo=None,
159 extradata=None,
159 extradata=None,
160 hasremovals=False,
160 hasremovals=False,
161 ):
161 ):
162 if positions is None:
162 if positions is None:
163 self.positions = self.findlines(data)
163 self.positions = self.findlines(data)
164 self.extrainfo = [0] * len(self.positions)
164 self.extrainfo = [0] * len(self.positions)
165 self.data = data
165 self.data = data
166 self.extradata = []
166 self.extradata = []
167 self.hasremovals = False
167 self.hasremovals = False
168 else:
168 else:
169 self.positions = positions[:]
169 self.positions = positions[:]
170 self.extrainfo = extrainfo[:]
170 self.extrainfo = extrainfo[:]
171 self.extradata = extradata[:]
171 self.extradata = extradata[:]
172 self.data = data
172 self.data = data
173 self.hasremovals = hasremovals
173 self.hasremovals = hasremovals
174
174
175 def findlines(self, data):
175 def findlines(self, data):
176 if not data:
176 if not data:
177 return []
177 return []
178 pos = data.find(b"\n")
178 pos = data.find(b"\n")
179 if pos == -1 or data[-1:] != b'\n':
179 if pos == -1 or data[-1:] != b'\n':
180 raise ValueError(b"Manifest did not end in a newline.")
180 raise ValueError(b"Manifest did not end in a newline.")
181 positions = [0]
181 positions = [0]
182 prev = data[: data.find(b'\x00')]
182 prev = data[: data.find(b'\x00')]
183 while pos < len(data) - 1 and pos != -1:
183 while pos < len(data) - 1 and pos != -1:
184 positions.append(pos + 1)
184 positions.append(pos + 1)
185 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
185 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
186 if nexts < prev:
186 if nexts < prev:
187 raise ValueError(b"Manifest lines not in sorted order.")
187 raise ValueError(b"Manifest lines not in sorted order.")
188 prev = nexts
188 prev = nexts
189 pos = data.find(b"\n", pos + 1)
189 pos = data.find(b"\n", pos + 1)
190 return positions
190 return positions
191
191
192 def _get(self, index):
192 def _get(self, index):
193 # get the position encoded in pos:
193 # get the position encoded in pos:
194 # positive number is an index in 'data'
194 # positive number is an index in 'data'
195 # negative number is in extrapieces
195 # negative number is in extrapieces
196 pos = self.positions[index]
196 pos = self.positions[index]
197 if pos >= 0:
197 if pos >= 0:
198 return self.data, pos
198 return self.data, pos
199 return self.extradata[-pos - 1], -1
199 return self.extradata[-pos - 1], -1
200
200
201 def _getkey(self, pos):
201 def _getkey(self, pos):
202 if pos >= 0:
202 if pos >= 0:
203 return self.data[pos : self.data.find(b'\x00', pos + 1)]
203 return self.data[pos : self.data.find(b'\x00', pos + 1)]
204 return self.extradata[-pos - 1][0]
204 return self.extradata[-pos - 1][0]
205
205
206 def bsearch(self, key):
206 def bsearch(self, key):
207 first = 0
207 first = 0
208 last = len(self.positions) - 1
208 last = len(self.positions) - 1
209
209
210 while first <= last:
210 while first <= last:
211 midpoint = (first + last) // 2
211 midpoint = (first + last) // 2
212 nextpos = self.positions[midpoint]
212 nextpos = self.positions[midpoint]
213 candidate = self._getkey(nextpos)
213 candidate = self._getkey(nextpos)
214 r = _cmp(key, candidate)
214 r = _cmp(key, candidate)
215 if r == 0:
215 if r == 0:
216 return midpoint
216 return midpoint
217 else:
217 else:
218 if r < 0:
218 if r < 0:
219 last = midpoint - 1
219 last = midpoint - 1
220 else:
220 else:
221 first = midpoint + 1
221 first = midpoint + 1
222 return -1
222 return -1
223
223
224 def bsearch2(self, key):
224 def bsearch2(self, key):
225 # same as the above, but will always return the position
225 # same as the above, but will always return the position
226 # done for performance reasons
226 # done for performance reasons
227 first = 0
227 first = 0
228 last = len(self.positions) - 1
228 last = len(self.positions) - 1
229
229
230 while first <= last:
230 while first <= last:
231 midpoint = (first + last) // 2
231 midpoint = (first + last) // 2
232 nextpos = self.positions[midpoint]
232 nextpos = self.positions[midpoint]
233 candidate = self._getkey(nextpos)
233 candidate = self._getkey(nextpos)
234 r = _cmp(key, candidate)
234 r = _cmp(key, candidate)
235 if r == 0:
235 if r == 0:
236 return (midpoint, True)
236 return (midpoint, True)
237 else:
237 else:
238 if r < 0:
238 if r < 0:
239 last = midpoint - 1
239 last = midpoint - 1
240 else:
240 else:
241 first = midpoint + 1
241 first = midpoint + 1
242 return (first, False)
242 return (first, False)
243
243
244 def __contains__(self, key):
244 def __contains__(self, key):
245 return self.bsearch(key) != -1
245 return self.bsearch(key) != -1
246
246
247 def _getflags(self, data, needle, pos):
247 def _getflags(self, data, needle, pos):
248 start = pos + 41
248 start = pos + 41
249 end = data.find(b"\n", start)
249 end = data.find(b"\n", start)
250 if end == -1:
250 if end == -1:
251 end = len(data) - 1
251 end = len(data) - 1
252 if start == end:
252 if start == end:
253 return b''
253 return b''
254 return self.data[start:end]
254 return self.data[start:end]
255
255
256 def __getitem__(self, key):
256 def __getitem__(self, key):
257 if not isinstance(key, bytes):
257 if not isinstance(key, bytes):
258 raise TypeError(b"getitem: manifest keys must be a bytes.")
258 raise TypeError(b"getitem: manifest keys must be a bytes.")
259 needle = self.bsearch(key)
259 needle = self.bsearch(key)
260 if needle == -1:
260 if needle == -1:
261 raise KeyError
261 raise KeyError
262 data, pos = self._get(needle)
262 data, pos = self._get(needle)
263 if pos == -1:
263 if pos == -1:
264 return (data[1], data[2])
264 return (data[1], data[2])
265 zeropos = data.find(b'\x00', pos)
265 zeropos = data.find(b'\x00', pos)
266 assert 0 <= needle <= len(self.positions)
266 assert 0 <= needle <= len(self.positions)
267 assert len(self.extrainfo) == len(self.positions)
267 assert len(self.extrainfo) == len(self.positions)
268 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
268 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
269 flags = self._getflags(data, needle, zeropos)
269 flags = self._getflags(data, needle, zeropos)
270 return (hashval, flags)
270 return (hashval, flags)
271
271
272 def __delitem__(self, key):
272 def __delitem__(self, key):
273 needle, found = self.bsearch2(key)
273 needle, found = self.bsearch2(key)
274 if not found:
274 if not found:
275 raise KeyError
275 raise KeyError
276 cur = self.positions[needle]
276 cur = self.positions[needle]
277 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
277 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
278 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
278 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
279 if cur >= 0:
279 if cur >= 0:
280 # This does NOT unsort the list as far as the search functions are
280 # This does NOT unsort the list as far as the search functions are
281 # concerned, as they only examine lines mapped by self.positions.
281 # concerned, as they only examine lines mapped by self.positions.
282 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
282 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
283 self.hasremovals = True
283 self.hasremovals = True
284
284
285 def __setitem__(self, key, value):
285 def __setitem__(self, key, value):
286 if not isinstance(key, bytes):
286 if not isinstance(key, bytes):
287 raise TypeError(b"setitem: manifest keys must be a byte string.")
287 raise TypeError(b"setitem: manifest keys must be a byte string.")
288 if not isinstance(value, tuple) or len(value) != 2:
288 if not isinstance(value, tuple) or len(value) != 2:
289 raise TypeError(
289 raise TypeError(
290 b"Manifest values must be a tuple of (node, flags)."
290 b"Manifest values must be a tuple of (node, flags)."
291 )
291 )
292 hashval = value[0]
292 hashval = value[0]
293 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
293 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
294 raise TypeError(b"node must be a 20-byte byte string")
294 raise TypeError(b"node must be a 20-byte byte string")
295 flags = value[1]
295 flags = value[1]
296 if len(hashval) == 22:
296 if len(hashval) == 22:
297 hashval = hashval[:-1]
297 hashval = hashval[:-1]
298 if not isinstance(flags, bytes) or len(flags) > 1:
298 if not isinstance(flags, bytes) or len(flags) > 1:
299 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
299 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
300 needle, found = self.bsearch2(key)
300 needle, found = self.bsearch2(key)
301 if found:
301 if found:
302 # put the item
302 # put the item
303 pos = self.positions[needle]
303 pos = self.positions[needle]
304 if pos < 0:
304 if pos < 0:
305 self.extradata[-pos - 1] = (key, hashval, value[1])
305 self.extradata[-pos - 1] = (key, hashval, value[1])
306 else:
306 else:
307 # just don't bother
307 # just don't bother
308 self.extradata.append((key, hashval, value[1]))
308 self.extradata.append((key, hashval, value[1]))
309 self.positions[needle] = -len(self.extradata)
309 self.positions[needle] = -len(self.extradata)
310 else:
310 else:
311 # not found, put it in with extra positions
311 # not found, put it in with extra positions
312 self.extradata.append((key, hashval, value[1]))
312 self.extradata.append((key, hashval, value[1]))
313 self.positions = (
313 self.positions = (
314 self.positions[:needle]
314 self.positions[:needle]
315 + [-len(self.extradata)]
315 + [-len(self.extradata)]
316 + self.positions[needle:]
316 + self.positions[needle:]
317 )
317 )
318 self.extrainfo = (
318 self.extrainfo = (
319 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
319 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
320 )
320 )
321
321
322 def copy(self):
322 def copy(self):
323 # XXX call _compact like in C?
323 # XXX call _compact like in C?
324 return _lazymanifest(
324 return _lazymanifest(
325 self.data,
325 self.data,
326 self.positions,
326 self.positions,
327 self.extrainfo,
327 self.extrainfo,
328 self.extradata,
328 self.extradata,
329 self.hasremovals,
329 self.hasremovals,
330 )
330 )
331
331
332 def _compact(self):
332 def _compact(self):
333 # hopefully not called TOO often
333 # hopefully not called TOO often
334 if len(self.extradata) == 0 and not self.hasremovals:
334 if len(self.extradata) == 0 and not self.hasremovals:
335 return
335 return
336 l = []
336 l = []
337 i = 0
337 i = 0
338 offset = 0
338 offset = 0
339 self.extrainfo = [0] * len(self.positions)
339 self.extrainfo = [0] * len(self.positions)
340 while i < len(self.positions):
340 while i < len(self.positions):
341 if self.positions[i] >= 0:
341 if self.positions[i] >= 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 last_cut = cur
343 last_cut = cur
344
344
345 # Collect all contiguous entries in the buffer at the current
345 # Collect all contiguous entries in the buffer at the current
346 # offset, breaking out only for added/modified items held in
346 # offset, breaking out only for added/modified items held in
347 # extradata, or a deleted line prior to the next position.
347 # extradata, or a deleted line prior to the next position.
348 while True:
348 while True:
349 self.positions[i] = offset
349 self.positions[i] = offset
350 i += 1
350 i += 1
351 if i == len(self.positions) or self.positions[i] < 0:
351 if i == len(self.positions) or self.positions[i] < 0:
352 break
352 break
353
353
354 # A removed file has no positions[] entry, but does have an
354 # A removed file has no positions[] entry, but does have an
355 # overwritten first byte. Break out and find the end of the
355 # overwritten first byte. Break out and find the end of the
356 # current good entry/entries if there is a removed file
356 # current good entry/entries if there is a removed file
357 # before the next position.
357 # before the next position.
358 if (
358 if (
359 self.hasremovals
359 self.hasremovals
360 and self.data.find(b'\n\x00', cur, self.positions[i])
360 and self.data.find(b'\n\x00', cur, self.positions[i])
361 != -1
361 != -1
362 ):
362 ):
363 break
363 break
364
364
365 offset += self.positions[i] - cur
365 offset += self.positions[i] - cur
366 cur = self.positions[i]
366 cur = self.positions[i]
367 end_cut = self.data.find(b'\n', cur)
367 end_cut = self.data.find(b'\n', cur)
368 if end_cut != -1:
368 if end_cut != -1:
369 end_cut += 1
369 end_cut += 1
370 offset += end_cut - cur
370 offset += end_cut - cur
371 l.append(self.data[last_cut:end_cut])
371 l.append(self.data[last_cut:end_cut])
372 else:
372 else:
373 while i < len(self.positions) and self.positions[i] < 0:
373 while i < len(self.positions) and self.positions[i] < 0:
374 cur = self.positions[i]
374 cur = self.positions[i]
375 t = self.extradata[-cur - 1]
375 t = self.extradata[-cur - 1]
376 l.append(self._pack(t))
376 l.append(self._pack(t))
377 self.positions[i] = offset
377 self.positions[i] = offset
378 if len(t[1]) > 20:
378 if len(t[1]) > 20:
379 self.extrainfo[i] = ord(t[1][21])
379 self.extrainfo[i] = ord(t[1][21])
380 offset += len(l[-1])
380 offset += len(l[-1])
381 i += 1
381 i += 1
382 self.data = b''.join(l)
382 self.data = b''.join(l)
383 self.hasremovals = False
383 self.hasremovals = False
384 self.extradata = []
384 self.extradata = []
385
385
386 def _pack(self, d):
386 def _pack(self, d):
387 return d[0] + b'\x00' + hex(d[1][:20]) + d[2] + b'\n'
387 return d[0] + b'\x00' + hex(d[1][:20]) + d[2] + b'\n'
388
388
389 def text(self):
389 def text(self):
390 self._compact()
390 self._compact()
391 return self.data
391 return self.data
392
392
393 def diff(self, m2, clean=False):
393 def diff(self, m2, clean=False):
394 '''Finds changes between the current manifest and m2.'''
394 '''Finds changes between the current manifest and m2.'''
395 # XXX think whether efficiency matters here
395 # XXX think whether efficiency matters here
396 diff = {}
396 diff = {}
397
397
398 for fn, e1, flags in self.iterentries():
398 for fn, e1, flags in self.iterentries():
399 if fn not in m2:
399 if fn not in m2:
400 diff[fn] = (e1, flags), (None, b'')
400 diff[fn] = (e1, flags), (None, b'')
401 else:
401 else:
402 e2 = m2[fn]
402 e2 = m2[fn]
403 if (e1, flags) != e2:
403 if (e1, flags) != e2:
404 diff[fn] = (e1, flags), e2
404 diff[fn] = (e1, flags), e2
405 elif clean:
405 elif clean:
406 diff[fn] = None
406 diff[fn] = None
407
407
408 for fn, e2, flags in m2.iterentries():
408 for fn, e2, flags in m2.iterentries():
409 if fn not in self:
409 if fn not in self:
410 diff[fn] = (None, b''), (e2, flags)
410 diff[fn] = (None, b''), (e2, flags)
411
411
412 return diff
412 return diff
413
413
414 def iterentries(self):
414 def iterentries(self):
415 return lazymanifestiterentries(self)
415 return lazymanifestiterentries(self)
416
416
417 def iterkeys(self):
417 def iterkeys(self):
418 return lazymanifestiter(self)
418 return lazymanifestiter(self)
419
419
420 def __iter__(self):
420 def __iter__(self):
421 return lazymanifestiter(self)
421 return lazymanifestiter(self)
422
422
423 def __len__(self):
423 def __len__(self):
424 return len(self.positions)
424 return len(self.positions)
425
425
426 def filtercopy(self, filterfn):
426 def filtercopy(self, filterfn):
427 # XXX should be optimized
427 # XXX should be optimized
428 c = _lazymanifest(b'')
428 c = _lazymanifest(b'')
429 for f, n, fl in self.iterentries():
429 for f, n, fl in self.iterentries():
430 if filterfn(f):
430 if filterfn(f):
431 c[f] = n, fl
431 c[f] = n, fl
432 return c
432 return c
433
433
434
434
435 try:
435 try:
436 _lazymanifest = parsers.lazymanifest
436 _lazymanifest = parsers.lazymanifest
437 except AttributeError:
437 except AttributeError:
438 pass
438 pass
439
439
440
440
441 @interfaceutil.implementer(repository.imanifestdict)
441 @interfaceutil.implementer(repository.imanifestdict)
442 class manifestdict(object):
442 class manifestdict(object):
443 def __init__(self, data=b''):
443 def __init__(self, data=b''):
444 self._lm = _lazymanifest(data)
444 self._lm = _lazymanifest(data)
445
445
446 def __getitem__(self, key):
446 def __getitem__(self, key):
447 return self._lm[key][0]
447 return self._lm[key][0]
448
448
449 def find(self, key):
449 def find(self, key):
450 return self._lm[key]
450 return self._lm[key]
451
451
452 def __len__(self):
452 def __len__(self):
453 return len(self._lm)
453 return len(self._lm)
454
454
455 def __nonzero__(self):
455 def __nonzero__(self):
456 # nonzero is covered by the __len__ function, but implementing it here
456 # nonzero is covered by the __len__ function, but implementing it here
457 # makes it easier for extensions to override.
457 # makes it easier for extensions to override.
458 return len(self._lm) != 0
458 return len(self._lm) != 0
459
459
460 __bool__ = __nonzero__
460 __bool__ = __nonzero__
461
461
462 def __setitem__(self, key, node):
462 def __setitem__(self, key, node):
463 self._lm[key] = node, self.flags(key)
463 self._lm[key] = node, self.flags(key)
464
464
465 def __contains__(self, key):
465 def __contains__(self, key):
466 if key is None:
466 if key is None:
467 return False
467 return False
468 return key in self._lm
468 return key in self._lm
469
469
470 def __delitem__(self, key):
470 def __delitem__(self, key):
471 del self._lm[key]
471 del self._lm[key]
472
472
473 def __iter__(self):
473 def __iter__(self):
474 return self._lm.__iter__()
474 return self._lm.__iter__()
475
475
476 def iterkeys(self):
476 def iterkeys(self):
477 return self._lm.iterkeys()
477 return self._lm.iterkeys()
478
478
479 def keys(self):
479 def keys(self):
480 return list(self.iterkeys())
480 return list(self.iterkeys())
481
481
482 def filesnotin(self, m2, match=None):
482 def filesnotin(self, m2, match=None):
483 '''Set of files in this manifest that are not in the other'''
483 '''Set of files in this manifest that are not in the other'''
484 if match:
484 if match:
485 m1 = self.matches(match)
485 m1 = self.matches(match)
486 m2 = m2.matches(match)
486 m2 = m2.matches(match)
487 return m1.filesnotin(m2)
487 return m1.filesnotin(m2)
488 diff = self.diff(m2)
488 diff = self.diff(m2)
489 files = set(
489 files = set(
490 filepath
490 filepath
491 for filepath, hashflags in pycompat.iteritems(diff)
491 for filepath, hashflags in pycompat.iteritems(diff)
492 if hashflags[1][0] is None
492 if hashflags[1][0] is None
493 )
493 )
494 return files
494 return files
495
495
496 @propertycache
496 @propertycache
497 def _dirs(self):
497 def _dirs(self):
498 return pathutil.dirs(self)
498 return pathutil.dirs(self)
499
499
500 def dirs(self):
500 def dirs(self):
501 return self._dirs
501 return self._dirs
502
502
503 def hasdir(self, dir):
503 def hasdir(self, dir):
504 return dir in self._dirs
504 return dir in self._dirs
505
505
506 def _filesfastpath(self, match):
506 def _filesfastpath(self, match):
507 '''Checks whether we can correctly and quickly iterate over matcher
507 '''Checks whether we can correctly and quickly iterate over matcher
508 files instead of over manifest files.'''
508 files instead of over manifest files.'''
509 files = match.files()
509 files = match.files()
510 return len(files) < 100 and (
510 return len(files) < 100 and (
511 match.isexact()
511 match.isexact()
512 or (match.prefix() and all(fn in self for fn in files))
512 or (match.prefix() and all(fn in self for fn in files))
513 )
513 )
514
514
515 def walk(self, match):
515 def walk(self, match):
516 '''Generates matching file names.
516 '''Generates matching file names.
517
517
518 Equivalent to manifest.matches(match).iterkeys(), but without creating
518 Equivalent to manifest.matches(match).iterkeys(), but without creating
519 an entirely new manifest.
519 an entirely new manifest.
520
520
521 It also reports nonexistent files by marking them bad with match.bad().
521 It also reports nonexistent files by marking them bad with match.bad().
522 '''
522 '''
523 if match.always():
523 if match.always():
524 for f in iter(self):
524 for f in iter(self):
525 yield f
525 yield f
526 return
526 return
527
527
528 fset = set(match.files())
528 fset = set(match.files())
529
529
530 # avoid the entire walk if we're only looking for specific files
530 # avoid the entire walk if we're only looking for specific files
531 if self._filesfastpath(match):
531 if self._filesfastpath(match):
532 for fn in sorted(fset):
532 for fn in sorted(fset):
533 yield fn
533 if fn in self:
534 yield fn
534 return
535 return
535
536
536 for fn in self:
537 for fn in self:
537 if fn in fset:
538 if fn in fset:
538 # specified pattern is the exact name
539 # specified pattern is the exact name
539 fset.remove(fn)
540 fset.remove(fn)
540 if match(fn):
541 if match(fn):
541 yield fn
542 yield fn
542
543
543 # for dirstate.walk, files=[''] means "walk the whole tree".
544 # for dirstate.walk, files=[''] means "walk the whole tree".
544 # follow that here, too
545 # follow that here, too
545 fset.discard(b'')
546 fset.discard(b'')
546
547
547 for fn in sorted(fset):
548 for fn in sorted(fset):
548 if not self.hasdir(fn):
549 if not self.hasdir(fn):
549 match.bad(fn, None)
550 match.bad(fn, None)
550
551
551 def matches(self, match):
552 def matches(self, match):
552 '''generate a new manifest filtered by the match argument'''
553 '''generate a new manifest filtered by the match argument'''
553 if match.always():
554 if match.always():
554 return self.copy()
555 return self.copy()
555
556
556 if self._filesfastpath(match):
557 if self._filesfastpath(match):
557 m = manifestdict()
558 m = manifestdict()
558 lm = self._lm
559 lm = self._lm
559 for fn in match.files():
560 for fn in match.files():
560 if fn in lm:
561 if fn in lm:
561 m._lm[fn] = lm[fn]
562 m._lm[fn] = lm[fn]
562 return m
563 return m
563
564
564 m = manifestdict()
565 m = manifestdict()
565 m._lm = self._lm.filtercopy(match)
566 m._lm = self._lm.filtercopy(match)
566 return m
567 return m
567
568
568 def diff(self, m2, match=None, clean=False):
569 def diff(self, m2, match=None, clean=False):
569 '''Finds changes between the current manifest and m2.
570 '''Finds changes between the current manifest and m2.
570
571
571 Args:
572 Args:
572 m2: the manifest to which this manifest should be compared.
573 m2: the manifest to which this manifest should be compared.
573 clean: if true, include files unchanged between these manifests
574 clean: if true, include files unchanged between these manifests
574 with a None value in the returned dictionary.
575 with a None value in the returned dictionary.
575
576
576 The result is returned as a dict with filename as key and
577 The result is returned as a dict with filename as key and
577 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
578 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
578 nodeid in the current/other manifest and fl1/fl2 is the flag
579 nodeid in the current/other manifest and fl1/fl2 is the flag
579 in the current/other manifest. Where the file does not exist,
580 in the current/other manifest. Where the file does not exist,
580 the nodeid will be None and the flags will be the empty
581 the nodeid will be None and the flags will be the empty
581 string.
582 string.
582 '''
583 '''
583 if match:
584 if match:
584 m1 = self.matches(match)
585 m1 = self.matches(match)
585 m2 = m2.matches(match)
586 m2 = m2.matches(match)
586 return m1.diff(m2, clean=clean)
587 return m1.diff(m2, clean=clean)
587 return self._lm.diff(m2._lm, clean)
588 return self._lm.diff(m2._lm, clean)
588
589
589 def setflag(self, key, flag):
590 def setflag(self, key, flag):
590 self._lm[key] = self[key], flag
591 self._lm[key] = self[key], flag
591
592
592 def get(self, key, default=None):
593 def get(self, key, default=None):
593 try:
594 try:
594 return self._lm[key][0]
595 return self._lm[key][0]
595 except KeyError:
596 except KeyError:
596 return default
597 return default
597
598
598 def flags(self, key):
599 def flags(self, key):
599 try:
600 try:
600 return self._lm[key][1]
601 return self._lm[key][1]
601 except KeyError:
602 except KeyError:
602 return b''
603 return b''
603
604
604 def copy(self):
605 def copy(self):
605 c = manifestdict()
606 c = manifestdict()
606 c._lm = self._lm.copy()
607 c._lm = self._lm.copy()
607 return c
608 return c
608
609
609 def items(self):
610 def items(self):
610 return (x[:2] for x in self._lm.iterentries())
611 return (x[:2] for x in self._lm.iterentries())
611
612
612 def iteritems(self):
613 def iteritems(self):
613 return (x[:2] for x in self._lm.iterentries())
614 return (x[:2] for x in self._lm.iterentries())
614
615
615 def iterentries(self):
616 def iterentries(self):
616 return self._lm.iterentries()
617 return self._lm.iterentries()
617
618
618 def text(self):
619 def text(self):
619 # most likely uses native version
620 # most likely uses native version
620 return self._lm.text()
621 return self._lm.text()
621
622
622 def fastdelta(self, base, changes):
623 def fastdelta(self, base, changes):
623 """Given a base manifest text as a bytearray and a list of changes
624 """Given a base manifest text as a bytearray and a list of changes
624 relative to that text, compute a delta that can be used by revlog.
625 relative to that text, compute a delta that can be used by revlog.
625 """
626 """
626 delta = []
627 delta = []
627 dstart = None
628 dstart = None
628 dend = None
629 dend = None
629 dline = [b""]
630 dline = [b""]
630 start = 0
631 start = 0
631 # zero copy representation of base as a buffer
632 # zero copy representation of base as a buffer
632 addbuf = util.buffer(base)
633 addbuf = util.buffer(base)
633
634
634 changes = list(changes)
635 changes = list(changes)
635 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
636 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
636 # start with a readonly loop that finds the offset of
637 # start with a readonly loop that finds the offset of
637 # each line and creates the deltas
638 # each line and creates the deltas
638 for f, todelete in changes:
639 for f, todelete in changes:
639 # bs will either be the index of the item or the insert point
640 # bs will either be the index of the item or the insert point
640 start, end = _msearch(addbuf, f, start)
641 start, end = _msearch(addbuf, f, start)
641 if not todelete:
642 if not todelete:
642 h, fl = self._lm[f]
643 h, fl = self._lm[f]
643 l = b"%s\0%s%s\n" % (f, hex(h), fl)
644 l = b"%s\0%s%s\n" % (f, hex(h), fl)
644 else:
645 else:
645 if start == end:
646 if start == end:
646 # item we want to delete was not found, error out
647 # item we want to delete was not found, error out
647 raise AssertionError(
648 raise AssertionError(
648 _(b"failed to remove %s from manifest") % f
649 _(b"failed to remove %s from manifest") % f
649 )
650 )
650 l = b""
651 l = b""
651 if dstart is not None and dstart <= start and dend >= start:
652 if dstart is not None and dstart <= start and dend >= start:
652 if dend < end:
653 if dend < end:
653 dend = end
654 dend = end
654 if l:
655 if l:
655 dline.append(l)
656 dline.append(l)
656 else:
657 else:
657 if dstart is not None:
658 if dstart is not None:
658 delta.append([dstart, dend, b"".join(dline)])
659 delta.append([dstart, dend, b"".join(dline)])
659 dstart = start
660 dstart = start
660 dend = end
661 dend = end
661 dline = [l]
662 dline = [l]
662
663
663 if dstart is not None:
664 if dstart is not None:
664 delta.append([dstart, dend, b"".join(dline)])
665 delta.append([dstart, dend, b"".join(dline)])
665 # apply the delta to the base, and get a delta for addrevision
666 # apply the delta to the base, and get a delta for addrevision
666 deltatext, arraytext = _addlistdelta(base, delta)
667 deltatext, arraytext = _addlistdelta(base, delta)
667 else:
668 else:
668 # For large changes, it's much cheaper to just build the text and
669 # For large changes, it's much cheaper to just build the text and
669 # diff it.
670 # diff it.
670 arraytext = bytearray(self.text())
671 arraytext = bytearray(self.text())
671 deltatext = mdiff.textdiff(
672 deltatext = mdiff.textdiff(
672 util.buffer(base), util.buffer(arraytext)
673 util.buffer(base), util.buffer(arraytext)
673 )
674 )
674
675
675 return arraytext, deltatext
676 return arraytext, deltatext
676
677
677
678
678 def _msearch(m, s, lo=0, hi=None):
679 def _msearch(m, s, lo=0, hi=None):
679 '''return a tuple (start, end) that says where to find s within m.
680 '''return a tuple (start, end) that says where to find s within m.
680
681
681 If the string is found m[start:end] are the line containing
682 If the string is found m[start:end] are the line containing
682 that string. If start == end the string was not found and
683 that string. If start == end the string was not found and
683 they indicate the proper sorted insertion point.
684 they indicate the proper sorted insertion point.
684
685
685 m should be a buffer, a memoryview or a byte string.
686 m should be a buffer, a memoryview or a byte string.
686 s is a byte string'''
687 s is a byte string'''
687
688
688 def advance(i, c):
689 def advance(i, c):
689 while i < lenm and m[i : i + 1] != c:
690 while i < lenm and m[i : i + 1] != c:
690 i += 1
691 i += 1
691 return i
692 return i
692
693
693 if not s:
694 if not s:
694 return (lo, lo)
695 return (lo, lo)
695 lenm = len(m)
696 lenm = len(m)
696 if not hi:
697 if not hi:
697 hi = lenm
698 hi = lenm
698 while lo < hi:
699 while lo < hi:
699 mid = (lo + hi) // 2
700 mid = (lo + hi) // 2
700 start = mid
701 start = mid
701 while start > 0 and m[start - 1 : start] != b'\n':
702 while start > 0 and m[start - 1 : start] != b'\n':
702 start -= 1
703 start -= 1
703 end = advance(start, b'\0')
704 end = advance(start, b'\0')
704 if bytes(m[start:end]) < s:
705 if bytes(m[start:end]) < s:
705 # we know that after the null there are 40 bytes of sha1
706 # we know that after the null there are 40 bytes of sha1
706 # this translates to the bisect lo = mid + 1
707 # this translates to the bisect lo = mid + 1
707 lo = advance(end + 40, b'\n') + 1
708 lo = advance(end + 40, b'\n') + 1
708 else:
709 else:
709 # this translates to the bisect hi = mid
710 # this translates to the bisect hi = mid
710 hi = start
711 hi = start
711 end = advance(lo, b'\0')
712 end = advance(lo, b'\0')
712 found = m[lo:end]
713 found = m[lo:end]
713 if s == found:
714 if s == found:
714 # we know that after the null there are 40 bytes of sha1
715 # we know that after the null there are 40 bytes of sha1
715 end = advance(end + 40, b'\n')
716 end = advance(end + 40, b'\n')
716 return (lo, end + 1)
717 return (lo, end + 1)
717 else:
718 else:
718 return (lo, lo)
719 return (lo, lo)
719
720
720
721
721 def _checkforbidden(l):
722 def _checkforbidden(l):
722 """Check filenames for illegal characters."""
723 """Check filenames for illegal characters."""
723 for f in l:
724 for f in l:
724 if b'\n' in f or b'\r' in f:
725 if b'\n' in f or b'\r' in f:
725 raise error.StorageError(
726 raise error.StorageError(
726 _(b"'\\n' and '\\r' disallowed in filenames: %r")
727 _(b"'\\n' and '\\r' disallowed in filenames: %r")
727 % pycompat.bytestr(f)
728 % pycompat.bytestr(f)
728 )
729 )
729
730
730
731
731 # apply the changes collected during the bisect loop to our addlist
732 # apply the changes collected during the bisect loop to our addlist
732 # return a delta suitable for addrevision
733 # return a delta suitable for addrevision
733 def _addlistdelta(addlist, x):
734 def _addlistdelta(addlist, x):
734 # for large addlist arrays, building a new array is cheaper
735 # for large addlist arrays, building a new array is cheaper
735 # than repeatedly modifying the existing one
736 # than repeatedly modifying the existing one
736 currentposition = 0
737 currentposition = 0
737 newaddlist = bytearray()
738 newaddlist = bytearray()
738
739
739 for start, end, content in x:
740 for start, end, content in x:
740 newaddlist += addlist[currentposition:start]
741 newaddlist += addlist[currentposition:start]
741 if content:
742 if content:
742 newaddlist += bytearray(content)
743 newaddlist += bytearray(content)
743
744
744 currentposition = end
745 currentposition = end
745
746
746 newaddlist += addlist[currentposition:]
747 newaddlist += addlist[currentposition:]
747
748
748 deltatext = b"".join(
749 deltatext = b"".join(
749 struct.pack(b">lll", start, end, len(content)) + content
750 struct.pack(b">lll", start, end, len(content)) + content
750 for start, end, content in x
751 for start, end, content in x
751 )
752 )
752 return deltatext, newaddlist
753 return deltatext, newaddlist
753
754
754
755
755 def _splittopdir(f):
756 def _splittopdir(f):
756 if b'/' in f:
757 if b'/' in f:
757 dir, subpath = f.split(b'/', 1)
758 dir, subpath = f.split(b'/', 1)
758 return dir + b'/', subpath
759 return dir + b'/', subpath
759 else:
760 else:
760 return b'', f
761 return b'', f
761
762
762
763
763 _noop = lambda s: None
764 _noop = lambda s: None
764
765
765
766
766 class treemanifest(object):
767 class treemanifest(object):
767 def __init__(self, dir=b'', text=b''):
768 def __init__(self, dir=b'', text=b''):
768 self._dir = dir
769 self._dir = dir
769 self._node = nullid
770 self._node = nullid
770 self._loadfunc = _noop
771 self._loadfunc = _noop
771 self._copyfunc = _noop
772 self._copyfunc = _noop
772 self._dirty = False
773 self._dirty = False
773 self._dirs = {}
774 self._dirs = {}
774 self._lazydirs = {}
775 self._lazydirs = {}
775 # Using _lazymanifest here is a little slower than plain old dicts
776 # Using _lazymanifest here is a little slower than plain old dicts
776 self._files = {}
777 self._files = {}
777 self._flags = {}
778 self._flags = {}
778 if text:
779 if text:
779
780
780 def readsubtree(subdir, subm):
781 def readsubtree(subdir, subm):
781 raise AssertionError(
782 raise AssertionError(
782 b'treemanifest constructor only accepts flat manifests'
783 b'treemanifest constructor only accepts flat manifests'
783 )
784 )
784
785
785 self.parse(text, readsubtree)
786 self.parse(text, readsubtree)
786 self._dirty = True # Mark flat manifest dirty after parsing
787 self._dirty = True # Mark flat manifest dirty after parsing
787
788
788 def _subpath(self, path):
789 def _subpath(self, path):
789 return self._dir + path
790 return self._dir + path
790
791
791 def _loadalllazy(self):
792 def _loadalllazy(self):
792 selfdirs = self._dirs
793 selfdirs = self._dirs
793 for d, (path, node, readsubtree, docopy) in pycompat.iteritems(
794 for d, (path, node, readsubtree, docopy) in pycompat.iteritems(
794 self._lazydirs
795 self._lazydirs
795 ):
796 ):
796 if docopy:
797 if docopy:
797 selfdirs[d] = readsubtree(path, node).copy()
798 selfdirs[d] = readsubtree(path, node).copy()
798 else:
799 else:
799 selfdirs[d] = readsubtree(path, node)
800 selfdirs[d] = readsubtree(path, node)
800 self._lazydirs = {}
801 self._lazydirs = {}
801
802
802 def _loadlazy(self, d):
803 def _loadlazy(self, d):
803 v = self._lazydirs.get(d)
804 v = self._lazydirs.get(d)
804 if v:
805 if v:
805 path, node, readsubtree, docopy = v
806 path, node, readsubtree, docopy = v
806 if docopy:
807 if docopy:
807 self._dirs[d] = readsubtree(path, node).copy()
808 self._dirs[d] = readsubtree(path, node).copy()
808 else:
809 else:
809 self._dirs[d] = readsubtree(path, node)
810 self._dirs[d] = readsubtree(path, node)
810 del self._lazydirs[d]
811 del self._lazydirs[d]
811
812
812 def _loadchildrensetlazy(self, visit):
813 def _loadchildrensetlazy(self, visit):
813 if not visit:
814 if not visit:
814 return None
815 return None
815 if visit == b'all' or visit == b'this':
816 if visit == b'all' or visit == b'this':
816 self._loadalllazy()
817 self._loadalllazy()
817 return None
818 return None
818
819
819 loadlazy = self._loadlazy
820 loadlazy = self._loadlazy
820 for k in visit:
821 for k in visit:
821 loadlazy(k + b'/')
822 loadlazy(k + b'/')
822 return visit
823 return visit
823
824
824 def _loaddifflazy(self, t1, t2):
825 def _loaddifflazy(self, t1, t2):
825 """load items in t1 and t2 if they're needed for diffing.
826 """load items in t1 and t2 if they're needed for diffing.
826
827
827 The criteria currently is:
828 The criteria currently is:
828 - if it's not present in _lazydirs in either t1 or t2, load it in the
829 - if it's not present in _lazydirs in either t1 or t2, load it in the
829 other (it may already be loaded or it may not exist, doesn't matter)
830 other (it may already be loaded or it may not exist, doesn't matter)
830 - if it's present in _lazydirs in both, compare the nodeid; if it
831 - if it's present in _lazydirs in both, compare the nodeid; if it
831 differs, load it in both
832 differs, load it in both
832 """
833 """
833 toloadlazy = []
834 toloadlazy = []
834 for d, v1 in pycompat.iteritems(t1._lazydirs):
835 for d, v1 in pycompat.iteritems(t1._lazydirs):
835 v2 = t2._lazydirs.get(d)
836 v2 = t2._lazydirs.get(d)
836 if not v2 or v2[1] != v1[1]:
837 if not v2 or v2[1] != v1[1]:
837 toloadlazy.append(d)
838 toloadlazy.append(d)
838 for d, v1 in pycompat.iteritems(t2._lazydirs):
839 for d, v1 in pycompat.iteritems(t2._lazydirs):
839 if d not in t1._lazydirs:
840 if d not in t1._lazydirs:
840 toloadlazy.append(d)
841 toloadlazy.append(d)
841
842
842 for d in toloadlazy:
843 for d in toloadlazy:
843 t1._loadlazy(d)
844 t1._loadlazy(d)
844 t2._loadlazy(d)
845 t2._loadlazy(d)
845
846
846 def __len__(self):
847 def __len__(self):
847 self._load()
848 self._load()
848 size = len(self._files)
849 size = len(self._files)
849 self._loadalllazy()
850 self._loadalllazy()
850 for m in self._dirs.values():
851 for m in self._dirs.values():
851 size += m.__len__()
852 size += m.__len__()
852 return size
853 return size
853
854
854 def __nonzero__(self):
855 def __nonzero__(self):
855 # Faster than "__len() != 0" since it avoids loading sub-manifests
856 # Faster than "__len() != 0" since it avoids loading sub-manifests
856 return not self._isempty()
857 return not self._isempty()
857
858
858 __bool__ = __nonzero__
859 __bool__ = __nonzero__
859
860
860 def _isempty(self):
861 def _isempty(self):
861 self._load() # for consistency; already loaded by all callers
862 self._load() # for consistency; already loaded by all callers
862 # See if we can skip loading everything.
863 # See if we can skip loading everything.
863 if self._files or (
864 if self._files or (
864 self._dirs and any(not m._isempty() for m in self._dirs.values())
865 self._dirs and any(not m._isempty() for m in self._dirs.values())
865 ):
866 ):
866 return False
867 return False
867 self._loadalllazy()
868 self._loadalllazy()
868 return not self._dirs or all(m._isempty() for m in self._dirs.values())
869 return not self._dirs or all(m._isempty() for m in self._dirs.values())
869
870
870 def __repr__(self):
871 def __repr__(self):
871 return (
872 return (
872 b'<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>'
873 b'<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>'
873 % (
874 % (
874 self._dir,
875 self._dir,
875 hex(self._node),
876 hex(self._node),
876 bool(self._loadfunc is _noop),
877 bool(self._loadfunc is _noop),
877 self._dirty,
878 self._dirty,
878 id(self),
879 id(self),
879 )
880 )
880 )
881 )
881
882
882 def dir(self):
883 def dir(self):
883 '''The directory that this tree manifest represents, including a
884 '''The directory that this tree manifest represents, including a
884 trailing '/'. Empty string for the repo root directory.'''
885 trailing '/'. Empty string for the repo root directory.'''
885 return self._dir
886 return self._dir
886
887
887 def node(self):
888 def node(self):
888 '''This node of this instance. nullid for unsaved instances. Should
889 '''This node of this instance. nullid for unsaved instances. Should
889 be updated when the instance is read or written from a revlog.
890 be updated when the instance is read or written from a revlog.
890 '''
891 '''
891 assert not self._dirty
892 assert not self._dirty
892 return self._node
893 return self._node
893
894
894 def setnode(self, node):
895 def setnode(self, node):
895 self._node = node
896 self._node = node
896 self._dirty = False
897 self._dirty = False
897
898
898 def iterentries(self):
899 def iterentries(self):
899 self._load()
900 self._load()
900 self._loadalllazy()
901 self._loadalllazy()
901 for p, n in sorted(
902 for p, n in sorted(
902 itertools.chain(self._dirs.items(), self._files.items())
903 itertools.chain(self._dirs.items(), self._files.items())
903 ):
904 ):
904 if p in self._files:
905 if p in self._files:
905 yield self._subpath(p), n, self._flags.get(p, b'')
906 yield self._subpath(p), n, self._flags.get(p, b'')
906 else:
907 else:
907 for x in n.iterentries():
908 for x in n.iterentries():
908 yield x
909 yield x
909
910
910 def items(self):
911 def items(self):
911 self._load()
912 self._load()
912 self._loadalllazy()
913 self._loadalllazy()
913 for p, n in sorted(
914 for p, n in sorted(
914 itertools.chain(self._dirs.items(), self._files.items())
915 itertools.chain(self._dirs.items(), self._files.items())
915 ):
916 ):
916 if p in self._files:
917 if p in self._files:
917 yield self._subpath(p), n
918 yield self._subpath(p), n
918 else:
919 else:
919 for f, sn in pycompat.iteritems(n):
920 for f, sn in pycompat.iteritems(n):
920 yield f, sn
921 yield f, sn
921
922
922 iteritems = items
923 iteritems = items
923
924
924 def iterkeys(self):
925 def iterkeys(self):
925 self._load()
926 self._load()
926 self._loadalllazy()
927 self._loadalllazy()
927 for p in sorted(itertools.chain(self._dirs, self._files)):
928 for p in sorted(itertools.chain(self._dirs, self._files)):
928 if p in self._files:
929 if p in self._files:
929 yield self._subpath(p)
930 yield self._subpath(p)
930 else:
931 else:
931 for f in self._dirs[p]:
932 for f in self._dirs[p]:
932 yield f
933 yield f
933
934
934 def keys(self):
935 def keys(self):
935 return list(self.iterkeys())
936 return list(self.iterkeys())
936
937
937 def __iter__(self):
938 def __iter__(self):
938 return self.iterkeys()
939 return self.iterkeys()
939
940
940 def __contains__(self, f):
941 def __contains__(self, f):
941 if f is None:
942 if f is None:
942 return False
943 return False
943 self._load()
944 self._load()
944 dir, subpath = _splittopdir(f)
945 dir, subpath = _splittopdir(f)
945 if dir:
946 if dir:
946 self._loadlazy(dir)
947 self._loadlazy(dir)
947
948
948 if dir not in self._dirs:
949 if dir not in self._dirs:
949 return False
950 return False
950
951
951 return self._dirs[dir].__contains__(subpath)
952 return self._dirs[dir].__contains__(subpath)
952 else:
953 else:
953 return f in self._files
954 return f in self._files
954
955
955 def get(self, f, default=None):
956 def get(self, f, default=None):
956 self._load()
957 self._load()
957 dir, subpath = _splittopdir(f)
958 dir, subpath = _splittopdir(f)
958 if dir:
959 if dir:
959 self._loadlazy(dir)
960 self._loadlazy(dir)
960
961
961 if dir not in self._dirs:
962 if dir not in self._dirs:
962 return default
963 return default
963 return self._dirs[dir].get(subpath, default)
964 return self._dirs[dir].get(subpath, default)
964 else:
965 else:
965 return self._files.get(f, default)
966 return self._files.get(f, default)
966
967
967 def __getitem__(self, f):
968 def __getitem__(self, f):
968 self._load()
969 self._load()
969 dir, subpath = _splittopdir(f)
970 dir, subpath = _splittopdir(f)
970 if dir:
971 if dir:
971 self._loadlazy(dir)
972 self._loadlazy(dir)
972
973
973 return self._dirs[dir].__getitem__(subpath)
974 return self._dirs[dir].__getitem__(subpath)
974 else:
975 else:
975 return self._files[f]
976 return self._files[f]
976
977
977 def flags(self, f):
978 def flags(self, f):
978 self._load()
979 self._load()
979 dir, subpath = _splittopdir(f)
980 dir, subpath = _splittopdir(f)
980 if dir:
981 if dir:
981 self._loadlazy(dir)
982 self._loadlazy(dir)
982
983
983 if dir not in self._dirs:
984 if dir not in self._dirs:
984 return b''
985 return b''
985 return self._dirs[dir].flags(subpath)
986 return self._dirs[dir].flags(subpath)
986 else:
987 else:
987 if f in self._lazydirs or f in self._dirs:
988 if f in self._lazydirs or f in self._dirs:
988 return b''
989 return b''
989 return self._flags.get(f, b'')
990 return self._flags.get(f, b'')
990
991
991 def find(self, f):
992 def find(self, f):
992 self._load()
993 self._load()
993 dir, subpath = _splittopdir(f)
994 dir, subpath = _splittopdir(f)
994 if dir:
995 if dir:
995 self._loadlazy(dir)
996 self._loadlazy(dir)
996
997
997 return self._dirs[dir].find(subpath)
998 return self._dirs[dir].find(subpath)
998 else:
999 else:
999 return self._files[f], self._flags.get(f, b'')
1000 return self._files[f], self._flags.get(f, b'')
1000
1001
1001 def __delitem__(self, f):
1002 def __delitem__(self, f):
1002 self._load()
1003 self._load()
1003 dir, subpath = _splittopdir(f)
1004 dir, subpath = _splittopdir(f)
1004 if dir:
1005 if dir:
1005 self._loadlazy(dir)
1006 self._loadlazy(dir)
1006
1007
1007 self._dirs[dir].__delitem__(subpath)
1008 self._dirs[dir].__delitem__(subpath)
1008 # If the directory is now empty, remove it
1009 # If the directory is now empty, remove it
1009 if self._dirs[dir]._isempty():
1010 if self._dirs[dir]._isempty():
1010 del self._dirs[dir]
1011 del self._dirs[dir]
1011 else:
1012 else:
1012 del self._files[f]
1013 del self._files[f]
1013 if f in self._flags:
1014 if f in self._flags:
1014 del self._flags[f]
1015 del self._flags[f]
1015 self._dirty = True
1016 self._dirty = True
1016
1017
1017 def __setitem__(self, f, n):
1018 def __setitem__(self, f, n):
1018 assert n is not None
1019 assert n is not None
1019 self._load()
1020 self._load()
1020 dir, subpath = _splittopdir(f)
1021 dir, subpath = _splittopdir(f)
1021 if dir:
1022 if dir:
1022 self._loadlazy(dir)
1023 self._loadlazy(dir)
1023 if dir not in self._dirs:
1024 if dir not in self._dirs:
1024 self._dirs[dir] = treemanifest(self._subpath(dir))
1025 self._dirs[dir] = treemanifest(self._subpath(dir))
1025 self._dirs[dir].__setitem__(subpath, n)
1026 self._dirs[dir].__setitem__(subpath, n)
1026 else:
1027 else:
1027 self._files[f] = n[:21] # to match manifestdict's behavior
1028 self._files[f] = n[:21] # to match manifestdict's behavior
1028 self._dirty = True
1029 self._dirty = True
1029
1030
1030 def _load(self):
1031 def _load(self):
1031 if self._loadfunc is not _noop:
1032 if self._loadfunc is not _noop:
1032 lf, self._loadfunc = self._loadfunc, _noop
1033 lf, self._loadfunc = self._loadfunc, _noop
1033 lf(self)
1034 lf(self)
1034 elif self._copyfunc is not _noop:
1035 elif self._copyfunc is not _noop:
1035 cf, self._copyfunc = self._copyfunc, _noop
1036 cf, self._copyfunc = self._copyfunc, _noop
1036 cf(self)
1037 cf(self)
1037
1038
1038 def setflag(self, f, flags):
1039 def setflag(self, f, flags):
1039 """Set the flags (symlink, executable) for path f."""
1040 """Set the flags (symlink, executable) for path f."""
1040 self._load()
1041 self._load()
1041 dir, subpath = _splittopdir(f)
1042 dir, subpath = _splittopdir(f)
1042 if dir:
1043 if dir:
1043 self._loadlazy(dir)
1044 self._loadlazy(dir)
1044 if dir not in self._dirs:
1045 if dir not in self._dirs:
1045 self._dirs[dir] = treemanifest(self._subpath(dir))
1046 self._dirs[dir] = treemanifest(self._subpath(dir))
1046 self._dirs[dir].setflag(subpath, flags)
1047 self._dirs[dir].setflag(subpath, flags)
1047 else:
1048 else:
1048 self._flags[f] = flags
1049 self._flags[f] = flags
1049 self._dirty = True
1050 self._dirty = True
1050
1051
1051 def copy(self):
1052 def copy(self):
1052 copy = treemanifest(self._dir)
1053 copy = treemanifest(self._dir)
1053 copy._node = self._node
1054 copy._node = self._node
1054 copy._dirty = self._dirty
1055 copy._dirty = self._dirty
1055 if self._copyfunc is _noop:
1056 if self._copyfunc is _noop:
1056
1057
1057 def _copyfunc(s):
1058 def _copyfunc(s):
1058 self._load()
1059 self._load()
1059 s._lazydirs = {
1060 s._lazydirs = {
1060 d: (p, n, r, True)
1061 d: (p, n, r, True)
1061 for d, (p, n, r, c) in pycompat.iteritems(self._lazydirs)
1062 for d, (p, n, r, c) in pycompat.iteritems(self._lazydirs)
1062 }
1063 }
1063 sdirs = s._dirs
1064 sdirs = s._dirs
1064 for d, v in pycompat.iteritems(self._dirs):
1065 for d, v in pycompat.iteritems(self._dirs):
1065 sdirs[d] = v.copy()
1066 sdirs[d] = v.copy()
1066 s._files = dict.copy(self._files)
1067 s._files = dict.copy(self._files)
1067 s._flags = dict.copy(self._flags)
1068 s._flags = dict.copy(self._flags)
1068
1069
1069 if self._loadfunc is _noop:
1070 if self._loadfunc is _noop:
1070 _copyfunc(copy)
1071 _copyfunc(copy)
1071 else:
1072 else:
1072 copy._copyfunc = _copyfunc
1073 copy._copyfunc = _copyfunc
1073 else:
1074 else:
1074 copy._copyfunc = self._copyfunc
1075 copy._copyfunc = self._copyfunc
1075 return copy
1076 return copy
1076
1077
1077 def filesnotin(self, m2, match=None):
1078 def filesnotin(self, m2, match=None):
1078 '''Set of files in this manifest that are not in the other'''
1079 '''Set of files in this manifest that are not in the other'''
1079 if match and not match.always():
1080 if match and not match.always():
1080 m1 = self.matches(match)
1081 m1 = self.matches(match)
1081 m2 = m2.matches(match)
1082 m2 = m2.matches(match)
1082 return m1.filesnotin(m2)
1083 return m1.filesnotin(m2)
1083
1084
1084 files = set()
1085 files = set()
1085
1086
1086 def _filesnotin(t1, t2):
1087 def _filesnotin(t1, t2):
1087 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1088 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1088 return
1089 return
1089 t1._load()
1090 t1._load()
1090 t2._load()
1091 t2._load()
1091 self._loaddifflazy(t1, t2)
1092 self._loaddifflazy(t1, t2)
1092 for d, m1 in pycompat.iteritems(t1._dirs):
1093 for d, m1 in pycompat.iteritems(t1._dirs):
1093 if d in t2._dirs:
1094 if d in t2._dirs:
1094 m2 = t2._dirs[d]
1095 m2 = t2._dirs[d]
1095 _filesnotin(m1, m2)
1096 _filesnotin(m1, m2)
1096 else:
1097 else:
1097 files.update(m1.iterkeys())
1098 files.update(m1.iterkeys())
1098
1099
1099 for fn in t1._files:
1100 for fn in t1._files:
1100 if fn not in t2._files:
1101 if fn not in t2._files:
1101 files.add(t1._subpath(fn))
1102 files.add(t1._subpath(fn))
1102
1103
1103 _filesnotin(self, m2)
1104 _filesnotin(self, m2)
1104 return files
1105 return files
1105
1106
1106 @propertycache
1107 @propertycache
1107 def _alldirs(self):
1108 def _alldirs(self):
1108 return pathutil.dirs(self)
1109 return pathutil.dirs(self)
1109
1110
1110 def dirs(self):
1111 def dirs(self):
1111 return self._alldirs
1112 return self._alldirs
1112
1113
1113 def hasdir(self, dir):
1114 def hasdir(self, dir):
1114 self._load()
1115 self._load()
1115 topdir, subdir = _splittopdir(dir)
1116 topdir, subdir = _splittopdir(dir)
1116 if topdir:
1117 if topdir:
1117 self._loadlazy(topdir)
1118 self._loadlazy(topdir)
1118 if topdir in self._dirs:
1119 if topdir in self._dirs:
1119 return self._dirs[topdir].hasdir(subdir)
1120 return self._dirs[topdir].hasdir(subdir)
1120 return False
1121 return False
1121 dirslash = dir + b'/'
1122 dirslash = dir + b'/'
1122 return dirslash in self._dirs or dirslash in self._lazydirs
1123 return dirslash in self._dirs or dirslash in self._lazydirs
1123
1124
1124 def walk(self, match):
1125 def walk(self, match):
1125 '''Generates matching file names.
1126 '''Generates matching file names.
1126
1127
1127 Equivalent to manifest.matches(match).iterkeys(), but without creating
1128 Equivalent to manifest.matches(match).iterkeys(), but without creating
1128 an entirely new manifest.
1129 an entirely new manifest.
1129
1130
1130 It also reports nonexistent files by marking them bad with match.bad().
1131 It also reports nonexistent files by marking them bad with match.bad().
1131 '''
1132 '''
1132 if match.always():
1133 if match.always():
1133 for f in iter(self):
1134 for f in iter(self):
1134 yield f
1135 yield f
1135 return
1136 return
1136
1137
1137 fset = set(match.files())
1138 fset = set(match.files())
1138
1139
1139 for fn in self._walk(match):
1140 for fn in self._walk(match):
1140 if fn in fset:
1141 if fn in fset:
1141 # specified pattern is the exact name
1142 # specified pattern is the exact name
1142 fset.remove(fn)
1143 fset.remove(fn)
1143 yield fn
1144 yield fn
1144
1145
1145 # for dirstate.walk, files=[''] means "walk the whole tree".
1146 # for dirstate.walk, files=[''] means "walk the whole tree".
1146 # follow that here, too
1147 # follow that here, too
1147 fset.discard(b'')
1148 fset.discard(b'')
1148
1149
1149 for fn in sorted(fset):
1150 for fn in sorted(fset):
1150 if not self.hasdir(fn):
1151 if not self.hasdir(fn):
1151 match.bad(fn, None)
1152 match.bad(fn, None)
1152
1153
1153 def _walk(self, match):
1154 def _walk(self, match):
1154 '''Recursively generates matching file names for walk().'''
1155 '''Recursively generates matching file names for walk().'''
1155 visit = match.visitchildrenset(self._dir[:-1])
1156 visit = match.visitchildrenset(self._dir[:-1])
1156 if not visit:
1157 if not visit:
1157 return
1158 return
1158
1159
1159 # yield this dir's files and walk its submanifests
1160 # yield this dir's files and walk its submanifests
1160 self._load()
1161 self._load()
1161 visit = self._loadchildrensetlazy(visit)
1162 visit = self._loadchildrensetlazy(visit)
1162 for p in sorted(list(self._dirs) + list(self._files)):
1163 for p in sorted(list(self._dirs) + list(self._files)):
1163 if p in self._files:
1164 if p in self._files:
1164 fullp = self._subpath(p)
1165 fullp = self._subpath(p)
1165 if match(fullp):
1166 if match(fullp):
1166 yield fullp
1167 yield fullp
1167 else:
1168 else:
1168 if not visit or p[:-1] in visit:
1169 if not visit or p[:-1] in visit:
1169 for f in self._dirs[p]._walk(match):
1170 for f in self._dirs[p]._walk(match):
1170 yield f
1171 yield f
1171
1172
1172 def matches(self, match):
1173 def matches(self, match):
1173 '''generate a new manifest filtered by the match argument'''
1174 '''generate a new manifest filtered by the match argument'''
1174 if match.always():
1175 if match.always():
1175 return self.copy()
1176 return self.copy()
1176
1177
1177 return self._matches(match)
1178 return self._matches(match)
1178
1179
1179 def _matches(self, match):
1180 def _matches(self, match):
1180 '''recursively generate a new manifest filtered by the match argument.
1181 '''recursively generate a new manifest filtered by the match argument.
1181 '''
1182 '''
1182
1183
1183 visit = match.visitchildrenset(self._dir[:-1])
1184 visit = match.visitchildrenset(self._dir[:-1])
1184 if visit == b'all':
1185 if visit == b'all':
1185 return self.copy()
1186 return self.copy()
1186 ret = treemanifest(self._dir)
1187 ret = treemanifest(self._dir)
1187 if not visit:
1188 if not visit:
1188 return ret
1189 return ret
1189
1190
1190 self._load()
1191 self._load()
1191 for fn in self._files:
1192 for fn in self._files:
1192 # While visitchildrenset *usually* lists only subdirs, this is
1193 # While visitchildrenset *usually* lists only subdirs, this is
1193 # actually up to the matcher and may have some files in the set().
1194 # actually up to the matcher and may have some files in the set().
1194 # If visit == 'this', we should obviously look at the files in this
1195 # If visit == 'this', we should obviously look at the files in this
1195 # directory; if visit is a set, and fn is in it, we should inspect
1196 # directory; if visit is a set, and fn is in it, we should inspect
1196 # fn (but no need to inspect things not in the set).
1197 # fn (but no need to inspect things not in the set).
1197 if visit != b'this' and fn not in visit:
1198 if visit != b'this' and fn not in visit:
1198 continue
1199 continue
1199 fullp = self._subpath(fn)
1200 fullp = self._subpath(fn)
1200 # visitchildrenset isn't perfect, we still need to call the regular
1201 # visitchildrenset isn't perfect, we still need to call the regular
1201 # matcher code to further filter results.
1202 # matcher code to further filter results.
1202 if not match(fullp):
1203 if not match(fullp):
1203 continue
1204 continue
1204 ret._files[fn] = self._files[fn]
1205 ret._files[fn] = self._files[fn]
1205 if fn in self._flags:
1206 if fn in self._flags:
1206 ret._flags[fn] = self._flags[fn]
1207 ret._flags[fn] = self._flags[fn]
1207
1208
1208 visit = self._loadchildrensetlazy(visit)
1209 visit = self._loadchildrensetlazy(visit)
1209 for dir, subm in pycompat.iteritems(self._dirs):
1210 for dir, subm in pycompat.iteritems(self._dirs):
1210 if visit and dir[:-1] not in visit:
1211 if visit and dir[:-1] not in visit:
1211 continue
1212 continue
1212 m = subm._matches(match)
1213 m = subm._matches(match)
1213 if not m._isempty():
1214 if not m._isempty():
1214 ret._dirs[dir] = m
1215 ret._dirs[dir] = m
1215
1216
1216 if not ret._isempty():
1217 if not ret._isempty():
1217 ret._dirty = True
1218 ret._dirty = True
1218 return ret
1219 return ret
1219
1220
1220 def diff(self, m2, match=None, clean=False):
1221 def diff(self, m2, match=None, clean=False):
1221 '''Finds changes between the current manifest and m2.
1222 '''Finds changes between the current manifest and m2.
1222
1223
1223 Args:
1224 Args:
1224 m2: the manifest to which this manifest should be compared.
1225 m2: the manifest to which this manifest should be compared.
1225 clean: if true, include files unchanged between these manifests
1226 clean: if true, include files unchanged between these manifests
1226 with a None value in the returned dictionary.
1227 with a None value in the returned dictionary.
1227
1228
1228 The result is returned as a dict with filename as key and
1229 The result is returned as a dict with filename as key and
1229 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1230 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1230 nodeid in the current/other manifest and fl1/fl2 is the flag
1231 nodeid in the current/other manifest and fl1/fl2 is the flag
1231 in the current/other manifest. Where the file does not exist,
1232 in the current/other manifest. Where the file does not exist,
1232 the nodeid will be None and the flags will be the empty
1233 the nodeid will be None and the flags will be the empty
1233 string.
1234 string.
1234 '''
1235 '''
1235 if match and not match.always():
1236 if match and not match.always():
1236 m1 = self.matches(match)
1237 m1 = self.matches(match)
1237 m2 = m2.matches(match)
1238 m2 = m2.matches(match)
1238 return m1.diff(m2, clean=clean)
1239 return m1.diff(m2, clean=clean)
1239 result = {}
1240 result = {}
1240 emptytree = treemanifest()
1241 emptytree = treemanifest()
1241
1242
1242 def _iterativediff(t1, t2, stack):
1243 def _iterativediff(t1, t2, stack):
1243 """compares two tree manifests and append new tree-manifests which
1244 """compares two tree manifests and append new tree-manifests which
1244 needs to be compared to stack"""
1245 needs to be compared to stack"""
1245 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1246 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1246 return
1247 return
1247 t1._load()
1248 t1._load()
1248 t2._load()
1249 t2._load()
1249 self._loaddifflazy(t1, t2)
1250 self._loaddifflazy(t1, t2)
1250
1251
1251 for d, m1 in pycompat.iteritems(t1._dirs):
1252 for d, m1 in pycompat.iteritems(t1._dirs):
1252 m2 = t2._dirs.get(d, emptytree)
1253 m2 = t2._dirs.get(d, emptytree)
1253 stack.append((m1, m2))
1254 stack.append((m1, m2))
1254
1255
1255 for d, m2 in pycompat.iteritems(t2._dirs):
1256 for d, m2 in pycompat.iteritems(t2._dirs):
1256 if d not in t1._dirs:
1257 if d not in t1._dirs:
1257 stack.append((emptytree, m2))
1258 stack.append((emptytree, m2))
1258
1259
1259 for fn, n1 in pycompat.iteritems(t1._files):
1260 for fn, n1 in pycompat.iteritems(t1._files):
1260 fl1 = t1._flags.get(fn, b'')
1261 fl1 = t1._flags.get(fn, b'')
1261 n2 = t2._files.get(fn, None)
1262 n2 = t2._files.get(fn, None)
1262 fl2 = t2._flags.get(fn, b'')
1263 fl2 = t2._flags.get(fn, b'')
1263 if n1 != n2 or fl1 != fl2:
1264 if n1 != n2 or fl1 != fl2:
1264 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1265 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1265 elif clean:
1266 elif clean:
1266 result[t1._subpath(fn)] = None
1267 result[t1._subpath(fn)] = None
1267
1268
1268 for fn, n2 in pycompat.iteritems(t2._files):
1269 for fn, n2 in pycompat.iteritems(t2._files):
1269 if fn not in t1._files:
1270 if fn not in t1._files:
1270 fl2 = t2._flags.get(fn, b'')
1271 fl2 = t2._flags.get(fn, b'')
1271 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1272 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1272
1273
1273 stackls = []
1274 stackls = []
1274 _iterativediff(self, m2, stackls)
1275 _iterativediff(self, m2, stackls)
1275 while stackls:
1276 while stackls:
1276 t1, t2 = stackls.pop()
1277 t1, t2 = stackls.pop()
1277 # stackls is populated in the function call
1278 # stackls is populated in the function call
1278 _iterativediff(t1, t2, stackls)
1279 _iterativediff(t1, t2, stackls)
1279 return result
1280 return result
1280
1281
1281 def unmodifiedsince(self, m2):
1282 def unmodifiedsince(self, m2):
1282 return not self._dirty and not m2._dirty and self._node == m2._node
1283 return not self._dirty and not m2._dirty and self._node == m2._node
1283
1284
1284 def parse(self, text, readsubtree):
1285 def parse(self, text, readsubtree):
1285 selflazy = self._lazydirs
1286 selflazy = self._lazydirs
1286 subpath = self._subpath
1287 subpath = self._subpath
1287 for f, n, fl in _parse(text):
1288 for f, n, fl in _parse(text):
1288 if fl == b't':
1289 if fl == b't':
1289 f = f + b'/'
1290 f = f + b'/'
1290 # False below means "doesn't need to be copied" and can use the
1291 # False below means "doesn't need to be copied" and can use the
1291 # cached value from readsubtree directly.
1292 # cached value from readsubtree directly.
1292 selflazy[f] = (subpath(f), n, readsubtree, False)
1293 selflazy[f] = (subpath(f), n, readsubtree, False)
1293 elif b'/' in f:
1294 elif b'/' in f:
1294 # This is a flat manifest, so use __setitem__ and setflag rather
1295 # This is a flat manifest, so use __setitem__ and setflag rather
1295 # than assigning directly to _files and _flags, so we can
1296 # than assigning directly to _files and _flags, so we can
1296 # assign a path in a subdirectory, and to mark dirty (compared
1297 # assign a path in a subdirectory, and to mark dirty (compared
1297 # to nullid).
1298 # to nullid).
1298 self[f] = n
1299 self[f] = n
1299 if fl:
1300 if fl:
1300 self.setflag(f, fl)
1301 self.setflag(f, fl)
1301 else:
1302 else:
1302 # Assigning to _files and _flags avoids marking as dirty,
1303 # Assigning to _files and _flags avoids marking as dirty,
1303 # and should be a little faster.
1304 # and should be a little faster.
1304 self._files[f] = n
1305 self._files[f] = n
1305 if fl:
1306 if fl:
1306 self._flags[f] = fl
1307 self._flags[f] = fl
1307
1308
1308 def text(self):
1309 def text(self):
1309 """Get the full data of this manifest as a bytestring."""
1310 """Get the full data of this manifest as a bytestring."""
1310 self._load()
1311 self._load()
1311 return _text(self.iterentries())
1312 return _text(self.iterentries())
1312
1313
1313 def dirtext(self):
1314 def dirtext(self):
1314 """Get the full data of this directory as a bytestring. Make sure that
1315 """Get the full data of this directory as a bytestring. Make sure that
1315 any submanifests have been written first, so their nodeids are correct.
1316 any submanifests have been written first, so their nodeids are correct.
1316 """
1317 """
1317 self._load()
1318 self._load()
1318 flags = self.flags
1319 flags = self.flags
1319 lazydirs = [
1320 lazydirs = [
1320 (d[:-1], v[1], b't') for d, v in pycompat.iteritems(self._lazydirs)
1321 (d[:-1], v[1], b't') for d, v in pycompat.iteritems(self._lazydirs)
1321 ]
1322 ]
1322 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1323 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1323 files = [(f, self._files[f], flags(f)) for f in self._files]
1324 files = [(f, self._files[f], flags(f)) for f in self._files]
1324 return _text(sorted(dirs + files + lazydirs))
1325 return _text(sorted(dirs + files + lazydirs))
1325
1326
1326 def read(self, gettext, readsubtree):
1327 def read(self, gettext, readsubtree):
1327 def _load_for_read(s):
1328 def _load_for_read(s):
1328 s.parse(gettext(), readsubtree)
1329 s.parse(gettext(), readsubtree)
1329 s._dirty = False
1330 s._dirty = False
1330
1331
1331 self._loadfunc = _load_for_read
1332 self._loadfunc = _load_for_read
1332
1333
1333 def writesubtrees(self, m1, m2, writesubtree, match):
1334 def writesubtrees(self, m1, m2, writesubtree, match):
1334 self._load() # for consistency; should never have any effect here
1335 self._load() # for consistency; should never have any effect here
1335 m1._load()
1336 m1._load()
1336 m2._load()
1337 m2._load()
1337 emptytree = treemanifest()
1338 emptytree = treemanifest()
1338
1339
1339 def getnode(m, d):
1340 def getnode(m, d):
1340 ld = m._lazydirs.get(d)
1341 ld = m._lazydirs.get(d)
1341 if ld:
1342 if ld:
1342 return ld[1]
1343 return ld[1]
1343 return m._dirs.get(d, emptytree)._node
1344 return m._dirs.get(d, emptytree)._node
1344
1345
1345 # let's skip investigating things that `match` says we do not need.
1346 # let's skip investigating things that `match` says we do not need.
1346 visit = match.visitchildrenset(self._dir[:-1])
1347 visit = match.visitchildrenset(self._dir[:-1])
1347 visit = self._loadchildrensetlazy(visit)
1348 visit = self._loadchildrensetlazy(visit)
1348 if visit == b'this' or visit == b'all':
1349 if visit == b'this' or visit == b'all':
1349 visit = None
1350 visit = None
1350 for d, subm in pycompat.iteritems(self._dirs):
1351 for d, subm in pycompat.iteritems(self._dirs):
1351 if visit and d[:-1] not in visit:
1352 if visit and d[:-1] not in visit:
1352 continue
1353 continue
1353 subp1 = getnode(m1, d)
1354 subp1 = getnode(m1, d)
1354 subp2 = getnode(m2, d)
1355 subp2 = getnode(m2, d)
1355 if subp1 == nullid:
1356 if subp1 == nullid:
1356 subp1, subp2 = subp2, subp1
1357 subp1, subp2 = subp2, subp1
1357 writesubtree(subm, subp1, subp2, match)
1358 writesubtree(subm, subp1, subp2, match)
1358
1359
1359 def walksubtrees(self, matcher=None):
1360 def walksubtrees(self, matcher=None):
1360 """Returns an iterator of the subtrees of this manifest, including this
1361 """Returns an iterator of the subtrees of this manifest, including this
1361 manifest itself.
1362 manifest itself.
1362
1363
1363 If `matcher` is provided, it only returns subtrees that match.
1364 If `matcher` is provided, it only returns subtrees that match.
1364 """
1365 """
1365 if matcher and not matcher.visitdir(self._dir[:-1]):
1366 if matcher and not matcher.visitdir(self._dir[:-1]):
1366 return
1367 return
1367 if not matcher or matcher(self._dir[:-1]):
1368 if not matcher or matcher(self._dir[:-1]):
1368 yield self
1369 yield self
1369
1370
1370 self._load()
1371 self._load()
1371 # OPT: use visitchildrenset to avoid loading everything.
1372 # OPT: use visitchildrenset to avoid loading everything.
1372 self._loadalllazy()
1373 self._loadalllazy()
1373 for d, subm in pycompat.iteritems(self._dirs):
1374 for d, subm in pycompat.iteritems(self._dirs):
1374 for subtree in subm.walksubtrees(matcher=matcher):
1375 for subtree in subm.walksubtrees(matcher=matcher):
1375 yield subtree
1376 yield subtree
1376
1377
1377
1378
1378 class manifestfulltextcache(util.lrucachedict):
1379 class manifestfulltextcache(util.lrucachedict):
1379 """File-backed LRU cache for the manifest cache
1380 """File-backed LRU cache for the manifest cache
1380
1381
1381 File consists of entries, up to EOF:
1382 File consists of entries, up to EOF:
1382
1383
1383 - 20 bytes node, 4 bytes length, <length> manifest data
1384 - 20 bytes node, 4 bytes length, <length> manifest data
1384
1385
1385 These are written in reverse cache order (oldest to newest).
1386 These are written in reverse cache order (oldest to newest).
1386
1387
1387 """
1388 """
1388
1389
1389 _file = b'manifestfulltextcache'
1390 _file = b'manifestfulltextcache'
1390
1391
1391 def __init__(self, max):
1392 def __init__(self, max):
1392 super(manifestfulltextcache, self).__init__(max)
1393 super(manifestfulltextcache, self).__init__(max)
1393 self._dirty = False
1394 self._dirty = False
1394 self._read = False
1395 self._read = False
1395 self._opener = None
1396 self._opener = None
1396
1397
1397 def read(self):
1398 def read(self):
1398 if self._read or self._opener is None:
1399 if self._read or self._opener is None:
1399 return
1400 return
1400
1401
1401 try:
1402 try:
1402 with self._opener(self._file) as fp:
1403 with self._opener(self._file) as fp:
1403 set = super(manifestfulltextcache, self).__setitem__
1404 set = super(manifestfulltextcache, self).__setitem__
1404 # ignore trailing data, this is a cache, corruption is skipped
1405 # ignore trailing data, this is a cache, corruption is skipped
1405 while True:
1406 while True:
1406 node = fp.read(20)
1407 node = fp.read(20)
1407 if len(node) < 20:
1408 if len(node) < 20:
1408 break
1409 break
1409 try:
1410 try:
1410 size = struct.unpack(b'>L', fp.read(4))[0]
1411 size = struct.unpack(b'>L', fp.read(4))[0]
1411 except struct.error:
1412 except struct.error:
1412 break
1413 break
1413 value = bytearray(fp.read(size))
1414 value = bytearray(fp.read(size))
1414 if len(value) != size:
1415 if len(value) != size:
1415 break
1416 break
1416 set(node, value)
1417 set(node, value)
1417 except IOError:
1418 except IOError:
1418 # the file is allowed to be missing
1419 # the file is allowed to be missing
1419 pass
1420 pass
1420
1421
1421 self._read = True
1422 self._read = True
1422 self._dirty = False
1423 self._dirty = False
1423
1424
1424 def write(self):
1425 def write(self):
1425 if not self._dirty or self._opener is None:
1426 if not self._dirty or self._opener is None:
1426 return
1427 return
1427 # rotate backwards to the first used node
1428 # rotate backwards to the first used node
1428 with self._opener(
1429 with self._opener(
1429 self._file, b'w', atomictemp=True, checkambig=True
1430 self._file, b'w', atomictemp=True, checkambig=True
1430 ) as fp:
1431 ) as fp:
1431 node = self._head.prev
1432 node = self._head.prev
1432 while True:
1433 while True:
1433 if node.key in self._cache:
1434 if node.key in self._cache:
1434 fp.write(node.key)
1435 fp.write(node.key)
1435 fp.write(struct.pack(b'>L', len(node.value)))
1436 fp.write(struct.pack(b'>L', len(node.value)))
1436 fp.write(node.value)
1437 fp.write(node.value)
1437 if node is self._head:
1438 if node is self._head:
1438 break
1439 break
1439 node = node.prev
1440 node = node.prev
1440
1441
1441 def __len__(self):
1442 def __len__(self):
1442 if not self._read:
1443 if not self._read:
1443 self.read()
1444 self.read()
1444 return super(manifestfulltextcache, self).__len__()
1445 return super(manifestfulltextcache, self).__len__()
1445
1446
1446 def __contains__(self, k):
1447 def __contains__(self, k):
1447 if not self._read:
1448 if not self._read:
1448 self.read()
1449 self.read()
1449 return super(manifestfulltextcache, self).__contains__(k)
1450 return super(manifestfulltextcache, self).__contains__(k)
1450
1451
1451 def __iter__(self):
1452 def __iter__(self):
1452 if not self._read:
1453 if not self._read:
1453 self.read()
1454 self.read()
1454 return super(manifestfulltextcache, self).__iter__()
1455 return super(manifestfulltextcache, self).__iter__()
1455
1456
1456 def __getitem__(self, k):
1457 def __getitem__(self, k):
1457 if not self._read:
1458 if not self._read:
1458 self.read()
1459 self.read()
1459 # the cache lru order can change on read
1460 # the cache lru order can change on read
1460 setdirty = self._cache.get(k) is not self._head
1461 setdirty = self._cache.get(k) is not self._head
1461 value = super(manifestfulltextcache, self).__getitem__(k)
1462 value = super(manifestfulltextcache, self).__getitem__(k)
1462 if setdirty:
1463 if setdirty:
1463 self._dirty = True
1464 self._dirty = True
1464 return value
1465 return value
1465
1466
1466 def __setitem__(self, k, v):
1467 def __setitem__(self, k, v):
1467 if not self._read:
1468 if not self._read:
1468 self.read()
1469 self.read()
1469 super(manifestfulltextcache, self).__setitem__(k, v)
1470 super(manifestfulltextcache, self).__setitem__(k, v)
1470 self._dirty = True
1471 self._dirty = True
1471
1472
1472 def __delitem__(self, k):
1473 def __delitem__(self, k):
1473 if not self._read:
1474 if not self._read:
1474 self.read()
1475 self.read()
1475 super(manifestfulltextcache, self).__delitem__(k)
1476 super(manifestfulltextcache, self).__delitem__(k)
1476 self._dirty = True
1477 self._dirty = True
1477
1478
1478 def get(self, k, default=None):
1479 def get(self, k, default=None):
1479 if not self._read:
1480 if not self._read:
1480 self.read()
1481 self.read()
1481 return super(manifestfulltextcache, self).get(k, default=default)
1482 return super(manifestfulltextcache, self).get(k, default=default)
1482
1483
1483 def clear(self, clear_persisted_data=False):
1484 def clear(self, clear_persisted_data=False):
1484 super(manifestfulltextcache, self).clear()
1485 super(manifestfulltextcache, self).clear()
1485 if clear_persisted_data:
1486 if clear_persisted_data:
1486 self._dirty = True
1487 self._dirty = True
1487 self.write()
1488 self.write()
1488 self._read = False
1489 self._read = False
1489
1490
1490
1491
1491 # and upper bound of what we expect from compression
1492 # and upper bound of what we expect from compression
1492 # (real live value seems to be "3")
1493 # (real live value seems to be "3")
1493 MAXCOMPRESSION = 3
1494 MAXCOMPRESSION = 3
1494
1495
1495
1496
1496 @interfaceutil.implementer(repository.imanifeststorage)
1497 @interfaceutil.implementer(repository.imanifeststorage)
1497 class manifestrevlog(object):
1498 class manifestrevlog(object):
1498 '''A revlog that stores manifest texts. This is responsible for caching the
1499 '''A revlog that stores manifest texts. This is responsible for caching the
1499 full-text manifest contents.
1500 full-text manifest contents.
1500 '''
1501 '''
1501
1502
1502 def __init__(
1503 def __init__(
1503 self,
1504 self,
1504 opener,
1505 opener,
1505 tree=b'',
1506 tree=b'',
1506 dirlogcache=None,
1507 dirlogcache=None,
1507 indexfile=None,
1508 indexfile=None,
1508 treemanifest=False,
1509 treemanifest=False,
1509 ):
1510 ):
1510 """Constructs a new manifest revlog
1511 """Constructs a new manifest revlog
1511
1512
1512 `indexfile` - used by extensions to have two manifests at once, like
1513 `indexfile` - used by extensions to have two manifests at once, like
1513 when transitioning between flatmanifeset and treemanifests.
1514 when transitioning between flatmanifeset and treemanifests.
1514
1515
1515 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1516 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1516 options can also be used to make this a tree manifest revlog. The opener
1517 options can also be used to make this a tree manifest revlog. The opener
1517 option takes precedence, so if it is set to True, we ignore whatever
1518 option takes precedence, so if it is set to True, we ignore whatever
1518 value is passed in to the constructor.
1519 value is passed in to the constructor.
1519 """
1520 """
1520 # During normal operations, we expect to deal with not more than four
1521 # During normal operations, we expect to deal with not more than four
1521 # revs at a time (such as during commit --amend). When rebasing large
1522 # revs at a time (such as during commit --amend). When rebasing large
1522 # stacks of commits, the number can go up, hence the config knob below.
1523 # stacks of commits, the number can go up, hence the config knob below.
1523 cachesize = 4
1524 cachesize = 4
1524 optiontreemanifest = False
1525 optiontreemanifest = False
1525 opts = getattr(opener, 'options', None)
1526 opts = getattr(opener, 'options', None)
1526 if opts is not None:
1527 if opts is not None:
1527 cachesize = opts.get(b'manifestcachesize', cachesize)
1528 cachesize = opts.get(b'manifestcachesize', cachesize)
1528 optiontreemanifest = opts.get(b'treemanifest', False)
1529 optiontreemanifest = opts.get(b'treemanifest', False)
1529
1530
1530 self._treeondisk = optiontreemanifest or treemanifest
1531 self._treeondisk = optiontreemanifest or treemanifest
1531
1532
1532 self._fulltextcache = manifestfulltextcache(cachesize)
1533 self._fulltextcache = manifestfulltextcache(cachesize)
1533
1534
1534 if tree:
1535 if tree:
1535 assert self._treeondisk, b'opts is %r' % opts
1536 assert self._treeondisk, b'opts is %r' % opts
1536
1537
1537 if indexfile is None:
1538 if indexfile is None:
1538 indexfile = b'00manifest.i'
1539 indexfile = b'00manifest.i'
1539 if tree:
1540 if tree:
1540 indexfile = b"meta/" + tree + indexfile
1541 indexfile = b"meta/" + tree + indexfile
1541
1542
1542 self.tree = tree
1543 self.tree = tree
1543
1544
1544 # The dirlogcache is kept on the root manifest log
1545 # The dirlogcache is kept on the root manifest log
1545 if tree:
1546 if tree:
1546 self._dirlogcache = dirlogcache
1547 self._dirlogcache = dirlogcache
1547 else:
1548 else:
1548 self._dirlogcache = {b'': self}
1549 self._dirlogcache = {b'': self}
1549
1550
1550 self._revlog = revlog.revlog(
1551 self._revlog = revlog.revlog(
1551 opener,
1552 opener,
1552 indexfile,
1553 indexfile,
1553 # only root indexfile is cached
1554 # only root indexfile is cached
1554 checkambig=not bool(tree),
1555 checkambig=not bool(tree),
1555 mmaplargeindex=True,
1556 mmaplargeindex=True,
1556 upperboundcomp=MAXCOMPRESSION,
1557 upperboundcomp=MAXCOMPRESSION,
1557 )
1558 )
1558
1559
1559 self.index = self._revlog.index
1560 self.index = self._revlog.index
1560 self.version = self._revlog.version
1561 self.version = self._revlog.version
1561 self._generaldelta = self._revlog._generaldelta
1562 self._generaldelta = self._revlog._generaldelta
1562
1563
1563 def _setupmanifestcachehooks(self, repo):
1564 def _setupmanifestcachehooks(self, repo):
1564 """Persist the manifestfulltextcache on lock release"""
1565 """Persist the manifestfulltextcache on lock release"""
1565 if not util.safehasattr(repo, b'_wlockref'):
1566 if not util.safehasattr(repo, b'_wlockref'):
1566 return
1567 return
1567
1568
1568 self._fulltextcache._opener = repo.wcachevfs
1569 self._fulltextcache._opener = repo.wcachevfs
1569 if repo._currentlock(repo._wlockref) is None:
1570 if repo._currentlock(repo._wlockref) is None:
1570 return
1571 return
1571
1572
1572 reporef = weakref.ref(repo)
1573 reporef = weakref.ref(repo)
1573 manifestrevlogref = weakref.ref(self)
1574 manifestrevlogref = weakref.ref(self)
1574
1575
1575 def persistmanifestcache(success):
1576 def persistmanifestcache(success):
1576 # Repo is in an unknown state, do not persist.
1577 # Repo is in an unknown state, do not persist.
1577 if not success:
1578 if not success:
1578 return
1579 return
1579
1580
1580 repo = reporef()
1581 repo = reporef()
1581 self = manifestrevlogref()
1582 self = manifestrevlogref()
1582 if repo is None or self is None:
1583 if repo is None or self is None:
1583 return
1584 return
1584 if repo.manifestlog.getstorage(b'') is not self:
1585 if repo.manifestlog.getstorage(b'') is not self:
1585 # there's a different manifest in play now, abort
1586 # there's a different manifest in play now, abort
1586 return
1587 return
1587 self._fulltextcache.write()
1588 self._fulltextcache.write()
1588
1589
1589 repo._afterlock(persistmanifestcache)
1590 repo._afterlock(persistmanifestcache)
1590
1591
1591 @property
1592 @property
1592 def fulltextcache(self):
1593 def fulltextcache(self):
1593 return self._fulltextcache
1594 return self._fulltextcache
1594
1595
1595 def clearcaches(self, clear_persisted_data=False):
1596 def clearcaches(self, clear_persisted_data=False):
1596 self._revlog.clearcaches()
1597 self._revlog.clearcaches()
1597 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1598 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1598 self._dirlogcache = {self.tree: self}
1599 self._dirlogcache = {self.tree: self}
1599
1600
1600 def dirlog(self, d):
1601 def dirlog(self, d):
1601 if d:
1602 if d:
1602 assert self._treeondisk
1603 assert self._treeondisk
1603 if d not in self._dirlogcache:
1604 if d not in self._dirlogcache:
1604 mfrevlog = manifestrevlog(
1605 mfrevlog = manifestrevlog(
1605 self.opener, d, self._dirlogcache, treemanifest=self._treeondisk
1606 self.opener, d, self._dirlogcache, treemanifest=self._treeondisk
1606 )
1607 )
1607 self._dirlogcache[d] = mfrevlog
1608 self._dirlogcache[d] = mfrevlog
1608 return self._dirlogcache[d]
1609 return self._dirlogcache[d]
1609
1610
1610 def add(
1611 def add(
1611 self,
1612 self,
1612 m,
1613 m,
1613 transaction,
1614 transaction,
1614 link,
1615 link,
1615 p1,
1616 p1,
1616 p2,
1617 p2,
1617 added,
1618 added,
1618 removed,
1619 removed,
1619 readtree=None,
1620 readtree=None,
1620 match=None,
1621 match=None,
1621 ):
1622 ):
1622 if p1 in self.fulltextcache and util.safehasattr(m, b'fastdelta'):
1623 if p1 in self.fulltextcache and util.safehasattr(m, b'fastdelta'):
1623 # If our first parent is in the manifest cache, we can
1624 # If our first parent is in the manifest cache, we can
1624 # compute a delta here using properties we know about the
1625 # compute a delta here using properties we know about the
1625 # manifest up-front, which may save time later for the
1626 # manifest up-front, which may save time later for the
1626 # revlog layer.
1627 # revlog layer.
1627
1628
1628 _checkforbidden(added)
1629 _checkforbidden(added)
1629 # combine the changed lists into one sorted iterator
1630 # combine the changed lists into one sorted iterator
1630 work = heapq.merge(
1631 work = heapq.merge(
1631 [(x, False) for x in sorted(added)],
1632 [(x, False) for x in sorted(added)],
1632 [(x, True) for x in sorted(removed)],
1633 [(x, True) for x in sorted(removed)],
1633 )
1634 )
1634
1635
1635 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1636 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1636 cachedelta = self._revlog.rev(p1), deltatext
1637 cachedelta = self._revlog.rev(p1), deltatext
1637 text = util.buffer(arraytext)
1638 text = util.buffer(arraytext)
1638 n = self._revlog.addrevision(
1639 n = self._revlog.addrevision(
1639 text, transaction, link, p1, p2, cachedelta
1640 text, transaction, link, p1, p2, cachedelta
1640 )
1641 )
1641 else:
1642 else:
1642 # The first parent manifest isn't already loaded, so we'll
1643 # The first parent manifest isn't already loaded, so we'll
1643 # just encode a fulltext of the manifest and pass that
1644 # just encode a fulltext of the manifest and pass that
1644 # through to the revlog layer, and let it handle the delta
1645 # through to the revlog layer, and let it handle the delta
1645 # process.
1646 # process.
1646 if self._treeondisk:
1647 if self._treeondisk:
1647 assert readtree, b"readtree must be set for treemanifest writes"
1648 assert readtree, b"readtree must be set for treemanifest writes"
1648 assert match, b"match must be specified for treemanifest writes"
1649 assert match, b"match must be specified for treemanifest writes"
1649 m1 = readtree(self.tree, p1)
1650 m1 = readtree(self.tree, p1)
1650 m2 = readtree(self.tree, p2)
1651 m2 = readtree(self.tree, p2)
1651 n = self._addtree(
1652 n = self._addtree(
1652 m, transaction, link, m1, m2, readtree, match=match
1653 m, transaction, link, m1, m2, readtree, match=match
1653 )
1654 )
1654 arraytext = None
1655 arraytext = None
1655 else:
1656 else:
1656 text = m.text()
1657 text = m.text()
1657 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1658 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1658 arraytext = bytearray(text)
1659 arraytext = bytearray(text)
1659
1660
1660 if arraytext is not None:
1661 if arraytext is not None:
1661 self.fulltextcache[n] = arraytext
1662 self.fulltextcache[n] = arraytext
1662
1663
1663 return n
1664 return n
1664
1665
1665 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1666 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1666 # If the manifest is unchanged compared to one parent,
1667 # If the manifest is unchanged compared to one parent,
1667 # don't write a new revision
1668 # don't write a new revision
1668 if self.tree != b'' and (
1669 if self.tree != b'' and (
1669 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1670 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1670 ):
1671 ):
1671 return m.node()
1672 return m.node()
1672
1673
1673 def writesubtree(subm, subp1, subp2, match):
1674 def writesubtree(subm, subp1, subp2, match):
1674 sublog = self.dirlog(subm.dir())
1675 sublog = self.dirlog(subm.dir())
1675 sublog.add(
1676 sublog.add(
1676 subm,
1677 subm,
1677 transaction,
1678 transaction,
1678 link,
1679 link,
1679 subp1,
1680 subp1,
1680 subp2,
1681 subp2,
1681 None,
1682 None,
1682 None,
1683 None,
1683 readtree=readtree,
1684 readtree=readtree,
1684 match=match,
1685 match=match,
1685 )
1686 )
1686
1687
1687 m.writesubtrees(m1, m2, writesubtree, match)
1688 m.writesubtrees(m1, m2, writesubtree, match)
1688 text = m.dirtext()
1689 text = m.dirtext()
1689 n = None
1690 n = None
1690 if self.tree != b'':
1691 if self.tree != b'':
1691 # Double-check whether contents are unchanged to one parent
1692 # Double-check whether contents are unchanged to one parent
1692 if text == m1.dirtext():
1693 if text == m1.dirtext():
1693 n = m1.node()
1694 n = m1.node()
1694 elif text == m2.dirtext():
1695 elif text == m2.dirtext():
1695 n = m2.node()
1696 n = m2.node()
1696
1697
1697 if not n:
1698 if not n:
1698 n = self._revlog.addrevision(
1699 n = self._revlog.addrevision(
1699 text, transaction, link, m1.node(), m2.node()
1700 text, transaction, link, m1.node(), m2.node()
1700 )
1701 )
1701
1702
1702 # Save nodeid so parent manifest can calculate its nodeid
1703 # Save nodeid so parent manifest can calculate its nodeid
1703 m.setnode(n)
1704 m.setnode(n)
1704 return n
1705 return n
1705
1706
1706 def __len__(self):
1707 def __len__(self):
1707 return len(self._revlog)
1708 return len(self._revlog)
1708
1709
1709 def __iter__(self):
1710 def __iter__(self):
1710 return self._revlog.__iter__()
1711 return self._revlog.__iter__()
1711
1712
1712 def rev(self, node):
1713 def rev(self, node):
1713 return self._revlog.rev(node)
1714 return self._revlog.rev(node)
1714
1715
1715 def node(self, rev):
1716 def node(self, rev):
1716 return self._revlog.node(rev)
1717 return self._revlog.node(rev)
1717
1718
1718 def lookup(self, value):
1719 def lookup(self, value):
1719 return self._revlog.lookup(value)
1720 return self._revlog.lookup(value)
1720
1721
1721 def parentrevs(self, rev):
1722 def parentrevs(self, rev):
1722 return self._revlog.parentrevs(rev)
1723 return self._revlog.parentrevs(rev)
1723
1724
1724 def parents(self, node):
1725 def parents(self, node):
1725 return self._revlog.parents(node)
1726 return self._revlog.parents(node)
1726
1727
1727 def linkrev(self, rev):
1728 def linkrev(self, rev):
1728 return self._revlog.linkrev(rev)
1729 return self._revlog.linkrev(rev)
1729
1730
1730 def checksize(self):
1731 def checksize(self):
1731 return self._revlog.checksize()
1732 return self._revlog.checksize()
1732
1733
1733 def revision(self, node, _df=None, raw=False):
1734 def revision(self, node, _df=None, raw=False):
1734 return self._revlog.revision(node, _df=_df, raw=raw)
1735 return self._revlog.revision(node, _df=_df, raw=raw)
1735
1736
1736 def rawdata(self, node, _df=None):
1737 def rawdata(self, node, _df=None):
1737 return self._revlog.rawdata(node, _df=_df)
1738 return self._revlog.rawdata(node, _df=_df)
1738
1739
1739 def revdiff(self, rev1, rev2):
1740 def revdiff(self, rev1, rev2):
1740 return self._revlog.revdiff(rev1, rev2)
1741 return self._revlog.revdiff(rev1, rev2)
1741
1742
1742 def cmp(self, node, text):
1743 def cmp(self, node, text):
1743 return self._revlog.cmp(node, text)
1744 return self._revlog.cmp(node, text)
1744
1745
1745 def deltaparent(self, rev):
1746 def deltaparent(self, rev):
1746 return self._revlog.deltaparent(rev)
1747 return self._revlog.deltaparent(rev)
1747
1748
1748 def emitrevisions(
1749 def emitrevisions(
1749 self,
1750 self,
1750 nodes,
1751 nodes,
1751 nodesorder=None,
1752 nodesorder=None,
1752 revisiondata=False,
1753 revisiondata=False,
1753 assumehaveparentrevisions=False,
1754 assumehaveparentrevisions=False,
1754 deltamode=repository.CG_DELTAMODE_STD,
1755 deltamode=repository.CG_DELTAMODE_STD,
1755 ):
1756 ):
1756 return self._revlog.emitrevisions(
1757 return self._revlog.emitrevisions(
1757 nodes,
1758 nodes,
1758 nodesorder=nodesorder,
1759 nodesorder=nodesorder,
1759 revisiondata=revisiondata,
1760 revisiondata=revisiondata,
1760 assumehaveparentrevisions=assumehaveparentrevisions,
1761 assumehaveparentrevisions=assumehaveparentrevisions,
1761 deltamode=deltamode,
1762 deltamode=deltamode,
1762 )
1763 )
1763
1764
1764 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1765 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1765 return self._revlog.addgroup(
1766 return self._revlog.addgroup(
1766 deltas, linkmapper, transaction, addrevisioncb=addrevisioncb
1767 deltas, linkmapper, transaction, addrevisioncb=addrevisioncb
1767 )
1768 )
1768
1769
1769 def rawsize(self, rev):
1770 def rawsize(self, rev):
1770 return self._revlog.rawsize(rev)
1771 return self._revlog.rawsize(rev)
1771
1772
1772 def getstrippoint(self, minlink):
1773 def getstrippoint(self, minlink):
1773 return self._revlog.getstrippoint(minlink)
1774 return self._revlog.getstrippoint(minlink)
1774
1775
1775 def strip(self, minlink, transaction):
1776 def strip(self, minlink, transaction):
1776 return self._revlog.strip(minlink, transaction)
1777 return self._revlog.strip(minlink, transaction)
1777
1778
1778 def files(self):
1779 def files(self):
1779 return self._revlog.files()
1780 return self._revlog.files()
1780
1781
1781 def clone(self, tr, destrevlog, **kwargs):
1782 def clone(self, tr, destrevlog, **kwargs):
1782 if not isinstance(destrevlog, manifestrevlog):
1783 if not isinstance(destrevlog, manifestrevlog):
1783 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1784 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1784
1785
1785 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1786 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1786
1787
1787 def storageinfo(
1788 def storageinfo(
1788 self,
1789 self,
1789 exclusivefiles=False,
1790 exclusivefiles=False,
1790 sharedfiles=False,
1791 sharedfiles=False,
1791 revisionscount=False,
1792 revisionscount=False,
1792 trackedsize=False,
1793 trackedsize=False,
1793 storedsize=False,
1794 storedsize=False,
1794 ):
1795 ):
1795 return self._revlog.storageinfo(
1796 return self._revlog.storageinfo(
1796 exclusivefiles=exclusivefiles,
1797 exclusivefiles=exclusivefiles,
1797 sharedfiles=sharedfiles,
1798 sharedfiles=sharedfiles,
1798 revisionscount=revisionscount,
1799 revisionscount=revisionscount,
1799 trackedsize=trackedsize,
1800 trackedsize=trackedsize,
1800 storedsize=storedsize,
1801 storedsize=storedsize,
1801 )
1802 )
1802
1803
1803 @property
1804 @property
1804 def indexfile(self):
1805 def indexfile(self):
1805 return self._revlog.indexfile
1806 return self._revlog.indexfile
1806
1807
1807 @indexfile.setter
1808 @indexfile.setter
1808 def indexfile(self, value):
1809 def indexfile(self, value):
1809 self._revlog.indexfile = value
1810 self._revlog.indexfile = value
1810
1811
1811 @property
1812 @property
1812 def opener(self):
1813 def opener(self):
1813 return self._revlog.opener
1814 return self._revlog.opener
1814
1815
1815 @opener.setter
1816 @opener.setter
1816 def opener(self, value):
1817 def opener(self, value):
1817 self._revlog.opener = value
1818 self._revlog.opener = value
1818
1819
1819
1820
1820 @interfaceutil.implementer(repository.imanifestlog)
1821 @interfaceutil.implementer(repository.imanifestlog)
1821 class manifestlog(object):
1822 class manifestlog(object):
1822 """A collection class representing the collection of manifest snapshots
1823 """A collection class representing the collection of manifest snapshots
1823 referenced by commits in the repository.
1824 referenced by commits in the repository.
1824
1825
1825 In this situation, 'manifest' refers to the abstract concept of a snapshot
1826 In this situation, 'manifest' refers to the abstract concept of a snapshot
1826 of the list of files in the given commit. Consumers of the output of this
1827 of the list of files in the given commit. Consumers of the output of this
1827 class do not care about the implementation details of the actual manifests
1828 class do not care about the implementation details of the actual manifests
1828 they receive (i.e. tree or flat or lazily loaded, etc)."""
1829 they receive (i.e. tree or flat or lazily loaded, etc)."""
1829
1830
1830 def __init__(self, opener, repo, rootstore, narrowmatch):
1831 def __init__(self, opener, repo, rootstore, narrowmatch):
1831 usetreemanifest = False
1832 usetreemanifest = False
1832 cachesize = 4
1833 cachesize = 4
1833
1834
1834 opts = getattr(opener, 'options', None)
1835 opts = getattr(opener, 'options', None)
1835 if opts is not None:
1836 if opts is not None:
1836 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1837 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1837 cachesize = opts.get(b'manifestcachesize', cachesize)
1838 cachesize = opts.get(b'manifestcachesize', cachesize)
1838
1839
1839 self._treemanifests = usetreemanifest
1840 self._treemanifests = usetreemanifest
1840
1841
1841 self._rootstore = rootstore
1842 self._rootstore = rootstore
1842 self._rootstore._setupmanifestcachehooks(repo)
1843 self._rootstore._setupmanifestcachehooks(repo)
1843 self._narrowmatch = narrowmatch
1844 self._narrowmatch = narrowmatch
1844
1845
1845 # A cache of the manifestctx or treemanifestctx for each directory
1846 # A cache of the manifestctx or treemanifestctx for each directory
1846 self._dirmancache = {}
1847 self._dirmancache = {}
1847 self._dirmancache[b''] = util.lrucachedict(cachesize)
1848 self._dirmancache[b''] = util.lrucachedict(cachesize)
1848
1849
1849 self._cachesize = cachesize
1850 self._cachesize = cachesize
1850
1851
1851 def __getitem__(self, node):
1852 def __getitem__(self, node):
1852 """Retrieves the manifest instance for the given node. Throws a
1853 """Retrieves the manifest instance for the given node. Throws a
1853 LookupError if not found.
1854 LookupError if not found.
1854 """
1855 """
1855 return self.get(b'', node)
1856 return self.get(b'', node)
1856
1857
1857 def get(self, tree, node, verify=True):
1858 def get(self, tree, node, verify=True):
1858 """Retrieves the manifest instance for the given node. Throws a
1859 """Retrieves the manifest instance for the given node. Throws a
1859 LookupError if not found.
1860 LookupError if not found.
1860
1861
1861 `verify` - if True an exception will be thrown if the node is not in
1862 `verify` - if True an exception will be thrown if the node is not in
1862 the revlog
1863 the revlog
1863 """
1864 """
1864 if node in self._dirmancache.get(tree, ()):
1865 if node in self._dirmancache.get(tree, ()):
1865 return self._dirmancache[tree][node]
1866 return self._dirmancache[tree][node]
1866
1867
1867 if not self._narrowmatch.always():
1868 if not self._narrowmatch.always():
1868 if not self._narrowmatch.visitdir(tree[:-1]):
1869 if not self._narrowmatch.visitdir(tree[:-1]):
1869 return excludeddirmanifestctx(tree, node)
1870 return excludeddirmanifestctx(tree, node)
1870 if tree:
1871 if tree:
1871 if self._rootstore._treeondisk:
1872 if self._rootstore._treeondisk:
1872 if verify:
1873 if verify:
1873 # Side-effect is LookupError is raised if node doesn't
1874 # Side-effect is LookupError is raised if node doesn't
1874 # exist.
1875 # exist.
1875 self.getstorage(tree).rev(node)
1876 self.getstorage(tree).rev(node)
1876
1877
1877 m = treemanifestctx(self, tree, node)
1878 m = treemanifestctx(self, tree, node)
1878 else:
1879 else:
1879 raise error.Abort(
1880 raise error.Abort(
1880 _(
1881 _(
1881 b"cannot ask for manifest directory '%s' in a flat "
1882 b"cannot ask for manifest directory '%s' in a flat "
1882 b"manifest"
1883 b"manifest"
1883 )
1884 )
1884 % tree
1885 % tree
1885 )
1886 )
1886 else:
1887 else:
1887 if verify:
1888 if verify:
1888 # Side-effect is LookupError is raised if node doesn't exist.
1889 # Side-effect is LookupError is raised if node doesn't exist.
1889 self._rootstore.rev(node)
1890 self._rootstore.rev(node)
1890
1891
1891 if self._treemanifests:
1892 if self._treemanifests:
1892 m = treemanifestctx(self, b'', node)
1893 m = treemanifestctx(self, b'', node)
1893 else:
1894 else:
1894 m = manifestctx(self, node)
1895 m = manifestctx(self, node)
1895
1896
1896 if node != nullid:
1897 if node != nullid:
1897 mancache = self._dirmancache.get(tree)
1898 mancache = self._dirmancache.get(tree)
1898 if not mancache:
1899 if not mancache:
1899 mancache = util.lrucachedict(self._cachesize)
1900 mancache = util.lrucachedict(self._cachesize)
1900 self._dirmancache[tree] = mancache
1901 self._dirmancache[tree] = mancache
1901 mancache[node] = m
1902 mancache[node] = m
1902 return m
1903 return m
1903
1904
1904 def getstorage(self, tree):
1905 def getstorage(self, tree):
1905 return self._rootstore.dirlog(tree)
1906 return self._rootstore.dirlog(tree)
1906
1907
1907 def clearcaches(self, clear_persisted_data=False):
1908 def clearcaches(self, clear_persisted_data=False):
1908 self._dirmancache.clear()
1909 self._dirmancache.clear()
1909 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1910 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1910
1911
1911 def rev(self, node):
1912 def rev(self, node):
1912 return self._rootstore.rev(node)
1913 return self._rootstore.rev(node)
1913
1914
1914
1915
1915 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1916 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1916 class memmanifestctx(object):
1917 class memmanifestctx(object):
1917 def __init__(self, manifestlog):
1918 def __init__(self, manifestlog):
1918 self._manifestlog = manifestlog
1919 self._manifestlog = manifestlog
1919 self._manifestdict = manifestdict()
1920 self._manifestdict = manifestdict()
1920
1921
1921 def _storage(self):
1922 def _storage(self):
1922 return self._manifestlog.getstorage(b'')
1923 return self._manifestlog.getstorage(b'')
1923
1924
1924 def copy(self):
1925 def copy(self):
1925 memmf = memmanifestctx(self._manifestlog)
1926 memmf = memmanifestctx(self._manifestlog)
1926 memmf._manifestdict = self.read().copy()
1927 memmf._manifestdict = self.read().copy()
1927 return memmf
1928 return memmf
1928
1929
1929 def read(self):
1930 def read(self):
1930 return self._manifestdict
1931 return self._manifestdict
1931
1932
1932 def write(self, transaction, link, p1, p2, added, removed, match=None):
1933 def write(self, transaction, link, p1, p2, added, removed, match=None):
1933 return self._storage().add(
1934 return self._storage().add(
1934 self._manifestdict,
1935 self._manifestdict,
1935 transaction,
1936 transaction,
1936 link,
1937 link,
1937 p1,
1938 p1,
1938 p2,
1939 p2,
1939 added,
1940 added,
1940 removed,
1941 removed,
1941 match=match,
1942 match=match,
1942 )
1943 )
1943
1944
1944
1945
1945 @interfaceutil.implementer(repository.imanifestrevisionstored)
1946 @interfaceutil.implementer(repository.imanifestrevisionstored)
1946 class manifestctx(object):
1947 class manifestctx(object):
1947 """A class representing a single revision of a manifest, including its
1948 """A class representing a single revision of a manifest, including its
1948 contents, its parent revs, and its linkrev.
1949 contents, its parent revs, and its linkrev.
1949 """
1950 """
1950
1951
1951 def __init__(self, manifestlog, node):
1952 def __init__(self, manifestlog, node):
1952 self._manifestlog = manifestlog
1953 self._manifestlog = manifestlog
1953 self._data = None
1954 self._data = None
1954
1955
1955 self._node = node
1956 self._node = node
1956
1957
1957 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1958 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1958 # but let's add it later when something needs it and we can load it
1959 # but let's add it later when something needs it and we can load it
1959 # lazily.
1960 # lazily.
1960 # self.p1, self.p2 = store.parents(node)
1961 # self.p1, self.p2 = store.parents(node)
1961 # rev = store.rev(node)
1962 # rev = store.rev(node)
1962 # self.linkrev = store.linkrev(rev)
1963 # self.linkrev = store.linkrev(rev)
1963
1964
1964 def _storage(self):
1965 def _storage(self):
1965 return self._manifestlog.getstorage(b'')
1966 return self._manifestlog.getstorage(b'')
1966
1967
1967 def node(self):
1968 def node(self):
1968 return self._node
1969 return self._node
1969
1970
1970 def copy(self):
1971 def copy(self):
1971 memmf = memmanifestctx(self._manifestlog)
1972 memmf = memmanifestctx(self._manifestlog)
1972 memmf._manifestdict = self.read().copy()
1973 memmf._manifestdict = self.read().copy()
1973 return memmf
1974 return memmf
1974
1975
1975 @propertycache
1976 @propertycache
1976 def parents(self):
1977 def parents(self):
1977 return self._storage().parents(self._node)
1978 return self._storage().parents(self._node)
1978
1979
1979 def read(self):
1980 def read(self):
1980 if self._data is None:
1981 if self._data is None:
1981 if self._node == nullid:
1982 if self._node == nullid:
1982 self._data = manifestdict()
1983 self._data = manifestdict()
1983 else:
1984 else:
1984 store = self._storage()
1985 store = self._storage()
1985 if self._node in store.fulltextcache:
1986 if self._node in store.fulltextcache:
1986 text = pycompat.bytestr(store.fulltextcache[self._node])
1987 text = pycompat.bytestr(store.fulltextcache[self._node])
1987 else:
1988 else:
1988 text = store.revision(self._node)
1989 text = store.revision(self._node)
1989 arraytext = bytearray(text)
1990 arraytext = bytearray(text)
1990 store.fulltextcache[self._node] = arraytext
1991 store.fulltextcache[self._node] = arraytext
1991 self._data = manifestdict(text)
1992 self._data = manifestdict(text)
1992 return self._data
1993 return self._data
1993
1994
1994 def readfast(self, shallow=False):
1995 def readfast(self, shallow=False):
1995 '''Calls either readdelta or read, based on which would be less work.
1996 '''Calls either readdelta or read, based on which would be less work.
1996 readdelta is called if the delta is against the p1, and therefore can be
1997 readdelta is called if the delta is against the p1, and therefore can be
1997 read quickly.
1998 read quickly.
1998
1999
1999 If `shallow` is True, nothing changes since this is a flat manifest.
2000 If `shallow` is True, nothing changes since this is a flat manifest.
2000 '''
2001 '''
2001 store = self._storage()
2002 store = self._storage()
2002 r = store.rev(self._node)
2003 r = store.rev(self._node)
2003 deltaparent = store.deltaparent(r)
2004 deltaparent = store.deltaparent(r)
2004 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2005 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2005 return self.readdelta()
2006 return self.readdelta()
2006 return self.read()
2007 return self.read()
2007
2008
2008 def readdelta(self, shallow=False):
2009 def readdelta(self, shallow=False):
2009 '''Returns a manifest containing just the entries that are present
2010 '''Returns a manifest containing just the entries that are present
2010 in this manifest, but not in its p1 manifest. This is efficient to read
2011 in this manifest, but not in its p1 manifest. This is efficient to read
2011 if the revlog delta is already p1.
2012 if the revlog delta is already p1.
2012
2013
2013 Changing the value of `shallow` has no effect on flat manifests.
2014 Changing the value of `shallow` has no effect on flat manifests.
2014 '''
2015 '''
2015 store = self._storage()
2016 store = self._storage()
2016 r = store.rev(self._node)
2017 r = store.rev(self._node)
2017 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2018 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2018 return manifestdict(d)
2019 return manifestdict(d)
2019
2020
2020 def find(self, key):
2021 def find(self, key):
2021 return self.read().find(key)
2022 return self.read().find(key)
2022
2023
2023
2024
2024 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2025 @interfaceutil.implementer(repository.imanifestrevisionwritable)
2025 class memtreemanifestctx(object):
2026 class memtreemanifestctx(object):
2026 def __init__(self, manifestlog, dir=b''):
2027 def __init__(self, manifestlog, dir=b''):
2027 self._manifestlog = manifestlog
2028 self._manifestlog = manifestlog
2028 self._dir = dir
2029 self._dir = dir
2029 self._treemanifest = treemanifest()
2030 self._treemanifest = treemanifest()
2030
2031
2031 def _storage(self):
2032 def _storage(self):
2032 return self._manifestlog.getstorage(b'')
2033 return self._manifestlog.getstorage(b'')
2033
2034
2034 def copy(self):
2035 def copy(self):
2035 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2036 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2036 memmf._treemanifest = self._treemanifest.copy()
2037 memmf._treemanifest = self._treemanifest.copy()
2037 return memmf
2038 return memmf
2038
2039
2039 def read(self):
2040 def read(self):
2040 return self._treemanifest
2041 return self._treemanifest
2041
2042
2042 def write(self, transaction, link, p1, p2, added, removed, match=None):
2043 def write(self, transaction, link, p1, p2, added, removed, match=None):
2043 def readtree(dir, node):
2044 def readtree(dir, node):
2044 return self._manifestlog.get(dir, node).read()
2045 return self._manifestlog.get(dir, node).read()
2045
2046
2046 return self._storage().add(
2047 return self._storage().add(
2047 self._treemanifest,
2048 self._treemanifest,
2048 transaction,
2049 transaction,
2049 link,
2050 link,
2050 p1,
2051 p1,
2051 p2,
2052 p2,
2052 added,
2053 added,
2053 removed,
2054 removed,
2054 readtree=readtree,
2055 readtree=readtree,
2055 match=match,
2056 match=match,
2056 )
2057 )
2057
2058
2058
2059
2059 @interfaceutil.implementer(repository.imanifestrevisionstored)
2060 @interfaceutil.implementer(repository.imanifestrevisionstored)
2060 class treemanifestctx(object):
2061 class treemanifestctx(object):
2061 def __init__(self, manifestlog, dir, node):
2062 def __init__(self, manifestlog, dir, node):
2062 self._manifestlog = manifestlog
2063 self._manifestlog = manifestlog
2063 self._dir = dir
2064 self._dir = dir
2064 self._data = None
2065 self._data = None
2065
2066
2066 self._node = node
2067 self._node = node
2067
2068
2068 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2069 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2069 # we can instantiate treemanifestctx objects for directories we don't
2070 # we can instantiate treemanifestctx objects for directories we don't
2070 # have on disk.
2071 # have on disk.
2071 # self.p1, self.p2 = store.parents(node)
2072 # self.p1, self.p2 = store.parents(node)
2072 # rev = store.rev(node)
2073 # rev = store.rev(node)
2073 # self.linkrev = store.linkrev(rev)
2074 # self.linkrev = store.linkrev(rev)
2074
2075
2075 def _storage(self):
2076 def _storage(self):
2076 narrowmatch = self._manifestlog._narrowmatch
2077 narrowmatch = self._manifestlog._narrowmatch
2077 if not narrowmatch.always():
2078 if not narrowmatch.always():
2078 if not narrowmatch.visitdir(self._dir[:-1]):
2079 if not narrowmatch.visitdir(self._dir[:-1]):
2079 return excludedmanifestrevlog(self._dir)
2080 return excludedmanifestrevlog(self._dir)
2080 return self._manifestlog.getstorage(self._dir)
2081 return self._manifestlog.getstorage(self._dir)
2081
2082
2082 def read(self):
2083 def read(self):
2083 if self._data is None:
2084 if self._data is None:
2084 store = self._storage()
2085 store = self._storage()
2085 if self._node == nullid:
2086 if self._node == nullid:
2086 self._data = treemanifest()
2087 self._data = treemanifest()
2087 # TODO accessing non-public API
2088 # TODO accessing non-public API
2088 elif store._treeondisk:
2089 elif store._treeondisk:
2089 m = treemanifest(dir=self._dir)
2090 m = treemanifest(dir=self._dir)
2090
2091
2091 def gettext():
2092 def gettext():
2092 return store.revision(self._node)
2093 return store.revision(self._node)
2093
2094
2094 def readsubtree(dir, subm):
2095 def readsubtree(dir, subm):
2095 # Set verify to False since we need to be able to create
2096 # Set verify to False since we need to be able to create
2096 # subtrees for trees that don't exist on disk.
2097 # subtrees for trees that don't exist on disk.
2097 return self._manifestlog.get(dir, subm, verify=False).read()
2098 return self._manifestlog.get(dir, subm, verify=False).read()
2098
2099
2099 m.read(gettext, readsubtree)
2100 m.read(gettext, readsubtree)
2100 m.setnode(self._node)
2101 m.setnode(self._node)
2101 self._data = m
2102 self._data = m
2102 else:
2103 else:
2103 if self._node in store.fulltextcache:
2104 if self._node in store.fulltextcache:
2104 text = pycompat.bytestr(store.fulltextcache[self._node])
2105 text = pycompat.bytestr(store.fulltextcache[self._node])
2105 else:
2106 else:
2106 text = store.revision(self._node)
2107 text = store.revision(self._node)
2107 arraytext = bytearray(text)
2108 arraytext = bytearray(text)
2108 store.fulltextcache[self._node] = arraytext
2109 store.fulltextcache[self._node] = arraytext
2109 self._data = treemanifest(dir=self._dir, text=text)
2110 self._data = treemanifest(dir=self._dir, text=text)
2110
2111
2111 return self._data
2112 return self._data
2112
2113
2113 def node(self):
2114 def node(self):
2114 return self._node
2115 return self._node
2115
2116
2116 def copy(self):
2117 def copy(self):
2117 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2118 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2118 memmf._treemanifest = self.read().copy()
2119 memmf._treemanifest = self.read().copy()
2119 return memmf
2120 return memmf
2120
2121
2121 @propertycache
2122 @propertycache
2122 def parents(self):
2123 def parents(self):
2123 return self._storage().parents(self._node)
2124 return self._storage().parents(self._node)
2124
2125
2125 def readdelta(self, shallow=False):
2126 def readdelta(self, shallow=False):
2126 '''Returns a manifest containing just the entries that are present
2127 '''Returns a manifest containing just the entries that are present
2127 in this manifest, but not in its p1 manifest. This is efficient to read
2128 in this manifest, but not in its p1 manifest. This is efficient to read
2128 if the revlog delta is already p1.
2129 if the revlog delta is already p1.
2129
2130
2130 If `shallow` is True, this will read the delta for this directory,
2131 If `shallow` is True, this will read the delta for this directory,
2131 without recursively reading subdirectory manifests. Instead, any
2132 without recursively reading subdirectory manifests. Instead, any
2132 subdirectory entry will be reported as it appears in the manifest, i.e.
2133 subdirectory entry will be reported as it appears in the manifest, i.e.
2133 the subdirectory will be reported among files and distinguished only by
2134 the subdirectory will be reported among files and distinguished only by
2134 its 't' flag.
2135 its 't' flag.
2135 '''
2136 '''
2136 store = self._storage()
2137 store = self._storage()
2137 if shallow:
2138 if shallow:
2138 r = store.rev(self._node)
2139 r = store.rev(self._node)
2139 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2140 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2140 return manifestdict(d)
2141 return manifestdict(d)
2141 else:
2142 else:
2142 # Need to perform a slow delta
2143 # Need to perform a slow delta
2143 r0 = store.deltaparent(store.rev(self._node))
2144 r0 = store.deltaparent(store.rev(self._node))
2144 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2145 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2145 m1 = self.read()
2146 m1 = self.read()
2146 md = treemanifest(dir=self._dir)
2147 md = treemanifest(dir=self._dir)
2147 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2148 for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
2148 if n1:
2149 if n1:
2149 md[f] = n1
2150 md[f] = n1
2150 if fl1:
2151 if fl1:
2151 md.setflag(f, fl1)
2152 md.setflag(f, fl1)
2152 return md
2153 return md
2153
2154
2154 def readfast(self, shallow=False):
2155 def readfast(self, shallow=False):
2155 '''Calls either readdelta or read, based on which would be less work.
2156 '''Calls either readdelta or read, based on which would be less work.
2156 readdelta is called if the delta is against the p1, and therefore can be
2157 readdelta is called if the delta is against the p1, and therefore can be
2157 read quickly.
2158 read quickly.
2158
2159
2159 If `shallow` is True, it only returns the entries from this manifest,
2160 If `shallow` is True, it only returns the entries from this manifest,
2160 and not any submanifests.
2161 and not any submanifests.
2161 '''
2162 '''
2162 store = self._storage()
2163 store = self._storage()
2163 r = store.rev(self._node)
2164 r = store.rev(self._node)
2164 deltaparent = store.deltaparent(r)
2165 deltaparent = store.deltaparent(r)
2165 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2166 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2166 return self.readdelta(shallow=shallow)
2167 return self.readdelta(shallow=shallow)
2167
2168
2168 if shallow:
2169 if shallow:
2169 return manifestdict(store.revision(self._node))
2170 return manifestdict(store.revision(self._node))
2170 else:
2171 else:
2171 return self.read()
2172 return self.read()
2172
2173
2173 def find(self, key):
2174 def find(self, key):
2174 return self.read().find(key)
2175 return self.read().find(key)
2175
2176
2176
2177
2177 class excludeddir(treemanifest):
2178 class excludeddir(treemanifest):
2178 """Stand-in for a directory that is excluded from the repository.
2179 """Stand-in for a directory that is excluded from the repository.
2179
2180
2180 With narrowing active on a repository that uses treemanifests,
2181 With narrowing active on a repository that uses treemanifests,
2181 some of the directory revlogs will be excluded from the resulting
2182 some of the directory revlogs will be excluded from the resulting
2182 clone. This is a huge storage win for clients, but means we need
2183 clone. This is a huge storage win for clients, but means we need
2183 some sort of pseudo-manifest to surface to internals so we can
2184 some sort of pseudo-manifest to surface to internals so we can
2184 detect a merge conflict outside the narrowspec. That's what this
2185 detect a merge conflict outside the narrowspec. That's what this
2185 class is: it stands in for a directory whose node is known, but
2186 class is: it stands in for a directory whose node is known, but
2186 whose contents are unknown.
2187 whose contents are unknown.
2187 """
2188 """
2188
2189
2189 def __init__(self, dir, node):
2190 def __init__(self, dir, node):
2190 super(excludeddir, self).__init__(dir)
2191 super(excludeddir, self).__init__(dir)
2191 self._node = node
2192 self._node = node
2192 # Add an empty file, which will be included by iterators and such,
2193 # Add an empty file, which will be included by iterators and such,
2193 # appearing as the directory itself (i.e. something like "dir/")
2194 # appearing as the directory itself (i.e. something like "dir/")
2194 self._files[b''] = node
2195 self._files[b''] = node
2195 self._flags[b''] = b't'
2196 self._flags[b''] = b't'
2196
2197
2197 # Manifests outside the narrowspec should never be modified, so avoid
2198 # Manifests outside the narrowspec should never be modified, so avoid
2198 # copying. This makes a noticeable difference when there are very many
2199 # copying. This makes a noticeable difference when there are very many
2199 # directories outside the narrowspec. Also, it makes sense for the copy to
2200 # directories outside the narrowspec. Also, it makes sense for the copy to
2200 # be of the same type as the original, which would not happen with the
2201 # be of the same type as the original, which would not happen with the
2201 # super type's copy().
2202 # super type's copy().
2202 def copy(self):
2203 def copy(self):
2203 return self
2204 return self
2204
2205
2205
2206
2206 class excludeddirmanifestctx(treemanifestctx):
2207 class excludeddirmanifestctx(treemanifestctx):
2207 """context wrapper for excludeddir - see that docstring for rationale"""
2208 """context wrapper for excludeddir - see that docstring for rationale"""
2208
2209
2209 def __init__(self, dir, node):
2210 def __init__(self, dir, node):
2210 self._dir = dir
2211 self._dir = dir
2211 self._node = node
2212 self._node = node
2212
2213
2213 def read(self):
2214 def read(self):
2214 return excludeddir(self._dir, self._node)
2215 return excludeddir(self._dir, self._node)
2215
2216
2216 def write(self, *args):
2217 def write(self, *args):
2217 raise error.ProgrammingError(
2218 raise error.ProgrammingError(
2218 b'attempt to write manifest from excluded dir %s' % self._dir
2219 b'attempt to write manifest from excluded dir %s' % self._dir
2219 )
2220 )
2220
2221
2221
2222
2222 class excludedmanifestrevlog(manifestrevlog):
2223 class excludedmanifestrevlog(manifestrevlog):
2223 """Stand-in for excluded treemanifest revlogs.
2224 """Stand-in for excluded treemanifest revlogs.
2224
2225
2225 When narrowing is active on a treemanifest repository, we'll have
2226 When narrowing is active on a treemanifest repository, we'll have
2226 references to directories we can't see due to the revlog being
2227 references to directories we can't see due to the revlog being
2227 skipped. This class exists to conform to the manifestrevlog
2228 skipped. This class exists to conform to the manifestrevlog
2228 interface for those directories and proactively prevent writes to
2229 interface for those directories and proactively prevent writes to
2229 outside the narrowspec.
2230 outside the narrowspec.
2230 """
2231 """
2231
2232
2232 def __init__(self, dir):
2233 def __init__(self, dir):
2233 self._dir = dir
2234 self._dir = dir
2234
2235
2235 def __len__(self):
2236 def __len__(self):
2236 raise error.ProgrammingError(
2237 raise error.ProgrammingError(
2237 b'attempt to get length of excluded dir %s' % self._dir
2238 b'attempt to get length of excluded dir %s' % self._dir
2238 )
2239 )
2239
2240
2240 def rev(self, node):
2241 def rev(self, node):
2241 raise error.ProgrammingError(
2242 raise error.ProgrammingError(
2242 b'attempt to get rev from excluded dir %s' % self._dir
2243 b'attempt to get rev from excluded dir %s' % self._dir
2243 )
2244 )
2244
2245
2245 def linkrev(self, node):
2246 def linkrev(self, node):
2246 raise error.ProgrammingError(
2247 raise error.ProgrammingError(
2247 b'attempt to get linkrev from excluded dir %s' % self._dir
2248 b'attempt to get linkrev from excluded dir %s' % self._dir
2248 )
2249 )
2249
2250
2250 def node(self, rev):
2251 def node(self, rev):
2251 raise error.ProgrammingError(
2252 raise error.ProgrammingError(
2252 b'attempt to get node from excluded dir %s' % self._dir
2253 b'attempt to get node from excluded dir %s' % self._dir
2253 )
2254 )
2254
2255
2255 def add(self, *args, **kwargs):
2256 def add(self, *args, **kwargs):
2256 # We should never write entries in dirlogs outside the narrow clone.
2257 # We should never write entries in dirlogs outside the narrow clone.
2257 # However, the method still gets called from writesubtree() in
2258 # However, the method still gets called from writesubtree() in
2258 # _addtree(), so we need to handle it. We should possibly make that
2259 # _addtree(), so we need to handle it. We should possibly make that
2259 # avoid calling add() with a clean manifest (_dirty is always False
2260 # avoid calling add() with a clean manifest (_dirty is always False
2260 # in excludeddir instances).
2261 # in excludeddir instances).
2261 pass
2262 pass
General Comments 0
You need to be logged in to leave comments. Login now