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