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