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