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