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