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