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