##// END OF EJS Templates
revlog: improve the robustness of the splitting process...
marmoute -
r51242:87f0155d stable
parent child Browse files
Show More
@@ -1,632 +1,632 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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
8
9 from .i18n import _
9 from .i18n import _
10 from .node import (
10 from .node import (
11 bin,
11 bin,
12 hex,
12 hex,
13 )
13 )
14 from .thirdparty import attr
14 from .thirdparty import attr
15
15
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 error,
18 error,
19 metadata,
19 metadata,
20 pycompat,
20 pycompat,
21 revlog,
21 revlog,
22 )
22 )
23 from .utils import (
23 from .utils import (
24 dateutil,
24 dateutil,
25 stringutil,
25 stringutil,
26 )
26 )
27 from .revlogutils import (
27 from .revlogutils import (
28 constants as revlog_constants,
28 constants as revlog_constants,
29 flagutil,
29 flagutil,
30 )
30 )
31
31
32 _defaultextra = {b'branch': b'default'}
32 _defaultextra = {b'branch': b'default'}
33
33
34
34
35 def _string_escape(text):
35 def _string_escape(text):
36 """
36 """
37 >>> from .pycompat import bytechr as chr
37 >>> from .pycompat import bytechr as chr
38 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
38 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
39 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
39 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
40 >>> s
40 >>> s
41 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
41 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
42 >>> res = _string_escape(s)
42 >>> res = _string_escape(s)
43 >>> s == _string_unescape(res)
43 >>> s == _string_unescape(res)
44 True
44 True
45 """
45 """
46 # subset of the string_escape codec
46 # subset of the string_escape codec
47 text = (
47 text = (
48 text.replace(b'\\', b'\\\\')
48 text.replace(b'\\', b'\\\\')
49 .replace(b'\n', b'\\n')
49 .replace(b'\n', b'\\n')
50 .replace(b'\r', b'\\r')
50 .replace(b'\r', b'\\r')
51 )
51 )
52 return text.replace(b'\0', b'\\0')
52 return text.replace(b'\0', b'\\0')
53
53
54
54
55 def _string_unescape(text):
55 def _string_unescape(text):
56 if b'\\0' in text:
56 if b'\\0' in text:
57 # fix up \0 without getting into trouble with \\0
57 # fix up \0 without getting into trouble with \\0
58 text = text.replace(b'\\\\', b'\\\\\n')
58 text = text.replace(b'\\\\', b'\\\\\n')
59 text = text.replace(b'\\0', b'\0')
59 text = text.replace(b'\\0', b'\0')
60 text = text.replace(b'\n', b'')
60 text = text.replace(b'\n', b'')
61 return stringutil.unescapestr(text)
61 return stringutil.unescapestr(text)
62
62
63
63
64 def decodeextra(text):
64 def decodeextra(text):
65 """
65 """
66 >>> from .pycompat import bytechr as chr
66 >>> from .pycompat import bytechr as chr
67 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
67 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
68 ... ).items())
68 ... ).items())
69 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
69 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
70 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
70 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
71 ... b'baz': chr(92) + chr(0) + b'2'})
71 ... b'baz': chr(92) + chr(0) + b'2'})
72 ... ).items())
72 ... ).items())
73 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
73 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
74 """
74 """
75 extra = _defaultextra.copy()
75 extra = _defaultextra.copy()
76 for l in text.split(b'\0'):
76 for l in text.split(b'\0'):
77 if l:
77 if l:
78 k, v = _string_unescape(l).split(b':', 1)
78 k, v = _string_unescape(l).split(b':', 1)
79 extra[k] = v
79 extra[k] = v
80 return extra
80 return extra
81
81
82
82
83 def encodeextra(d):
83 def encodeextra(d):
84 # keys must be sorted to produce a deterministic changelog entry
84 # keys must be sorted to produce a deterministic changelog entry
85 items = [_string_escape(b'%s:%s' % (k, d[k])) for k in sorted(d)]
85 items = [_string_escape(b'%s:%s' % (k, d[k])) for k in sorted(d)]
86 return b"\0".join(items)
86 return b"\0".join(items)
87
87
88
88
89 def stripdesc(desc):
89 def stripdesc(desc):
90 """strip trailing whitespace and leading and trailing empty lines"""
90 """strip trailing whitespace and leading and trailing empty lines"""
91 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
91 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
92
92
93
93
94 class appender:
94 class appender:
95 """the changelog index must be updated last on disk, so we use this class
95 """the changelog index must be updated last on disk, so we use this class
96 to delay writes to it"""
96 to delay writes to it"""
97
97
98 def __init__(self, vfs, name, mode, buf):
98 def __init__(self, vfs, name, mode, buf):
99 self.data = buf
99 self.data = buf
100 fp = vfs(name, mode)
100 fp = vfs(name, mode)
101 self.fp = fp
101 self.fp = fp
102 self.offset = fp.tell()
102 self.offset = fp.tell()
103 self.size = vfs.fstat(fp).st_size
103 self.size = vfs.fstat(fp).st_size
104 self._end = self.size
104 self._end = self.size
105
105
106 def end(self):
106 def end(self):
107 return self._end
107 return self._end
108
108
109 def tell(self):
109 def tell(self):
110 return self.offset
110 return self.offset
111
111
112 def flush(self):
112 def flush(self):
113 pass
113 pass
114
114
115 @property
115 @property
116 def closed(self):
116 def closed(self):
117 return self.fp.closed
117 return self.fp.closed
118
118
119 def close(self):
119 def close(self):
120 self.fp.close()
120 self.fp.close()
121
121
122 def seek(self, offset, whence=0):
122 def seek(self, offset, whence=0):
123 '''virtual file offset spans real file and data'''
123 '''virtual file offset spans real file and data'''
124 if whence == 0:
124 if whence == 0:
125 self.offset = offset
125 self.offset = offset
126 elif whence == 1:
126 elif whence == 1:
127 self.offset += offset
127 self.offset += offset
128 elif whence == 2:
128 elif whence == 2:
129 self.offset = self.end() + offset
129 self.offset = self.end() + offset
130 if self.offset < self.size:
130 if self.offset < self.size:
131 self.fp.seek(self.offset)
131 self.fp.seek(self.offset)
132
132
133 def read(self, count=-1):
133 def read(self, count=-1):
134 '''only trick here is reads that span real file and data'''
134 '''only trick here is reads that span real file and data'''
135 ret = b""
135 ret = b""
136 if self.offset < self.size:
136 if self.offset < self.size:
137 s = self.fp.read(count)
137 s = self.fp.read(count)
138 ret = s
138 ret = s
139 self.offset += len(s)
139 self.offset += len(s)
140 if count > 0:
140 if count > 0:
141 count -= len(s)
141 count -= len(s)
142 if count != 0:
142 if count != 0:
143 doff = self.offset - self.size
143 doff = self.offset - self.size
144 self.data.insert(0, b"".join(self.data))
144 self.data.insert(0, b"".join(self.data))
145 del self.data[1:]
145 del self.data[1:]
146 s = self.data[0][doff : doff + count]
146 s = self.data[0][doff : doff + count]
147 self.offset += len(s)
147 self.offset += len(s)
148 ret += s
148 ret += s
149 return ret
149 return ret
150
150
151 def write(self, s):
151 def write(self, s):
152 self.data.append(bytes(s))
152 self.data.append(bytes(s))
153 self.offset += len(s)
153 self.offset += len(s)
154 self._end += len(s)
154 self._end += len(s)
155
155
156 def __enter__(self):
156 def __enter__(self):
157 self.fp.__enter__()
157 self.fp.__enter__()
158 return self
158 return self
159
159
160 def __exit__(self, *args):
160 def __exit__(self, *args):
161 return self.fp.__exit__(*args)
161 return self.fp.__exit__(*args)
162
162
163
163
164 class _divertopener:
164 class _divertopener:
165 def __init__(self, opener, target):
165 def __init__(self, opener, target):
166 self._opener = opener
166 self._opener = opener
167 self._target = target
167 self._target = target
168
168
169 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
169 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
170 if name != self._target:
170 if name != self._target:
171 return self._opener(name, mode, **kwargs)
171 return self._opener(name, mode, **kwargs)
172 return self._opener(name + b".a", mode, **kwargs)
172 return self._opener(name + b".a", mode, **kwargs)
173
173
174 def __getattr__(self, attr):
174 def __getattr__(self, attr):
175 return getattr(self._opener, attr)
175 return getattr(self._opener, attr)
176
176
177
177
178 def _delayopener(opener, target, buf):
178 def _delayopener(opener, target, buf):
179 """build an opener that stores chunks in 'buf' instead of 'target'"""
179 """build an opener that stores chunks in 'buf' instead of 'target'"""
180
180
181 def _delay(name, mode=b'r', checkambig=False, **kwargs):
181 def _delay(name, mode=b'r', checkambig=False, **kwargs):
182 if name != target:
182 if name != target:
183 return opener(name, mode, **kwargs)
183 return opener(name, mode, **kwargs)
184 assert not kwargs
184 assert not kwargs
185 return appender(opener, name, mode, buf)
185 return appender(opener, name, mode, buf)
186
186
187 return _delay
187 return _delay
188
188
189
189
190 @attr.s
190 @attr.s
191 class _changelogrevision:
191 class _changelogrevision:
192 # Extensions might modify _defaultextra, so let the constructor below pass
192 # Extensions might modify _defaultextra, so let the constructor below pass
193 # it in
193 # it in
194 extra = attr.ib()
194 extra = attr.ib()
195 manifest = attr.ib()
195 manifest = attr.ib()
196 user = attr.ib(default=b'')
196 user = attr.ib(default=b'')
197 date = attr.ib(default=(0, 0))
197 date = attr.ib(default=(0, 0))
198 files = attr.ib(default=attr.Factory(list))
198 files = attr.ib(default=attr.Factory(list))
199 filesadded = attr.ib(default=None)
199 filesadded = attr.ib(default=None)
200 filesremoved = attr.ib(default=None)
200 filesremoved = attr.ib(default=None)
201 p1copies = attr.ib(default=None)
201 p1copies = attr.ib(default=None)
202 p2copies = attr.ib(default=None)
202 p2copies = attr.ib(default=None)
203 description = attr.ib(default=b'')
203 description = attr.ib(default=b'')
204 branchinfo = attr.ib(default=(_defaultextra[b'branch'], False))
204 branchinfo = attr.ib(default=(_defaultextra[b'branch'], False))
205
205
206
206
207 class changelogrevision:
207 class changelogrevision:
208 """Holds results of a parsed changelog revision.
208 """Holds results of a parsed changelog revision.
209
209
210 Changelog revisions consist of multiple pieces of data, including
210 Changelog revisions consist of multiple pieces of data, including
211 the manifest node, user, and date. This object exposes a view into
211 the manifest node, user, and date. This object exposes a view into
212 the parsed object.
212 the parsed object.
213 """
213 """
214
214
215 __slots__ = (
215 __slots__ = (
216 '_offsets',
216 '_offsets',
217 '_text',
217 '_text',
218 '_sidedata',
218 '_sidedata',
219 '_cpsd',
219 '_cpsd',
220 '_changes',
220 '_changes',
221 )
221 )
222
222
223 def __new__(cls, cl, text, sidedata, cpsd):
223 def __new__(cls, cl, text, sidedata, cpsd):
224 if not text:
224 if not text:
225 return _changelogrevision(extra=_defaultextra, manifest=cl.nullid)
225 return _changelogrevision(extra=_defaultextra, manifest=cl.nullid)
226
226
227 self = super(changelogrevision, cls).__new__(cls)
227 self = super(changelogrevision, cls).__new__(cls)
228 # We could return here and implement the following as an __init__.
228 # We could return here and implement the following as an __init__.
229 # But doing it here is equivalent and saves an extra function call.
229 # But doing it here is equivalent and saves an extra function call.
230
230
231 # format used:
231 # format used:
232 # nodeid\n : manifest node in ascii
232 # nodeid\n : manifest node in ascii
233 # user\n : user, no \n or \r allowed
233 # user\n : user, no \n or \r allowed
234 # time tz extra\n : date (time is int or float, timezone is int)
234 # time tz extra\n : date (time is int or float, timezone is int)
235 # : extra is metadata, encoded and separated by '\0'
235 # : extra is metadata, encoded and separated by '\0'
236 # : older versions ignore it
236 # : older versions ignore it
237 # files\n\n : files modified by the cset, no \n or \r allowed
237 # files\n\n : files modified by the cset, no \n or \r allowed
238 # (.*) : comment (free text, ideally utf-8)
238 # (.*) : comment (free text, ideally utf-8)
239 #
239 #
240 # changelog v0 doesn't use extra
240 # changelog v0 doesn't use extra
241
241
242 nl1 = text.index(b'\n')
242 nl1 = text.index(b'\n')
243 nl2 = text.index(b'\n', nl1 + 1)
243 nl2 = text.index(b'\n', nl1 + 1)
244 nl3 = text.index(b'\n', nl2 + 1)
244 nl3 = text.index(b'\n', nl2 + 1)
245
245
246 # The list of files may be empty. Which means nl3 is the first of the
246 # The list of files may be empty. Which means nl3 is the first of the
247 # double newline that precedes the description.
247 # double newline that precedes the description.
248 if text[nl3 + 1 : nl3 + 2] == b'\n':
248 if text[nl3 + 1 : nl3 + 2] == b'\n':
249 doublenl = nl3
249 doublenl = nl3
250 else:
250 else:
251 doublenl = text.index(b'\n\n', nl3 + 1)
251 doublenl = text.index(b'\n\n', nl3 + 1)
252
252
253 self._offsets = (nl1, nl2, nl3, doublenl)
253 self._offsets = (nl1, nl2, nl3, doublenl)
254 self._text = text
254 self._text = text
255 self._sidedata = sidedata
255 self._sidedata = sidedata
256 self._cpsd = cpsd
256 self._cpsd = cpsd
257 self._changes = None
257 self._changes = None
258
258
259 return self
259 return self
260
260
261 @property
261 @property
262 def manifest(self):
262 def manifest(self):
263 return bin(self._text[0 : self._offsets[0]])
263 return bin(self._text[0 : self._offsets[0]])
264
264
265 @property
265 @property
266 def user(self):
266 def user(self):
267 off = self._offsets
267 off = self._offsets
268 return encoding.tolocal(self._text[off[0] + 1 : off[1]])
268 return encoding.tolocal(self._text[off[0] + 1 : off[1]])
269
269
270 @property
270 @property
271 def _rawdate(self):
271 def _rawdate(self):
272 off = self._offsets
272 off = self._offsets
273 dateextra = self._text[off[1] + 1 : off[2]]
273 dateextra = self._text[off[1] + 1 : off[2]]
274 return dateextra.split(b' ', 2)[0:2]
274 return dateextra.split(b' ', 2)[0:2]
275
275
276 @property
276 @property
277 def _rawextra(self):
277 def _rawextra(self):
278 off = self._offsets
278 off = self._offsets
279 dateextra = self._text[off[1] + 1 : off[2]]
279 dateextra = self._text[off[1] + 1 : off[2]]
280 fields = dateextra.split(b' ', 2)
280 fields = dateextra.split(b' ', 2)
281 if len(fields) != 3:
281 if len(fields) != 3:
282 return None
282 return None
283
283
284 return fields[2]
284 return fields[2]
285
285
286 @property
286 @property
287 def date(self):
287 def date(self):
288 raw = self._rawdate
288 raw = self._rawdate
289 time = float(raw[0])
289 time = float(raw[0])
290 # Various tools did silly things with the timezone.
290 # Various tools did silly things with the timezone.
291 try:
291 try:
292 timezone = int(raw[1])
292 timezone = int(raw[1])
293 except ValueError:
293 except ValueError:
294 timezone = 0
294 timezone = 0
295
295
296 return time, timezone
296 return time, timezone
297
297
298 @property
298 @property
299 def extra(self):
299 def extra(self):
300 raw = self._rawextra
300 raw = self._rawextra
301 if raw is None:
301 if raw is None:
302 return _defaultextra
302 return _defaultextra
303
303
304 return decodeextra(raw)
304 return decodeextra(raw)
305
305
306 @property
306 @property
307 def changes(self):
307 def changes(self):
308 if self._changes is not None:
308 if self._changes is not None:
309 return self._changes
309 return self._changes
310 if self._cpsd:
310 if self._cpsd:
311 changes = metadata.decode_files_sidedata(self._sidedata)
311 changes = metadata.decode_files_sidedata(self._sidedata)
312 else:
312 else:
313 changes = metadata.ChangingFiles(
313 changes = metadata.ChangingFiles(
314 touched=self.files or (),
314 touched=self.files or (),
315 added=self.filesadded or (),
315 added=self.filesadded or (),
316 removed=self.filesremoved or (),
316 removed=self.filesremoved or (),
317 p1_copies=self.p1copies or {},
317 p1_copies=self.p1copies or {},
318 p2_copies=self.p2copies or {},
318 p2_copies=self.p2copies or {},
319 )
319 )
320 self._changes = changes
320 self._changes = changes
321 return changes
321 return changes
322
322
323 @property
323 @property
324 def files(self):
324 def files(self):
325 if self._cpsd:
325 if self._cpsd:
326 return sorted(self.changes.touched)
326 return sorted(self.changes.touched)
327 off = self._offsets
327 off = self._offsets
328 if off[2] == off[3]:
328 if off[2] == off[3]:
329 return []
329 return []
330
330
331 return self._text[off[2] + 1 : off[3]].split(b'\n')
331 return self._text[off[2] + 1 : off[3]].split(b'\n')
332
332
333 @property
333 @property
334 def filesadded(self):
334 def filesadded(self):
335 if self._cpsd:
335 if self._cpsd:
336 return self.changes.added
336 return self.changes.added
337 else:
337 else:
338 rawindices = self.extra.get(b'filesadded')
338 rawindices = self.extra.get(b'filesadded')
339 if rawindices is None:
339 if rawindices is None:
340 return None
340 return None
341 return metadata.decodefileindices(self.files, rawindices)
341 return metadata.decodefileindices(self.files, rawindices)
342
342
343 @property
343 @property
344 def filesremoved(self):
344 def filesremoved(self):
345 if self._cpsd:
345 if self._cpsd:
346 return self.changes.removed
346 return self.changes.removed
347 else:
347 else:
348 rawindices = self.extra.get(b'filesremoved')
348 rawindices = self.extra.get(b'filesremoved')
349 if rawindices is None:
349 if rawindices is None:
350 return None
350 return None
351 return metadata.decodefileindices(self.files, rawindices)
351 return metadata.decodefileindices(self.files, rawindices)
352
352
353 @property
353 @property
354 def p1copies(self):
354 def p1copies(self):
355 if self._cpsd:
355 if self._cpsd:
356 return self.changes.copied_from_p1
356 return self.changes.copied_from_p1
357 else:
357 else:
358 rawcopies = self.extra.get(b'p1copies')
358 rawcopies = self.extra.get(b'p1copies')
359 if rawcopies is None:
359 if rawcopies is None:
360 return None
360 return None
361 return metadata.decodecopies(self.files, rawcopies)
361 return metadata.decodecopies(self.files, rawcopies)
362
362
363 @property
363 @property
364 def p2copies(self):
364 def p2copies(self):
365 if self._cpsd:
365 if self._cpsd:
366 return self.changes.copied_from_p2
366 return self.changes.copied_from_p2
367 else:
367 else:
368 rawcopies = self.extra.get(b'p2copies')
368 rawcopies = self.extra.get(b'p2copies')
369 if rawcopies is None:
369 if rawcopies is None:
370 return None
370 return None
371 return metadata.decodecopies(self.files, rawcopies)
371 return metadata.decodecopies(self.files, rawcopies)
372
372
373 @property
373 @property
374 def description(self):
374 def description(self):
375 return encoding.tolocal(self._text[self._offsets[3] + 2 :])
375 return encoding.tolocal(self._text[self._offsets[3] + 2 :])
376
376
377 @property
377 @property
378 def branchinfo(self):
378 def branchinfo(self):
379 extra = self.extra
379 extra = self.extra
380 return encoding.tolocal(extra.get(b"branch")), b'close' in extra
380 return encoding.tolocal(extra.get(b"branch")), b'close' in extra
381
381
382
382
383 class changelog(revlog.revlog):
383 class changelog(revlog.revlog):
384 def __init__(self, opener, trypending=False, concurrencychecker=None):
384 def __init__(self, opener, trypending=False, concurrencychecker=None):
385 """Load a changelog revlog using an opener.
385 """Load a changelog revlog using an opener.
386
386
387 If ``trypending`` is true, we attempt to load the index from a
387 If ``trypending`` is true, we attempt to load the index from a
388 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
388 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
389 The ``00changelog.i.a`` file contains index (and possibly inline
389 The ``00changelog.i.a`` file contains index (and possibly inline
390 revision) data for a transaction that hasn't been finalized yet.
390 revision) data for a transaction that hasn't been finalized yet.
391 It exists in a separate file to facilitate readers (such as
391 It exists in a separate file to facilitate readers (such as
392 hooks processes) accessing data before a transaction is finalized.
392 hooks processes) accessing data before a transaction is finalized.
393
393
394 ``concurrencychecker`` will be passed to the revlog init function, see
394 ``concurrencychecker`` will be passed to the revlog init function, see
395 the documentation there.
395 the documentation there.
396 """
396 """
397 revlog.revlog.__init__(
397 revlog.revlog.__init__(
398 self,
398 self,
399 opener,
399 opener,
400 target=(revlog_constants.KIND_CHANGELOG, None),
400 target=(revlog_constants.KIND_CHANGELOG, None),
401 radix=b'00changelog',
401 radix=b'00changelog',
402 checkambig=True,
402 checkambig=True,
403 mmaplargeindex=True,
403 mmaplargeindex=True,
404 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
404 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
405 concurrencychecker=concurrencychecker,
405 concurrencychecker=concurrencychecker,
406 trypending=trypending,
406 trypending=trypending,
407 )
407 )
408
408
409 if self._initempty and (self._format_version == revlog.REVLOGV1):
409 if self._initempty and (self._format_version == revlog.REVLOGV1):
410 # changelogs don't benefit from generaldelta.
410 # changelogs don't benefit from generaldelta.
411
411
412 self._format_flags &= ~revlog.FLAG_GENERALDELTA
412 self._format_flags &= ~revlog.FLAG_GENERALDELTA
413 self._generaldelta = False
413 self._generaldelta = False
414
414
415 # Delta chains for changelogs tend to be very small because entries
415 # Delta chains for changelogs tend to be very small because entries
416 # tend to be small and don't delta well with each. So disable delta
416 # tend to be small and don't delta well with each. So disable delta
417 # chains.
417 # chains.
418 self._storedeltachains = False
418 self._storedeltachains = False
419
419
420 self._realopener = opener
420 self._realopener = opener
421 self._delayed = False
421 self._delayed = False
422 self._delaybuf = None
422 self._delaybuf = None
423 self._divert = False
423 self._divert = False
424 self._filteredrevs = frozenset()
424 self._filteredrevs = frozenset()
425 self._filteredrevs_hashcache = {}
425 self._filteredrevs_hashcache = {}
426 self._copiesstorage = opener.options.get(b'copies-storage')
426 self._copiesstorage = opener.options.get(b'copies-storage')
427
427
428 @property
428 @property
429 def filteredrevs(self):
429 def filteredrevs(self):
430 return self._filteredrevs
430 return self._filteredrevs
431
431
432 @filteredrevs.setter
432 @filteredrevs.setter
433 def filteredrevs(self, val):
433 def filteredrevs(self, val):
434 # Ensure all updates go through this function
434 # Ensure all updates go through this function
435 assert isinstance(val, frozenset)
435 assert isinstance(val, frozenset)
436 self._filteredrevs = val
436 self._filteredrevs = val
437 self._filteredrevs_hashcache = {}
437 self._filteredrevs_hashcache = {}
438
438
439 def _write_docket(self, tr):
439 def _write_docket(self, tr):
440 if not self._delayed:
440 if not self._delayed:
441 super(changelog, self)._write_docket(tr)
441 super(changelog, self)._write_docket(tr)
442
442
443 def delayupdate(self, tr):
443 def delayupdate(self, tr):
444 """delay visibility of index updates to other readers"""
444 """delay visibility of index updates to other readers"""
445 if self._docket is None and not self._delayed:
445 if self._docket is None and not self._delayed:
446 if len(self) == 0:
446 if len(self) == 0:
447 self._divert = True
447 self._divert = True
448 if self._realopener.exists(self._indexfile + b'.a'):
448 if self._realopener.exists(self._indexfile + b'.a'):
449 self._realopener.unlink(self._indexfile + b'.a')
449 self._realopener.unlink(self._indexfile + b'.a')
450 self.opener = _divertopener(self._realopener, self._indexfile)
450 self.opener = _divertopener(self._realopener, self._indexfile)
451 else:
451 else:
452 self._delaybuf = []
452 self._delaybuf = []
453 self.opener = _delayopener(
453 self.opener = _delayopener(
454 self._realopener, self._indexfile, self._delaybuf
454 self._realopener, self._indexfile, self._delaybuf
455 )
455 )
456 self._segmentfile.opener = self.opener
456 self._segmentfile.opener = self.opener
457 self._segmentfile_sidedata.opener = self.opener
457 self._segmentfile_sidedata.opener = self.opener
458 self._delayed = True
458 self._delayed = True
459 tr.addpending(b'cl-%i' % id(self), self._writepending)
459 tr.addpending(b'cl-%i' % id(self), self._writepending)
460 tr.addfinalize(b'cl-%i' % id(self), self._finalize)
460 tr.addfinalize(b'cl-%i' % id(self), self._finalize)
461
461
462 def _finalize(self, tr):
462 def _finalize(self, tr):
463 """finalize index updates"""
463 """finalize index updates"""
464 self._delayed = False
464 self._delayed = False
465 self.opener = self._realopener
465 self.opener = self._realopener
466 self._segmentfile.opener = self.opener
466 self._segmentfile.opener = self.opener
467 self._segmentfile_sidedata.opener = self.opener
467 self._segmentfile_sidedata.opener = self.opener
468 # move redirected index data back into place
468 # move redirected index data back into place
469 if self._docket is not None:
469 if self._docket is not None:
470 self._write_docket(tr)
470 self._write_docket(tr)
471 elif self._divert:
471 elif self._divert:
472 assert not self._delaybuf
472 assert not self._delaybuf
473 tmpname = self._indexfile + b".a"
473 tmpname = self._indexfile + b".a"
474 nfile = self.opener.open(tmpname)
474 nfile = self.opener.open(tmpname)
475 nfile.close()
475 nfile.close()
476 self.opener.rename(tmpname, self._indexfile, checkambig=True)
476 self.opener.rename(tmpname, self._indexfile, checkambig=True)
477 elif self._delaybuf:
477 elif self._delaybuf:
478 fp = self.opener(self._indexfile, b'a', checkambig=True)
478 fp = self.opener(self._indexfile, b'a', checkambig=True)
479 fp.write(b"".join(self._delaybuf))
479 fp.write(b"".join(self._delaybuf))
480 fp.close()
480 fp.close()
481 self._delaybuf = None
481 self._delaybuf = None
482 self._divert = False
482 self._divert = False
483 # split when we're done
483 # split when we're done
484 self._enforceinlinesize(tr)
484 self._enforceinlinesize(tr, side_write=False)
485
485
486 def _writepending(self, tr):
486 def _writepending(self, tr):
487 """create a file containing the unfinalized state for
487 """create a file containing the unfinalized state for
488 pretxnchangegroup"""
488 pretxnchangegroup"""
489 if self._docket:
489 if self._docket:
490 return self._docket.write(tr, pending=True)
490 return self._docket.write(tr, pending=True)
491 if self._delaybuf:
491 if self._delaybuf:
492 # make a temporary copy of the index
492 # make a temporary copy of the index
493 fp1 = self._realopener(self._indexfile)
493 fp1 = self._realopener(self._indexfile)
494 pendingfilename = self._indexfile + b".a"
494 pendingfilename = self._indexfile + b".a"
495 # register as a temp file to ensure cleanup on failure
495 # register as a temp file to ensure cleanup on failure
496 tr.registertmp(pendingfilename)
496 tr.registertmp(pendingfilename)
497 # write existing data
497 # write existing data
498 fp2 = self._realopener(pendingfilename, b"w")
498 fp2 = self._realopener(pendingfilename, b"w")
499 fp2.write(fp1.read())
499 fp2.write(fp1.read())
500 # add pending data
500 # add pending data
501 fp2.write(b"".join(self._delaybuf))
501 fp2.write(b"".join(self._delaybuf))
502 fp2.close()
502 fp2.close()
503 # switch modes so finalize can simply rename
503 # switch modes so finalize can simply rename
504 self._delaybuf = None
504 self._delaybuf = None
505 self._divert = True
505 self._divert = True
506 self.opener = _divertopener(self._realopener, self._indexfile)
506 self.opener = _divertopener(self._realopener, self._indexfile)
507 self._segmentfile.opener = self.opener
507 self._segmentfile.opener = self.opener
508 self._segmentfile_sidedata.opener = self.opener
508 self._segmentfile_sidedata.opener = self.opener
509
509
510 if self._divert:
510 if self._divert:
511 return True
511 return True
512
512
513 return False
513 return False
514
514
515 def _enforceinlinesize(self, tr):
515 def _enforceinlinesize(self, tr, side_write=True):
516 if not self._delayed:
516 if not self._delayed:
517 revlog.revlog._enforceinlinesize(self, tr)
517 revlog.revlog._enforceinlinesize(self, tr, side_write=side_write)
518
518
519 def read(self, nodeorrev):
519 def read(self, nodeorrev):
520 """Obtain data from a parsed changelog revision.
520 """Obtain data from a parsed changelog revision.
521
521
522 Returns a 6-tuple of:
522 Returns a 6-tuple of:
523
523
524 - manifest node in binary
524 - manifest node in binary
525 - author/user as a localstr
525 - author/user as a localstr
526 - date as a 2-tuple of (time, timezone)
526 - date as a 2-tuple of (time, timezone)
527 - list of files
527 - list of files
528 - commit message as a localstr
528 - commit message as a localstr
529 - dict of extra metadata
529 - dict of extra metadata
530
530
531 Unless you need to access all fields, consider calling
531 Unless you need to access all fields, consider calling
532 ``changelogrevision`` instead, as it is faster for partial object
532 ``changelogrevision`` instead, as it is faster for partial object
533 access.
533 access.
534 """
534 """
535 d = self._revisiondata(nodeorrev)
535 d = self._revisiondata(nodeorrev)
536 sidedata = self.sidedata(nodeorrev)
536 sidedata = self.sidedata(nodeorrev)
537 copy_sd = self._copiesstorage == b'changeset-sidedata'
537 copy_sd = self._copiesstorage == b'changeset-sidedata'
538 c = changelogrevision(self, d, sidedata, copy_sd)
538 c = changelogrevision(self, d, sidedata, copy_sd)
539 return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
539 return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
540
540
541 def changelogrevision(self, nodeorrev):
541 def changelogrevision(self, nodeorrev):
542 """Obtain a ``changelogrevision`` for a node or revision."""
542 """Obtain a ``changelogrevision`` for a node or revision."""
543 text = self._revisiondata(nodeorrev)
543 text = self._revisiondata(nodeorrev)
544 sidedata = self.sidedata(nodeorrev)
544 sidedata = self.sidedata(nodeorrev)
545 return changelogrevision(
545 return changelogrevision(
546 self, text, sidedata, self._copiesstorage == b'changeset-sidedata'
546 self, text, sidedata, self._copiesstorage == b'changeset-sidedata'
547 )
547 )
548
548
549 def readfiles(self, nodeorrev):
549 def readfiles(self, nodeorrev):
550 """
550 """
551 short version of read that only returns the files modified by the cset
551 short version of read that only returns the files modified by the cset
552 """
552 """
553 text = self.revision(nodeorrev)
553 text = self.revision(nodeorrev)
554 if not text:
554 if not text:
555 return []
555 return []
556 last = text.index(b"\n\n")
556 last = text.index(b"\n\n")
557 l = text[:last].split(b'\n')
557 l = text[:last].split(b'\n')
558 return l[3:]
558 return l[3:]
559
559
560 def add(
560 def add(
561 self,
561 self,
562 manifest,
562 manifest,
563 files,
563 files,
564 desc,
564 desc,
565 transaction,
565 transaction,
566 p1,
566 p1,
567 p2,
567 p2,
568 user,
568 user,
569 date=None,
569 date=None,
570 extra=None,
570 extra=None,
571 ):
571 ):
572 # Convert to UTF-8 encoded bytestrings as the very first
572 # Convert to UTF-8 encoded bytestrings as the very first
573 # thing: calling any method on a localstr object will turn it
573 # thing: calling any method on a localstr object will turn it
574 # into a str object and the cached UTF-8 string is thus lost.
574 # into a str object and the cached UTF-8 string is thus lost.
575 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
575 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
576
576
577 user = user.strip()
577 user = user.strip()
578 # An empty username or a username with a "\n" will make the
578 # An empty username or a username with a "\n" will make the
579 # revision text contain two "\n\n" sequences -> corrupt
579 # revision text contain two "\n\n" sequences -> corrupt
580 # repository since read cannot unpack the revision.
580 # repository since read cannot unpack the revision.
581 if not user:
581 if not user:
582 raise error.StorageError(_(b"empty username"))
582 raise error.StorageError(_(b"empty username"))
583 if b"\n" in user:
583 if b"\n" in user:
584 raise error.StorageError(
584 raise error.StorageError(
585 _(b"username %r contains a newline") % pycompat.bytestr(user)
585 _(b"username %r contains a newline") % pycompat.bytestr(user)
586 )
586 )
587
587
588 desc = stripdesc(desc)
588 desc = stripdesc(desc)
589
589
590 if date:
590 if date:
591 parseddate = b"%d %d" % dateutil.parsedate(date)
591 parseddate = b"%d %d" % dateutil.parsedate(date)
592 else:
592 else:
593 parseddate = b"%d %d" % dateutil.makedate()
593 parseddate = b"%d %d" % dateutil.makedate()
594 if extra:
594 if extra:
595 branch = extra.get(b"branch")
595 branch = extra.get(b"branch")
596 if branch in (b"default", b""):
596 if branch in (b"default", b""):
597 del extra[b"branch"]
597 del extra[b"branch"]
598 elif branch in (b".", b"null", b"tip"):
598 elif branch in (b".", b"null", b"tip"):
599 raise error.StorageError(
599 raise error.StorageError(
600 _(b'the name \'%s\' is reserved') % branch
600 _(b'the name \'%s\' is reserved') % branch
601 )
601 )
602 sortedfiles = sorted(files.touched)
602 sortedfiles = sorted(files.touched)
603 flags = 0
603 flags = 0
604 sidedata = None
604 sidedata = None
605 if self._copiesstorage == b'changeset-sidedata':
605 if self._copiesstorage == b'changeset-sidedata':
606 if files.has_copies_info:
606 if files.has_copies_info:
607 flags |= flagutil.REVIDX_HASCOPIESINFO
607 flags |= flagutil.REVIDX_HASCOPIESINFO
608 sidedata = metadata.encode_files_sidedata(files)
608 sidedata = metadata.encode_files_sidedata(files)
609
609
610 if extra:
610 if extra:
611 extra = encodeextra(extra)
611 extra = encodeextra(extra)
612 parseddate = b"%s %s" % (parseddate, extra)
612 parseddate = b"%s %s" % (parseddate, extra)
613 l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc]
613 l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc]
614 text = b"\n".join(l)
614 text = b"\n".join(l)
615 rev = self.addrevision(
615 rev = self.addrevision(
616 text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags
616 text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags
617 )
617 )
618 return self.node(rev)
618 return self.node(rev)
619
619
620 def branchinfo(self, rev):
620 def branchinfo(self, rev):
621 """return the branch name and open/close state of a revision
621 """return the branch name and open/close state of a revision
622
622
623 This function exists because creating a changectx object
623 This function exists because creating a changectx object
624 just to access this is costly."""
624 just to access this is costly."""
625 return self.changelogrevision(rev).branchinfo
625 return self.changelogrevision(rev).branchinfo
626
626
627 def _nodeduplicatecallback(self, transaction, rev):
627 def _nodeduplicatecallback(self, transaction, rev):
628 # keep track of revisions that got "re-added", eg: unbunde of know rev.
628 # keep track of revisions that got "re-added", eg: unbunde of know rev.
629 #
629 #
630 # We track them in a list to preserve their order from the source bundle
630 # We track them in a list to preserve their order from the source bundle
631 duplicates = transaction.changes.setdefault(b'revduplicates', [])
631 duplicates = transaction.changes.setdefault(b'revduplicates', [])
632 duplicates.append(rev)
632 duplicates.append(rev)
@@ -1,303 +1,304 b''
1 # filelog.py - file history class for mercurial
1 # filelog.py - file history class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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
8
9 from .i18n import _
9 from .i18n import _
10 from .node import nullrev
10 from .node import nullrev
11 from . import (
11 from . import (
12 error,
12 error,
13 revlog,
13 revlog,
14 )
14 )
15 from .interfaces import (
15 from .interfaces import (
16 repository,
16 repository,
17 util as interfaceutil,
17 util as interfaceutil,
18 )
18 )
19 from .utils import storageutil
19 from .utils import storageutil
20 from .revlogutils import (
20 from .revlogutils import (
21 constants as revlog_constants,
21 constants as revlog_constants,
22 rewrite,
22 rewrite,
23 )
23 )
24
24
25
25
26 @interfaceutil.implementer(repository.ifilestorage)
26 @interfaceutil.implementer(repository.ifilestorage)
27 class filelog:
27 class filelog:
28 def __init__(self, opener, path):
28 def __init__(self, opener, path, try_split=False):
29 self._revlog = revlog.revlog(
29 self._revlog = revlog.revlog(
30 opener,
30 opener,
31 # XXX should use the unencoded path
31 # XXX should use the unencoded path
32 target=(revlog_constants.KIND_FILELOG, path),
32 target=(revlog_constants.KIND_FILELOG, path),
33 radix=b'/'.join((b'data', path)),
33 radix=b'/'.join((b'data', path)),
34 censorable=True,
34 censorable=True,
35 canonical_parent_order=False, # see comment in revlog.py
35 canonical_parent_order=False, # see comment in revlog.py
36 try_split=try_split,
36 )
37 )
37 # Full name of the user visible file, relative to the repository root.
38 # Full name of the user visible file, relative to the repository root.
38 # Used by LFS.
39 # Used by LFS.
39 self._revlog.filename = path
40 self._revlog.filename = path
40 self.nullid = self._revlog.nullid
41 self.nullid = self._revlog.nullid
41 opts = opener.options
42 opts = opener.options
42 self._fix_issue6528 = opts.get(b'issue6528.fix-incoming', True)
43 self._fix_issue6528 = opts.get(b'issue6528.fix-incoming', True)
43
44
44 def __len__(self):
45 def __len__(self):
45 return len(self._revlog)
46 return len(self._revlog)
46
47
47 def __iter__(self):
48 def __iter__(self):
48 return self._revlog.__iter__()
49 return self._revlog.__iter__()
49
50
50 def hasnode(self, node):
51 def hasnode(self, node):
51 if node in (self.nullid, nullrev):
52 if node in (self.nullid, nullrev):
52 return False
53 return False
53
54
54 try:
55 try:
55 self._revlog.rev(node)
56 self._revlog.rev(node)
56 return True
57 return True
57 except (TypeError, ValueError, IndexError, error.LookupError):
58 except (TypeError, ValueError, IndexError, error.LookupError):
58 return False
59 return False
59
60
60 def revs(self, start=0, stop=None):
61 def revs(self, start=0, stop=None):
61 return self._revlog.revs(start=start, stop=stop)
62 return self._revlog.revs(start=start, stop=stop)
62
63
63 def parents(self, node):
64 def parents(self, node):
64 return self._revlog.parents(node)
65 return self._revlog.parents(node)
65
66
66 def parentrevs(self, rev):
67 def parentrevs(self, rev):
67 return self._revlog.parentrevs(rev)
68 return self._revlog.parentrevs(rev)
68
69
69 def rev(self, node):
70 def rev(self, node):
70 return self._revlog.rev(node)
71 return self._revlog.rev(node)
71
72
72 def node(self, rev):
73 def node(self, rev):
73 return self._revlog.node(rev)
74 return self._revlog.node(rev)
74
75
75 def lookup(self, node):
76 def lookup(self, node):
76 return storageutil.fileidlookup(
77 return storageutil.fileidlookup(
77 self._revlog, node, self._revlog.display_id
78 self._revlog, node, self._revlog.display_id
78 )
79 )
79
80
80 def linkrev(self, rev):
81 def linkrev(self, rev):
81 return self._revlog.linkrev(rev)
82 return self._revlog.linkrev(rev)
82
83
83 def commonancestorsheads(self, node1, node2):
84 def commonancestorsheads(self, node1, node2):
84 return self._revlog.commonancestorsheads(node1, node2)
85 return self._revlog.commonancestorsheads(node1, node2)
85
86
86 # Used by dagop.blockdescendants().
87 # Used by dagop.blockdescendants().
87 def descendants(self, revs):
88 def descendants(self, revs):
88 return self._revlog.descendants(revs)
89 return self._revlog.descendants(revs)
89
90
90 def heads(self, start=None, stop=None):
91 def heads(self, start=None, stop=None):
91 return self._revlog.heads(start, stop)
92 return self._revlog.heads(start, stop)
92
93
93 # Used by hgweb, children extension.
94 # Used by hgweb, children extension.
94 def children(self, node):
95 def children(self, node):
95 return self._revlog.children(node)
96 return self._revlog.children(node)
96
97
97 def iscensored(self, rev):
98 def iscensored(self, rev):
98 return self._revlog.iscensored(rev)
99 return self._revlog.iscensored(rev)
99
100
100 def revision(self, node, _df=None):
101 def revision(self, node, _df=None):
101 return self._revlog.revision(node, _df=_df)
102 return self._revlog.revision(node, _df=_df)
102
103
103 def rawdata(self, node, _df=None):
104 def rawdata(self, node, _df=None):
104 return self._revlog.rawdata(node, _df=_df)
105 return self._revlog.rawdata(node, _df=_df)
105
106
106 def emitrevisions(
107 def emitrevisions(
107 self,
108 self,
108 nodes,
109 nodes,
109 nodesorder=None,
110 nodesorder=None,
110 revisiondata=False,
111 revisiondata=False,
111 assumehaveparentrevisions=False,
112 assumehaveparentrevisions=False,
112 deltamode=repository.CG_DELTAMODE_STD,
113 deltamode=repository.CG_DELTAMODE_STD,
113 sidedata_helpers=None,
114 sidedata_helpers=None,
114 debug_info=None,
115 debug_info=None,
115 ):
116 ):
116 return self._revlog.emitrevisions(
117 return self._revlog.emitrevisions(
117 nodes,
118 nodes,
118 nodesorder=nodesorder,
119 nodesorder=nodesorder,
119 revisiondata=revisiondata,
120 revisiondata=revisiondata,
120 assumehaveparentrevisions=assumehaveparentrevisions,
121 assumehaveparentrevisions=assumehaveparentrevisions,
121 deltamode=deltamode,
122 deltamode=deltamode,
122 sidedata_helpers=sidedata_helpers,
123 sidedata_helpers=sidedata_helpers,
123 debug_info=debug_info,
124 debug_info=debug_info,
124 )
125 )
125
126
126 def addrevision(
127 def addrevision(
127 self,
128 self,
128 revisiondata,
129 revisiondata,
129 transaction,
130 transaction,
130 linkrev,
131 linkrev,
131 p1,
132 p1,
132 p2,
133 p2,
133 node=None,
134 node=None,
134 flags=revlog.REVIDX_DEFAULT_FLAGS,
135 flags=revlog.REVIDX_DEFAULT_FLAGS,
135 cachedelta=None,
136 cachedelta=None,
136 ):
137 ):
137 return self._revlog.addrevision(
138 return self._revlog.addrevision(
138 revisiondata,
139 revisiondata,
139 transaction,
140 transaction,
140 linkrev,
141 linkrev,
141 p1,
142 p1,
142 p2,
143 p2,
143 node=node,
144 node=node,
144 flags=flags,
145 flags=flags,
145 cachedelta=cachedelta,
146 cachedelta=cachedelta,
146 )
147 )
147
148
148 def addgroup(
149 def addgroup(
149 self,
150 self,
150 deltas,
151 deltas,
151 linkmapper,
152 linkmapper,
152 transaction,
153 transaction,
153 addrevisioncb=None,
154 addrevisioncb=None,
154 duplicaterevisioncb=None,
155 duplicaterevisioncb=None,
155 maybemissingparents=False,
156 maybemissingparents=False,
156 debug_info=None,
157 debug_info=None,
157 delta_base_reuse_policy=None,
158 delta_base_reuse_policy=None,
158 ):
159 ):
159 if maybemissingparents:
160 if maybemissingparents:
160 raise error.Abort(
161 raise error.Abort(
161 _(
162 _(
162 b'revlog storage does not support missing '
163 b'revlog storage does not support missing '
163 b'parents write mode'
164 b'parents write mode'
164 )
165 )
165 )
166 )
166
167
167 with self._revlog._writing(transaction):
168 with self._revlog._writing(transaction):
168
169
169 if self._fix_issue6528:
170 if self._fix_issue6528:
170 deltas = rewrite.filter_delta_issue6528(self._revlog, deltas)
171 deltas = rewrite.filter_delta_issue6528(self._revlog, deltas)
171
172
172 return self._revlog.addgroup(
173 return self._revlog.addgroup(
173 deltas,
174 deltas,
174 linkmapper,
175 linkmapper,
175 transaction,
176 transaction,
176 addrevisioncb=addrevisioncb,
177 addrevisioncb=addrevisioncb,
177 duplicaterevisioncb=duplicaterevisioncb,
178 duplicaterevisioncb=duplicaterevisioncb,
178 debug_info=debug_info,
179 debug_info=debug_info,
179 delta_base_reuse_policy=delta_base_reuse_policy,
180 delta_base_reuse_policy=delta_base_reuse_policy,
180 )
181 )
181
182
182 def getstrippoint(self, minlink):
183 def getstrippoint(self, minlink):
183 return self._revlog.getstrippoint(minlink)
184 return self._revlog.getstrippoint(minlink)
184
185
185 def strip(self, minlink, transaction):
186 def strip(self, minlink, transaction):
186 return self._revlog.strip(minlink, transaction)
187 return self._revlog.strip(minlink, transaction)
187
188
188 def censorrevision(self, tr, node, tombstone=b''):
189 def censorrevision(self, tr, node, tombstone=b''):
189 return self._revlog.censorrevision(tr, node, tombstone=tombstone)
190 return self._revlog.censorrevision(tr, node, tombstone=tombstone)
190
191
191 def files(self):
192 def files(self):
192 return self._revlog.files()
193 return self._revlog.files()
193
194
194 def read(self, node):
195 def read(self, node):
195 return storageutil.filtermetadata(self.revision(node))
196 return storageutil.filtermetadata(self.revision(node))
196
197
197 def add(self, text, meta, transaction, link, p1=None, p2=None):
198 def add(self, text, meta, transaction, link, p1=None, p2=None):
198 if meta or text.startswith(b'\1\n'):
199 if meta or text.startswith(b'\1\n'):
199 text = storageutil.packmeta(meta, text)
200 text = storageutil.packmeta(meta, text)
200 rev = self.addrevision(text, transaction, link, p1, p2)
201 rev = self.addrevision(text, transaction, link, p1, p2)
201 return self.node(rev)
202 return self.node(rev)
202
203
203 def renamed(self, node):
204 def renamed(self, node):
204 return storageutil.filerevisioncopied(self, node)
205 return storageutil.filerevisioncopied(self, node)
205
206
206 def size(self, rev):
207 def size(self, rev):
207 """return the size of a given revision"""
208 """return the size of a given revision"""
208
209
209 # for revisions with renames, we have to go the slow way
210 # for revisions with renames, we have to go the slow way
210 node = self.node(rev)
211 node = self.node(rev)
211 if self.iscensored(rev):
212 if self.iscensored(rev):
212 return 0
213 return 0
213 if self.renamed(node):
214 if self.renamed(node):
214 return len(self.read(node))
215 return len(self.read(node))
215
216
216 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
217 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
217 # XXX See also basefilectx.cmp.
218 # XXX See also basefilectx.cmp.
218 return self._revlog.size(rev)
219 return self._revlog.size(rev)
219
220
220 def cmp(self, node, text):
221 def cmp(self, node, text):
221 """compare text with a given file revision
222 """compare text with a given file revision
222
223
223 returns True if text is different than what is stored.
224 returns True if text is different than what is stored.
224 """
225 """
225 return not storageutil.filedataequivalent(self, node, text)
226 return not storageutil.filedataequivalent(self, node, text)
226
227
227 def verifyintegrity(self, state):
228 def verifyintegrity(self, state):
228 return self._revlog.verifyintegrity(state)
229 return self._revlog.verifyintegrity(state)
229
230
230 def storageinfo(
231 def storageinfo(
231 self,
232 self,
232 exclusivefiles=False,
233 exclusivefiles=False,
233 sharedfiles=False,
234 sharedfiles=False,
234 revisionscount=False,
235 revisionscount=False,
235 trackedsize=False,
236 trackedsize=False,
236 storedsize=False,
237 storedsize=False,
237 ):
238 ):
238 return self._revlog.storageinfo(
239 return self._revlog.storageinfo(
239 exclusivefiles=exclusivefiles,
240 exclusivefiles=exclusivefiles,
240 sharedfiles=sharedfiles,
241 sharedfiles=sharedfiles,
241 revisionscount=revisionscount,
242 revisionscount=revisionscount,
242 trackedsize=trackedsize,
243 trackedsize=trackedsize,
243 storedsize=storedsize,
244 storedsize=storedsize,
244 )
245 )
245
246
246 # Used by repo upgrade.
247 # Used by repo upgrade.
247 def clone(self, tr, destrevlog, **kwargs):
248 def clone(self, tr, destrevlog, **kwargs):
248 if not isinstance(destrevlog, filelog):
249 if not isinstance(destrevlog, filelog):
249 msg = b'expected filelog to clone(), not %r'
250 msg = b'expected filelog to clone(), not %r'
250 msg %= destrevlog
251 msg %= destrevlog
251 raise error.ProgrammingError(msg)
252 raise error.ProgrammingError(msg)
252
253
253 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
254 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
254
255
255
256
256 class narrowfilelog(filelog):
257 class narrowfilelog(filelog):
257 """Filelog variation to be used with narrow stores."""
258 """Filelog variation to be used with narrow stores."""
258
259
259 def __init__(self, opener, path, narrowmatch):
260 def __init__(self, opener, path, narrowmatch, try_split=False):
260 super(narrowfilelog, self).__init__(opener, path)
261 super(narrowfilelog, self).__init__(opener, path, try_split=try_split)
261 self._narrowmatch = narrowmatch
262 self._narrowmatch = narrowmatch
262
263
263 def renamed(self, node):
264 def renamed(self, node):
264 res = super(narrowfilelog, self).renamed(node)
265 res = super(narrowfilelog, self).renamed(node)
265
266
266 # Renames that come from outside the narrowspec are problematic
267 # Renames that come from outside the narrowspec are problematic
267 # because we may lack the base text for the rename. This can result
268 # because we may lack the base text for the rename. This can result
268 # in code attempting to walk the ancestry or compute a diff
269 # in code attempting to walk the ancestry or compute a diff
269 # encountering a missing revision. We address this by silently
270 # encountering a missing revision. We address this by silently
270 # removing rename metadata if the source file is outside the
271 # removing rename metadata if the source file is outside the
271 # narrow spec.
272 # narrow spec.
272 #
273 #
273 # A better solution would be to see if the base revision is available,
274 # A better solution would be to see if the base revision is available,
274 # rather than assuming it isn't.
275 # rather than assuming it isn't.
275 #
276 #
276 # An even better solution would be to teach all consumers of rename
277 # An even better solution would be to teach all consumers of rename
277 # metadata that the base revision may not be available.
278 # metadata that the base revision may not be available.
278 #
279 #
279 # TODO consider better ways of doing this.
280 # TODO consider better ways of doing this.
280 if res and not self._narrowmatch(res[0]):
281 if res and not self._narrowmatch(res[0]):
281 return None
282 return None
282
283
283 return res
284 return res
284
285
285 def size(self, rev):
286 def size(self, rev):
286 # Because we have a custom renamed() that may lie, we need to call
287 # Because we have a custom renamed() that may lie, we need to call
287 # the base renamed() to report accurate results.
288 # the base renamed() to report accurate results.
288 node = self.node(rev)
289 node = self.node(rev)
289 if super(narrowfilelog, self).renamed(node):
290 if super(narrowfilelog, self).renamed(node):
290 return len(self.read(node))
291 return len(self.read(node))
291 else:
292 else:
292 return super(narrowfilelog, self).size(rev)
293 return super(narrowfilelog, self).size(rev)
293
294
294 def cmp(self, node, text):
295 def cmp(self, node, text):
295 # We don't call `super` because narrow parents can be buggy in case of a
296 # We don't call `super` because narrow parents can be buggy in case of a
296 # ambiguous dirstate. Always take the slow path until there is a better
297 # ambiguous dirstate. Always take the slow path until there is a better
297 # fix, see issue6150.
298 # fix, see issue6150.
298
299
299 # Censored files compare against the empty file.
300 # Censored files compare against the empty file.
300 if self.iscensored(self.rev(node)):
301 if self.iscensored(self.rev(node)):
301 return text != b''
302 return text != b''
302
303
303 return self.read(node) != text
304 return self.read(node) != text
@@ -1,3984 +1,3995 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 # coding: utf-8
2 # coding: utf-8
3 #
3 #
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9
9
10 import functools
10 import functools
11 import os
11 import os
12 import random
12 import random
13 import re
13 import re
14 import sys
14 import sys
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from concurrent import futures
18 from concurrent import futures
19 from typing import (
19 from typing import (
20 Optional,
20 Optional,
21 )
21 )
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullrev,
27 nullrev,
28 sha1nodeconstants,
28 sha1nodeconstants,
29 short,
29 short,
30 )
30 )
31 from .pycompat import (
31 from .pycompat import (
32 delattr,
32 delattr,
33 getattr,
33 getattr,
34 )
34 )
35 from . import (
35 from . import (
36 bookmarks,
36 bookmarks,
37 branchmap,
37 branchmap,
38 bundle2,
38 bundle2,
39 bundlecaches,
39 bundlecaches,
40 changegroup,
40 changegroup,
41 color,
41 color,
42 commit,
42 commit,
43 context,
43 context,
44 dirstate,
44 dirstate,
45 discovery,
45 discovery,
46 encoding,
46 encoding,
47 error,
47 error,
48 exchange,
48 exchange,
49 extensions,
49 extensions,
50 filelog,
50 filelog,
51 hook,
51 hook,
52 lock as lockmod,
52 lock as lockmod,
53 match as matchmod,
53 match as matchmod,
54 mergestate as mergestatemod,
54 mergestate as mergestatemod,
55 mergeutil,
55 mergeutil,
56 namespaces,
56 namespaces,
57 narrowspec,
57 narrowspec,
58 obsolete,
58 obsolete,
59 pathutil,
59 pathutil,
60 phases,
60 phases,
61 pushkey,
61 pushkey,
62 pycompat,
62 pycompat,
63 rcutil,
63 rcutil,
64 repoview,
64 repoview,
65 requirements as requirementsmod,
65 requirements as requirementsmod,
66 revlog,
66 revlog,
67 revset,
67 revset,
68 revsetlang,
68 revsetlang,
69 scmutil,
69 scmutil,
70 sparse,
70 sparse,
71 store as storemod,
71 store as storemod,
72 subrepoutil,
72 subrepoutil,
73 tags as tagsmod,
73 tags as tagsmod,
74 transaction,
74 transaction,
75 txnutil,
75 txnutil,
76 util,
76 util,
77 vfs as vfsmod,
77 vfs as vfsmod,
78 wireprototypes,
78 wireprototypes,
79 )
79 )
80
80
81 from .interfaces import (
81 from .interfaces import (
82 repository,
82 repository,
83 util as interfaceutil,
83 util as interfaceutil,
84 )
84 )
85
85
86 from .utils import (
86 from .utils import (
87 hashutil,
87 hashutil,
88 procutil,
88 procutil,
89 stringutil,
89 stringutil,
90 urlutil,
90 urlutil,
91 )
91 )
92
92
93 from .revlogutils import (
93 from .revlogutils import (
94 concurrency_checker as revlogchecker,
94 concurrency_checker as revlogchecker,
95 constants as revlogconst,
95 constants as revlogconst,
96 sidedata as sidedatamod,
96 sidedata as sidedatamod,
97 )
97 )
98
98
99 release = lockmod.release
99 release = lockmod.release
100 urlerr = util.urlerr
100 urlerr = util.urlerr
101 urlreq = util.urlreq
101 urlreq = util.urlreq
102
102
103 RE_SKIP_DIRSTATE_ROLLBACK = re.compile(
103 RE_SKIP_DIRSTATE_ROLLBACK = re.compile(
104 b"^((dirstate|narrowspec.dirstate).*|branch$)"
104 b"^((dirstate|narrowspec.dirstate).*|branch$)"
105 )
105 )
106
106
107 # set of (path, vfs-location) tuples. vfs-location is:
107 # set of (path, vfs-location) tuples. vfs-location is:
108 # - 'plain for vfs relative paths
108 # - 'plain for vfs relative paths
109 # - '' for svfs relative paths
109 # - '' for svfs relative paths
110 _cachedfiles = set()
110 _cachedfiles = set()
111
111
112
112
113 class _basefilecache(scmutil.filecache):
113 class _basefilecache(scmutil.filecache):
114 """All filecache usage on repo are done for logic that should be unfiltered"""
114 """All filecache usage on repo are done for logic that should be unfiltered"""
115
115
116 def __get__(self, repo, type=None):
116 def __get__(self, repo, type=None):
117 if repo is None:
117 if repo is None:
118 return self
118 return self
119 # proxy to unfiltered __dict__ since filtered repo has no entry
119 # proxy to unfiltered __dict__ since filtered repo has no entry
120 unfi = repo.unfiltered()
120 unfi = repo.unfiltered()
121 try:
121 try:
122 return unfi.__dict__[self.sname]
122 return unfi.__dict__[self.sname]
123 except KeyError:
123 except KeyError:
124 pass
124 pass
125 return super(_basefilecache, self).__get__(unfi, type)
125 return super(_basefilecache, self).__get__(unfi, type)
126
126
127 def set(self, repo, value):
127 def set(self, repo, value):
128 return super(_basefilecache, self).set(repo.unfiltered(), value)
128 return super(_basefilecache, self).set(repo.unfiltered(), value)
129
129
130
130
131 class repofilecache(_basefilecache):
131 class repofilecache(_basefilecache):
132 """filecache for files in .hg but outside of .hg/store"""
132 """filecache for files in .hg but outside of .hg/store"""
133
133
134 def __init__(self, *paths):
134 def __init__(self, *paths):
135 super(repofilecache, self).__init__(*paths)
135 super(repofilecache, self).__init__(*paths)
136 for path in paths:
136 for path in paths:
137 _cachedfiles.add((path, b'plain'))
137 _cachedfiles.add((path, b'plain'))
138
138
139 def join(self, obj, fname):
139 def join(self, obj, fname):
140 return obj.vfs.join(fname)
140 return obj.vfs.join(fname)
141
141
142
142
143 class storecache(_basefilecache):
143 class storecache(_basefilecache):
144 """filecache for files in the store"""
144 """filecache for files in the store"""
145
145
146 def __init__(self, *paths):
146 def __init__(self, *paths):
147 super(storecache, self).__init__(*paths)
147 super(storecache, self).__init__(*paths)
148 for path in paths:
148 for path in paths:
149 _cachedfiles.add((path, b''))
149 _cachedfiles.add((path, b''))
150
150
151 def join(self, obj, fname):
151 def join(self, obj, fname):
152 return obj.sjoin(fname)
152 return obj.sjoin(fname)
153
153
154
154
155 class changelogcache(storecache):
155 class changelogcache(storecache):
156 """filecache for the changelog"""
156 """filecache for the changelog"""
157
157
158 def __init__(self):
158 def __init__(self):
159 super(changelogcache, self).__init__()
159 super(changelogcache, self).__init__()
160 _cachedfiles.add((b'00changelog.i', b''))
160 _cachedfiles.add((b'00changelog.i', b''))
161 _cachedfiles.add((b'00changelog.n', b''))
161 _cachedfiles.add((b'00changelog.n', b''))
162
162
163 def tracked_paths(self, obj):
163 def tracked_paths(self, obj):
164 paths = [self.join(obj, b'00changelog.i')]
164 paths = [self.join(obj, b'00changelog.i')]
165 if obj.store.opener.options.get(b'persistent-nodemap', False):
165 if obj.store.opener.options.get(b'persistent-nodemap', False):
166 paths.append(self.join(obj, b'00changelog.n'))
166 paths.append(self.join(obj, b'00changelog.n'))
167 return paths
167 return paths
168
168
169
169
170 class manifestlogcache(storecache):
170 class manifestlogcache(storecache):
171 """filecache for the manifestlog"""
171 """filecache for the manifestlog"""
172
172
173 def __init__(self):
173 def __init__(self):
174 super(manifestlogcache, self).__init__()
174 super(manifestlogcache, self).__init__()
175 _cachedfiles.add((b'00manifest.i', b''))
175 _cachedfiles.add((b'00manifest.i', b''))
176 _cachedfiles.add((b'00manifest.n', b''))
176 _cachedfiles.add((b'00manifest.n', b''))
177
177
178 def tracked_paths(self, obj):
178 def tracked_paths(self, obj):
179 paths = [self.join(obj, b'00manifest.i')]
179 paths = [self.join(obj, b'00manifest.i')]
180 if obj.store.opener.options.get(b'persistent-nodemap', False):
180 if obj.store.opener.options.get(b'persistent-nodemap', False):
181 paths.append(self.join(obj, b'00manifest.n'))
181 paths.append(self.join(obj, b'00manifest.n'))
182 return paths
182 return paths
183
183
184
184
185 class mixedrepostorecache(_basefilecache):
185 class mixedrepostorecache(_basefilecache):
186 """filecache for a mix files in .hg/store and outside"""
186 """filecache for a mix files in .hg/store and outside"""
187
187
188 def __init__(self, *pathsandlocations):
188 def __init__(self, *pathsandlocations):
189 # scmutil.filecache only uses the path for passing back into our
189 # scmutil.filecache only uses the path for passing back into our
190 # join(), so we can safely pass a list of paths and locations
190 # join(), so we can safely pass a list of paths and locations
191 super(mixedrepostorecache, self).__init__(*pathsandlocations)
191 super(mixedrepostorecache, self).__init__(*pathsandlocations)
192 _cachedfiles.update(pathsandlocations)
192 _cachedfiles.update(pathsandlocations)
193
193
194 def join(self, obj, fnameandlocation):
194 def join(self, obj, fnameandlocation):
195 fname, location = fnameandlocation
195 fname, location = fnameandlocation
196 if location == b'plain':
196 if location == b'plain':
197 return obj.vfs.join(fname)
197 return obj.vfs.join(fname)
198 else:
198 else:
199 if location != b'':
199 if location != b'':
200 raise error.ProgrammingError(
200 raise error.ProgrammingError(
201 b'unexpected location: %s' % location
201 b'unexpected location: %s' % location
202 )
202 )
203 return obj.sjoin(fname)
203 return obj.sjoin(fname)
204
204
205
205
206 def isfilecached(repo, name):
206 def isfilecached(repo, name):
207 """check if a repo has already cached "name" filecache-ed property
207 """check if a repo has already cached "name" filecache-ed property
208
208
209 This returns (cachedobj-or-None, iscached) tuple.
209 This returns (cachedobj-or-None, iscached) tuple.
210 """
210 """
211 cacheentry = repo.unfiltered()._filecache.get(name, None)
211 cacheentry = repo.unfiltered()._filecache.get(name, None)
212 if not cacheentry:
212 if not cacheentry:
213 return None, False
213 return None, False
214 return cacheentry.obj, True
214 return cacheentry.obj, True
215
215
216
216
217 class unfilteredpropertycache(util.propertycache):
217 class unfilteredpropertycache(util.propertycache):
218 """propertycache that apply to unfiltered repo only"""
218 """propertycache that apply to unfiltered repo only"""
219
219
220 def __get__(self, repo, type=None):
220 def __get__(self, repo, type=None):
221 unfi = repo.unfiltered()
221 unfi = repo.unfiltered()
222 if unfi is repo:
222 if unfi is repo:
223 return super(unfilteredpropertycache, self).__get__(unfi)
223 return super(unfilteredpropertycache, self).__get__(unfi)
224 return getattr(unfi, self.name)
224 return getattr(unfi, self.name)
225
225
226
226
227 class filteredpropertycache(util.propertycache):
227 class filteredpropertycache(util.propertycache):
228 """propertycache that must take filtering in account"""
228 """propertycache that must take filtering in account"""
229
229
230 def cachevalue(self, obj, value):
230 def cachevalue(self, obj, value):
231 object.__setattr__(obj, self.name, value)
231 object.__setattr__(obj, self.name, value)
232
232
233
233
234 def hasunfilteredcache(repo, name):
234 def hasunfilteredcache(repo, name):
235 """check if a repo has an unfilteredpropertycache value for <name>"""
235 """check if a repo has an unfilteredpropertycache value for <name>"""
236 return name in vars(repo.unfiltered())
236 return name in vars(repo.unfiltered())
237
237
238
238
239 def unfilteredmethod(orig):
239 def unfilteredmethod(orig):
240 """decorate method that always need to be run on unfiltered version"""
240 """decorate method that always need to be run on unfiltered version"""
241
241
242 @functools.wraps(orig)
242 @functools.wraps(orig)
243 def wrapper(repo, *args, **kwargs):
243 def wrapper(repo, *args, **kwargs):
244 return orig(repo.unfiltered(), *args, **kwargs)
244 return orig(repo.unfiltered(), *args, **kwargs)
245
245
246 return wrapper
246 return wrapper
247
247
248
248
249 moderncaps = {
249 moderncaps = {
250 b'lookup',
250 b'lookup',
251 b'branchmap',
251 b'branchmap',
252 b'pushkey',
252 b'pushkey',
253 b'known',
253 b'known',
254 b'getbundle',
254 b'getbundle',
255 b'unbundle',
255 b'unbundle',
256 }
256 }
257 legacycaps = moderncaps.union({b'changegroupsubset'})
257 legacycaps = moderncaps.union({b'changegroupsubset'})
258
258
259
259
260 @interfaceutil.implementer(repository.ipeercommandexecutor)
260 @interfaceutil.implementer(repository.ipeercommandexecutor)
261 class localcommandexecutor:
261 class localcommandexecutor:
262 def __init__(self, peer):
262 def __init__(self, peer):
263 self._peer = peer
263 self._peer = peer
264 self._sent = False
264 self._sent = False
265 self._closed = False
265 self._closed = False
266
266
267 def __enter__(self):
267 def __enter__(self):
268 return self
268 return self
269
269
270 def __exit__(self, exctype, excvalue, exctb):
270 def __exit__(self, exctype, excvalue, exctb):
271 self.close()
271 self.close()
272
272
273 def callcommand(self, command, args):
273 def callcommand(self, command, args):
274 if self._sent:
274 if self._sent:
275 raise error.ProgrammingError(
275 raise error.ProgrammingError(
276 b'callcommand() cannot be used after sendcommands()'
276 b'callcommand() cannot be used after sendcommands()'
277 )
277 )
278
278
279 if self._closed:
279 if self._closed:
280 raise error.ProgrammingError(
280 raise error.ProgrammingError(
281 b'callcommand() cannot be used after close()'
281 b'callcommand() cannot be used after close()'
282 )
282 )
283
283
284 # We don't need to support anything fancy. Just call the named
284 # We don't need to support anything fancy. Just call the named
285 # method on the peer and return a resolved future.
285 # method on the peer and return a resolved future.
286 fn = getattr(self._peer, pycompat.sysstr(command))
286 fn = getattr(self._peer, pycompat.sysstr(command))
287
287
288 f = futures.Future()
288 f = futures.Future()
289
289
290 try:
290 try:
291 result = fn(**pycompat.strkwargs(args))
291 result = fn(**pycompat.strkwargs(args))
292 except Exception:
292 except Exception:
293 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
293 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
294 else:
294 else:
295 f.set_result(result)
295 f.set_result(result)
296
296
297 return f
297 return f
298
298
299 def sendcommands(self):
299 def sendcommands(self):
300 self._sent = True
300 self._sent = True
301
301
302 def close(self):
302 def close(self):
303 self._closed = True
303 self._closed = True
304
304
305
305
306 @interfaceutil.implementer(repository.ipeercommands)
306 @interfaceutil.implementer(repository.ipeercommands)
307 class localpeer(repository.peer):
307 class localpeer(repository.peer):
308 '''peer for a local repo; reflects only the most recent API'''
308 '''peer for a local repo; reflects only the most recent API'''
309
309
310 def __init__(self, repo, caps=None, path=None):
310 def __init__(self, repo, caps=None, path=None):
311 super(localpeer, self).__init__(repo.ui, path=path)
311 super(localpeer, self).__init__(repo.ui, path=path)
312
312
313 if caps is None:
313 if caps is None:
314 caps = moderncaps.copy()
314 caps = moderncaps.copy()
315 self._repo = repo.filtered(b'served')
315 self._repo = repo.filtered(b'served')
316
316
317 if repo._wanted_sidedata:
317 if repo._wanted_sidedata:
318 formatted = bundle2.format_remote_wanted_sidedata(repo)
318 formatted = bundle2.format_remote_wanted_sidedata(repo)
319 caps.add(b'exp-wanted-sidedata=' + formatted)
319 caps.add(b'exp-wanted-sidedata=' + formatted)
320
320
321 self._caps = repo._restrictcapabilities(caps)
321 self._caps = repo._restrictcapabilities(caps)
322
322
323 # Begin of _basepeer interface.
323 # Begin of _basepeer interface.
324
324
325 def url(self):
325 def url(self):
326 return self._repo.url()
326 return self._repo.url()
327
327
328 def local(self):
328 def local(self):
329 return self._repo
329 return self._repo
330
330
331 def canpush(self):
331 def canpush(self):
332 return True
332 return True
333
333
334 def close(self):
334 def close(self):
335 self._repo.close()
335 self._repo.close()
336
336
337 # End of _basepeer interface.
337 # End of _basepeer interface.
338
338
339 # Begin of _basewirecommands interface.
339 # Begin of _basewirecommands interface.
340
340
341 def branchmap(self):
341 def branchmap(self):
342 return self._repo.branchmap()
342 return self._repo.branchmap()
343
343
344 def capabilities(self):
344 def capabilities(self):
345 return self._caps
345 return self._caps
346
346
347 def clonebundles(self):
347 def clonebundles(self):
348 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
348 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
349
349
350 def debugwireargs(self, one, two, three=None, four=None, five=None):
350 def debugwireargs(self, one, two, three=None, four=None, five=None):
351 """Used to test argument passing over the wire"""
351 """Used to test argument passing over the wire"""
352 return b"%s %s %s %s %s" % (
352 return b"%s %s %s %s %s" % (
353 one,
353 one,
354 two,
354 two,
355 pycompat.bytestr(three),
355 pycompat.bytestr(three),
356 pycompat.bytestr(four),
356 pycompat.bytestr(four),
357 pycompat.bytestr(five),
357 pycompat.bytestr(five),
358 )
358 )
359
359
360 def getbundle(
360 def getbundle(
361 self,
361 self,
362 source,
362 source,
363 heads=None,
363 heads=None,
364 common=None,
364 common=None,
365 bundlecaps=None,
365 bundlecaps=None,
366 remote_sidedata=None,
366 remote_sidedata=None,
367 **kwargs
367 **kwargs
368 ):
368 ):
369 chunks = exchange.getbundlechunks(
369 chunks = exchange.getbundlechunks(
370 self._repo,
370 self._repo,
371 source,
371 source,
372 heads=heads,
372 heads=heads,
373 common=common,
373 common=common,
374 bundlecaps=bundlecaps,
374 bundlecaps=bundlecaps,
375 remote_sidedata=remote_sidedata,
375 remote_sidedata=remote_sidedata,
376 **kwargs
376 **kwargs
377 )[1]
377 )[1]
378 cb = util.chunkbuffer(chunks)
378 cb = util.chunkbuffer(chunks)
379
379
380 if exchange.bundle2requested(bundlecaps):
380 if exchange.bundle2requested(bundlecaps):
381 # When requesting a bundle2, getbundle returns a stream to make the
381 # When requesting a bundle2, getbundle returns a stream to make the
382 # wire level function happier. We need to build a proper object
382 # wire level function happier. We need to build a proper object
383 # from it in local peer.
383 # from it in local peer.
384 return bundle2.getunbundler(self.ui, cb)
384 return bundle2.getunbundler(self.ui, cb)
385 else:
385 else:
386 return changegroup.getunbundler(b'01', cb, None)
386 return changegroup.getunbundler(b'01', cb, None)
387
387
388 def heads(self):
388 def heads(self):
389 return self._repo.heads()
389 return self._repo.heads()
390
390
391 def known(self, nodes):
391 def known(self, nodes):
392 return self._repo.known(nodes)
392 return self._repo.known(nodes)
393
393
394 def listkeys(self, namespace):
394 def listkeys(self, namespace):
395 return self._repo.listkeys(namespace)
395 return self._repo.listkeys(namespace)
396
396
397 def lookup(self, key):
397 def lookup(self, key):
398 return self._repo.lookup(key)
398 return self._repo.lookup(key)
399
399
400 def pushkey(self, namespace, key, old, new):
400 def pushkey(self, namespace, key, old, new):
401 return self._repo.pushkey(namespace, key, old, new)
401 return self._repo.pushkey(namespace, key, old, new)
402
402
403 def stream_out(self):
403 def stream_out(self):
404 raise error.Abort(_(b'cannot perform stream clone against local peer'))
404 raise error.Abort(_(b'cannot perform stream clone against local peer'))
405
405
406 def unbundle(self, bundle, heads, url):
406 def unbundle(self, bundle, heads, url):
407 """apply a bundle on a repo
407 """apply a bundle on a repo
408
408
409 This function handles the repo locking itself."""
409 This function handles the repo locking itself."""
410 try:
410 try:
411 try:
411 try:
412 bundle = exchange.readbundle(self.ui, bundle, None)
412 bundle = exchange.readbundle(self.ui, bundle, None)
413 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
413 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
414 if util.safehasattr(ret, b'getchunks'):
414 if util.safehasattr(ret, b'getchunks'):
415 # This is a bundle20 object, turn it into an unbundler.
415 # This is a bundle20 object, turn it into an unbundler.
416 # This little dance should be dropped eventually when the
416 # This little dance should be dropped eventually when the
417 # API is finally improved.
417 # API is finally improved.
418 stream = util.chunkbuffer(ret.getchunks())
418 stream = util.chunkbuffer(ret.getchunks())
419 ret = bundle2.getunbundler(self.ui, stream)
419 ret = bundle2.getunbundler(self.ui, stream)
420 return ret
420 return ret
421 except Exception as exc:
421 except Exception as exc:
422 # If the exception contains output salvaged from a bundle2
422 # If the exception contains output salvaged from a bundle2
423 # reply, we need to make sure it is printed before continuing
423 # reply, we need to make sure it is printed before continuing
424 # to fail. So we build a bundle2 with such output and consume
424 # to fail. So we build a bundle2 with such output and consume
425 # it directly.
425 # it directly.
426 #
426 #
427 # This is not very elegant but allows a "simple" solution for
427 # This is not very elegant but allows a "simple" solution for
428 # issue4594
428 # issue4594
429 output = getattr(exc, '_bundle2salvagedoutput', ())
429 output = getattr(exc, '_bundle2salvagedoutput', ())
430 if output:
430 if output:
431 bundler = bundle2.bundle20(self._repo.ui)
431 bundler = bundle2.bundle20(self._repo.ui)
432 for out in output:
432 for out in output:
433 bundler.addpart(out)
433 bundler.addpart(out)
434 stream = util.chunkbuffer(bundler.getchunks())
434 stream = util.chunkbuffer(bundler.getchunks())
435 b = bundle2.getunbundler(self.ui, stream)
435 b = bundle2.getunbundler(self.ui, stream)
436 bundle2.processbundle(self._repo, b)
436 bundle2.processbundle(self._repo, b)
437 raise
437 raise
438 except error.PushRaced as exc:
438 except error.PushRaced as exc:
439 raise error.ResponseError(
439 raise error.ResponseError(
440 _(b'push failed:'), stringutil.forcebytestr(exc)
440 _(b'push failed:'), stringutil.forcebytestr(exc)
441 )
441 )
442
442
443 # End of _basewirecommands interface.
443 # End of _basewirecommands interface.
444
444
445 # Begin of peer interface.
445 # Begin of peer interface.
446
446
447 def commandexecutor(self):
447 def commandexecutor(self):
448 return localcommandexecutor(self)
448 return localcommandexecutor(self)
449
449
450 # End of peer interface.
450 # End of peer interface.
451
451
452
452
453 @interfaceutil.implementer(repository.ipeerlegacycommands)
453 @interfaceutil.implementer(repository.ipeerlegacycommands)
454 class locallegacypeer(localpeer):
454 class locallegacypeer(localpeer):
455 """peer extension which implements legacy methods too; used for tests with
455 """peer extension which implements legacy methods too; used for tests with
456 restricted capabilities"""
456 restricted capabilities"""
457
457
458 def __init__(self, repo, path=None):
458 def __init__(self, repo, path=None):
459 super(locallegacypeer, self).__init__(repo, caps=legacycaps, path=path)
459 super(locallegacypeer, self).__init__(repo, caps=legacycaps, path=path)
460
460
461 # Begin of baselegacywirecommands interface.
461 # Begin of baselegacywirecommands interface.
462
462
463 def between(self, pairs):
463 def between(self, pairs):
464 return self._repo.between(pairs)
464 return self._repo.between(pairs)
465
465
466 def branches(self, nodes):
466 def branches(self, nodes):
467 return self._repo.branches(nodes)
467 return self._repo.branches(nodes)
468
468
469 def changegroup(self, nodes, source):
469 def changegroup(self, nodes, source):
470 outgoing = discovery.outgoing(
470 outgoing = discovery.outgoing(
471 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
471 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
472 )
472 )
473 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
473 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
474
474
475 def changegroupsubset(self, bases, heads, source):
475 def changegroupsubset(self, bases, heads, source):
476 outgoing = discovery.outgoing(
476 outgoing = discovery.outgoing(
477 self._repo, missingroots=bases, ancestorsof=heads
477 self._repo, missingroots=bases, ancestorsof=heads
478 )
478 )
479 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
479 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
480
480
481 # End of baselegacywirecommands interface.
481 # End of baselegacywirecommands interface.
482
482
483
483
484 # Functions receiving (ui, features) that extensions can register to impact
484 # Functions receiving (ui, features) that extensions can register to impact
485 # the ability to load repositories with custom requirements. Only
485 # the ability to load repositories with custom requirements. Only
486 # functions defined in loaded extensions are called.
486 # functions defined in loaded extensions are called.
487 #
487 #
488 # The function receives a set of requirement strings that the repository
488 # The function receives a set of requirement strings that the repository
489 # is capable of opening. Functions will typically add elements to the
489 # is capable of opening. Functions will typically add elements to the
490 # set to reflect that the extension knows how to handle that requirements.
490 # set to reflect that the extension knows how to handle that requirements.
491 featuresetupfuncs = set()
491 featuresetupfuncs = set()
492
492
493
493
494 def _getsharedvfs(hgvfs, requirements):
494 def _getsharedvfs(hgvfs, requirements):
495 """returns the vfs object pointing to root of shared source
495 """returns the vfs object pointing to root of shared source
496 repo for a shared repository
496 repo for a shared repository
497
497
498 hgvfs is vfs pointing at .hg/ of current repo (shared one)
498 hgvfs is vfs pointing at .hg/ of current repo (shared one)
499 requirements is a set of requirements of current repo (shared one)
499 requirements is a set of requirements of current repo (shared one)
500 """
500 """
501 # The ``shared`` or ``relshared`` requirements indicate the
501 # The ``shared`` or ``relshared`` requirements indicate the
502 # store lives in the path contained in the ``.hg/sharedpath`` file.
502 # store lives in the path contained in the ``.hg/sharedpath`` file.
503 # This is an absolute path for ``shared`` and relative to
503 # This is an absolute path for ``shared`` and relative to
504 # ``.hg/`` for ``relshared``.
504 # ``.hg/`` for ``relshared``.
505 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
505 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
506 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
506 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
507 sharedpath = util.normpath(hgvfs.join(sharedpath))
507 sharedpath = util.normpath(hgvfs.join(sharedpath))
508
508
509 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
509 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
510
510
511 if not sharedvfs.exists():
511 if not sharedvfs.exists():
512 raise error.RepoError(
512 raise error.RepoError(
513 _(b'.hg/sharedpath points to nonexistent directory %s')
513 _(b'.hg/sharedpath points to nonexistent directory %s')
514 % sharedvfs.base
514 % sharedvfs.base
515 )
515 )
516 return sharedvfs
516 return sharedvfs
517
517
518
518
519 def _readrequires(vfs, allowmissing):
519 def _readrequires(vfs, allowmissing):
520 """reads the require file present at root of this vfs
520 """reads the require file present at root of this vfs
521 and return a set of requirements
521 and return a set of requirements
522
522
523 If allowmissing is True, we suppress FileNotFoundError if raised"""
523 If allowmissing is True, we suppress FileNotFoundError if raised"""
524 # requires file contains a newline-delimited list of
524 # requires file contains a newline-delimited list of
525 # features/capabilities the opener (us) must have in order to use
525 # features/capabilities the opener (us) must have in order to use
526 # the repository. This file was introduced in Mercurial 0.9.2,
526 # the repository. This file was introduced in Mercurial 0.9.2,
527 # which means very old repositories may not have one. We assume
527 # which means very old repositories may not have one. We assume
528 # a missing file translates to no requirements.
528 # a missing file translates to no requirements.
529 read = vfs.tryread if allowmissing else vfs.read
529 read = vfs.tryread if allowmissing else vfs.read
530 return set(read(b'requires').splitlines())
530 return set(read(b'requires').splitlines())
531
531
532
532
533 def makelocalrepository(baseui, path: bytes, intents=None):
533 def makelocalrepository(baseui, path: bytes, intents=None):
534 """Create a local repository object.
534 """Create a local repository object.
535
535
536 Given arguments needed to construct a local repository, this function
536 Given arguments needed to construct a local repository, this function
537 performs various early repository loading functionality (such as
537 performs various early repository loading functionality (such as
538 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
538 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
539 the repository can be opened, derives a type suitable for representing
539 the repository can be opened, derives a type suitable for representing
540 that repository, and returns an instance of it.
540 that repository, and returns an instance of it.
541
541
542 The returned object conforms to the ``repository.completelocalrepository``
542 The returned object conforms to the ``repository.completelocalrepository``
543 interface.
543 interface.
544
544
545 The repository type is derived by calling a series of factory functions
545 The repository type is derived by calling a series of factory functions
546 for each aspect/interface of the final repository. These are defined by
546 for each aspect/interface of the final repository. These are defined by
547 ``REPO_INTERFACES``.
547 ``REPO_INTERFACES``.
548
548
549 Each factory function is called to produce a type implementing a specific
549 Each factory function is called to produce a type implementing a specific
550 interface. The cumulative list of returned types will be combined into a
550 interface. The cumulative list of returned types will be combined into a
551 new type and that type will be instantiated to represent the local
551 new type and that type will be instantiated to represent the local
552 repository.
552 repository.
553
553
554 The factory functions each receive various state that may be consulted
554 The factory functions each receive various state that may be consulted
555 as part of deriving a type.
555 as part of deriving a type.
556
556
557 Extensions should wrap these factory functions to customize repository type
557 Extensions should wrap these factory functions to customize repository type
558 creation. Note that an extension's wrapped function may be called even if
558 creation. Note that an extension's wrapped function may be called even if
559 that extension is not loaded for the repo being constructed. Extensions
559 that extension is not loaded for the repo being constructed. Extensions
560 should check if their ``__name__`` appears in the
560 should check if their ``__name__`` appears in the
561 ``extensionmodulenames`` set passed to the factory function and no-op if
561 ``extensionmodulenames`` set passed to the factory function and no-op if
562 not.
562 not.
563 """
563 """
564 ui = baseui.copy()
564 ui = baseui.copy()
565 # Prevent copying repo configuration.
565 # Prevent copying repo configuration.
566 ui.copy = baseui.copy
566 ui.copy = baseui.copy
567
567
568 # Working directory VFS rooted at repository root.
568 # Working directory VFS rooted at repository root.
569 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
569 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
570
570
571 # Main VFS for .hg/ directory.
571 # Main VFS for .hg/ directory.
572 hgpath = wdirvfs.join(b'.hg')
572 hgpath = wdirvfs.join(b'.hg')
573 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
573 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
574 # Whether this repository is shared one or not
574 # Whether this repository is shared one or not
575 shared = False
575 shared = False
576 # If this repository is shared, vfs pointing to shared repo
576 # If this repository is shared, vfs pointing to shared repo
577 sharedvfs = None
577 sharedvfs = None
578
578
579 # The .hg/ path should exist and should be a directory. All other
579 # The .hg/ path should exist and should be a directory. All other
580 # cases are errors.
580 # cases are errors.
581 if not hgvfs.isdir():
581 if not hgvfs.isdir():
582 try:
582 try:
583 hgvfs.stat()
583 hgvfs.stat()
584 except FileNotFoundError:
584 except FileNotFoundError:
585 pass
585 pass
586 except ValueError as e:
586 except ValueError as e:
587 # Can be raised on Python 3.8 when path is invalid.
587 # Can be raised on Python 3.8 when path is invalid.
588 raise error.Abort(
588 raise error.Abort(
589 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
589 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
590 )
590 )
591
591
592 raise error.RepoError(_(b'repository %s not found') % path)
592 raise error.RepoError(_(b'repository %s not found') % path)
593
593
594 requirements = _readrequires(hgvfs, True)
594 requirements = _readrequires(hgvfs, True)
595 shared = (
595 shared = (
596 requirementsmod.SHARED_REQUIREMENT in requirements
596 requirementsmod.SHARED_REQUIREMENT in requirements
597 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
597 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
598 )
598 )
599 storevfs = None
599 storevfs = None
600 if shared:
600 if shared:
601 # This is a shared repo
601 # This is a shared repo
602 sharedvfs = _getsharedvfs(hgvfs, requirements)
602 sharedvfs = _getsharedvfs(hgvfs, requirements)
603 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
603 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
604 else:
604 else:
605 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
605 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
606
606
607 # if .hg/requires contains the sharesafe requirement, it means
607 # if .hg/requires contains the sharesafe requirement, it means
608 # there exists a `.hg/store/requires` too and we should read it
608 # there exists a `.hg/store/requires` too and we should read it
609 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
609 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
610 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
610 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
611 # is not present, refer checkrequirementscompat() for that
611 # is not present, refer checkrequirementscompat() for that
612 #
612 #
613 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
613 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
614 # repository was shared the old way. We check the share source .hg/requires
614 # repository was shared the old way. We check the share source .hg/requires
615 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
615 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
616 # to be reshared
616 # to be reshared
617 hint = _(b"see `hg help config.format.use-share-safe` for more information")
617 hint = _(b"see `hg help config.format.use-share-safe` for more information")
618 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
618 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
619 if (
619 if (
620 shared
620 shared
621 and requirementsmod.SHARESAFE_REQUIREMENT
621 and requirementsmod.SHARESAFE_REQUIREMENT
622 not in _readrequires(sharedvfs, True)
622 not in _readrequires(sharedvfs, True)
623 ):
623 ):
624 mismatch_warn = ui.configbool(
624 mismatch_warn = ui.configbool(
625 b'share', b'safe-mismatch.source-not-safe.warn'
625 b'share', b'safe-mismatch.source-not-safe.warn'
626 )
626 )
627 mismatch_config = ui.config(
627 mismatch_config = ui.config(
628 b'share', b'safe-mismatch.source-not-safe'
628 b'share', b'safe-mismatch.source-not-safe'
629 )
629 )
630 mismatch_verbose_upgrade = ui.configbool(
630 mismatch_verbose_upgrade = ui.configbool(
631 b'share', b'safe-mismatch.source-not-safe:verbose-upgrade'
631 b'share', b'safe-mismatch.source-not-safe:verbose-upgrade'
632 )
632 )
633 if mismatch_config in (
633 if mismatch_config in (
634 b'downgrade-allow',
634 b'downgrade-allow',
635 b'allow',
635 b'allow',
636 b'downgrade-abort',
636 b'downgrade-abort',
637 ):
637 ):
638 # prevent cyclic import localrepo -> upgrade -> localrepo
638 # prevent cyclic import localrepo -> upgrade -> localrepo
639 from . import upgrade
639 from . import upgrade
640
640
641 upgrade.downgrade_share_to_non_safe(
641 upgrade.downgrade_share_to_non_safe(
642 ui,
642 ui,
643 hgvfs,
643 hgvfs,
644 sharedvfs,
644 sharedvfs,
645 requirements,
645 requirements,
646 mismatch_config,
646 mismatch_config,
647 mismatch_warn,
647 mismatch_warn,
648 mismatch_verbose_upgrade,
648 mismatch_verbose_upgrade,
649 )
649 )
650 elif mismatch_config == b'abort':
650 elif mismatch_config == b'abort':
651 raise error.Abort(
651 raise error.Abort(
652 _(b"share source does not support share-safe requirement"),
652 _(b"share source does not support share-safe requirement"),
653 hint=hint,
653 hint=hint,
654 )
654 )
655 else:
655 else:
656 raise error.Abort(
656 raise error.Abort(
657 _(
657 _(
658 b"share-safe mismatch with source.\nUnrecognized"
658 b"share-safe mismatch with source.\nUnrecognized"
659 b" value '%s' of `share.safe-mismatch.source-not-safe`"
659 b" value '%s' of `share.safe-mismatch.source-not-safe`"
660 b" set."
660 b" set."
661 )
661 )
662 % mismatch_config,
662 % mismatch_config,
663 hint=hint,
663 hint=hint,
664 )
664 )
665 else:
665 else:
666 requirements |= _readrequires(storevfs, False)
666 requirements |= _readrequires(storevfs, False)
667 elif shared:
667 elif shared:
668 sourcerequires = _readrequires(sharedvfs, False)
668 sourcerequires = _readrequires(sharedvfs, False)
669 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
669 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
670 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
670 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
671 mismatch_warn = ui.configbool(
671 mismatch_warn = ui.configbool(
672 b'share', b'safe-mismatch.source-safe.warn'
672 b'share', b'safe-mismatch.source-safe.warn'
673 )
673 )
674 mismatch_verbose_upgrade = ui.configbool(
674 mismatch_verbose_upgrade = ui.configbool(
675 b'share', b'safe-mismatch.source-safe:verbose-upgrade'
675 b'share', b'safe-mismatch.source-safe:verbose-upgrade'
676 )
676 )
677 if mismatch_config in (
677 if mismatch_config in (
678 b'upgrade-allow',
678 b'upgrade-allow',
679 b'allow',
679 b'allow',
680 b'upgrade-abort',
680 b'upgrade-abort',
681 ):
681 ):
682 # prevent cyclic import localrepo -> upgrade -> localrepo
682 # prevent cyclic import localrepo -> upgrade -> localrepo
683 from . import upgrade
683 from . import upgrade
684
684
685 upgrade.upgrade_share_to_safe(
685 upgrade.upgrade_share_to_safe(
686 ui,
686 ui,
687 hgvfs,
687 hgvfs,
688 storevfs,
688 storevfs,
689 requirements,
689 requirements,
690 mismatch_config,
690 mismatch_config,
691 mismatch_warn,
691 mismatch_warn,
692 mismatch_verbose_upgrade,
692 mismatch_verbose_upgrade,
693 )
693 )
694 elif mismatch_config == b'abort':
694 elif mismatch_config == b'abort':
695 raise error.Abort(
695 raise error.Abort(
696 _(
696 _(
697 b'version mismatch: source uses share-safe'
697 b'version mismatch: source uses share-safe'
698 b' functionality while the current share does not'
698 b' functionality while the current share does not'
699 ),
699 ),
700 hint=hint,
700 hint=hint,
701 )
701 )
702 else:
702 else:
703 raise error.Abort(
703 raise error.Abort(
704 _(
704 _(
705 b"share-safe mismatch with source.\nUnrecognized"
705 b"share-safe mismatch with source.\nUnrecognized"
706 b" value '%s' of `share.safe-mismatch.source-safe` set."
706 b" value '%s' of `share.safe-mismatch.source-safe` set."
707 )
707 )
708 % mismatch_config,
708 % mismatch_config,
709 hint=hint,
709 hint=hint,
710 )
710 )
711
711
712 # The .hg/hgrc file may load extensions or contain config options
712 # The .hg/hgrc file may load extensions or contain config options
713 # that influence repository construction. Attempt to load it and
713 # that influence repository construction. Attempt to load it and
714 # process any new extensions that it may have pulled in.
714 # process any new extensions that it may have pulled in.
715 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
715 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
716 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
716 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
717 extensions.loadall(ui)
717 extensions.loadall(ui)
718 extensions.populateui(ui)
718 extensions.populateui(ui)
719
719
720 # Set of module names of extensions loaded for this repository.
720 # Set of module names of extensions loaded for this repository.
721 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
721 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
722
722
723 supportedrequirements = gathersupportedrequirements(ui)
723 supportedrequirements = gathersupportedrequirements(ui)
724
724
725 # We first validate the requirements are known.
725 # We first validate the requirements are known.
726 ensurerequirementsrecognized(requirements, supportedrequirements)
726 ensurerequirementsrecognized(requirements, supportedrequirements)
727
727
728 # Then we validate that the known set is reasonable to use together.
728 # Then we validate that the known set is reasonable to use together.
729 ensurerequirementscompatible(ui, requirements)
729 ensurerequirementscompatible(ui, requirements)
730
730
731 # TODO there are unhandled edge cases related to opening repositories with
731 # TODO there are unhandled edge cases related to opening repositories with
732 # shared storage. If storage is shared, we should also test for requirements
732 # shared storage. If storage is shared, we should also test for requirements
733 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
733 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
734 # that repo, as that repo may load extensions needed to open it. This is a
734 # that repo, as that repo may load extensions needed to open it. This is a
735 # bit complicated because we don't want the other hgrc to overwrite settings
735 # bit complicated because we don't want the other hgrc to overwrite settings
736 # in this hgrc.
736 # in this hgrc.
737 #
737 #
738 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
738 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
739 # file when sharing repos. But if a requirement is added after the share is
739 # file when sharing repos. But if a requirement is added after the share is
740 # performed, thereby introducing a new requirement for the opener, we may
740 # performed, thereby introducing a new requirement for the opener, we may
741 # will not see that and could encounter a run-time error interacting with
741 # will not see that and could encounter a run-time error interacting with
742 # that shared store since it has an unknown-to-us requirement.
742 # that shared store since it has an unknown-to-us requirement.
743
743
744 # At this point, we know we should be capable of opening the repository.
744 # At this point, we know we should be capable of opening the repository.
745 # Now get on with doing that.
745 # Now get on with doing that.
746
746
747 features = set()
747 features = set()
748
748
749 # The "store" part of the repository holds versioned data. How it is
749 # The "store" part of the repository holds versioned data. How it is
750 # accessed is determined by various requirements. If `shared` or
750 # accessed is determined by various requirements. If `shared` or
751 # `relshared` requirements are present, this indicates current repository
751 # `relshared` requirements are present, this indicates current repository
752 # is a share and store exists in path mentioned in `.hg/sharedpath`
752 # is a share and store exists in path mentioned in `.hg/sharedpath`
753 if shared:
753 if shared:
754 storebasepath = sharedvfs.base
754 storebasepath = sharedvfs.base
755 cachepath = sharedvfs.join(b'cache')
755 cachepath = sharedvfs.join(b'cache')
756 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
756 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
757 else:
757 else:
758 storebasepath = hgvfs.base
758 storebasepath = hgvfs.base
759 cachepath = hgvfs.join(b'cache')
759 cachepath = hgvfs.join(b'cache')
760 wcachepath = hgvfs.join(b'wcache')
760 wcachepath = hgvfs.join(b'wcache')
761
761
762 # The store has changed over time and the exact layout is dictated by
762 # The store has changed over time and the exact layout is dictated by
763 # requirements. The store interface abstracts differences across all
763 # requirements. The store interface abstracts differences across all
764 # of them.
764 # of them.
765 store = makestore(
765 store = makestore(
766 requirements,
766 requirements,
767 storebasepath,
767 storebasepath,
768 lambda base: vfsmod.vfs(base, cacheaudited=True),
768 lambda base: vfsmod.vfs(base, cacheaudited=True),
769 )
769 )
770 hgvfs.createmode = store.createmode
770 hgvfs.createmode = store.createmode
771
771
772 storevfs = store.vfs
772 storevfs = store.vfs
773 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
773 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
774
774
775 if (
775 if (
776 requirementsmod.REVLOGV2_REQUIREMENT in requirements
776 requirementsmod.REVLOGV2_REQUIREMENT in requirements
777 or requirementsmod.CHANGELOGV2_REQUIREMENT in requirements
777 or requirementsmod.CHANGELOGV2_REQUIREMENT in requirements
778 ):
778 ):
779 features.add(repository.REPO_FEATURE_SIDE_DATA)
779 features.add(repository.REPO_FEATURE_SIDE_DATA)
780 # the revlogv2 docket introduced race condition that we need to fix
780 # the revlogv2 docket introduced race condition that we need to fix
781 features.discard(repository.REPO_FEATURE_STREAM_CLONE)
781 features.discard(repository.REPO_FEATURE_STREAM_CLONE)
782
782
783 # The cache vfs is used to manage cache files.
783 # The cache vfs is used to manage cache files.
784 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
784 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
785 cachevfs.createmode = store.createmode
785 cachevfs.createmode = store.createmode
786 # The cache vfs is used to manage cache files related to the working copy
786 # The cache vfs is used to manage cache files related to the working copy
787 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
787 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
788 wcachevfs.createmode = store.createmode
788 wcachevfs.createmode = store.createmode
789
789
790 # Now resolve the type for the repository object. We do this by repeatedly
790 # Now resolve the type for the repository object. We do this by repeatedly
791 # calling a factory function to produces types for specific aspects of the
791 # calling a factory function to produces types for specific aspects of the
792 # repo's operation. The aggregate returned types are used as base classes
792 # repo's operation. The aggregate returned types are used as base classes
793 # for a dynamically-derived type, which will represent our new repository.
793 # for a dynamically-derived type, which will represent our new repository.
794
794
795 bases = []
795 bases = []
796 extrastate = {}
796 extrastate = {}
797
797
798 for iface, fn in REPO_INTERFACES:
798 for iface, fn in REPO_INTERFACES:
799 # We pass all potentially useful state to give extensions tons of
799 # We pass all potentially useful state to give extensions tons of
800 # flexibility.
800 # flexibility.
801 typ = fn()(
801 typ = fn()(
802 ui=ui,
802 ui=ui,
803 intents=intents,
803 intents=intents,
804 requirements=requirements,
804 requirements=requirements,
805 features=features,
805 features=features,
806 wdirvfs=wdirvfs,
806 wdirvfs=wdirvfs,
807 hgvfs=hgvfs,
807 hgvfs=hgvfs,
808 store=store,
808 store=store,
809 storevfs=storevfs,
809 storevfs=storevfs,
810 storeoptions=storevfs.options,
810 storeoptions=storevfs.options,
811 cachevfs=cachevfs,
811 cachevfs=cachevfs,
812 wcachevfs=wcachevfs,
812 wcachevfs=wcachevfs,
813 extensionmodulenames=extensionmodulenames,
813 extensionmodulenames=extensionmodulenames,
814 extrastate=extrastate,
814 extrastate=extrastate,
815 baseclasses=bases,
815 baseclasses=bases,
816 )
816 )
817
817
818 if not isinstance(typ, type):
818 if not isinstance(typ, type):
819 raise error.ProgrammingError(
819 raise error.ProgrammingError(
820 b'unable to construct type for %s' % iface
820 b'unable to construct type for %s' % iface
821 )
821 )
822
822
823 bases.append(typ)
823 bases.append(typ)
824
824
825 # type() allows you to use characters in type names that wouldn't be
825 # type() allows you to use characters in type names that wouldn't be
826 # recognized as Python symbols in source code. We abuse that to add
826 # recognized as Python symbols in source code. We abuse that to add
827 # rich information about our constructed repo.
827 # rich information about our constructed repo.
828 name = pycompat.sysstr(
828 name = pycompat.sysstr(
829 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
829 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
830 )
830 )
831
831
832 cls = type(name, tuple(bases), {})
832 cls = type(name, tuple(bases), {})
833
833
834 return cls(
834 return cls(
835 baseui=baseui,
835 baseui=baseui,
836 ui=ui,
836 ui=ui,
837 origroot=path,
837 origroot=path,
838 wdirvfs=wdirvfs,
838 wdirvfs=wdirvfs,
839 hgvfs=hgvfs,
839 hgvfs=hgvfs,
840 requirements=requirements,
840 requirements=requirements,
841 supportedrequirements=supportedrequirements,
841 supportedrequirements=supportedrequirements,
842 sharedpath=storebasepath,
842 sharedpath=storebasepath,
843 store=store,
843 store=store,
844 cachevfs=cachevfs,
844 cachevfs=cachevfs,
845 wcachevfs=wcachevfs,
845 wcachevfs=wcachevfs,
846 features=features,
846 features=features,
847 intents=intents,
847 intents=intents,
848 )
848 )
849
849
850
850
851 def loadhgrc(
851 def loadhgrc(
852 ui,
852 ui,
853 wdirvfs: vfsmod.vfs,
853 wdirvfs: vfsmod.vfs,
854 hgvfs: vfsmod.vfs,
854 hgvfs: vfsmod.vfs,
855 requirements,
855 requirements,
856 sharedvfs: Optional[vfsmod.vfs] = None,
856 sharedvfs: Optional[vfsmod.vfs] = None,
857 ):
857 ):
858 """Load hgrc files/content into a ui instance.
858 """Load hgrc files/content into a ui instance.
859
859
860 This is called during repository opening to load any additional
860 This is called during repository opening to load any additional
861 config files or settings relevant to the current repository.
861 config files or settings relevant to the current repository.
862
862
863 Returns a bool indicating whether any additional configs were loaded.
863 Returns a bool indicating whether any additional configs were loaded.
864
864
865 Extensions should monkeypatch this function to modify how per-repo
865 Extensions should monkeypatch this function to modify how per-repo
866 configs are loaded. For example, an extension may wish to pull in
866 configs are loaded. For example, an extension may wish to pull in
867 configs from alternate files or sources.
867 configs from alternate files or sources.
868
868
869 sharedvfs is vfs object pointing to source repo if the current one is a
869 sharedvfs is vfs object pointing to source repo if the current one is a
870 shared one
870 shared one
871 """
871 """
872 if not rcutil.use_repo_hgrc():
872 if not rcutil.use_repo_hgrc():
873 return False
873 return False
874
874
875 ret = False
875 ret = False
876 # first load config from shared source if we has to
876 # first load config from shared source if we has to
877 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
877 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
878 try:
878 try:
879 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
879 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
880 ret = True
880 ret = True
881 except IOError:
881 except IOError:
882 pass
882 pass
883
883
884 try:
884 try:
885 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
885 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
886 ret = True
886 ret = True
887 except IOError:
887 except IOError:
888 pass
888 pass
889
889
890 try:
890 try:
891 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
891 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
892 ret = True
892 ret = True
893 except IOError:
893 except IOError:
894 pass
894 pass
895
895
896 return ret
896 return ret
897
897
898
898
899 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
899 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
900 """Perform additional actions after .hg/hgrc is loaded.
900 """Perform additional actions after .hg/hgrc is loaded.
901
901
902 This function is called during repository loading immediately after
902 This function is called during repository loading immediately after
903 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
903 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
904
904
905 The function can be used to validate configs, automatically add
905 The function can be used to validate configs, automatically add
906 options (including extensions) based on requirements, etc.
906 options (including extensions) based on requirements, etc.
907 """
907 """
908
908
909 # Map of requirements to list of extensions to load automatically when
909 # Map of requirements to list of extensions to load automatically when
910 # requirement is present.
910 # requirement is present.
911 autoextensions = {
911 autoextensions = {
912 b'git': [b'git'],
912 b'git': [b'git'],
913 b'largefiles': [b'largefiles'],
913 b'largefiles': [b'largefiles'],
914 b'lfs': [b'lfs'],
914 b'lfs': [b'lfs'],
915 }
915 }
916
916
917 for requirement, names in sorted(autoextensions.items()):
917 for requirement, names in sorted(autoextensions.items()):
918 if requirement not in requirements:
918 if requirement not in requirements:
919 continue
919 continue
920
920
921 for name in names:
921 for name in names:
922 if not ui.hasconfig(b'extensions', name):
922 if not ui.hasconfig(b'extensions', name):
923 ui.setconfig(b'extensions', name, b'', source=b'autoload')
923 ui.setconfig(b'extensions', name, b'', source=b'autoload')
924
924
925
925
926 def gathersupportedrequirements(ui):
926 def gathersupportedrequirements(ui):
927 """Determine the complete set of recognized requirements."""
927 """Determine the complete set of recognized requirements."""
928 # Start with all requirements supported by this file.
928 # Start with all requirements supported by this file.
929 supported = set(localrepository._basesupported)
929 supported = set(localrepository._basesupported)
930
930
931 # Execute ``featuresetupfuncs`` entries if they belong to an extension
931 # Execute ``featuresetupfuncs`` entries if they belong to an extension
932 # relevant to this ui instance.
932 # relevant to this ui instance.
933 modules = {m.__name__ for n, m in extensions.extensions(ui)}
933 modules = {m.__name__ for n, m in extensions.extensions(ui)}
934
934
935 for fn in featuresetupfuncs:
935 for fn in featuresetupfuncs:
936 if fn.__module__ in modules:
936 if fn.__module__ in modules:
937 fn(ui, supported)
937 fn(ui, supported)
938
938
939 # Add derived requirements from registered compression engines.
939 # Add derived requirements from registered compression engines.
940 for name in util.compengines:
940 for name in util.compengines:
941 engine = util.compengines[name]
941 engine = util.compengines[name]
942 if engine.available() and engine.revlogheader():
942 if engine.available() and engine.revlogheader():
943 supported.add(b'exp-compression-%s' % name)
943 supported.add(b'exp-compression-%s' % name)
944 if engine.name() == b'zstd':
944 if engine.name() == b'zstd':
945 supported.add(requirementsmod.REVLOG_COMPRESSION_ZSTD)
945 supported.add(requirementsmod.REVLOG_COMPRESSION_ZSTD)
946
946
947 return supported
947 return supported
948
948
949
949
950 def ensurerequirementsrecognized(requirements, supported):
950 def ensurerequirementsrecognized(requirements, supported):
951 """Validate that a set of local requirements is recognized.
951 """Validate that a set of local requirements is recognized.
952
952
953 Receives a set of requirements. Raises an ``error.RepoError`` if there
953 Receives a set of requirements. Raises an ``error.RepoError`` if there
954 exists any requirement in that set that currently loaded code doesn't
954 exists any requirement in that set that currently loaded code doesn't
955 recognize.
955 recognize.
956
956
957 Returns a set of supported requirements.
957 Returns a set of supported requirements.
958 """
958 """
959 missing = set()
959 missing = set()
960
960
961 for requirement in requirements:
961 for requirement in requirements:
962 if requirement in supported:
962 if requirement in supported:
963 continue
963 continue
964
964
965 if not requirement or not requirement[0:1].isalnum():
965 if not requirement or not requirement[0:1].isalnum():
966 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
966 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
967
967
968 missing.add(requirement)
968 missing.add(requirement)
969
969
970 if missing:
970 if missing:
971 raise error.RequirementError(
971 raise error.RequirementError(
972 _(b'repository requires features unknown to this Mercurial: %s')
972 _(b'repository requires features unknown to this Mercurial: %s')
973 % b' '.join(sorted(missing)),
973 % b' '.join(sorted(missing)),
974 hint=_(
974 hint=_(
975 b'see https://mercurial-scm.org/wiki/MissingRequirement '
975 b'see https://mercurial-scm.org/wiki/MissingRequirement '
976 b'for more information'
976 b'for more information'
977 ),
977 ),
978 )
978 )
979
979
980
980
981 def ensurerequirementscompatible(ui, requirements):
981 def ensurerequirementscompatible(ui, requirements):
982 """Validates that a set of recognized requirements is mutually compatible.
982 """Validates that a set of recognized requirements is mutually compatible.
983
983
984 Some requirements may not be compatible with others or require
984 Some requirements may not be compatible with others or require
985 config options that aren't enabled. This function is called during
985 config options that aren't enabled. This function is called during
986 repository opening to ensure that the set of requirements needed
986 repository opening to ensure that the set of requirements needed
987 to open a repository is sane and compatible with config options.
987 to open a repository is sane and compatible with config options.
988
988
989 Extensions can monkeypatch this function to perform additional
989 Extensions can monkeypatch this function to perform additional
990 checking.
990 checking.
991
991
992 ``error.RepoError`` should be raised on failure.
992 ``error.RepoError`` should be raised on failure.
993 """
993 """
994 if (
994 if (
995 requirementsmod.SPARSE_REQUIREMENT in requirements
995 requirementsmod.SPARSE_REQUIREMENT in requirements
996 and not sparse.enabled
996 and not sparse.enabled
997 ):
997 ):
998 raise error.RepoError(
998 raise error.RepoError(
999 _(
999 _(
1000 b'repository is using sparse feature but '
1000 b'repository is using sparse feature but '
1001 b'sparse is not enabled; enable the '
1001 b'sparse is not enabled; enable the '
1002 b'"sparse" extensions to access'
1002 b'"sparse" extensions to access'
1003 )
1003 )
1004 )
1004 )
1005
1005
1006
1006
1007 def makestore(requirements, path, vfstype):
1007 def makestore(requirements, path, vfstype):
1008 """Construct a storage object for a repository."""
1008 """Construct a storage object for a repository."""
1009 if requirementsmod.STORE_REQUIREMENT in requirements:
1009 if requirementsmod.STORE_REQUIREMENT in requirements:
1010 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
1010 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
1011 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
1011 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
1012 return storemod.fncachestore(path, vfstype, dotencode)
1012 return storemod.fncachestore(path, vfstype, dotencode)
1013
1013
1014 return storemod.encodedstore(path, vfstype)
1014 return storemod.encodedstore(path, vfstype)
1015
1015
1016 return storemod.basicstore(path, vfstype)
1016 return storemod.basicstore(path, vfstype)
1017
1017
1018
1018
1019 def resolvestorevfsoptions(ui, requirements, features):
1019 def resolvestorevfsoptions(ui, requirements, features):
1020 """Resolve the options to pass to the store vfs opener.
1020 """Resolve the options to pass to the store vfs opener.
1021
1021
1022 The returned dict is used to influence behavior of the storage layer.
1022 The returned dict is used to influence behavior of the storage layer.
1023 """
1023 """
1024 options = {}
1024 options = {}
1025
1025
1026 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
1026 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
1027 options[b'treemanifest'] = True
1027 options[b'treemanifest'] = True
1028
1028
1029 # experimental config: format.manifestcachesize
1029 # experimental config: format.manifestcachesize
1030 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
1030 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
1031 if manifestcachesize is not None:
1031 if manifestcachesize is not None:
1032 options[b'manifestcachesize'] = manifestcachesize
1032 options[b'manifestcachesize'] = manifestcachesize
1033
1033
1034 # In the absence of another requirement superseding a revlog-related
1034 # In the absence of another requirement superseding a revlog-related
1035 # requirement, we have to assume the repo is using revlog version 0.
1035 # requirement, we have to assume the repo is using revlog version 0.
1036 # This revlog format is super old and we don't bother trying to parse
1036 # This revlog format is super old and we don't bother trying to parse
1037 # opener options for it because those options wouldn't do anything
1037 # opener options for it because those options wouldn't do anything
1038 # meaningful on such old repos.
1038 # meaningful on such old repos.
1039 if (
1039 if (
1040 requirementsmod.REVLOGV1_REQUIREMENT in requirements
1040 requirementsmod.REVLOGV1_REQUIREMENT in requirements
1041 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
1041 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
1042 ):
1042 ):
1043 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
1043 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
1044 else: # explicitly mark repo as using revlogv0
1044 else: # explicitly mark repo as using revlogv0
1045 options[b'revlogv0'] = True
1045 options[b'revlogv0'] = True
1046
1046
1047 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
1047 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
1048 options[b'copies-storage'] = b'changeset-sidedata'
1048 options[b'copies-storage'] = b'changeset-sidedata'
1049 else:
1049 else:
1050 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1050 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1051 copiesextramode = (b'changeset-only', b'compatibility')
1051 copiesextramode = (b'changeset-only', b'compatibility')
1052 if writecopiesto in copiesextramode:
1052 if writecopiesto in copiesextramode:
1053 options[b'copies-storage'] = b'extra'
1053 options[b'copies-storage'] = b'extra'
1054
1054
1055 return options
1055 return options
1056
1056
1057
1057
1058 def resolverevlogstorevfsoptions(ui, requirements, features):
1058 def resolverevlogstorevfsoptions(ui, requirements, features):
1059 """Resolve opener options specific to revlogs."""
1059 """Resolve opener options specific to revlogs."""
1060
1060
1061 options = {}
1061 options = {}
1062 options[b'flagprocessors'] = {}
1062 options[b'flagprocessors'] = {}
1063
1063
1064 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1064 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1065 options[b'revlogv1'] = True
1065 options[b'revlogv1'] = True
1066 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1066 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1067 options[b'revlogv2'] = True
1067 options[b'revlogv2'] = True
1068 if requirementsmod.CHANGELOGV2_REQUIREMENT in requirements:
1068 if requirementsmod.CHANGELOGV2_REQUIREMENT in requirements:
1069 options[b'changelogv2'] = True
1069 options[b'changelogv2'] = True
1070 cmp_rank = ui.configbool(b'experimental', b'changelog-v2.compute-rank')
1070 cmp_rank = ui.configbool(b'experimental', b'changelog-v2.compute-rank')
1071 options[b'changelogv2.compute-rank'] = cmp_rank
1071 options[b'changelogv2.compute-rank'] = cmp_rank
1072
1072
1073 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1073 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1074 options[b'generaldelta'] = True
1074 options[b'generaldelta'] = True
1075
1075
1076 # experimental config: format.chunkcachesize
1076 # experimental config: format.chunkcachesize
1077 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1077 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1078 if chunkcachesize is not None:
1078 if chunkcachesize is not None:
1079 options[b'chunkcachesize'] = chunkcachesize
1079 options[b'chunkcachesize'] = chunkcachesize
1080
1080
1081 deltabothparents = ui.configbool(
1081 deltabothparents = ui.configbool(
1082 b'storage', b'revlog.optimize-delta-parent-choice'
1082 b'storage', b'revlog.optimize-delta-parent-choice'
1083 )
1083 )
1084 options[b'deltabothparents'] = deltabothparents
1084 options[b'deltabothparents'] = deltabothparents
1085 dps_cgds = ui.configint(
1085 dps_cgds = ui.configint(
1086 b'storage',
1086 b'storage',
1087 b'revlog.delta-parent-search.candidate-group-chunk-size',
1087 b'revlog.delta-parent-search.candidate-group-chunk-size',
1088 )
1088 )
1089 options[b'delta-parent-search.candidate-group-chunk-size'] = dps_cgds
1089 options[b'delta-parent-search.candidate-group-chunk-size'] = dps_cgds
1090 options[b'debug-delta'] = ui.configbool(b'debug', b'revlog.debug-delta')
1090 options[b'debug-delta'] = ui.configbool(b'debug', b'revlog.debug-delta')
1091
1091
1092 issue6528 = ui.configbool(b'storage', b'revlog.issue6528.fix-incoming')
1092 issue6528 = ui.configbool(b'storage', b'revlog.issue6528.fix-incoming')
1093 options[b'issue6528.fix-incoming'] = issue6528
1093 options[b'issue6528.fix-incoming'] = issue6528
1094
1094
1095 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1095 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1096 lazydeltabase = False
1096 lazydeltabase = False
1097 if lazydelta:
1097 if lazydelta:
1098 lazydeltabase = ui.configbool(
1098 lazydeltabase = ui.configbool(
1099 b'storage', b'revlog.reuse-external-delta-parent'
1099 b'storage', b'revlog.reuse-external-delta-parent'
1100 )
1100 )
1101 if lazydeltabase is None:
1101 if lazydeltabase is None:
1102 lazydeltabase = not scmutil.gddeltaconfig(ui)
1102 lazydeltabase = not scmutil.gddeltaconfig(ui)
1103 options[b'lazydelta'] = lazydelta
1103 options[b'lazydelta'] = lazydelta
1104 options[b'lazydeltabase'] = lazydeltabase
1104 options[b'lazydeltabase'] = lazydeltabase
1105
1105
1106 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1106 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1107 if 0 <= chainspan:
1107 if 0 <= chainspan:
1108 options[b'maxdeltachainspan'] = chainspan
1108 options[b'maxdeltachainspan'] = chainspan
1109
1109
1110 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1110 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1111 if mmapindexthreshold is not None:
1111 if mmapindexthreshold is not None:
1112 options[b'mmapindexthreshold'] = mmapindexthreshold
1112 options[b'mmapindexthreshold'] = mmapindexthreshold
1113
1113
1114 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1114 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1115 srdensitythres = float(
1115 srdensitythres = float(
1116 ui.config(b'experimental', b'sparse-read.density-threshold')
1116 ui.config(b'experimental', b'sparse-read.density-threshold')
1117 )
1117 )
1118 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1118 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1119 options[b'with-sparse-read'] = withsparseread
1119 options[b'with-sparse-read'] = withsparseread
1120 options[b'sparse-read-density-threshold'] = srdensitythres
1120 options[b'sparse-read-density-threshold'] = srdensitythres
1121 options[b'sparse-read-min-gap-size'] = srmingapsize
1121 options[b'sparse-read-min-gap-size'] = srmingapsize
1122
1122
1123 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1123 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1124 options[b'sparse-revlog'] = sparserevlog
1124 options[b'sparse-revlog'] = sparserevlog
1125 if sparserevlog:
1125 if sparserevlog:
1126 options[b'generaldelta'] = True
1126 options[b'generaldelta'] = True
1127
1127
1128 maxchainlen = None
1128 maxchainlen = None
1129 if sparserevlog:
1129 if sparserevlog:
1130 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1130 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1131 # experimental config: format.maxchainlen
1131 # experimental config: format.maxchainlen
1132 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1132 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1133 if maxchainlen is not None:
1133 if maxchainlen is not None:
1134 options[b'maxchainlen'] = maxchainlen
1134 options[b'maxchainlen'] = maxchainlen
1135
1135
1136 for r in requirements:
1136 for r in requirements:
1137 # we allow multiple compression engine requirement to co-exist because
1137 # we allow multiple compression engine requirement to co-exist because
1138 # strickly speaking, revlog seems to support mixed compression style.
1138 # strickly speaking, revlog seems to support mixed compression style.
1139 #
1139 #
1140 # The compression used for new entries will be "the last one"
1140 # The compression used for new entries will be "the last one"
1141 prefix = r.startswith
1141 prefix = r.startswith
1142 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1142 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1143 options[b'compengine'] = r.split(b'-', 2)[2]
1143 options[b'compengine'] = r.split(b'-', 2)[2]
1144
1144
1145 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1145 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1146 if options[b'zlib.level'] is not None:
1146 if options[b'zlib.level'] is not None:
1147 if not (0 <= options[b'zlib.level'] <= 9):
1147 if not (0 <= options[b'zlib.level'] <= 9):
1148 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1148 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1149 raise error.Abort(msg % options[b'zlib.level'])
1149 raise error.Abort(msg % options[b'zlib.level'])
1150 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1150 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1151 if options[b'zstd.level'] is not None:
1151 if options[b'zstd.level'] is not None:
1152 if not (0 <= options[b'zstd.level'] <= 22):
1152 if not (0 <= options[b'zstd.level'] <= 22):
1153 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1153 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1154 raise error.Abort(msg % options[b'zstd.level'])
1154 raise error.Abort(msg % options[b'zstd.level'])
1155
1155
1156 if requirementsmod.NARROW_REQUIREMENT in requirements:
1156 if requirementsmod.NARROW_REQUIREMENT in requirements:
1157 options[b'enableellipsis'] = True
1157 options[b'enableellipsis'] = True
1158
1158
1159 if ui.configbool(b'experimental', b'rust.index'):
1159 if ui.configbool(b'experimental', b'rust.index'):
1160 options[b'rust.index'] = True
1160 options[b'rust.index'] = True
1161 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1161 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1162 slow_path = ui.config(
1162 slow_path = ui.config(
1163 b'storage', b'revlog.persistent-nodemap.slow-path'
1163 b'storage', b'revlog.persistent-nodemap.slow-path'
1164 )
1164 )
1165 if slow_path not in (b'allow', b'warn', b'abort'):
1165 if slow_path not in (b'allow', b'warn', b'abort'):
1166 default = ui.config_default(
1166 default = ui.config_default(
1167 b'storage', b'revlog.persistent-nodemap.slow-path'
1167 b'storage', b'revlog.persistent-nodemap.slow-path'
1168 )
1168 )
1169 msg = _(
1169 msg = _(
1170 b'unknown value for config '
1170 b'unknown value for config '
1171 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1171 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1172 )
1172 )
1173 ui.warn(msg % slow_path)
1173 ui.warn(msg % slow_path)
1174 if not ui.quiet:
1174 if not ui.quiet:
1175 ui.warn(_(b'falling back to default value: %s\n') % default)
1175 ui.warn(_(b'falling back to default value: %s\n') % default)
1176 slow_path = default
1176 slow_path = default
1177
1177
1178 msg = _(
1178 msg = _(
1179 b"accessing `persistent-nodemap` repository without associated "
1179 b"accessing `persistent-nodemap` repository without associated "
1180 b"fast implementation."
1180 b"fast implementation."
1181 )
1181 )
1182 hint = _(
1182 hint = _(
1183 b"check `hg help config.format.use-persistent-nodemap` "
1183 b"check `hg help config.format.use-persistent-nodemap` "
1184 b"for details"
1184 b"for details"
1185 )
1185 )
1186 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1186 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1187 if slow_path == b'warn':
1187 if slow_path == b'warn':
1188 msg = b"warning: " + msg + b'\n'
1188 msg = b"warning: " + msg + b'\n'
1189 ui.warn(msg)
1189 ui.warn(msg)
1190 if not ui.quiet:
1190 if not ui.quiet:
1191 hint = b'(' + hint + b')\n'
1191 hint = b'(' + hint + b')\n'
1192 ui.warn(hint)
1192 ui.warn(hint)
1193 if slow_path == b'abort':
1193 if slow_path == b'abort':
1194 raise error.Abort(msg, hint=hint)
1194 raise error.Abort(msg, hint=hint)
1195 options[b'persistent-nodemap'] = True
1195 options[b'persistent-nodemap'] = True
1196 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1196 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1197 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1197 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1198 if slow_path not in (b'allow', b'warn', b'abort'):
1198 if slow_path not in (b'allow', b'warn', b'abort'):
1199 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1199 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1200 msg = _(b'unknown value for config "dirstate-v2.slow-path": "%s"\n')
1200 msg = _(b'unknown value for config "dirstate-v2.slow-path": "%s"\n')
1201 ui.warn(msg % slow_path)
1201 ui.warn(msg % slow_path)
1202 if not ui.quiet:
1202 if not ui.quiet:
1203 ui.warn(_(b'falling back to default value: %s\n') % default)
1203 ui.warn(_(b'falling back to default value: %s\n') % default)
1204 slow_path = default
1204 slow_path = default
1205
1205
1206 msg = _(
1206 msg = _(
1207 b"accessing `dirstate-v2` repository without associated "
1207 b"accessing `dirstate-v2` repository without associated "
1208 b"fast implementation."
1208 b"fast implementation."
1209 )
1209 )
1210 hint = _(
1210 hint = _(
1211 b"check `hg help config.format.use-dirstate-v2` " b"for details"
1211 b"check `hg help config.format.use-dirstate-v2` " b"for details"
1212 )
1212 )
1213 if not dirstate.HAS_FAST_DIRSTATE_V2:
1213 if not dirstate.HAS_FAST_DIRSTATE_V2:
1214 if slow_path == b'warn':
1214 if slow_path == b'warn':
1215 msg = b"warning: " + msg + b'\n'
1215 msg = b"warning: " + msg + b'\n'
1216 ui.warn(msg)
1216 ui.warn(msg)
1217 if not ui.quiet:
1217 if not ui.quiet:
1218 hint = b'(' + hint + b')\n'
1218 hint = b'(' + hint + b')\n'
1219 ui.warn(hint)
1219 ui.warn(hint)
1220 if slow_path == b'abort':
1220 if slow_path == b'abort':
1221 raise error.Abort(msg, hint=hint)
1221 raise error.Abort(msg, hint=hint)
1222 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1222 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1223 options[b'persistent-nodemap.mmap'] = True
1223 options[b'persistent-nodemap.mmap'] = True
1224 if ui.configbool(b'devel', b'persistent-nodemap'):
1224 if ui.configbool(b'devel', b'persistent-nodemap'):
1225 options[b'devel-force-nodemap'] = True
1225 options[b'devel-force-nodemap'] = True
1226
1226
1227 return options
1227 return options
1228
1228
1229
1229
1230 def makemain(**kwargs):
1230 def makemain(**kwargs):
1231 """Produce a type conforming to ``ilocalrepositorymain``."""
1231 """Produce a type conforming to ``ilocalrepositorymain``."""
1232 return localrepository
1232 return localrepository
1233
1233
1234
1234
1235 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1235 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1236 class revlogfilestorage:
1236 class revlogfilestorage:
1237 """File storage when using revlogs."""
1237 """File storage when using revlogs."""
1238
1238
1239 def file(self, path):
1239 def file(self, path):
1240 if path.startswith(b'/'):
1240 if path.startswith(b'/'):
1241 path = path[1:]
1241 path = path[1:]
1242
1242
1243 return filelog.filelog(self.svfs, path)
1243 try_split = (
1244 self.currenttransaction() is not None
1245 or txnutil.mayhavepending(self.root)
1246 )
1247
1248 return filelog.filelog(self.svfs, path, try_split=try_split)
1244
1249
1245
1250
1246 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1251 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1247 class revlognarrowfilestorage:
1252 class revlognarrowfilestorage:
1248 """File storage when using revlogs and narrow files."""
1253 """File storage when using revlogs and narrow files."""
1249
1254
1250 def file(self, path):
1255 def file(self, path):
1251 if path.startswith(b'/'):
1256 if path.startswith(b'/'):
1252 path = path[1:]
1257 path = path[1:]
1253
1258
1254 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1259 try_split = (
1260 self.currenttransaction() is not None
1261 or txnutil.mayhavepending(self.root)
1262 )
1263 return filelog.narrowfilelog(
1264 self.svfs, path, self._storenarrowmatch, try_split=try_split
1265 )
1255
1266
1256
1267
1257 def makefilestorage(requirements, features, **kwargs):
1268 def makefilestorage(requirements, features, **kwargs):
1258 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1269 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1259 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1270 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1260 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1271 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1261
1272
1262 if requirementsmod.NARROW_REQUIREMENT in requirements:
1273 if requirementsmod.NARROW_REQUIREMENT in requirements:
1263 return revlognarrowfilestorage
1274 return revlognarrowfilestorage
1264 else:
1275 else:
1265 return revlogfilestorage
1276 return revlogfilestorage
1266
1277
1267
1278
1268 # List of repository interfaces and factory functions for them. Each
1279 # List of repository interfaces and factory functions for them. Each
1269 # will be called in order during ``makelocalrepository()`` to iteratively
1280 # will be called in order during ``makelocalrepository()`` to iteratively
1270 # derive the final type for a local repository instance. We capture the
1281 # derive the final type for a local repository instance. We capture the
1271 # function as a lambda so we don't hold a reference and the module-level
1282 # function as a lambda so we don't hold a reference and the module-level
1272 # functions can be wrapped.
1283 # functions can be wrapped.
1273 REPO_INTERFACES = [
1284 REPO_INTERFACES = [
1274 (repository.ilocalrepositorymain, lambda: makemain),
1285 (repository.ilocalrepositorymain, lambda: makemain),
1275 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1286 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1276 ]
1287 ]
1277
1288
1278
1289
1279 @interfaceutil.implementer(repository.ilocalrepositorymain)
1290 @interfaceutil.implementer(repository.ilocalrepositorymain)
1280 class localrepository:
1291 class localrepository:
1281 """Main class for representing local repositories.
1292 """Main class for representing local repositories.
1282
1293
1283 All local repositories are instances of this class.
1294 All local repositories are instances of this class.
1284
1295
1285 Constructed on its own, instances of this class are not usable as
1296 Constructed on its own, instances of this class are not usable as
1286 repository objects. To obtain a usable repository object, call
1297 repository objects. To obtain a usable repository object, call
1287 ``hg.repository()``, ``localrepo.instance()``, or
1298 ``hg.repository()``, ``localrepo.instance()``, or
1288 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1299 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1289 ``instance()`` adds support for creating new repositories.
1300 ``instance()`` adds support for creating new repositories.
1290 ``hg.repository()`` adds more extension integration, including calling
1301 ``hg.repository()`` adds more extension integration, including calling
1291 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1302 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1292 used.
1303 used.
1293 """
1304 """
1294
1305
1295 _basesupported = {
1306 _basesupported = {
1296 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1307 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1297 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1308 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1298 requirementsmod.CHANGELOGV2_REQUIREMENT,
1309 requirementsmod.CHANGELOGV2_REQUIREMENT,
1299 requirementsmod.COPIESSDC_REQUIREMENT,
1310 requirementsmod.COPIESSDC_REQUIREMENT,
1300 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1311 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1301 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1312 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1302 requirementsmod.DOTENCODE_REQUIREMENT,
1313 requirementsmod.DOTENCODE_REQUIREMENT,
1303 requirementsmod.FNCACHE_REQUIREMENT,
1314 requirementsmod.FNCACHE_REQUIREMENT,
1304 requirementsmod.GENERALDELTA_REQUIREMENT,
1315 requirementsmod.GENERALDELTA_REQUIREMENT,
1305 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1316 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1306 requirementsmod.NODEMAP_REQUIREMENT,
1317 requirementsmod.NODEMAP_REQUIREMENT,
1307 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1318 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1308 requirementsmod.REVLOGV1_REQUIREMENT,
1319 requirementsmod.REVLOGV1_REQUIREMENT,
1309 requirementsmod.REVLOGV2_REQUIREMENT,
1320 requirementsmod.REVLOGV2_REQUIREMENT,
1310 requirementsmod.SHARED_REQUIREMENT,
1321 requirementsmod.SHARED_REQUIREMENT,
1311 requirementsmod.SHARESAFE_REQUIREMENT,
1322 requirementsmod.SHARESAFE_REQUIREMENT,
1312 requirementsmod.SPARSE_REQUIREMENT,
1323 requirementsmod.SPARSE_REQUIREMENT,
1313 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1324 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1314 requirementsmod.STORE_REQUIREMENT,
1325 requirementsmod.STORE_REQUIREMENT,
1315 requirementsmod.TREEMANIFEST_REQUIREMENT,
1326 requirementsmod.TREEMANIFEST_REQUIREMENT,
1316 }
1327 }
1317
1328
1318 # list of prefix for file which can be written without 'wlock'
1329 # list of prefix for file which can be written without 'wlock'
1319 # Extensions should extend this list when needed
1330 # Extensions should extend this list when needed
1320 _wlockfreeprefix = {
1331 _wlockfreeprefix = {
1321 # We migh consider requiring 'wlock' for the next
1332 # We migh consider requiring 'wlock' for the next
1322 # two, but pretty much all the existing code assume
1333 # two, but pretty much all the existing code assume
1323 # wlock is not needed so we keep them excluded for
1334 # wlock is not needed so we keep them excluded for
1324 # now.
1335 # now.
1325 b'hgrc',
1336 b'hgrc',
1326 b'requires',
1337 b'requires',
1327 # XXX cache is a complicatged business someone
1338 # XXX cache is a complicatged business someone
1328 # should investigate this in depth at some point
1339 # should investigate this in depth at some point
1329 b'cache/',
1340 b'cache/',
1330 # XXX bisect was still a bit too messy at the time
1341 # XXX bisect was still a bit too messy at the time
1331 # this changeset was introduced. Someone should fix
1342 # this changeset was introduced. Someone should fix
1332 # the remainig bit and drop this line
1343 # the remainig bit and drop this line
1333 b'bisect.state',
1344 b'bisect.state',
1334 }
1345 }
1335
1346
1336 def __init__(
1347 def __init__(
1337 self,
1348 self,
1338 baseui,
1349 baseui,
1339 ui,
1350 ui,
1340 origroot: bytes,
1351 origroot: bytes,
1341 wdirvfs: vfsmod.vfs,
1352 wdirvfs: vfsmod.vfs,
1342 hgvfs: vfsmod.vfs,
1353 hgvfs: vfsmod.vfs,
1343 requirements,
1354 requirements,
1344 supportedrequirements,
1355 supportedrequirements,
1345 sharedpath: bytes,
1356 sharedpath: bytes,
1346 store,
1357 store,
1347 cachevfs: vfsmod.vfs,
1358 cachevfs: vfsmod.vfs,
1348 wcachevfs: vfsmod.vfs,
1359 wcachevfs: vfsmod.vfs,
1349 features,
1360 features,
1350 intents=None,
1361 intents=None,
1351 ):
1362 ):
1352 """Create a new local repository instance.
1363 """Create a new local repository instance.
1353
1364
1354 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1365 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1355 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1366 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1356 object.
1367 object.
1357
1368
1358 Arguments:
1369 Arguments:
1359
1370
1360 baseui
1371 baseui
1361 ``ui.ui`` instance that ``ui`` argument was based off of.
1372 ``ui.ui`` instance that ``ui`` argument was based off of.
1362
1373
1363 ui
1374 ui
1364 ``ui.ui`` instance for use by the repository.
1375 ``ui.ui`` instance for use by the repository.
1365
1376
1366 origroot
1377 origroot
1367 ``bytes`` path to working directory root of this repository.
1378 ``bytes`` path to working directory root of this repository.
1368
1379
1369 wdirvfs
1380 wdirvfs
1370 ``vfs.vfs`` rooted at the working directory.
1381 ``vfs.vfs`` rooted at the working directory.
1371
1382
1372 hgvfs
1383 hgvfs
1373 ``vfs.vfs`` rooted at .hg/
1384 ``vfs.vfs`` rooted at .hg/
1374
1385
1375 requirements
1386 requirements
1376 ``set`` of bytestrings representing repository opening requirements.
1387 ``set`` of bytestrings representing repository opening requirements.
1377
1388
1378 supportedrequirements
1389 supportedrequirements
1379 ``set`` of bytestrings representing repository requirements that we
1390 ``set`` of bytestrings representing repository requirements that we
1380 know how to open. May be a supetset of ``requirements``.
1391 know how to open. May be a supetset of ``requirements``.
1381
1392
1382 sharedpath
1393 sharedpath
1383 ``bytes`` Defining path to storage base directory. Points to a
1394 ``bytes`` Defining path to storage base directory. Points to a
1384 ``.hg/`` directory somewhere.
1395 ``.hg/`` directory somewhere.
1385
1396
1386 store
1397 store
1387 ``store.basicstore`` (or derived) instance providing access to
1398 ``store.basicstore`` (or derived) instance providing access to
1388 versioned storage.
1399 versioned storage.
1389
1400
1390 cachevfs
1401 cachevfs
1391 ``vfs.vfs`` used for cache files.
1402 ``vfs.vfs`` used for cache files.
1392
1403
1393 wcachevfs
1404 wcachevfs
1394 ``vfs.vfs`` used for cache files related to the working copy.
1405 ``vfs.vfs`` used for cache files related to the working copy.
1395
1406
1396 features
1407 features
1397 ``set`` of bytestrings defining features/capabilities of this
1408 ``set`` of bytestrings defining features/capabilities of this
1398 instance.
1409 instance.
1399
1410
1400 intents
1411 intents
1401 ``set`` of system strings indicating what this repo will be used
1412 ``set`` of system strings indicating what this repo will be used
1402 for.
1413 for.
1403 """
1414 """
1404 self.baseui = baseui
1415 self.baseui = baseui
1405 self.ui = ui
1416 self.ui = ui
1406 self.origroot = origroot
1417 self.origroot = origroot
1407 # vfs rooted at working directory.
1418 # vfs rooted at working directory.
1408 self.wvfs = wdirvfs
1419 self.wvfs = wdirvfs
1409 self.root = wdirvfs.base
1420 self.root = wdirvfs.base
1410 # vfs rooted at .hg/. Used to access most non-store paths.
1421 # vfs rooted at .hg/. Used to access most non-store paths.
1411 self.vfs = hgvfs
1422 self.vfs = hgvfs
1412 self.path = hgvfs.base
1423 self.path = hgvfs.base
1413 self.requirements = requirements
1424 self.requirements = requirements
1414 self.nodeconstants = sha1nodeconstants
1425 self.nodeconstants = sha1nodeconstants
1415 self.nullid = self.nodeconstants.nullid
1426 self.nullid = self.nodeconstants.nullid
1416 self.supported = supportedrequirements
1427 self.supported = supportedrequirements
1417 self.sharedpath = sharedpath
1428 self.sharedpath = sharedpath
1418 self.store = store
1429 self.store = store
1419 self.cachevfs = cachevfs
1430 self.cachevfs = cachevfs
1420 self.wcachevfs = wcachevfs
1431 self.wcachevfs = wcachevfs
1421 self.features = features
1432 self.features = features
1422
1433
1423 self.filtername = None
1434 self.filtername = None
1424
1435
1425 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1436 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1426 b'devel', b'check-locks'
1437 b'devel', b'check-locks'
1427 ):
1438 ):
1428 self.vfs.audit = self._getvfsward(self.vfs.audit)
1439 self.vfs.audit = self._getvfsward(self.vfs.audit)
1429 # A list of callback to shape the phase if no data were found.
1440 # A list of callback to shape the phase if no data were found.
1430 # Callback are in the form: func(repo, roots) --> processed root.
1441 # Callback are in the form: func(repo, roots) --> processed root.
1431 # This list it to be filled by extension during repo setup
1442 # This list it to be filled by extension during repo setup
1432 self._phasedefaults = []
1443 self._phasedefaults = []
1433
1444
1434 color.setup(self.ui)
1445 color.setup(self.ui)
1435
1446
1436 self.spath = self.store.path
1447 self.spath = self.store.path
1437 self.svfs = self.store.vfs
1448 self.svfs = self.store.vfs
1438 self.sjoin = self.store.join
1449 self.sjoin = self.store.join
1439 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1450 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1440 b'devel', b'check-locks'
1451 b'devel', b'check-locks'
1441 ):
1452 ):
1442 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1453 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1443 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1454 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1444 else: # standard vfs
1455 else: # standard vfs
1445 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1456 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1446
1457
1447 self._dirstatevalidatewarned = False
1458 self._dirstatevalidatewarned = False
1448
1459
1449 self._branchcaches = branchmap.BranchMapCache()
1460 self._branchcaches = branchmap.BranchMapCache()
1450 self._revbranchcache = None
1461 self._revbranchcache = None
1451 self._filterpats = {}
1462 self._filterpats = {}
1452 self._datafilters = {}
1463 self._datafilters = {}
1453 self._transref = self._lockref = self._wlockref = None
1464 self._transref = self._lockref = self._wlockref = None
1454
1465
1455 # A cache for various files under .hg/ that tracks file changes,
1466 # A cache for various files under .hg/ that tracks file changes,
1456 # (used by the filecache decorator)
1467 # (used by the filecache decorator)
1457 #
1468 #
1458 # Maps a property name to its util.filecacheentry
1469 # Maps a property name to its util.filecacheentry
1459 self._filecache = {}
1470 self._filecache = {}
1460
1471
1461 # hold sets of revision to be filtered
1472 # hold sets of revision to be filtered
1462 # should be cleared when something might have changed the filter value:
1473 # should be cleared when something might have changed the filter value:
1463 # - new changesets,
1474 # - new changesets,
1464 # - phase change,
1475 # - phase change,
1465 # - new obsolescence marker,
1476 # - new obsolescence marker,
1466 # - working directory parent change,
1477 # - working directory parent change,
1467 # - bookmark changes
1478 # - bookmark changes
1468 self.filteredrevcache = {}
1479 self.filteredrevcache = {}
1469
1480
1470 self._dirstate = None
1481 self._dirstate = None
1471 # post-dirstate-status hooks
1482 # post-dirstate-status hooks
1472 self._postdsstatus = []
1483 self._postdsstatus = []
1473
1484
1474 self._pending_narrow_pats = None
1485 self._pending_narrow_pats = None
1475 self._pending_narrow_pats_dirstate = None
1486 self._pending_narrow_pats_dirstate = None
1476
1487
1477 # generic mapping between names and nodes
1488 # generic mapping between names and nodes
1478 self.names = namespaces.namespaces()
1489 self.names = namespaces.namespaces()
1479
1490
1480 # Key to signature value.
1491 # Key to signature value.
1481 self._sparsesignaturecache = {}
1492 self._sparsesignaturecache = {}
1482 # Signature to cached matcher instance.
1493 # Signature to cached matcher instance.
1483 self._sparsematchercache = {}
1494 self._sparsematchercache = {}
1484
1495
1485 self._extrafilterid = repoview.extrafilter(ui)
1496 self._extrafilterid = repoview.extrafilter(ui)
1486
1497
1487 self.filecopiesmode = None
1498 self.filecopiesmode = None
1488 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1499 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1489 self.filecopiesmode = b'changeset-sidedata'
1500 self.filecopiesmode = b'changeset-sidedata'
1490
1501
1491 self._wanted_sidedata = set()
1502 self._wanted_sidedata = set()
1492 self._sidedata_computers = {}
1503 self._sidedata_computers = {}
1493 sidedatamod.set_sidedata_spec_for_repo(self)
1504 sidedatamod.set_sidedata_spec_for_repo(self)
1494
1505
1495 def _getvfsward(self, origfunc):
1506 def _getvfsward(self, origfunc):
1496 """build a ward for self.vfs"""
1507 """build a ward for self.vfs"""
1497 rref = weakref.ref(self)
1508 rref = weakref.ref(self)
1498
1509
1499 def checkvfs(path, mode=None):
1510 def checkvfs(path, mode=None):
1500 ret = origfunc(path, mode=mode)
1511 ret = origfunc(path, mode=mode)
1501 repo = rref()
1512 repo = rref()
1502 if (
1513 if (
1503 repo is None
1514 repo is None
1504 or not util.safehasattr(repo, b'_wlockref')
1515 or not util.safehasattr(repo, b'_wlockref')
1505 or not util.safehasattr(repo, b'_lockref')
1516 or not util.safehasattr(repo, b'_lockref')
1506 ):
1517 ):
1507 return
1518 return
1508 if mode in (None, b'r', b'rb'):
1519 if mode in (None, b'r', b'rb'):
1509 return
1520 return
1510 if path.startswith(repo.path):
1521 if path.startswith(repo.path):
1511 # truncate name relative to the repository (.hg)
1522 # truncate name relative to the repository (.hg)
1512 path = path[len(repo.path) + 1 :]
1523 path = path[len(repo.path) + 1 :]
1513 if path.startswith(b'cache/'):
1524 if path.startswith(b'cache/'):
1514 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1525 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1515 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1526 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1516 # path prefixes covered by 'lock'
1527 # path prefixes covered by 'lock'
1517 vfs_path_prefixes = (
1528 vfs_path_prefixes = (
1518 b'journal.',
1529 b'journal.',
1519 b'undo.',
1530 b'undo.',
1520 b'strip-backup/',
1531 b'strip-backup/',
1521 b'cache/',
1532 b'cache/',
1522 )
1533 )
1523 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1534 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1524 if repo._currentlock(repo._lockref) is None:
1535 if repo._currentlock(repo._lockref) is None:
1525 repo.ui.develwarn(
1536 repo.ui.develwarn(
1526 b'write with no lock: "%s"' % path,
1537 b'write with no lock: "%s"' % path,
1527 stacklevel=3,
1538 stacklevel=3,
1528 config=b'check-locks',
1539 config=b'check-locks',
1529 )
1540 )
1530 elif repo._currentlock(repo._wlockref) is None:
1541 elif repo._currentlock(repo._wlockref) is None:
1531 # rest of vfs files are covered by 'wlock'
1542 # rest of vfs files are covered by 'wlock'
1532 #
1543 #
1533 # exclude special files
1544 # exclude special files
1534 for prefix in self._wlockfreeprefix:
1545 for prefix in self._wlockfreeprefix:
1535 if path.startswith(prefix):
1546 if path.startswith(prefix):
1536 return
1547 return
1537 repo.ui.develwarn(
1548 repo.ui.develwarn(
1538 b'write with no wlock: "%s"' % path,
1549 b'write with no wlock: "%s"' % path,
1539 stacklevel=3,
1550 stacklevel=3,
1540 config=b'check-locks',
1551 config=b'check-locks',
1541 )
1552 )
1542 return ret
1553 return ret
1543
1554
1544 return checkvfs
1555 return checkvfs
1545
1556
1546 def _getsvfsward(self, origfunc):
1557 def _getsvfsward(self, origfunc):
1547 """build a ward for self.svfs"""
1558 """build a ward for self.svfs"""
1548 rref = weakref.ref(self)
1559 rref = weakref.ref(self)
1549
1560
1550 def checksvfs(path, mode=None):
1561 def checksvfs(path, mode=None):
1551 ret = origfunc(path, mode=mode)
1562 ret = origfunc(path, mode=mode)
1552 repo = rref()
1563 repo = rref()
1553 if repo is None or not util.safehasattr(repo, b'_lockref'):
1564 if repo is None or not util.safehasattr(repo, b'_lockref'):
1554 return
1565 return
1555 if mode in (None, b'r', b'rb'):
1566 if mode in (None, b'r', b'rb'):
1556 return
1567 return
1557 if path.startswith(repo.sharedpath):
1568 if path.startswith(repo.sharedpath):
1558 # truncate name relative to the repository (.hg)
1569 # truncate name relative to the repository (.hg)
1559 path = path[len(repo.sharedpath) + 1 :]
1570 path = path[len(repo.sharedpath) + 1 :]
1560 if repo._currentlock(repo._lockref) is None:
1571 if repo._currentlock(repo._lockref) is None:
1561 repo.ui.develwarn(
1572 repo.ui.develwarn(
1562 b'write with no lock: "%s"' % path, stacklevel=4
1573 b'write with no lock: "%s"' % path, stacklevel=4
1563 )
1574 )
1564 return ret
1575 return ret
1565
1576
1566 return checksvfs
1577 return checksvfs
1567
1578
1568 @property
1579 @property
1569 def vfs_map(self):
1580 def vfs_map(self):
1570 return {
1581 return {
1571 b'': self.svfs,
1582 b'': self.svfs,
1572 b'plain': self.vfs,
1583 b'plain': self.vfs,
1573 b'store': self.svfs,
1584 b'store': self.svfs,
1574 }
1585 }
1575
1586
1576 def close(self):
1587 def close(self):
1577 self._writecaches()
1588 self._writecaches()
1578
1589
1579 def _writecaches(self):
1590 def _writecaches(self):
1580 if self._revbranchcache:
1591 if self._revbranchcache:
1581 self._revbranchcache.write()
1592 self._revbranchcache.write()
1582
1593
1583 def _restrictcapabilities(self, caps):
1594 def _restrictcapabilities(self, caps):
1584 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1595 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1585 caps = set(caps)
1596 caps = set(caps)
1586 capsblob = bundle2.encodecaps(
1597 capsblob = bundle2.encodecaps(
1587 bundle2.getrepocaps(self, role=b'client')
1598 bundle2.getrepocaps(self, role=b'client')
1588 )
1599 )
1589 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1600 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1590 if self.ui.configbool(b'experimental', b'narrow'):
1601 if self.ui.configbool(b'experimental', b'narrow'):
1591 caps.add(wireprototypes.NARROWCAP)
1602 caps.add(wireprototypes.NARROWCAP)
1592 return caps
1603 return caps
1593
1604
1594 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1605 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1595 # self -> auditor -> self._checknested -> self
1606 # self -> auditor -> self._checknested -> self
1596
1607
1597 @property
1608 @property
1598 def auditor(self):
1609 def auditor(self):
1599 # This is only used by context.workingctx.match in order to
1610 # This is only used by context.workingctx.match in order to
1600 # detect files in subrepos.
1611 # detect files in subrepos.
1601 return pathutil.pathauditor(self.root, callback=self._checknested)
1612 return pathutil.pathauditor(self.root, callback=self._checknested)
1602
1613
1603 @property
1614 @property
1604 def nofsauditor(self):
1615 def nofsauditor(self):
1605 # This is only used by context.basectx.match in order to detect
1616 # This is only used by context.basectx.match in order to detect
1606 # files in subrepos.
1617 # files in subrepos.
1607 return pathutil.pathauditor(
1618 return pathutil.pathauditor(
1608 self.root, callback=self._checknested, realfs=False, cached=True
1619 self.root, callback=self._checknested, realfs=False, cached=True
1609 )
1620 )
1610
1621
1611 def _checknested(self, path):
1622 def _checknested(self, path):
1612 """Determine if path is a legal nested repository."""
1623 """Determine if path is a legal nested repository."""
1613 if not path.startswith(self.root):
1624 if not path.startswith(self.root):
1614 return False
1625 return False
1615 subpath = path[len(self.root) + 1 :]
1626 subpath = path[len(self.root) + 1 :]
1616 normsubpath = util.pconvert(subpath)
1627 normsubpath = util.pconvert(subpath)
1617
1628
1618 # XXX: Checking against the current working copy is wrong in
1629 # XXX: Checking against the current working copy is wrong in
1619 # the sense that it can reject things like
1630 # the sense that it can reject things like
1620 #
1631 #
1621 # $ hg cat -r 10 sub/x.txt
1632 # $ hg cat -r 10 sub/x.txt
1622 #
1633 #
1623 # if sub/ is no longer a subrepository in the working copy
1634 # if sub/ is no longer a subrepository in the working copy
1624 # parent revision.
1635 # parent revision.
1625 #
1636 #
1626 # However, it can of course also allow things that would have
1637 # However, it can of course also allow things that would have
1627 # been rejected before, such as the above cat command if sub/
1638 # been rejected before, such as the above cat command if sub/
1628 # is a subrepository now, but was a normal directory before.
1639 # is a subrepository now, but was a normal directory before.
1629 # The old path auditor would have rejected by mistake since it
1640 # The old path auditor would have rejected by mistake since it
1630 # panics when it sees sub/.hg/.
1641 # panics when it sees sub/.hg/.
1631 #
1642 #
1632 # All in all, checking against the working copy seems sensible
1643 # All in all, checking against the working copy seems sensible
1633 # since we want to prevent access to nested repositories on
1644 # since we want to prevent access to nested repositories on
1634 # the filesystem *now*.
1645 # the filesystem *now*.
1635 ctx = self[None]
1646 ctx = self[None]
1636 parts = util.splitpath(subpath)
1647 parts = util.splitpath(subpath)
1637 while parts:
1648 while parts:
1638 prefix = b'/'.join(parts)
1649 prefix = b'/'.join(parts)
1639 if prefix in ctx.substate:
1650 if prefix in ctx.substate:
1640 if prefix == normsubpath:
1651 if prefix == normsubpath:
1641 return True
1652 return True
1642 else:
1653 else:
1643 sub = ctx.sub(prefix)
1654 sub = ctx.sub(prefix)
1644 return sub.checknested(subpath[len(prefix) + 1 :])
1655 return sub.checknested(subpath[len(prefix) + 1 :])
1645 else:
1656 else:
1646 parts.pop()
1657 parts.pop()
1647 return False
1658 return False
1648
1659
1649 def peer(self, path=None):
1660 def peer(self, path=None):
1650 return localpeer(self, path=path) # not cached to avoid reference cycle
1661 return localpeer(self, path=path) # not cached to avoid reference cycle
1651
1662
1652 def unfiltered(self):
1663 def unfiltered(self):
1653 """Return unfiltered version of the repository
1664 """Return unfiltered version of the repository
1654
1665
1655 Intended to be overwritten by filtered repo."""
1666 Intended to be overwritten by filtered repo."""
1656 return self
1667 return self
1657
1668
1658 def filtered(self, name, visibilityexceptions=None):
1669 def filtered(self, name, visibilityexceptions=None):
1659 """Return a filtered version of a repository
1670 """Return a filtered version of a repository
1660
1671
1661 The `name` parameter is the identifier of the requested view. This
1672 The `name` parameter is the identifier of the requested view. This
1662 will return a repoview object set "exactly" to the specified view.
1673 will return a repoview object set "exactly" to the specified view.
1663
1674
1664 This function does not apply recursive filtering to a repository. For
1675 This function does not apply recursive filtering to a repository. For
1665 example calling `repo.filtered("served")` will return a repoview using
1676 example calling `repo.filtered("served")` will return a repoview using
1666 the "served" view, regardless of the initial view used by `repo`.
1677 the "served" view, regardless of the initial view used by `repo`.
1667
1678
1668 In other word, there is always only one level of `repoview` "filtering".
1679 In other word, there is always only one level of `repoview` "filtering".
1669 """
1680 """
1670 if self._extrafilterid is not None and b'%' not in name:
1681 if self._extrafilterid is not None and b'%' not in name:
1671 name = name + b'%' + self._extrafilterid
1682 name = name + b'%' + self._extrafilterid
1672
1683
1673 cls = repoview.newtype(self.unfiltered().__class__)
1684 cls = repoview.newtype(self.unfiltered().__class__)
1674 return cls(self, name, visibilityexceptions)
1685 return cls(self, name, visibilityexceptions)
1675
1686
1676 @mixedrepostorecache(
1687 @mixedrepostorecache(
1677 (b'bookmarks', b'plain'),
1688 (b'bookmarks', b'plain'),
1678 (b'bookmarks.current', b'plain'),
1689 (b'bookmarks.current', b'plain'),
1679 (b'bookmarks', b''),
1690 (b'bookmarks', b''),
1680 (b'00changelog.i', b''),
1691 (b'00changelog.i', b''),
1681 )
1692 )
1682 def _bookmarks(self):
1693 def _bookmarks(self):
1683 # Since the multiple files involved in the transaction cannot be
1694 # Since the multiple files involved in the transaction cannot be
1684 # written atomically (with current repository format), there is a race
1695 # written atomically (with current repository format), there is a race
1685 # condition here.
1696 # condition here.
1686 #
1697 #
1687 # 1) changelog content A is read
1698 # 1) changelog content A is read
1688 # 2) outside transaction update changelog to content B
1699 # 2) outside transaction update changelog to content B
1689 # 3) outside transaction update bookmark file referring to content B
1700 # 3) outside transaction update bookmark file referring to content B
1690 # 4) bookmarks file content is read and filtered against changelog-A
1701 # 4) bookmarks file content is read and filtered against changelog-A
1691 #
1702 #
1692 # When this happens, bookmarks against nodes missing from A are dropped.
1703 # When this happens, bookmarks against nodes missing from A are dropped.
1693 #
1704 #
1694 # Having this happening during read is not great, but it become worse
1705 # Having this happening during read is not great, but it become worse
1695 # when this happen during write because the bookmarks to the "unknown"
1706 # when this happen during write because the bookmarks to the "unknown"
1696 # nodes will be dropped for good. However, writes happen within locks.
1707 # nodes will be dropped for good. However, writes happen within locks.
1697 # This locking makes it possible to have a race free consistent read.
1708 # This locking makes it possible to have a race free consistent read.
1698 # For this purpose data read from disc before locking are
1709 # For this purpose data read from disc before locking are
1699 # "invalidated" right after the locks are taken. This invalidations are
1710 # "invalidated" right after the locks are taken. This invalidations are
1700 # "light", the `filecache` mechanism keep the data in memory and will
1711 # "light", the `filecache` mechanism keep the data in memory and will
1701 # reuse them if the underlying files did not changed. Not parsing the
1712 # reuse them if the underlying files did not changed. Not parsing the
1702 # same data multiple times helps performances.
1713 # same data multiple times helps performances.
1703 #
1714 #
1704 # Unfortunately in the case describe above, the files tracked by the
1715 # Unfortunately in the case describe above, the files tracked by the
1705 # bookmarks file cache might not have changed, but the in-memory
1716 # bookmarks file cache might not have changed, but the in-memory
1706 # content is still "wrong" because we used an older changelog content
1717 # content is still "wrong" because we used an older changelog content
1707 # to process the on-disk data. So after locking, the changelog would be
1718 # to process the on-disk data. So after locking, the changelog would be
1708 # refreshed but `_bookmarks` would be preserved.
1719 # refreshed but `_bookmarks` would be preserved.
1709 # Adding `00changelog.i` to the list of tracked file is not
1720 # Adding `00changelog.i` to the list of tracked file is not
1710 # enough, because at the time we build the content for `_bookmarks` in
1721 # enough, because at the time we build the content for `_bookmarks` in
1711 # (4), the changelog file has already diverged from the content used
1722 # (4), the changelog file has already diverged from the content used
1712 # for loading `changelog` in (1)
1723 # for loading `changelog` in (1)
1713 #
1724 #
1714 # To prevent the issue, we force the changelog to be explicitly
1725 # To prevent the issue, we force the changelog to be explicitly
1715 # reloaded while computing `_bookmarks`. The data race can still happen
1726 # reloaded while computing `_bookmarks`. The data race can still happen
1716 # without the lock (with a narrower window), but it would no longer go
1727 # without the lock (with a narrower window), but it would no longer go
1717 # undetected during the lock time refresh.
1728 # undetected during the lock time refresh.
1718 #
1729 #
1719 # The new schedule is as follow
1730 # The new schedule is as follow
1720 #
1731 #
1721 # 1) filecache logic detect that `_bookmarks` needs to be computed
1732 # 1) filecache logic detect that `_bookmarks` needs to be computed
1722 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1733 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1723 # 3) We force `changelog` filecache to be tested
1734 # 3) We force `changelog` filecache to be tested
1724 # 4) cachestat for `changelog` are captured (for changelog)
1735 # 4) cachestat for `changelog` are captured (for changelog)
1725 # 5) `_bookmarks` is computed and cached
1736 # 5) `_bookmarks` is computed and cached
1726 #
1737 #
1727 # The step in (3) ensure we have a changelog at least as recent as the
1738 # The step in (3) ensure we have a changelog at least as recent as the
1728 # cache stat computed in (1). As a result at locking time:
1739 # cache stat computed in (1). As a result at locking time:
1729 # * if the changelog did not changed since (1) -> we can reuse the data
1740 # * if the changelog did not changed since (1) -> we can reuse the data
1730 # * otherwise -> the bookmarks get refreshed.
1741 # * otherwise -> the bookmarks get refreshed.
1731 self._refreshchangelog()
1742 self._refreshchangelog()
1732 return bookmarks.bmstore(self)
1743 return bookmarks.bmstore(self)
1733
1744
1734 def _refreshchangelog(self):
1745 def _refreshchangelog(self):
1735 """make sure the in memory changelog match the on-disk one"""
1746 """make sure the in memory changelog match the on-disk one"""
1736 if 'changelog' in vars(self) and self.currenttransaction() is None:
1747 if 'changelog' in vars(self) and self.currenttransaction() is None:
1737 del self.changelog
1748 del self.changelog
1738
1749
1739 @property
1750 @property
1740 def _activebookmark(self):
1751 def _activebookmark(self):
1741 return self._bookmarks.active
1752 return self._bookmarks.active
1742
1753
1743 # _phasesets depend on changelog. what we need is to call
1754 # _phasesets depend on changelog. what we need is to call
1744 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1755 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1745 # can't be easily expressed in filecache mechanism.
1756 # can't be easily expressed in filecache mechanism.
1746 @storecache(b'phaseroots', b'00changelog.i')
1757 @storecache(b'phaseroots', b'00changelog.i')
1747 def _phasecache(self):
1758 def _phasecache(self):
1748 return phases.phasecache(self, self._phasedefaults)
1759 return phases.phasecache(self, self._phasedefaults)
1749
1760
1750 @storecache(b'obsstore')
1761 @storecache(b'obsstore')
1751 def obsstore(self):
1762 def obsstore(self):
1752 return obsolete.makestore(self.ui, self)
1763 return obsolete.makestore(self.ui, self)
1753
1764
1754 @changelogcache()
1765 @changelogcache()
1755 def changelog(repo):
1766 def changelog(repo):
1756 # load dirstate before changelog to avoid race see issue6303
1767 # load dirstate before changelog to avoid race see issue6303
1757 repo.dirstate.prefetch_parents()
1768 repo.dirstate.prefetch_parents()
1758 return repo.store.changelog(
1769 return repo.store.changelog(
1759 txnutil.mayhavepending(repo.root),
1770 txnutil.mayhavepending(repo.root),
1760 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1771 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1761 )
1772 )
1762
1773
1763 @manifestlogcache()
1774 @manifestlogcache()
1764 def manifestlog(self):
1775 def manifestlog(self):
1765 return self.store.manifestlog(self, self._storenarrowmatch)
1776 return self.store.manifestlog(self, self._storenarrowmatch)
1766
1777
1767 @unfilteredpropertycache
1778 @unfilteredpropertycache
1768 def dirstate(self):
1779 def dirstate(self):
1769 if self._dirstate is None:
1780 if self._dirstate is None:
1770 self._dirstate = self._makedirstate()
1781 self._dirstate = self._makedirstate()
1771 else:
1782 else:
1772 self._dirstate.refresh()
1783 self._dirstate.refresh()
1773 return self._dirstate
1784 return self._dirstate
1774
1785
1775 def _makedirstate(self):
1786 def _makedirstate(self):
1776 """Extension point for wrapping the dirstate per-repo."""
1787 """Extension point for wrapping the dirstate per-repo."""
1777 sparsematchfn = None
1788 sparsematchfn = None
1778 if sparse.use_sparse(self):
1789 if sparse.use_sparse(self):
1779 sparsematchfn = lambda: sparse.matcher(self)
1790 sparsematchfn = lambda: sparse.matcher(self)
1780 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1791 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1781 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1792 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1782 use_dirstate_v2 = v2_req in self.requirements
1793 use_dirstate_v2 = v2_req in self.requirements
1783 use_tracked_hint = th in self.requirements
1794 use_tracked_hint = th in self.requirements
1784
1795
1785 return dirstate.dirstate(
1796 return dirstate.dirstate(
1786 self.vfs,
1797 self.vfs,
1787 self.ui,
1798 self.ui,
1788 self.root,
1799 self.root,
1789 self._dirstatevalidate,
1800 self._dirstatevalidate,
1790 sparsematchfn,
1801 sparsematchfn,
1791 self.nodeconstants,
1802 self.nodeconstants,
1792 use_dirstate_v2,
1803 use_dirstate_v2,
1793 use_tracked_hint=use_tracked_hint,
1804 use_tracked_hint=use_tracked_hint,
1794 )
1805 )
1795
1806
1796 def _dirstatevalidate(self, node):
1807 def _dirstatevalidate(self, node):
1797 try:
1808 try:
1798 self.changelog.rev(node)
1809 self.changelog.rev(node)
1799 return node
1810 return node
1800 except error.LookupError:
1811 except error.LookupError:
1801 if not self._dirstatevalidatewarned:
1812 if not self._dirstatevalidatewarned:
1802 self._dirstatevalidatewarned = True
1813 self._dirstatevalidatewarned = True
1803 self.ui.warn(
1814 self.ui.warn(
1804 _(b"warning: ignoring unknown working parent %s!\n")
1815 _(b"warning: ignoring unknown working parent %s!\n")
1805 % short(node)
1816 % short(node)
1806 )
1817 )
1807 return self.nullid
1818 return self.nullid
1808
1819
1809 @storecache(narrowspec.FILENAME)
1820 @storecache(narrowspec.FILENAME)
1810 def narrowpats(self):
1821 def narrowpats(self):
1811 """matcher patterns for this repository's narrowspec
1822 """matcher patterns for this repository's narrowspec
1812
1823
1813 A tuple of (includes, excludes).
1824 A tuple of (includes, excludes).
1814 """
1825 """
1815 # the narrow management should probably move into its own object
1826 # the narrow management should probably move into its own object
1816 val = self._pending_narrow_pats
1827 val = self._pending_narrow_pats
1817 if val is None:
1828 if val is None:
1818 val = narrowspec.load(self)
1829 val = narrowspec.load(self)
1819 return val
1830 return val
1820
1831
1821 @storecache(narrowspec.FILENAME)
1832 @storecache(narrowspec.FILENAME)
1822 def _storenarrowmatch(self):
1833 def _storenarrowmatch(self):
1823 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1834 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1824 return matchmod.always()
1835 return matchmod.always()
1825 include, exclude = self.narrowpats
1836 include, exclude = self.narrowpats
1826 return narrowspec.match(self.root, include=include, exclude=exclude)
1837 return narrowspec.match(self.root, include=include, exclude=exclude)
1827
1838
1828 @storecache(narrowspec.FILENAME)
1839 @storecache(narrowspec.FILENAME)
1829 def _narrowmatch(self):
1840 def _narrowmatch(self):
1830 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1841 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1831 return matchmod.always()
1842 return matchmod.always()
1832 narrowspec.checkworkingcopynarrowspec(self)
1843 narrowspec.checkworkingcopynarrowspec(self)
1833 include, exclude = self.narrowpats
1844 include, exclude = self.narrowpats
1834 return narrowspec.match(self.root, include=include, exclude=exclude)
1845 return narrowspec.match(self.root, include=include, exclude=exclude)
1835
1846
1836 def narrowmatch(self, match=None, includeexact=False):
1847 def narrowmatch(self, match=None, includeexact=False):
1837 """matcher corresponding the the repo's narrowspec
1848 """matcher corresponding the the repo's narrowspec
1838
1849
1839 If `match` is given, then that will be intersected with the narrow
1850 If `match` is given, then that will be intersected with the narrow
1840 matcher.
1851 matcher.
1841
1852
1842 If `includeexact` is True, then any exact matches from `match` will
1853 If `includeexact` is True, then any exact matches from `match` will
1843 be included even if they're outside the narrowspec.
1854 be included even if they're outside the narrowspec.
1844 """
1855 """
1845 if match:
1856 if match:
1846 if includeexact and not self._narrowmatch.always():
1857 if includeexact and not self._narrowmatch.always():
1847 # do not exclude explicitly-specified paths so that they can
1858 # do not exclude explicitly-specified paths so that they can
1848 # be warned later on
1859 # be warned later on
1849 em = matchmod.exact(match.files())
1860 em = matchmod.exact(match.files())
1850 nm = matchmod.unionmatcher([self._narrowmatch, em])
1861 nm = matchmod.unionmatcher([self._narrowmatch, em])
1851 return matchmod.intersectmatchers(match, nm)
1862 return matchmod.intersectmatchers(match, nm)
1852 return matchmod.intersectmatchers(match, self._narrowmatch)
1863 return matchmod.intersectmatchers(match, self._narrowmatch)
1853 return self._narrowmatch
1864 return self._narrowmatch
1854
1865
1855 def setnarrowpats(self, newincludes, newexcludes):
1866 def setnarrowpats(self, newincludes, newexcludes):
1856 narrowspec.save(self, newincludes, newexcludes)
1867 narrowspec.save(self, newincludes, newexcludes)
1857 self.invalidate(clearfilecache=True)
1868 self.invalidate(clearfilecache=True)
1858
1869
1859 @unfilteredpropertycache
1870 @unfilteredpropertycache
1860 def _quick_access_changeid_null(self):
1871 def _quick_access_changeid_null(self):
1861 return {
1872 return {
1862 b'null': (nullrev, self.nodeconstants.nullid),
1873 b'null': (nullrev, self.nodeconstants.nullid),
1863 nullrev: (nullrev, self.nodeconstants.nullid),
1874 nullrev: (nullrev, self.nodeconstants.nullid),
1864 self.nullid: (nullrev, self.nullid),
1875 self.nullid: (nullrev, self.nullid),
1865 }
1876 }
1866
1877
1867 @unfilteredpropertycache
1878 @unfilteredpropertycache
1868 def _quick_access_changeid_wc(self):
1879 def _quick_access_changeid_wc(self):
1869 # also fast path access to the working copy parents
1880 # also fast path access to the working copy parents
1870 # however, only do it for filter that ensure wc is visible.
1881 # however, only do it for filter that ensure wc is visible.
1871 quick = self._quick_access_changeid_null.copy()
1882 quick = self._quick_access_changeid_null.copy()
1872 cl = self.unfiltered().changelog
1883 cl = self.unfiltered().changelog
1873 for node in self.dirstate.parents():
1884 for node in self.dirstate.parents():
1874 if node == self.nullid:
1885 if node == self.nullid:
1875 continue
1886 continue
1876 rev = cl.index.get_rev(node)
1887 rev = cl.index.get_rev(node)
1877 if rev is None:
1888 if rev is None:
1878 # unknown working copy parent case:
1889 # unknown working copy parent case:
1879 #
1890 #
1880 # skip the fast path and let higher code deal with it
1891 # skip the fast path and let higher code deal with it
1881 continue
1892 continue
1882 pair = (rev, node)
1893 pair = (rev, node)
1883 quick[rev] = pair
1894 quick[rev] = pair
1884 quick[node] = pair
1895 quick[node] = pair
1885 # also add the parents of the parents
1896 # also add the parents of the parents
1886 for r in cl.parentrevs(rev):
1897 for r in cl.parentrevs(rev):
1887 if r == nullrev:
1898 if r == nullrev:
1888 continue
1899 continue
1889 n = cl.node(r)
1900 n = cl.node(r)
1890 pair = (r, n)
1901 pair = (r, n)
1891 quick[r] = pair
1902 quick[r] = pair
1892 quick[n] = pair
1903 quick[n] = pair
1893 p1node = self.dirstate.p1()
1904 p1node = self.dirstate.p1()
1894 if p1node != self.nullid:
1905 if p1node != self.nullid:
1895 quick[b'.'] = quick[p1node]
1906 quick[b'.'] = quick[p1node]
1896 return quick
1907 return quick
1897
1908
1898 @unfilteredmethod
1909 @unfilteredmethod
1899 def _quick_access_changeid_invalidate(self):
1910 def _quick_access_changeid_invalidate(self):
1900 if '_quick_access_changeid_wc' in vars(self):
1911 if '_quick_access_changeid_wc' in vars(self):
1901 del self.__dict__['_quick_access_changeid_wc']
1912 del self.__dict__['_quick_access_changeid_wc']
1902
1913
1903 @property
1914 @property
1904 def _quick_access_changeid(self):
1915 def _quick_access_changeid(self):
1905 """an helper dictionnary for __getitem__ calls
1916 """an helper dictionnary for __getitem__ calls
1906
1917
1907 This contains a list of symbol we can recognise right away without
1918 This contains a list of symbol we can recognise right away without
1908 further processing.
1919 further processing.
1909 """
1920 """
1910 if self.filtername in repoview.filter_has_wc:
1921 if self.filtername in repoview.filter_has_wc:
1911 return self._quick_access_changeid_wc
1922 return self._quick_access_changeid_wc
1912 return self._quick_access_changeid_null
1923 return self._quick_access_changeid_null
1913
1924
1914 def __getitem__(self, changeid):
1925 def __getitem__(self, changeid):
1915 # dealing with special cases
1926 # dealing with special cases
1916 if changeid is None:
1927 if changeid is None:
1917 return context.workingctx(self)
1928 return context.workingctx(self)
1918 if isinstance(changeid, context.basectx):
1929 if isinstance(changeid, context.basectx):
1919 return changeid
1930 return changeid
1920
1931
1921 # dealing with multiple revisions
1932 # dealing with multiple revisions
1922 if isinstance(changeid, slice):
1933 if isinstance(changeid, slice):
1923 # wdirrev isn't contiguous so the slice shouldn't include it
1934 # wdirrev isn't contiguous so the slice shouldn't include it
1924 return [
1935 return [
1925 self[i]
1936 self[i]
1926 for i in range(*changeid.indices(len(self)))
1937 for i in range(*changeid.indices(len(self)))
1927 if i not in self.changelog.filteredrevs
1938 if i not in self.changelog.filteredrevs
1928 ]
1939 ]
1929
1940
1930 # dealing with some special values
1941 # dealing with some special values
1931 quick_access = self._quick_access_changeid.get(changeid)
1942 quick_access = self._quick_access_changeid.get(changeid)
1932 if quick_access is not None:
1943 if quick_access is not None:
1933 rev, node = quick_access
1944 rev, node = quick_access
1934 return context.changectx(self, rev, node, maybe_filtered=False)
1945 return context.changectx(self, rev, node, maybe_filtered=False)
1935 if changeid == b'tip':
1946 if changeid == b'tip':
1936 node = self.changelog.tip()
1947 node = self.changelog.tip()
1937 rev = self.changelog.rev(node)
1948 rev = self.changelog.rev(node)
1938 return context.changectx(self, rev, node)
1949 return context.changectx(self, rev, node)
1939
1950
1940 # dealing with arbitrary values
1951 # dealing with arbitrary values
1941 try:
1952 try:
1942 if isinstance(changeid, int):
1953 if isinstance(changeid, int):
1943 node = self.changelog.node(changeid)
1954 node = self.changelog.node(changeid)
1944 rev = changeid
1955 rev = changeid
1945 elif changeid == b'.':
1956 elif changeid == b'.':
1946 # this is a hack to delay/avoid loading obsmarkers
1957 # this is a hack to delay/avoid loading obsmarkers
1947 # when we know that '.' won't be hidden
1958 # when we know that '.' won't be hidden
1948 node = self.dirstate.p1()
1959 node = self.dirstate.p1()
1949 rev = self.unfiltered().changelog.rev(node)
1960 rev = self.unfiltered().changelog.rev(node)
1950 elif len(changeid) == self.nodeconstants.nodelen:
1961 elif len(changeid) == self.nodeconstants.nodelen:
1951 try:
1962 try:
1952 node = changeid
1963 node = changeid
1953 rev = self.changelog.rev(changeid)
1964 rev = self.changelog.rev(changeid)
1954 except error.FilteredLookupError:
1965 except error.FilteredLookupError:
1955 changeid = hex(changeid) # for the error message
1966 changeid = hex(changeid) # for the error message
1956 raise
1967 raise
1957 except LookupError:
1968 except LookupError:
1958 # check if it might have come from damaged dirstate
1969 # check if it might have come from damaged dirstate
1959 #
1970 #
1960 # XXX we could avoid the unfiltered if we had a recognizable
1971 # XXX we could avoid the unfiltered if we had a recognizable
1961 # exception for filtered changeset access
1972 # exception for filtered changeset access
1962 if (
1973 if (
1963 self.local()
1974 self.local()
1964 and changeid in self.unfiltered().dirstate.parents()
1975 and changeid in self.unfiltered().dirstate.parents()
1965 ):
1976 ):
1966 msg = _(b"working directory has unknown parent '%s'!")
1977 msg = _(b"working directory has unknown parent '%s'!")
1967 raise error.Abort(msg % short(changeid))
1978 raise error.Abort(msg % short(changeid))
1968 changeid = hex(changeid) # for the error message
1979 changeid = hex(changeid) # for the error message
1969 raise
1980 raise
1970
1981
1971 elif len(changeid) == 2 * self.nodeconstants.nodelen:
1982 elif len(changeid) == 2 * self.nodeconstants.nodelen:
1972 node = bin(changeid)
1983 node = bin(changeid)
1973 rev = self.changelog.rev(node)
1984 rev = self.changelog.rev(node)
1974 else:
1985 else:
1975 raise error.ProgrammingError(
1986 raise error.ProgrammingError(
1976 b"unsupported changeid '%s' of type %s"
1987 b"unsupported changeid '%s' of type %s"
1977 % (changeid, pycompat.bytestr(type(changeid)))
1988 % (changeid, pycompat.bytestr(type(changeid)))
1978 )
1989 )
1979
1990
1980 return context.changectx(self, rev, node)
1991 return context.changectx(self, rev, node)
1981
1992
1982 except (error.FilteredIndexError, error.FilteredLookupError):
1993 except (error.FilteredIndexError, error.FilteredLookupError):
1983 raise error.FilteredRepoLookupError(
1994 raise error.FilteredRepoLookupError(
1984 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1995 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1985 )
1996 )
1986 except (IndexError, LookupError):
1997 except (IndexError, LookupError):
1987 raise error.RepoLookupError(
1998 raise error.RepoLookupError(
1988 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1999 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1989 )
2000 )
1990 except error.WdirUnsupported:
2001 except error.WdirUnsupported:
1991 return context.workingctx(self)
2002 return context.workingctx(self)
1992
2003
1993 def __contains__(self, changeid):
2004 def __contains__(self, changeid):
1994 """True if the given changeid exists"""
2005 """True if the given changeid exists"""
1995 try:
2006 try:
1996 self[changeid]
2007 self[changeid]
1997 return True
2008 return True
1998 except error.RepoLookupError:
2009 except error.RepoLookupError:
1999 return False
2010 return False
2000
2011
2001 def __nonzero__(self):
2012 def __nonzero__(self):
2002 return True
2013 return True
2003
2014
2004 __bool__ = __nonzero__
2015 __bool__ = __nonzero__
2005
2016
2006 def __len__(self):
2017 def __len__(self):
2007 # no need to pay the cost of repoview.changelog
2018 # no need to pay the cost of repoview.changelog
2008 unfi = self.unfiltered()
2019 unfi = self.unfiltered()
2009 return len(unfi.changelog)
2020 return len(unfi.changelog)
2010
2021
2011 def __iter__(self):
2022 def __iter__(self):
2012 return iter(self.changelog)
2023 return iter(self.changelog)
2013
2024
2014 def revs(self, expr: bytes, *args):
2025 def revs(self, expr: bytes, *args):
2015 """Find revisions matching a revset.
2026 """Find revisions matching a revset.
2016
2027
2017 The revset is specified as a string ``expr`` that may contain
2028 The revset is specified as a string ``expr`` that may contain
2018 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2029 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2019
2030
2020 Revset aliases from the configuration are not expanded. To expand
2031 Revset aliases from the configuration are not expanded. To expand
2021 user aliases, consider calling ``scmutil.revrange()`` or
2032 user aliases, consider calling ``scmutil.revrange()`` or
2022 ``repo.anyrevs([expr], user=True)``.
2033 ``repo.anyrevs([expr], user=True)``.
2023
2034
2024 Returns a smartset.abstractsmartset, which is a list-like interface
2035 Returns a smartset.abstractsmartset, which is a list-like interface
2025 that contains integer revisions.
2036 that contains integer revisions.
2026 """
2037 """
2027 tree = revsetlang.spectree(expr, *args)
2038 tree = revsetlang.spectree(expr, *args)
2028 return revset.makematcher(tree)(self)
2039 return revset.makematcher(tree)(self)
2029
2040
2030 def set(self, expr: bytes, *args):
2041 def set(self, expr: bytes, *args):
2031 """Find revisions matching a revset and emit changectx instances.
2042 """Find revisions matching a revset and emit changectx instances.
2032
2043
2033 This is a convenience wrapper around ``revs()`` that iterates the
2044 This is a convenience wrapper around ``revs()`` that iterates the
2034 result and is a generator of changectx instances.
2045 result and is a generator of changectx instances.
2035
2046
2036 Revset aliases from the configuration are not expanded. To expand
2047 Revset aliases from the configuration are not expanded. To expand
2037 user aliases, consider calling ``scmutil.revrange()``.
2048 user aliases, consider calling ``scmutil.revrange()``.
2038 """
2049 """
2039 for r in self.revs(expr, *args):
2050 for r in self.revs(expr, *args):
2040 yield self[r]
2051 yield self[r]
2041
2052
2042 def anyrevs(self, specs: bytes, user=False, localalias=None):
2053 def anyrevs(self, specs: bytes, user=False, localalias=None):
2043 """Find revisions matching one of the given revsets.
2054 """Find revisions matching one of the given revsets.
2044
2055
2045 Revset aliases from the configuration are not expanded by default. To
2056 Revset aliases from the configuration are not expanded by default. To
2046 expand user aliases, specify ``user=True``. To provide some local
2057 expand user aliases, specify ``user=True``. To provide some local
2047 definitions overriding user aliases, set ``localalias`` to
2058 definitions overriding user aliases, set ``localalias`` to
2048 ``{name: definitionstring}``.
2059 ``{name: definitionstring}``.
2049 """
2060 """
2050 if specs == [b'null']:
2061 if specs == [b'null']:
2051 return revset.baseset([nullrev])
2062 return revset.baseset([nullrev])
2052 if specs == [b'.']:
2063 if specs == [b'.']:
2053 quick_data = self._quick_access_changeid.get(b'.')
2064 quick_data = self._quick_access_changeid.get(b'.')
2054 if quick_data is not None:
2065 if quick_data is not None:
2055 return revset.baseset([quick_data[0]])
2066 return revset.baseset([quick_data[0]])
2056 if user:
2067 if user:
2057 m = revset.matchany(
2068 m = revset.matchany(
2058 self.ui,
2069 self.ui,
2059 specs,
2070 specs,
2060 lookup=revset.lookupfn(self),
2071 lookup=revset.lookupfn(self),
2061 localalias=localalias,
2072 localalias=localalias,
2062 )
2073 )
2063 else:
2074 else:
2064 m = revset.matchany(None, specs, localalias=localalias)
2075 m = revset.matchany(None, specs, localalias=localalias)
2065 return m(self)
2076 return m(self)
2066
2077
2067 def url(self) -> bytes:
2078 def url(self) -> bytes:
2068 return b'file:' + self.root
2079 return b'file:' + self.root
2069
2080
2070 def hook(self, name, throw=False, **args):
2081 def hook(self, name, throw=False, **args):
2071 """Call a hook, passing this repo instance.
2082 """Call a hook, passing this repo instance.
2072
2083
2073 This a convenience method to aid invoking hooks. Extensions likely
2084 This a convenience method to aid invoking hooks. Extensions likely
2074 won't call this unless they have registered a custom hook or are
2085 won't call this unless they have registered a custom hook or are
2075 replacing code that is expected to call a hook.
2086 replacing code that is expected to call a hook.
2076 """
2087 """
2077 return hook.hook(self.ui, self, name, throw, **args)
2088 return hook.hook(self.ui, self, name, throw, **args)
2078
2089
2079 @filteredpropertycache
2090 @filteredpropertycache
2080 def _tagscache(self):
2091 def _tagscache(self):
2081 """Returns a tagscache object that contains various tags related
2092 """Returns a tagscache object that contains various tags related
2082 caches."""
2093 caches."""
2083
2094
2084 # This simplifies its cache management by having one decorated
2095 # This simplifies its cache management by having one decorated
2085 # function (this one) and the rest simply fetch things from it.
2096 # function (this one) and the rest simply fetch things from it.
2086 class tagscache:
2097 class tagscache:
2087 def __init__(self):
2098 def __init__(self):
2088 # These two define the set of tags for this repository. tags
2099 # These two define the set of tags for this repository. tags
2089 # maps tag name to node; tagtypes maps tag name to 'global' or
2100 # maps tag name to node; tagtypes maps tag name to 'global' or
2090 # 'local'. (Global tags are defined by .hgtags across all
2101 # 'local'. (Global tags are defined by .hgtags across all
2091 # heads, and local tags are defined in .hg/localtags.)
2102 # heads, and local tags are defined in .hg/localtags.)
2092 # They constitute the in-memory cache of tags.
2103 # They constitute the in-memory cache of tags.
2093 self.tags = self.tagtypes = None
2104 self.tags = self.tagtypes = None
2094
2105
2095 self.nodetagscache = self.tagslist = None
2106 self.nodetagscache = self.tagslist = None
2096
2107
2097 cache = tagscache()
2108 cache = tagscache()
2098 cache.tags, cache.tagtypes = self._findtags()
2109 cache.tags, cache.tagtypes = self._findtags()
2099
2110
2100 return cache
2111 return cache
2101
2112
2102 def tags(self):
2113 def tags(self):
2103 '''return a mapping of tag to node'''
2114 '''return a mapping of tag to node'''
2104 t = {}
2115 t = {}
2105 if self.changelog.filteredrevs:
2116 if self.changelog.filteredrevs:
2106 tags, tt = self._findtags()
2117 tags, tt = self._findtags()
2107 else:
2118 else:
2108 tags = self._tagscache.tags
2119 tags = self._tagscache.tags
2109 rev = self.changelog.rev
2120 rev = self.changelog.rev
2110 for k, v in tags.items():
2121 for k, v in tags.items():
2111 try:
2122 try:
2112 # ignore tags to unknown nodes
2123 # ignore tags to unknown nodes
2113 rev(v)
2124 rev(v)
2114 t[k] = v
2125 t[k] = v
2115 except (error.LookupError, ValueError):
2126 except (error.LookupError, ValueError):
2116 pass
2127 pass
2117 return t
2128 return t
2118
2129
2119 def _findtags(self):
2130 def _findtags(self):
2120 """Do the hard work of finding tags. Return a pair of dicts
2131 """Do the hard work of finding tags. Return a pair of dicts
2121 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2132 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2122 maps tag name to a string like \'global\' or \'local\'.
2133 maps tag name to a string like \'global\' or \'local\'.
2123 Subclasses or extensions are free to add their own tags, but
2134 Subclasses or extensions are free to add their own tags, but
2124 should be aware that the returned dicts will be retained for the
2135 should be aware that the returned dicts will be retained for the
2125 duration of the localrepo object."""
2136 duration of the localrepo object."""
2126
2137
2127 # XXX what tagtype should subclasses/extensions use? Currently
2138 # XXX what tagtype should subclasses/extensions use? Currently
2128 # mq and bookmarks add tags, but do not set the tagtype at all.
2139 # mq and bookmarks add tags, but do not set the tagtype at all.
2129 # Should each extension invent its own tag type? Should there
2140 # Should each extension invent its own tag type? Should there
2130 # be one tagtype for all such "virtual" tags? Or is the status
2141 # be one tagtype for all such "virtual" tags? Or is the status
2131 # quo fine?
2142 # quo fine?
2132
2143
2133 # map tag name to (node, hist)
2144 # map tag name to (node, hist)
2134 alltags = tagsmod.findglobaltags(self.ui, self)
2145 alltags = tagsmod.findglobaltags(self.ui, self)
2135 # map tag name to tag type
2146 # map tag name to tag type
2136 tagtypes = {tag: b'global' for tag in alltags}
2147 tagtypes = {tag: b'global' for tag in alltags}
2137
2148
2138 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2149 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2139
2150
2140 # Build the return dicts. Have to re-encode tag names because
2151 # Build the return dicts. Have to re-encode tag names because
2141 # the tags module always uses UTF-8 (in order not to lose info
2152 # the tags module always uses UTF-8 (in order not to lose info
2142 # writing to the cache), but the rest of Mercurial wants them in
2153 # writing to the cache), but the rest of Mercurial wants them in
2143 # local encoding.
2154 # local encoding.
2144 tags = {}
2155 tags = {}
2145 for name, (node, hist) in alltags.items():
2156 for name, (node, hist) in alltags.items():
2146 if node != self.nullid:
2157 if node != self.nullid:
2147 tags[encoding.tolocal(name)] = node
2158 tags[encoding.tolocal(name)] = node
2148 tags[b'tip'] = self.changelog.tip()
2159 tags[b'tip'] = self.changelog.tip()
2149 tagtypes = {
2160 tagtypes = {
2150 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2161 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2151 }
2162 }
2152 return (tags, tagtypes)
2163 return (tags, tagtypes)
2153
2164
2154 def tagtype(self, tagname):
2165 def tagtype(self, tagname):
2155 """
2166 """
2156 return the type of the given tag. result can be:
2167 return the type of the given tag. result can be:
2157
2168
2158 'local' : a local tag
2169 'local' : a local tag
2159 'global' : a global tag
2170 'global' : a global tag
2160 None : tag does not exist
2171 None : tag does not exist
2161 """
2172 """
2162
2173
2163 return self._tagscache.tagtypes.get(tagname)
2174 return self._tagscache.tagtypes.get(tagname)
2164
2175
2165 def tagslist(self):
2176 def tagslist(self):
2166 '''return a list of tags ordered by revision'''
2177 '''return a list of tags ordered by revision'''
2167 if not self._tagscache.tagslist:
2178 if not self._tagscache.tagslist:
2168 l = []
2179 l = []
2169 for t, n in self.tags().items():
2180 for t, n in self.tags().items():
2170 l.append((self.changelog.rev(n), t, n))
2181 l.append((self.changelog.rev(n), t, n))
2171 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2182 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2172
2183
2173 return self._tagscache.tagslist
2184 return self._tagscache.tagslist
2174
2185
2175 def nodetags(self, node):
2186 def nodetags(self, node):
2176 '''return the tags associated with a node'''
2187 '''return the tags associated with a node'''
2177 if not self._tagscache.nodetagscache:
2188 if not self._tagscache.nodetagscache:
2178 nodetagscache = {}
2189 nodetagscache = {}
2179 for t, n in self._tagscache.tags.items():
2190 for t, n in self._tagscache.tags.items():
2180 nodetagscache.setdefault(n, []).append(t)
2191 nodetagscache.setdefault(n, []).append(t)
2181 for tags in nodetagscache.values():
2192 for tags in nodetagscache.values():
2182 tags.sort()
2193 tags.sort()
2183 self._tagscache.nodetagscache = nodetagscache
2194 self._tagscache.nodetagscache = nodetagscache
2184 return self._tagscache.nodetagscache.get(node, [])
2195 return self._tagscache.nodetagscache.get(node, [])
2185
2196
2186 def nodebookmarks(self, node):
2197 def nodebookmarks(self, node):
2187 """return the list of bookmarks pointing to the specified node"""
2198 """return the list of bookmarks pointing to the specified node"""
2188 return self._bookmarks.names(node)
2199 return self._bookmarks.names(node)
2189
2200
2190 def branchmap(self):
2201 def branchmap(self):
2191 """returns a dictionary {branch: [branchheads]} with branchheads
2202 """returns a dictionary {branch: [branchheads]} with branchheads
2192 ordered by increasing revision number"""
2203 ordered by increasing revision number"""
2193 return self._branchcaches[self]
2204 return self._branchcaches[self]
2194
2205
2195 @unfilteredmethod
2206 @unfilteredmethod
2196 def revbranchcache(self):
2207 def revbranchcache(self):
2197 if not self._revbranchcache:
2208 if not self._revbranchcache:
2198 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2209 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2199 return self._revbranchcache
2210 return self._revbranchcache
2200
2211
2201 def register_changeset(self, rev, changelogrevision):
2212 def register_changeset(self, rev, changelogrevision):
2202 self.revbranchcache().setdata(rev, changelogrevision)
2213 self.revbranchcache().setdata(rev, changelogrevision)
2203
2214
2204 def branchtip(self, branch, ignoremissing=False):
2215 def branchtip(self, branch, ignoremissing=False):
2205 """return the tip node for a given branch
2216 """return the tip node for a given branch
2206
2217
2207 If ignoremissing is True, then this method will not raise an error.
2218 If ignoremissing is True, then this method will not raise an error.
2208 This is helpful for callers that only expect None for a missing branch
2219 This is helpful for callers that only expect None for a missing branch
2209 (e.g. namespace).
2220 (e.g. namespace).
2210
2221
2211 """
2222 """
2212 try:
2223 try:
2213 return self.branchmap().branchtip(branch)
2224 return self.branchmap().branchtip(branch)
2214 except KeyError:
2225 except KeyError:
2215 if not ignoremissing:
2226 if not ignoremissing:
2216 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2227 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2217 else:
2228 else:
2218 pass
2229 pass
2219
2230
2220 def lookup(self, key):
2231 def lookup(self, key):
2221 node = scmutil.revsymbol(self, key).node()
2232 node = scmutil.revsymbol(self, key).node()
2222 if node is None:
2233 if node is None:
2223 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2234 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2224 return node
2235 return node
2225
2236
2226 def lookupbranch(self, key):
2237 def lookupbranch(self, key):
2227 if self.branchmap().hasbranch(key):
2238 if self.branchmap().hasbranch(key):
2228 return key
2239 return key
2229
2240
2230 return scmutil.revsymbol(self, key).branch()
2241 return scmutil.revsymbol(self, key).branch()
2231
2242
2232 def known(self, nodes):
2243 def known(self, nodes):
2233 cl = self.changelog
2244 cl = self.changelog
2234 get_rev = cl.index.get_rev
2245 get_rev = cl.index.get_rev
2235 filtered = cl.filteredrevs
2246 filtered = cl.filteredrevs
2236 result = []
2247 result = []
2237 for n in nodes:
2248 for n in nodes:
2238 r = get_rev(n)
2249 r = get_rev(n)
2239 resp = not (r is None or r in filtered)
2250 resp = not (r is None or r in filtered)
2240 result.append(resp)
2251 result.append(resp)
2241 return result
2252 return result
2242
2253
2243 def local(self):
2254 def local(self):
2244 return self
2255 return self
2245
2256
2246 def publishing(self):
2257 def publishing(self):
2247 # it's safe (and desirable) to trust the publish flag unconditionally
2258 # it's safe (and desirable) to trust the publish flag unconditionally
2248 # so that we don't finalize changes shared between users via ssh or nfs
2259 # so that we don't finalize changes shared between users via ssh or nfs
2249 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2260 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2250
2261
2251 def cancopy(self):
2262 def cancopy(self):
2252 # so statichttprepo's override of local() works
2263 # so statichttprepo's override of local() works
2253 if not self.local():
2264 if not self.local():
2254 return False
2265 return False
2255 if not self.publishing():
2266 if not self.publishing():
2256 return True
2267 return True
2257 # if publishing we can't copy if there is filtered content
2268 # if publishing we can't copy if there is filtered content
2258 return not self.filtered(b'visible').changelog.filteredrevs
2269 return not self.filtered(b'visible').changelog.filteredrevs
2259
2270
2260 def shared(self):
2271 def shared(self):
2261 '''the type of shared repository (None if not shared)'''
2272 '''the type of shared repository (None if not shared)'''
2262 if self.sharedpath != self.path:
2273 if self.sharedpath != self.path:
2263 return b'store'
2274 return b'store'
2264 return None
2275 return None
2265
2276
2266 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2277 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2267 return self.vfs.reljoin(self.root, f, *insidef)
2278 return self.vfs.reljoin(self.root, f, *insidef)
2268
2279
2269 def setparents(self, p1, p2=None):
2280 def setparents(self, p1, p2=None):
2270 if p2 is None:
2281 if p2 is None:
2271 p2 = self.nullid
2282 p2 = self.nullid
2272 self[None].setparents(p1, p2)
2283 self[None].setparents(p1, p2)
2273 self._quick_access_changeid_invalidate()
2284 self._quick_access_changeid_invalidate()
2274
2285
2275 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2286 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2276 """changeid must be a changeset revision, if specified.
2287 """changeid must be a changeset revision, if specified.
2277 fileid can be a file revision or node."""
2288 fileid can be a file revision or node."""
2278 return context.filectx(
2289 return context.filectx(
2279 self, path, changeid, fileid, changectx=changectx
2290 self, path, changeid, fileid, changectx=changectx
2280 )
2291 )
2281
2292
2282 def getcwd(self) -> bytes:
2293 def getcwd(self) -> bytes:
2283 return self.dirstate.getcwd()
2294 return self.dirstate.getcwd()
2284
2295
2285 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2296 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2286 return self.dirstate.pathto(f, cwd)
2297 return self.dirstate.pathto(f, cwd)
2287
2298
2288 def _loadfilter(self, filter):
2299 def _loadfilter(self, filter):
2289 if filter not in self._filterpats:
2300 if filter not in self._filterpats:
2290 l = []
2301 l = []
2291 for pat, cmd in self.ui.configitems(filter):
2302 for pat, cmd in self.ui.configitems(filter):
2292 if cmd == b'!':
2303 if cmd == b'!':
2293 continue
2304 continue
2294 mf = matchmod.match(self.root, b'', [pat])
2305 mf = matchmod.match(self.root, b'', [pat])
2295 fn = None
2306 fn = None
2296 params = cmd
2307 params = cmd
2297 for name, filterfn in self._datafilters.items():
2308 for name, filterfn in self._datafilters.items():
2298 if cmd.startswith(name):
2309 if cmd.startswith(name):
2299 fn = filterfn
2310 fn = filterfn
2300 params = cmd[len(name) :].lstrip()
2311 params = cmd[len(name) :].lstrip()
2301 break
2312 break
2302 if not fn:
2313 if not fn:
2303 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2314 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2304 fn.__name__ = 'commandfilter'
2315 fn.__name__ = 'commandfilter'
2305 # Wrap old filters not supporting keyword arguments
2316 # Wrap old filters not supporting keyword arguments
2306 if not pycompat.getargspec(fn)[2]:
2317 if not pycompat.getargspec(fn)[2]:
2307 oldfn = fn
2318 oldfn = fn
2308 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2319 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2309 fn.__name__ = 'compat-' + oldfn.__name__
2320 fn.__name__ = 'compat-' + oldfn.__name__
2310 l.append((mf, fn, params))
2321 l.append((mf, fn, params))
2311 self._filterpats[filter] = l
2322 self._filterpats[filter] = l
2312 return self._filterpats[filter]
2323 return self._filterpats[filter]
2313
2324
2314 def _filter(self, filterpats, filename, data):
2325 def _filter(self, filterpats, filename, data):
2315 for mf, fn, cmd in filterpats:
2326 for mf, fn, cmd in filterpats:
2316 if mf(filename):
2327 if mf(filename):
2317 self.ui.debug(
2328 self.ui.debug(
2318 b"filtering %s through %s\n"
2329 b"filtering %s through %s\n"
2319 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2330 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2320 )
2331 )
2321 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2332 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2322 break
2333 break
2323
2334
2324 return data
2335 return data
2325
2336
2326 @unfilteredpropertycache
2337 @unfilteredpropertycache
2327 def _encodefilterpats(self):
2338 def _encodefilterpats(self):
2328 return self._loadfilter(b'encode')
2339 return self._loadfilter(b'encode')
2329
2340
2330 @unfilteredpropertycache
2341 @unfilteredpropertycache
2331 def _decodefilterpats(self):
2342 def _decodefilterpats(self):
2332 return self._loadfilter(b'decode')
2343 return self._loadfilter(b'decode')
2333
2344
2334 def adddatafilter(self, name, filter):
2345 def adddatafilter(self, name, filter):
2335 self._datafilters[name] = filter
2346 self._datafilters[name] = filter
2336
2347
2337 def wread(self, filename: bytes) -> bytes:
2348 def wread(self, filename: bytes) -> bytes:
2338 if self.wvfs.islink(filename):
2349 if self.wvfs.islink(filename):
2339 data = self.wvfs.readlink(filename)
2350 data = self.wvfs.readlink(filename)
2340 else:
2351 else:
2341 data = self.wvfs.read(filename)
2352 data = self.wvfs.read(filename)
2342 return self._filter(self._encodefilterpats, filename, data)
2353 return self._filter(self._encodefilterpats, filename, data)
2343
2354
2344 def wwrite(
2355 def wwrite(
2345 self,
2356 self,
2346 filename: bytes,
2357 filename: bytes,
2347 data: bytes,
2358 data: bytes,
2348 flags: bytes,
2359 flags: bytes,
2349 backgroundclose=False,
2360 backgroundclose=False,
2350 **kwargs
2361 **kwargs
2351 ) -> int:
2362 ) -> int:
2352 """write ``data`` into ``filename`` in the working directory
2363 """write ``data`` into ``filename`` in the working directory
2353
2364
2354 This returns length of written (maybe decoded) data.
2365 This returns length of written (maybe decoded) data.
2355 """
2366 """
2356 data = self._filter(self._decodefilterpats, filename, data)
2367 data = self._filter(self._decodefilterpats, filename, data)
2357 if b'l' in flags:
2368 if b'l' in flags:
2358 self.wvfs.symlink(data, filename)
2369 self.wvfs.symlink(data, filename)
2359 else:
2370 else:
2360 self.wvfs.write(
2371 self.wvfs.write(
2361 filename, data, backgroundclose=backgroundclose, **kwargs
2372 filename, data, backgroundclose=backgroundclose, **kwargs
2362 )
2373 )
2363 if b'x' in flags:
2374 if b'x' in flags:
2364 self.wvfs.setflags(filename, False, True)
2375 self.wvfs.setflags(filename, False, True)
2365 else:
2376 else:
2366 self.wvfs.setflags(filename, False, False)
2377 self.wvfs.setflags(filename, False, False)
2367 return len(data)
2378 return len(data)
2368
2379
2369 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2380 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2370 return self._filter(self._decodefilterpats, filename, data)
2381 return self._filter(self._decodefilterpats, filename, data)
2371
2382
2372 def currenttransaction(self):
2383 def currenttransaction(self):
2373 """return the current transaction or None if non exists"""
2384 """return the current transaction or None if non exists"""
2374 if self._transref:
2385 if self._transref:
2375 tr = self._transref()
2386 tr = self._transref()
2376 else:
2387 else:
2377 tr = None
2388 tr = None
2378
2389
2379 if tr and tr.running():
2390 if tr and tr.running():
2380 return tr
2391 return tr
2381 return None
2392 return None
2382
2393
2383 def transaction(self, desc, report=None):
2394 def transaction(self, desc, report=None):
2384 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2395 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2385 b'devel', b'check-locks'
2396 b'devel', b'check-locks'
2386 ):
2397 ):
2387 if self._currentlock(self._lockref) is None:
2398 if self._currentlock(self._lockref) is None:
2388 raise error.ProgrammingError(b'transaction requires locking')
2399 raise error.ProgrammingError(b'transaction requires locking')
2389 tr = self.currenttransaction()
2400 tr = self.currenttransaction()
2390 if tr is not None:
2401 if tr is not None:
2391 return tr.nest(name=desc)
2402 return tr.nest(name=desc)
2392
2403
2393 # abort here if the journal already exists
2404 # abort here if the journal already exists
2394 if self.svfs.exists(b"journal"):
2405 if self.svfs.exists(b"journal"):
2395 raise error.RepoError(
2406 raise error.RepoError(
2396 _(b"abandoned transaction found"),
2407 _(b"abandoned transaction found"),
2397 hint=_(b"run 'hg recover' to clean up transaction"),
2408 hint=_(b"run 'hg recover' to clean up transaction"),
2398 )
2409 )
2399
2410
2400 # At that point your dirstate should be clean:
2411 # At that point your dirstate should be clean:
2401 #
2412 #
2402 # - If you don't have the wlock, why would you still have a dirty
2413 # - If you don't have the wlock, why would you still have a dirty
2403 # dirstate ?
2414 # dirstate ?
2404 #
2415 #
2405 # - If you hold the wlock, you should not be opening a transaction in
2416 # - If you hold the wlock, you should not be opening a transaction in
2406 # the middle of a `distate.changing_*` block. The transaction needs to
2417 # the middle of a `distate.changing_*` block. The transaction needs to
2407 # be open before that and wrap the change-context.
2418 # be open before that and wrap the change-context.
2408 #
2419 #
2409 # - If you are not within a `dirstate.changing_*` context, why is our
2420 # - If you are not within a `dirstate.changing_*` context, why is our
2410 # dirstate dirty?
2421 # dirstate dirty?
2411 if self.dirstate._dirty:
2422 if self.dirstate._dirty:
2412 m = "cannot open a transaction with a dirty dirstate"
2423 m = "cannot open a transaction with a dirty dirstate"
2413 raise error.ProgrammingError(m)
2424 raise error.ProgrammingError(m)
2414
2425
2415 idbase = b"%.40f#%f" % (random.random(), time.time())
2426 idbase = b"%.40f#%f" % (random.random(), time.time())
2416 ha = hex(hashutil.sha1(idbase).digest())
2427 ha = hex(hashutil.sha1(idbase).digest())
2417 txnid = b'TXN:' + ha
2428 txnid = b'TXN:' + ha
2418 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2429 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2419
2430
2420 self._writejournal(desc)
2431 self._writejournal(desc)
2421 if report:
2432 if report:
2422 rp = report
2433 rp = report
2423 else:
2434 else:
2424 rp = self.ui.warn
2435 rp = self.ui.warn
2425 vfsmap = self.vfs_map
2436 vfsmap = self.vfs_map
2426 # we must avoid cyclic reference between repo and transaction.
2437 # we must avoid cyclic reference between repo and transaction.
2427 reporef = weakref.ref(self)
2438 reporef = weakref.ref(self)
2428 # Code to track tag movement
2439 # Code to track tag movement
2429 #
2440 #
2430 # Since tags are all handled as file content, it is actually quite hard
2441 # Since tags are all handled as file content, it is actually quite hard
2431 # to track these movement from a code perspective. So we fallback to a
2442 # to track these movement from a code perspective. So we fallback to a
2432 # tracking at the repository level. One could envision to track changes
2443 # tracking at the repository level. One could envision to track changes
2433 # to the '.hgtags' file through changegroup apply but that fails to
2444 # to the '.hgtags' file through changegroup apply but that fails to
2434 # cope with case where transaction expose new heads without changegroup
2445 # cope with case where transaction expose new heads without changegroup
2435 # being involved (eg: phase movement).
2446 # being involved (eg: phase movement).
2436 #
2447 #
2437 # For now, We gate the feature behind a flag since this likely comes
2448 # For now, We gate the feature behind a flag since this likely comes
2438 # with performance impacts. The current code run more often than needed
2449 # with performance impacts. The current code run more often than needed
2439 # and do not use caches as much as it could. The current focus is on
2450 # and do not use caches as much as it could. The current focus is on
2440 # the behavior of the feature so we disable it by default. The flag
2451 # the behavior of the feature so we disable it by default. The flag
2441 # will be removed when we are happy with the performance impact.
2452 # will be removed when we are happy with the performance impact.
2442 #
2453 #
2443 # Once this feature is no longer experimental move the following
2454 # Once this feature is no longer experimental move the following
2444 # documentation to the appropriate help section:
2455 # documentation to the appropriate help section:
2445 #
2456 #
2446 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2457 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2447 # tags (new or changed or deleted tags). In addition the details of
2458 # tags (new or changed or deleted tags). In addition the details of
2448 # these changes are made available in a file at:
2459 # these changes are made available in a file at:
2449 # ``REPOROOT/.hg/changes/tags.changes``.
2460 # ``REPOROOT/.hg/changes/tags.changes``.
2450 # Make sure you check for HG_TAG_MOVED before reading that file as it
2461 # Make sure you check for HG_TAG_MOVED before reading that file as it
2451 # might exist from a previous transaction even if no tag were touched
2462 # might exist from a previous transaction even if no tag were touched
2452 # in this one. Changes are recorded in a line base format::
2463 # in this one. Changes are recorded in a line base format::
2453 #
2464 #
2454 # <action> <hex-node> <tag-name>\n
2465 # <action> <hex-node> <tag-name>\n
2455 #
2466 #
2456 # Actions are defined as follow:
2467 # Actions are defined as follow:
2457 # "-R": tag is removed,
2468 # "-R": tag is removed,
2458 # "+A": tag is added,
2469 # "+A": tag is added,
2459 # "-M": tag is moved (old value),
2470 # "-M": tag is moved (old value),
2460 # "+M": tag is moved (new value),
2471 # "+M": tag is moved (new value),
2461 tracktags = lambda x: None
2472 tracktags = lambda x: None
2462 # experimental config: experimental.hook-track-tags
2473 # experimental config: experimental.hook-track-tags
2463 shouldtracktags = self.ui.configbool(
2474 shouldtracktags = self.ui.configbool(
2464 b'experimental', b'hook-track-tags'
2475 b'experimental', b'hook-track-tags'
2465 )
2476 )
2466 if desc != b'strip' and shouldtracktags:
2477 if desc != b'strip' and shouldtracktags:
2467 oldheads = self.changelog.headrevs()
2478 oldheads = self.changelog.headrevs()
2468
2479
2469 def tracktags(tr2):
2480 def tracktags(tr2):
2470 repo = reporef()
2481 repo = reporef()
2471 assert repo is not None # help pytype
2482 assert repo is not None # help pytype
2472 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2483 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2473 newheads = repo.changelog.headrevs()
2484 newheads = repo.changelog.headrevs()
2474 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2485 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2475 # notes: we compare lists here.
2486 # notes: we compare lists here.
2476 # As we do it only once buiding set would not be cheaper
2487 # As we do it only once buiding set would not be cheaper
2477 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2488 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2478 if changes:
2489 if changes:
2479 tr2.hookargs[b'tag_moved'] = b'1'
2490 tr2.hookargs[b'tag_moved'] = b'1'
2480 with repo.vfs(
2491 with repo.vfs(
2481 b'changes/tags.changes', b'w', atomictemp=True
2492 b'changes/tags.changes', b'w', atomictemp=True
2482 ) as changesfile:
2493 ) as changesfile:
2483 # note: we do not register the file to the transaction
2494 # note: we do not register the file to the transaction
2484 # because we needs it to still exist on the transaction
2495 # because we needs it to still exist on the transaction
2485 # is close (for txnclose hooks)
2496 # is close (for txnclose hooks)
2486 tagsmod.writediff(changesfile, changes)
2497 tagsmod.writediff(changesfile, changes)
2487
2498
2488 def validate(tr2):
2499 def validate(tr2):
2489 """will run pre-closing hooks"""
2500 """will run pre-closing hooks"""
2490 # XXX the transaction API is a bit lacking here so we take a hacky
2501 # XXX the transaction API is a bit lacking here so we take a hacky
2491 # path for now
2502 # path for now
2492 #
2503 #
2493 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2504 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2494 # dict is copied before these run. In addition we needs the data
2505 # dict is copied before these run. In addition we needs the data
2495 # available to in memory hooks too.
2506 # available to in memory hooks too.
2496 #
2507 #
2497 # Moreover, we also need to make sure this runs before txnclose
2508 # Moreover, we also need to make sure this runs before txnclose
2498 # hooks and there is no "pending" mechanism that would execute
2509 # hooks and there is no "pending" mechanism that would execute
2499 # logic only if hooks are about to run.
2510 # logic only if hooks are about to run.
2500 #
2511 #
2501 # Fixing this limitation of the transaction is also needed to track
2512 # Fixing this limitation of the transaction is also needed to track
2502 # other families of changes (bookmarks, phases, obsolescence).
2513 # other families of changes (bookmarks, phases, obsolescence).
2503 #
2514 #
2504 # This will have to be fixed before we remove the experimental
2515 # This will have to be fixed before we remove the experimental
2505 # gating.
2516 # gating.
2506 tracktags(tr2)
2517 tracktags(tr2)
2507 repo = reporef()
2518 repo = reporef()
2508 assert repo is not None # help pytype
2519 assert repo is not None # help pytype
2509
2520
2510 singleheadopt = (b'experimental', b'single-head-per-branch')
2521 singleheadopt = (b'experimental', b'single-head-per-branch')
2511 singlehead = repo.ui.configbool(*singleheadopt)
2522 singlehead = repo.ui.configbool(*singleheadopt)
2512 if singlehead:
2523 if singlehead:
2513 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2524 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2514 accountclosed = singleheadsub.get(
2525 accountclosed = singleheadsub.get(
2515 b"account-closed-heads", False
2526 b"account-closed-heads", False
2516 )
2527 )
2517 if singleheadsub.get(b"public-changes-only", False):
2528 if singleheadsub.get(b"public-changes-only", False):
2518 filtername = b"immutable"
2529 filtername = b"immutable"
2519 else:
2530 else:
2520 filtername = b"visible"
2531 filtername = b"visible"
2521 scmutil.enforcesinglehead(
2532 scmutil.enforcesinglehead(
2522 repo, tr2, desc, accountclosed, filtername
2533 repo, tr2, desc, accountclosed, filtername
2523 )
2534 )
2524 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2535 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2525 for name, (old, new) in sorted(
2536 for name, (old, new) in sorted(
2526 tr.changes[b'bookmarks'].items()
2537 tr.changes[b'bookmarks'].items()
2527 ):
2538 ):
2528 args = tr.hookargs.copy()
2539 args = tr.hookargs.copy()
2529 args.update(bookmarks.preparehookargs(name, old, new))
2540 args.update(bookmarks.preparehookargs(name, old, new))
2530 repo.hook(
2541 repo.hook(
2531 b'pretxnclose-bookmark',
2542 b'pretxnclose-bookmark',
2532 throw=True,
2543 throw=True,
2533 **pycompat.strkwargs(args)
2544 **pycompat.strkwargs(args)
2534 )
2545 )
2535 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2546 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2536 cl = repo.unfiltered().changelog
2547 cl = repo.unfiltered().changelog
2537 for revs, (old, new) in tr.changes[b'phases']:
2548 for revs, (old, new) in tr.changes[b'phases']:
2538 for rev in revs:
2549 for rev in revs:
2539 args = tr.hookargs.copy()
2550 args = tr.hookargs.copy()
2540 node = hex(cl.node(rev))
2551 node = hex(cl.node(rev))
2541 args.update(phases.preparehookargs(node, old, new))
2552 args.update(phases.preparehookargs(node, old, new))
2542 repo.hook(
2553 repo.hook(
2543 b'pretxnclose-phase',
2554 b'pretxnclose-phase',
2544 throw=True,
2555 throw=True,
2545 **pycompat.strkwargs(args)
2556 **pycompat.strkwargs(args)
2546 )
2557 )
2547
2558
2548 repo.hook(
2559 repo.hook(
2549 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2560 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2550 )
2561 )
2551
2562
2552 def releasefn(tr, success):
2563 def releasefn(tr, success):
2553 repo = reporef()
2564 repo = reporef()
2554 if repo is None:
2565 if repo is None:
2555 # If the repo has been GC'd (and this release function is being
2566 # If the repo has been GC'd (and this release function is being
2556 # called from transaction.__del__), there's not much we can do,
2567 # called from transaction.__del__), there's not much we can do,
2557 # so just leave the unfinished transaction there and let the
2568 # so just leave the unfinished transaction there and let the
2558 # user run `hg recover`.
2569 # user run `hg recover`.
2559 return
2570 return
2560 if success:
2571 if success:
2561 # this should be explicitly invoked here, because
2572 # this should be explicitly invoked here, because
2562 # in-memory changes aren't written out at closing
2573 # in-memory changes aren't written out at closing
2563 # transaction, if tr.addfilegenerator (via
2574 # transaction, if tr.addfilegenerator (via
2564 # dirstate.write or so) isn't invoked while
2575 # dirstate.write or so) isn't invoked while
2565 # transaction running
2576 # transaction running
2566 repo.dirstate.write(None)
2577 repo.dirstate.write(None)
2567 else:
2578 else:
2568 # discard all changes (including ones already written
2579 # discard all changes (including ones already written
2569 # out) in this transaction
2580 # out) in this transaction
2570 repo.invalidate(clearfilecache=True)
2581 repo.invalidate(clearfilecache=True)
2571
2582
2572 tr = transaction.transaction(
2583 tr = transaction.transaction(
2573 rp,
2584 rp,
2574 self.svfs,
2585 self.svfs,
2575 vfsmap,
2586 vfsmap,
2576 b"journal",
2587 b"journal",
2577 b"undo",
2588 b"undo",
2578 lambda: None,
2589 lambda: None,
2579 self.store.createmode,
2590 self.store.createmode,
2580 validator=validate,
2591 validator=validate,
2581 releasefn=releasefn,
2592 releasefn=releasefn,
2582 checkambigfiles=_cachedfiles,
2593 checkambigfiles=_cachedfiles,
2583 name=desc,
2594 name=desc,
2584 )
2595 )
2585 for vfs_id, path in self._journalfiles():
2596 for vfs_id, path in self._journalfiles():
2586 tr.add_journal(vfs_id, path)
2597 tr.add_journal(vfs_id, path)
2587 tr.changes[b'origrepolen'] = len(self)
2598 tr.changes[b'origrepolen'] = len(self)
2588 tr.changes[b'obsmarkers'] = set()
2599 tr.changes[b'obsmarkers'] = set()
2589 tr.changes[b'phases'] = []
2600 tr.changes[b'phases'] = []
2590 tr.changes[b'bookmarks'] = {}
2601 tr.changes[b'bookmarks'] = {}
2591
2602
2592 tr.hookargs[b'txnid'] = txnid
2603 tr.hookargs[b'txnid'] = txnid
2593 tr.hookargs[b'txnname'] = desc
2604 tr.hookargs[b'txnname'] = desc
2594 tr.hookargs[b'changes'] = tr.changes
2605 tr.hookargs[b'changes'] = tr.changes
2595 # note: writing the fncache only during finalize mean that the file is
2606 # note: writing the fncache only during finalize mean that the file is
2596 # outdated when running hooks. As fncache is used for streaming clone,
2607 # outdated when running hooks. As fncache is used for streaming clone,
2597 # this is not expected to break anything that happen during the hooks.
2608 # this is not expected to break anything that happen during the hooks.
2598 tr.addfinalize(b'flush-fncache', self.store.write)
2609 tr.addfinalize(b'flush-fncache', self.store.write)
2599
2610
2600 def txnclosehook(tr2):
2611 def txnclosehook(tr2):
2601 """To be run if transaction is successful, will schedule a hook run"""
2612 """To be run if transaction is successful, will schedule a hook run"""
2602 # Don't reference tr2 in hook() so we don't hold a reference.
2613 # Don't reference tr2 in hook() so we don't hold a reference.
2603 # This reduces memory consumption when there are multiple
2614 # This reduces memory consumption when there are multiple
2604 # transactions per lock. This can likely go away if issue5045
2615 # transactions per lock. This can likely go away if issue5045
2605 # fixes the function accumulation.
2616 # fixes the function accumulation.
2606 hookargs = tr2.hookargs
2617 hookargs = tr2.hookargs
2607
2618
2608 def hookfunc(unused_success):
2619 def hookfunc(unused_success):
2609 repo = reporef()
2620 repo = reporef()
2610 assert repo is not None # help pytype
2621 assert repo is not None # help pytype
2611
2622
2612 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2623 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2613 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2624 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2614 for name, (old, new) in bmchanges:
2625 for name, (old, new) in bmchanges:
2615 args = tr.hookargs.copy()
2626 args = tr.hookargs.copy()
2616 args.update(bookmarks.preparehookargs(name, old, new))
2627 args.update(bookmarks.preparehookargs(name, old, new))
2617 repo.hook(
2628 repo.hook(
2618 b'txnclose-bookmark',
2629 b'txnclose-bookmark',
2619 throw=False,
2630 throw=False,
2620 **pycompat.strkwargs(args)
2631 **pycompat.strkwargs(args)
2621 )
2632 )
2622
2633
2623 if hook.hashook(repo.ui, b'txnclose-phase'):
2634 if hook.hashook(repo.ui, b'txnclose-phase'):
2624 cl = repo.unfiltered().changelog
2635 cl = repo.unfiltered().changelog
2625 phasemv = sorted(
2636 phasemv = sorted(
2626 tr.changes[b'phases'], key=lambda r: r[0][0]
2637 tr.changes[b'phases'], key=lambda r: r[0][0]
2627 )
2638 )
2628 for revs, (old, new) in phasemv:
2639 for revs, (old, new) in phasemv:
2629 for rev in revs:
2640 for rev in revs:
2630 args = tr.hookargs.copy()
2641 args = tr.hookargs.copy()
2631 node = hex(cl.node(rev))
2642 node = hex(cl.node(rev))
2632 args.update(phases.preparehookargs(node, old, new))
2643 args.update(phases.preparehookargs(node, old, new))
2633 repo.hook(
2644 repo.hook(
2634 b'txnclose-phase',
2645 b'txnclose-phase',
2635 throw=False,
2646 throw=False,
2636 **pycompat.strkwargs(args)
2647 **pycompat.strkwargs(args)
2637 )
2648 )
2638
2649
2639 repo.hook(
2650 repo.hook(
2640 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2651 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2641 )
2652 )
2642
2653
2643 repo = reporef()
2654 repo = reporef()
2644 assert repo is not None # help pytype
2655 assert repo is not None # help pytype
2645 repo._afterlock(hookfunc)
2656 repo._afterlock(hookfunc)
2646
2657
2647 tr.addfinalize(b'txnclose-hook', txnclosehook)
2658 tr.addfinalize(b'txnclose-hook', txnclosehook)
2648 # Include a leading "-" to make it happen before the transaction summary
2659 # Include a leading "-" to make it happen before the transaction summary
2649 # reports registered via scmutil.registersummarycallback() whose names
2660 # reports registered via scmutil.registersummarycallback() whose names
2650 # are 00-txnreport etc. That way, the caches will be warm when the
2661 # are 00-txnreport etc. That way, the caches will be warm when the
2651 # callbacks run.
2662 # callbacks run.
2652 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2663 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2653
2664
2654 def txnaborthook(tr2):
2665 def txnaborthook(tr2):
2655 """To be run if transaction is aborted"""
2666 """To be run if transaction is aborted"""
2656 repo = reporef()
2667 repo = reporef()
2657 assert repo is not None # help pytype
2668 assert repo is not None # help pytype
2658 repo.hook(
2669 repo.hook(
2659 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2670 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2660 )
2671 )
2661
2672
2662 tr.addabort(b'txnabort-hook', txnaborthook)
2673 tr.addabort(b'txnabort-hook', txnaborthook)
2663 # avoid eager cache invalidation. in-memory data should be identical
2674 # avoid eager cache invalidation. in-memory data should be identical
2664 # to stored data if transaction has no error.
2675 # to stored data if transaction has no error.
2665 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2676 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2666 self._transref = weakref.ref(tr)
2677 self._transref = weakref.ref(tr)
2667 scmutil.registersummarycallback(self, tr, desc)
2678 scmutil.registersummarycallback(self, tr, desc)
2668 # This only exist to deal with the need of rollback to have viable
2679 # This only exist to deal with the need of rollback to have viable
2669 # parents at the end of the operation. So backup viable parents at the
2680 # parents at the end of the operation. So backup viable parents at the
2670 # time of this operation.
2681 # time of this operation.
2671 #
2682 #
2672 # We only do it when the `wlock` is taken, otherwise other might be
2683 # We only do it when the `wlock` is taken, otherwise other might be
2673 # altering the dirstate under us.
2684 # altering the dirstate under us.
2674 #
2685 #
2675 # This is really not a great way to do this (first, because we cannot
2686 # This is really not a great way to do this (first, because we cannot
2676 # always do it). There are more viable alternative that exists
2687 # always do it). There are more viable alternative that exists
2677 #
2688 #
2678 # - backing only the working copy parent in a dedicated files and doing
2689 # - backing only the working copy parent in a dedicated files and doing
2679 # a clean "keep-update" to them on `hg rollback`.
2690 # a clean "keep-update" to them on `hg rollback`.
2680 #
2691 #
2681 # - slightly changing the behavior an applying a logic similar to "hg
2692 # - slightly changing the behavior an applying a logic similar to "hg
2682 # strip" to pick a working copy destination on `hg rollback`
2693 # strip" to pick a working copy destination on `hg rollback`
2683 if self.currentwlock() is not None:
2694 if self.currentwlock() is not None:
2684 ds = self.dirstate
2695 ds = self.dirstate
2685 if not self.vfs.exists(b'branch'):
2696 if not self.vfs.exists(b'branch'):
2686 # force a file to be written if None exist
2697 # force a file to be written if None exist
2687 ds.setbranch(b'default', None)
2698 ds.setbranch(b'default', None)
2688
2699
2689 def backup_dirstate(tr):
2700 def backup_dirstate(tr):
2690 for f in ds.all_file_names():
2701 for f in ds.all_file_names():
2691 # hardlink backup is okay because `dirstate` is always
2702 # hardlink backup is okay because `dirstate` is always
2692 # atomically written and possible data file are append only
2703 # atomically written and possible data file are append only
2693 # and resistant to trailing data.
2704 # and resistant to trailing data.
2694 tr.addbackup(f, hardlink=True, location=b'plain')
2705 tr.addbackup(f, hardlink=True, location=b'plain')
2695
2706
2696 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2707 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2697 return tr
2708 return tr
2698
2709
2699 def _journalfiles(self):
2710 def _journalfiles(self):
2700 return (
2711 return (
2701 (self.svfs, b'journal'),
2712 (self.svfs, b'journal'),
2702 (self.vfs, b'journal.desc'),
2713 (self.vfs, b'journal.desc'),
2703 )
2714 )
2704
2715
2705 def undofiles(self):
2716 def undofiles(self):
2706 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2717 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2707
2718
2708 @unfilteredmethod
2719 @unfilteredmethod
2709 def _writejournal(self, desc):
2720 def _writejournal(self, desc):
2710 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2721 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2711
2722
2712 def recover(self):
2723 def recover(self):
2713 with self.lock():
2724 with self.lock():
2714 if self.svfs.exists(b"journal"):
2725 if self.svfs.exists(b"journal"):
2715 self.ui.status(_(b"rolling back interrupted transaction\n"))
2726 self.ui.status(_(b"rolling back interrupted transaction\n"))
2716 vfsmap = self.vfs_map
2727 vfsmap = self.vfs_map
2717 transaction.rollback(
2728 transaction.rollback(
2718 self.svfs,
2729 self.svfs,
2719 vfsmap,
2730 vfsmap,
2720 b"journal",
2731 b"journal",
2721 self.ui.warn,
2732 self.ui.warn,
2722 checkambigfiles=_cachedfiles,
2733 checkambigfiles=_cachedfiles,
2723 )
2734 )
2724 self.invalidate()
2735 self.invalidate()
2725 return True
2736 return True
2726 else:
2737 else:
2727 self.ui.warn(_(b"no interrupted transaction available\n"))
2738 self.ui.warn(_(b"no interrupted transaction available\n"))
2728 return False
2739 return False
2729
2740
2730 def rollback(self, dryrun=False, force=False):
2741 def rollback(self, dryrun=False, force=False):
2731 wlock = lock = None
2742 wlock = lock = None
2732 try:
2743 try:
2733 wlock = self.wlock()
2744 wlock = self.wlock()
2734 lock = self.lock()
2745 lock = self.lock()
2735 if self.svfs.exists(b"undo"):
2746 if self.svfs.exists(b"undo"):
2736 return self._rollback(dryrun, force)
2747 return self._rollback(dryrun, force)
2737 else:
2748 else:
2738 self.ui.warn(_(b"no rollback information available\n"))
2749 self.ui.warn(_(b"no rollback information available\n"))
2739 return 1
2750 return 1
2740 finally:
2751 finally:
2741 release(lock, wlock)
2752 release(lock, wlock)
2742
2753
2743 @unfilteredmethod # Until we get smarter cache management
2754 @unfilteredmethod # Until we get smarter cache management
2744 def _rollback(self, dryrun, force):
2755 def _rollback(self, dryrun, force):
2745 ui = self.ui
2756 ui = self.ui
2746
2757
2747 parents = self.dirstate.parents()
2758 parents = self.dirstate.parents()
2748 try:
2759 try:
2749 args = self.vfs.read(b'undo.desc').splitlines()
2760 args = self.vfs.read(b'undo.desc').splitlines()
2750 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2761 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2751 if len(args) >= 3:
2762 if len(args) >= 3:
2752 detail = args[2]
2763 detail = args[2]
2753 oldtip = oldlen - 1
2764 oldtip = oldlen - 1
2754
2765
2755 if detail and ui.verbose:
2766 if detail and ui.verbose:
2756 msg = _(
2767 msg = _(
2757 b'repository tip rolled back to revision %d'
2768 b'repository tip rolled back to revision %d'
2758 b' (undo %s: %s)\n'
2769 b' (undo %s: %s)\n'
2759 ) % (oldtip, desc, detail)
2770 ) % (oldtip, desc, detail)
2760 else:
2771 else:
2761 msg = _(
2772 msg = _(
2762 b'repository tip rolled back to revision %d (undo %s)\n'
2773 b'repository tip rolled back to revision %d (undo %s)\n'
2763 ) % (oldtip, desc)
2774 ) % (oldtip, desc)
2764 parentgone = any(self[p].rev() > oldtip for p in parents)
2775 parentgone = any(self[p].rev() > oldtip for p in parents)
2765 except IOError:
2776 except IOError:
2766 msg = _(b'rolling back unknown transaction\n')
2777 msg = _(b'rolling back unknown transaction\n')
2767 desc = None
2778 desc = None
2768 parentgone = True
2779 parentgone = True
2769
2780
2770 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2781 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2771 raise error.Abort(
2782 raise error.Abort(
2772 _(
2783 _(
2773 b'rollback of last commit while not checked out '
2784 b'rollback of last commit while not checked out '
2774 b'may lose data'
2785 b'may lose data'
2775 ),
2786 ),
2776 hint=_(b'use -f to force'),
2787 hint=_(b'use -f to force'),
2777 )
2788 )
2778
2789
2779 ui.status(msg)
2790 ui.status(msg)
2780 if dryrun:
2791 if dryrun:
2781 return 0
2792 return 0
2782
2793
2783 self.destroying()
2794 self.destroying()
2784 vfsmap = self.vfs_map
2795 vfsmap = self.vfs_map
2785 skip_journal_pattern = None
2796 skip_journal_pattern = None
2786 if not parentgone:
2797 if not parentgone:
2787 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2798 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2788 transaction.rollback(
2799 transaction.rollback(
2789 self.svfs,
2800 self.svfs,
2790 vfsmap,
2801 vfsmap,
2791 b'undo',
2802 b'undo',
2792 ui.warn,
2803 ui.warn,
2793 checkambigfiles=_cachedfiles,
2804 checkambigfiles=_cachedfiles,
2794 skip_journal_pattern=skip_journal_pattern,
2805 skip_journal_pattern=skip_journal_pattern,
2795 )
2806 )
2796 self.invalidate()
2807 self.invalidate()
2797 self.dirstate.invalidate()
2808 self.dirstate.invalidate()
2798
2809
2799 if parentgone:
2810 if parentgone:
2800 # replace this with some explicit parent update in the future.
2811 # replace this with some explicit parent update in the future.
2801 has_node = self.changelog.index.has_node
2812 has_node = self.changelog.index.has_node
2802 if not all(has_node(p) for p in self.dirstate._pl):
2813 if not all(has_node(p) for p in self.dirstate._pl):
2803 # There was no dirstate to backup initially, we need to drop
2814 # There was no dirstate to backup initially, we need to drop
2804 # the existing one.
2815 # the existing one.
2805 with self.dirstate.changing_parents(self):
2816 with self.dirstate.changing_parents(self):
2806 self.dirstate.setparents(self.nullid)
2817 self.dirstate.setparents(self.nullid)
2807 self.dirstate.clear()
2818 self.dirstate.clear()
2808
2819
2809 parents = tuple([p.rev() for p in self[None].parents()])
2820 parents = tuple([p.rev() for p in self[None].parents()])
2810 if len(parents) > 1:
2821 if len(parents) > 1:
2811 ui.status(
2822 ui.status(
2812 _(
2823 _(
2813 b'working directory now based on '
2824 b'working directory now based on '
2814 b'revisions %d and %d\n'
2825 b'revisions %d and %d\n'
2815 )
2826 )
2816 % parents
2827 % parents
2817 )
2828 )
2818 else:
2829 else:
2819 ui.status(
2830 ui.status(
2820 _(b'working directory now based on revision %d\n') % parents
2831 _(b'working directory now based on revision %d\n') % parents
2821 )
2832 )
2822 mergestatemod.mergestate.clean(self)
2833 mergestatemod.mergestate.clean(self)
2823
2834
2824 # TODO: if we know which new heads may result from this rollback, pass
2835 # TODO: if we know which new heads may result from this rollback, pass
2825 # them to destroy(), which will prevent the branchhead cache from being
2836 # them to destroy(), which will prevent the branchhead cache from being
2826 # invalidated.
2837 # invalidated.
2827 self.destroyed()
2838 self.destroyed()
2828 return 0
2839 return 0
2829
2840
2830 def _buildcacheupdater(self, newtransaction):
2841 def _buildcacheupdater(self, newtransaction):
2831 """called during transaction to build the callback updating cache
2842 """called during transaction to build the callback updating cache
2832
2843
2833 Lives on the repository to help extension who might want to augment
2844 Lives on the repository to help extension who might want to augment
2834 this logic. For this purpose, the created transaction is passed to the
2845 this logic. For this purpose, the created transaction is passed to the
2835 method.
2846 method.
2836 """
2847 """
2837 # we must avoid cyclic reference between repo and transaction.
2848 # we must avoid cyclic reference between repo and transaction.
2838 reporef = weakref.ref(self)
2849 reporef = weakref.ref(self)
2839
2850
2840 def updater(tr):
2851 def updater(tr):
2841 repo = reporef()
2852 repo = reporef()
2842 assert repo is not None # help pytype
2853 assert repo is not None # help pytype
2843 repo.updatecaches(tr)
2854 repo.updatecaches(tr)
2844
2855
2845 return updater
2856 return updater
2846
2857
2847 @unfilteredmethod
2858 @unfilteredmethod
2848 def updatecaches(self, tr=None, full=False, caches=None):
2859 def updatecaches(self, tr=None, full=False, caches=None):
2849 """warm appropriate caches
2860 """warm appropriate caches
2850
2861
2851 If this function is called after a transaction closed. The transaction
2862 If this function is called after a transaction closed. The transaction
2852 will be available in the 'tr' argument. This can be used to selectively
2863 will be available in the 'tr' argument. This can be used to selectively
2853 update caches relevant to the changes in that transaction.
2864 update caches relevant to the changes in that transaction.
2854
2865
2855 If 'full' is set, make sure all caches the function knows about have
2866 If 'full' is set, make sure all caches the function knows about have
2856 up-to-date data. Even the ones usually loaded more lazily.
2867 up-to-date data. Even the ones usually loaded more lazily.
2857
2868
2858 The `full` argument can take a special "post-clone" value. In this case
2869 The `full` argument can take a special "post-clone" value. In this case
2859 the cache warming is made after a clone and of the slower cache might
2870 the cache warming is made after a clone and of the slower cache might
2860 be skipped, namely the `.fnodetags` one. This argument is 5.8 specific
2871 be skipped, namely the `.fnodetags` one. This argument is 5.8 specific
2861 as we plan for a cleaner way to deal with this for 5.9.
2872 as we plan for a cleaner way to deal with this for 5.9.
2862 """
2873 """
2863 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2874 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2864 # During strip, many caches are invalid but
2875 # During strip, many caches are invalid but
2865 # later call to `destroyed` will refresh them.
2876 # later call to `destroyed` will refresh them.
2866 return
2877 return
2867
2878
2868 unfi = self.unfiltered()
2879 unfi = self.unfiltered()
2869
2880
2870 if full:
2881 if full:
2871 msg = (
2882 msg = (
2872 "`full` argument for `repo.updatecaches` is deprecated\n"
2883 "`full` argument for `repo.updatecaches` is deprecated\n"
2873 "(use `caches=repository.CACHE_ALL` instead)"
2884 "(use `caches=repository.CACHE_ALL` instead)"
2874 )
2885 )
2875 self.ui.deprecwarn(msg, b"5.9")
2886 self.ui.deprecwarn(msg, b"5.9")
2876 caches = repository.CACHES_ALL
2887 caches = repository.CACHES_ALL
2877 if full == b"post-clone":
2888 if full == b"post-clone":
2878 caches = repository.CACHES_POST_CLONE
2889 caches = repository.CACHES_POST_CLONE
2879 caches = repository.CACHES_ALL
2890 caches = repository.CACHES_ALL
2880 elif caches is None:
2891 elif caches is None:
2881 caches = repository.CACHES_DEFAULT
2892 caches = repository.CACHES_DEFAULT
2882
2893
2883 if repository.CACHE_BRANCHMAP_SERVED in caches:
2894 if repository.CACHE_BRANCHMAP_SERVED in caches:
2884 if tr is None or tr.changes[b'origrepolen'] < len(self):
2895 if tr is None or tr.changes[b'origrepolen'] < len(self):
2885 # accessing the 'served' branchmap should refresh all the others,
2896 # accessing the 'served' branchmap should refresh all the others,
2886 self.ui.debug(b'updating the branch cache\n')
2897 self.ui.debug(b'updating the branch cache\n')
2887 self.filtered(b'served').branchmap()
2898 self.filtered(b'served').branchmap()
2888 self.filtered(b'served.hidden').branchmap()
2899 self.filtered(b'served.hidden').branchmap()
2889 # flush all possibly delayed write.
2900 # flush all possibly delayed write.
2890 self._branchcaches.write_delayed(self)
2901 self._branchcaches.write_delayed(self)
2891
2902
2892 if repository.CACHE_CHANGELOG_CACHE in caches:
2903 if repository.CACHE_CHANGELOG_CACHE in caches:
2893 self.changelog.update_caches(transaction=tr)
2904 self.changelog.update_caches(transaction=tr)
2894
2905
2895 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2906 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2896 self.manifestlog.update_caches(transaction=tr)
2907 self.manifestlog.update_caches(transaction=tr)
2897
2908
2898 if repository.CACHE_REV_BRANCH in caches:
2909 if repository.CACHE_REV_BRANCH in caches:
2899 rbc = unfi.revbranchcache()
2910 rbc = unfi.revbranchcache()
2900 for r in unfi.changelog:
2911 for r in unfi.changelog:
2901 rbc.branchinfo(r)
2912 rbc.branchinfo(r)
2902 rbc.write()
2913 rbc.write()
2903
2914
2904 if repository.CACHE_FULL_MANIFEST in caches:
2915 if repository.CACHE_FULL_MANIFEST in caches:
2905 # ensure the working copy parents are in the manifestfulltextcache
2916 # ensure the working copy parents are in the manifestfulltextcache
2906 for ctx in self[b'.'].parents():
2917 for ctx in self[b'.'].parents():
2907 ctx.manifest() # accessing the manifest is enough
2918 ctx.manifest() # accessing the manifest is enough
2908
2919
2909 if repository.CACHE_FILE_NODE_TAGS in caches:
2920 if repository.CACHE_FILE_NODE_TAGS in caches:
2910 # accessing fnode cache warms the cache
2921 # accessing fnode cache warms the cache
2911 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2922 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2912
2923
2913 if repository.CACHE_TAGS_DEFAULT in caches:
2924 if repository.CACHE_TAGS_DEFAULT in caches:
2914 # accessing tags warm the cache
2925 # accessing tags warm the cache
2915 self.tags()
2926 self.tags()
2916 if repository.CACHE_TAGS_SERVED in caches:
2927 if repository.CACHE_TAGS_SERVED in caches:
2917 self.filtered(b'served').tags()
2928 self.filtered(b'served').tags()
2918
2929
2919 if repository.CACHE_BRANCHMAP_ALL in caches:
2930 if repository.CACHE_BRANCHMAP_ALL in caches:
2920 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2931 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2921 # so we're forcing a write to cause these caches to be warmed up
2932 # so we're forcing a write to cause these caches to be warmed up
2922 # even if they haven't explicitly been requested yet (if they've
2933 # even if they haven't explicitly been requested yet (if they've
2923 # never been used by hg, they won't ever have been written, even if
2934 # never been used by hg, they won't ever have been written, even if
2924 # they're a subset of another kind of cache that *has* been used).
2935 # they're a subset of another kind of cache that *has* been used).
2925 for filt in repoview.filtertable.keys():
2936 for filt in repoview.filtertable.keys():
2926 filtered = self.filtered(filt)
2937 filtered = self.filtered(filt)
2927 filtered.branchmap().write(filtered)
2938 filtered.branchmap().write(filtered)
2928
2939
2929 def invalidatecaches(self):
2940 def invalidatecaches(self):
2930 if '_tagscache' in vars(self):
2941 if '_tagscache' in vars(self):
2931 # can't use delattr on proxy
2942 # can't use delattr on proxy
2932 del self.__dict__['_tagscache']
2943 del self.__dict__['_tagscache']
2933
2944
2934 self._branchcaches.clear()
2945 self._branchcaches.clear()
2935 self.invalidatevolatilesets()
2946 self.invalidatevolatilesets()
2936 self._sparsesignaturecache.clear()
2947 self._sparsesignaturecache.clear()
2937
2948
2938 def invalidatevolatilesets(self):
2949 def invalidatevolatilesets(self):
2939 self.filteredrevcache.clear()
2950 self.filteredrevcache.clear()
2940 obsolete.clearobscaches(self)
2951 obsolete.clearobscaches(self)
2941 self._quick_access_changeid_invalidate()
2952 self._quick_access_changeid_invalidate()
2942
2953
2943 def invalidatedirstate(self):
2954 def invalidatedirstate(self):
2944 """Invalidates the dirstate, causing the next call to dirstate
2955 """Invalidates the dirstate, causing the next call to dirstate
2945 to check if it was modified since the last time it was read,
2956 to check if it was modified since the last time it was read,
2946 rereading it if it has.
2957 rereading it if it has.
2947
2958
2948 This is different to dirstate.invalidate() that it doesn't always
2959 This is different to dirstate.invalidate() that it doesn't always
2949 rereads the dirstate. Use dirstate.invalidate() if you want to
2960 rereads the dirstate. Use dirstate.invalidate() if you want to
2950 explicitly read the dirstate again (i.e. restoring it to a previous
2961 explicitly read the dirstate again (i.e. restoring it to a previous
2951 known good state)."""
2962 known good state)."""
2952 unfi = self.unfiltered()
2963 unfi = self.unfiltered()
2953 if 'dirstate' in unfi.__dict__:
2964 if 'dirstate' in unfi.__dict__:
2954 assert not self.dirstate.is_changing_any
2965 assert not self.dirstate.is_changing_any
2955 del unfi.__dict__['dirstate']
2966 del unfi.__dict__['dirstate']
2956
2967
2957 def invalidate(self, clearfilecache=False):
2968 def invalidate(self, clearfilecache=False):
2958 """Invalidates both store and non-store parts other than dirstate
2969 """Invalidates both store and non-store parts other than dirstate
2959
2970
2960 If a transaction is running, invalidation of store is omitted,
2971 If a transaction is running, invalidation of store is omitted,
2961 because discarding in-memory changes might cause inconsistency
2972 because discarding in-memory changes might cause inconsistency
2962 (e.g. incomplete fncache causes unintentional failure, but
2973 (e.g. incomplete fncache causes unintentional failure, but
2963 redundant one doesn't).
2974 redundant one doesn't).
2964 """
2975 """
2965 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2976 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2966 for k in list(self._filecache.keys()):
2977 for k in list(self._filecache.keys()):
2967 if (
2978 if (
2968 k == b'changelog'
2979 k == b'changelog'
2969 and self.currenttransaction()
2980 and self.currenttransaction()
2970 and self.changelog._delayed
2981 and self.changelog._delayed
2971 ):
2982 ):
2972 # The changelog object may store unwritten revisions. We don't
2983 # The changelog object may store unwritten revisions. We don't
2973 # want to lose them.
2984 # want to lose them.
2974 # TODO: Solve the problem instead of working around it.
2985 # TODO: Solve the problem instead of working around it.
2975 continue
2986 continue
2976
2987
2977 if clearfilecache:
2988 if clearfilecache:
2978 del self._filecache[k]
2989 del self._filecache[k]
2979 try:
2990 try:
2980 delattr(unfiltered, k)
2991 delattr(unfiltered, k)
2981 except AttributeError:
2992 except AttributeError:
2982 pass
2993 pass
2983 self.invalidatecaches()
2994 self.invalidatecaches()
2984 if not self.currenttransaction():
2995 if not self.currenttransaction():
2985 # TODO: Changing contents of store outside transaction
2996 # TODO: Changing contents of store outside transaction
2986 # causes inconsistency. We should make in-memory store
2997 # causes inconsistency. We should make in-memory store
2987 # changes detectable, and abort if changed.
2998 # changes detectable, and abort if changed.
2988 self.store.invalidatecaches()
2999 self.store.invalidatecaches()
2989
3000
2990 def invalidateall(self):
3001 def invalidateall(self):
2991 """Fully invalidates both store and non-store parts, causing the
3002 """Fully invalidates both store and non-store parts, causing the
2992 subsequent operation to reread any outside changes."""
3003 subsequent operation to reread any outside changes."""
2993 # extension should hook this to invalidate its caches
3004 # extension should hook this to invalidate its caches
2994 self.invalidate()
3005 self.invalidate()
2995 self.invalidatedirstate()
3006 self.invalidatedirstate()
2996
3007
2997 @unfilteredmethod
3008 @unfilteredmethod
2998 def _refreshfilecachestats(self, tr):
3009 def _refreshfilecachestats(self, tr):
2999 """Reload stats of cached files so that they are flagged as valid"""
3010 """Reload stats of cached files so that they are flagged as valid"""
3000 for k, ce in self._filecache.items():
3011 for k, ce in self._filecache.items():
3001 k = pycompat.sysstr(k)
3012 k = pycompat.sysstr(k)
3002 if k == 'dirstate' or k not in self.__dict__:
3013 if k == 'dirstate' or k not in self.__dict__:
3003 continue
3014 continue
3004 ce.refresh()
3015 ce.refresh()
3005
3016
3006 def _lock(
3017 def _lock(
3007 self,
3018 self,
3008 vfs,
3019 vfs,
3009 lockname,
3020 lockname,
3010 wait,
3021 wait,
3011 releasefn,
3022 releasefn,
3012 acquirefn,
3023 acquirefn,
3013 desc,
3024 desc,
3014 ):
3025 ):
3015 timeout = 0
3026 timeout = 0
3016 warntimeout = 0
3027 warntimeout = 0
3017 if wait:
3028 if wait:
3018 timeout = self.ui.configint(b"ui", b"timeout")
3029 timeout = self.ui.configint(b"ui", b"timeout")
3019 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3030 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3020 # internal config: ui.signal-safe-lock
3031 # internal config: ui.signal-safe-lock
3021 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3032 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3022
3033
3023 l = lockmod.trylock(
3034 l = lockmod.trylock(
3024 self.ui,
3035 self.ui,
3025 vfs,
3036 vfs,
3026 lockname,
3037 lockname,
3027 timeout,
3038 timeout,
3028 warntimeout,
3039 warntimeout,
3029 releasefn=releasefn,
3040 releasefn=releasefn,
3030 acquirefn=acquirefn,
3041 acquirefn=acquirefn,
3031 desc=desc,
3042 desc=desc,
3032 signalsafe=signalsafe,
3043 signalsafe=signalsafe,
3033 )
3044 )
3034 return l
3045 return l
3035
3046
3036 def _afterlock(self, callback):
3047 def _afterlock(self, callback):
3037 """add a callback to be run when the repository is fully unlocked
3048 """add a callback to be run when the repository is fully unlocked
3038
3049
3039 The callback will be executed when the outermost lock is released
3050 The callback will be executed when the outermost lock is released
3040 (with wlock being higher level than 'lock')."""
3051 (with wlock being higher level than 'lock')."""
3041 for ref in (self._wlockref, self._lockref):
3052 for ref in (self._wlockref, self._lockref):
3042 l = ref and ref()
3053 l = ref and ref()
3043 if l and l.held:
3054 if l and l.held:
3044 l.postrelease.append(callback)
3055 l.postrelease.append(callback)
3045 break
3056 break
3046 else: # no lock have been found.
3057 else: # no lock have been found.
3047 callback(True)
3058 callback(True)
3048
3059
3049 def lock(self, wait=True):
3060 def lock(self, wait=True):
3050 """Lock the repository store (.hg/store) and return a weak reference
3061 """Lock the repository store (.hg/store) and return a weak reference
3051 to the lock. Use this before modifying the store (e.g. committing or
3062 to the lock. Use this before modifying the store (e.g. committing or
3052 stripping). If you are opening a transaction, get a lock as well.)
3063 stripping). If you are opening a transaction, get a lock as well.)
3053
3064
3054 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3065 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3055 'wlock' first to avoid a dead-lock hazard."""
3066 'wlock' first to avoid a dead-lock hazard."""
3056 l = self._currentlock(self._lockref)
3067 l = self._currentlock(self._lockref)
3057 if l is not None:
3068 if l is not None:
3058 l.lock()
3069 l.lock()
3059 return l
3070 return l
3060
3071
3061 l = self._lock(
3072 l = self._lock(
3062 vfs=self.svfs,
3073 vfs=self.svfs,
3063 lockname=b"lock",
3074 lockname=b"lock",
3064 wait=wait,
3075 wait=wait,
3065 releasefn=None,
3076 releasefn=None,
3066 acquirefn=self.invalidate,
3077 acquirefn=self.invalidate,
3067 desc=_(b'repository %s') % self.origroot,
3078 desc=_(b'repository %s') % self.origroot,
3068 )
3079 )
3069 self._lockref = weakref.ref(l)
3080 self._lockref = weakref.ref(l)
3070 return l
3081 return l
3071
3082
3072 def wlock(self, wait=True):
3083 def wlock(self, wait=True):
3073 """Lock the non-store parts of the repository (everything under
3084 """Lock the non-store parts of the repository (everything under
3074 .hg except .hg/store) and return a weak reference to the lock.
3085 .hg except .hg/store) and return a weak reference to the lock.
3075
3086
3076 Use this before modifying files in .hg.
3087 Use this before modifying files in .hg.
3077
3088
3078 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3089 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3079 'wlock' first to avoid a dead-lock hazard."""
3090 'wlock' first to avoid a dead-lock hazard."""
3080 l = self._wlockref() if self._wlockref else None
3091 l = self._wlockref() if self._wlockref else None
3081 if l is not None and l.held:
3092 if l is not None and l.held:
3082 l.lock()
3093 l.lock()
3083 return l
3094 return l
3084
3095
3085 # We do not need to check for non-waiting lock acquisition. Such
3096 # We do not need to check for non-waiting lock acquisition. Such
3086 # acquisition would not cause dead-lock as they would just fail.
3097 # acquisition would not cause dead-lock as they would just fail.
3087 if wait and (
3098 if wait and (
3088 self.ui.configbool(b'devel', b'all-warnings')
3099 self.ui.configbool(b'devel', b'all-warnings')
3089 or self.ui.configbool(b'devel', b'check-locks')
3100 or self.ui.configbool(b'devel', b'check-locks')
3090 ):
3101 ):
3091 if self._currentlock(self._lockref) is not None:
3102 if self._currentlock(self._lockref) is not None:
3092 self.ui.develwarn(b'"wlock" acquired after "lock"')
3103 self.ui.develwarn(b'"wlock" acquired after "lock"')
3093
3104
3094 def unlock():
3105 def unlock():
3095 if self.dirstate.is_changing_any:
3106 if self.dirstate.is_changing_any:
3096 msg = b"wlock release in the middle of a changing parents"
3107 msg = b"wlock release in the middle of a changing parents"
3097 self.ui.develwarn(msg)
3108 self.ui.develwarn(msg)
3098 self.dirstate.invalidate()
3109 self.dirstate.invalidate()
3099 else:
3110 else:
3100 if self.dirstate._dirty:
3111 if self.dirstate._dirty:
3101 msg = b"dirty dirstate on wlock release"
3112 msg = b"dirty dirstate on wlock release"
3102 self.ui.develwarn(msg)
3113 self.ui.develwarn(msg)
3103 self.dirstate.write(None)
3114 self.dirstate.write(None)
3104
3115
3105 unfi = self.unfiltered()
3116 unfi = self.unfiltered()
3106 if 'dirstate' in unfi.__dict__:
3117 if 'dirstate' in unfi.__dict__:
3107 del unfi.__dict__['dirstate']
3118 del unfi.__dict__['dirstate']
3108
3119
3109 l = self._lock(
3120 l = self._lock(
3110 self.vfs,
3121 self.vfs,
3111 b"wlock",
3122 b"wlock",
3112 wait,
3123 wait,
3113 unlock,
3124 unlock,
3114 self.invalidatedirstate,
3125 self.invalidatedirstate,
3115 _(b'working directory of %s') % self.origroot,
3126 _(b'working directory of %s') % self.origroot,
3116 )
3127 )
3117 self._wlockref = weakref.ref(l)
3128 self._wlockref = weakref.ref(l)
3118 return l
3129 return l
3119
3130
3120 def _currentlock(self, lockref):
3131 def _currentlock(self, lockref):
3121 """Returns the lock if it's held, or None if it's not."""
3132 """Returns the lock if it's held, or None if it's not."""
3122 if lockref is None:
3133 if lockref is None:
3123 return None
3134 return None
3124 l = lockref()
3135 l = lockref()
3125 if l is None or not l.held:
3136 if l is None or not l.held:
3126 return None
3137 return None
3127 return l
3138 return l
3128
3139
3129 def currentwlock(self):
3140 def currentwlock(self):
3130 """Returns the wlock if it's held, or None if it's not."""
3141 """Returns the wlock if it's held, or None if it's not."""
3131 return self._currentlock(self._wlockref)
3142 return self._currentlock(self._wlockref)
3132
3143
3133 def checkcommitpatterns(self, wctx, match, status, fail):
3144 def checkcommitpatterns(self, wctx, match, status, fail):
3134 """check for commit arguments that aren't committable"""
3145 """check for commit arguments that aren't committable"""
3135 if match.isexact() or match.prefix():
3146 if match.isexact() or match.prefix():
3136 matched = set(status.modified + status.added + status.removed)
3147 matched = set(status.modified + status.added + status.removed)
3137
3148
3138 for f in match.files():
3149 for f in match.files():
3139 f = self.dirstate.normalize(f)
3150 f = self.dirstate.normalize(f)
3140 if f == b'.' or f in matched or f in wctx.substate:
3151 if f == b'.' or f in matched or f in wctx.substate:
3141 continue
3152 continue
3142 if f in status.deleted:
3153 if f in status.deleted:
3143 fail(f, _(b'file not found!'))
3154 fail(f, _(b'file not found!'))
3144 # Is it a directory that exists or used to exist?
3155 # Is it a directory that exists or used to exist?
3145 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3156 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3146 d = f + b'/'
3157 d = f + b'/'
3147 for mf in matched:
3158 for mf in matched:
3148 if mf.startswith(d):
3159 if mf.startswith(d):
3149 break
3160 break
3150 else:
3161 else:
3151 fail(f, _(b"no match under directory!"))
3162 fail(f, _(b"no match under directory!"))
3152 elif f not in self.dirstate:
3163 elif f not in self.dirstate:
3153 fail(f, _(b"file not tracked!"))
3164 fail(f, _(b"file not tracked!"))
3154
3165
3155 @unfilteredmethod
3166 @unfilteredmethod
3156 def commit(
3167 def commit(
3157 self,
3168 self,
3158 text=b"",
3169 text=b"",
3159 user=None,
3170 user=None,
3160 date=None,
3171 date=None,
3161 match=None,
3172 match=None,
3162 force=False,
3173 force=False,
3163 editor=None,
3174 editor=None,
3164 extra=None,
3175 extra=None,
3165 ):
3176 ):
3166 """Add a new revision to current repository.
3177 """Add a new revision to current repository.
3167
3178
3168 Revision information is gathered from the working directory,
3179 Revision information is gathered from the working directory,
3169 match can be used to filter the committed files. If editor is
3180 match can be used to filter the committed files. If editor is
3170 supplied, it is called to get a commit message.
3181 supplied, it is called to get a commit message.
3171 """
3182 """
3172 if extra is None:
3183 if extra is None:
3173 extra = {}
3184 extra = {}
3174
3185
3175 def fail(f, msg):
3186 def fail(f, msg):
3176 raise error.InputError(b'%s: %s' % (f, msg))
3187 raise error.InputError(b'%s: %s' % (f, msg))
3177
3188
3178 if not match:
3189 if not match:
3179 match = matchmod.always()
3190 match = matchmod.always()
3180
3191
3181 if not force:
3192 if not force:
3182 match.bad = fail
3193 match.bad = fail
3183
3194
3184 # lock() for recent changelog (see issue4368)
3195 # lock() for recent changelog (see issue4368)
3185 with self.wlock(), self.lock():
3196 with self.wlock(), self.lock():
3186 wctx = self[None]
3197 wctx = self[None]
3187 merge = len(wctx.parents()) > 1
3198 merge = len(wctx.parents()) > 1
3188
3199
3189 if not force and merge and not match.always():
3200 if not force and merge and not match.always():
3190 raise error.Abort(
3201 raise error.Abort(
3191 _(
3202 _(
3192 b'cannot partially commit a merge '
3203 b'cannot partially commit a merge '
3193 b'(do not specify files or patterns)'
3204 b'(do not specify files or patterns)'
3194 )
3205 )
3195 )
3206 )
3196
3207
3197 status = self.status(match=match, clean=force)
3208 status = self.status(match=match, clean=force)
3198 if force:
3209 if force:
3199 status.modified.extend(
3210 status.modified.extend(
3200 status.clean
3211 status.clean
3201 ) # mq may commit clean files
3212 ) # mq may commit clean files
3202
3213
3203 # check subrepos
3214 # check subrepos
3204 subs, commitsubs, newstate = subrepoutil.precommit(
3215 subs, commitsubs, newstate = subrepoutil.precommit(
3205 self.ui, wctx, status, match, force=force
3216 self.ui, wctx, status, match, force=force
3206 )
3217 )
3207
3218
3208 # make sure all explicit patterns are matched
3219 # make sure all explicit patterns are matched
3209 if not force:
3220 if not force:
3210 self.checkcommitpatterns(wctx, match, status, fail)
3221 self.checkcommitpatterns(wctx, match, status, fail)
3211
3222
3212 cctx = context.workingcommitctx(
3223 cctx = context.workingcommitctx(
3213 self, status, text, user, date, extra
3224 self, status, text, user, date, extra
3214 )
3225 )
3215
3226
3216 ms = mergestatemod.mergestate.read(self)
3227 ms = mergestatemod.mergestate.read(self)
3217 mergeutil.checkunresolved(ms)
3228 mergeutil.checkunresolved(ms)
3218
3229
3219 # internal config: ui.allowemptycommit
3230 # internal config: ui.allowemptycommit
3220 if cctx.isempty() and not self.ui.configbool(
3231 if cctx.isempty() and not self.ui.configbool(
3221 b'ui', b'allowemptycommit'
3232 b'ui', b'allowemptycommit'
3222 ):
3233 ):
3223 self.ui.debug(b'nothing to commit, clearing merge state\n')
3234 self.ui.debug(b'nothing to commit, clearing merge state\n')
3224 ms.reset()
3235 ms.reset()
3225 return None
3236 return None
3226
3237
3227 if merge and cctx.deleted():
3238 if merge and cctx.deleted():
3228 raise error.Abort(_(b"cannot commit merge with missing files"))
3239 raise error.Abort(_(b"cannot commit merge with missing files"))
3229
3240
3230 if editor:
3241 if editor:
3231 cctx._text = editor(self, cctx, subs)
3242 cctx._text = editor(self, cctx, subs)
3232 edited = text != cctx._text
3243 edited = text != cctx._text
3233
3244
3234 # Save commit message in case this transaction gets rolled back
3245 # Save commit message in case this transaction gets rolled back
3235 # (e.g. by a pretxncommit hook). Leave the content alone on
3246 # (e.g. by a pretxncommit hook). Leave the content alone on
3236 # the assumption that the user will use the same editor again.
3247 # the assumption that the user will use the same editor again.
3237 msg_path = self.savecommitmessage(cctx._text)
3248 msg_path = self.savecommitmessage(cctx._text)
3238
3249
3239 # commit subs and write new state
3250 # commit subs and write new state
3240 if subs:
3251 if subs:
3241 uipathfn = scmutil.getuipathfn(self)
3252 uipathfn = scmutil.getuipathfn(self)
3242 for s in sorted(commitsubs):
3253 for s in sorted(commitsubs):
3243 sub = wctx.sub(s)
3254 sub = wctx.sub(s)
3244 self.ui.status(
3255 self.ui.status(
3245 _(b'committing subrepository %s\n')
3256 _(b'committing subrepository %s\n')
3246 % uipathfn(subrepoutil.subrelpath(sub))
3257 % uipathfn(subrepoutil.subrelpath(sub))
3247 )
3258 )
3248 sr = sub.commit(cctx._text, user, date)
3259 sr = sub.commit(cctx._text, user, date)
3249 newstate[s] = (newstate[s][0], sr)
3260 newstate[s] = (newstate[s][0], sr)
3250 subrepoutil.writestate(self, newstate)
3261 subrepoutil.writestate(self, newstate)
3251
3262
3252 p1, p2 = self.dirstate.parents()
3263 p1, p2 = self.dirstate.parents()
3253 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3264 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3254 try:
3265 try:
3255 self.hook(
3266 self.hook(
3256 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3267 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3257 )
3268 )
3258 with self.transaction(b'commit'):
3269 with self.transaction(b'commit'):
3259 ret = self.commitctx(cctx, True)
3270 ret = self.commitctx(cctx, True)
3260 # update bookmarks, dirstate and mergestate
3271 # update bookmarks, dirstate and mergestate
3261 bookmarks.update(self, [p1, p2], ret)
3272 bookmarks.update(self, [p1, p2], ret)
3262 cctx.markcommitted(ret)
3273 cctx.markcommitted(ret)
3263 ms.reset()
3274 ms.reset()
3264 except: # re-raises
3275 except: # re-raises
3265 if edited:
3276 if edited:
3266 self.ui.write(
3277 self.ui.write(
3267 _(b'note: commit message saved in %s\n') % msg_path
3278 _(b'note: commit message saved in %s\n') % msg_path
3268 )
3279 )
3269 self.ui.write(
3280 self.ui.write(
3270 _(
3281 _(
3271 b"note: use 'hg commit --logfile "
3282 b"note: use 'hg commit --logfile "
3272 b"%s --edit' to reuse it\n"
3283 b"%s --edit' to reuse it\n"
3273 )
3284 )
3274 % msg_path
3285 % msg_path
3275 )
3286 )
3276 raise
3287 raise
3277
3288
3278 def commithook(unused_success):
3289 def commithook(unused_success):
3279 # hack for command that use a temporary commit (eg: histedit)
3290 # hack for command that use a temporary commit (eg: histedit)
3280 # temporary commit got stripped before hook release
3291 # temporary commit got stripped before hook release
3281 if self.changelog.hasnode(ret):
3292 if self.changelog.hasnode(ret):
3282 self.hook(
3293 self.hook(
3283 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3294 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3284 )
3295 )
3285
3296
3286 self._afterlock(commithook)
3297 self._afterlock(commithook)
3287 return ret
3298 return ret
3288
3299
3289 @unfilteredmethod
3300 @unfilteredmethod
3290 def commitctx(self, ctx, error=False, origctx=None):
3301 def commitctx(self, ctx, error=False, origctx=None):
3291 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3302 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3292
3303
3293 @unfilteredmethod
3304 @unfilteredmethod
3294 def destroying(self):
3305 def destroying(self):
3295 """Inform the repository that nodes are about to be destroyed.
3306 """Inform the repository that nodes are about to be destroyed.
3296 Intended for use by strip and rollback, so there's a common
3307 Intended for use by strip and rollback, so there's a common
3297 place for anything that has to be done before destroying history.
3308 place for anything that has to be done before destroying history.
3298
3309
3299 This is mostly useful for saving state that is in memory and waiting
3310 This is mostly useful for saving state that is in memory and waiting
3300 to be flushed when the current lock is released. Because a call to
3311 to be flushed when the current lock is released. Because a call to
3301 destroyed is imminent, the repo will be invalidated causing those
3312 destroyed is imminent, the repo will be invalidated causing those
3302 changes to stay in memory (waiting for the next unlock), or vanish
3313 changes to stay in memory (waiting for the next unlock), or vanish
3303 completely.
3314 completely.
3304 """
3315 """
3305 # When using the same lock to commit and strip, the phasecache is left
3316 # When using the same lock to commit and strip, the phasecache is left
3306 # dirty after committing. Then when we strip, the repo is invalidated,
3317 # dirty after committing. Then when we strip, the repo is invalidated,
3307 # causing those changes to disappear.
3318 # causing those changes to disappear.
3308 if '_phasecache' in vars(self):
3319 if '_phasecache' in vars(self):
3309 self._phasecache.write()
3320 self._phasecache.write()
3310
3321
3311 @unfilteredmethod
3322 @unfilteredmethod
3312 def destroyed(self):
3323 def destroyed(self):
3313 """Inform the repository that nodes have been destroyed.
3324 """Inform the repository that nodes have been destroyed.
3314 Intended for use by strip and rollback, so there's a common
3325 Intended for use by strip and rollback, so there's a common
3315 place for anything that has to be done after destroying history.
3326 place for anything that has to be done after destroying history.
3316 """
3327 """
3317 # When one tries to:
3328 # When one tries to:
3318 # 1) destroy nodes thus calling this method (e.g. strip)
3329 # 1) destroy nodes thus calling this method (e.g. strip)
3319 # 2) use phasecache somewhere (e.g. commit)
3330 # 2) use phasecache somewhere (e.g. commit)
3320 #
3331 #
3321 # then 2) will fail because the phasecache contains nodes that were
3332 # then 2) will fail because the phasecache contains nodes that were
3322 # removed. We can either remove phasecache from the filecache,
3333 # removed. We can either remove phasecache from the filecache,
3323 # causing it to reload next time it is accessed, or simply filter
3334 # causing it to reload next time it is accessed, or simply filter
3324 # the removed nodes now and write the updated cache.
3335 # the removed nodes now and write the updated cache.
3325 self._phasecache.filterunknown(self)
3336 self._phasecache.filterunknown(self)
3326 self._phasecache.write()
3337 self._phasecache.write()
3327
3338
3328 # refresh all repository caches
3339 # refresh all repository caches
3329 self.updatecaches()
3340 self.updatecaches()
3330
3341
3331 # Ensure the persistent tag cache is updated. Doing it now
3342 # Ensure the persistent tag cache is updated. Doing it now
3332 # means that the tag cache only has to worry about destroyed
3343 # means that the tag cache only has to worry about destroyed
3333 # heads immediately after a strip/rollback. That in turn
3344 # heads immediately after a strip/rollback. That in turn
3334 # guarantees that "cachetip == currenttip" (comparing both rev
3345 # guarantees that "cachetip == currenttip" (comparing both rev
3335 # and node) always means no nodes have been added or destroyed.
3346 # and node) always means no nodes have been added or destroyed.
3336
3347
3337 # XXX this is suboptimal when qrefresh'ing: we strip the current
3348 # XXX this is suboptimal when qrefresh'ing: we strip the current
3338 # head, refresh the tag cache, then immediately add a new head.
3349 # head, refresh the tag cache, then immediately add a new head.
3339 # But I think doing it this way is necessary for the "instant
3350 # But I think doing it this way is necessary for the "instant
3340 # tag cache retrieval" case to work.
3351 # tag cache retrieval" case to work.
3341 self.invalidate()
3352 self.invalidate()
3342
3353
3343 def status(
3354 def status(
3344 self,
3355 self,
3345 node1=b'.',
3356 node1=b'.',
3346 node2=None,
3357 node2=None,
3347 match=None,
3358 match=None,
3348 ignored=False,
3359 ignored=False,
3349 clean=False,
3360 clean=False,
3350 unknown=False,
3361 unknown=False,
3351 listsubrepos=False,
3362 listsubrepos=False,
3352 ):
3363 ):
3353 '''a convenience method that calls node1.status(node2)'''
3364 '''a convenience method that calls node1.status(node2)'''
3354 return self[node1].status(
3365 return self[node1].status(
3355 node2, match, ignored, clean, unknown, listsubrepos
3366 node2, match, ignored, clean, unknown, listsubrepos
3356 )
3367 )
3357
3368
3358 def addpostdsstatus(self, ps):
3369 def addpostdsstatus(self, ps):
3359 """Add a callback to run within the wlock, at the point at which status
3370 """Add a callback to run within the wlock, at the point at which status
3360 fixups happen.
3371 fixups happen.
3361
3372
3362 On status completion, callback(wctx, status) will be called with the
3373 On status completion, callback(wctx, status) will be called with the
3363 wlock held, unless the dirstate has changed from underneath or the wlock
3374 wlock held, unless the dirstate has changed from underneath or the wlock
3364 couldn't be grabbed.
3375 couldn't be grabbed.
3365
3376
3366 Callbacks should not capture and use a cached copy of the dirstate --
3377 Callbacks should not capture and use a cached copy of the dirstate --
3367 it might change in the meanwhile. Instead, they should access the
3378 it might change in the meanwhile. Instead, they should access the
3368 dirstate via wctx.repo().dirstate.
3379 dirstate via wctx.repo().dirstate.
3369
3380
3370 This list is emptied out after each status run -- extensions should
3381 This list is emptied out after each status run -- extensions should
3371 make sure it adds to this list each time dirstate.status is called.
3382 make sure it adds to this list each time dirstate.status is called.
3372 Extensions should also make sure they don't call this for statuses
3383 Extensions should also make sure they don't call this for statuses
3373 that don't involve the dirstate.
3384 that don't involve the dirstate.
3374 """
3385 """
3375
3386
3376 # The list is located here for uniqueness reasons -- it is actually
3387 # The list is located here for uniqueness reasons -- it is actually
3377 # managed by the workingctx, but that isn't unique per-repo.
3388 # managed by the workingctx, but that isn't unique per-repo.
3378 self._postdsstatus.append(ps)
3389 self._postdsstatus.append(ps)
3379
3390
3380 def postdsstatus(self):
3391 def postdsstatus(self):
3381 """Used by workingctx to get the list of post-dirstate-status hooks."""
3392 """Used by workingctx to get the list of post-dirstate-status hooks."""
3382 return self._postdsstatus
3393 return self._postdsstatus
3383
3394
3384 def clearpostdsstatus(self):
3395 def clearpostdsstatus(self):
3385 """Used by workingctx to clear post-dirstate-status hooks."""
3396 """Used by workingctx to clear post-dirstate-status hooks."""
3386 del self._postdsstatus[:]
3397 del self._postdsstatus[:]
3387
3398
3388 def heads(self, start=None):
3399 def heads(self, start=None):
3389 if start is None:
3400 if start is None:
3390 cl = self.changelog
3401 cl = self.changelog
3391 headrevs = reversed(cl.headrevs())
3402 headrevs = reversed(cl.headrevs())
3392 return [cl.node(rev) for rev in headrevs]
3403 return [cl.node(rev) for rev in headrevs]
3393
3404
3394 heads = self.changelog.heads(start)
3405 heads = self.changelog.heads(start)
3395 # sort the output in rev descending order
3406 # sort the output in rev descending order
3396 return sorted(heads, key=self.changelog.rev, reverse=True)
3407 return sorted(heads, key=self.changelog.rev, reverse=True)
3397
3408
3398 def branchheads(self, branch=None, start=None, closed=False):
3409 def branchheads(self, branch=None, start=None, closed=False):
3399 """return a (possibly filtered) list of heads for the given branch
3410 """return a (possibly filtered) list of heads for the given branch
3400
3411
3401 Heads are returned in topological order, from newest to oldest.
3412 Heads are returned in topological order, from newest to oldest.
3402 If branch is None, use the dirstate branch.
3413 If branch is None, use the dirstate branch.
3403 If start is not None, return only heads reachable from start.
3414 If start is not None, return only heads reachable from start.
3404 If closed is True, return heads that are marked as closed as well.
3415 If closed is True, return heads that are marked as closed as well.
3405 """
3416 """
3406 if branch is None:
3417 if branch is None:
3407 branch = self[None].branch()
3418 branch = self[None].branch()
3408 branches = self.branchmap()
3419 branches = self.branchmap()
3409 if not branches.hasbranch(branch):
3420 if not branches.hasbranch(branch):
3410 return []
3421 return []
3411 # the cache returns heads ordered lowest to highest
3422 # the cache returns heads ordered lowest to highest
3412 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3423 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3413 if start is not None:
3424 if start is not None:
3414 # filter out the heads that cannot be reached from startrev
3425 # filter out the heads that cannot be reached from startrev
3415 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3426 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3416 bheads = [h for h in bheads if h in fbheads]
3427 bheads = [h for h in bheads if h in fbheads]
3417 return bheads
3428 return bheads
3418
3429
3419 def branches(self, nodes):
3430 def branches(self, nodes):
3420 if not nodes:
3431 if not nodes:
3421 nodes = [self.changelog.tip()]
3432 nodes = [self.changelog.tip()]
3422 b = []
3433 b = []
3423 for n in nodes:
3434 for n in nodes:
3424 t = n
3435 t = n
3425 while True:
3436 while True:
3426 p = self.changelog.parents(n)
3437 p = self.changelog.parents(n)
3427 if p[1] != self.nullid or p[0] == self.nullid:
3438 if p[1] != self.nullid or p[0] == self.nullid:
3428 b.append((t, n, p[0], p[1]))
3439 b.append((t, n, p[0], p[1]))
3429 break
3440 break
3430 n = p[0]
3441 n = p[0]
3431 return b
3442 return b
3432
3443
3433 def between(self, pairs):
3444 def between(self, pairs):
3434 r = []
3445 r = []
3435
3446
3436 for top, bottom in pairs:
3447 for top, bottom in pairs:
3437 n, l, i = top, [], 0
3448 n, l, i = top, [], 0
3438 f = 1
3449 f = 1
3439
3450
3440 while n != bottom and n != self.nullid:
3451 while n != bottom and n != self.nullid:
3441 p = self.changelog.parents(n)[0]
3452 p = self.changelog.parents(n)[0]
3442 if i == f:
3453 if i == f:
3443 l.append(n)
3454 l.append(n)
3444 f = f * 2
3455 f = f * 2
3445 n = p
3456 n = p
3446 i += 1
3457 i += 1
3447
3458
3448 r.append(l)
3459 r.append(l)
3449
3460
3450 return r
3461 return r
3451
3462
3452 def checkpush(self, pushop):
3463 def checkpush(self, pushop):
3453 """Extensions can override this function if additional checks have
3464 """Extensions can override this function if additional checks have
3454 to be performed before pushing, or call it if they override push
3465 to be performed before pushing, or call it if they override push
3455 command.
3466 command.
3456 """
3467 """
3457
3468
3458 @unfilteredpropertycache
3469 @unfilteredpropertycache
3459 def prepushoutgoinghooks(self):
3470 def prepushoutgoinghooks(self):
3460 """Return util.hooks consists of a pushop with repo, remote, outgoing
3471 """Return util.hooks consists of a pushop with repo, remote, outgoing
3461 methods, which are called before pushing changesets.
3472 methods, which are called before pushing changesets.
3462 """
3473 """
3463 return util.hooks()
3474 return util.hooks()
3464
3475
3465 def pushkey(self, namespace, key, old, new):
3476 def pushkey(self, namespace, key, old, new):
3466 try:
3477 try:
3467 tr = self.currenttransaction()
3478 tr = self.currenttransaction()
3468 hookargs = {}
3479 hookargs = {}
3469 if tr is not None:
3480 if tr is not None:
3470 hookargs.update(tr.hookargs)
3481 hookargs.update(tr.hookargs)
3471 hookargs = pycompat.strkwargs(hookargs)
3482 hookargs = pycompat.strkwargs(hookargs)
3472 hookargs['namespace'] = namespace
3483 hookargs['namespace'] = namespace
3473 hookargs['key'] = key
3484 hookargs['key'] = key
3474 hookargs['old'] = old
3485 hookargs['old'] = old
3475 hookargs['new'] = new
3486 hookargs['new'] = new
3476 self.hook(b'prepushkey', throw=True, **hookargs)
3487 self.hook(b'prepushkey', throw=True, **hookargs)
3477 except error.HookAbort as exc:
3488 except error.HookAbort as exc:
3478 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3489 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3479 if exc.hint:
3490 if exc.hint:
3480 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3491 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3481 return False
3492 return False
3482 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3493 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3483 ret = pushkey.push(self, namespace, key, old, new)
3494 ret = pushkey.push(self, namespace, key, old, new)
3484
3495
3485 def runhook(unused_success):
3496 def runhook(unused_success):
3486 self.hook(
3497 self.hook(
3487 b'pushkey',
3498 b'pushkey',
3488 namespace=namespace,
3499 namespace=namespace,
3489 key=key,
3500 key=key,
3490 old=old,
3501 old=old,
3491 new=new,
3502 new=new,
3492 ret=ret,
3503 ret=ret,
3493 )
3504 )
3494
3505
3495 self._afterlock(runhook)
3506 self._afterlock(runhook)
3496 return ret
3507 return ret
3497
3508
3498 def listkeys(self, namespace):
3509 def listkeys(self, namespace):
3499 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3510 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3500 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3511 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3501 values = pushkey.list(self, namespace)
3512 values = pushkey.list(self, namespace)
3502 self.hook(b'listkeys', namespace=namespace, values=values)
3513 self.hook(b'listkeys', namespace=namespace, values=values)
3503 return values
3514 return values
3504
3515
3505 def debugwireargs(self, one, two, three=None, four=None, five=None):
3516 def debugwireargs(self, one, two, three=None, four=None, five=None):
3506 '''used to test argument passing over the wire'''
3517 '''used to test argument passing over the wire'''
3507 return b"%s %s %s %s %s" % (
3518 return b"%s %s %s %s %s" % (
3508 one,
3519 one,
3509 two,
3520 two,
3510 pycompat.bytestr(three),
3521 pycompat.bytestr(three),
3511 pycompat.bytestr(four),
3522 pycompat.bytestr(four),
3512 pycompat.bytestr(five),
3523 pycompat.bytestr(five),
3513 )
3524 )
3514
3525
3515 def savecommitmessage(self, text):
3526 def savecommitmessage(self, text):
3516 fp = self.vfs(b'last-message.txt', b'wb')
3527 fp = self.vfs(b'last-message.txt', b'wb')
3517 try:
3528 try:
3518 fp.write(text)
3529 fp.write(text)
3519 finally:
3530 finally:
3520 fp.close()
3531 fp.close()
3521 return self.pathto(fp.name[len(self.root) + 1 :])
3532 return self.pathto(fp.name[len(self.root) + 1 :])
3522
3533
3523 def register_wanted_sidedata(self, category):
3534 def register_wanted_sidedata(self, category):
3524 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3535 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3525 # Only revlogv2 repos can want sidedata.
3536 # Only revlogv2 repos can want sidedata.
3526 return
3537 return
3527 self._wanted_sidedata.add(pycompat.bytestr(category))
3538 self._wanted_sidedata.add(pycompat.bytestr(category))
3528
3539
3529 def register_sidedata_computer(
3540 def register_sidedata_computer(
3530 self, kind, category, keys, computer, flags, replace=False
3541 self, kind, category, keys, computer, flags, replace=False
3531 ):
3542 ):
3532 if kind not in revlogconst.ALL_KINDS:
3543 if kind not in revlogconst.ALL_KINDS:
3533 msg = _(b"unexpected revlog kind '%s'.")
3544 msg = _(b"unexpected revlog kind '%s'.")
3534 raise error.ProgrammingError(msg % kind)
3545 raise error.ProgrammingError(msg % kind)
3535 category = pycompat.bytestr(category)
3546 category = pycompat.bytestr(category)
3536 already_registered = category in self._sidedata_computers.get(kind, [])
3547 already_registered = category in self._sidedata_computers.get(kind, [])
3537 if already_registered and not replace:
3548 if already_registered and not replace:
3538 msg = _(
3549 msg = _(
3539 b"cannot register a sidedata computer twice for category '%s'."
3550 b"cannot register a sidedata computer twice for category '%s'."
3540 )
3551 )
3541 raise error.ProgrammingError(msg % category)
3552 raise error.ProgrammingError(msg % category)
3542 if replace and not already_registered:
3553 if replace and not already_registered:
3543 msg = _(
3554 msg = _(
3544 b"cannot replace a sidedata computer that isn't registered "
3555 b"cannot replace a sidedata computer that isn't registered "
3545 b"for category '%s'."
3556 b"for category '%s'."
3546 )
3557 )
3547 raise error.ProgrammingError(msg % category)
3558 raise error.ProgrammingError(msg % category)
3548 self._sidedata_computers.setdefault(kind, {})
3559 self._sidedata_computers.setdefault(kind, {})
3549 self._sidedata_computers[kind][category] = (keys, computer, flags)
3560 self._sidedata_computers[kind][category] = (keys, computer, flags)
3550
3561
3551
3562
3552 def undoname(fn: bytes) -> bytes:
3563 def undoname(fn: bytes) -> bytes:
3553 base, name = os.path.split(fn)
3564 base, name = os.path.split(fn)
3554 assert name.startswith(b'journal')
3565 assert name.startswith(b'journal')
3555 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3566 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3556
3567
3557
3568
3558 def instance(ui, path: bytes, create, intents=None, createopts=None):
3569 def instance(ui, path: bytes, create, intents=None, createopts=None):
3559 # prevent cyclic import localrepo -> upgrade -> localrepo
3570 # prevent cyclic import localrepo -> upgrade -> localrepo
3560 from . import upgrade
3571 from . import upgrade
3561
3572
3562 localpath = urlutil.urllocalpath(path)
3573 localpath = urlutil.urllocalpath(path)
3563 if create:
3574 if create:
3564 createrepository(ui, localpath, createopts=createopts)
3575 createrepository(ui, localpath, createopts=createopts)
3565
3576
3566 def repo_maker():
3577 def repo_maker():
3567 return makelocalrepository(ui, localpath, intents=intents)
3578 return makelocalrepository(ui, localpath, intents=intents)
3568
3579
3569 repo = repo_maker()
3580 repo = repo_maker()
3570 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3581 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3571 return repo
3582 return repo
3572
3583
3573
3584
3574 def islocal(path: bytes) -> bool:
3585 def islocal(path: bytes) -> bool:
3575 return True
3586 return True
3576
3587
3577
3588
3578 def defaultcreateopts(ui, createopts=None):
3589 def defaultcreateopts(ui, createopts=None):
3579 """Populate the default creation options for a repository.
3590 """Populate the default creation options for a repository.
3580
3591
3581 A dictionary of explicitly requested creation options can be passed
3592 A dictionary of explicitly requested creation options can be passed
3582 in. Missing keys will be populated.
3593 in. Missing keys will be populated.
3583 """
3594 """
3584 createopts = dict(createopts or {})
3595 createopts = dict(createopts or {})
3585
3596
3586 if b'backend' not in createopts:
3597 if b'backend' not in createopts:
3587 # experimental config: storage.new-repo-backend
3598 # experimental config: storage.new-repo-backend
3588 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3599 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3589
3600
3590 return createopts
3601 return createopts
3591
3602
3592
3603
3593 def clone_requirements(ui, createopts, srcrepo):
3604 def clone_requirements(ui, createopts, srcrepo):
3594 """clone the requirements of a local repo for a local clone
3605 """clone the requirements of a local repo for a local clone
3595
3606
3596 The store requirements are unchanged while the working copy requirements
3607 The store requirements are unchanged while the working copy requirements
3597 depends on the configuration
3608 depends on the configuration
3598 """
3609 """
3599 target_requirements = set()
3610 target_requirements = set()
3600 if not srcrepo.requirements:
3611 if not srcrepo.requirements:
3601 # this is a legacy revlog "v0" repository, we cannot do anything fancy
3612 # this is a legacy revlog "v0" repository, we cannot do anything fancy
3602 # with it.
3613 # with it.
3603 return target_requirements
3614 return target_requirements
3604 createopts = defaultcreateopts(ui, createopts=createopts)
3615 createopts = defaultcreateopts(ui, createopts=createopts)
3605 for r in newreporequirements(ui, createopts):
3616 for r in newreporequirements(ui, createopts):
3606 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3617 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3607 target_requirements.add(r)
3618 target_requirements.add(r)
3608
3619
3609 for r in srcrepo.requirements:
3620 for r in srcrepo.requirements:
3610 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3621 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3611 target_requirements.add(r)
3622 target_requirements.add(r)
3612 return target_requirements
3623 return target_requirements
3613
3624
3614
3625
3615 def newreporequirements(ui, createopts):
3626 def newreporequirements(ui, createopts):
3616 """Determine the set of requirements for a new local repository.
3627 """Determine the set of requirements for a new local repository.
3617
3628
3618 Extensions can wrap this function to specify custom requirements for
3629 Extensions can wrap this function to specify custom requirements for
3619 new repositories.
3630 new repositories.
3620 """
3631 """
3621
3632
3622 if b'backend' not in createopts:
3633 if b'backend' not in createopts:
3623 raise error.ProgrammingError(
3634 raise error.ProgrammingError(
3624 b'backend key not present in createopts; '
3635 b'backend key not present in createopts; '
3625 b'was defaultcreateopts() called?'
3636 b'was defaultcreateopts() called?'
3626 )
3637 )
3627
3638
3628 if createopts[b'backend'] != b'revlogv1':
3639 if createopts[b'backend'] != b'revlogv1':
3629 raise error.Abort(
3640 raise error.Abort(
3630 _(
3641 _(
3631 b'unable to determine repository requirements for '
3642 b'unable to determine repository requirements for '
3632 b'storage backend: %s'
3643 b'storage backend: %s'
3633 )
3644 )
3634 % createopts[b'backend']
3645 % createopts[b'backend']
3635 )
3646 )
3636
3647
3637 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3648 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3638 if ui.configbool(b'format', b'usestore'):
3649 if ui.configbool(b'format', b'usestore'):
3639 requirements.add(requirementsmod.STORE_REQUIREMENT)
3650 requirements.add(requirementsmod.STORE_REQUIREMENT)
3640 if ui.configbool(b'format', b'usefncache'):
3651 if ui.configbool(b'format', b'usefncache'):
3641 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3652 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3642 if ui.configbool(b'format', b'dotencode'):
3653 if ui.configbool(b'format', b'dotencode'):
3643 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3654 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3644
3655
3645 compengines = ui.configlist(b'format', b'revlog-compression')
3656 compengines = ui.configlist(b'format', b'revlog-compression')
3646 for compengine in compengines:
3657 for compengine in compengines:
3647 if compengine in util.compengines:
3658 if compengine in util.compengines:
3648 engine = util.compengines[compengine]
3659 engine = util.compengines[compengine]
3649 if engine.available() and engine.revlogheader():
3660 if engine.available() and engine.revlogheader():
3650 break
3661 break
3651 else:
3662 else:
3652 raise error.Abort(
3663 raise error.Abort(
3653 _(
3664 _(
3654 b'compression engines %s defined by '
3665 b'compression engines %s defined by '
3655 b'format.revlog-compression not available'
3666 b'format.revlog-compression not available'
3656 )
3667 )
3657 % b', '.join(b'"%s"' % e for e in compengines),
3668 % b', '.join(b'"%s"' % e for e in compengines),
3658 hint=_(
3669 hint=_(
3659 b'run "hg debuginstall" to list available '
3670 b'run "hg debuginstall" to list available '
3660 b'compression engines'
3671 b'compression engines'
3661 ),
3672 ),
3662 )
3673 )
3663
3674
3664 # zlib is the historical default and doesn't need an explicit requirement.
3675 # zlib is the historical default and doesn't need an explicit requirement.
3665 if compengine == b'zstd':
3676 if compengine == b'zstd':
3666 requirements.add(b'revlog-compression-zstd')
3677 requirements.add(b'revlog-compression-zstd')
3667 elif compengine != b'zlib':
3678 elif compengine != b'zlib':
3668 requirements.add(b'exp-compression-%s' % compengine)
3679 requirements.add(b'exp-compression-%s' % compengine)
3669
3680
3670 if scmutil.gdinitconfig(ui):
3681 if scmutil.gdinitconfig(ui):
3671 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3682 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3672 if ui.configbool(b'format', b'sparse-revlog'):
3683 if ui.configbool(b'format', b'sparse-revlog'):
3673 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3684 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3674
3685
3675 # experimental config: format.use-dirstate-v2
3686 # experimental config: format.use-dirstate-v2
3676 # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
3687 # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
3677 if ui.configbool(b'format', b'use-dirstate-v2'):
3688 if ui.configbool(b'format', b'use-dirstate-v2'):
3678 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3689 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3679
3690
3680 # experimental config: format.exp-use-copies-side-data-changeset
3691 # experimental config: format.exp-use-copies-side-data-changeset
3681 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3692 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3682 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3693 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3683 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3694 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3684 if ui.configbool(b'experimental', b'treemanifest'):
3695 if ui.configbool(b'experimental', b'treemanifest'):
3685 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3696 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3686
3697
3687 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3698 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3688 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3699 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3689 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3700 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3690
3701
3691 revlogv2 = ui.config(b'experimental', b'revlogv2')
3702 revlogv2 = ui.config(b'experimental', b'revlogv2')
3692 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3703 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3693 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3704 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3694 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3705 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3695 # experimental config: format.internal-phase
3706 # experimental config: format.internal-phase
3696 if ui.configbool(b'format', b'use-internal-phase'):
3707 if ui.configbool(b'format', b'use-internal-phase'):
3697 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3708 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3698
3709
3699 # experimental config: format.exp-archived-phase
3710 # experimental config: format.exp-archived-phase
3700 if ui.configbool(b'format', b'exp-archived-phase'):
3711 if ui.configbool(b'format', b'exp-archived-phase'):
3701 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3712 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3702
3713
3703 if createopts.get(b'narrowfiles'):
3714 if createopts.get(b'narrowfiles'):
3704 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3715 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3705
3716
3706 if createopts.get(b'lfs'):
3717 if createopts.get(b'lfs'):
3707 requirements.add(b'lfs')
3718 requirements.add(b'lfs')
3708
3719
3709 if ui.configbool(b'format', b'bookmarks-in-store'):
3720 if ui.configbool(b'format', b'bookmarks-in-store'):
3710 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3721 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3711
3722
3712 if ui.configbool(b'format', b'use-persistent-nodemap'):
3723 if ui.configbool(b'format', b'use-persistent-nodemap'):
3713 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3724 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3714
3725
3715 # if share-safe is enabled, let's create the new repository with the new
3726 # if share-safe is enabled, let's create the new repository with the new
3716 # requirement
3727 # requirement
3717 if ui.configbool(b'format', b'use-share-safe'):
3728 if ui.configbool(b'format', b'use-share-safe'):
3718 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3729 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3719
3730
3720 # if we are creating a share-repoΒΉ we have to handle requirement
3731 # if we are creating a share-repoΒΉ we have to handle requirement
3721 # differently.
3732 # differently.
3722 #
3733 #
3723 # [1] (i.e. reusing the store from another repository, just having a
3734 # [1] (i.e. reusing the store from another repository, just having a
3724 # working copy)
3735 # working copy)
3725 if b'sharedrepo' in createopts:
3736 if b'sharedrepo' in createopts:
3726 source_requirements = set(createopts[b'sharedrepo'].requirements)
3737 source_requirements = set(createopts[b'sharedrepo'].requirements)
3727
3738
3728 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3739 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3729 # share to an old school repository, we have to copy the
3740 # share to an old school repository, we have to copy the
3730 # requirements and hope for the best.
3741 # requirements and hope for the best.
3731 requirements = source_requirements
3742 requirements = source_requirements
3732 else:
3743 else:
3733 # We have control on the working copy only, so "copy" the non
3744 # We have control on the working copy only, so "copy" the non
3734 # working copy part over, ignoring previous logic.
3745 # working copy part over, ignoring previous logic.
3735 to_drop = set()
3746 to_drop = set()
3736 for req in requirements:
3747 for req in requirements:
3737 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3748 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3738 continue
3749 continue
3739 if req in source_requirements:
3750 if req in source_requirements:
3740 continue
3751 continue
3741 to_drop.add(req)
3752 to_drop.add(req)
3742 requirements -= to_drop
3753 requirements -= to_drop
3743 requirements |= source_requirements
3754 requirements |= source_requirements
3744
3755
3745 if createopts.get(b'sharedrelative'):
3756 if createopts.get(b'sharedrelative'):
3746 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3757 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3747 else:
3758 else:
3748 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3759 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3749
3760
3750 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3761 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3751 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3762 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3752 msg = _(b"ignoring unknown tracked key version: %d\n")
3763 msg = _(b"ignoring unknown tracked key version: %d\n")
3753 hint = _(
3764 hint = _(
3754 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3765 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3755 )
3766 )
3756 if version != 1:
3767 if version != 1:
3757 ui.warn(msg % version, hint=hint)
3768 ui.warn(msg % version, hint=hint)
3758 else:
3769 else:
3759 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3770 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3760
3771
3761 return requirements
3772 return requirements
3762
3773
3763
3774
3764 def checkrequirementscompat(ui, requirements):
3775 def checkrequirementscompat(ui, requirements):
3765 """Checks compatibility of repository requirements enabled and disabled.
3776 """Checks compatibility of repository requirements enabled and disabled.
3766
3777
3767 Returns a set of requirements which needs to be dropped because dependend
3778 Returns a set of requirements which needs to be dropped because dependend
3768 requirements are not enabled. Also warns users about it"""
3779 requirements are not enabled. Also warns users about it"""
3769
3780
3770 dropped = set()
3781 dropped = set()
3771
3782
3772 if requirementsmod.STORE_REQUIREMENT not in requirements:
3783 if requirementsmod.STORE_REQUIREMENT not in requirements:
3773 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3784 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3774 ui.warn(
3785 ui.warn(
3775 _(
3786 _(
3776 b'ignoring enabled \'format.bookmarks-in-store\' config '
3787 b'ignoring enabled \'format.bookmarks-in-store\' config '
3777 b'beacuse it is incompatible with disabled '
3788 b'beacuse it is incompatible with disabled '
3778 b'\'format.usestore\' config\n'
3789 b'\'format.usestore\' config\n'
3779 )
3790 )
3780 )
3791 )
3781 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3792 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3782
3793
3783 if (
3794 if (
3784 requirementsmod.SHARED_REQUIREMENT in requirements
3795 requirementsmod.SHARED_REQUIREMENT in requirements
3785 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3796 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3786 ):
3797 ):
3787 raise error.Abort(
3798 raise error.Abort(
3788 _(
3799 _(
3789 b"cannot create shared repository as source was created"
3800 b"cannot create shared repository as source was created"
3790 b" with 'format.usestore' config disabled"
3801 b" with 'format.usestore' config disabled"
3791 )
3802 )
3792 )
3803 )
3793
3804
3794 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3805 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3795 if ui.hasconfig(b'format', b'use-share-safe'):
3806 if ui.hasconfig(b'format', b'use-share-safe'):
3796 msg = _(
3807 msg = _(
3797 b"ignoring enabled 'format.use-share-safe' config because "
3808 b"ignoring enabled 'format.use-share-safe' config because "
3798 b"it is incompatible with disabled 'format.usestore'"
3809 b"it is incompatible with disabled 'format.usestore'"
3799 b" config\n"
3810 b" config\n"
3800 )
3811 )
3801 ui.warn(msg)
3812 ui.warn(msg)
3802 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3813 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3803
3814
3804 return dropped
3815 return dropped
3805
3816
3806
3817
3807 def filterknowncreateopts(ui, createopts):
3818 def filterknowncreateopts(ui, createopts):
3808 """Filters a dict of repo creation options against options that are known.
3819 """Filters a dict of repo creation options against options that are known.
3809
3820
3810 Receives a dict of repo creation options and returns a dict of those
3821 Receives a dict of repo creation options and returns a dict of those
3811 options that we don't know how to handle.
3822 options that we don't know how to handle.
3812
3823
3813 This function is called as part of repository creation. If the
3824 This function is called as part of repository creation. If the
3814 returned dict contains any items, repository creation will not
3825 returned dict contains any items, repository creation will not
3815 be allowed, as it means there was a request to create a repository
3826 be allowed, as it means there was a request to create a repository
3816 with options not recognized by loaded code.
3827 with options not recognized by loaded code.
3817
3828
3818 Extensions can wrap this function to filter out creation options
3829 Extensions can wrap this function to filter out creation options
3819 they know how to handle.
3830 they know how to handle.
3820 """
3831 """
3821 known = {
3832 known = {
3822 b'backend',
3833 b'backend',
3823 b'lfs',
3834 b'lfs',
3824 b'narrowfiles',
3835 b'narrowfiles',
3825 b'sharedrepo',
3836 b'sharedrepo',
3826 b'sharedrelative',
3837 b'sharedrelative',
3827 b'shareditems',
3838 b'shareditems',
3828 b'shallowfilestore',
3839 b'shallowfilestore',
3829 }
3840 }
3830
3841
3831 return {k: v for k, v in createopts.items() if k not in known}
3842 return {k: v for k, v in createopts.items() if k not in known}
3832
3843
3833
3844
3834 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3845 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3835 """Create a new repository in a vfs.
3846 """Create a new repository in a vfs.
3836
3847
3837 ``path`` path to the new repo's working directory.
3848 ``path`` path to the new repo's working directory.
3838 ``createopts`` options for the new repository.
3849 ``createopts`` options for the new repository.
3839 ``requirement`` predefined set of requirements.
3850 ``requirement`` predefined set of requirements.
3840 (incompatible with ``createopts``)
3851 (incompatible with ``createopts``)
3841
3852
3842 The following keys for ``createopts`` are recognized:
3853 The following keys for ``createopts`` are recognized:
3843
3854
3844 backend
3855 backend
3845 The storage backend to use.
3856 The storage backend to use.
3846 lfs
3857 lfs
3847 Repository will be created with ``lfs`` requirement. The lfs extension
3858 Repository will be created with ``lfs`` requirement. The lfs extension
3848 will automatically be loaded when the repository is accessed.
3859 will automatically be loaded when the repository is accessed.
3849 narrowfiles
3860 narrowfiles
3850 Set up repository to support narrow file storage.
3861 Set up repository to support narrow file storage.
3851 sharedrepo
3862 sharedrepo
3852 Repository object from which storage should be shared.
3863 Repository object from which storage should be shared.
3853 sharedrelative
3864 sharedrelative
3854 Boolean indicating if the path to the shared repo should be
3865 Boolean indicating if the path to the shared repo should be
3855 stored as relative. By default, the pointer to the "parent" repo
3866 stored as relative. By default, the pointer to the "parent" repo
3856 is stored as an absolute path.
3867 is stored as an absolute path.
3857 shareditems
3868 shareditems
3858 Set of items to share to the new repository (in addition to storage).
3869 Set of items to share to the new repository (in addition to storage).
3859 shallowfilestore
3870 shallowfilestore
3860 Indicates that storage for files should be shallow (not all ancestor
3871 Indicates that storage for files should be shallow (not all ancestor
3861 revisions are known).
3872 revisions are known).
3862 """
3873 """
3863
3874
3864 if requirements is not None:
3875 if requirements is not None:
3865 if createopts is not None:
3876 if createopts is not None:
3866 msg = b'cannot specify both createopts and requirements'
3877 msg = b'cannot specify both createopts and requirements'
3867 raise error.ProgrammingError(msg)
3878 raise error.ProgrammingError(msg)
3868 createopts = {}
3879 createopts = {}
3869 else:
3880 else:
3870 createopts = defaultcreateopts(ui, createopts=createopts)
3881 createopts = defaultcreateopts(ui, createopts=createopts)
3871
3882
3872 unknownopts = filterknowncreateopts(ui, createopts)
3883 unknownopts = filterknowncreateopts(ui, createopts)
3873
3884
3874 if not isinstance(unknownopts, dict):
3885 if not isinstance(unknownopts, dict):
3875 raise error.ProgrammingError(
3886 raise error.ProgrammingError(
3876 b'filterknowncreateopts() did not return a dict'
3887 b'filterknowncreateopts() did not return a dict'
3877 )
3888 )
3878
3889
3879 if unknownopts:
3890 if unknownopts:
3880 raise error.Abort(
3891 raise error.Abort(
3881 _(
3892 _(
3882 b'unable to create repository because of unknown '
3893 b'unable to create repository because of unknown '
3883 b'creation option: %s'
3894 b'creation option: %s'
3884 )
3895 )
3885 % b', '.join(sorted(unknownopts)),
3896 % b', '.join(sorted(unknownopts)),
3886 hint=_(b'is a required extension not loaded?'),
3897 hint=_(b'is a required extension not loaded?'),
3887 )
3898 )
3888
3899
3889 requirements = newreporequirements(ui, createopts=createopts)
3900 requirements = newreporequirements(ui, createopts=createopts)
3890 requirements -= checkrequirementscompat(ui, requirements)
3901 requirements -= checkrequirementscompat(ui, requirements)
3891
3902
3892 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3903 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3893
3904
3894 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3905 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3895 if hgvfs.exists():
3906 if hgvfs.exists():
3896 raise error.RepoError(_(b'repository %s already exists') % path)
3907 raise error.RepoError(_(b'repository %s already exists') % path)
3897
3908
3898 if b'sharedrepo' in createopts:
3909 if b'sharedrepo' in createopts:
3899 sharedpath = createopts[b'sharedrepo'].sharedpath
3910 sharedpath = createopts[b'sharedrepo'].sharedpath
3900
3911
3901 if createopts.get(b'sharedrelative'):
3912 if createopts.get(b'sharedrelative'):
3902 try:
3913 try:
3903 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3914 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3904 sharedpath = util.pconvert(sharedpath)
3915 sharedpath = util.pconvert(sharedpath)
3905 except (IOError, ValueError) as e:
3916 except (IOError, ValueError) as e:
3906 # ValueError is raised on Windows if the drive letters differ
3917 # ValueError is raised on Windows if the drive letters differ
3907 # on each path.
3918 # on each path.
3908 raise error.Abort(
3919 raise error.Abort(
3909 _(b'cannot calculate relative path'),
3920 _(b'cannot calculate relative path'),
3910 hint=stringutil.forcebytestr(e),
3921 hint=stringutil.forcebytestr(e),
3911 )
3922 )
3912
3923
3913 if not wdirvfs.exists():
3924 if not wdirvfs.exists():
3914 wdirvfs.makedirs()
3925 wdirvfs.makedirs()
3915
3926
3916 hgvfs.makedir(notindexed=True)
3927 hgvfs.makedir(notindexed=True)
3917 if b'sharedrepo' not in createopts:
3928 if b'sharedrepo' not in createopts:
3918 hgvfs.mkdir(b'cache')
3929 hgvfs.mkdir(b'cache')
3919 hgvfs.mkdir(b'wcache')
3930 hgvfs.mkdir(b'wcache')
3920
3931
3921 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3932 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3922 if has_store and b'sharedrepo' not in createopts:
3933 if has_store and b'sharedrepo' not in createopts:
3923 hgvfs.mkdir(b'store')
3934 hgvfs.mkdir(b'store')
3924
3935
3925 # We create an invalid changelog outside the store so very old
3936 # We create an invalid changelog outside the store so very old
3926 # Mercurial versions (which didn't know about the requirements
3937 # Mercurial versions (which didn't know about the requirements
3927 # file) encounter an error on reading the changelog. This
3938 # file) encounter an error on reading the changelog. This
3928 # effectively locks out old clients and prevents them from
3939 # effectively locks out old clients and prevents them from
3929 # mucking with a repo in an unknown format.
3940 # mucking with a repo in an unknown format.
3930 #
3941 #
3931 # The revlog header has version 65535, which won't be recognized by
3942 # The revlog header has version 65535, which won't be recognized by
3932 # such old clients.
3943 # such old clients.
3933 hgvfs.append(
3944 hgvfs.append(
3934 b'00changelog.i',
3945 b'00changelog.i',
3935 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3946 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3936 b'layout',
3947 b'layout',
3937 )
3948 )
3938
3949
3939 # Filter the requirements into working copy and store ones
3950 # Filter the requirements into working copy and store ones
3940 wcreq, storereq = scmutil.filterrequirements(requirements)
3951 wcreq, storereq = scmutil.filterrequirements(requirements)
3941 # write working copy ones
3952 # write working copy ones
3942 scmutil.writerequires(hgvfs, wcreq)
3953 scmutil.writerequires(hgvfs, wcreq)
3943 # If there are store requirements and the current repository
3954 # If there are store requirements and the current repository
3944 # is not a shared one, write stored requirements
3955 # is not a shared one, write stored requirements
3945 # For new shared repository, we don't need to write the store
3956 # For new shared repository, we don't need to write the store
3946 # requirements as they are already present in store requires
3957 # requirements as they are already present in store requires
3947 if storereq and b'sharedrepo' not in createopts:
3958 if storereq and b'sharedrepo' not in createopts:
3948 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3959 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3949 scmutil.writerequires(storevfs, storereq)
3960 scmutil.writerequires(storevfs, storereq)
3950
3961
3951 # Write out file telling readers where to find the shared store.
3962 # Write out file telling readers where to find the shared store.
3952 if b'sharedrepo' in createopts:
3963 if b'sharedrepo' in createopts:
3953 hgvfs.write(b'sharedpath', sharedpath)
3964 hgvfs.write(b'sharedpath', sharedpath)
3954
3965
3955 if createopts.get(b'shareditems'):
3966 if createopts.get(b'shareditems'):
3956 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3967 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3957 hgvfs.write(b'shared', shared)
3968 hgvfs.write(b'shared', shared)
3958
3969
3959
3970
3960 def poisonrepository(repo):
3971 def poisonrepository(repo):
3961 """Poison a repository instance so it can no longer be used."""
3972 """Poison a repository instance so it can no longer be used."""
3962 # Perform any cleanup on the instance.
3973 # Perform any cleanup on the instance.
3963 repo.close()
3974 repo.close()
3964
3975
3965 # Our strategy is to replace the type of the object with one that
3976 # Our strategy is to replace the type of the object with one that
3966 # has all attribute lookups result in error.
3977 # has all attribute lookups result in error.
3967 #
3978 #
3968 # But we have to allow the close() method because some constructors
3979 # But we have to allow the close() method because some constructors
3969 # of repos call close() on repo references.
3980 # of repos call close() on repo references.
3970 class poisonedrepository:
3981 class poisonedrepository:
3971 def __getattribute__(self, item):
3982 def __getattribute__(self, item):
3972 if item == 'close':
3983 if item == 'close':
3973 return object.__getattribute__(self, item)
3984 return object.__getattribute__(self, item)
3974
3985
3975 raise error.ProgrammingError(
3986 raise error.ProgrammingError(
3976 b'repo instances should not be used after unshare'
3987 b'repo instances should not be used after unshare'
3977 )
3988 )
3978
3989
3979 def close(self):
3990 def close(self):
3980 pass
3991 pass
3981
3992
3982 # We may have a repoview, which intercepts __setattr__. So be sure
3993 # We may have a repoview, which intercepts __setattr__. So be sure
3983 # we operate at the lowest level possible.
3994 # we operate at the lowest level possible.
3984 object.__setattr__(repo, '__class__', poisonedrepository)
3995 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,3385 +1,3399 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 # coding: utf8
2 # coding: utf8
3 #
3 #
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """Storage back-end for Mercurial.
9 """Storage back-end for Mercurial.
10
10
11 This provides efficient delta storage with O(1) retrieve and append
11 This provides efficient delta storage with O(1) retrieve and append
12 and O(changes) merge between branches.
12 and O(changes) merge between branches.
13 """
13 """
14
14
15
15
16 import binascii
16 import binascii
17 import collections
17 import collections
18 import contextlib
18 import contextlib
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import zlib
22 import zlib
23
23
24 # import stuff from node for others to import from revlog
24 # import stuff from node for others to import from revlog
25 from .node import (
25 from .node import (
26 bin,
26 bin,
27 hex,
27 hex,
28 nullrev,
28 nullrev,
29 sha1nodeconstants,
29 sha1nodeconstants,
30 short,
30 short,
31 wdirrev,
31 wdirrev,
32 )
32 )
33 from .i18n import _
33 from .i18n import _
34 from .pycompat import getattr
34 from .pycompat import getattr
35 from .revlogutils.constants import (
35 from .revlogutils.constants import (
36 ALL_KINDS,
36 ALL_KINDS,
37 CHANGELOGV2,
37 CHANGELOGV2,
38 COMP_MODE_DEFAULT,
38 COMP_MODE_DEFAULT,
39 COMP_MODE_INLINE,
39 COMP_MODE_INLINE,
40 COMP_MODE_PLAIN,
40 COMP_MODE_PLAIN,
41 DELTA_BASE_REUSE_NO,
41 DELTA_BASE_REUSE_NO,
42 DELTA_BASE_REUSE_TRY,
42 DELTA_BASE_REUSE_TRY,
43 ENTRY_RANK,
43 ENTRY_RANK,
44 FEATURES_BY_VERSION,
44 FEATURES_BY_VERSION,
45 FLAG_GENERALDELTA,
45 FLAG_GENERALDELTA,
46 FLAG_INLINE_DATA,
46 FLAG_INLINE_DATA,
47 INDEX_HEADER,
47 INDEX_HEADER,
48 KIND_CHANGELOG,
48 KIND_CHANGELOG,
49 KIND_FILELOG,
49 KIND_FILELOG,
50 RANK_UNKNOWN,
50 RANK_UNKNOWN,
51 REVLOGV0,
51 REVLOGV0,
52 REVLOGV1,
52 REVLOGV1,
53 REVLOGV1_FLAGS,
53 REVLOGV1_FLAGS,
54 REVLOGV2,
54 REVLOGV2,
55 REVLOGV2_FLAGS,
55 REVLOGV2_FLAGS,
56 REVLOG_DEFAULT_FLAGS,
56 REVLOG_DEFAULT_FLAGS,
57 REVLOG_DEFAULT_FORMAT,
57 REVLOG_DEFAULT_FORMAT,
58 REVLOG_DEFAULT_VERSION,
58 REVLOG_DEFAULT_VERSION,
59 SUPPORTED_FLAGS,
59 SUPPORTED_FLAGS,
60 )
60 )
61 from .revlogutils.flagutil import (
61 from .revlogutils.flagutil import (
62 REVIDX_DEFAULT_FLAGS,
62 REVIDX_DEFAULT_FLAGS,
63 REVIDX_ELLIPSIS,
63 REVIDX_ELLIPSIS,
64 REVIDX_EXTSTORED,
64 REVIDX_EXTSTORED,
65 REVIDX_FLAGS_ORDER,
65 REVIDX_FLAGS_ORDER,
66 REVIDX_HASCOPIESINFO,
66 REVIDX_HASCOPIESINFO,
67 REVIDX_ISCENSORED,
67 REVIDX_ISCENSORED,
68 REVIDX_RAWTEXT_CHANGING_FLAGS,
68 REVIDX_RAWTEXT_CHANGING_FLAGS,
69 )
69 )
70 from .thirdparty import attr
70 from .thirdparty import attr
71 from . import (
71 from . import (
72 ancestor,
72 ancestor,
73 dagop,
73 dagop,
74 error,
74 error,
75 mdiff,
75 mdiff,
76 policy,
76 policy,
77 pycompat,
77 pycompat,
78 revlogutils,
78 revlogutils,
79 templatefilters,
79 templatefilters,
80 util,
80 util,
81 )
81 )
82 from .interfaces import (
82 from .interfaces import (
83 repository,
83 repository,
84 util as interfaceutil,
84 util as interfaceutil,
85 )
85 )
86 from .revlogutils import (
86 from .revlogutils import (
87 deltas as deltautil,
87 deltas as deltautil,
88 docket as docketutil,
88 docket as docketutil,
89 flagutil,
89 flagutil,
90 nodemap as nodemaputil,
90 nodemap as nodemaputil,
91 randomaccessfile,
91 randomaccessfile,
92 revlogv0,
92 revlogv0,
93 rewrite,
93 rewrite,
94 sidedata as sidedatautil,
94 sidedata as sidedatautil,
95 )
95 )
96 from .utils import (
96 from .utils import (
97 storageutil,
97 storageutil,
98 stringutil,
98 stringutil,
99 )
99 )
100
100
101 # blanked usage of all the name to prevent pyflakes constraints
101 # blanked usage of all the name to prevent pyflakes constraints
102 # We need these name available in the module for extensions.
102 # We need these name available in the module for extensions.
103
103
104 REVLOGV0
104 REVLOGV0
105 REVLOGV1
105 REVLOGV1
106 REVLOGV2
106 REVLOGV2
107 CHANGELOGV2
107 CHANGELOGV2
108 FLAG_INLINE_DATA
108 FLAG_INLINE_DATA
109 FLAG_GENERALDELTA
109 FLAG_GENERALDELTA
110 REVLOG_DEFAULT_FLAGS
110 REVLOG_DEFAULT_FLAGS
111 REVLOG_DEFAULT_FORMAT
111 REVLOG_DEFAULT_FORMAT
112 REVLOG_DEFAULT_VERSION
112 REVLOG_DEFAULT_VERSION
113 REVLOGV1_FLAGS
113 REVLOGV1_FLAGS
114 REVLOGV2_FLAGS
114 REVLOGV2_FLAGS
115 REVIDX_ISCENSORED
115 REVIDX_ISCENSORED
116 REVIDX_ELLIPSIS
116 REVIDX_ELLIPSIS
117 REVIDX_HASCOPIESINFO
117 REVIDX_HASCOPIESINFO
118 REVIDX_EXTSTORED
118 REVIDX_EXTSTORED
119 REVIDX_DEFAULT_FLAGS
119 REVIDX_DEFAULT_FLAGS
120 REVIDX_FLAGS_ORDER
120 REVIDX_FLAGS_ORDER
121 REVIDX_RAWTEXT_CHANGING_FLAGS
121 REVIDX_RAWTEXT_CHANGING_FLAGS
122
122
123 parsers = policy.importmod('parsers')
123 parsers = policy.importmod('parsers')
124 rustancestor = policy.importrust('ancestor')
124 rustancestor = policy.importrust('ancestor')
125 rustdagop = policy.importrust('dagop')
125 rustdagop = policy.importrust('dagop')
126 rustrevlog = policy.importrust('revlog')
126 rustrevlog = policy.importrust('revlog')
127
127
128 # Aliased for performance.
128 # Aliased for performance.
129 _zlibdecompress = zlib.decompress
129 _zlibdecompress = zlib.decompress
130
130
131 # max size of inline data embedded into a revlog
131 # max size of inline data embedded into a revlog
132 _maxinline = 131072
132 _maxinline = 131072
133
133
134 # Flag processors for REVIDX_ELLIPSIS.
134 # Flag processors for REVIDX_ELLIPSIS.
135 def ellipsisreadprocessor(rl, text):
135 def ellipsisreadprocessor(rl, text):
136 return text, False
136 return text, False
137
137
138
138
139 def ellipsiswriteprocessor(rl, text):
139 def ellipsiswriteprocessor(rl, text):
140 return text, False
140 return text, False
141
141
142
142
143 def ellipsisrawprocessor(rl, text):
143 def ellipsisrawprocessor(rl, text):
144 return False
144 return False
145
145
146
146
147 ellipsisprocessor = (
147 ellipsisprocessor = (
148 ellipsisreadprocessor,
148 ellipsisreadprocessor,
149 ellipsiswriteprocessor,
149 ellipsiswriteprocessor,
150 ellipsisrawprocessor,
150 ellipsisrawprocessor,
151 )
151 )
152
152
153
153
154 def _verify_revision(rl, skipflags, state, node):
154 def _verify_revision(rl, skipflags, state, node):
155 """Verify the integrity of the given revlog ``node`` while providing a hook
155 """Verify the integrity of the given revlog ``node`` while providing a hook
156 point for extensions to influence the operation."""
156 point for extensions to influence the operation."""
157 if skipflags:
157 if skipflags:
158 state[b'skipread'].add(node)
158 state[b'skipread'].add(node)
159 else:
159 else:
160 # Side-effect: read content and verify hash.
160 # Side-effect: read content and verify hash.
161 rl.revision(node)
161 rl.revision(node)
162
162
163
163
164 # True if a fast implementation for persistent-nodemap is available
164 # True if a fast implementation for persistent-nodemap is available
165 #
165 #
166 # We also consider we have a "fast" implementation in "pure" python because
166 # We also consider we have a "fast" implementation in "pure" python because
167 # people using pure don't really have performance consideration (and a
167 # people using pure don't really have performance consideration (and a
168 # wheelbarrow of other slowness source)
168 # wheelbarrow of other slowness source)
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
170 parsers, 'BaseIndexObject'
170 parsers, 'BaseIndexObject'
171 )
171 )
172
172
173
173
174 @interfaceutil.implementer(repository.irevisiondelta)
174 @interfaceutil.implementer(repository.irevisiondelta)
175 @attr.s(slots=True)
175 @attr.s(slots=True)
176 class revlogrevisiondelta:
176 class revlogrevisiondelta:
177 node = attr.ib()
177 node = attr.ib()
178 p1node = attr.ib()
178 p1node = attr.ib()
179 p2node = attr.ib()
179 p2node = attr.ib()
180 basenode = attr.ib()
180 basenode = attr.ib()
181 flags = attr.ib()
181 flags = attr.ib()
182 baserevisionsize = attr.ib()
182 baserevisionsize = attr.ib()
183 revision = attr.ib()
183 revision = attr.ib()
184 delta = attr.ib()
184 delta = attr.ib()
185 sidedata = attr.ib()
185 sidedata = attr.ib()
186 protocol_flags = attr.ib()
186 protocol_flags = attr.ib()
187 linknode = attr.ib(default=None)
187 linknode = attr.ib(default=None)
188
188
189
189
190 @interfaceutil.implementer(repository.iverifyproblem)
190 @interfaceutil.implementer(repository.iverifyproblem)
191 @attr.s(frozen=True)
191 @attr.s(frozen=True)
192 class revlogproblem:
192 class revlogproblem:
193 warning = attr.ib(default=None)
193 warning = attr.ib(default=None)
194 error = attr.ib(default=None)
194 error = attr.ib(default=None)
195 node = attr.ib(default=None)
195 node = attr.ib(default=None)
196
196
197
197
198 def parse_index_v1(data, inline):
198 def parse_index_v1(data, inline):
199 # call the C implementation to parse the index data
199 # call the C implementation to parse the index data
200 index, cache = parsers.parse_index2(data, inline)
200 index, cache = parsers.parse_index2(data, inline)
201 return index, cache
201 return index, cache
202
202
203
203
204 def parse_index_v2(data, inline):
204 def parse_index_v2(data, inline):
205 # call the C implementation to parse the index data
205 # call the C implementation to parse the index data
206 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
206 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
207 return index, cache
207 return index, cache
208
208
209
209
210 def parse_index_cl_v2(data, inline):
210 def parse_index_cl_v2(data, inline):
211 # call the C implementation to parse the index data
211 # call the C implementation to parse the index data
212 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
212 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
213 return index, cache
213 return index, cache
214
214
215
215
216 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
216 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
217
217
218 def parse_index_v1_nodemap(data, inline):
218 def parse_index_v1_nodemap(data, inline):
219 index, cache = parsers.parse_index_devel_nodemap(data, inline)
219 index, cache = parsers.parse_index_devel_nodemap(data, inline)
220 return index, cache
220 return index, cache
221
221
222
222
223 else:
223 else:
224 parse_index_v1_nodemap = None
224 parse_index_v1_nodemap = None
225
225
226
226
227 def parse_index_v1_mixed(data, inline):
227 def parse_index_v1_mixed(data, inline):
228 index, cache = parse_index_v1(data, inline)
228 index, cache = parse_index_v1(data, inline)
229 return rustrevlog.MixedIndex(index), cache
229 return rustrevlog.MixedIndex(index), cache
230
230
231
231
232 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
232 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
233 # signed integer)
233 # signed integer)
234 _maxentrysize = 0x7FFFFFFF
234 _maxentrysize = 0x7FFFFFFF
235
235
236 FILE_TOO_SHORT_MSG = _(
236 FILE_TOO_SHORT_MSG = _(
237 b'cannot read from revlog %s;'
237 b'cannot read from revlog %s;'
238 b' expected %d bytes from offset %d, data size is %d'
238 b' expected %d bytes from offset %d, data size is %d'
239 )
239 )
240
240
241 hexdigits = b'0123456789abcdefABCDEF'
241 hexdigits = b'0123456789abcdefABCDEF'
242
242
243
243
244 class revlog:
244 class revlog:
245 """
245 """
246 the underlying revision storage object
246 the underlying revision storage object
247
247
248 A revlog consists of two parts, an index and the revision data.
248 A revlog consists of two parts, an index and the revision data.
249
249
250 The index is a file with a fixed record size containing
250 The index is a file with a fixed record size containing
251 information on each revision, including its nodeid (hash), the
251 information on each revision, including its nodeid (hash), the
252 nodeids of its parents, the position and offset of its data within
252 nodeids of its parents, the position and offset of its data within
253 the data file, and the revision it's based on. Finally, each entry
253 the data file, and the revision it's based on. Finally, each entry
254 contains a linkrev entry that can serve as a pointer to external
254 contains a linkrev entry that can serve as a pointer to external
255 data.
255 data.
256
256
257 The revision data itself is a linear collection of data chunks.
257 The revision data itself is a linear collection of data chunks.
258 Each chunk represents a revision and is usually represented as a
258 Each chunk represents a revision and is usually represented as a
259 delta against the previous chunk. To bound lookup time, runs of
259 delta against the previous chunk. To bound lookup time, runs of
260 deltas are limited to about 2 times the length of the original
260 deltas are limited to about 2 times the length of the original
261 version data. This makes retrieval of a version proportional to
261 version data. This makes retrieval of a version proportional to
262 its size, or O(1) relative to the number of revisions.
262 its size, or O(1) relative to the number of revisions.
263
263
264 Both pieces of the revlog are written to in an append-only
264 Both pieces of the revlog are written to in an append-only
265 fashion, which means we never need to rewrite a file to insert or
265 fashion, which means we never need to rewrite a file to insert or
266 remove data, and can use some simple techniques to avoid the need
266 remove data, and can use some simple techniques to avoid the need
267 for locking while reading.
267 for locking while reading.
268
268
269 If checkambig, indexfile is opened with checkambig=True at
269 If checkambig, indexfile is opened with checkambig=True at
270 writing, to avoid file stat ambiguity.
270 writing, to avoid file stat ambiguity.
271
271
272 If mmaplargeindex is True, and an mmapindexthreshold is set, the
272 If mmaplargeindex is True, and an mmapindexthreshold is set, the
273 index will be mmapped rather than read if it is larger than the
273 index will be mmapped rather than read if it is larger than the
274 configured threshold.
274 configured threshold.
275
275
276 If censorable is True, the revlog can have censored revisions.
276 If censorable is True, the revlog can have censored revisions.
277
277
278 If `upperboundcomp` is not None, this is the expected maximal gain from
278 If `upperboundcomp` is not None, this is the expected maximal gain from
279 compression for the data content.
279 compression for the data content.
280
280
281 `concurrencychecker` is an optional function that receives 3 arguments: a
281 `concurrencychecker` is an optional function that receives 3 arguments: a
282 file handle, a filename, and an expected position. It should check whether
282 file handle, a filename, and an expected position. It should check whether
283 the current position in the file handle is valid, and log/warn/fail (by
283 the current position in the file handle is valid, and log/warn/fail (by
284 raising).
284 raising).
285
285
286 See mercurial/revlogutils/contants.py for details about the content of an
286 See mercurial/revlogutils/contants.py for details about the content of an
287 index entry.
287 index entry.
288 """
288 """
289
289
290 _flagserrorclass = error.RevlogError
290 _flagserrorclass = error.RevlogError
291
291
292 def __init__(
292 def __init__(
293 self,
293 self,
294 opener,
294 opener,
295 target,
295 target,
296 radix,
296 radix,
297 postfix=None, # only exist for `tmpcensored` now
297 postfix=None, # only exist for `tmpcensored` now
298 checkambig=False,
298 checkambig=False,
299 mmaplargeindex=False,
299 mmaplargeindex=False,
300 censorable=False,
300 censorable=False,
301 upperboundcomp=None,
301 upperboundcomp=None,
302 persistentnodemap=False,
302 persistentnodemap=False,
303 concurrencychecker=None,
303 concurrencychecker=None,
304 trypending=False,
304 trypending=False,
305 try_split=False,
305 canonical_parent_order=True,
306 canonical_parent_order=True,
306 ):
307 ):
307 """
308 """
308 create a revlog object
309 create a revlog object
309
310
310 opener is a function that abstracts the file opening operation
311 opener is a function that abstracts the file opening operation
311 and can be used to implement COW semantics or the like.
312 and can be used to implement COW semantics or the like.
312
313
313 `target`: a (KIND, ID) tuple that identify the content stored in
314 `target`: a (KIND, ID) tuple that identify the content stored in
314 this revlog. It help the rest of the code to understand what the revlog
315 this revlog. It help the rest of the code to understand what the revlog
315 is about without having to resort to heuristic and index filename
316 is about without having to resort to heuristic and index filename
316 analysis. Note: that this must be reliably be set by normal code, but
317 analysis. Note: that this must be reliably be set by normal code, but
317 that test, debug, or performance measurement code might not set this to
318 that test, debug, or performance measurement code might not set this to
318 accurate value.
319 accurate value.
319 """
320 """
320 self.upperboundcomp = upperboundcomp
321 self.upperboundcomp = upperboundcomp
321
322
322 self.radix = radix
323 self.radix = radix
323
324
324 self._docket_file = None
325 self._docket_file = None
325 self._indexfile = None
326 self._indexfile = None
326 self._datafile = None
327 self._datafile = None
327 self._sidedatafile = None
328 self._sidedatafile = None
328 self._nodemap_file = None
329 self._nodemap_file = None
329 self.postfix = postfix
330 self.postfix = postfix
330 self._trypending = trypending
331 self._trypending = trypending
332 self._try_split = try_split
331 self.opener = opener
333 self.opener = opener
332 if persistentnodemap:
334 if persistentnodemap:
333 self._nodemap_file = nodemaputil.get_nodemap_file(self)
335 self._nodemap_file = nodemaputil.get_nodemap_file(self)
334
336
335 assert target[0] in ALL_KINDS
337 assert target[0] in ALL_KINDS
336 assert len(target) == 2
338 assert len(target) == 2
337 self.target = target
339 self.target = target
338 # When True, indexfile is opened with checkambig=True at writing, to
340 # When True, indexfile is opened with checkambig=True at writing, to
339 # avoid file stat ambiguity.
341 # avoid file stat ambiguity.
340 self._checkambig = checkambig
342 self._checkambig = checkambig
341 self._mmaplargeindex = mmaplargeindex
343 self._mmaplargeindex = mmaplargeindex
342 self._censorable = censorable
344 self._censorable = censorable
343 # 3-tuple of (node, rev, text) for a raw revision.
345 # 3-tuple of (node, rev, text) for a raw revision.
344 self._revisioncache = None
346 self._revisioncache = None
345 # Maps rev to chain base rev.
347 # Maps rev to chain base rev.
346 self._chainbasecache = util.lrucachedict(100)
348 self._chainbasecache = util.lrucachedict(100)
347 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
349 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
348 self._chunkcache = (0, b'')
350 self._chunkcache = (0, b'')
349 # How much data to read and cache into the raw revlog data cache.
351 # How much data to read and cache into the raw revlog data cache.
350 self._chunkcachesize = 65536
352 self._chunkcachesize = 65536
351 self._maxchainlen = None
353 self._maxchainlen = None
352 self._deltabothparents = True
354 self._deltabothparents = True
353 self._candidate_group_chunk_size = 0
355 self._candidate_group_chunk_size = 0
354 self._debug_delta = False
356 self._debug_delta = False
355 self.index = None
357 self.index = None
356 self._docket = None
358 self._docket = None
357 self._nodemap_docket = None
359 self._nodemap_docket = None
358 # Mapping of partial identifiers to full nodes.
360 # Mapping of partial identifiers to full nodes.
359 self._pcache = {}
361 self._pcache = {}
360 # Mapping of revision integer to full node.
362 # Mapping of revision integer to full node.
361 self._compengine = b'zlib'
363 self._compengine = b'zlib'
362 self._compengineopts = {}
364 self._compengineopts = {}
363 self._maxdeltachainspan = -1
365 self._maxdeltachainspan = -1
364 self._withsparseread = False
366 self._withsparseread = False
365 self._sparserevlog = False
367 self._sparserevlog = False
366 self.hassidedata = False
368 self.hassidedata = False
367 self._srdensitythreshold = 0.50
369 self._srdensitythreshold = 0.50
368 self._srmingapsize = 262144
370 self._srmingapsize = 262144
369
371
370 # other optionnals features
372 # other optionnals features
371
373
372 # might remove rank configuration once the computation has no impact
374 # might remove rank configuration once the computation has no impact
373 self._compute_rank = False
375 self._compute_rank = False
374
376
375 # Make copy of flag processors so each revlog instance can support
377 # Make copy of flag processors so each revlog instance can support
376 # custom flags.
378 # custom flags.
377 self._flagprocessors = dict(flagutil.flagprocessors)
379 self._flagprocessors = dict(flagutil.flagprocessors)
378
380
379 # 3-tuple of file handles being used for active writing.
381 # 3-tuple of file handles being used for active writing.
380 self._writinghandles = None
382 self._writinghandles = None
381 # prevent nesting of addgroup
383 # prevent nesting of addgroup
382 self._adding_group = None
384 self._adding_group = None
383
385
384 self._loadindex()
386 self._loadindex()
385
387
386 self._concurrencychecker = concurrencychecker
388 self._concurrencychecker = concurrencychecker
387
389
388 # parent order is supposed to be semantically irrelevant, so we
390 # parent order is supposed to be semantically irrelevant, so we
389 # normally resort parents to ensure that the first parent is non-null,
391 # normally resort parents to ensure that the first parent is non-null,
390 # if there is a non-null parent at all.
392 # if there is a non-null parent at all.
391 # filelog abuses the parent order as flag to mark some instances of
393 # filelog abuses the parent order as flag to mark some instances of
392 # meta-encoded files, so allow it to disable this behavior.
394 # meta-encoded files, so allow it to disable this behavior.
393 self.canonical_parent_order = canonical_parent_order
395 self.canonical_parent_order = canonical_parent_order
394
396
395 def _init_opts(self):
397 def _init_opts(self):
396 """process options (from above/config) to setup associated default revlog mode
398 """process options (from above/config) to setup associated default revlog mode
397
399
398 These values might be affected when actually reading on disk information.
400 These values might be affected when actually reading on disk information.
399
401
400 The relevant values are returned for use in _loadindex().
402 The relevant values are returned for use in _loadindex().
401
403
402 * newversionflags:
404 * newversionflags:
403 version header to use if we need to create a new revlog
405 version header to use if we need to create a new revlog
404
406
405 * mmapindexthreshold:
407 * mmapindexthreshold:
406 minimal index size for start to use mmap
408 minimal index size for start to use mmap
407
409
408 * force_nodemap:
410 * force_nodemap:
409 force the usage of a "development" version of the nodemap code
411 force the usage of a "development" version of the nodemap code
410 """
412 """
411 mmapindexthreshold = None
413 mmapindexthreshold = None
412 opts = self.opener.options
414 opts = self.opener.options
413
415
414 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
416 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
415 new_header = CHANGELOGV2
417 new_header = CHANGELOGV2
416 self._compute_rank = opts.get(b'changelogv2.compute-rank', True)
418 self._compute_rank = opts.get(b'changelogv2.compute-rank', True)
417 elif b'revlogv2' in opts:
419 elif b'revlogv2' in opts:
418 new_header = REVLOGV2
420 new_header = REVLOGV2
419 elif b'revlogv1' in opts:
421 elif b'revlogv1' in opts:
420 new_header = REVLOGV1 | FLAG_INLINE_DATA
422 new_header = REVLOGV1 | FLAG_INLINE_DATA
421 if b'generaldelta' in opts:
423 if b'generaldelta' in opts:
422 new_header |= FLAG_GENERALDELTA
424 new_header |= FLAG_GENERALDELTA
423 elif b'revlogv0' in self.opener.options:
425 elif b'revlogv0' in self.opener.options:
424 new_header = REVLOGV0
426 new_header = REVLOGV0
425 else:
427 else:
426 new_header = REVLOG_DEFAULT_VERSION
428 new_header = REVLOG_DEFAULT_VERSION
427
429
428 if b'chunkcachesize' in opts:
430 if b'chunkcachesize' in opts:
429 self._chunkcachesize = opts[b'chunkcachesize']
431 self._chunkcachesize = opts[b'chunkcachesize']
430 if b'maxchainlen' in opts:
432 if b'maxchainlen' in opts:
431 self._maxchainlen = opts[b'maxchainlen']
433 self._maxchainlen = opts[b'maxchainlen']
432 if b'deltabothparents' in opts:
434 if b'deltabothparents' in opts:
433 self._deltabothparents = opts[b'deltabothparents']
435 self._deltabothparents = opts[b'deltabothparents']
434 dps_cgds = opts.get(b'delta-parent-search.candidate-group-chunk-size')
436 dps_cgds = opts.get(b'delta-parent-search.candidate-group-chunk-size')
435 if dps_cgds:
437 if dps_cgds:
436 self._candidate_group_chunk_size = dps_cgds
438 self._candidate_group_chunk_size = dps_cgds
437 self._lazydelta = bool(opts.get(b'lazydelta', True))
439 self._lazydelta = bool(opts.get(b'lazydelta', True))
438 self._lazydeltabase = False
440 self._lazydeltabase = False
439 if self._lazydelta:
441 if self._lazydelta:
440 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
442 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
441 if b'debug-delta' in opts:
443 if b'debug-delta' in opts:
442 self._debug_delta = opts[b'debug-delta']
444 self._debug_delta = opts[b'debug-delta']
443 if b'compengine' in opts:
445 if b'compengine' in opts:
444 self._compengine = opts[b'compengine']
446 self._compengine = opts[b'compengine']
445 if b'zlib.level' in opts:
447 if b'zlib.level' in opts:
446 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
448 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
447 if b'zstd.level' in opts:
449 if b'zstd.level' in opts:
448 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
450 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
449 if b'maxdeltachainspan' in opts:
451 if b'maxdeltachainspan' in opts:
450 self._maxdeltachainspan = opts[b'maxdeltachainspan']
452 self._maxdeltachainspan = opts[b'maxdeltachainspan']
451 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
453 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
452 mmapindexthreshold = opts[b'mmapindexthreshold']
454 mmapindexthreshold = opts[b'mmapindexthreshold']
453 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
455 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
454 withsparseread = bool(opts.get(b'with-sparse-read', False))
456 withsparseread = bool(opts.get(b'with-sparse-read', False))
455 # sparse-revlog forces sparse-read
457 # sparse-revlog forces sparse-read
456 self._withsparseread = self._sparserevlog or withsparseread
458 self._withsparseread = self._sparserevlog or withsparseread
457 if b'sparse-read-density-threshold' in opts:
459 if b'sparse-read-density-threshold' in opts:
458 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
460 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
459 if b'sparse-read-min-gap-size' in opts:
461 if b'sparse-read-min-gap-size' in opts:
460 self._srmingapsize = opts[b'sparse-read-min-gap-size']
462 self._srmingapsize = opts[b'sparse-read-min-gap-size']
461 if opts.get(b'enableellipsis'):
463 if opts.get(b'enableellipsis'):
462 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
464 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
463
465
464 # revlog v0 doesn't have flag processors
466 # revlog v0 doesn't have flag processors
465 for flag, processor in opts.get(b'flagprocessors', {}).items():
467 for flag, processor in opts.get(b'flagprocessors', {}).items():
466 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
468 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
467
469
468 if self._chunkcachesize <= 0:
470 if self._chunkcachesize <= 0:
469 raise error.RevlogError(
471 raise error.RevlogError(
470 _(b'revlog chunk cache size %r is not greater than 0')
472 _(b'revlog chunk cache size %r is not greater than 0')
471 % self._chunkcachesize
473 % self._chunkcachesize
472 )
474 )
473 elif self._chunkcachesize & (self._chunkcachesize - 1):
475 elif self._chunkcachesize & (self._chunkcachesize - 1):
474 raise error.RevlogError(
476 raise error.RevlogError(
475 _(b'revlog chunk cache size %r is not a power of 2')
477 _(b'revlog chunk cache size %r is not a power of 2')
476 % self._chunkcachesize
478 % self._chunkcachesize
477 )
479 )
478 force_nodemap = opts.get(b'devel-force-nodemap', False)
480 force_nodemap = opts.get(b'devel-force-nodemap', False)
479 return new_header, mmapindexthreshold, force_nodemap
481 return new_header, mmapindexthreshold, force_nodemap
480
482
481 def _get_data(self, filepath, mmap_threshold, size=None):
483 def _get_data(self, filepath, mmap_threshold, size=None):
482 """return a file content with or without mmap
484 """return a file content with or without mmap
483
485
484 If the file is missing return the empty string"""
486 If the file is missing return the empty string"""
485 try:
487 try:
486 with self.opener(filepath) as fp:
488 with self.opener(filepath) as fp:
487 if mmap_threshold is not None:
489 if mmap_threshold is not None:
488 file_size = self.opener.fstat(fp).st_size
490 file_size = self.opener.fstat(fp).st_size
489 if file_size >= mmap_threshold:
491 if file_size >= mmap_threshold:
490 if size is not None:
492 if size is not None:
491 # avoid potentiel mmap crash
493 # avoid potentiel mmap crash
492 size = min(file_size, size)
494 size = min(file_size, size)
493 # TODO: should .close() to release resources without
495 # TODO: should .close() to release resources without
494 # relying on Python GC
496 # relying on Python GC
495 if size is None:
497 if size is None:
496 return util.buffer(util.mmapread(fp))
498 return util.buffer(util.mmapread(fp))
497 else:
499 else:
498 return util.buffer(util.mmapread(fp, size))
500 return util.buffer(util.mmapread(fp, size))
499 if size is None:
501 if size is None:
500 return fp.read()
502 return fp.read()
501 else:
503 else:
502 return fp.read(size)
504 return fp.read(size)
503 except FileNotFoundError:
505 except FileNotFoundError:
504 return b''
506 return b''
505
507
506 def _loadindex(self, docket=None):
508 def _loadindex(self, docket=None):
507
509
508 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
510 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
509
511
510 if self.postfix is not None:
512 if self.postfix is not None:
511 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
513 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
512 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
514 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
513 entry_point = b'%s.i.a' % self.radix
515 entry_point = b'%s.i.a' % self.radix
516 elif self._try_split and self.opener.exists(b'%s.i.s' % self.radix):
517 entry_point = b'%s.i.s' % self.radix
514 else:
518 else:
515 entry_point = b'%s.i' % self.radix
519 entry_point = b'%s.i' % self.radix
516
520
517 if docket is not None:
521 if docket is not None:
518 self._docket = docket
522 self._docket = docket
519 self._docket_file = entry_point
523 self._docket_file = entry_point
520 else:
524 else:
521 self._initempty = True
525 self._initempty = True
522 entry_data = self._get_data(entry_point, mmapindexthreshold)
526 entry_data = self._get_data(entry_point, mmapindexthreshold)
523 if len(entry_data) > 0:
527 if len(entry_data) > 0:
524 header = INDEX_HEADER.unpack(entry_data[:4])[0]
528 header = INDEX_HEADER.unpack(entry_data[:4])[0]
525 self._initempty = False
529 self._initempty = False
526 else:
530 else:
527 header = new_header
531 header = new_header
528
532
529 self._format_flags = header & ~0xFFFF
533 self._format_flags = header & ~0xFFFF
530 self._format_version = header & 0xFFFF
534 self._format_version = header & 0xFFFF
531
535
532 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
536 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
533 if supported_flags is None:
537 if supported_flags is None:
534 msg = _(b'unknown version (%d) in revlog %s')
538 msg = _(b'unknown version (%d) in revlog %s')
535 msg %= (self._format_version, self.display_id)
539 msg %= (self._format_version, self.display_id)
536 raise error.RevlogError(msg)
540 raise error.RevlogError(msg)
537 elif self._format_flags & ~supported_flags:
541 elif self._format_flags & ~supported_flags:
538 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
542 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
539 display_flag = self._format_flags >> 16
543 display_flag = self._format_flags >> 16
540 msg %= (display_flag, self._format_version, self.display_id)
544 msg %= (display_flag, self._format_version, self.display_id)
541 raise error.RevlogError(msg)
545 raise error.RevlogError(msg)
542
546
543 features = FEATURES_BY_VERSION[self._format_version]
547 features = FEATURES_BY_VERSION[self._format_version]
544 self._inline = features[b'inline'](self._format_flags)
548 self._inline = features[b'inline'](self._format_flags)
545 self._generaldelta = features[b'generaldelta'](self._format_flags)
549 self._generaldelta = features[b'generaldelta'](self._format_flags)
546 self.hassidedata = features[b'sidedata']
550 self.hassidedata = features[b'sidedata']
547
551
548 if not features[b'docket']:
552 if not features[b'docket']:
549 self._indexfile = entry_point
553 self._indexfile = entry_point
550 index_data = entry_data
554 index_data = entry_data
551 else:
555 else:
552 self._docket_file = entry_point
556 self._docket_file = entry_point
553 if self._initempty:
557 if self._initempty:
554 self._docket = docketutil.default_docket(self, header)
558 self._docket = docketutil.default_docket(self, header)
555 else:
559 else:
556 self._docket = docketutil.parse_docket(
560 self._docket = docketutil.parse_docket(
557 self, entry_data, use_pending=self._trypending
561 self, entry_data, use_pending=self._trypending
558 )
562 )
559
563
560 if self._docket is not None:
564 if self._docket is not None:
561 self._indexfile = self._docket.index_filepath()
565 self._indexfile = self._docket.index_filepath()
562 index_data = b''
566 index_data = b''
563 index_size = self._docket.index_end
567 index_size = self._docket.index_end
564 if index_size > 0:
568 if index_size > 0:
565 index_data = self._get_data(
569 index_data = self._get_data(
566 self._indexfile, mmapindexthreshold, size=index_size
570 self._indexfile, mmapindexthreshold, size=index_size
567 )
571 )
568 if len(index_data) < index_size:
572 if len(index_data) < index_size:
569 msg = _(b'too few index data for %s: got %d, expected %d')
573 msg = _(b'too few index data for %s: got %d, expected %d')
570 msg %= (self.display_id, len(index_data), index_size)
574 msg %= (self.display_id, len(index_data), index_size)
571 raise error.RevlogError(msg)
575 raise error.RevlogError(msg)
572
576
573 self._inline = False
577 self._inline = False
574 # generaldelta implied by version 2 revlogs.
578 # generaldelta implied by version 2 revlogs.
575 self._generaldelta = True
579 self._generaldelta = True
576 # the logic for persistent nodemap will be dealt with within the
580 # the logic for persistent nodemap will be dealt with within the
577 # main docket, so disable it for now.
581 # main docket, so disable it for now.
578 self._nodemap_file = None
582 self._nodemap_file = None
579
583
580 if self._docket is not None:
584 if self._docket is not None:
581 self._datafile = self._docket.data_filepath()
585 self._datafile = self._docket.data_filepath()
582 self._sidedatafile = self._docket.sidedata_filepath()
586 self._sidedatafile = self._docket.sidedata_filepath()
583 elif self.postfix is None:
587 elif self.postfix is None:
584 self._datafile = b'%s.d' % self.radix
588 self._datafile = b'%s.d' % self.radix
585 else:
589 else:
586 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
590 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
587
591
588 self.nodeconstants = sha1nodeconstants
592 self.nodeconstants = sha1nodeconstants
589 self.nullid = self.nodeconstants.nullid
593 self.nullid = self.nodeconstants.nullid
590
594
591 # sparse-revlog can't be on without general-delta (issue6056)
595 # sparse-revlog can't be on without general-delta (issue6056)
592 if not self._generaldelta:
596 if not self._generaldelta:
593 self._sparserevlog = False
597 self._sparserevlog = False
594
598
595 self._storedeltachains = True
599 self._storedeltachains = True
596
600
597 devel_nodemap = (
601 devel_nodemap = (
598 self._nodemap_file
602 self._nodemap_file
599 and force_nodemap
603 and force_nodemap
600 and parse_index_v1_nodemap is not None
604 and parse_index_v1_nodemap is not None
601 )
605 )
602
606
603 use_rust_index = False
607 use_rust_index = False
604 if rustrevlog is not None:
608 if rustrevlog is not None:
605 if self._nodemap_file is not None:
609 if self._nodemap_file is not None:
606 use_rust_index = True
610 use_rust_index = True
607 else:
611 else:
608 use_rust_index = self.opener.options.get(b'rust.index')
612 use_rust_index = self.opener.options.get(b'rust.index')
609
613
610 self._parse_index = parse_index_v1
614 self._parse_index = parse_index_v1
611 if self._format_version == REVLOGV0:
615 if self._format_version == REVLOGV0:
612 self._parse_index = revlogv0.parse_index_v0
616 self._parse_index = revlogv0.parse_index_v0
613 elif self._format_version == REVLOGV2:
617 elif self._format_version == REVLOGV2:
614 self._parse_index = parse_index_v2
618 self._parse_index = parse_index_v2
615 elif self._format_version == CHANGELOGV2:
619 elif self._format_version == CHANGELOGV2:
616 self._parse_index = parse_index_cl_v2
620 self._parse_index = parse_index_cl_v2
617 elif devel_nodemap:
621 elif devel_nodemap:
618 self._parse_index = parse_index_v1_nodemap
622 self._parse_index = parse_index_v1_nodemap
619 elif use_rust_index:
623 elif use_rust_index:
620 self._parse_index = parse_index_v1_mixed
624 self._parse_index = parse_index_v1_mixed
621 try:
625 try:
622 d = self._parse_index(index_data, self._inline)
626 d = self._parse_index(index_data, self._inline)
623 index, chunkcache = d
627 index, chunkcache = d
624 use_nodemap = (
628 use_nodemap = (
625 not self._inline
629 not self._inline
626 and self._nodemap_file is not None
630 and self._nodemap_file is not None
627 and util.safehasattr(index, 'update_nodemap_data')
631 and util.safehasattr(index, 'update_nodemap_data')
628 )
632 )
629 if use_nodemap:
633 if use_nodemap:
630 nodemap_data = nodemaputil.persisted_data(self)
634 nodemap_data = nodemaputil.persisted_data(self)
631 if nodemap_data is not None:
635 if nodemap_data is not None:
632 docket = nodemap_data[0]
636 docket = nodemap_data[0]
633 if (
637 if (
634 len(d[0]) > docket.tip_rev
638 len(d[0]) > docket.tip_rev
635 and d[0][docket.tip_rev][7] == docket.tip_node
639 and d[0][docket.tip_rev][7] == docket.tip_node
636 ):
640 ):
637 # no changelog tampering
641 # no changelog tampering
638 self._nodemap_docket = docket
642 self._nodemap_docket = docket
639 index.update_nodemap_data(*nodemap_data)
643 index.update_nodemap_data(*nodemap_data)
640 except (ValueError, IndexError):
644 except (ValueError, IndexError):
641 raise error.RevlogError(
645 raise error.RevlogError(
642 _(b"index %s is corrupted") % self.display_id
646 _(b"index %s is corrupted") % self.display_id
643 )
647 )
644 self.index = index
648 self.index = index
645 self._segmentfile = randomaccessfile.randomaccessfile(
649 self._segmentfile = randomaccessfile.randomaccessfile(
646 self.opener,
650 self.opener,
647 (self._indexfile if self._inline else self._datafile),
651 (self._indexfile if self._inline else self._datafile),
648 self._chunkcachesize,
652 self._chunkcachesize,
649 chunkcache,
653 chunkcache,
650 )
654 )
651 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
655 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
652 self.opener,
656 self.opener,
653 self._sidedatafile,
657 self._sidedatafile,
654 self._chunkcachesize,
658 self._chunkcachesize,
655 )
659 )
656 # revnum -> (chain-length, sum-delta-length)
660 # revnum -> (chain-length, sum-delta-length)
657 self._chaininfocache = util.lrucachedict(500)
661 self._chaininfocache = util.lrucachedict(500)
658 # revlog header -> revlog compressor
662 # revlog header -> revlog compressor
659 self._decompressors = {}
663 self._decompressors = {}
660
664
661 @util.propertycache
665 @util.propertycache
662 def revlog_kind(self):
666 def revlog_kind(self):
663 return self.target[0]
667 return self.target[0]
664
668
665 @util.propertycache
669 @util.propertycache
666 def display_id(self):
670 def display_id(self):
667 """The public facing "ID" of the revlog that we use in message"""
671 """The public facing "ID" of the revlog that we use in message"""
668 if self.revlog_kind == KIND_FILELOG:
672 if self.revlog_kind == KIND_FILELOG:
669 # Reference the file without the "data/" prefix, so it is familiar
673 # Reference the file without the "data/" prefix, so it is familiar
670 # to the user.
674 # to the user.
671 return self.target[1]
675 return self.target[1]
672 else:
676 else:
673 return self.radix
677 return self.radix
674
678
675 def _get_decompressor(self, t):
679 def _get_decompressor(self, t):
676 try:
680 try:
677 compressor = self._decompressors[t]
681 compressor = self._decompressors[t]
678 except KeyError:
682 except KeyError:
679 try:
683 try:
680 engine = util.compengines.forrevlogheader(t)
684 engine = util.compengines.forrevlogheader(t)
681 compressor = engine.revlogcompressor(self._compengineopts)
685 compressor = engine.revlogcompressor(self._compengineopts)
682 self._decompressors[t] = compressor
686 self._decompressors[t] = compressor
683 except KeyError:
687 except KeyError:
684 raise error.RevlogError(
688 raise error.RevlogError(
685 _(b'unknown compression type %s') % binascii.hexlify(t)
689 _(b'unknown compression type %s') % binascii.hexlify(t)
686 )
690 )
687 return compressor
691 return compressor
688
692
689 @util.propertycache
693 @util.propertycache
690 def _compressor(self):
694 def _compressor(self):
691 engine = util.compengines[self._compengine]
695 engine = util.compengines[self._compengine]
692 return engine.revlogcompressor(self._compengineopts)
696 return engine.revlogcompressor(self._compengineopts)
693
697
694 @util.propertycache
698 @util.propertycache
695 def _decompressor(self):
699 def _decompressor(self):
696 """the default decompressor"""
700 """the default decompressor"""
697 if self._docket is None:
701 if self._docket is None:
698 return None
702 return None
699 t = self._docket.default_compression_header
703 t = self._docket.default_compression_header
700 c = self._get_decompressor(t)
704 c = self._get_decompressor(t)
701 return c.decompress
705 return c.decompress
702
706
703 def _indexfp(self):
707 def _indexfp(self):
704 """file object for the revlog's index file"""
708 """file object for the revlog's index file"""
705 return self.opener(self._indexfile, mode=b"r")
709 return self.opener(self._indexfile, mode=b"r")
706
710
707 def __index_write_fp(self):
711 def __index_write_fp(self):
708 # You should not use this directly and use `_writing` instead
712 # You should not use this directly and use `_writing` instead
709 try:
713 try:
710 f = self.opener(
714 f = self.opener(
711 self._indexfile, mode=b"r+", checkambig=self._checkambig
715 self._indexfile, mode=b"r+", checkambig=self._checkambig
712 )
716 )
713 if self._docket is None:
717 if self._docket is None:
714 f.seek(0, os.SEEK_END)
718 f.seek(0, os.SEEK_END)
715 else:
719 else:
716 f.seek(self._docket.index_end, os.SEEK_SET)
720 f.seek(self._docket.index_end, os.SEEK_SET)
717 return f
721 return f
718 except FileNotFoundError:
722 except FileNotFoundError:
719 return self.opener(
723 return self.opener(
720 self._indexfile, mode=b"w+", checkambig=self._checkambig
724 self._indexfile, mode=b"w+", checkambig=self._checkambig
721 )
725 )
722
726
723 def __index_new_fp(self):
727 def __index_new_fp(self):
724 # You should not use this unless you are upgrading from inline revlog
728 # You should not use this unless you are upgrading from inline revlog
725 return self.opener(
729 return self.opener(
726 self._indexfile,
730 self._indexfile,
727 mode=b"w",
731 mode=b"w",
728 checkambig=self._checkambig,
732 checkambig=self._checkambig,
729 atomictemp=True,
733 atomictemp=True,
730 )
734 )
731
735
732 def _datafp(self, mode=b'r'):
736 def _datafp(self, mode=b'r'):
733 """file object for the revlog's data file"""
737 """file object for the revlog's data file"""
734 return self.opener(self._datafile, mode=mode)
738 return self.opener(self._datafile, mode=mode)
735
739
736 @contextlib.contextmanager
740 @contextlib.contextmanager
737 def _sidedatareadfp(self):
741 def _sidedatareadfp(self):
738 """file object suitable to read sidedata"""
742 """file object suitable to read sidedata"""
739 if self._writinghandles:
743 if self._writinghandles:
740 yield self._writinghandles[2]
744 yield self._writinghandles[2]
741 else:
745 else:
742 with self.opener(self._sidedatafile) as fp:
746 with self.opener(self._sidedatafile) as fp:
743 yield fp
747 yield fp
744
748
745 def tiprev(self):
749 def tiprev(self):
746 return len(self.index) - 1
750 return len(self.index) - 1
747
751
748 def tip(self):
752 def tip(self):
749 return self.node(self.tiprev())
753 return self.node(self.tiprev())
750
754
751 def __contains__(self, rev):
755 def __contains__(self, rev):
752 return 0 <= rev < len(self)
756 return 0 <= rev < len(self)
753
757
754 def __len__(self):
758 def __len__(self):
755 return len(self.index)
759 return len(self.index)
756
760
757 def __iter__(self):
761 def __iter__(self):
758 return iter(range(len(self)))
762 return iter(range(len(self)))
759
763
760 def revs(self, start=0, stop=None):
764 def revs(self, start=0, stop=None):
761 """iterate over all rev in this revlog (from start to stop)"""
765 """iterate over all rev in this revlog (from start to stop)"""
762 return storageutil.iterrevs(len(self), start=start, stop=stop)
766 return storageutil.iterrevs(len(self), start=start, stop=stop)
763
767
764 def hasnode(self, node):
768 def hasnode(self, node):
765 try:
769 try:
766 self.rev(node)
770 self.rev(node)
767 return True
771 return True
768 except KeyError:
772 except KeyError:
769 return False
773 return False
770
774
771 def candelta(self, baserev, rev):
775 def candelta(self, baserev, rev):
772 """whether two revisions (baserev, rev) can be delta-ed or not"""
776 """whether two revisions (baserev, rev) can be delta-ed or not"""
773 # Disable delta if either rev requires a content-changing flag
777 # Disable delta if either rev requires a content-changing flag
774 # processor (ex. LFS). This is because such flag processor can alter
778 # processor (ex. LFS). This is because such flag processor can alter
775 # the rawtext content that the delta will be based on, and two clients
779 # the rawtext content that the delta will be based on, and two clients
776 # could have a same revlog node with different flags (i.e. different
780 # could have a same revlog node with different flags (i.e. different
777 # rawtext contents) and the delta could be incompatible.
781 # rawtext contents) and the delta could be incompatible.
778 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
782 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
779 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
783 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
780 ):
784 ):
781 return False
785 return False
782 return True
786 return True
783
787
784 def update_caches(self, transaction):
788 def update_caches(self, transaction):
785 if self._nodemap_file is not None:
789 if self._nodemap_file is not None:
786 if transaction is None:
790 if transaction is None:
787 nodemaputil.update_persistent_nodemap(self)
791 nodemaputil.update_persistent_nodemap(self)
788 else:
792 else:
789 nodemaputil.setup_persistent_nodemap(transaction, self)
793 nodemaputil.setup_persistent_nodemap(transaction, self)
790
794
791 def clearcaches(self):
795 def clearcaches(self):
792 self._revisioncache = None
796 self._revisioncache = None
793 self._chainbasecache.clear()
797 self._chainbasecache.clear()
794 self._segmentfile.clear_cache()
798 self._segmentfile.clear_cache()
795 self._segmentfile_sidedata.clear_cache()
799 self._segmentfile_sidedata.clear_cache()
796 self._pcache = {}
800 self._pcache = {}
797 self._nodemap_docket = None
801 self._nodemap_docket = None
798 self.index.clearcaches()
802 self.index.clearcaches()
799 # The python code is the one responsible for validating the docket, we
803 # The python code is the one responsible for validating the docket, we
800 # end up having to refresh it here.
804 # end up having to refresh it here.
801 use_nodemap = (
805 use_nodemap = (
802 not self._inline
806 not self._inline
803 and self._nodemap_file is not None
807 and self._nodemap_file is not None
804 and util.safehasattr(self.index, 'update_nodemap_data')
808 and util.safehasattr(self.index, 'update_nodemap_data')
805 )
809 )
806 if use_nodemap:
810 if use_nodemap:
807 nodemap_data = nodemaputil.persisted_data(self)
811 nodemap_data = nodemaputil.persisted_data(self)
808 if nodemap_data is not None:
812 if nodemap_data is not None:
809 self._nodemap_docket = nodemap_data[0]
813 self._nodemap_docket = nodemap_data[0]
810 self.index.update_nodemap_data(*nodemap_data)
814 self.index.update_nodemap_data(*nodemap_data)
811
815
812 def rev(self, node):
816 def rev(self, node):
813 try:
817 try:
814 return self.index.rev(node)
818 return self.index.rev(node)
815 except TypeError:
819 except TypeError:
816 raise
820 raise
817 except error.RevlogError:
821 except error.RevlogError:
818 # parsers.c radix tree lookup failed
822 # parsers.c radix tree lookup failed
819 if (
823 if (
820 node == self.nodeconstants.wdirid
824 node == self.nodeconstants.wdirid
821 or node in self.nodeconstants.wdirfilenodeids
825 or node in self.nodeconstants.wdirfilenodeids
822 ):
826 ):
823 raise error.WdirUnsupported
827 raise error.WdirUnsupported
824 raise error.LookupError(node, self.display_id, _(b'no node'))
828 raise error.LookupError(node, self.display_id, _(b'no node'))
825
829
826 # Accessors for index entries.
830 # Accessors for index entries.
827
831
828 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
832 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
829 # are flags.
833 # are flags.
830 def start(self, rev):
834 def start(self, rev):
831 return int(self.index[rev][0] >> 16)
835 return int(self.index[rev][0] >> 16)
832
836
833 def sidedata_cut_off(self, rev):
837 def sidedata_cut_off(self, rev):
834 sd_cut_off = self.index[rev][8]
838 sd_cut_off = self.index[rev][8]
835 if sd_cut_off != 0:
839 if sd_cut_off != 0:
836 return sd_cut_off
840 return sd_cut_off
837 # This is some annoying dance, because entries without sidedata
841 # This is some annoying dance, because entries without sidedata
838 # currently use 0 as their ofsset. (instead of previous-offset +
842 # currently use 0 as their ofsset. (instead of previous-offset +
839 # previous-size)
843 # previous-size)
840 #
844 #
841 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
845 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
842 # In the meantime, we need this.
846 # In the meantime, we need this.
843 while 0 <= rev:
847 while 0 <= rev:
844 e = self.index[rev]
848 e = self.index[rev]
845 if e[9] != 0:
849 if e[9] != 0:
846 return e[8] + e[9]
850 return e[8] + e[9]
847 rev -= 1
851 rev -= 1
848 return 0
852 return 0
849
853
850 def flags(self, rev):
854 def flags(self, rev):
851 return self.index[rev][0] & 0xFFFF
855 return self.index[rev][0] & 0xFFFF
852
856
853 def length(self, rev):
857 def length(self, rev):
854 return self.index[rev][1]
858 return self.index[rev][1]
855
859
856 def sidedata_length(self, rev):
860 def sidedata_length(self, rev):
857 if not self.hassidedata:
861 if not self.hassidedata:
858 return 0
862 return 0
859 return self.index[rev][9]
863 return self.index[rev][9]
860
864
861 def rawsize(self, rev):
865 def rawsize(self, rev):
862 """return the length of the uncompressed text for a given revision"""
866 """return the length of the uncompressed text for a given revision"""
863 l = self.index[rev][2]
867 l = self.index[rev][2]
864 if l >= 0:
868 if l >= 0:
865 return l
869 return l
866
870
867 t = self.rawdata(rev)
871 t = self.rawdata(rev)
868 return len(t)
872 return len(t)
869
873
870 def size(self, rev):
874 def size(self, rev):
871 """length of non-raw text (processed by a "read" flag processor)"""
875 """length of non-raw text (processed by a "read" flag processor)"""
872 # fast path: if no "read" flag processor could change the content,
876 # fast path: if no "read" flag processor could change the content,
873 # size is rawsize. note: ELLIPSIS is known to not change the content.
877 # size is rawsize. note: ELLIPSIS is known to not change the content.
874 flags = self.flags(rev)
878 flags = self.flags(rev)
875 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
879 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
876 return self.rawsize(rev)
880 return self.rawsize(rev)
877
881
878 return len(self.revision(rev))
882 return len(self.revision(rev))
879
883
880 def fast_rank(self, rev):
884 def fast_rank(self, rev):
881 """Return the rank of a revision if already known, or None otherwise.
885 """Return the rank of a revision if already known, or None otherwise.
882
886
883 The rank of a revision is the size of the sub-graph it defines as a
887 The rank of a revision is the size of the sub-graph it defines as a
884 head. Equivalently, the rank of a revision `r` is the size of the set
888 head. Equivalently, the rank of a revision `r` is the size of the set
885 `ancestors(r)`, `r` included.
889 `ancestors(r)`, `r` included.
886
890
887 This method returns the rank retrieved from the revlog in constant
891 This method returns the rank retrieved from the revlog in constant
888 time. It makes no attempt at computing unknown values for versions of
892 time. It makes no attempt at computing unknown values for versions of
889 the revlog which do not persist the rank.
893 the revlog which do not persist the rank.
890 """
894 """
891 rank = self.index[rev][ENTRY_RANK]
895 rank = self.index[rev][ENTRY_RANK]
892 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
896 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
893 return None
897 return None
894 if rev == nullrev:
898 if rev == nullrev:
895 return 0 # convention
899 return 0 # convention
896 return rank
900 return rank
897
901
898 def chainbase(self, rev):
902 def chainbase(self, rev):
899 base = self._chainbasecache.get(rev)
903 base = self._chainbasecache.get(rev)
900 if base is not None:
904 if base is not None:
901 return base
905 return base
902
906
903 index = self.index
907 index = self.index
904 iterrev = rev
908 iterrev = rev
905 base = index[iterrev][3]
909 base = index[iterrev][3]
906 while base != iterrev:
910 while base != iterrev:
907 iterrev = base
911 iterrev = base
908 base = index[iterrev][3]
912 base = index[iterrev][3]
909
913
910 self._chainbasecache[rev] = base
914 self._chainbasecache[rev] = base
911 return base
915 return base
912
916
913 def linkrev(self, rev):
917 def linkrev(self, rev):
914 return self.index[rev][4]
918 return self.index[rev][4]
915
919
916 def parentrevs(self, rev):
920 def parentrevs(self, rev):
917 try:
921 try:
918 entry = self.index[rev]
922 entry = self.index[rev]
919 except IndexError:
923 except IndexError:
920 if rev == wdirrev:
924 if rev == wdirrev:
921 raise error.WdirUnsupported
925 raise error.WdirUnsupported
922 raise
926 raise
923
927
924 if self.canonical_parent_order and entry[5] == nullrev:
928 if self.canonical_parent_order and entry[5] == nullrev:
925 return entry[6], entry[5]
929 return entry[6], entry[5]
926 else:
930 else:
927 return entry[5], entry[6]
931 return entry[5], entry[6]
928
932
929 # fast parentrevs(rev) where rev isn't filtered
933 # fast parentrevs(rev) where rev isn't filtered
930 _uncheckedparentrevs = parentrevs
934 _uncheckedparentrevs = parentrevs
931
935
932 def node(self, rev):
936 def node(self, rev):
933 try:
937 try:
934 return self.index[rev][7]
938 return self.index[rev][7]
935 except IndexError:
939 except IndexError:
936 if rev == wdirrev:
940 if rev == wdirrev:
937 raise error.WdirUnsupported
941 raise error.WdirUnsupported
938 raise
942 raise
939
943
940 # Derived from index values.
944 # Derived from index values.
941
945
942 def end(self, rev):
946 def end(self, rev):
943 return self.start(rev) + self.length(rev)
947 return self.start(rev) + self.length(rev)
944
948
945 def parents(self, node):
949 def parents(self, node):
946 i = self.index
950 i = self.index
947 d = i[self.rev(node)]
951 d = i[self.rev(node)]
948 # inline node() to avoid function call overhead
952 # inline node() to avoid function call overhead
949 if self.canonical_parent_order and d[5] == self.nullid:
953 if self.canonical_parent_order and d[5] == self.nullid:
950 return i[d[6]][7], i[d[5]][7]
954 return i[d[6]][7], i[d[5]][7]
951 else:
955 else:
952 return i[d[5]][7], i[d[6]][7]
956 return i[d[5]][7], i[d[6]][7]
953
957
954 def chainlen(self, rev):
958 def chainlen(self, rev):
955 return self._chaininfo(rev)[0]
959 return self._chaininfo(rev)[0]
956
960
957 def _chaininfo(self, rev):
961 def _chaininfo(self, rev):
958 chaininfocache = self._chaininfocache
962 chaininfocache = self._chaininfocache
959 if rev in chaininfocache:
963 if rev in chaininfocache:
960 return chaininfocache[rev]
964 return chaininfocache[rev]
961 index = self.index
965 index = self.index
962 generaldelta = self._generaldelta
966 generaldelta = self._generaldelta
963 iterrev = rev
967 iterrev = rev
964 e = index[iterrev]
968 e = index[iterrev]
965 clen = 0
969 clen = 0
966 compresseddeltalen = 0
970 compresseddeltalen = 0
967 while iterrev != e[3]:
971 while iterrev != e[3]:
968 clen += 1
972 clen += 1
969 compresseddeltalen += e[1]
973 compresseddeltalen += e[1]
970 if generaldelta:
974 if generaldelta:
971 iterrev = e[3]
975 iterrev = e[3]
972 else:
976 else:
973 iterrev -= 1
977 iterrev -= 1
974 if iterrev in chaininfocache:
978 if iterrev in chaininfocache:
975 t = chaininfocache[iterrev]
979 t = chaininfocache[iterrev]
976 clen += t[0]
980 clen += t[0]
977 compresseddeltalen += t[1]
981 compresseddeltalen += t[1]
978 break
982 break
979 e = index[iterrev]
983 e = index[iterrev]
980 else:
984 else:
981 # Add text length of base since decompressing that also takes
985 # Add text length of base since decompressing that also takes
982 # work. For cache hits the length is already included.
986 # work. For cache hits the length is already included.
983 compresseddeltalen += e[1]
987 compresseddeltalen += e[1]
984 r = (clen, compresseddeltalen)
988 r = (clen, compresseddeltalen)
985 chaininfocache[rev] = r
989 chaininfocache[rev] = r
986 return r
990 return r
987
991
988 def _deltachain(self, rev, stoprev=None):
992 def _deltachain(self, rev, stoprev=None):
989 """Obtain the delta chain for a revision.
993 """Obtain the delta chain for a revision.
990
994
991 ``stoprev`` specifies a revision to stop at. If not specified, we
995 ``stoprev`` specifies a revision to stop at. If not specified, we
992 stop at the base of the chain.
996 stop at the base of the chain.
993
997
994 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
998 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
995 revs in ascending order and ``stopped`` is a bool indicating whether
999 revs in ascending order and ``stopped`` is a bool indicating whether
996 ``stoprev`` was hit.
1000 ``stoprev`` was hit.
997 """
1001 """
998 # Try C implementation.
1002 # Try C implementation.
999 try:
1003 try:
1000 return self.index.deltachain(rev, stoprev, self._generaldelta)
1004 return self.index.deltachain(rev, stoprev, self._generaldelta)
1001 except AttributeError:
1005 except AttributeError:
1002 pass
1006 pass
1003
1007
1004 chain = []
1008 chain = []
1005
1009
1006 # Alias to prevent attribute lookup in tight loop.
1010 # Alias to prevent attribute lookup in tight loop.
1007 index = self.index
1011 index = self.index
1008 generaldelta = self._generaldelta
1012 generaldelta = self._generaldelta
1009
1013
1010 iterrev = rev
1014 iterrev = rev
1011 e = index[iterrev]
1015 e = index[iterrev]
1012 while iterrev != e[3] and iterrev != stoprev:
1016 while iterrev != e[3] and iterrev != stoprev:
1013 chain.append(iterrev)
1017 chain.append(iterrev)
1014 if generaldelta:
1018 if generaldelta:
1015 iterrev = e[3]
1019 iterrev = e[3]
1016 else:
1020 else:
1017 iterrev -= 1
1021 iterrev -= 1
1018 e = index[iterrev]
1022 e = index[iterrev]
1019
1023
1020 if iterrev == stoprev:
1024 if iterrev == stoprev:
1021 stopped = True
1025 stopped = True
1022 else:
1026 else:
1023 chain.append(iterrev)
1027 chain.append(iterrev)
1024 stopped = False
1028 stopped = False
1025
1029
1026 chain.reverse()
1030 chain.reverse()
1027 return chain, stopped
1031 return chain, stopped
1028
1032
1029 def ancestors(self, revs, stoprev=0, inclusive=False):
1033 def ancestors(self, revs, stoprev=0, inclusive=False):
1030 """Generate the ancestors of 'revs' in reverse revision order.
1034 """Generate the ancestors of 'revs' in reverse revision order.
1031 Does not generate revs lower than stoprev.
1035 Does not generate revs lower than stoprev.
1032
1036
1033 See the documentation for ancestor.lazyancestors for more details."""
1037 See the documentation for ancestor.lazyancestors for more details."""
1034
1038
1035 # first, make sure start revisions aren't filtered
1039 # first, make sure start revisions aren't filtered
1036 revs = list(revs)
1040 revs = list(revs)
1037 checkrev = self.node
1041 checkrev = self.node
1038 for r in revs:
1042 for r in revs:
1039 checkrev(r)
1043 checkrev(r)
1040 # and we're sure ancestors aren't filtered as well
1044 # and we're sure ancestors aren't filtered as well
1041
1045
1042 if rustancestor is not None and self.index.rust_ext_compat:
1046 if rustancestor is not None and self.index.rust_ext_compat:
1043 lazyancestors = rustancestor.LazyAncestors
1047 lazyancestors = rustancestor.LazyAncestors
1044 arg = self.index
1048 arg = self.index
1045 else:
1049 else:
1046 lazyancestors = ancestor.lazyancestors
1050 lazyancestors = ancestor.lazyancestors
1047 arg = self._uncheckedparentrevs
1051 arg = self._uncheckedparentrevs
1048 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1052 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1049
1053
1050 def descendants(self, revs):
1054 def descendants(self, revs):
1051 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1055 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1052
1056
1053 def findcommonmissing(self, common=None, heads=None):
1057 def findcommonmissing(self, common=None, heads=None):
1054 """Return a tuple of the ancestors of common and the ancestors of heads
1058 """Return a tuple of the ancestors of common and the ancestors of heads
1055 that are not ancestors of common. In revset terminology, we return the
1059 that are not ancestors of common. In revset terminology, we return the
1056 tuple:
1060 tuple:
1057
1061
1058 ::common, (::heads) - (::common)
1062 ::common, (::heads) - (::common)
1059
1063
1060 The list is sorted by revision number, meaning it is
1064 The list is sorted by revision number, meaning it is
1061 topologically sorted.
1065 topologically sorted.
1062
1066
1063 'heads' and 'common' are both lists of node IDs. If heads is
1067 'heads' and 'common' are both lists of node IDs. If heads is
1064 not supplied, uses all of the revlog's heads. If common is not
1068 not supplied, uses all of the revlog's heads. If common is not
1065 supplied, uses nullid."""
1069 supplied, uses nullid."""
1066 if common is None:
1070 if common is None:
1067 common = [self.nullid]
1071 common = [self.nullid]
1068 if heads is None:
1072 if heads is None:
1069 heads = self.heads()
1073 heads = self.heads()
1070
1074
1071 common = [self.rev(n) for n in common]
1075 common = [self.rev(n) for n in common]
1072 heads = [self.rev(n) for n in heads]
1076 heads = [self.rev(n) for n in heads]
1073
1077
1074 # we want the ancestors, but inclusive
1078 # we want the ancestors, but inclusive
1075 class lazyset:
1079 class lazyset:
1076 def __init__(self, lazyvalues):
1080 def __init__(self, lazyvalues):
1077 self.addedvalues = set()
1081 self.addedvalues = set()
1078 self.lazyvalues = lazyvalues
1082 self.lazyvalues = lazyvalues
1079
1083
1080 def __contains__(self, value):
1084 def __contains__(self, value):
1081 return value in self.addedvalues or value in self.lazyvalues
1085 return value in self.addedvalues or value in self.lazyvalues
1082
1086
1083 def __iter__(self):
1087 def __iter__(self):
1084 added = self.addedvalues
1088 added = self.addedvalues
1085 for r in added:
1089 for r in added:
1086 yield r
1090 yield r
1087 for r in self.lazyvalues:
1091 for r in self.lazyvalues:
1088 if not r in added:
1092 if not r in added:
1089 yield r
1093 yield r
1090
1094
1091 def add(self, value):
1095 def add(self, value):
1092 self.addedvalues.add(value)
1096 self.addedvalues.add(value)
1093
1097
1094 def update(self, values):
1098 def update(self, values):
1095 self.addedvalues.update(values)
1099 self.addedvalues.update(values)
1096
1100
1097 has = lazyset(self.ancestors(common))
1101 has = lazyset(self.ancestors(common))
1098 has.add(nullrev)
1102 has.add(nullrev)
1099 has.update(common)
1103 has.update(common)
1100
1104
1101 # take all ancestors from heads that aren't in has
1105 # take all ancestors from heads that aren't in has
1102 missing = set()
1106 missing = set()
1103 visit = collections.deque(r for r in heads if r not in has)
1107 visit = collections.deque(r for r in heads if r not in has)
1104 while visit:
1108 while visit:
1105 r = visit.popleft()
1109 r = visit.popleft()
1106 if r in missing:
1110 if r in missing:
1107 continue
1111 continue
1108 else:
1112 else:
1109 missing.add(r)
1113 missing.add(r)
1110 for p in self.parentrevs(r):
1114 for p in self.parentrevs(r):
1111 if p not in has:
1115 if p not in has:
1112 visit.append(p)
1116 visit.append(p)
1113 missing = list(missing)
1117 missing = list(missing)
1114 missing.sort()
1118 missing.sort()
1115 return has, [self.node(miss) for miss in missing]
1119 return has, [self.node(miss) for miss in missing]
1116
1120
1117 def incrementalmissingrevs(self, common=None):
1121 def incrementalmissingrevs(self, common=None):
1118 """Return an object that can be used to incrementally compute the
1122 """Return an object that can be used to incrementally compute the
1119 revision numbers of the ancestors of arbitrary sets that are not
1123 revision numbers of the ancestors of arbitrary sets that are not
1120 ancestors of common. This is an ancestor.incrementalmissingancestors
1124 ancestors of common. This is an ancestor.incrementalmissingancestors
1121 object.
1125 object.
1122
1126
1123 'common' is a list of revision numbers. If common is not supplied, uses
1127 'common' is a list of revision numbers. If common is not supplied, uses
1124 nullrev.
1128 nullrev.
1125 """
1129 """
1126 if common is None:
1130 if common is None:
1127 common = [nullrev]
1131 common = [nullrev]
1128
1132
1129 if rustancestor is not None and self.index.rust_ext_compat:
1133 if rustancestor is not None and self.index.rust_ext_compat:
1130 return rustancestor.MissingAncestors(self.index, common)
1134 return rustancestor.MissingAncestors(self.index, common)
1131 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1135 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1132
1136
1133 def findmissingrevs(self, common=None, heads=None):
1137 def findmissingrevs(self, common=None, heads=None):
1134 """Return the revision numbers of the ancestors of heads that
1138 """Return the revision numbers of the ancestors of heads that
1135 are not ancestors of common.
1139 are not ancestors of common.
1136
1140
1137 More specifically, return a list of revision numbers corresponding to
1141 More specifically, return a list of revision numbers corresponding to
1138 nodes N such that every N satisfies the following constraints:
1142 nodes N such that every N satisfies the following constraints:
1139
1143
1140 1. N is an ancestor of some node in 'heads'
1144 1. N is an ancestor of some node in 'heads'
1141 2. N is not an ancestor of any node in 'common'
1145 2. N is not an ancestor of any node in 'common'
1142
1146
1143 The list is sorted by revision number, meaning it is
1147 The list is sorted by revision number, meaning it is
1144 topologically sorted.
1148 topologically sorted.
1145
1149
1146 'heads' and 'common' are both lists of revision numbers. If heads is
1150 'heads' and 'common' are both lists of revision numbers. If heads is
1147 not supplied, uses all of the revlog's heads. If common is not
1151 not supplied, uses all of the revlog's heads. If common is not
1148 supplied, uses nullid."""
1152 supplied, uses nullid."""
1149 if common is None:
1153 if common is None:
1150 common = [nullrev]
1154 common = [nullrev]
1151 if heads is None:
1155 if heads is None:
1152 heads = self.headrevs()
1156 heads = self.headrevs()
1153
1157
1154 inc = self.incrementalmissingrevs(common=common)
1158 inc = self.incrementalmissingrevs(common=common)
1155 return inc.missingancestors(heads)
1159 return inc.missingancestors(heads)
1156
1160
1157 def findmissing(self, common=None, heads=None):
1161 def findmissing(self, common=None, heads=None):
1158 """Return the ancestors of heads that are not ancestors of common.
1162 """Return the ancestors of heads that are not ancestors of common.
1159
1163
1160 More specifically, return a list of nodes N such that every N
1164 More specifically, return a list of nodes N such that every N
1161 satisfies the following constraints:
1165 satisfies the following constraints:
1162
1166
1163 1. N is an ancestor of some node in 'heads'
1167 1. N is an ancestor of some node in 'heads'
1164 2. N is not an ancestor of any node in 'common'
1168 2. N is not an ancestor of any node in 'common'
1165
1169
1166 The list is sorted by revision number, meaning it is
1170 The list is sorted by revision number, meaning it is
1167 topologically sorted.
1171 topologically sorted.
1168
1172
1169 'heads' and 'common' are both lists of node IDs. If heads is
1173 'heads' and 'common' are both lists of node IDs. If heads is
1170 not supplied, uses all of the revlog's heads. If common is not
1174 not supplied, uses all of the revlog's heads. If common is not
1171 supplied, uses nullid."""
1175 supplied, uses nullid."""
1172 if common is None:
1176 if common is None:
1173 common = [self.nullid]
1177 common = [self.nullid]
1174 if heads is None:
1178 if heads is None:
1175 heads = self.heads()
1179 heads = self.heads()
1176
1180
1177 common = [self.rev(n) for n in common]
1181 common = [self.rev(n) for n in common]
1178 heads = [self.rev(n) for n in heads]
1182 heads = [self.rev(n) for n in heads]
1179
1183
1180 inc = self.incrementalmissingrevs(common=common)
1184 inc = self.incrementalmissingrevs(common=common)
1181 return [self.node(r) for r in inc.missingancestors(heads)]
1185 return [self.node(r) for r in inc.missingancestors(heads)]
1182
1186
1183 def nodesbetween(self, roots=None, heads=None):
1187 def nodesbetween(self, roots=None, heads=None):
1184 """Return a topological path from 'roots' to 'heads'.
1188 """Return a topological path from 'roots' to 'heads'.
1185
1189
1186 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1190 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1187 topologically sorted list of all nodes N that satisfy both of
1191 topologically sorted list of all nodes N that satisfy both of
1188 these constraints:
1192 these constraints:
1189
1193
1190 1. N is a descendant of some node in 'roots'
1194 1. N is a descendant of some node in 'roots'
1191 2. N is an ancestor of some node in 'heads'
1195 2. N is an ancestor of some node in 'heads'
1192
1196
1193 Every node is considered to be both a descendant and an ancestor
1197 Every node is considered to be both a descendant and an ancestor
1194 of itself, so every reachable node in 'roots' and 'heads' will be
1198 of itself, so every reachable node in 'roots' and 'heads' will be
1195 included in 'nodes'.
1199 included in 'nodes'.
1196
1200
1197 'outroots' is the list of reachable nodes in 'roots', i.e., the
1201 'outroots' is the list of reachable nodes in 'roots', i.e., the
1198 subset of 'roots' that is returned in 'nodes'. Likewise,
1202 subset of 'roots' that is returned in 'nodes'. Likewise,
1199 'outheads' is the subset of 'heads' that is also in 'nodes'.
1203 'outheads' is the subset of 'heads' that is also in 'nodes'.
1200
1204
1201 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1205 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1202 unspecified, uses nullid as the only root. If 'heads' is
1206 unspecified, uses nullid as the only root. If 'heads' is
1203 unspecified, uses list of all of the revlog's heads."""
1207 unspecified, uses list of all of the revlog's heads."""
1204 nonodes = ([], [], [])
1208 nonodes = ([], [], [])
1205 if roots is not None:
1209 if roots is not None:
1206 roots = list(roots)
1210 roots = list(roots)
1207 if not roots:
1211 if not roots:
1208 return nonodes
1212 return nonodes
1209 lowestrev = min([self.rev(n) for n in roots])
1213 lowestrev = min([self.rev(n) for n in roots])
1210 else:
1214 else:
1211 roots = [self.nullid] # Everybody's a descendant of nullid
1215 roots = [self.nullid] # Everybody's a descendant of nullid
1212 lowestrev = nullrev
1216 lowestrev = nullrev
1213 if (lowestrev == nullrev) and (heads is None):
1217 if (lowestrev == nullrev) and (heads is None):
1214 # We want _all_ the nodes!
1218 # We want _all_ the nodes!
1215 return (
1219 return (
1216 [self.node(r) for r in self],
1220 [self.node(r) for r in self],
1217 [self.nullid],
1221 [self.nullid],
1218 list(self.heads()),
1222 list(self.heads()),
1219 )
1223 )
1220 if heads is None:
1224 if heads is None:
1221 # All nodes are ancestors, so the latest ancestor is the last
1225 # All nodes are ancestors, so the latest ancestor is the last
1222 # node.
1226 # node.
1223 highestrev = len(self) - 1
1227 highestrev = len(self) - 1
1224 # Set ancestors to None to signal that every node is an ancestor.
1228 # Set ancestors to None to signal that every node is an ancestor.
1225 ancestors = None
1229 ancestors = None
1226 # Set heads to an empty dictionary for later discovery of heads
1230 # Set heads to an empty dictionary for later discovery of heads
1227 heads = {}
1231 heads = {}
1228 else:
1232 else:
1229 heads = list(heads)
1233 heads = list(heads)
1230 if not heads:
1234 if not heads:
1231 return nonodes
1235 return nonodes
1232 ancestors = set()
1236 ancestors = set()
1233 # Turn heads into a dictionary so we can remove 'fake' heads.
1237 # Turn heads into a dictionary so we can remove 'fake' heads.
1234 # Also, later we will be using it to filter out the heads we can't
1238 # Also, later we will be using it to filter out the heads we can't
1235 # find from roots.
1239 # find from roots.
1236 heads = dict.fromkeys(heads, False)
1240 heads = dict.fromkeys(heads, False)
1237 # Start at the top and keep marking parents until we're done.
1241 # Start at the top and keep marking parents until we're done.
1238 nodestotag = set(heads)
1242 nodestotag = set(heads)
1239 # Remember where the top was so we can use it as a limit later.
1243 # Remember where the top was so we can use it as a limit later.
1240 highestrev = max([self.rev(n) for n in nodestotag])
1244 highestrev = max([self.rev(n) for n in nodestotag])
1241 while nodestotag:
1245 while nodestotag:
1242 # grab a node to tag
1246 # grab a node to tag
1243 n = nodestotag.pop()
1247 n = nodestotag.pop()
1244 # Never tag nullid
1248 # Never tag nullid
1245 if n == self.nullid:
1249 if n == self.nullid:
1246 continue
1250 continue
1247 # A node's revision number represents its place in a
1251 # A node's revision number represents its place in a
1248 # topologically sorted list of nodes.
1252 # topologically sorted list of nodes.
1249 r = self.rev(n)
1253 r = self.rev(n)
1250 if r >= lowestrev:
1254 if r >= lowestrev:
1251 if n not in ancestors:
1255 if n not in ancestors:
1252 # If we are possibly a descendant of one of the roots
1256 # If we are possibly a descendant of one of the roots
1253 # and we haven't already been marked as an ancestor
1257 # and we haven't already been marked as an ancestor
1254 ancestors.add(n) # Mark as ancestor
1258 ancestors.add(n) # Mark as ancestor
1255 # Add non-nullid parents to list of nodes to tag.
1259 # Add non-nullid parents to list of nodes to tag.
1256 nodestotag.update(
1260 nodestotag.update(
1257 [p for p in self.parents(n) if p != self.nullid]
1261 [p for p in self.parents(n) if p != self.nullid]
1258 )
1262 )
1259 elif n in heads: # We've seen it before, is it a fake head?
1263 elif n in heads: # We've seen it before, is it a fake head?
1260 # So it is, real heads should not be the ancestors of
1264 # So it is, real heads should not be the ancestors of
1261 # any other heads.
1265 # any other heads.
1262 heads.pop(n)
1266 heads.pop(n)
1263 if not ancestors:
1267 if not ancestors:
1264 return nonodes
1268 return nonodes
1265 # Now that we have our set of ancestors, we want to remove any
1269 # Now that we have our set of ancestors, we want to remove any
1266 # roots that are not ancestors.
1270 # roots that are not ancestors.
1267
1271
1268 # If one of the roots was nullid, everything is included anyway.
1272 # If one of the roots was nullid, everything is included anyway.
1269 if lowestrev > nullrev:
1273 if lowestrev > nullrev:
1270 # But, since we weren't, let's recompute the lowest rev to not
1274 # But, since we weren't, let's recompute the lowest rev to not
1271 # include roots that aren't ancestors.
1275 # include roots that aren't ancestors.
1272
1276
1273 # Filter out roots that aren't ancestors of heads
1277 # Filter out roots that aren't ancestors of heads
1274 roots = [root for root in roots if root in ancestors]
1278 roots = [root for root in roots if root in ancestors]
1275 # Recompute the lowest revision
1279 # Recompute the lowest revision
1276 if roots:
1280 if roots:
1277 lowestrev = min([self.rev(root) for root in roots])
1281 lowestrev = min([self.rev(root) for root in roots])
1278 else:
1282 else:
1279 # No more roots? Return empty list
1283 # No more roots? Return empty list
1280 return nonodes
1284 return nonodes
1281 else:
1285 else:
1282 # We are descending from nullid, and don't need to care about
1286 # We are descending from nullid, and don't need to care about
1283 # any other roots.
1287 # any other roots.
1284 lowestrev = nullrev
1288 lowestrev = nullrev
1285 roots = [self.nullid]
1289 roots = [self.nullid]
1286 # Transform our roots list into a set.
1290 # Transform our roots list into a set.
1287 descendants = set(roots)
1291 descendants = set(roots)
1288 # Also, keep the original roots so we can filter out roots that aren't
1292 # Also, keep the original roots so we can filter out roots that aren't
1289 # 'real' roots (i.e. are descended from other roots).
1293 # 'real' roots (i.e. are descended from other roots).
1290 roots = descendants.copy()
1294 roots = descendants.copy()
1291 # Our topologically sorted list of output nodes.
1295 # Our topologically sorted list of output nodes.
1292 orderedout = []
1296 orderedout = []
1293 # Don't start at nullid since we don't want nullid in our output list,
1297 # Don't start at nullid since we don't want nullid in our output list,
1294 # and if nullid shows up in descendants, empty parents will look like
1298 # and if nullid shows up in descendants, empty parents will look like
1295 # they're descendants.
1299 # they're descendants.
1296 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1300 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1297 n = self.node(r)
1301 n = self.node(r)
1298 isdescendant = False
1302 isdescendant = False
1299 if lowestrev == nullrev: # Everybody is a descendant of nullid
1303 if lowestrev == nullrev: # Everybody is a descendant of nullid
1300 isdescendant = True
1304 isdescendant = True
1301 elif n in descendants:
1305 elif n in descendants:
1302 # n is already a descendant
1306 # n is already a descendant
1303 isdescendant = True
1307 isdescendant = True
1304 # This check only needs to be done here because all the roots
1308 # This check only needs to be done here because all the roots
1305 # will start being marked is descendants before the loop.
1309 # will start being marked is descendants before the loop.
1306 if n in roots:
1310 if n in roots:
1307 # If n was a root, check if it's a 'real' root.
1311 # If n was a root, check if it's a 'real' root.
1308 p = tuple(self.parents(n))
1312 p = tuple(self.parents(n))
1309 # If any of its parents are descendants, it's not a root.
1313 # If any of its parents are descendants, it's not a root.
1310 if (p[0] in descendants) or (p[1] in descendants):
1314 if (p[0] in descendants) or (p[1] in descendants):
1311 roots.remove(n)
1315 roots.remove(n)
1312 else:
1316 else:
1313 p = tuple(self.parents(n))
1317 p = tuple(self.parents(n))
1314 # A node is a descendant if either of its parents are
1318 # A node is a descendant if either of its parents are
1315 # descendants. (We seeded the dependents list with the roots
1319 # descendants. (We seeded the dependents list with the roots
1316 # up there, remember?)
1320 # up there, remember?)
1317 if (p[0] in descendants) or (p[1] in descendants):
1321 if (p[0] in descendants) or (p[1] in descendants):
1318 descendants.add(n)
1322 descendants.add(n)
1319 isdescendant = True
1323 isdescendant = True
1320 if isdescendant and ((ancestors is None) or (n in ancestors)):
1324 if isdescendant and ((ancestors is None) or (n in ancestors)):
1321 # Only include nodes that are both descendants and ancestors.
1325 # Only include nodes that are both descendants and ancestors.
1322 orderedout.append(n)
1326 orderedout.append(n)
1323 if (ancestors is not None) and (n in heads):
1327 if (ancestors is not None) and (n in heads):
1324 # We're trying to figure out which heads are reachable
1328 # We're trying to figure out which heads are reachable
1325 # from roots.
1329 # from roots.
1326 # Mark this head as having been reached
1330 # Mark this head as having been reached
1327 heads[n] = True
1331 heads[n] = True
1328 elif ancestors is None:
1332 elif ancestors is None:
1329 # Otherwise, we're trying to discover the heads.
1333 # Otherwise, we're trying to discover the heads.
1330 # Assume this is a head because if it isn't, the next step
1334 # Assume this is a head because if it isn't, the next step
1331 # will eventually remove it.
1335 # will eventually remove it.
1332 heads[n] = True
1336 heads[n] = True
1333 # But, obviously its parents aren't.
1337 # But, obviously its parents aren't.
1334 for p in self.parents(n):
1338 for p in self.parents(n):
1335 heads.pop(p, None)
1339 heads.pop(p, None)
1336 heads = [head for head, flag in heads.items() if flag]
1340 heads = [head for head, flag in heads.items() if flag]
1337 roots = list(roots)
1341 roots = list(roots)
1338 assert orderedout
1342 assert orderedout
1339 assert roots
1343 assert roots
1340 assert heads
1344 assert heads
1341 return (orderedout, roots, heads)
1345 return (orderedout, roots, heads)
1342
1346
1343 def headrevs(self, revs=None):
1347 def headrevs(self, revs=None):
1344 if revs is None:
1348 if revs is None:
1345 try:
1349 try:
1346 return self.index.headrevs()
1350 return self.index.headrevs()
1347 except AttributeError:
1351 except AttributeError:
1348 return self._headrevs()
1352 return self._headrevs()
1349 if rustdagop is not None and self.index.rust_ext_compat:
1353 if rustdagop is not None and self.index.rust_ext_compat:
1350 return rustdagop.headrevs(self.index, revs)
1354 return rustdagop.headrevs(self.index, revs)
1351 return dagop.headrevs(revs, self._uncheckedparentrevs)
1355 return dagop.headrevs(revs, self._uncheckedparentrevs)
1352
1356
1353 def computephases(self, roots):
1357 def computephases(self, roots):
1354 return self.index.computephasesmapsets(roots)
1358 return self.index.computephasesmapsets(roots)
1355
1359
1356 def _headrevs(self):
1360 def _headrevs(self):
1357 count = len(self)
1361 count = len(self)
1358 if not count:
1362 if not count:
1359 return [nullrev]
1363 return [nullrev]
1360 # we won't iter over filtered rev so nobody is a head at start
1364 # we won't iter over filtered rev so nobody is a head at start
1361 ishead = [0] * (count + 1)
1365 ishead = [0] * (count + 1)
1362 index = self.index
1366 index = self.index
1363 for r in self:
1367 for r in self:
1364 ishead[r] = 1 # I may be an head
1368 ishead[r] = 1 # I may be an head
1365 e = index[r]
1369 e = index[r]
1366 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1370 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1367 return [r for r, val in enumerate(ishead) if val]
1371 return [r for r, val in enumerate(ishead) if val]
1368
1372
1369 def heads(self, start=None, stop=None):
1373 def heads(self, start=None, stop=None):
1370 """return the list of all nodes that have no children
1374 """return the list of all nodes that have no children
1371
1375
1372 if start is specified, only heads that are descendants of
1376 if start is specified, only heads that are descendants of
1373 start will be returned
1377 start will be returned
1374 if stop is specified, it will consider all the revs from stop
1378 if stop is specified, it will consider all the revs from stop
1375 as if they had no children
1379 as if they had no children
1376 """
1380 """
1377 if start is None and stop is None:
1381 if start is None and stop is None:
1378 if not len(self):
1382 if not len(self):
1379 return [self.nullid]
1383 return [self.nullid]
1380 return [self.node(r) for r in self.headrevs()]
1384 return [self.node(r) for r in self.headrevs()]
1381
1385
1382 if start is None:
1386 if start is None:
1383 start = nullrev
1387 start = nullrev
1384 else:
1388 else:
1385 start = self.rev(start)
1389 start = self.rev(start)
1386
1390
1387 stoprevs = {self.rev(n) for n in stop or []}
1391 stoprevs = {self.rev(n) for n in stop or []}
1388
1392
1389 revs = dagop.headrevssubset(
1393 revs = dagop.headrevssubset(
1390 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1394 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1391 )
1395 )
1392
1396
1393 return [self.node(rev) for rev in revs]
1397 return [self.node(rev) for rev in revs]
1394
1398
1395 def children(self, node):
1399 def children(self, node):
1396 """find the children of a given node"""
1400 """find the children of a given node"""
1397 c = []
1401 c = []
1398 p = self.rev(node)
1402 p = self.rev(node)
1399 for r in self.revs(start=p + 1):
1403 for r in self.revs(start=p + 1):
1400 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1404 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1401 if prevs:
1405 if prevs:
1402 for pr in prevs:
1406 for pr in prevs:
1403 if pr == p:
1407 if pr == p:
1404 c.append(self.node(r))
1408 c.append(self.node(r))
1405 elif p == nullrev:
1409 elif p == nullrev:
1406 c.append(self.node(r))
1410 c.append(self.node(r))
1407 return c
1411 return c
1408
1412
1409 def commonancestorsheads(self, a, b):
1413 def commonancestorsheads(self, a, b):
1410 """calculate all the heads of the common ancestors of nodes a and b"""
1414 """calculate all the heads of the common ancestors of nodes a and b"""
1411 a, b = self.rev(a), self.rev(b)
1415 a, b = self.rev(a), self.rev(b)
1412 ancs = self._commonancestorsheads(a, b)
1416 ancs = self._commonancestorsheads(a, b)
1413 return pycompat.maplist(self.node, ancs)
1417 return pycompat.maplist(self.node, ancs)
1414
1418
1415 def _commonancestorsheads(self, *revs):
1419 def _commonancestorsheads(self, *revs):
1416 """calculate all the heads of the common ancestors of revs"""
1420 """calculate all the heads of the common ancestors of revs"""
1417 try:
1421 try:
1418 ancs = self.index.commonancestorsheads(*revs)
1422 ancs = self.index.commonancestorsheads(*revs)
1419 except (AttributeError, OverflowError): # C implementation failed
1423 except (AttributeError, OverflowError): # C implementation failed
1420 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1424 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1421 return ancs
1425 return ancs
1422
1426
1423 def isancestor(self, a, b):
1427 def isancestor(self, a, b):
1424 """return True if node a is an ancestor of node b
1428 """return True if node a is an ancestor of node b
1425
1429
1426 A revision is considered an ancestor of itself."""
1430 A revision is considered an ancestor of itself."""
1427 a, b = self.rev(a), self.rev(b)
1431 a, b = self.rev(a), self.rev(b)
1428 return self.isancestorrev(a, b)
1432 return self.isancestorrev(a, b)
1429
1433
1430 def isancestorrev(self, a, b):
1434 def isancestorrev(self, a, b):
1431 """return True if revision a is an ancestor of revision b
1435 """return True if revision a is an ancestor of revision b
1432
1436
1433 A revision is considered an ancestor of itself.
1437 A revision is considered an ancestor of itself.
1434
1438
1435 The implementation of this is trivial but the use of
1439 The implementation of this is trivial but the use of
1436 reachableroots is not."""
1440 reachableroots is not."""
1437 if a == nullrev:
1441 if a == nullrev:
1438 return True
1442 return True
1439 elif a == b:
1443 elif a == b:
1440 return True
1444 return True
1441 elif a > b:
1445 elif a > b:
1442 return False
1446 return False
1443 return bool(self.reachableroots(a, [b], [a], includepath=False))
1447 return bool(self.reachableroots(a, [b], [a], includepath=False))
1444
1448
1445 def reachableroots(self, minroot, heads, roots, includepath=False):
1449 def reachableroots(self, minroot, heads, roots, includepath=False):
1446 """return (heads(::(<roots> and <roots>::<heads>)))
1450 """return (heads(::(<roots> and <roots>::<heads>)))
1447
1451
1448 If includepath is True, return (<roots>::<heads>)."""
1452 If includepath is True, return (<roots>::<heads>)."""
1449 try:
1453 try:
1450 return self.index.reachableroots2(
1454 return self.index.reachableroots2(
1451 minroot, heads, roots, includepath
1455 minroot, heads, roots, includepath
1452 )
1456 )
1453 except AttributeError:
1457 except AttributeError:
1454 return dagop._reachablerootspure(
1458 return dagop._reachablerootspure(
1455 self.parentrevs, minroot, roots, heads, includepath
1459 self.parentrevs, minroot, roots, heads, includepath
1456 )
1460 )
1457
1461
1458 def ancestor(self, a, b):
1462 def ancestor(self, a, b):
1459 """calculate the "best" common ancestor of nodes a and b"""
1463 """calculate the "best" common ancestor of nodes a and b"""
1460
1464
1461 a, b = self.rev(a), self.rev(b)
1465 a, b = self.rev(a), self.rev(b)
1462 try:
1466 try:
1463 ancs = self.index.ancestors(a, b)
1467 ancs = self.index.ancestors(a, b)
1464 except (AttributeError, OverflowError):
1468 except (AttributeError, OverflowError):
1465 ancs = ancestor.ancestors(self.parentrevs, a, b)
1469 ancs = ancestor.ancestors(self.parentrevs, a, b)
1466 if ancs:
1470 if ancs:
1467 # choose a consistent winner when there's a tie
1471 # choose a consistent winner when there's a tie
1468 return min(map(self.node, ancs))
1472 return min(map(self.node, ancs))
1469 return self.nullid
1473 return self.nullid
1470
1474
1471 def _match(self, id):
1475 def _match(self, id):
1472 if isinstance(id, int):
1476 if isinstance(id, int):
1473 # rev
1477 # rev
1474 return self.node(id)
1478 return self.node(id)
1475 if len(id) == self.nodeconstants.nodelen:
1479 if len(id) == self.nodeconstants.nodelen:
1476 # possibly a binary node
1480 # possibly a binary node
1477 # odds of a binary node being all hex in ASCII are 1 in 10**25
1481 # odds of a binary node being all hex in ASCII are 1 in 10**25
1478 try:
1482 try:
1479 node = id
1483 node = id
1480 self.rev(node) # quick search the index
1484 self.rev(node) # quick search the index
1481 return node
1485 return node
1482 except error.LookupError:
1486 except error.LookupError:
1483 pass # may be partial hex id
1487 pass # may be partial hex id
1484 try:
1488 try:
1485 # str(rev)
1489 # str(rev)
1486 rev = int(id)
1490 rev = int(id)
1487 if b"%d" % rev != id:
1491 if b"%d" % rev != id:
1488 raise ValueError
1492 raise ValueError
1489 if rev < 0:
1493 if rev < 0:
1490 rev = len(self) + rev
1494 rev = len(self) + rev
1491 if rev < 0 or rev >= len(self):
1495 if rev < 0 or rev >= len(self):
1492 raise ValueError
1496 raise ValueError
1493 return self.node(rev)
1497 return self.node(rev)
1494 except (ValueError, OverflowError):
1498 except (ValueError, OverflowError):
1495 pass
1499 pass
1496 if len(id) == 2 * self.nodeconstants.nodelen:
1500 if len(id) == 2 * self.nodeconstants.nodelen:
1497 try:
1501 try:
1498 # a full hex nodeid?
1502 # a full hex nodeid?
1499 node = bin(id)
1503 node = bin(id)
1500 self.rev(node)
1504 self.rev(node)
1501 return node
1505 return node
1502 except (binascii.Error, error.LookupError):
1506 except (binascii.Error, error.LookupError):
1503 pass
1507 pass
1504
1508
1505 def _partialmatch(self, id):
1509 def _partialmatch(self, id):
1506 # we don't care wdirfilenodeids as they should be always full hash
1510 # we don't care wdirfilenodeids as they should be always full hash
1507 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1511 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1508 ambiguous = False
1512 ambiguous = False
1509 try:
1513 try:
1510 partial = self.index.partialmatch(id)
1514 partial = self.index.partialmatch(id)
1511 if partial and self.hasnode(partial):
1515 if partial and self.hasnode(partial):
1512 if maybewdir:
1516 if maybewdir:
1513 # single 'ff...' match in radix tree, ambiguous with wdir
1517 # single 'ff...' match in radix tree, ambiguous with wdir
1514 ambiguous = True
1518 ambiguous = True
1515 else:
1519 else:
1516 return partial
1520 return partial
1517 elif maybewdir:
1521 elif maybewdir:
1518 # no 'ff...' match in radix tree, wdir identified
1522 # no 'ff...' match in radix tree, wdir identified
1519 raise error.WdirUnsupported
1523 raise error.WdirUnsupported
1520 else:
1524 else:
1521 return None
1525 return None
1522 except error.RevlogError:
1526 except error.RevlogError:
1523 # parsers.c radix tree lookup gave multiple matches
1527 # parsers.c radix tree lookup gave multiple matches
1524 # fast path: for unfiltered changelog, radix tree is accurate
1528 # fast path: for unfiltered changelog, radix tree is accurate
1525 if not getattr(self, 'filteredrevs', None):
1529 if not getattr(self, 'filteredrevs', None):
1526 ambiguous = True
1530 ambiguous = True
1527 # fall through to slow path that filters hidden revisions
1531 # fall through to slow path that filters hidden revisions
1528 except (AttributeError, ValueError):
1532 except (AttributeError, ValueError):
1529 # we are pure python, or key is not hex
1533 # we are pure python, or key is not hex
1530 pass
1534 pass
1531 if ambiguous:
1535 if ambiguous:
1532 raise error.AmbiguousPrefixLookupError(
1536 raise error.AmbiguousPrefixLookupError(
1533 id, self.display_id, _(b'ambiguous identifier')
1537 id, self.display_id, _(b'ambiguous identifier')
1534 )
1538 )
1535
1539
1536 if id in self._pcache:
1540 if id in self._pcache:
1537 return self._pcache[id]
1541 return self._pcache[id]
1538
1542
1539 if len(id) <= 40:
1543 if len(id) <= 40:
1540 # hex(node)[:...]
1544 # hex(node)[:...]
1541 l = len(id) // 2 * 2 # grab an even number of digits
1545 l = len(id) // 2 * 2 # grab an even number of digits
1542 try:
1546 try:
1543 # we're dropping the last digit, so let's check that it's hex,
1547 # we're dropping the last digit, so let's check that it's hex,
1544 # to avoid the expensive computation below if it's not
1548 # to avoid the expensive computation below if it's not
1545 if len(id) % 2 > 0:
1549 if len(id) % 2 > 0:
1546 if not (id[-1] in hexdigits):
1550 if not (id[-1] in hexdigits):
1547 return None
1551 return None
1548 prefix = bin(id[:l])
1552 prefix = bin(id[:l])
1549 except binascii.Error:
1553 except binascii.Error:
1550 pass
1554 pass
1551 else:
1555 else:
1552 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1556 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1553 nl = [
1557 nl = [
1554 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1558 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1555 ]
1559 ]
1556 if self.nodeconstants.nullhex.startswith(id):
1560 if self.nodeconstants.nullhex.startswith(id):
1557 nl.append(self.nullid)
1561 nl.append(self.nullid)
1558 if len(nl) > 0:
1562 if len(nl) > 0:
1559 if len(nl) == 1 and not maybewdir:
1563 if len(nl) == 1 and not maybewdir:
1560 self._pcache[id] = nl[0]
1564 self._pcache[id] = nl[0]
1561 return nl[0]
1565 return nl[0]
1562 raise error.AmbiguousPrefixLookupError(
1566 raise error.AmbiguousPrefixLookupError(
1563 id, self.display_id, _(b'ambiguous identifier')
1567 id, self.display_id, _(b'ambiguous identifier')
1564 )
1568 )
1565 if maybewdir:
1569 if maybewdir:
1566 raise error.WdirUnsupported
1570 raise error.WdirUnsupported
1567 return None
1571 return None
1568
1572
1569 def lookup(self, id):
1573 def lookup(self, id):
1570 """locate a node based on:
1574 """locate a node based on:
1571 - revision number or str(revision number)
1575 - revision number or str(revision number)
1572 - nodeid or subset of hex nodeid
1576 - nodeid or subset of hex nodeid
1573 """
1577 """
1574 n = self._match(id)
1578 n = self._match(id)
1575 if n is not None:
1579 if n is not None:
1576 return n
1580 return n
1577 n = self._partialmatch(id)
1581 n = self._partialmatch(id)
1578 if n:
1582 if n:
1579 return n
1583 return n
1580
1584
1581 raise error.LookupError(id, self.display_id, _(b'no match found'))
1585 raise error.LookupError(id, self.display_id, _(b'no match found'))
1582
1586
1583 def shortest(self, node, minlength=1):
1587 def shortest(self, node, minlength=1):
1584 """Find the shortest unambiguous prefix that matches node."""
1588 """Find the shortest unambiguous prefix that matches node."""
1585
1589
1586 def isvalid(prefix):
1590 def isvalid(prefix):
1587 try:
1591 try:
1588 matchednode = self._partialmatch(prefix)
1592 matchednode = self._partialmatch(prefix)
1589 except error.AmbiguousPrefixLookupError:
1593 except error.AmbiguousPrefixLookupError:
1590 return False
1594 return False
1591 except error.WdirUnsupported:
1595 except error.WdirUnsupported:
1592 # single 'ff...' match
1596 # single 'ff...' match
1593 return True
1597 return True
1594 if matchednode is None:
1598 if matchednode is None:
1595 raise error.LookupError(node, self.display_id, _(b'no node'))
1599 raise error.LookupError(node, self.display_id, _(b'no node'))
1596 return True
1600 return True
1597
1601
1598 def maybewdir(prefix):
1602 def maybewdir(prefix):
1599 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1603 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1600
1604
1601 hexnode = hex(node)
1605 hexnode = hex(node)
1602
1606
1603 def disambiguate(hexnode, minlength):
1607 def disambiguate(hexnode, minlength):
1604 """Disambiguate against wdirid."""
1608 """Disambiguate against wdirid."""
1605 for length in range(minlength, len(hexnode) + 1):
1609 for length in range(minlength, len(hexnode) + 1):
1606 prefix = hexnode[:length]
1610 prefix = hexnode[:length]
1607 if not maybewdir(prefix):
1611 if not maybewdir(prefix):
1608 return prefix
1612 return prefix
1609
1613
1610 if not getattr(self, 'filteredrevs', None):
1614 if not getattr(self, 'filteredrevs', None):
1611 try:
1615 try:
1612 length = max(self.index.shortest(node), minlength)
1616 length = max(self.index.shortest(node), minlength)
1613 return disambiguate(hexnode, length)
1617 return disambiguate(hexnode, length)
1614 except error.RevlogError:
1618 except error.RevlogError:
1615 if node != self.nodeconstants.wdirid:
1619 if node != self.nodeconstants.wdirid:
1616 raise error.LookupError(
1620 raise error.LookupError(
1617 node, self.display_id, _(b'no node')
1621 node, self.display_id, _(b'no node')
1618 )
1622 )
1619 except AttributeError:
1623 except AttributeError:
1620 # Fall through to pure code
1624 # Fall through to pure code
1621 pass
1625 pass
1622
1626
1623 if node == self.nodeconstants.wdirid:
1627 if node == self.nodeconstants.wdirid:
1624 for length in range(minlength, len(hexnode) + 1):
1628 for length in range(minlength, len(hexnode) + 1):
1625 prefix = hexnode[:length]
1629 prefix = hexnode[:length]
1626 if isvalid(prefix):
1630 if isvalid(prefix):
1627 return prefix
1631 return prefix
1628
1632
1629 for length in range(minlength, len(hexnode) + 1):
1633 for length in range(minlength, len(hexnode) + 1):
1630 prefix = hexnode[:length]
1634 prefix = hexnode[:length]
1631 if isvalid(prefix):
1635 if isvalid(prefix):
1632 return disambiguate(hexnode, length)
1636 return disambiguate(hexnode, length)
1633
1637
1634 def cmp(self, node, text):
1638 def cmp(self, node, text):
1635 """compare text with a given file revision
1639 """compare text with a given file revision
1636
1640
1637 returns True if text is different than what is stored.
1641 returns True if text is different than what is stored.
1638 """
1642 """
1639 p1, p2 = self.parents(node)
1643 p1, p2 = self.parents(node)
1640 return storageutil.hashrevisionsha1(text, p1, p2) != node
1644 return storageutil.hashrevisionsha1(text, p1, p2) != node
1641
1645
1642 def _getsegmentforrevs(self, startrev, endrev, df=None):
1646 def _getsegmentforrevs(self, startrev, endrev, df=None):
1643 """Obtain a segment of raw data corresponding to a range of revisions.
1647 """Obtain a segment of raw data corresponding to a range of revisions.
1644
1648
1645 Accepts the start and end revisions and an optional already-open
1649 Accepts the start and end revisions and an optional already-open
1646 file handle to be used for reading. If the file handle is read, its
1650 file handle to be used for reading. If the file handle is read, its
1647 seek position will not be preserved.
1651 seek position will not be preserved.
1648
1652
1649 Requests for data may be satisfied by a cache.
1653 Requests for data may be satisfied by a cache.
1650
1654
1651 Returns a 2-tuple of (offset, data) for the requested range of
1655 Returns a 2-tuple of (offset, data) for the requested range of
1652 revisions. Offset is the integer offset from the beginning of the
1656 revisions. Offset is the integer offset from the beginning of the
1653 revlog and data is a str or buffer of the raw byte data.
1657 revlog and data is a str or buffer of the raw byte data.
1654
1658
1655 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1659 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1656 to determine where each revision's data begins and ends.
1660 to determine where each revision's data begins and ends.
1657 """
1661 """
1658 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1662 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1659 # (functions are expensive).
1663 # (functions are expensive).
1660 index = self.index
1664 index = self.index
1661 istart = index[startrev]
1665 istart = index[startrev]
1662 start = int(istart[0] >> 16)
1666 start = int(istart[0] >> 16)
1663 if startrev == endrev:
1667 if startrev == endrev:
1664 end = start + istart[1]
1668 end = start + istart[1]
1665 else:
1669 else:
1666 iend = index[endrev]
1670 iend = index[endrev]
1667 end = int(iend[0] >> 16) + iend[1]
1671 end = int(iend[0] >> 16) + iend[1]
1668
1672
1669 if self._inline:
1673 if self._inline:
1670 start += (startrev + 1) * self.index.entry_size
1674 start += (startrev + 1) * self.index.entry_size
1671 end += (endrev + 1) * self.index.entry_size
1675 end += (endrev + 1) * self.index.entry_size
1672 length = end - start
1676 length = end - start
1673
1677
1674 return start, self._segmentfile.read_chunk(start, length, df)
1678 return start, self._segmentfile.read_chunk(start, length, df)
1675
1679
1676 def _chunk(self, rev, df=None):
1680 def _chunk(self, rev, df=None):
1677 """Obtain a single decompressed chunk for a revision.
1681 """Obtain a single decompressed chunk for a revision.
1678
1682
1679 Accepts an integer revision and an optional already-open file handle
1683 Accepts an integer revision and an optional already-open file handle
1680 to be used for reading. If used, the seek position of the file will not
1684 to be used for reading. If used, the seek position of the file will not
1681 be preserved.
1685 be preserved.
1682
1686
1683 Returns a str holding uncompressed data for the requested revision.
1687 Returns a str holding uncompressed data for the requested revision.
1684 """
1688 """
1685 compression_mode = self.index[rev][10]
1689 compression_mode = self.index[rev][10]
1686 data = self._getsegmentforrevs(rev, rev, df=df)[1]
1690 data = self._getsegmentforrevs(rev, rev, df=df)[1]
1687 if compression_mode == COMP_MODE_PLAIN:
1691 if compression_mode == COMP_MODE_PLAIN:
1688 return data
1692 return data
1689 elif compression_mode == COMP_MODE_DEFAULT:
1693 elif compression_mode == COMP_MODE_DEFAULT:
1690 return self._decompressor(data)
1694 return self._decompressor(data)
1691 elif compression_mode == COMP_MODE_INLINE:
1695 elif compression_mode == COMP_MODE_INLINE:
1692 return self.decompress(data)
1696 return self.decompress(data)
1693 else:
1697 else:
1694 msg = b'unknown compression mode %d'
1698 msg = b'unknown compression mode %d'
1695 msg %= compression_mode
1699 msg %= compression_mode
1696 raise error.RevlogError(msg)
1700 raise error.RevlogError(msg)
1697
1701
1698 def _chunks(self, revs, df=None, targetsize=None):
1702 def _chunks(self, revs, df=None, targetsize=None):
1699 """Obtain decompressed chunks for the specified revisions.
1703 """Obtain decompressed chunks for the specified revisions.
1700
1704
1701 Accepts an iterable of numeric revisions that are assumed to be in
1705 Accepts an iterable of numeric revisions that are assumed to be in
1702 ascending order. Also accepts an optional already-open file handle
1706 ascending order. Also accepts an optional already-open file handle
1703 to be used for reading. If used, the seek position of the file will
1707 to be used for reading. If used, the seek position of the file will
1704 not be preserved.
1708 not be preserved.
1705
1709
1706 This function is similar to calling ``self._chunk()`` multiple times,
1710 This function is similar to calling ``self._chunk()`` multiple times,
1707 but is faster.
1711 but is faster.
1708
1712
1709 Returns a list with decompressed data for each requested revision.
1713 Returns a list with decompressed data for each requested revision.
1710 """
1714 """
1711 if not revs:
1715 if not revs:
1712 return []
1716 return []
1713 start = self.start
1717 start = self.start
1714 length = self.length
1718 length = self.length
1715 inline = self._inline
1719 inline = self._inline
1716 iosize = self.index.entry_size
1720 iosize = self.index.entry_size
1717 buffer = util.buffer
1721 buffer = util.buffer
1718
1722
1719 l = []
1723 l = []
1720 ladd = l.append
1724 ladd = l.append
1721
1725
1722 if not self._withsparseread:
1726 if not self._withsparseread:
1723 slicedchunks = (revs,)
1727 slicedchunks = (revs,)
1724 else:
1728 else:
1725 slicedchunks = deltautil.slicechunk(
1729 slicedchunks = deltautil.slicechunk(
1726 self, revs, targetsize=targetsize
1730 self, revs, targetsize=targetsize
1727 )
1731 )
1728
1732
1729 for revschunk in slicedchunks:
1733 for revschunk in slicedchunks:
1730 firstrev = revschunk[0]
1734 firstrev = revschunk[0]
1731 # Skip trailing revisions with empty diff
1735 # Skip trailing revisions with empty diff
1732 for lastrev in revschunk[::-1]:
1736 for lastrev in revschunk[::-1]:
1733 if length(lastrev) != 0:
1737 if length(lastrev) != 0:
1734 break
1738 break
1735
1739
1736 try:
1740 try:
1737 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1741 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1738 except OverflowError:
1742 except OverflowError:
1739 # issue4215 - we can't cache a run of chunks greater than
1743 # issue4215 - we can't cache a run of chunks greater than
1740 # 2G on Windows
1744 # 2G on Windows
1741 return [self._chunk(rev, df=df) for rev in revschunk]
1745 return [self._chunk(rev, df=df) for rev in revschunk]
1742
1746
1743 decomp = self.decompress
1747 decomp = self.decompress
1744 # self._decompressor might be None, but will not be used in that case
1748 # self._decompressor might be None, but will not be used in that case
1745 def_decomp = self._decompressor
1749 def_decomp = self._decompressor
1746 for rev in revschunk:
1750 for rev in revschunk:
1747 chunkstart = start(rev)
1751 chunkstart = start(rev)
1748 if inline:
1752 if inline:
1749 chunkstart += (rev + 1) * iosize
1753 chunkstart += (rev + 1) * iosize
1750 chunklength = length(rev)
1754 chunklength = length(rev)
1751 comp_mode = self.index[rev][10]
1755 comp_mode = self.index[rev][10]
1752 c = buffer(data, chunkstart - offset, chunklength)
1756 c = buffer(data, chunkstart - offset, chunklength)
1753 if comp_mode == COMP_MODE_PLAIN:
1757 if comp_mode == COMP_MODE_PLAIN:
1754 ladd(c)
1758 ladd(c)
1755 elif comp_mode == COMP_MODE_INLINE:
1759 elif comp_mode == COMP_MODE_INLINE:
1756 ladd(decomp(c))
1760 ladd(decomp(c))
1757 elif comp_mode == COMP_MODE_DEFAULT:
1761 elif comp_mode == COMP_MODE_DEFAULT:
1758 ladd(def_decomp(c))
1762 ladd(def_decomp(c))
1759 else:
1763 else:
1760 msg = b'unknown compression mode %d'
1764 msg = b'unknown compression mode %d'
1761 msg %= comp_mode
1765 msg %= comp_mode
1762 raise error.RevlogError(msg)
1766 raise error.RevlogError(msg)
1763
1767
1764 return l
1768 return l
1765
1769
1766 def deltaparent(self, rev):
1770 def deltaparent(self, rev):
1767 """return deltaparent of the given revision"""
1771 """return deltaparent of the given revision"""
1768 base = self.index[rev][3]
1772 base = self.index[rev][3]
1769 if base == rev:
1773 if base == rev:
1770 return nullrev
1774 return nullrev
1771 elif self._generaldelta:
1775 elif self._generaldelta:
1772 return base
1776 return base
1773 else:
1777 else:
1774 return rev - 1
1778 return rev - 1
1775
1779
1776 def issnapshot(self, rev):
1780 def issnapshot(self, rev):
1777 """tells whether rev is a snapshot"""
1781 """tells whether rev is a snapshot"""
1778 if not self._sparserevlog:
1782 if not self._sparserevlog:
1779 return self.deltaparent(rev) == nullrev
1783 return self.deltaparent(rev) == nullrev
1780 elif util.safehasattr(self.index, b'issnapshot'):
1784 elif util.safehasattr(self.index, b'issnapshot'):
1781 # directly assign the method to cache the testing and access
1785 # directly assign the method to cache the testing and access
1782 self.issnapshot = self.index.issnapshot
1786 self.issnapshot = self.index.issnapshot
1783 return self.issnapshot(rev)
1787 return self.issnapshot(rev)
1784 if rev == nullrev:
1788 if rev == nullrev:
1785 return True
1789 return True
1786 entry = self.index[rev]
1790 entry = self.index[rev]
1787 base = entry[3]
1791 base = entry[3]
1788 if base == rev:
1792 if base == rev:
1789 return True
1793 return True
1790 if base == nullrev:
1794 if base == nullrev:
1791 return True
1795 return True
1792 p1 = entry[5]
1796 p1 = entry[5]
1793 while self.length(p1) == 0:
1797 while self.length(p1) == 0:
1794 b = self.deltaparent(p1)
1798 b = self.deltaparent(p1)
1795 if b == p1:
1799 if b == p1:
1796 break
1800 break
1797 p1 = b
1801 p1 = b
1798 p2 = entry[6]
1802 p2 = entry[6]
1799 while self.length(p2) == 0:
1803 while self.length(p2) == 0:
1800 b = self.deltaparent(p2)
1804 b = self.deltaparent(p2)
1801 if b == p2:
1805 if b == p2:
1802 break
1806 break
1803 p2 = b
1807 p2 = b
1804 if base == p1 or base == p2:
1808 if base == p1 or base == p2:
1805 return False
1809 return False
1806 return self.issnapshot(base)
1810 return self.issnapshot(base)
1807
1811
1808 def snapshotdepth(self, rev):
1812 def snapshotdepth(self, rev):
1809 """number of snapshot in the chain before this one"""
1813 """number of snapshot in the chain before this one"""
1810 if not self.issnapshot(rev):
1814 if not self.issnapshot(rev):
1811 raise error.ProgrammingError(b'revision %d not a snapshot')
1815 raise error.ProgrammingError(b'revision %d not a snapshot')
1812 return len(self._deltachain(rev)[0]) - 1
1816 return len(self._deltachain(rev)[0]) - 1
1813
1817
1814 def revdiff(self, rev1, rev2):
1818 def revdiff(self, rev1, rev2):
1815 """return or calculate a delta between two revisions
1819 """return or calculate a delta between two revisions
1816
1820
1817 The delta calculated is in binary form and is intended to be written to
1821 The delta calculated is in binary form and is intended to be written to
1818 revlog data directly. So this function needs raw revision data.
1822 revlog data directly. So this function needs raw revision data.
1819 """
1823 """
1820 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1824 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1821 return bytes(self._chunk(rev2))
1825 return bytes(self._chunk(rev2))
1822
1826
1823 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1827 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1824
1828
1825 def revision(self, nodeorrev, _df=None):
1829 def revision(self, nodeorrev, _df=None):
1826 """return an uncompressed revision of a given node or revision
1830 """return an uncompressed revision of a given node or revision
1827 number.
1831 number.
1828
1832
1829 _df - an existing file handle to read from. (internal-only)
1833 _df - an existing file handle to read from. (internal-only)
1830 """
1834 """
1831 return self._revisiondata(nodeorrev, _df)
1835 return self._revisiondata(nodeorrev, _df)
1832
1836
1833 def sidedata(self, nodeorrev, _df=None):
1837 def sidedata(self, nodeorrev, _df=None):
1834 """a map of extra data related to the changeset but not part of the hash
1838 """a map of extra data related to the changeset but not part of the hash
1835
1839
1836 This function currently return a dictionary. However, more advanced
1840 This function currently return a dictionary. However, more advanced
1837 mapping object will likely be used in the future for a more
1841 mapping object will likely be used in the future for a more
1838 efficient/lazy code.
1842 efficient/lazy code.
1839 """
1843 """
1840 # deal with <nodeorrev> argument type
1844 # deal with <nodeorrev> argument type
1841 if isinstance(nodeorrev, int):
1845 if isinstance(nodeorrev, int):
1842 rev = nodeorrev
1846 rev = nodeorrev
1843 else:
1847 else:
1844 rev = self.rev(nodeorrev)
1848 rev = self.rev(nodeorrev)
1845 return self._sidedata(rev)
1849 return self._sidedata(rev)
1846
1850
1847 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1851 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1848 # deal with <nodeorrev> argument type
1852 # deal with <nodeorrev> argument type
1849 if isinstance(nodeorrev, int):
1853 if isinstance(nodeorrev, int):
1850 rev = nodeorrev
1854 rev = nodeorrev
1851 node = self.node(rev)
1855 node = self.node(rev)
1852 else:
1856 else:
1853 node = nodeorrev
1857 node = nodeorrev
1854 rev = None
1858 rev = None
1855
1859
1856 # fast path the special `nullid` rev
1860 # fast path the special `nullid` rev
1857 if node == self.nullid:
1861 if node == self.nullid:
1858 return b""
1862 return b""
1859
1863
1860 # ``rawtext`` is the text as stored inside the revlog. Might be the
1864 # ``rawtext`` is the text as stored inside the revlog. Might be the
1861 # revision or might need to be processed to retrieve the revision.
1865 # revision or might need to be processed to retrieve the revision.
1862 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1866 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1863
1867
1864 if raw and validated:
1868 if raw and validated:
1865 # if we don't want to process the raw text and that raw
1869 # if we don't want to process the raw text and that raw
1866 # text is cached, we can exit early.
1870 # text is cached, we can exit early.
1867 return rawtext
1871 return rawtext
1868 if rev is None:
1872 if rev is None:
1869 rev = self.rev(node)
1873 rev = self.rev(node)
1870 # the revlog's flag for this revision
1874 # the revlog's flag for this revision
1871 # (usually alter its state or content)
1875 # (usually alter its state or content)
1872 flags = self.flags(rev)
1876 flags = self.flags(rev)
1873
1877
1874 if validated and flags == REVIDX_DEFAULT_FLAGS:
1878 if validated and flags == REVIDX_DEFAULT_FLAGS:
1875 # no extra flags set, no flag processor runs, text = rawtext
1879 # no extra flags set, no flag processor runs, text = rawtext
1876 return rawtext
1880 return rawtext
1877
1881
1878 if raw:
1882 if raw:
1879 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1883 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1880 text = rawtext
1884 text = rawtext
1881 else:
1885 else:
1882 r = flagutil.processflagsread(self, rawtext, flags)
1886 r = flagutil.processflagsread(self, rawtext, flags)
1883 text, validatehash = r
1887 text, validatehash = r
1884 if validatehash:
1888 if validatehash:
1885 self.checkhash(text, node, rev=rev)
1889 self.checkhash(text, node, rev=rev)
1886 if not validated:
1890 if not validated:
1887 self._revisioncache = (node, rev, rawtext)
1891 self._revisioncache = (node, rev, rawtext)
1888
1892
1889 return text
1893 return text
1890
1894
1891 def _rawtext(self, node, rev, _df=None):
1895 def _rawtext(self, node, rev, _df=None):
1892 """return the possibly unvalidated rawtext for a revision
1896 """return the possibly unvalidated rawtext for a revision
1893
1897
1894 returns (rev, rawtext, validated)
1898 returns (rev, rawtext, validated)
1895 """
1899 """
1896
1900
1897 # revision in the cache (could be useful to apply delta)
1901 # revision in the cache (could be useful to apply delta)
1898 cachedrev = None
1902 cachedrev = None
1899 # An intermediate text to apply deltas to
1903 # An intermediate text to apply deltas to
1900 basetext = None
1904 basetext = None
1901
1905
1902 # Check if we have the entry in cache
1906 # Check if we have the entry in cache
1903 # The cache entry looks like (node, rev, rawtext)
1907 # The cache entry looks like (node, rev, rawtext)
1904 if self._revisioncache:
1908 if self._revisioncache:
1905 if self._revisioncache[0] == node:
1909 if self._revisioncache[0] == node:
1906 return (rev, self._revisioncache[2], True)
1910 return (rev, self._revisioncache[2], True)
1907 cachedrev = self._revisioncache[1]
1911 cachedrev = self._revisioncache[1]
1908
1912
1909 if rev is None:
1913 if rev is None:
1910 rev = self.rev(node)
1914 rev = self.rev(node)
1911
1915
1912 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1916 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1913 if stopped:
1917 if stopped:
1914 basetext = self._revisioncache[2]
1918 basetext = self._revisioncache[2]
1915
1919
1916 # drop cache to save memory, the caller is expected to
1920 # drop cache to save memory, the caller is expected to
1917 # update self._revisioncache after validating the text
1921 # update self._revisioncache after validating the text
1918 self._revisioncache = None
1922 self._revisioncache = None
1919
1923
1920 targetsize = None
1924 targetsize = None
1921 rawsize = self.index[rev][2]
1925 rawsize = self.index[rev][2]
1922 if 0 <= rawsize:
1926 if 0 <= rawsize:
1923 targetsize = 4 * rawsize
1927 targetsize = 4 * rawsize
1924
1928
1925 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1929 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1926 if basetext is None:
1930 if basetext is None:
1927 basetext = bytes(bins[0])
1931 basetext = bytes(bins[0])
1928 bins = bins[1:]
1932 bins = bins[1:]
1929
1933
1930 rawtext = mdiff.patches(basetext, bins)
1934 rawtext = mdiff.patches(basetext, bins)
1931 del basetext # let us have a chance to free memory early
1935 del basetext # let us have a chance to free memory early
1932 return (rev, rawtext, False)
1936 return (rev, rawtext, False)
1933
1937
1934 def _sidedata(self, rev):
1938 def _sidedata(self, rev):
1935 """Return the sidedata for a given revision number."""
1939 """Return the sidedata for a given revision number."""
1936 index_entry = self.index[rev]
1940 index_entry = self.index[rev]
1937 sidedata_offset = index_entry[8]
1941 sidedata_offset = index_entry[8]
1938 sidedata_size = index_entry[9]
1942 sidedata_size = index_entry[9]
1939
1943
1940 if self._inline:
1944 if self._inline:
1941 sidedata_offset += self.index.entry_size * (1 + rev)
1945 sidedata_offset += self.index.entry_size * (1 + rev)
1942 if sidedata_size == 0:
1946 if sidedata_size == 0:
1943 return {}
1947 return {}
1944
1948
1945 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
1949 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
1946 filename = self._sidedatafile
1950 filename = self._sidedatafile
1947 end = self._docket.sidedata_end
1951 end = self._docket.sidedata_end
1948 offset = sidedata_offset
1952 offset = sidedata_offset
1949 length = sidedata_size
1953 length = sidedata_size
1950 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
1954 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
1951 raise error.RevlogError(m)
1955 raise error.RevlogError(m)
1952
1956
1953 comp_segment = self._segmentfile_sidedata.read_chunk(
1957 comp_segment = self._segmentfile_sidedata.read_chunk(
1954 sidedata_offset, sidedata_size
1958 sidedata_offset, sidedata_size
1955 )
1959 )
1956
1960
1957 comp = self.index[rev][11]
1961 comp = self.index[rev][11]
1958 if comp == COMP_MODE_PLAIN:
1962 if comp == COMP_MODE_PLAIN:
1959 segment = comp_segment
1963 segment = comp_segment
1960 elif comp == COMP_MODE_DEFAULT:
1964 elif comp == COMP_MODE_DEFAULT:
1961 segment = self._decompressor(comp_segment)
1965 segment = self._decompressor(comp_segment)
1962 elif comp == COMP_MODE_INLINE:
1966 elif comp == COMP_MODE_INLINE:
1963 segment = self.decompress(comp_segment)
1967 segment = self.decompress(comp_segment)
1964 else:
1968 else:
1965 msg = b'unknown compression mode %d'
1969 msg = b'unknown compression mode %d'
1966 msg %= comp
1970 msg %= comp
1967 raise error.RevlogError(msg)
1971 raise error.RevlogError(msg)
1968
1972
1969 sidedata = sidedatautil.deserialize_sidedata(segment)
1973 sidedata = sidedatautil.deserialize_sidedata(segment)
1970 return sidedata
1974 return sidedata
1971
1975
1972 def rawdata(self, nodeorrev, _df=None):
1976 def rawdata(self, nodeorrev, _df=None):
1973 """return an uncompressed raw data of a given node or revision number.
1977 """return an uncompressed raw data of a given node or revision number.
1974
1978
1975 _df - an existing file handle to read from. (internal-only)
1979 _df - an existing file handle to read from. (internal-only)
1976 """
1980 """
1977 return self._revisiondata(nodeorrev, _df, raw=True)
1981 return self._revisiondata(nodeorrev, _df, raw=True)
1978
1982
1979 def hash(self, text, p1, p2):
1983 def hash(self, text, p1, p2):
1980 """Compute a node hash.
1984 """Compute a node hash.
1981
1985
1982 Available as a function so that subclasses can replace the hash
1986 Available as a function so that subclasses can replace the hash
1983 as needed.
1987 as needed.
1984 """
1988 """
1985 return storageutil.hashrevisionsha1(text, p1, p2)
1989 return storageutil.hashrevisionsha1(text, p1, p2)
1986
1990
1987 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1991 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1988 """Check node hash integrity.
1992 """Check node hash integrity.
1989
1993
1990 Available as a function so that subclasses can extend hash mismatch
1994 Available as a function so that subclasses can extend hash mismatch
1991 behaviors as needed.
1995 behaviors as needed.
1992 """
1996 """
1993 try:
1997 try:
1994 if p1 is None and p2 is None:
1998 if p1 is None and p2 is None:
1995 p1, p2 = self.parents(node)
1999 p1, p2 = self.parents(node)
1996 if node != self.hash(text, p1, p2):
2000 if node != self.hash(text, p1, p2):
1997 # Clear the revision cache on hash failure. The revision cache
2001 # Clear the revision cache on hash failure. The revision cache
1998 # only stores the raw revision and clearing the cache does have
2002 # only stores the raw revision and clearing the cache does have
1999 # the side-effect that we won't have a cache hit when the raw
2003 # the side-effect that we won't have a cache hit when the raw
2000 # revision data is accessed. But this case should be rare and
2004 # revision data is accessed. But this case should be rare and
2001 # it is extra work to teach the cache about the hash
2005 # it is extra work to teach the cache about the hash
2002 # verification state.
2006 # verification state.
2003 if self._revisioncache and self._revisioncache[0] == node:
2007 if self._revisioncache and self._revisioncache[0] == node:
2004 self._revisioncache = None
2008 self._revisioncache = None
2005
2009
2006 revornode = rev
2010 revornode = rev
2007 if revornode is None:
2011 if revornode is None:
2008 revornode = templatefilters.short(hex(node))
2012 revornode = templatefilters.short(hex(node))
2009 raise error.RevlogError(
2013 raise error.RevlogError(
2010 _(b"integrity check failed on %s:%s")
2014 _(b"integrity check failed on %s:%s")
2011 % (self.display_id, pycompat.bytestr(revornode))
2015 % (self.display_id, pycompat.bytestr(revornode))
2012 )
2016 )
2013 except error.RevlogError:
2017 except error.RevlogError:
2014 if self._censorable and storageutil.iscensoredtext(text):
2018 if self._censorable and storageutil.iscensoredtext(text):
2015 raise error.CensoredNodeError(self.display_id, node, text)
2019 raise error.CensoredNodeError(self.display_id, node, text)
2016 raise
2020 raise
2017
2021
2018 def _enforceinlinesize(self, tr):
2022 def _enforceinlinesize(self, tr, side_write=True):
2019 """Check if the revlog is too big for inline and convert if so.
2023 """Check if the revlog is too big for inline and convert if so.
2020
2024
2021 This should be called after revisions are added to the revlog. If the
2025 This should be called after revisions are added to the revlog. If the
2022 revlog has grown too large to be an inline revlog, it will convert it
2026 revlog has grown too large to be an inline revlog, it will convert it
2023 to use multiple index and data files.
2027 to use multiple index and data files.
2024 """
2028 """
2025 tiprev = len(self) - 1
2029 tiprev = len(self) - 1
2026 total_size = self.start(tiprev) + self.length(tiprev)
2030 total_size = self.start(tiprev) + self.length(tiprev)
2027 if not self._inline or total_size < _maxinline:
2031 if not self._inline or total_size < _maxinline:
2028 return
2032 return
2029
2033
2030 troffset = tr.findoffset(self._indexfile)
2034 troffset = tr.findoffset(self._indexfile)
2031 if troffset is None:
2035 if troffset is None:
2032 raise error.RevlogError(
2036 raise error.RevlogError(
2033 _(b"%s not found in the transaction") % self._indexfile
2037 _(b"%s not found in the transaction") % self._indexfile
2034 )
2038 )
2035 trindex = None
2039 if troffset:
2040 tr.addbackup(self._indexfile, for_offset=True)
2036 tr.add(self._datafile, 0)
2041 tr.add(self._datafile, 0)
2037
2042
2038 existing_handles = False
2043 existing_handles = False
2039 if self._writinghandles is not None:
2044 if self._writinghandles is not None:
2040 existing_handles = True
2045 existing_handles = True
2041 fp = self._writinghandles[0]
2046 fp = self._writinghandles[0]
2042 fp.flush()
2047 fp.flush()
2043 fp.close()
2048 fp.close()
2044 # We can't use the cached file handle after close(). So prevent
2049 # We can't use the cached file handle after close(). So prevent
2045 # its usage.
2050 # its usage.
2046 self._writinghandles = None
2051 self._writinghandles = None
2047 self._segmentfile.writing_handle = None
2052 self._segmentfile.writing_handle = None
2048 # No need to deal with sidedata writing handle as it is only
2053 # No need to deal with sidedata writing handle as it is only
2049 # relevant with revlog-v2 which is never inline, not reaching
2054 # relevant with revlog-v2 which is never inline, not reaching
2050 # this code
2055 # this code
2056 if side_write:
2057 old_index_file_path = self._indexfile
2058 new_index_file_path = self._indexfile + b'.s'
2059 opener = self.opener
2060
2061 fncache = getattr(opener, 'fncache', None)
2062 if fncache is not None:
2063 fncache.addignore(new_index_file_path)
2064
2065 # the "split" index replace the real index when the transaction is finalized
2066 def finalize_callback(tr):
2067 opener.rename(
2068 new_index_file_path,
2069 old_index_file_path,
2070 checkambig=True,
2071 )
2072
2073 tr.registertmp(new_index_file_path)
2074 if self.target[1] is not None:
2075 finalize_id = b'000-revlog-split-%d-%s' % self.target
2076 else:
2077 finalize_id = b'000-revlog-split-%d' % self.target[0]
2078 tr.addfinalize(finalize_id, finalize_callback)
2051
2079
2052 new_dfh = self._datafp(b'w+')
2080 new_dfh = self._datafp(b'w+')
2053 new_dfh.truncate(0) # drop any potentially existing data
2081 new_dfh.truncate(0) # drop any potentially existing data
2054 try:
2082 try:
2055 with self._indexfp() as read_ifh:
2083 with self._indexfp() as read_ifh:
2056 for r in self:
2084 for r in self:
2057 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2085 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2058 if (
2059 trindex is None
2060 and troffset
2061 <= self.start(r) + r * self.index.entry_size
2062 ):
2063 trindex = r
2064 new_dfh.flush()
2086 new_dfh.flush()
2065
2087
2066 if trindex is None:
2088 if side_write:
2067 trindex = 0
2089 self._indexfile = new_index_file_path
2068
2069 with self.__index_new_fp() as fp:
2090 with self.__index_new_fp() as fp:
2070 self._format_flags &= ~FLAG_INLINE_DATA
2091 self._format_flags &= ~FLAG_INLINE_DATA
2071 self._inline = False
2092 self._inline = False
2072 for i in self:
2093 for i in self:
2073 e = self.index.entry_binary(i)
2094 e = self.index.entry_binary(i)
2074 if i == 0 and self._docket is None:
2095 if i == 0 and self._docket is None:
2075 header = self._format_flags | self._format_version
2096 header = self._format_flags | self._format_version
2076 header = self.index.pack_header(header)
2097 header = self.index.pack_header(header)
2077 e = header + e
2098 e = header + e
2078 fp.write(e)
2099 fp.write(e)
2079 if self._docket is not None:
2100 if self._docket is not None:
2080 self._docket.index_end = fp.tell()
2101 self._docket.index_end = fp.tell()
2081
2102
2082 # There is a small transactional race here. If the rename of
2103 # If we don't use side-write, the temp file replace the real
2083 # the index fails, we should remove the datafile. It is more
2104 # index when we exit the context manager
2084 # important to ensure that the data file is not truncated
2105
2085 # when the index is replaced as otherwise data is lost.
2086 tr.replace(self._datafile, self.start(trindex))
2087
2088 # the temp file replace the real index when we exit the context
2089 # manager
2090
2091 tr.replace(self._indexfile, trindex * self.index.entry_size)
2092 nodemaputil.setup_persistent_nodemap(tr, self)
2106 nodemaputil.setup_persistent_nodemap(tr, self)
2093 self._segmentfile = randomaccessfile.randomaccessfile(
2107 self._segmentfile = randomaccessfile.randomaccessfile(
2094 self.opener,
2108 self.opener,
2095 self._datafile,
2109 self._datafile,
2096 self._chunkcachesize,
2110 self._chunkcachesize,
2097 )
2111 )
2098
2112
2099 if existing_handles:
2113 if existing_handles:
2100 # switched from inline to conventional reopen the index
2114 # switched from inline to conventional reopen the index
2101 ifh = self.__index_write_fp()
2115 ifh = self.__index_write_fp()
2102 self._writinghandles = (ifh, new_dfh, None)
2116 self._writinghandles = (ifh, new_dfh, None)
2103 self._segmentfile.writing_handle = new_dfh
2117 self._segmentfile.writing_handle = new_dfh
2104 new_dfh = None
2118 new_dfh = None
2105 # No need to deal with sidedata writing handle as it is only
2119 # No need to deal with sidedata writing handle as it is only
2106 # relevant with revlog-v2 which is never inline, not reaching
2120 # relevant with revlog-v2 which is never inline, not reaching
2107 # this code
2121 # this code
2108 finally:
2122 finally:
2109 if new_dfh is not None:
2123 if new_dfh is not None:
2110 new_dfh.close()
2124 new_dfh.close()
2111
2125
2112 def _nodeduplicatecallback(self, transaction, node):
2126 def _nodeduplicatecallback(self, transaction, node):
2113 """called when trying to add a node already stored."""
2127 """called when trying to add a node already stored."""
2114
2128
2115 @contextlib.contextmanager
2129 @contextlib.contextmanager
2116 def reading(self):
2130 def reading(self):
2117 """Context manager that keeps data and sidedata files open for reading"""
2131 """Context manager that keeps data and sidedata files open for reading"""
2118 with self._segmentfile.reading():
2132 with self._segmentfile.reading():
2119 with self._segmentfile_sidedata.reading():
2133 with self._segmentfile_sidedata.reading():
2120 yield
2134 yield
2121
2135
2122 @contextlib.contextmanager
2136 @contextlib.contextmanager
2123 def _writing(self, transaction):
2137 def _writing(self, transaction):
2124 if self._trypending:
2138 if self._trypending:
2125 msg = b'try to write in a `trypending` revlog: %s'
2139 msg = b'try to write in a `trypending` revlog: %s'
2126 msg %= self.display_id
2140 msg %= self.display_id
2127 raise error.ProgrammingError(msg)
2141 raise error.ProgrammingError(msg)
2128 if self._writinghandles is not None:
2142 if self._writinghandles is not None:
2129 yield
2143 yield
2130 else:
2144 else:
2131 ifh = dfh = sdfh = None
2145 ifh = dfh = sdfh = None
2132 try:
2146 try:
2133 r = len(self)
2147 r = len(self)
2134 # opening the data file.
2148 # opening the data file.
2135 dsize = 0
2149 dsize = 0
2136 if r:
2150 if r:
2137 dsize = self.end(r - 1)
2151 dsize = self.end(r - 1)
2138 dfh = None
2152 dfh = None
2139 if not self._inline:
2153 if not self._inline:
2140 try:
2154 try:
2141 dfh = self._datafp(b"r+")
2155 dfh = self._datafp(b"r+")
2142 if self._docket is None:
2156 if self._docket is None:
2143 dfh.seek(0, os.SEEK_END)
2157 dfh.seek(0, os.SEEK_END)
2144 else:
2158 else:
2145 dfh.seek(self._docket.data_end, os.SEEK_SET)
2159 dfh.seek(self._docket.data_end, os.SEEK_SET)
2146 except FileNotFoundError:
2160 except FileNotFoundError:
2147 dfh = self._datafp(b"w+")
2161 dfh = self._datafp(b"w+")
2148 transaction.add(self._datafile, dsize)
2162 transaction.add(self._datafile, dsize)
2149 if self._sidedatafile is not None:
2163 if self._sidedatafile is not None:
2150 # revlog-v2 does not inline, help Pytype
2164 # revlog-v2 does not inline, help Pytype
2151 assert dfh is not None
2165 assert dfh is not None
2152 try:
2166 try:
2153 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2167 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2154 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2168 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2155 except FileNotFoundError:
2169 except FileNotFoundError:
2156 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2170 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2157 transaction.add(
2171 transaction.add(
2158 self._sidedatafile, self._docket.sidedata_end
2172 self._sidedatafile, self._docket.sidedata_end
2159 )
2173 )
2160
2174
2161 # opening the index file.
2175 # opening the index file.
2162 isize = r * self.index.entry_size
2176 isize = r * self.index.entry_size
2163 ifh = self.__index_write_fp()
2177 ifh = self.__index_write_fp()
2164 if self._inline:
2178 if self._inline:
2165 transaction.add(self._indexfile, dsize + isize)
2179 transaction.add(self._indexfile, dsize + isize)
2166 else:
2180 else:
2167 transaction.add(self._indexfile, isize)
2181 transaction.add(self._indexfile, isize)
2168 # exposing all file handle for writing.
2182 # exposing all file handle for writing.
2169 self._writinghandles = (ifh, dfh, sdfh)
2183 self._writinghandles = (ifh, dfh, sdfh)
2170 self._segmentfile.writing_handle = ifh if self._inline else dfh
2184 self._segmentfile.writing_handle = ifh if self._inline else dfh
2171 self._segmentfile_sidedata.writing_handle = sdfh
2185 self._segmentfile_sidedata.writing_handle = sdfh
2172 yield
2186 yield
2173 if self._docket is not None:
2187 if self._docket is not None:
2174 self._write_docket(transaction)
2188 self._write_docket(transaction)
2175 finally:
2189 finally:
2176 self._writinghandles = None
2190 self._writinghandles = None
2177 self._segmentfile.writing_handle = None
2191 self._segmentfile.writing_handle = None
2178 self._segmentfile_sidedata.writing_handle = None
2192 self._segmentfile_sidedata.writing_handle = None
2179 if dfh is not None:
2193 if dfh is not None:
2180 dfh.close()
2194 dfh.close()
2181 if sdfh is not None:
2195 if sdfh is not None:
2182 sdfh.close()
2196 sdfh.close()
2183 # closing the index file last to avoid exposing referent to
2197 # closing the index file last to avoid exposing referent to
2184 # potential unflushed data content.
2198 # potential unflushed data content.
2185 if ifh is not None:
2199 if ifh is not None:
2186 ifh.close()
2200 ifh.close()
2187
2201
2188 def _write_docket(self, transaction):
2202 def _write_docket(self, transaction):
2189 """write the current docket on disk
2203 """write the current docket on disk
2190
2204
2191 Exist as a method to help changelog to implement transaction logic
2205 Exist as a method to help changelog to implement transaction logic
2192
2206
2193 We could also imagine using the same transaction logic for all revlog
2207 We could also imagine using the same transaction logic for all revlog
2194 since docket are cheap."""
2208 since docket are cheap."""
2195 self._docket.write(transaction)
2209 self._docket.write(transaction)
2196
2210
2197 def addrevision(
2211 def addrevision(
2198 self,
2212 self,
2199 text,
2213 text,
2200 transaction,
2214 transaction,
2201 link,
2215 link,
2202 p1,
2216 p1,
2203 p2,
2217 p2,
2204 cachedelta=None,
2218 cachedelta=None,
2205 node=None,
2219 node=None,
2206 flags=REVIDX_DEFAULT_FLAGS,
2220 flags=REVIDX_DEFAULT_FLAGS,
2207 deltacomputer=None,
2221 deltacomputer=None,
2208 sidedata=None,
2222 sidedata=None,
2209 ):
2223 ):
2210 """add a revision to the log
2224 """add a revision to the log
2211
2225
2212 text - the revision data to add
2226 text - the revision data to add
2213 transaction - the transaction object used for rollback
2227 transaction - the transaction object used for rollback
2214 link - the linkrev data to add
2228 link - the linkrev data to add
2215 p1, p2 - the parent nodeids of the revision
2229 p1, p2 - the parent nodeids of the revision
2216 cachedelta - an optional precomputed delta
2230 cachedelta - an optional precomputed delta
2217 node - nodeid of revision; typically node is not specified, and it is
2231 node - nodeid of revision; typically node is not specified, and it is
2218 computed by default as hash(text, p1, p2), however subclasses might
2232 computed by default as hash(text, p1, p2), however subclasses might
2219 use different hashing method (and override checkhash() in such case)
2233 use different hashing method (and override checkhash() in such case)
2220 flags - the known flags to set on the revision
2234 flags - the known flags to set on the revision
2221 deltacomputer - an optional deltacomputer instance shared between
2235 deltacomputer - an optional deltacomputer instance shared between
2222 multiple calls
2236 multiple calls
2223 """
2237 """
2224 if link == nullrev:
2238 if link == nullrev:
2225 raise error.RevlogError(
2239 raise error.RevlogError(
2226 _(b"attempted to add linkrev -1 to %s") % self.display_id
2240 _(b"attempted to add linkrev -1 to %s") % self.display_id
2227 )
2241 )
2228
2242
2229 if sidedata is None:
2243 if sidedata is None:
2230 sidedata = {}
2244 sidedata = {}
2231 elif sidedata and not self.hassidedata:
2245 elif sidedata and not self.hassidedata:
2232 raise error.ProgrammingError(
2246 raise error.ProgrammingError(
2233 _(b"trying to add sidedata to a revlog who don't support them")
2247 _(b"trying to add sidedata to a revlog who don't support them")
2234 )
2248 )
2235
2249
2236 if flags:
2250 if flags:
2237 node = node or self.hash(text, p1, p2)
2251 node = node or self.hash(text, p1, p2)
2238
2252
2239 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2253 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2240
2254
2241 # If the flag processor modifies the revision data, ignore any provided
2255 # If the flag processor modifies the revision data, ignore any provided
2242 # cachedelta.
2256 # cachedelta.
2243 if rawtext != text:
2257 if rawtext != text:
2244 cachedelta = None
2258 cachedelta = None
2245
2259
2246 if len(rawtext) > _maxentrysize:
2260 if len(rawtext) > _maxentrysize:
2247 raise error.RevlogError(
2261 raise error.RevlogError(
2248 _(
2262 _(
2249 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2263 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2250 )
2264 )
2251 % (self.display_id, len(rawtext))
2265 % (self.display_id, len(rawtext))
2252 )
2266 )
2253
2267
2254 node = node or self.hash(rawtext, p1, p2)
2268 node = node or self.hash(rawtext, p1, p2)
2255 rev = self.index.get_rev(node)
2269 rev = self.index.get_rev(node)
2256 if rev is not None:
2270 if rev is not None:
2257 return rev
2271 return rev
2258
2272
2259 if validatehash:
2273 if validatehash:
2260 self.checkhash(rawtext, node, p1=p1, p2=p2)
2274 self.checkhash(rawtext, node, p1=p1, p2=p2)
2261
2275
2262 return self.addrawrevision(
2276 return self.addrawrevision(
2263 rawtext,
2277 rawtext,
2264 transaction,
2278 transaction,
2265 link,
2279 link,
2266 p1,
2280 p1,
2267 p2,
2281 p2,
2268 node,
2282 node,
2269 flags,
2283 flags,
2270 cachedelta=cachedelta,
2284 cachedelta=cachedelta,
2271 deltacomputer=deltacomputer,
2285 deltacomputer=deltacomputer,
2272 sidedata=sidedata,
2286 sidedata=sidedata,
2273 )
2287 )
2274
2288
2275 def addrawrevision(
2289 def addrawrevision(
2276 self,
2290 self,
2277 rawtext,
2291 rawtext,
2278 transaction,
2292 transaction,
2279 link,
2293 link,
2280 p1,
2294 p1,
2281 p2,
2295 p2,
2282 node,
2296 node,
2283 flags,
2297 flags,
2284 cachedelta=None,
2298 cachedelta=None,
2285 deltacomputer=None,
2299 deltacomputer=None,
2286 sidedata=None,
2300 sidedata=None,
2287 ):
2301 ):
2288 """add a raw revision with known flags, node and parents
2302 """add a raw revision with known flags, node and parents
2289 useful when reusing a revision not stored in this revlog (ex: received
2303 useful when reusing a revision not stored in this revlog (ex: received
2290 over wire, or read from an external bundle).
2304 over wire, or read from an external bundle).
2291 """
2305 """
2292 with self._writing(transaction):
2306 with self._writing(transaction):
2293 return self._addrevision(
2307 return self._addrevision(
2294 node,
2308 node,
2295 rawtext,
2309 rawtext,
2296 transaction,
2310 transaction,
2297 link,
2311 link,
2298 p1,
2312 p1,
2299 p2,
2313 p2,
2300 flags,
2314 flags,
2301 cachedelta,
2315 cachedelta,
2302 deltacomputer=deltacomputer,
2316 deltacomputer=deltacomputer,
2303 sidedata=sidedata,
2317 sidedata=sidedata,
2304 )
2318 )
2305
2319
2306 def compress(self, data):
2320 def compress(self, data):
2307 """Generate a possibly-compressed representation of data."""
2321 """Generate a possibly-compressed representation of data."""
2308 if not data:
2322 if not data:
2309 return b'', data
2323 return b'', data
2310
2324
2311 compressed = self._compressor.compress(data)
2325 compressed = self._compressor.compress(data)
2312
2326
2313 if compressed:
2327 if compressed:
2314 # The revlog compressor added the header in the returned data.
2328 # The revlog compressor added the header in the returned data.
2315 return b'', compressed
2329 return b'', compressed
2316
2330
2317 if data[0:1] == b'\0':
2331 if data[0:1] == b'\0':
2318 return b'', data
2332 return b'', data
2319 return b'u', data
2333 return b'u', data
2320
2334
2321 def decompress(self, data):
2335 def decompress(self, data):
2322 """Decompress a revlog chunk.
2336 """Decompress a revlog chunk.
2323
2337
2324 The chunk is expected to begin with a header identifying the
2338 The chunk is expected to begin with a header identifying the
2325 format type so it can be routed to an appropriate decompressor.
2339 format type so it can be routed to an appropriate decompressor.
2326 """
2340 """
2327 if not data:
2341 if not data:
2328 return data
2342 return data
2329
2343
2330 # Revlogs are read much more frequently than they are written and many
2344 # Revlogs are read much more frequently than they are written and many
2331 # chunks only take microseconds to decompress, so performance is
2345 # chunks only take microseconds to decompress, so performance is
2332 # important here.
2346 # important here.
2333 #
2347 #
2334 # We can make a few assumptions about revlogs:
2348 # We can make a few assumptions about revlogs:
2335 #
2349 #
2336 # 1) the majority of chunks will be compressed (as opposed to inline
2350 # 1) the majority of chunks will be compressed (as opposed to inline
2337 # raw data).
2351 # raw data).
2338 # 2) decompressing *any* data will likely by at least 10x slower than
2352 # 2) decompressing *any* data will likely by at least 10x slower than
2339 # returning raw inline data.
2353 # returning raw inline data.
2340 # 3) we want to prioritize common and officially supported compression
2354 # 3) we want to prioritize common and officially supported compression
2341 # engines
2355 # engines
2342 #
2356 #
2343 # It follows that we want to optimize for "decompress compressed data
2357 # It follows that we want to optimize for "decompress compressed data
2344 # when encoded with common and officially supported compression engines"
2358 # when encoded with common and officially supported compression engines"
2345 # case over "raw data" and "data encoded by less common or non-official
2359 # case over "raw data" and "data encoded by less common or non-official
2346 # compression engines." That is why we have the inline lookup first
2360 # compression engines." That is why we have the inline lookup first
2347 # followed by the compengines lookup.
2361 # followed by the compengines lookup.
2348 #
2362 #
2349 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2363 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2350 # compressed chunks. And this matters for changelog and manifest reads.
2364 # compressed chunks. And this matters for changelog and manifest reads.
2351 t = data[0:1]
2365 t = data[0:1]
2352
2366
2353 if t == b'x':
2367 if t == b'x':
2354 try:
2368 try:
2355 return _zlibdecompress(data)
2369 return _zlibdecompress(data)
2356 except zlib.error as e:
2370 except zlib.error as e:
2357 raise error.RevlogError(
2371 raise error.RevlogError(
2358 _(b'revlog decompress error: %s')
2372 _(b'revlog decompress error: %s')
2359 % stringutil.forcebytestr(e)
2373 % stringutil.forcebytestr(e)
2360 )
2374 )
2361 # '\0' is more common than 'u' so it goes first.
2375 # '\0' is more common than 'u' so it goes first.
2362 elif t == b'\0':
2376 elif t == b'\0':
2363 return data
2377 return data
2364 elif t == b'u':
2378 elif t == b'u':
2365 return util.buffer(data, 1)
2379 return util.buffer(data, 1)
2366
2380
2367 compressor = self._get_decompressor(t)
2381 compressor = self._get_decompressor(t)
2368
2382
2369 return compressor.decompress(data)
2383 return compressor.decompress(data)
2370
2384
2371 def _addrevision(
2385 def _addrevision(
2372 self,
2386 self,
2373 node,
2387 node,
2374 rawtext,
2388 rawtext,
2375 transaction,
2389 transaction,
2376 link,
2390 link,
2377 p1,
2391 p1,
2378 p2,
2392 p2,
2379 flags,
2393 flags,
2380 cachedelta,
2394 cachedelta,
2381 alwayscache=False,
2395 alwayscache=False,
2382 deltacomputer=None,
2396 deltacomputer=None,
2383 sidedata=None,
2397 sidedata=None,
2384 ):
2398 ):
2385 """internal function to add revisions to the log
2399 """internal function to add revisions to the log
2386
2400
2387 see addrevision for argument descriptions.
2401 see addrevision for argument descriptions.
2388
2402
2389 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2403 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2390
2404
2391 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2405 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2392 be used.
2406 be used.
2393
2407
2394 invariants:
2408 invariants:
2395 - rawtext is optional (can be None); if not set, cachedelta must be set.
2409 - rawtext is optional (can be None); if not set, cachedelta must be set.
2396 if both are set, they must correspond to each other.
2410 if both are set, they must correspond to each other.
2397 """
2411 """
2398 if node == self.nullid:
2412 if node == self.nullid:
2399 raise error.RevlogError(
2413 raise error.RevlogError(
2400 _(b"%s: attempt to add null revision") % self.display_id
2414 _(b"%s: attempt to add null revision") % self.display_id
2401 )
2415 )
2402 if (
2416 if (
2403 node == self.nodeconstants.wdirid
2417 node == self.nodeconstants.wdirid
2404 or node in self.nodeconstants.wdirfilenodeids
2418 or node in self.nodeconstants.wdirfilenodeids
2405 ):
2419 ):
2406 raise error.RevlogError(
2420 raise error.RevlogError(
2407 _(b"%s: attempt to add wdir revision") % self.display_id
2421 _(b"%s: attempt to add wdir revision") % self.display_id
2408 )
2422 )
2409 if self._writinghandles is None:
2423 if self._writinghandles is None:
2410 msg = b'adding revision outside `revlog._writing` context'
2424 msg = b'adding revision outside `revlog._writing` context'
2411 raise error.ProgrammingError(msg)
2425 raise error.ProgrammingError(msg)
2412
2426
2413 if self._inline:
2427 if self._inline:
2414 fh = self._writinghandles[0]
2428 fh = self._writinghandles[0]
2415 else:
2429 else:
2416 fh = self._writinghandles[1]
2430 fh = self._writinghandles[1]
2417
2431
2418 btext = [rawtext]
2432 btext = [rawtext]
2419
2433
2420 curr = len(self)
2434 curr = len(self)
2421 prev = curr - 1
2435 prev = curr - 1
2422
2436
2423 offset = self._get_data_offset(prev)
2437 offset = self._get_data_offset(prev)
2424
2438
2425 if self._concurrencychecker:
2439 if self._concurrencychecker:
2426 ifh, dfh, sdfh = self._writinghandles
2440 ifh, dfh, sdfh = self._writinghandles
2427 # XXX no checking for the sidedata file
2441 # XXX no checking for the sidedata file
2428 if self._inline:
2442 if self._inline:
2429 # offset is "as if" it were in the .d file, so we need to add on
2443 # offset is "as if" it were in the .d file, so we need to add on
2430 # the size of the entry metadata.
2444 # the size of the entry metadata.
2431 self._concurrencychecker(
2445 self._concurrencychecker(
2432 ifh, self._indexfile, offset + curr * self.index.entry_size
2446 ifh, self._indexfile, offset + curr * self.index.entry_size
2433 )
2447 )
2434 else:
2448 else:
2435 # Entries in the .i are a consistent size.
2449 # Entries in the .i are a consistent size.
2436 self._concurrencychecker(
2450 self._concurrencychecker(
2437 ifh, self._indexfile, curr * self.index.entry_size
2451 ifh, self._indexfile, curr * self.index.entry_size
2438 )
2452 )
2439 self._concurrencychecker(dfh, self._datafile, offset)
2453 self._concurrencychecker(dfh, self._datafile, offset)
2440
2454
2441 p1r, p2r = self.rev(p1), self.rev(p2)
2455 p1r, p2r = self.rev(p1), self.rev(p2)
2442
2456
2443 # full versions are inserted when the needed deltas
2457 # full versions are inserted when the needed deltas
2444 # become comparable to the uncompressed text
2458 # become comparable to the uncompressed text
2445 if rawtext is None:
2459 if rawtext is None:
2446 # need rawtext size, before changed by flag processors, which is
2460 # need rawtext size, before changed by flag processors, which is
2447 # the non-raw size. use revlog explicitly to avoid filelog's extra
2461 # the non-raw size. use revlog explicitly to avoid filelog's extra
2448 # logic that might remove metadata size.
2462 # logic that might remove metadata size.
2449 textlen = mdiff.patchedsize(
2463 textlen = mdiff.patchedsize(
2450 revlog.size(self, cachedelta[0]), cachedelta[1]
2464 revlog.size(self, cachedelta[0]), cachedelta[1]
2451 )
2465 )
2452 else:
2466 else:
2453 textlen = len(rawtext)
2467 textlen = len(rawtext)
2454
2468
2455 if deltacomputer is None:
2469 if deltacomputer is None:
2456 write_debug = None
2470 write_debug = None
2457 if self._debug_delta:
2471 if self._debug_delta:
2458 write_debug = transaction._report
2472 write_debug = transaction._report
2459 deltacomputer = deltautil.deltacomputer(
2473 deltacomputer = deltautil.deltacomputer(
2460 self, write_debug=write_debug
2474 self, write_debug=write_debug
2461 )
2475 )
2462
2476
2463 if cachedelta is not None and len(cachedelta) == 2:
2477 if cachedelta is not None and len(cachedelta) == 2:
2464 # If the cached delta has no information about how it should be
2478 # If the cached delta has no information about how it should be
2465 # reused, add the default reuse instruction according to the
2479 # reused, add the default reuse instruction according to the
2466 # revlog's configuration.
2480 # revlog's configuration.
2467 if self._generaldelta and self._lazydeltabase:
2481 if self._generaldelta and self._lazydeltabase:
2468 delta_base_reuse = DELTA_BASE_REUSE_TRY
2482 delta_base_reuse = DELTA_BASE_REUSE_TRY
2469 else:
2483 else:
2470 delta_base_reuse = DELTA_BASE_REUSE_NO
2484 delta_base_reuse = DELTA_BASE_REUSE_NO
2471 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2485 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2472
2486
2473 revinfo = revlogutils.revisioninfo(
2487 revinfo = revlogutils.revisioninfo(
2474 node,
2488 node,
2475 p1,
2489 p1,
2476 p2,
2490 p2,
2477 btext,
2491 btext,
2478 textlen,
2492 textlen,
2479 cachedelta,
2493 cachedelta,
2480 flags,
2494 flags,
2481 )
2495 )
2482
2496
2483 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2497 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2484
2498
2485 compression_mode = COMP_MODE_INLINE
2499 compression_mode = COMP_MODE_INLINE
2486 if self._docket is not None:
2500 if self._docket is not None:
2487 default_comp = self._docket.default_compression_header
2501 default_comp = self._docket.default_compression_header
2488 r = deltautil.delta_compression(default_comp, deltainfo)
2502 r = deltautil.delta_compression(default_comp, deltainfo)
2489 compression_mode, deltainfo = r
2503 compression_mode, deltainfo = r
2490
2504
2491 sidedata_compression_mode = COMP_MODE_INLINE
2505 sidedata_compression_mode = COMP_MODE_INLINE
2492 if sidedata and self.hassidedata:
2506 if sidedata and self.hassidedata:
2493 sidedata_compression_mode = COMP_MODE_PLAIN
2507 sidedata_compression_mode = COMP_MODE_PLAIN
2494 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2508 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2495 sidedata_offset = self._docket.sidedata_end
2509 sidedata_offset = self._docket.sidedata_end
2496 h, comp_sidedata = self.compress(serialized_sidedata)
2510 h, comp_sidedata = self.compress(serialized_sidedata)
2497 if (
2511 if (
2498 h != b'u'
2512 h != b'u'
2499 and comp_sidedata[0:1] != b'\0'
2513 and comp_sidedata[0:1] != b'\0'
2500 and len(comp_sidedata) < len(serialized_sidedata)
2514 and len(comp_sidedata) < len(serialized_sidedata)
2501 ):
2515 ):
2502 assert not h
2516 assert not h
2503 if (
2517 if (
2504 comp_sidedata[0:1]
2518 comp_sidedata[0:1]
2505 == self._docket.default_compression_header
2519 == self._docket.default_compression_header
2506 ):
2520 ):
2507 sidedata_compression_mode = COMP_MODE_DEFAULT
2521 sidedata_compression_mode = COMP_MODE_DEFAULT
2508 serialized_sidedata = comp_sidedata
2522 serialized_sidedata = comp_sidedata
2509 else:
2523 else:
2510 sidedata_compression_mode = COMP_MODE_INLINE
2524 sidedata_compression_mode = COMP_MODE_INLINE
2511 serialized_sidedata = comp_sidedata
2525 serialized_sidedata = comp_sidedata
2512 else:
2526 else:
2513 serialized_sidedata = b""
2527 serialized_sidedata = b""
2514 # Don't store the offset if the sidedata is empty, that way
2528 # Don't store the offset if the sidedata is empty, that way
2515 # we can easily detect empty sidedata and they will be no different
2529 # we can easily detect empty sidedata and they will be no different
2516 # than ones we manually add.
2530 # than ones we manually add.
2517 sidedata_offset = 0
2531 sidedata_offset = 0
2518
2532
2519 rank = RANK_UNKNOWN
2533 rank = RANK_UNKNOWN
2520 if self._compute_rank:
2534 if self._compute_rank:
2521 if (p1r, p2r) == (nullrev, nullrev):
2535 if (p1r, p2r) == (nullrev, nullrev):
2522 rank = 1
2536 rank = 1
2523 elif p1r != nullrev and p2r == nullrev:
2537 elif p1r != nullrev and p2r == nullrev:
2524 rank = 1 + self.fast_rank(p1r)
2538 rank = 1 + self.fast_rank(p1r)
2525 elif p1r == nullrev and p2r != nullrev:
2539 elif p1r == nullrev and p2r != nullrev:
2526 rank = 1 + self.fast_rank(p2r)
2540 rank = 1 + self.fast_rank(p2r)
2527 else: # merge node
2541 else: # merge node
2528 if rustdagop is not None and self.index.rust_ext_compat:
2542 if rustdagop is not None and self.index.rust_ext_compat:
2529 rank = rustdagop.rank(self.index, p1r, p2r)
2543 rank = rustdagop.rank(self.index, p1r, p2r)
2530 else:
2544 else:
2531 pmin, pmax = sorted((p1r, p2r))
2545 pmin, pmax = sorted((p1r, p2r))
2532 rank = 1 + self.fast_rank(pmax)
2546 rank = 1 + self.fast_rank(pmax)
2533 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2547 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2534
2548
2535 e = revlogutils.entry(
2549 e = revlogutils.entry(
2536 flags=flags,
2550 flags=flags,
2537 data_offset=offset,
2551 data_offset=offset,
2538 data_compressed_length=deltainfo.deltalen,
2552 data_compressed_length=deltainfo.deltalen,
2539 data_uncompressed_length=textlen,
2553 data_uncompressed_length=textlen,
2540 data_compression_mode=compression_mode,
2554 data_compression_mode=compression_mode,
2541 data_delta_base=deltainfo.base,
2555 data_delta_base=deltainfo.base,
2542 link_rev=link,
2556 link_rev=link,
2543 parent_rev_1=p1r,
2557 parent_rev_1=p1r,
2544 parent_rev_2=p2r,
2558 parent_rev_2=p2r,
2545 node_id=node,
2559 node_id=node,
2546 sidedata_offset=sidedata_offset,
2560 sidedata_offset=sidedata_offset,
2547 sidedata_compressed_length=len(serialized_sidedata),
2561 sidedata_compressed_length=len(serialized_sidedata),
2548 sidedata_compression_mode=sidedata_compression_mode,
2562 sidedata_compression_mode=sidedata_compression_mode,
2549 rank=rank,
2563 rank=rank,
2550 )
2564 )
2551
2565
2552 self.index.append(e)
2566 self.index.append(e)
2553 entry = self.index.entry_binary(curr)
2567 entry = self.index.entry_binary(curr)
2554 if curr == 0 and self._docket is None:
2568 if curr == 0 and self._docket is None:
2555 header = self._format_flags | self._format_version
2569 header = self._format_flags | self._format_version
2556 header = self.index.pack_header(header)
2570 header = self.index.pack_header(header)
2557 entry = header + entry
2571 entry = header + entry
2558 self._writeentry(
2572 self._writeentry(
2559 transaction,
2573 transaction,
2560 entry,
2574 entry,
2561 deltainfo.data,
2575 deltainfo.data,
2562 link,
2576 link,
2563 offset,
2577 offset,
2564 serialized_sidedata,
2578 serialized_sidedata,
2565 sidedata_offset,
2579 sidedata_offset,
2566 )
2580 )
2567
2581
2568 rawtext = btext[0]
2582 rawtext = btext[0]
2569
2583
2570 if alwayscache and rawtext is None:
2584 if alwayscache and rawtext is None:
2571 rawtext = deltacomputer.buildtext(revinfo, fh)
2585 rawtext = deltacomputer.buildtext(revinfo, fh)
2572
2586
2573 if type(rawtext) == bytes: # only accept immutable objects
2587 if type(rawtext) == bytes: # only accept immutable objects
2574 self._revisioncache = (node, curr, rawtext)
2588 self._revisioncache = (node, curr, rawtext)
2575 self._chainbasecache[curr] = deltainfo.chainbase
2589 self._chainbasecache[curr] = deltainfo.chainbase
2576 return curr
2590 return curr
2577
2591
2578 def _get_data_offset(self, prev):
2592 def _get_data_offset(self, prev):
2579 """Returns the current offset in the (in-transaction) data file.
2593 """Returns the current offset in the (in-transaction) data file.
2580 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2594 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2581 file to store that information: since sidedata can be rewritten to the
2595 file to store that information: since sidedata can be rewritten to the
2582 end of the data file within a transaction, you can have cases where, for
2596 end of the data file within a transaction, you can have cases where, for
2583 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2597 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2584 to `n - 1`'s sidedata being written after `n`'s data.
2598 to `n - 1`'s sidedata being written after `n`'s data.
2585
2599
2586 TODO cache this in a docket file before getting out of experimental."""
2600 TODO cache this in a docket file before getting out of experimental."""
2587 if self._docket is None:
2601 if self._docket is None:
2588 return self.end(prev)
2602 return self.end(prev)
2589 else:
2603 else:
2590 return self._docket.data_end
2604 return self._docket.data_end
2591
2605
2592 def _writeentry(
2606 def _writeentry(
2593 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2607 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2594 ):
2608 ):
2595 # Files opened in a+ mode have inconsistent behavior on various
2609 # Files opened in a+ mode have inconsistent behavior on various
2596 # platforms. Windows requires that a file positioning call be made
2610 # platforms. Windows requires that a file positioning call be made
2597 # when the file handle transitions between reads and writes. See
2611 # when the file handle transitions between reads and writes. See
2598 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2612 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2599 # platforms, Python or the platform itself can be buggy. Some versions
2613 # platforms, Python or the platform itself can be buggy. Some versions
2600 # of Solaris have been observed to not append at the end of the file
2614 # of Solaris have been observed to not append at the end of the file
2601 # if the file was seeked to before the end. See issue4943 for more.
2615 # if the file was seeked to before the end. See issue4943 for more.
2602 #
2616 #
2603 # We work around this issue by inserting a seek() before writing.
2617 # We work around this issue by inserting a seek() before writing.
2604 # Note: This is likely not necessary on Python 3. However, because
2618 # Note: This is likely not necessary on Python 3. However, because
2605 # the file handle is reused for reads and may be seeked there, we need
2619 # the file handle is reused for reads and may be seeked there, we need
2606 # to be careful before changing this.
2620 # to be careful before changing this.
2607 if self._writinghandles is None:
2621 if self._writinghandles is None:
2608 msg = b'adding revision outside `revlog._writing` context'
2622 msg = b'adding revision outside `revlog._writing` context'
2609 raise error.ProgrammingError(msg)
2623 raise error.ProgrammingError(msg)
2610 ifh, dfh, sdfh = self._writinghandles
2624 ifh, dfh, sdfh = self._writinghandles
2611 if self._docket is None:
2625 if self._docket is None:
2612 ifh.seek(0, os.SEEK_END)
2626 ifh.seek(0, os.SEEK_END)
2613 else:
2627 else:
2614 ifh.seek(self._docket.index_end, os.SEEK_SET)
2628 ifh.seek(self._docket.index_end, os.SEEK_SET)
2615 if dfh:
2629 if dfh:
2616 if self._docket is None:
2630 if self._docket is None:
2617 dfh.seek(0, os.SEEK_END)
2631 dfh.seek(0, os.SEEK_END)
2618 else:
2632 else:
2619 dfh.seek(self._docket.data_end, os.SEEK_SET)
2633 dfh.seek(self._docket.data_end, os.SEEK_SET)
2620 if sdfh:
2634 if sdfh:
2621 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2635 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2622
2636
2623 curr = len(self) - 1
2637 curr = len(self) - 1
2624 if not self._inline:
2638 if not self._inline:
2625 transaction.add(self._datafile, offset)
2639 transaction.add(self._datafile, offset)
2626 if self._sidedatafile:
2640 if self._sidedatafile:
2627 transaction.add(self._sidedatafile, sidedata_offset)
2641 transaction.add(self._sidedatafile, sidedata_offset)
2628 transaction.add(self._indexfile, curr * len(entry))
2642 transaction.add(self._indexfile, curr * len(entry))
2629 if data[0]:
2643 if data[0]:
2630 dfh.write(data[0])
2644 dfh.write(data[0])
2631 dfh.write(data[1])
2645 dfh.write(data[1])
2632 if sidedata:
2646 if sidedata:
2633 sdfh.write(sidedata)
2647 sdfh.write(sidedata)
2634 ifh.write(entry)
2648 ifh.write(entry)
2635 else:
2649 else:
2636 offset += curr * self.index.entry_size
2650 offset += curr * self.index.entry_size
2637 transaction.add(self._indexfile, offset)
2651 transaction.add(self._indexfile, offset)
2638 ifh.write(entry)
2652 ifh.write(entry)
2639 ifh.write(data[0])
2653 ifh.write(data[0])
2640 ifh.write(data[1])
2654 ifh.write(data[1])
2641 assert not sidedata
2655 assert not sidedata
2642 self._enforceinlinesize(transaction)
2656 self._enforceinlinesize(transaction)
2643 if self._docket is not None:
2657 if self._docket is not None:
2644 # revlog-v2 always has 3 writing handles, help Pytype
2658 # revlog-v2 always has 3 writing handles, help Pytype
2645 wh1 = self._writinghandles[0]
2659 wh1 = self._writinghandles[0]
2646 wh2 = self._writinghandles[1]
2660 wh2 = self._writinghandles[1]
2647 wh3 = self._writinghandles[2]
2661 wh3 = self._writinghandles[2]
2648 assert wh1 is not None
2662 assert wh1 is not None
2649 assert wh2 is not None
2663 assert wh2 is not None
2650 assert wh3 is not None
2664 assert wh3 is not None
2651 self._docket.index_end = wh1.tell()
2665 self._docket.index_end = wh1.tell()
2652 self._docket.data_end = wh2.tell()
2666 self._docket.data_end = wh2.tell()
2653 self._docket.sidedata_end = wh3.tell()
2667 self._docket.sidedata_end = wh3.tell()
2654
2668
2655 nodemaputil.setup_persistent_nodemap(transaction, self)
2669 nodemaputil.setup_persistent_nodemap(transaction, self)
2656
2670
2657 def addgroup(
2671 def addgroup(
2658 self,
2672 self,
2659 deltas,
2673 deltas,
2660 linkmapper,
2674 linkmapper,
2661 transaction,
2675 transaction,
2662 alwayscache=False,
2676 alwayscache=False,
2663 addrevisioncb=None,
2677 addrevisioncb=None,
2664 duplicaterevisioncb=None,
2678 duplicaterevisioncb=None,
2665 debug_info=None,
2679 debug_info=None,
2666 delta_base_reuse_policy=None,
2680 delta_base_reuse_policy=None,
2667 ):
2681 ):
2668 """
2682 """
2669 add a delta group
2683 add a delta group
2670
2684
2671 given a set of deltas, add them to the revision log. the
2685 given a set of deltas, add them to the revision log. the
2672 first delta is against its parent, which should be in our
2686 first delta is against its parent, which should be in our
2673 log, the rest are against the previous delta.
2687 log, the rest are against the previous delta.
2674
2688
2675 If ``addrevisioncb`` is defined, it will be called with arguments of
2689 If ``addrevisioncb`` is defined, it will be called with arguments of
2676 this revlog and the node that was added.
2690 this revlog and the node that was added.
2677 """
2691 """
2678
2692
2679 if self._adding_group:
2693 if self._adding_group:
2680 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2694 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2681
2695
2682 # read the default delta-base reuse policy from revlog config if the
2696 # read the default delta-base reuse policy from revlog config if the
2683 # group did not specify one.
2697 # group did not specify one.
2684 if delta_base_reuse_policy is None:
2698 if delta_base_reuse_policy is None:
2685 if self._generaldelta and self._lazydeltabase:
2699 if self._generaldelta and self._lazydeltabase:
2686 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
2700 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
2687 else:
2701 else:
2688 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
2702 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
2689
2703
2690 self._adding_group = True
2704 self._adding_group = True
2691 empty = True
2705 empty = True
2692 try:
2706 try:
2693 with self._writing(transaction):
2707 with self._writing(transaction):
2694 write_debug = None
2708 write_debug = None
2695 if self._debug_delta:
2709 if self._debug_delta:
2696 write_debug = transaction._report
2710 write_debug = transaction._report
2697 deltacomputer = deltautil.deltacomputer(
2711 deltacomputer = deltautil.deltacomputer(
2698 self,
2712 self,
2699 write_debug=write_debug,
2713 write_debug=write_debug,
2700 debug_info=debug_info,
2714 debug_info=debug_info,
2701 )
2715 )
2702 # loop through our set of deltas
2716 # loop through our set of deltas
2703 for data in deltas:
2717 for data in deltas:
2704 (
2718 (
2705 node,
2719 node,
2706 p1,
2720 p1,
2707 p2,
2721 p2,
2708 linknode,
2722 linknode,
2709 deltabase,
2723 deltabase,
2710 delta,
2724 delta,
2711 flags,
2725 flags,
2712 sidedata,
2726 sidedata,
2713 ) = data
2727 ) = data
2714 link = linkmapper(linknode)
2728 link = linkmapper(linknode)
2715 flags = flags or REVIDX_DEFAULT_FLAGS
2729 flags = flags or REVIDX_DEFAULT_FLAGS
2716
2730
2717 rev = self.index.get_rev(node)
2731 rev = self.index.get_rev(node)
2718 if rev is not None:
2732 if rev is not None:
2719 # this can happen if two branches make the same change
2733 # this can happen if two branches make the same change
2720 self._nodeduplicatecallback(transaction, rev)
2734 self._nodeduplicatecallback(transaction, rev)
2721 if duplicaterevisioncb:
2735 if duplicaterevisioncb:
2722 duplicaterevisioncb(self, rev)
2736 duplicaterevisioncb(self, rev)
2723 empty = False
2737 empty = False
2724 continue
2738 continue
2725
2739
2726 for p in (p1, p2):
2740 for p in (p1, p2):
2727 if not self.index.has_node(p):
2741 if not self.index.has_node(p):
2728 raise error.LookupError(
2742 raise error.LookupError(
2729 p, self.radix, _(b'unknown parent')
2743 p, self.radix, _(b'unknown parent')
2730 )
2744 )
2731
2745
2732 if not self.index.has_node(deltabase):
2746 if not self.index.has_node(deltabase):
2733 raise error.LookupError(
2747 raise error.LookupError(
2734 deltabase, self.display_id, _(b'unknown delta base')
2748 deltabase, self.display_id, _(b'unknown delta base')
2735 )
2749 )
2736
2750
2737 baserev = self.rev(deltabase)
2751 baserev = self.rev(deltabase)
2738
2752
2739 if baserev != nullrev and self.iscensored(baserev):
2753 if baserev != nullrev and self.iscensored(baserev):
2740 # if base is censored, delta must be full replacement in a
2754 # if base is censored, delta must be full replacement in a
2741 # single patch operation
2755 # single patch operation
2742 hlen = struct.calcsize(b">lll")
2756 hlen = struct.calcsize(b">lll")
2743 oldlen = self.rawsize(baserev)
2757 oldlen = self.rawsize(baserev)
2744 newlen = len(delta) - hlen
2758 newlen = len(delta) - hlen
2745 if delta[:hlen] != mdiff.replacediffheader(
2759 if delta[:hlen] != mdiff.replacediffheader(
2746 oldlen, newlen
2760 oldlen, newlen
2747 ):
2761 ):
2748 raise error.CensoredBaseError(
2762 raise error.CensoredBaseError(
2749 self.display_id, self.node(baserev)
2763 self.display_id, self.node(baserev)
2750 )
2764 )
2751
2765
2752 if not flags and self._peek_iscensored(baserev, delta):
2766 if not flags and self._peek_iscensored(baserev, delta):
2753 flags |= REVIDX_ISCENSORED
2767 flags |= REVIDX_ISCENSORED
2754
2768
2755 # We assume consumers of addrevisioncb will want to retrieve
2769 # We assume consumers of addrevisioncb will want to retrieve
2756 # the added revision, which will require a call to
2770 # the added revision, which will require a call to
2757 # revision(). revision() will fast path if there is a cache
2771 # revision(). revision() will fast path if there is a cache
2758 # hit. So, we tell _addrevision() to always cache in this case.
2772 # hit. So, we tell _addrevision() to always cache in this case.
2759 # We're only using addgroup() in the context of changegroup
2773 # We're only using addgroup() in the context of changegroup
2760 # generation so the revision data can always be handled as raw
2774 # generation so the revision data can always be handled as raw
2761 # by the flagprocessor.
2775 # by the flagprocessor.
2762 rev = self._addrevision(
2776 rev = self._addrevision(
2763 node,
2777 node,
2764 None,
2778 None,
2765 transaction,
2779 transaction,
2766 link,
2780 link,
2767 p1,
2781 p1,
2768 p2,
2782 p2,
2769 flags,
2783 flags,
2770 (baserev, delta, delta_base_reuse_policy),
2784 (baserev, delta, delta_base_reuse_policy),
2771 alwayscache=alwayscache,
2785 alwayscache=alwayscache,
2772 deltacomputer=deltacomputer,
2786 deltacomputer=deltacomputer,
2773 sidedata=sidedata,
2787 sidedata=sidedata,
2774 )
2788 )
2775
2789
2776 if addrevisioncb:
2790 if addrevisioncb:
2777 addrevisioncb(self, rev)
2791 addrevisioncb(self, rev)
2778 empty = False
2792 empty = False
2779 finally:
2793 finally:
2780 self._adding_group = False
2794 self._adding_group = False
2781 return not empty
2795 return not empty
2782
2796
2783 def iscensored(self, rev):
2797 def iscensored(self, rev):
2784 """Check if a file revision is censored."""
2798 """Check if a file revision is censored."""
2785 if not self._censorable:
2799 if not self._censorable:
2786 return False
2800 return False
2787
2801
2788 return self.flags(rev) & REVIDX_ISCENSORED
2802 return self.flags(rev) & REVIDX_ISCENSORED
2789
2803
2790 def _peek_iscensored(self, baserev, delta):
2804 def _peek_iscensored(self, baserev, delta):
2791 """Quickly check if a delta produces a censored revision."""
2805 """Quickly check if a delta produces a censored revision."""
2792 if not self._censorable:
2806 if not self._censorable:
2793 return False
2807 return False
2794
2808
2795 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2809 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2796
2810
2797 def getstrippoint(self, minlink):
2811 def getstrippoint(self, minlink):
2798 """find the minimum rev that must be stripped to strip the linkrev
2812 """find the minimum rev that must be stripped to strip the linkrev
2799
2813
2800 Returns a tuple containing the minimum rev and a set of all revs that
2814 Returns a tuple containing the minimum rev and a set of all revs that
2801 have linkrevs that will be broken by this strip.
2815 have linkrevs that will be broken by this strip.
2802 """
2816 """
2803 return storageutil.resolvestripinfo(
2817 return storageutil.resolvestripinfo(
2804 minlink,
2818 minlink,
2805 len(self) - 1,
2819 len(self) - 1,
2806 self.headrevs(),
2820 self.headrevs(),
2807 self.linkrev,
2821 self.linkrev,
2808 self.parentrevs,
2822 self.parentrevs,
2809 )
2823 )
2810
2824
2811 def strip(self, minlink, transaction):
2825 def strip(self, minlink, transaction):
2812 """truncate the revlog on the first revision with a linkrev >= minlink
2826 """truncate the revlog on the first revision with a linkrev >= minlink
2813
2827
2814 This function is called when we're stripping revision minlink and
2828 This function is called when we're stripping revision minlink and
2815 its descendants from the repository.
2829 its descendants from the repository.
2816
2830
2817 We have to remove all revisions with linkrev >= minlink, because
2831 We have to remove all revisions with linkrev >= minlink, because
2818 the equivalent changelog revisions will be renumbered after the
2832 the equivalent changelog revisions will be renumbered after the
2819 strip.
2833 strip.
2820
2834
2821 So we truncate the revlog on the first of these revisions, and
2835 So we truncate the revlog on the first of these revisions, and
2822 trust that the caller has saved the revisions that shouldn't be
2836 trust that the caller has saved the revisions that shouldn't be
2823 removed and that it'll re-add them after this truncation.
2837 removed and that it'll re-add them after this truncation.
2824 """
2838 """
2825 if len(self) == 0:
2839 if len(self) == 0:
2826 return
2840 return
2827
2841
2828 rev, _ = self.getstrippoint(minlink)
2842 rev, _ = self.getstrippoint(minlink)
2829 if rev == len(self):
2843 if rev == len(self):
2830 return
2844 return
2831
2845
2832 # first truncate the files on disk
2846 # first truncate the files on disk
2833 data_end = self.start(rev)
2847 data_end = self.start(rev)
2834 if not self._inline:
2848 if not self._inline:
2835 transaction.add(self._datafile, data_end)
2849 transaction.add(self._datafile, data_end)
2836 end = rev * self.index.entry_size
2850 end = rev * self.index.entry_size
2837 else:
2851 else:
2838 end = data_end + (rev * self.index.entry_size)
2852 end = data_end + (rev * self.index.entry_size)
2839
2853
2840 if self._sidedatafile:
2854 if self._sidedatafile:
2841 sidedata_end = self.sidedata_cut_off(rev)
2855 sidedata_end = self.sidedata_cut_off(rev)
2842 transaction.add(self._sidedatafile, sidedata_end)
2856 transaction.add(self._sidedatafile, sidedata_end)
2843
2857
2844 transaction.add(self._indexfile, end)
2858 transaction.add(self._indexfile, end)
2845 if self._docket is not None:
2859 if self._docket is not None:
2846 # XXX we could, leverage the docket while stripping. However it is
2860 # XXX we could, leverage the docket while stripping. However it is
2847 # not powerfull enough at the time of this comment
2861 # not powerfull enough at the time of this comment
2848 self._docket.index_end = end
2862 self._docket.index_end = end
2849 self._docket.data_end = data_end
2863 self._docket.data_end = data_end
2850 self._docket.sidedata_end = sidedata_end
2864 self._docket.sidedata_end = sidedata_end
2851 self._docket.write(transaction, stripping=True)
2865 self._docket.write(transaction, stripping=True)
2852
2866
2853 # then reset internal state in memory to forget those revisions
2867 # then reset internal state in memory to forget those revisions
2854 self._revisioncache = None
2868 self._revisioncache = None
2855 self._chaininfocache = util.lrucachedict(500)
2869 self._chaininfocache = util.lrucachedict(500)
2856 self._segmentfile.clear_cache()
2870 self._segmentfile.clear_cache()
2857 self._segmentfile_sidedata.clear_cache()
2871 self._segmentfile_sidedata.clear_cache()
2858
2872
2859 del self.index[rev:-1]
2873 del self.index[rev:-1]
2860
2874
2861 def checksize(self):
2875 def checksize(self):
2862 """Check size of index and data files
2876 """Check size of index and data files
2863
2877
2864 return a (dd, di) tuple.
2878 return a (dd, di) tuple.
2865 - dd: extra bytes for the "data" file
2879 - dd: extra bytes for the "data" file
2866 - di: extra bytes for the "index" file
2880 - di: extra bytes for the "index" file
2867
2881
2868 A healthy revlog will return (0, 0).
2882 A healthy revlog will return (0, 0).
2869 """
2883 """
2870 expected = 0
2884 expected = 0
2871 if len(self):
2885 if len(self):
2872 expected = max(0, self.end(len(self) - 1))
2886 expected = max(0, self.end(len(self) - 1))
2873
2887
2874 try:
2888 try:
2875 with self._datafp() as f:
2889 with self._datafp() as f:
2876 f.seek(0, io.SEEK_END)
2890 f.seek(0, io.SEEK_END)
2877 actual = f.tell()
2891 actual = f.tell()
2878 dd = actual - expected
2892 dd = actual - expected
2879 except FileNotFoundError:
2893 except FileNotFoundError:
2880 dd = 0
2894 dd = 0
2881
2895
2882 try:
2896 try:
2883 f = self.opener(self._indexfile)
2897 f = self.opener(self._indexfile)
2884 f.seek(0, io.SEEK_END)
2898 f.seek(0, io.SEEK_END)
2885 actual = f.tell()
2899 actual = f.tell()
2886 f.close()
2900 f.close()
2887 s = self.index.entry_size
2901 s = self.index.entry_size
2888 i = max(0, actual // s)
2902 i = max(0, actual // s)
2889 di = actual - (i * s)
2903 di = actual - (i * s)
2890 if self._inline:
2904 if self._inline:
2891 databytes = 0
2905 databytes = 0
2892 for r in self:
2906 for r in self:
2893 databytes += max(0, self.length(r))
2907 databytes += max(0, self.length(r))
2894 dd = 0
2908 dd = 0
2895 di = actual - len(self) * s - databytes
2909 di = actual - len(self) * s - databytes
2896 except FileNotFoundError:
2910 except FileNotFoundError:
2897 di = 0
2911 di = 0
2898
2912
2899 return (dd, di)
2913 return (dd, di)
2900
2914
2901 def files(self):
2915 def files(self):
2902 res = [self._indexfile]
2916 res = [self._indexfile]
2903 if self._docket_file is None:
2917 if self._docket_file is None:
2904 if not self._inline:
2918 if not self._inline:
2905 res.append(self._datafile)
2919 res.append(self._datafile)
2906 else:
2920 else:
2907 res.append(self._docket_file)
2921 res.append(self._docket_file)
2908 res.extend(self._docket.old_index_filepaths(include_empty=False))
2922 res.extend(self._docket.old_index_filepaths(include_empty=False))
2909 if self._docket.data_end:
2923 if self._docket.data_end:
2910 res.append(self._datafile)
2924 res.append(self._datafile)
2911 res.extend(self._docket.old_data_filepaths(include_empty=False))
2925 res.extend(self._docket.old_data_filepaths(include_empty=False))
2912 if self._docket.sidedata_end:
2926 if self._docket.sidedata_end:
2913 res.append(self._sidedatafile)
2927 res.append(self._sidedatafile)
2914 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
2928 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
2915 return res
2929 return res
2916
2930
2917 def emitrevisions(
2931 def emitrevisions(
2918 self,
2932 self,
2919 nodes,
2933 nodes,
2920 nodesorder=None,
2934 nodesorder=None,
2921 revisiondata=False,
2935 revisiondata=False,
2922 assumehaveparentrevisions=False,
2936 assumehaveparentrevisions=False,
2923 deltamode=repository.CG_DELTAMODE_STD,
2937 deltamode=repository.CG_DELTAMODE_STD,
2924 sidedata_helpers=None,
2938 sidedata_helpers=None,
2925 debug_info=None,
2939 debug_info=None,
2926 ):
2940 ):
2927 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2941 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2928 raise error.ProgrammingError(
2942 raise error.ProgrammingError(
2929 b'unhandled value for nodesorder: %s' % nodesorder
2943 b'unhandled value for nodesorder: %s' % nodesorder
2930 )
2944 )
2931
2945
2932 if nodesorder is None and not self._generaldelta:
2946 if nodesorder is None and not self._generaldelta:
2933 nodesorder = b'storage'
2947 nodesorder = b'storage'
2934
2948
2935 if (
2949 if (
2936 not self._storedeltachains
2950 not self._storedeltachains
2937 and deltamode != repository.CG_DELTAMODE_PREV
2951 and deltamode != repository.CG_DELTAMODE_PREV
2938 ):
2952 ):
2939 deltamode = repository.CG_DELTAMODE_FULL
2953 deltamode = repository.CG_DELTAMODE_FULL
2940
2954
2941 return storageutil.emitrevisions(
2955 return storageutil.emitrevisions(
2942 self,
2956 self,
2943 nodes,
2957 nodes,
2944 nodesorder,
2958 nodesorder,
2945 revlogrevisiondelta,
2959 revlogrevisiondelta,
2946 deltaparentfn=self.deltaparent,
2960 deltaparentfn=self.deltaparent,
2947 candeltafn=self.candelta,
2961 candeltafn=self.candelta,
2948 rawsizefn=self.rawsize,
2962 rawsizefn=self.rawsize,
2949 revdifffn=self.revdiff,
2963 revdifffn=self.revdiff,
2950 flagsfn=self.flags,
2964 flagsfn=self.flags,
2951 deltamode=deltamode,
2965 deltamode=deltamode,
2952 revisiondata=revisiondata,
2966 revisiondata=revisiondata,
2953 assumehaveparentrevisions=assumehaveparentrevisions,
2967 assumehaveparentrevisions=assumehaveparentrevisions,
2954 sidedata_helpers=sidedata_helpers,
2968 sidedata_helpers=sidedata_helpers,
2955 debug_info=debug_info,
2969 debug_info=debug_info,
2956 )
2970 )
2957
2971
2958 DELTAREUSEALWAYS = b'always'
2972 DELTAREUSEALWAYS = b'always'
2959 DELTAREUSESAMEREVS = b'samerevs'
2973 DELTAREUSESAMEREVS = b'samerevs'
2960 DELTAREUSENEVER = b'never'
2974 DELTAREUSENEVER = b'never'
2961
2975
2962 DELTAREUSEFULLADD = b'fulladd'
2976 DELTAREUSEFULLADD = b'fulladd'
2963
2977
2964 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2978 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2965
2979
2966 def clone(
2980 def clone(
2967 self,
2981 self,
2968 tr,
2982 tr,
2969 destrevlog,
2983 destrevlog,
2970 addrevisioncb=None,
2984 addrevisioncb=None,
2971 deltareuse=DELTAREUSESAMEREVS,
2985 deltareuse=DELTAREUSESAMEREVS,
2972 forcedeltabothparents=None,
2986 forcedeltabothparents=None,
2973 sidedata_helpers=None,
2987 sidedata_helpers=None,
2974 ):
2988 ):
2975 """Copy this revlog to another, possibly with format changes.
2989 """Copy this revlog to another, possibly with format changes.
2976
2990
2977 The destination revlog will contain the same revisions and nodes.
2991 The destination revlog will contain the same revisions and nodes.
2978 However, it may not be bit-for-bit identical due to e.g. delta encoding
2992 However, it may not be bit-for-bit identical due to e.g. delta encoding
2979 differences.
2993 differences.
2980
2994
2981 The ``deltareuse`` argument control how deltas from the existing revlog
2995 The ``deltareuse`` argument control how deltas from the existing revlog
2982 are preserved in the destination revlog. The argument can have the
2996 are preserved in the destination revlog. The argument can have the
2983 following values:
2997 following values:
2984
2998
2985 DELTAREUSEALWAYS
2999 DELTAREUSEALWAYS
2986 Deltas will always be reused (if possible), even if the destination
3000 Deltas will always be reused (if possible), even if the destination
2987 revlog would not select the same revisions for the delta. This is the
3001 revlog would not select the same revisions for the delta. This is the
2988 fastest mode of operation.
3002 fastest mode of operation.
2989 DELTAREUSESAMEREVS
3003 DELTAREUSESAMEREVS
2990 Deltas will be reused if the destination revlog would pick the same
3004 Deltas will be reused if the destination revlog would pick the same
2991 revisions for the delta. This mode strikes a balance between speed
3005 revisions for the delta. This mode strikes a balance between speed
2992 and optimization.
3006 and optimization.
2993 DELTAREUSENEVER
3007 DELTAREUSENEVER
2994 Deltas will never be reused. This is the slowest mode of execution.
3008 Deltas will never be reused. This is the slowest mode of execution.
2995 This mode can be used to recompute deltas (e.g. if the diff/delta
3009 This mode can be used to recompute deltas (e.g. if the diff/delta
2996 algorithm changes).
3010 algorithm changes).
2997 DELTAREUSEFULLADD
3011 DELTAREUSEFULLADD
2998 Revision will be re-added as if their were new content. This is
3012 Revision will be re-added as if their were new content. This is
2999 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3013 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3000 eg: large file detection and handling.
3014 eg: large file detection and handling.
3001
3015
3002 Delta computation can be slow, so the choice of delta reuse policy can
3016 Delta computation can be slow, so the choice of delta reuse policy can
3003 significantly affect run time.
3017 significantly affect run time.
3004
3018
3005 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3019 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3006 two extremes. Deltas will be reused if they are appropriate. But if the
3020 two extremes. Deltas will be reused if they are appropriate. But if the
3007 delta could choose a better revision, it will do so. This means if you
3021 delta could choose a better revision, it will do so. This means if you
3008 are converting a non-generaldelta revlog to a generaldelta revlog,
3022 are converting a non-generaldelta revlog to a generaldelta revlog,
3009 deltas will be recomputed if the delta's parent isn't a parent of the
3023 deltas will be recomputed if the delta's parent isn't a parent of the
3010 revision.
3024 revision.
3011
3025
3012 In addition to the delta policy, the ``forcedeltabothparents``
3026 In addition to the delta policy, the ``forcedeltabothparents``
3013 argument controls whether to force compute deltas against both parents
3027 argument controls whether to force compute deltas against both parents
3014 for merges. By default, the current default is used.
3028 for merges. By default, the current default is used.
3015
3029
3016 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3030 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3017 `sidedata_helpers`.
3031 `sidedata_helpers`.
3018 """
3032 """
3019 if deltareuse not in self.DELTAREUSEALL:
3033 if deltareuse not in self.DELTAREUSEALL:
3020 raise ValueError(
3034 raise ValueError(
3021 _(b'value for deltareuse invalid: %s') % deltareuse
3035 _(b'value for deltareuse invalid: %s') % deltareuse
3022 )
3036 )
3023
3037
3024 if len(destrevlog):
3038 if len(destrevlog):
3025 raise ValueError(_(b'destination revlog is not empty'))
3039 raise ValueError(_(b'destination revlog is not empty'))
3026
3040
3027 if getattr(self, 'filteredrevs', None):
3041 if getattr(self, 'filteredrevs', None):
3028 raise ValueError(_(b'source revlog has filtered revisions'))
3042 raise ValueError(_(b'source revlog has filtered revisions'))
3029 if getattr(destrevlog, 'filteredrevs', None):
3043 if getattr(destrevlog, 'filteredrevs', None):
3030 raise ValueError(_(b'destination revlog has filtered revisions'))
3044 raise ValueError(_(b'destination revlog has filtered revisions'))
3031
3045
3032 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3046 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3033 # if possible.
3047 # if possible.
3034 oldlazydelta = destrevlog._lazydelta
3048 oldlazydelta = destrevlog._lazydelta
3035 oldlazydeltabase = destrevlog._lazydeltabase
3049 oldlazydeltabase = destrevlog._lazydeltabase
3036 oldamd = destrevlog._deltabothparents
3050 oldamd = destrevlog._deltabothparents
3037
3051
3038 try:
3052 try:
3039 if deltareuse == self.DELTAREUSEALWAYS:
3053 if deltareuse == self.DELTAREUSEALWAYS:
3040 destrevlog._lazydeltabase = True
3054 destrevlog._lazydeltabase = True
3041 destrevlog._lazydelta = True
3055 destrevlog._lazydelta = True
3042 elif deltareuse == self.DELTAREUSESAMEREVS:
3056 elif deltareuse == self.DELTAREUSESAMEREVS:
3043 destrevlog._lazydeltabase = False
3057 destrevlog._lazydeltabase = False
3044 destrevlog._lazydelta = True
3058 destrevlog._lazydelta = True
3045 elif deltareuse == self.DELTAREUSENEVER:
3059 elif deltareuse == self.DELTAREUSENEVER:
3046 destrevlog._lazydeltabase = False
3060 destrevlog._lazydeltabase = False
3047 destrevlog._lazydelta = False
3061 destrevlog._lazydelta = False
3048
3062
3049 destrevlog._deltabothparents = forcedeltabothparents or oldamd
3063 destrevlog._deltabothparents = forcedeltabothparents or oldamd
3050
3064
3051 self._clone(
3065 self._clone(
3052 tr,
3066 tr,
3053 destrevlog,
3067 destrevlog,
3054 addrevisioncb,
3068 addrevisioncb,
3055 deltareuse,
3069 deltareuse,
3056 forcedeltabothparents,
3070 forcedeltabothparents,
3057 sidedata_helpers,
3071 sidedata_helpers,
3058 )
3072 )
3059
3073
3060 finally:
3074 finally:
3061 destrevlog._lazydelta = oldlazydelta
3075 destrevlog._lazydelta = oldlazydelta
3062 destrevlog._lazydeltabase = oldlazydeltabase
3076 destrevlog._lazydeltabase = oldlazydeltabase
3063 destrevlog._deltabothparents = oldamd
3077 destrevlog._deltabothparents = oldamd
3064
3078
3065 def _clone(
3079 def _clone(
3066 self,
3080 self,
3067 tr,
3081 tr,
3068 destrevlog,
3082 destrevlog,
3069 addrevisioncb,
3083 addrevisioncb,
3070 deltareuse,
3084 deltareuse,
3071 forcedeltabothparents,
3085 forcedeltabothparents,
3072 sidedata_helpers,
3086 sidedata_helpers,
3073 ):
3087 ):
3074 """perform the core duty of `revlog.clone` after parameter processing"""
3088 """perform the core duty of `revlog.clone` after parameter processing"""
3075 write_debug = None
3089 write_debug = None
3076 if self._debug_delta:
3090 if self._debug_delta:
3077 write_debug = tr._report
3091 write_debug = tr._report
3078 deltacomputer = deltautil.deltacomputer(
3092 deltacomputer = deltautil.deltacomputer(
3079 destrevlog,
3093 destrevlog,
3080 write_debug=write_debug,
3094 write_debug=write_debug,
3081 )
3095 )
3082 index = self.index
3096 index = self.index
3083 for rev in self:
3097 for rev in self:
3084 entry = index[rev]
3098 entry = index[rev]
3085
3099
3086 # Some classes override linkrev to take filtered revs into
3100 # Some classes override linkrev to take filtered revs into
3087 # account. Use raw entry from index.
3101 # account. Use raw entry from index.
3088 flags = entry[0] & 0xFFFF
3102 flags = entry[0] & 0xFFFF
3089 linkrev = entry[4]
3103 linkrev = entry[4]
3090 p1 = index[entry[5]][7]
3104 p1 = index[entry[5]][7]
3091 p2 = index[entry[6]][7]
3105 p2 = index[entry[6]][7]
3092 node = entry[7]
3106 node = entry[7]
3093
3107
3094 # (Possibly) reuse the delta from the revlog if allowed and
3108 # (Possibly) reuse the delta from the revlog if allowed and
3095 # the revlog chunk is a delta.
3109 # the revlog chunk is a delta.
3096 cachedelta = None
3110 cachedelta = None
3097 rawtext = None
3111 rawtext = None
3098 if deltareuse == self.DELTAREUSEFULLADD:
3112 if deltareuse == self.DELTAREUSEFULLADD:
3099 text = self._revisiondata(rev)
3113 text = self._revisiondata(rev)
3100 sidedata = self.sidedata(rev)
3114 sidedata = self.sidedata(rev)
3101
3115
3102 if sidedata_helpers is not None:
3116 if sidedata_helpers is not None:
3103 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3117 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3104 self, sidedata_helpers, sidedata, rev
3118 self, sidedata_helpers, sidedata, rev
3105 )
3119 )
3106 flags = flags | new_flags[0] & ~new_flags[1]
3120 flags = flags | new_flags[0] & ~new_flags[1]
3107
3121
3108 destrevlog.addrevision(
3122 destrevlog.addrevision(
3109 text,
3123 text,
3110 tr,
3124 tr,
3111 linkrev,
3125 linkrev,
3112 p1,
3126 p1,
3113 p2,
3127 p2,
3114 cachedelta=cachedelta,
3128 cachedelta=cachedelta,
3115 node=node,
3129 node=node,
3116 flags=flags,
3130 flags=flags,
3117 deltacomputer=deltacomputer,
3131 deltacomputer=deltacomputer,
3118 sidedata=sidedata,
3132 sidedata=sidedata,
3119 )
3133 )
3120 else:
3134 else:
3121 if destrevlog._lazydelta:
3135 if destrevlog._lazydelta:
3122 dp = self.deltaparent(rev)
3136 dp = self.deltaparent(rev)
3123 if dp != nullrev:
3137 if dp != nullrev:
3124 cachedelta = (dp, bytes(self._chunk(rev)))
3138 cachedelta = (dp, bytes(self._chunk(rev)))
3125
3139
3126 sidedata = None
3140 sidedata = None
3127 if not cachedelta:
3141 if not cachedelta:
3128 rawtext = self._revisiondata(rev)
3142 rawtext = self._revisiondata(rev)
3129 sidedata = self.sidedata(rev)
3143 sidedata = self.sidedata(rev)
3130 if sidedata is None:
3144 if sidedata is None:
3131 sidedata = self.sidedata(rev)
3145 sidedata = self.sidedata(rev)
3132
3146
3133 if sidedata_helpers is not None:
3147 if sidedata_helpers is not None:
3134 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3148 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3135 self, sidedata_helpers, sidedata, rev
3149 self, sidedata_helpers, sidedata, rev
3136 )
3150 )
3137 flags = flags | new_flags[0] & ~new_flags[1]
3151 flags = flags | new_flags[0] & ~new_flags[1]
3138
3152
3139 with destrevlog._writing(tr):
3153 with destrevlog._writing(tr):
3140 destrevlog._addrevision(
3154 destrevlog._addrevision(
3141 node,
3155 node,
3142 rawtext,
3156 rawtext,
3143 tr,
3157 tr,
3144 linkrev,
3158 linkrev,
3145 p1,
3159 p1,
3146 p2,
3160 p2,
3147 flags,
3161 flags,
3148 cachedelta,
3162 cachedelta,
3149 deltacomputer=deltacomputer,
3163 deltacomputer=deltacomputer,
3150 sidedata=sidedata,
3164 sidedata=sidedata,
3151 )
3165 )
3152
3166
3153 if addrevisioncb:
3167 if addrevisioncb:
3154 addrevisioncb(self, rev, node)
3168 addrevisioncb(self, rev, node)
3155
3169
3156 def censorrevision(self, tr, censornode, tombstone=b''):
3170 def censorrevision(self, tr, censornode, tombstone=b''):
3157 if self._format_version == REVLOGV0:
3171 if self._format_version == REVLOGV0:
3158 raise error.RevlogError(
3172 raise error.RevlogError(
3159 _(b'cannot censor with version %d revlogs')
3173 _(b'cannot censor with version %d revlogs')
3160 % self._format_version
3174 % self._format_version
3161 )
3175 )
3162 elif self._format_version == REVLOGV1:
3176 elif self._format_version == REVLOGV1:
3163 rewrite.v1_censor(self, tr, censornode, tombstone)
3177 rewrite.v1_censor(self, tr, censornode, tombstone)
3164 else:
3178 else:
3165 rewrite.v2_censor(self, tr, censornode, tombstone)
3179 rewrite.v2_censor(self, tr, censornode, tombstone)
3166
3180
3167 def verifyintegrity(self, state):
3181 def verifyintegrity(self, state):
3168 """Verifies the integrity of the revlog.
3182 """Verifies the integrity of the revlog.
3169
3183
3170 Yields ``revlogproblem`` instances describing problems that are
3184 Yields ``revlogproblem`` instances describing problems that are
3171 found.
3185 found.
3172 """
3186 """
3173 dd, di = self.checksize()
3187 dd, di = self.checksize()
3174 if dd:
3188 if dd:
3175 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3189 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3176 if di:
3190 if di:
3177 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3191 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3178
3192
3179 version = self._format_version
3193 version = self._format_version
3180
3194
3181 # The verifier tells us what version revlog we should be.
3195 # The verifier tells us what version revlog we should be.
3182 if version != state[b'expectedversion']:
3196 if version != state[b'expectedversion']:
3183 yield revlogproblem(
3197 yield revlogproblem(
3184 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3198 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3185 % (self.display_id, version, state[b'expectedversion'])
3199 % (self.display_id, version, state[b'expectedversion'])
3186 )
3200 )
3187
3201
3188 state[b'skipread'] = set()
3202 state[b'skipread'] = set()
3189 state[b'safe_renamed'] = set()
3203 state[b'safe_renamed'] = set()
3190
3204
3191 for rev in self:
3205 for rev in self:
3192 node = self.node(rev)
3206 node = self.node(rev)
3193
3207
3194 # Verify contents. 4 cases to care about:
3208 # Verify contents. 4 cases to care about:
3195 #
3209 #
3196 # common: the most common case
3210 # common: the most common case
3197 # rename: with a rename
3211 # rename: with a rename
3198 # meta: file content starts with b'\1\n', the metadata
3212 # meta: file content starts with b'\1\n', the metadata
3199 # header defined in filelog.py, but without a rename
3213 # header defined in filelog.py, but without a rename
3200 # ext: content stored externally
3214 # ext: content stored externally
3201 #
3215 #
3202 # More formally, their differences are shown below:
3216 # More formally, their differences are shown below:
3203 #
3217 #
3204 # | common | rename | meta | ext
3218 # | common | rename | meta | ext
3205 # -------------------------------------------------------
3219 # -------------------------------------------------------
3206 # flags() | 0 | 0 | 0 | not 0
3220 # flags() | 0 | 0 | 0 | not 0
3207 # renamed() | False | True | False | ?
3221 # renamed() | False | True | False | ?
3208 # rawtext[0:2]=='\1\n'| False | True | True | ?
3222 # rawtext[0:2]=='\1\n'| False | True | True | ?
3209 #
3223 #
3210 # "rawtext" means the raw text stored in revlog data, which
3224 # "rawtext" means the raw text stored in revlog data, which
3211 # could be retrieved by "rawdata(rev)". "text"
3225 # could be retrieved by "rawdata(rev)". "text"
3212 # mentioned below is "revision(rev)".
3226 # mentioned below is "revision(rev)".
3213 #
3227 #
3214 # There are 3 different lengths stored physically:
3228 # There are 3 different lengths stored physically:
3215 # 1. L1: rawsize, stored in revlog index
3229 # 1. L1: rawsize, stored in revlog index
3216 # 2. L2: len(rawtext), stored in revlog data
3230 # 2. L2: len(rawtext), stored in revlog data
3217 # 3. L3: len(text), stored in revlog data if flags==0, or
3231 # 3. L3: len(text), stored in revlog data if flags==0, or
3218 # possibly somewhere else if flags!=0
3232 # possibly somewhere else if flags!=0
3219 #
3233 #
3220 # L1 should be equal to L2. L3 could be different from them.
3234 # L1 should be equal to L2. L3 could be different from them.
3221 # "text" may or may not affect commit hash depending on flag
3235 # "text" may or may not affect commit hash depending on flag
3222 # processors (see flagutil.addflagprocessor).
3236 # processors (see flagutil.addflagprocessor).
3223 #
3237 #
3224 # | common | rename | meta | ext
3238 # | common | rename | meta | ext
3225 # -------------------------------------------------
3239 # -------------------------------------------------
3226 # rawsize() | L1 | L1 | L1 | L1
3240 # rawsize() | L1 | L1 | L1 | L1
3227 # size() | L1 | L2-LM | L1(*) | L1 (?)
3241 # size() | L1 | L2-LM | L1(*) | L1 (?)
3228 # len(rawtext) | L2 | L2 | L2 | L2
3242 # len(rawtext) | L2 | L2 | L2 | L2
3229 # len(text) | L2 | L2 | L2 | L3
3243 # len(text) | L2 | L2 | L2 | L3
3230 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3244 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3231 #
3245 #
3232 # LM: length of metadata, depending on rawtext
3246 # LM: length of metadata, depending on rawtext
3233 # (*): not ideal, see comment in filelog.size
3247 # (*): not ideal, see comment in filelog.size
3234 # (?): could be "- len(meta)" if the resolved content has
3248 # (?): could be "- len(meta)" if the resolved content has
3235 # rename metadata
3249 # rename metadata
3236 #
3250 #
3237 # Checks needed to be done:
3251 # Checks needed to be done:
3238 # 1. length check: L1 == L2, in all cases.
3252 # 1. length check: L1 == L2, in all cases.
3239 # 2. hash check: depending on flag processor, we may need to
3253 # 2. hash check: depending on flag processor, we may need to
3240 # use either "text" (external), or "rawtext" (in revlog).
3254 # use either "text" (external), or "rawtext" (in revlog).
3241
3255
3242 try:
3256 try:
3243 skipflags = state.get(b'skipflags', 0)
3257 skipflags = state.get(b'skipflags', 0)
3244 if skipflags:
3258 if skipflags:
3245 skipflags &= self.flags(rev)
3259 skipflags &= self.flags(rev)
3246
3260
3247 _verify_revision(self, skipflags, state, node)
3261 _verify_revision(self, skipflags, state, node)
3248
3262
3249 l1 = self.rawsize(rev)
3263 l1 = self.rawsize(rev)
3250 l2 = len(self.rawdata(node))
3264 l2 = len(self.rawdata(node))
3251
3265
3252 if l1 != l2:
3266 if l1 != l2:
3253 yield revlogproblem(
3267 yield revlogproblem(
3254 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3268 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3255 node=node,
3269 node=node,
3256 )
3270 )
3257
3271
3258 except error.CensoredNodeError:
3272 except error.CensoredNodeError:
3259 if state[b'erroroncensored']:
3273 if state[b'erroroncensored']:
3260 yield revlogproblem(
3274 yield revlogproblem(
3261 error=_(b'censored file data'), node=node
3275 error=_(b'censored file data'), node=node
3262 )
3276 )
3263 state[b'skipread'].add(node)
3277 state[b'skipread'].add(node)
3264 except Exception as e:
3278 except Exception as e:
3265 yield revlogproblem(
3279 yield revlogproblem(
3266 error=_(b'unpacking %s: %s')
3280 error=_(b'unpacking %s: %s')
3267 % (short(node), stringutil.forcebytestr(e)),
3281 % (short(node), stringutil.forcebytestr(e)),
3268 node=node,
3282 node=node,
3269 )
3283 )
3270 state[b'skipread'].add(node)
3284 state[b'skipread'].add(node)
3271
3285
3272 def storageinfo(
3286 def storageinfo(
3273 self,
3287 self,
3274 exclusivefiles=False,
3288 exclusivefiles=False,
3275 sharedfiles=False,
3289 sharedfiles=False,
3276 revisionscount=False,
3290 revisionscount=False,
3277 trackedsize=False,
3291 trackedsize=False,
3278 storedsize=False,
3292 storedsize=False,
3279 ):
3293 ):
3280 d = {}
3294 d = {}
3281
3295
3282 if exclusivefiles:
3296 if exclusivefiles:
3283 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3297 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3284 if not self._inline:
3298 if not self._inline:
3285 d[b'exclusivefiles'].append((self.opener, self._datafile))
3299 d[b'exclusivefiles'].append((self.opener, self._datafile))
3286
3300
3287 if sharedfiles:
3301 if sharedfiles:
3288 d[b'sharedfiles'] = []
3302 d[b'sharedfiles'] = []
3289
3303
3290 if revisionscount:
3304 if revisionscount:
3291 d[b'revisionscount'] = len(self)
3305 d[b'revisionscount'] = len(self)
3292
3306
3293 if trackedsize:
3307 if trackedsize:
3294 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3308 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3295
3309
3296 if storedsize:
3310 if storedsize:
3297 d[b'storedsize'] = sum(
3311 d[b'storedsize'] = sum(
3298 self.opener.stat(path).st_size for path in self.files()
3312 self.opener.stat(path).st_size for path in self.files()
3299 )
3313 )
3300
3314
3301 return d
3315 return d
3302
3316
3303 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3317 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3304 if not self.hassidedata:
3318 if not self.hassidedata:
3305 return
3319 return
3306 # revlog formats with sidedata support does not support inline
3320 # revlog formats with sidedata support does not support inline
3307 assert not self._inline
3321 assert not self._inline
3308 if not helpers[1] and not helpers[2]:
3322 if not helpers[1] and not helpers[2]:
3309 # Nothing to generate or remove
3323 # Nothing to generate or remove
3310 return
3324 return
3311
3325
3312 new_entries = []
3326 new_entries = []
3313 # append the new sidedata
3327 # append the new sidedata
3314 with self._writing(transaction):
3328 with self._writing(transaction):
3315 ifh, dfh, sdfh = self._writinghandles
3329 ifh, dfh, sdfh = self._writinghandles
3316 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3330 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3317
3331
3318 current_offset = sdfh.tell()
3332 current_offset = sdfh.tell()
3319 for rev in range(startrev, endrev + 1):
3333 for rev in range(startrev, endrev + 1):
3320 entry = self.index[rev]
3334 entry = self.index[rev]
3321 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3335 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3322 store=self,
3336 store=self,
3323 sidedata_helpers=helpers,
3337 sidedata_helpers=helpers,
3324 sidedata={},
3338 sidedata={},
3325 rev=rev,
3339 rev=rev,
3326 )
3340 )
3327
3341
3328 serialized_sidedata = sidedatautil.serialize_sidedata(
3342 serialized_sidedata = sidedatautil.serialize_sidedata(
3329 new_sidedata
3343 new_sidedata
3330 )
3344 )
3331
3345
3332 sidedata_compression_mode = COMP_MODE_INLINE
3346 sidedata_compression_mode = COMP_MODE_INLINE
3333 if serialized_sidedata and self.hassidedata:
3347 if serialized_sidedata and self.hassidedata:
3334 sidedata_compression_mode = COMP_MODE_PLAIN
3348 sidedata_compression_mode = COMP_MODE_PLAIN
3335 h, comp_sidedata = self.compress(serialized_sidedata)
3349 h, comp_sidedata = self.compress(serialized_sidedata)
3336 if (
3350 if (
3337 h != b'u'
3351 h != b'u'
3338 and comp_sidedata[0] != b'\0'
3352 and comp_sidedata[0] != b'\0'
3339 and len(comp_sidedata) < len(serialized_sidedata)
3353 and len(comp_sidedata) < len(serialized_sidedata)
3340 ):
3354 ):
3341 assert not h
3355 assert not h
3342 if (
3356 if (
3343 comp_sidedata[0]
3357 comp_sidedata[0]
3344 == self._docket.default_compression_header
3358 == self._docket.default_compression_header
3345 ):
3359 ):
3346 sidedata_compression_mode = COMP_MODE_DEFAULT
3360 sidedata_compression_mode = COMP_MODE_DEFAULT
3347 serialized_sidedata = comp_sidedata
3361 serialized_sidedata = comp_sidedata
3348 else:
3362 else:
3349 sidedata_compression_mode = COMP_MODE_INLINE
3363 sidedata_compression_mode = COMP_MODE_INLINE
3350 serialized_sidedata = comp_sidedata
3364 serialized_sidedata = comp_sidedata
3351 if entry[8] != 0 or entry[9] != 0:
3365 if entry[8] != 0 or entry[9] != 0:
3352 # rewriting entries that already have sidedata is not
3366 # rewriting entries that already have sidedata is not
3353 # supported yet, because it introduces garbage data in the
3367 # supported yet, because it introduces garbage data in the
3354 # revlog.
3368 # revlog.
3355 msg = b"rewriting existing sidedata is not supported yet"
3369 msg = b"rewriting existing sidedata is not supported yet"
3356 raise error.Abort(msg)
3370 raise error.Abort(msg)
3357
3371
3358 # Apply (potential) flags to add and to remove after running
3372 # Apply (potential) flags to add and to remove after running
3359 # the sidedata helpers
3373 # the sidedata helpers
3360 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3374 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3361 entry_update = (
3375 entry_update = (
3362 current_offset,
3376 current_offset,
3363 len(serialized_sidedata),
3377 len(serialized_sidedata),
3364 new_offset_flags,
3378 new_offset_flags,
3365 sidedata_compression_mode,
3379 sidedata_compression_mode,
3366 )
3380 )
3367
3381
3368 # the sidedata computation might have move the file cursors around
3382 # the sidedata computation might have move the file cursors around
3369 sdfh.seek(current_offset, os.SEEK_SET)
3383 sdfh.seek(current_offset, os.SEEK_SET)
3370 sdfh.write(serialized_sidedata)
3384 sdfh.write(serialized_sidedata)
3371 new_entries.append(entry_update)
3385 new_entries.append(entry_update)
3372 current_offset += len(serialized_sidedata)
3386 current_offset += len(serialized_sidedata)
3373 self._docket.sidedata_end = sdfh.tell()
3387 self._docket.sidedata_end = sdfh.tell()
3374
3388
3375 # rewrite the new index entries
3389 # rewrite the new index entries
3376 ifh.seek(startrev * self.index.entry_size)
3390 ifh.seek(startrev * self.index.entry_size)
3377 for i, e in enumerate(new_entries):
3391 for i, e in enumerate(new_entries):
3378 rev = startrev + i
3392 rev = startrev + i
3379 self.index.replace_sidedata_info(rev, *e)
3393 self.index.replace_sidedata_info(rev, *e)
3380 packed = self.index.entry_binary(rev)
3394 packed = self.index.entry_binary(rev)
3381 if rev == 0 and self._docket is None:
3395 if rev == 0 and self._docket is None:
3382 header = self._format_flags | self._format_version
3396 header = self._format_flags | self._format_version
3383 header = self.index.pack_header(header)
3397 header = self.index.pack_header(header)
3384 packed = header + packed
3398 packed = header + packed
3385 ifh.write(packed)
3399 ifh.write(packed)
@@ -1,433 +1,428 b''
1 Test correctness of revlog inline -> non-inline transition
1 Test correctness of revlog inline -> non-inline transition
2 ----------------------------------------------------------
2 ----------------------------------------------------------
3
3
4 Helper extension to intercept renames and kill process
4 Helper extension to intercept renames and kill process
5
5
6 $ cat > $TESTTMP/intercept_before_rename.py << EOF
6 $ cat > $TESTTMP/intercept_before_rename.py << EOF
7 > import os
7 > import os
8 > import signal
8 > import signal
9 > from mercurial import extensions, util
9 > from mercurial import extensions, util
10 >
10 >
11 > def extsetup(ui):
11 > def extsetup(ui):
12 > def close(orig, *args, **kwargs):
12 > def rename(orig, src, dest, *args, **kwargs):
13 > path = util.normpath(args[0]._atomictempfile__name)
13 > path = util.normpath(dest)
14 > if path.endswith(b'/.hg/store/data/file.i'):
14 > if path.endswith(b'data/file.i'):
15 > os.kill(os.getpid(), signal.SIGKILL)
15 > os.kill(os.getpid(), signal.SIGKILL)
16 > return orig(*args, **kwargs)
16 > return orig(src, dest, *args, **kwargs)
17 > extensions.wrapfunction(util.atomictempfile, 'close', close)
17 > extensions.wrapfunction(util, 'rename', rename)
18 > EOF
18 > EOF
19
19
20 $ cat > $TESTTMP/intercept_after_rename.py << EOF
20 $ cat > $TESTTMP/intercept_after_rename.py << EOF
21 > import os
21 > import os
22 > import signal
22 > import signal
23 > from mercurial import extensions, util
23 > from mercurial import extensions, util
24 >
24 >
25 > def extsetup(ui):
25 > def extsetup(ui):
26 > def close(orig, *args, **kwargs):
26 > def close(orig, *args, **kwargs):
27 > path = util.normpath(args[0]._atomictempfile__name)
27 > path = util.normpath(args[0]._atomictempfile__name)
28 > r = orig(*args, **kwargs)
28 > r = orig(*args, **kwargs)
29 > if path.endswith(b'/.hg/store/data/file.i'):
29 > if path.endswith(b'/.hg/store/data/file.i'):
30 > os.kill(os.getpid(), signal.SIGKILL)
30 > os.kill(os.getpid(), signal.SIGKILL)
31 > return r
31 > return r
32 > extensions.wrapfunction(util.atomictempfile, 'close', close)
32 > extensions.wrapfunction(util.atomictempfile, 'close', close)
33 > def extsetup(ui):
34 > def rename(orig, src, dest, *args, **kwargs):
35 > path = util.normpath(dest)
36 > r = orig(src, dest, *args, **kwargs)
37 > if path.endswith(b'data/file.i'):
38 > os.kill(os.getpid(), signal.SIGKILL)
39 > return r
40 > extensions.wrapfunction(util, 'rename', rename)
33 > EOF
41 > EOF
34
42
35 $ cat > $TESTTMP/killme.py << EOF
43 $ cat > $TESTTMP/killme.py << EOF
36 > import os
44 > import os
37 > import signal
45 > import signal
38 >
46 >
39 > def killme(ui, repo, hooktype, **kwargs):
47 > def killme(ui, repo, hooktype, **kwargs):
40 > os.kill(os.getpid(), signal.SIGKILL)
48 > os.kill(os.getpid(), signal.SIGKILL)
41 > EOF
49 > EOF
42
50
43 $ cat > $TESTTMP/reader_wait_split.py << EOF
51 $ cat > $TESTTMP/reader_wait_split.py << EOF
44 > import os
52 > import os
45 > import signal
53 > import signal
46 > from mercurial import extensions, revlog, testing
54 > from mercurial import extensions, revlog, testing
47 > def _wait_post_load(orig, self, *args, **kwargs):
55 > def _wait_post_load(orig, self, *args, **kwargs):
48 > wait = b'data/file' in self.radix
56 > wait = b'data/file' in self.radix
49 > if wait:
57 > if wait:
50 > testing.wait_file(b"$TESTTMP/writer-revlog-split")
58 > testing.wait_file(b"$TESTTMP/writer-revlog-split")
51 > r = orig(self, *args, **kwargs)
59 > r = orig(self, *args, **kwargs)
52 > if wait:
60 > if wait:
53 > testing.write_file(b"$TESTTMP/reader-index-read")
61 > testing.write_file(b"$TESTTMP/reader-index-read")
54 > testing.wait_file(b"$TESTTMP/writer-revlog-unsplit")
62 > testing.wait_file(b"$TESTTMP/writer-revlog-unsplit")
55 > return r
63 > return r
56 >
64 >
57 > def extsetup(ui):
65 > def extsetup(ui):
58 > extensions.wrapfunction(revlog.revlog, '_loadindex', _wait_post_load)
66 > extensions.wrapfunction(revlog.revlog, '_loadindex', _wait_post_load)
59 > EOF
67 > EOF
60
68
61 setup a repository for tests
69 setup a repository for tests
62 ----------------------------
70 ----------------------------
63
71
64 $ cat >> $HGRCPATH << EOF
72 $ cat >> $HGRCPATH << EOF
65 > [format]
73 > [format]
66 > revlog-compression=none
74 > revlog-compression=none
67 > EOF
75 > EOF
68
76
69 $ hg init troffset-computation
77 $ hg init troffset-computation
70 $ cd troffset-computation
78 $ cd troffset-computation
71 $ printf '%20d' '1' > file
79 $ printf '%20d' '1' > file
72 $ hg commit -Aqma
80 $ hg commit -Aqma
73 $ printf '%1024d' '1' > file
81 $ printf '%1024d' '1' > file
74 $ hg commit -Aqmb
82 $ hg commit -Aqmb
75 $ printf '%20d' '1' > file
83 $ printf '%20d' '1' > file
76 $ hg commit -Aqmc
84 $ hg commit -Aqmc
77 $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
85 $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
78 $ hg commit -AqmD
86 $ hg commit -AqmD --traceback
79
87
80 Reference size:
88 Reference size:
81 $ f -s file
89 $ f -s file
82 file: size=131072
90 file: size=131072
83 $ f -s .hg/store/data/file*
91 $ f -s .hg/store/data/file*
84 .hg/store/data/file.d: size=132139
92 .hg/store/data/file.d: size=132139
85 .hg/store/data/file.i: size=256
93 .hg/store/data/file.i: size=256
86
94
87 $ cd ..
95 $ cd ..
88
96
89
97
90 Test a hard crash after the file was split but before the transaction was committed
98 Test a hard crash after the file was split but before the transaction was committed
91 ===================================================================================
99 ===================================================================================
92
100
93 Test offset computation to correctly factor in the index entries themselves.
101 Test offset computation to correctly factor in the index entries themselves.
94 Also test that the new data size has the correct size if the transaction is aborted
102 Also test that the new data size has the correct size if the transaction is aborted
95 after the index has been replaced.
103 after the index has been replaced.
96
104
97 Test repo has commits a, b, c, D, where D is large (grows the revlog enough that it
105 Test repo has commits a, b, c, D, where D is large (grows the revlog enough that it
98 transitions to non-inline storage). The clone initially has changes a, b
106 transitions to non-inline storage). The clone initially has changes a, b
99 and will transition to non-inline storage when adding c, D.
107 and will transition to non-inline storage when adding c, D.
100
108
101 If the transaction adding c, D is rolled back, then we don't undo the revlog split,
109 If the transaction adding c, D is rolled back, then we don't undo the revlog split,
102 but truncate the index and the data to remove both c and D.
110 but truncate the index and the data to remove both c and D.
103
111
104
112
105 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy
113 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy
106 $ cd troffset-computation-copy
114 $ cd troffset-computation-copy
107
115
108 Reference size:
116 Reference size:
109 $ f -s file
117 $ f -s file
110 file: size=1024
118 file: size=1024
111 $ f -s .hg/store/data/file*
119 $ f -s .hg/store/data/file*
112 .hg/store/data/file.i: size=1174
120 .hg/store/data/file.i: size=1174
113
121
114 $ cat > .hg/hgrc <<EOF
122 $ cat > .hg/hgrc <<EOF
115 > [hooks]
123 > [hooks]
116 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
124 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
117 > EOF
125 > EOF
118 #if chg
126 #if chg
119 $ hg pull ../troffset-computation
127 $ hg pull ../troffset-computation
120 pulling from ../troffset-computation
128 pulling from ../troffset-computation
121 [255]
129 [255]
122 #else
130 #else
123 $ hg pull ../troffset-computation
131 $ hg pull ../troffset-computation
124 pulling from ../troffset-computation
132 pulling from ../troffset-computation
125 Killed
133 Killed
126 [137]
134 [137]
127 #endif
135 #endif
128
136
129
137
130 The revlog have been split on disk
138 The inline revlog still exist, but a split version exist next to it
131
139
132 $ f -s .hg/store/data/file*
140 $ f -s .hg/store/data/file*
133 .hg/store/data/file.d: size=132139
141 .hg/store/data/file.d: size=132139
134 .hg/store/data/file.i: size=256
142 .hg/store/data/file.i: size=132395
143 .hg/store/data/file.i.s: size=256
135
144
136 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file | tail -1
137 data/file.i 128
138
145
139 The first file.i entry should match the "Reference size" above.
146 The first file.i entry should match the "Reference size" above.
140 The first file.d entry is the temporary record during the split,
147 The first file.d entry is the temporary record during the split,
141
148
142 The second entry after the split happened. The sum of the second file.d
149 A "temporary file" entry exist for the split index.
143 and the second file.i entry should match the first file.i entry.
144
150
145 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
151 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
146 data/file.i 1174
152 data/file.i 1174
147 data/file.d 0
153 data/file.d 0
148 data/file.d 1046
154 $ cat .hg/store/journal.backupfiles | tr -s '\000' ' ' | tr -s '\00' ' '| grep data/file
149 data/file.i 128
155 data/file.i data/journal.backup.file.i 0
156 data/file.i.s 0
157
158 recover is rolling the split back, the fncache is still valid
159
150 $ hg recover
160 $ hg recover
151 rolling back interrupted transaction
161 rolling back interrupted transaction
152 (verify step skipped, run `hg verify` to check your repository content)
162 (verify step skipped, run `hg verify` to check your repository content)
153 $ f -s .hg/store/data/file*
163 $ f -s .hg/store/data/file*
154 .hg/store/data/file.d: size=1046
164 .hg/store/data/file.i: size=1174
155 .hg/store/data/file.i: size=128
156 $ hg tip
165 $ hg tip
157 changeset: 1:cfa8d6e60429
166 changeset: 1:cfa8d6e60429
158 tag: tip
167 tag: tip
159 user: test
168 user: test
160 date: Thu Jan 01 00:00:00 1970 +0000
169 date: Thu Jan 01 00:00:00 1970 +0000
161 summary: b
170 summary: b
162
171
163 $ hg verify -q
172 $ hg verify -q
164 warning: revlog 'data/file.d' not in fncache!
165 1 warnings encountered!
166 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
167 $ hg debugrebuildfncache --only-data
173 $ hg debugrebuildfncache --only-data
168 adding data/file.d
174 fncache already up to date
169 1 items added, 0 removed from fncache
170 $ hg verify -q
175 $ hg verify -q
171 $ cd ..
176 $ cd ..
172
177
173 Test a hard crash right before the index is move into place
178 Test a hard crash right before the index is move into place
174 ===========================================================
179 ===========================================================
175
180
176 Now retry the procedure but intercept the rename of the index and check that
181 Now retry the procedure but intercept the rename of the index and check that
177 the journal does not contain the new index size. This demonstrates the edge case
182 the journal does not contain the new index size. This demonstrates the edge case
178 where the data file is left as garbage.
183 where the data file is left as garbage.
179
184
180 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy2
185 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy2
181 $ cd troffset-computation-copy2
186 $ cd troffset-computation-copy2
182
187
183 Reference size:
188 Reference size:
184 $ f -s file
189 $ f -s file
185 file: size=1024
190 file: size=1024
186 $ f -s .hg/store/data/file*
191 $ f -s .hg/store/data/file*
187 .hg/store/data/file.i: size=1174
192 .hg/store/data/file.i: size=1174
188
193
189 $ cat > .hg/hgrc <<EOF
194 $ cat > .hg/hgrc <<EOF
190 > [extensions]
195 > [extensions]
191 > intercept_rename = $TESTTMP/intercept_before_rename.py
196 > intercept_rename = $TESTTMP/intercept_before_rename.py
192 > [hooks]
193 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
194 > EOF
197 > EOF
195 #if chg
198 #if chg
196 $ hg pull ../troffset-computation
199 $ hg pull ../troffset-computation
197 pulling from ../troffset-computation
200 pulling from ../troffset-computation
201 searching for changes
202 adding changesets
203 adding manifests
204 adding file changes
198 [255]
205 [255]
199 #else
206 #else
200 $ hg pull ../troffset-computation
207 $ hg pull ../troffset-computation
201 pulling from ../troffset-computation
208 pulling from ../troffset-computation
209 searching for changes
210 adding changesets
211 adding manifests
212 adding file changes
202 Killed
213 Killed
203 [137]
214 [137]
204 #endif
215 #endif
205
216
206 The data file is created, but the revlog is still inline
217 The inline revlog still exist, but a split version exist next to it
207
218
208 $ f -s .hg/store/data/file*
219 $ f -s .hg/store/data/file*
209 .hg/store/data/file.d: size=132139
220 .hg/store/data/file.d: size=132139
210 .hg/store/data/file.i: size=132395
221 .hg/store/data/file.i: size=132395
222 .hg/store/data/file.i.s: size=256
211
223
212 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
224 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
213 data/file.i 1174
225 data/file.i 1174
214 data/file.d 0
226 data/file.d 0
215 data/file.d 1046
227
228 recover is rolling the split back, the fncache is still valid
216
229
217 $ hg recover
230 $ hg recover
218 rolling back interrupted transaction
231 rolling back interrupted transaction
219 (verify step skipped, run `hg verify` to check your repository content)
232 (verify step skipped, run `hg verify` to check your repository content)
220 $ f -s .hg/store/data/file*
233 $ f -s .hg/store/data/file*
221 .hg/store/data/file.d: size=1046
222 .hg/store/data/file.i: size=1174
234 .hg/store/data/file.i: size=1174
223 $ hg tip
235 $ hg tip
224 changeset: 1:cfa8d6e60429
236 changeset: 1:cfa8d6e60429
225 tag: tip
237 tag: tip
226 user: test
238 user: test
227 date: Thu Jan 01 00:00:00 1970 +0000
239 date: Thu Jan 01 00:00:00 1970 +0000
228 summary: b
240 summary: b
229
241
230 $ hg verify -q
242 $ hg verify -q
231 $ cd ..
243 $ cd ..
232
244
233 Test a hard crash right after the index is move into place
245 Test a hard crash right after the index is move into place
234 ===========================================================
246 ===========================================================
235
247
236 Now retry the procedure but intercept the rename of the index.
248 Now retry the procedure but intercept the rename of the index.
237
249
238 Things get corrupted /o\
239
240 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-crash-after-rename
250 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-crash-after-rename
241 $ cd troffset-computation-crash-after-rename
251 $ cd troffset-computation-crash-after-rename
242
252
243 Reference size:
253 Reference size:
244 $ f -s file
254 $ f -s file
245 file: size=1024
255 file: size=1024
246 $ f -s .hg/store/data/file*
256 $ f -s .hg/store/data/file*
247 .hg/store/data/file.i: size=1174
257 .hg/store/data/file.i: size=1174
248
258
249 $ cat > .hg/hgrc <<EOF
259 $ cat > .hg/hgrc <<EOF
250 > [extensions]
260 > [extensions]
251 > intercept_rename = $TESTTMP/intercept_after_rename.py
261 > intercept_rename = $TESTTMP/intercept_after_rename.py
252 > [hooks]
253 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
254 > EOF
262 > EOF
255 #if chg
263 #if chg
256 $ hg pull ../troffset-computation
264 $ hg pull ../troffset-computation
257 pulling from ../troffset-computation
265 pulling from ../troffset-computation
266 searching for changes
267 adding changesets
268 adding manifests
269 adding file changes
258 [255]
270 [255]
259 #else
271 #else
260 $ hg pull ../troffset-computation
272 $ hg pull ../troffset-computation
261 pulling from ../troffset-computation
273 pulling from ../troffset-computation
274 searching for changes
275 adding changesets
276 adding manifests
277 adding file changes
262 Killed
278 Killed
263 [137]
279 [137]
264 #endif
280 #endif
265
281
266 the revlog has been split on disk
282 The inline revlog was over written on disk
267
283
268 $ f -s .hg/store/data/file*
284 $ f -s .hg/store/data/file*
269 .hg/store/data/file.d: size=132139
285 .hg/store/data/file.d: size=132139
270 .hg/store/data/file.i: size=256
286 .hg/store/data/file.i: size=256
271
287
272 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
288 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
273 data/file.i 1174
289 data/file.i 1174
274 data/file.d 0
290 data/file.d 0
275 data/file.d 1046
291
292 recover is rolling the split back, the fncache is still valid
276
293
277 $ hg recover
294 $ hg recover
278 rolling back interrupted transaction
295 rolling back interrupted transaction
279 abort: attempted to truncate data/file.i to 1174 bytes, but it was already 256 bytes
296 (verify step skipped, run `hg verify` to check your repository content)
280
281 [255]
282 $ f -s .hg/store/data/file*
297 $ f -s .hg/store/data/file*
283 .hg/store/data/file.d: size=1046
298 .hg/store/data/file.i: size=1174
284 .hg/store/data/file.i: size=256
285 $ hg tip
299 $ hg tip
286 changeset: 1:cfa8d6e60429
300 changeset: 1:cfa8d6e60429
287 tag: tip
301 tag: tip
288 user: test
302 user: test
289 date: Thu Jan 01 00:00:00 1970 +0000
303 date: Thu Jan 01 00:00:00 1970 +0000
290 summary: b
304 summary: b
291
305
292 $ hg verify -q
306 $ hg verify -q
293 abandoned transaction found - run hg recover
294 warning: revlog 'data/file.d' not in fncache!
295 file@0: data length off by -131093 bytes
296 file@2: unpacking fa1120531cc1: partial read of revlog data/file.d; expected 21 bytes from offset 1046, got 0
297 file@3: unpacking a631378adaa3: partial read of revlog data/file.d; expected 131072 bytes from offset 1067, got -21
298 file@?: rev 2 points to nonexistent changeset 2
299 (expected )
300 file@?: fa1120531cc1 not in manifests
301 file@?: rev 3 points to nonexistent changeset 3
302 (expected )
303 file@?: a631378adaa3 not in manifests
304 not checking dirstate because of previous errors
305 3 warnings encountered!
306 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
307 7 integrity errors encountered!
308 (first damaged changeset appears to be 0)
309 [1]
310 $ cd ..
307 $ cd ..
311
308
312 Have the transaction rollback itself without any hard crash
309 Have the transaction rollback itself without any hard crash
313 ===========================================================
310 ===========================================================
314
311
315
312
316 Repeat the original test but let hg rollback the transaction.
313 Repeat the original test but let hg rollback the transaction.
317
314
318 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy-rb
315 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy-rb
319 $ cd troffset-computation-copy-rb
316 $ cd troffset-computation-copy-rb
320 $ cat > .hg/hgrc <<EOF
317 $ cat > .hg/hgrc <<EOF
321 > [hooks]
318 > [hooks]
322 > pretxnchangegroup = false
319 > pretxnchangegroup = false
323 > EOF
320 > EOF
324 $ hg pull ../troffset-computation
321 $ hg pull ../troffset-computation
325 pulling from ../troffset-computation
322 pulling from ../troffset-computation
326 searching for changes
323 searching for changes
327 adding changesets
324 adding changesets
328 adding manifests
325 adding manifests
329 adding file changes
326 adding file changes
330 transaction abort!
327 transaction abort!
331 rollback completed
328 rollback completed
332 abort: pretxnchangegroup hook exited with status 1
329 abort: pretxnchangegroup hook exited with status 1
333 [40]
330 [40]
334
331
335 File are still split on disk, with the expected size.
332 The split was rollback
336
333
337 $ f -s .hg/store/data/file*
334 $ f -s .hg/store/data/file*
338 .hg/store/data/file.d: size=1046
335 .hg/store/data/file.d: size=0
339 .hg/store/data/file.i: size=128
336 .hg/store/data/file.i: size=1174
337
340
338
341 $ hg tip
339 $ hg tip
342 changeset: 1:cfa8d6e60429
340 changeset: 1:cfa8d6e60429
343 tag: tip
341 tag: tip
344 user: test
342 user: test
345 date: Thu Jan 01 00:00:00 1970 +0000
343 date: Thu Jan 01 00:00:00 1970 +0000
346 summary: b
344 summary: b
347
345
348 $ hg verify -q
346 $ hg verify -q
349 warning: revlog 'data/file.d' not in fncache!
350 1 warnings encountered!
351 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
352 $ cd ..
347 $ cd ..
353
348
354 Read race
349 Read race
355 =========
350 =========
356
351
357 We check that a client that started reading a revlog (its index) after the
352 We check that a client that started reading a revlog (its index) after the
358 split and end reading (the data) after the rollback should be fine
353 split and end reading (the data) after the rollback should be fine
359
354
360 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-race
355 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-race
361 $ cd troffset-computation-race
356 $ cd troffset-computation-race
362 $ cat > .hg/hgrc <<EOF
357 $ cat > .hg/hgrc <<EOF
363 > [hooks]
358 > [hooks]
364 > pretxnchangegroup=$RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/reader-index-read $TESTTMP/writer-revlog-split
359 > pretxnchangegroup=$RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/reader-index-read $TESTTMP/writer-revlog-split
365 > pretxnclose = false
360 > pretxnclose = false
366 > EOF
361 > EOF
367
362
368 start a reader
363 start a reader
369
364
370 $ hg cat --rev 0 file \
365 $ hg cat --rev 0 file \
371 > --config "extensions.wait_read=$TESTTMP/reader_wait_split.py" \
366 > --config "extensions.wait_read=$TESTTMP/reader_wait_split.py" \
372 > 2> $TESTTMP/reader.stderr \
367 > 2> $TESTTMP/reader.stderr \
373 > > $TESTTMP/reader.stdout &
368 > > $TESTTMP/reader.stdout &
374
369
375 Do a failed pull in //
370 Do a failed pull in //
376
371
377 $ hg pull ../troffset-computation
372 $ hg pull ../troffset-computation
378 pulling from ../troffset-computation
373 pulling from ../troffset-computation
379 searching for changes
374 searching for changes
380 adding changesets
375 adding changesets
381 adding manifests
376 adding manifests
382 adding file changes
377 adding file changes
383 transaction abort!
378 transaction abort!
384 rollback completed
379 rollback completed
385 abort: pretxnclose hook exited with status 1
380 abort: pretxnclose hook exited with status 1
386 [40]
381 [40]
387 $ touch $TESTTMP/writer-revlog-unsplit
382 $ touch $TESTTMP/writer-revlog-unsplit
388 $ wait
383 $ wait
389
384
390 The reader should be fine
385 The reader should be fine
391 $ cat $TESTTMP/reader.stderr
386 $ cat $TESTTMP/reader.stderr
392 $ cat $TESTTMP/reader.stdout
387 $ cat $TESTTMP/reader.stdout
393 1 (no-eol)
388 1 (no-eol)
394 $ cd ..
389 $ cd ..
395
390
396 pending hooks
391 pending hooks
397 =============
392 =============
398
393
399 We checks that hooks properly see the inside of the transaction, while other process don't.
394 We checks that hooks properly see the inside of the transaction, while other process don't.
400
395
401 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-hooks
396 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-hooks
402 $ cd troffset-computation-hooks
397 $ cd troffset-computation-hooks
403 $ cat > .hg/hgrc <<EOF
398 $ cat > .hg/hgrc <<EOF
404 > [hooks]
399 > [hooks]
405 > pretxnclose.01-echo = hg cat -r 'max(all())' file | f --size
400 > pretxnclose.01-echo = hg cat -r 'max(all())' file | f --size
406 > pretxnclose.02-echo = $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/hook-done $TESTTMP/hook-tr-ready
401 > pretxnclose.02-echo = $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/hook-done $TESTTMP/hook-tr-ready
407 > pretxnclose.03-abort = false
402 > pretxnclose.03-abort = false
408 > EOF
403 > EOF
409
404
410 $ (
405 $ (
411 > $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/hook-tr-ready;\
406 > $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/hook-tr-ready;\
412 > hg cat -r 'max(all())' file | f --size;\
407 > hg cat -r 'max(all())' file | f --size;\
413 > touch $TESTTMP/hook-done
408 > touch $TESTTMP/hook-done
414 > ) >stdout 2>stderr &
409 > ) >stdout 2>stderr &
415
410
416 $ hg pull ../troffset-computation
411 $ hg pull ../troffset-computation
417 pulling from ../troffset-computation
412 pulling from ../troffset-computation
418 searching for changes
413 searching for changes
419 adding changesets
414 adding changesets
420 adding manifests
415 adding manifests
421 adding file changes
416 adding file changes
422 size=131072
417 size=131072
423 transaction abort!
418 transaction abort!
424 rollback completed
419 rollback completed
425 abort: pretxnclose.03-abort hook exited with status 1
420 abort: pretxnclose.03-abort hook exited with status 1
426 [40]
421 [40]
427
422
428 $ cat stdout
423 $ cat stdout
429 size=1024
424 size=1024
430 $ cat stderr
425 $ cat stderr
431
426
432
427
433 $ cd ..
428 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now