##// END OF EJS Templates
flagutil: move the `flagprocessors` mapping in the new module...
marmoute -
r42955:05c80f9e default
parent child Browse files
Show More
@@ -1,2704 +1,2700
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
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 nullhex,
28 nullhex,
29 nullid,
29 nullid,
30 nullrev,
30 nullrev,
31 short,
31 short,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .revlogutils.constants import (
38 from .revlogutils.constants import (
39 FLAG_GENERALDELTA,
39 FLAG_GENERALDELTA,
40 FLAG_INLINE_DATA,
40 FLAG_INLINE_DATA,
41 REVLOGV0,
41 REVLOGV0,
42 REVLOGV1,
42 REVLOGV1,
43 REVLOGV1_FLAGS,
43 REVLOGV1_FLAGS,
44 REVLOGV2,
44 REVLOGV2,
45 REVLOGV2_FLAGS,
45 REVLOGV2_FLAGS,
46 REVLOG_DEFAULT_FLAGS,
46 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FORMAT,
47 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_VERSION,
48 REVLOG_DEFAULT_VERSION,
49 )
49 )
50 from .revlogutils.flagutil import (
50 from .revlogutils.flagutil import (
51 REVIDX_DEFAULT_FLAGS,
51 REVIDX_DEFAULT_FLAGS,
52 REVIDX_ELLIPSIS,
52 REVIDX_ELLIPSIS,
53 REVIDX_EXTSTORED,
53 REVIDX_EXTSTORED,
54 REVIDX_FLAGS_ORDER,
54 REVIDX_FLAGS_ORDER,
55 REVIDX_ISCENSORED,
55 REVIDX_ISCENSORED,
56 REVIDX_KNOWN_FLAGS,
56 REVIDX_KNOWN_FLAGS,
57 REVIDX_RAWTEXT_CHANGING_FLAGS,
57 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 )
58 )
59 from .thirdparty import (
59 from .thirdparty import (
60 attr,
60 attr,
61 )
61 )
62 from . import (
62 from . import (
63 ancestor,
63 ancestor,
64 dagop,
64 dagop,
65 error,
65 error,
66 mdiff,
66 mdiff,
67 policy,
67 policy,
68 pycompat,
68 pycompat,
69 repository,
69 repository,
70 templatefilters,
70 templatefilters,
71 util,
71 util,
72 )
72 )
73 from .revlogutils import (
73 from .revlogutils import (
74 deltas as deltautil,
74 deltas as deltautil,
75 flagutil,
75 )
76 )
76 from .utils import (
77 from .utils import (
77 interfaceutil,
78 interfaceutil,
78 storageutil,
79 storageutil,
79 stringutil,
80 stringutil,
80 )
81 )
81
82
82 # blanked usage of all the name to prevent pyflakes constraints
83 # blanked usage of all the name to prevent pyflakes constraints
83 # We need these name available in the module for extensions.
84 # We need these name available in the module for extensions.
84 REVLOGV0
85 REVLOGV0
85 REVLOGV1
86 REVLOGV1
86 REVLOGV2
87 REVLOGV2
87 FLAG_INLINE_DATA
88 FLAG_INLINE_DATA
88 FLAG_GENERALDELTA
89 FLAG_GENERALDELTA
89 REVLOG_DEFAULT_FLAGS
90 REVLOG_DEFAULT_FLAGS
90 REVLOG_DEFAULT_FORMAT
91 REVLOG_DEFAULT_FORMAT
91 REVLOG_DEFAULT_VERSION
92 REVLOG_DEFAULT_VERSION
92 REVLOGV1_FLAGS
93 REVLOGV1_FLAGS
93 REVLOGV2_FLAGS
94 REVLOGV2_FLAGS
94 REVIDX_ISCENSORED
95 REVIDX_ISCENSORED
95 REVIDX_ELLIPSIS
96 REVIDX_ELLIPSIS
96 REVIDX_EXTSTORED
97 REVIDX_EXTSTORED
97 REVIDX_DEFAULT_FLAGS
98 REVIDX_DEFAULT_FLAGS
98 REVIDX_FLAGS_ORDER
99 REVIDX_FLAGS_ORDER
99 REVIDX_KNOWN_FLAGS
100 REVIDX_KNOWN_FLAGS
100 REVIDX_RAWTEXT_CHANGING_FLAGS
101 REVIDX_RAWTEXT_CHANGING_FLAGS
101
102
102 parsers = policy.importmod(r'parsers')
103 parsers = policy.importmod(r'parsers')
103 rustancestor = policy.importrust(r'ancestor')
104 rustancestor = policy.importrust(r'ancestor')
104 rustdagop = policy.importrust(r'dagop')
105 rustdagop = policy.importrust(r'dagop')
105
106
106 # Aliased for performance.
107 # Aliased for performance.
107 _zlibdecompress = zlib.decompress
108 _zlibdecompress = zlib.decompress
108
109
109 # max size of revlog with inline data
110 # max size of revlog with inline data
110 _maxinline = 131072
111 _maxinline = 131072
111 _chunksize = 1048576
112 _chunksize = 1048576
112
113
113 # Store flag processors (cf. 'addflagprocessor()' to register)
114 _flagprocessors = {
115 REVIDX_ISCENSORED: None,
116 }
117
118 # Flag processors for REVIDX_ELLIPSIS.
114 # Flag processors for REVIDX_ELLIPSIS.
119 def ellipsisreadprocessor(rl, text):
115 def ellipsisreadprocessor(rl, text):
120 return text, False
116 return text, False
121
117
122 def ellipsiswriteprocessor(rl, text):
118 def ellipsiswriteprocessor(rl, text):
123 return text, False
119 return text, False
124
120
125 def ellipsisrawprocessor(rl, text):
121 def ellipsisrawprocessor(rl, text):
126 return False
122 return False
127
123
128 ellipsisprocessor = (
124 ellipsisprocessor = (
129 ellipsisreadprocessor,
125 ellipsisreadprocessor,
130 ellipsiswriteprocessor,
126 ellipsiswriteprocessor,
131 ellipsisrawprocessor,
127 ellipsisrawprocessor,
132 )
128 )
133
129
134 def addflagprocessor(flag, processor):
130 def addflagprocessor(flag, processor):
135 """Register a flag processor on a revision data flag.
131 """Register a flag processor on a revision data flag.
136
132
137 Invariant:
133 Invariant:
138 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
134 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
139 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
135 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
140 - Only one flag processor can be registered on a specific flag.
136 - Only one flag processor can be registered on a specific flag.
141 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
137 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
142 following signatures:
138 following signatures:
143 - (read) f(self, rawtext) -> text, bool
139 - (read) f(self, rawtext) -> text, bool
144 - (write) f(self, text) -> rawtext, bool
140 - (write) f(self, text) -> rawtext, bool
145 - (raw) f(self, rawtext) -> bool
141 - (raw) f(self, rawtext) -> bool
146 "text" is presented to the user. "rawtext" is stored in revlog data, not
142 "text" is presented to the user. "rawtext" is stored in revlog data, not
147 directly visible to the user.
143 directly visible to the user.
148 The boolean returned by these transforms is used to determine whether
144 The boolean returned by these transforms is used to determine whether
149 the returned text can be used for hash integrity checking. For example,
145 the returned text can be used for hash integrity checking. For example,
150 if "write" returns False, then "text" is used to generate hash. If
146 if "write" returns False, then "text" is used to generate hash. If
151 "write" returns True, that basically means "rawtext" returned by "write"
147 "write" returns True, that basically means "rawtext" returned by "write"
152 should be used to generate hash. Usually, "write" and "read" return
148 should be used to generate hash. Usually, "write" and "read" return
153 different booleans. And "raw" returns a same boolean as "write".
149 different booleans. And "raw" returns a same boolean as "write".
154
150
155 Note: The 'raw' transform is used for changegroup generation and in some
151 Note: The 'raw' transform is used for changegroup generation and in some
156 debug commands. In this case the transform only indicates whether the
152 debug commands. In this case the transform only indicates whether the
157 contents can be used for hash integrity checks.
153 contents can be used for hash integrity checks.
158 """
154 """
159 _insertflagprocessor(flag, processor, _flagprocessors)
155 _insertflagprocessor(flag, processor, flagutil.flagprocessors)
160
156
161 def _insertflagprocessor(flag, processor, flagprocessors):
157 def _insertflagprocessor(flag, processor, flagprocessors):
162 if not flag & REVIDX_KNOWN_FLAGS:
158 if not flag & REVIDX_KNOWN_FLAGS:
163 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
159 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
164 raise error.ProgrammingError(msg)
160 raise error.ProgrammingError(msg)
165 if flag not in REVIDX_FLAGS_ORDER:
161 if flag not in REVIDX_FLAGS_ORDER:
166 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
162 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
167 raise error.ProgrammingError(msg)
163 raise error.ProgrammingError(msg)
168 if flag in flagprocessors:
164 if flag in flagprocessors:
169 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
165 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
170 raise error.Abort(msg)
166 raise error.Abort(msg)
171 flagprocessors[flag] = processor
167 flagprocessors[flag] = processor
172
168
173 def getoffset(q):
169 def getoffset(q):
174 return int(q >> 16)
170 return int(q >> 16)
175
171
176 def gettype(q):
172 def gettype(q):
177 return int(q & 0xFFFF)
173 return int(q & 0xFFFF)
178
174
179 def offset_type(offset, type):
175 def offset_type(offset, type):
180 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
176 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
181 raise ValueError('unknown revlog index flags')
177 raise ValueError('unknown revlog index flags')
182 return int(int(offset) << 16 | type)
178 return int(int(offset) << 16 | type)
183
179
184 @attr.s(slots=True, frozen=True)
180 @attr.s(slots=True, frozen=True)
185 class _revisioninfo(object):
181 class _revisioninfo(object):
186 """Information about a revision that allows building its fulltext
182 """Information about a revision that allows building its fulltext
187 node: expected hash of the revision
183 node: expected hash of the revision
188 p1, p2: parent revs of the revision
184 p1, p2: parent revs of the revision
189 btext: built text cache consisting of a one-element list
185 btext: built text cache consisting of a one-element list
190 cachedelta: (baserev, uncompressed_delta) or None
186 cachedelta: (baserev, uncompressed_delta) or None
191 flags: flags associated to the revision storage
187 flags: flags associated to the revision storage
192
188
193 One of btext[0] or cachedelta must be set.
189 One of btext[0] or cachedelta must be set.
194 """
190 """
195 node = attr.ib()
191 node = attr.ib()
196 p1 = attr.ib()
192 p1 = attr.ib()
197 p2 = attr.ib()
193 p2 = attr.ib()
198 btext = attr.ib()
194 btext = attr.ib()
199 textlen = attr.ib()
195 textlen = attr.ib()
200 cachedelta = attr.ib()
196 cachedelta = attr.ib()
201 flags = attr.ib()
197 flags = attr.ib()
202
198
203 @interfaceutil.implementer(repository.irevisiondelta)
199 @interfaceutil.implementer(repository.irevisiondelta)
204 @attr.s(slots=True)
200 @attr.s(slots=True)
205 class revlogrevisiondelta(object):
201 class revlogrevisiondelta(object):
206 node = attr.ib()
202 node = attr.ib()
207 p1node = attr.ib()
203 p1node = attr.ib()
208 p2node = attr.ib()
204 p2node = attr.ib()
209 basenode = attr.ib()
205 basenode = attr.ib()
210 flags = attr.ib()
206 flags = attr.ib()
211 baserevisionsize = attr.ib()
207 baserevisionsize = attr.ib()
212 revision = attr.ib()
208 revision = attr.ib()
213 delta = attr.ib()
209 delta = attr.ib()
214 linknode = attr.ib(default=None)
210 linknode = attr.ib(default=None)
215
211
216 @interfaceutil.implementer(repository.iverifyproblem)
212 @interfaceutil.implementer(repository.iverifyproblem)
217 @attr.s(frozen=True)
213 @attr.s(frozen=True)
218 class revlogproblem(object):
214 class revlogproblem(object):
219 warning = attr.ib(default=None)
215 warning = attr.ib(default=None)
220 error = attr.ib(default=None)
216 error = attr.ib(default=None)
221 node = attr.ib(default=None)
217 node = attr.ib(default=None)
222
218
223 # index v0:
219 # index v0:
224 # 4 bytes: offset
220 # 4 bytes: offset
225 # 4 bytes: compressed length
221 # 4 bytes: compressed length
226 # 4 bytes: base rev
222 # 4 bytes: base rev
227 # 4 bytes: link rev
223 # 4 bytes: link rev
228 # 20 bytes: parent 1 nodeid
224 # 20 bytes: parent 1 nodeid
229 # 20 bytes: parent 2 nodeid
225 # 20 bytes: parent 2 nodeid
230 # 20 bytes: nodeid
226 # 20 bytes: nodeid
231 indexformatv0 = struct.Struct(">4l20s20s20s")
227 indexformatv0 = struct.Struct(">4l20s20s20s")
232 indexformatv0_pack = indexformatv0.pack
228 indexformatv0_pack = indexformatv0.pack
233 indexformatv0_unpack = indexformatv0.unpack
229 indexformatv0_unpack = indexformatv0.unpack
234
230
235 class revlogoldindex(list):
231 class revlogoldindex(list):
236 def __getitem__(self, i):
232 def __getitem__(self, i):
237 if i == -1:
233 if i == -1:
238 return (0, 0, 0, -1, -1, -1, -1, nullid)
234 return (0, 0, 0, -1, -1, -1, -1, nullid)
239 return list.__getitem__(self, i)
235 return list.__getitem__(self, i)
240
236
241 class revlogoldio(object):
237 class revlogoldio(object):
242 def __init__(self):
238 def __init__(self):
243 self.size = indexformatv0.size
239 self.size = indexformatv0.size
244
240
245 def parseindex(self, data, inline):
241 def parseindex(self, data, inline):
246 s = self.size
242 s = self.size
247 index = []
243 index = []
248 nodemap = {nullid: nullrev}
244 nodemap = {nullid: nullrev}
249 n = off = 0
245 n = off = 0
250 l = len(data)
246 l = len(data)
251 while off + s <= l:
247 while off + s <= l:
252 cur = data[off:off + s]
248 cur = data[off:off + s]
253 off += s
249 off += s
254 e = indexformatv0_unpack(cur)
250 e = indexformatv0_unpack(cur)
255 # transform to revlogv1 format
251 # transform to revlogv1 format
256 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
252 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
257 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
253 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
258 index.append(e2)
254 index.append(e2)
259 nodemap[e[6]] = n
255 nodemap[e[6]] = n
260 n += 1
256 n += 1
261
257
262 return revlogoldindex(index), nodemap, None
258 return revlogoldindex(index), nodemap, None
263
259
264 def packentry(self, entry, node, version, rev):
260 def packentry(self, entry, node, version, rev):
265 if gettype(entry[0]):
261 if gettype(entry[0]):
266 raise error.RevlogError(_('index entry flags need revlog '
262 raise error.RevlogError(_('index entry flags need revlog '
267 'version 1'))
263 'version 1'))
268 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
264 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
269 node(entry[5]), node(entry[6]), entry[7])
265 node(entry[5]), node(entry[6]), entry[7])
270 return indexformatv0_pack(*e2)
266 return indexformatv0_pack(*e2)
271
267
272 # index ng:
268 # index ng:
273 # 6 bytes: offset
269 # 6 bytes: offset
274 # 2 bytes: flags
270 # 2 bytes: flags
275 # 4 bytes: compressed length
271 # 4 bytes: compressed length
276 # 4 bytes: uncompressed length
272 # 4 bytes: uncompressed length
277 # 4 bytes: base rev
273 # 4 bytes: base rev
278 # 4 bytes: link rev
274 # 4 bytes: link rev
279 # 4 bytes: parent 1 rev
275 # 4 bytes: parent 1 rev
280 # 4 bytes: parent 2 rev
276 # 4 bytes: parent 2 rev
281 # 32 bytes: nodeid
277 # 32 bytes: nodeid
282 indexformatng = struct.Struct(">Qiiiiii20s12x")
278 indexformatng = struct.Struct(">Qiiiiii20s12x")
283 indexformatng_pack = indexformatng.pack
279 indexformatng_pack = indexformatng.pack
284 versionformat = struct.Struct(">I")
280 versionformat = struct.Struct(">I")
285 versionformat_pack = versionformat.pack
281 versionformat_pack = versionformat.pack
286 versionformat_unpack = versionformat.unpack
282 versionformat_unpack = versionformat.unpack
287
283
288 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
284 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
289 # signed integer)
285 # signed integer)
290 _maxentrysize = 0x7fffffff
286 _maxentrysize = 0x7fffffff
291
287
292 class revlogio(object):
288 class revlogio(object):
293 def __init__(self):
289 def __init__(self):
294 self.size = indexformatng.size
290 self.size = indexformatng.size
295
291
296 def parseindex(self, data, inline):
292 def parseindex(self, data, inline):
297 # call the C implementation to parse the index data
293 # call the C implementation to parse the index data
298 index, cache = parsers.parse_index2(data, inline)
294 index, cache = parsers.parse_index2(data, inline)
299 return index, getattr(index, 'nodemap', None), cache
295 return index, getattr(index, 'nodemap', None), cache
300
296
301 def packentry(self, entry, node, version, rev):
297 def packentry(self, entry, node, version, rev):
302 p = indexformatng_pack(*entry)
298 p = indexformatng_pack(*entry)
303 if rev == 0:
299 if rev == 0:
304 p = versionformat_pack(version) + p[4:]
300 p = versionformat_pack(version) + p[4:]
305 return p
301 return p
306
302
307 class revlog(object):
303 class revlog(object):
308 """
304 """
309 the underlying revision storage object
305 the underlying revision storage object
310
306
311 A revlog consists of two parts, an index and the revision data.
307 A revlog consists of two parts, an index and the revision data.
312
308
313 The index is a file with a fixed record size containing
309 The index is a file with a fixed record size containing
314 information on each revision, including its nodeid (hash), the
310 information on each revision, including its nodeid (hash), the
315 nodeids of its parents, the position and offset of its data within
311 nodeids of its parents, the position and offset of its data within
316 the data file, and the revision it's based on. Finally, each entry
312 the data file, and the revision it's based on. Finally, each entry
317 contains a linkrev entry that can serve as a pointer to external
313 contains a linkrev entry that can serve as a pointer to external
318 data.
314 data.
319
315
320 The revision data itself is a linear collection of data chunks.
316 The revision data itself is a linear collection of data chunks.
321 Each chunk represents a revision and is usually represented as a
317 Each chunk represents a revision and is usually represented as a
322 delta against the previous chunk. To bound lookup time, runs of
318 delta against the previous chunk. To bound lookup time, runs of
323 deltas are limited to about 2 times the length of the original
319 deltas are limited to about 2 times the length of the original
324 version data. This makes retrieval of a version proportional to
320 version data. This makes retrieval of a version proportional to
325 its size, or O(1) relative to the number of revisions.
321 its size, or O(1) relative to the number of revisions.
326
322
327 Both pieces of the revlog are written to in an append-only
323 Both pieces of the revlog are written to in an append-only
328 fashion, which means we never need to rewrite a file to insert or
324 fashion, which means we never need to rewrite a file to insert or
329 remove data, and can use some simple techniques to avoid the need
325 remove data, and can use some simple techniques to avoid the need
330 for locking while reading.
326 for locking while reading.
331
327
332 If checkambig, indexfile is opened with checkambig=True at
328 If checkambig, indexfile is opened with checkambig=True at
333 writing, to avoid file stat ambiguity.
329 writing, to avoid file stat ambiguity.
334
330
335 If mmaplargeindex is True, and an mmapindexthreshold is set, the
331 If mmaplargeindex is True, and an mmapindexthreshold is set, the
336 index will be mmapped rather than read if it is larger than the
332 index will be mmapped rather than read if it is larger than the
337 configured threshold.
333 configured threshold.
338
334
339 If censorable is True, the revlog can have censored revisions.
335 If censorable is True, the revlog can have censored revisions.
340
336
341 If `upperboundcomp` is not None, this is the expected maximal gain from
337 If `upperboundcomp` is not None, this is the expected maximal gain from
342 compression for the data content.
338 compression for the data content.
343 """
339 """
344 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
340 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
345 mmaplargeindex=False, censorable=False,
341 mmaplargeindex=False, censorable=False,
346 upperboundcomp=None):
342 upperboundcomp=None):
347 """
343 """
348 create a revlog object
344 create a revlog object
349
345
350 opener is a function that abstracts the file opening operation
346 opener is a function that abstracts the file opening operation
351 and can be used to implement COW semantics or the like.
347 and can be used to implement COW semantics or the like.
352
348
353 """
349 """
354 self.upperboundcomp = upperboundcomp
350 self.upperboundcomp = upperboundcomp
355 self.indexfile = indexfile
351 self.indexfile = indexfile
356 self.datafile = datafile or (indexfile[:-2] + ".d")
352 self.datafile = datafile or (indexfile[:-2] + ".d")
357 self.opener = opener
353 self.opener = opener
358 # When True, indexfile is opened with checkambig=True at writing, to
354 # When True, indexfile is opened with checkambig=True at writing, to
359 # avoid file stat ambiguity.
355 # avoid file stat ambiguity.
360 self._checkambig = checkambig
356 self._checkambig = checkambig
361 self._mmaplargeindex = mmaplargeindex
357 self._mmaplargeindex = mmaplargeindex
362 self._censorable = censorable
358 self._censorable = censorable
363 # 3-tuple of (node, rev, text) for a raw revision.
359 # 3-tuple of (node, rev, text) for a raw revision.
364 self._revisioncache = None
360 self._revisioncache = None
365 # Maps rev to chain base rev.
361 # Maps rev to chain base rev.
366 self._chainbasecache = util.lrucachedict(100)
362 self._chainbasecache = util.lrucachedict(100)
367 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
363 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
368 self._chunkcache = (0, '')
364 self._chunkcache = (0, '')
369 # How much data to read and cache into the raw revlog data cache.
365 # How much data to read and cache into the raw revlog data cache.
370 self._chunkcachesize = 65536
366 self._chunkcachesize = 65536
371 self._maxchainlen = None
367 self._maxchainlen = None
372 self._deltabothparents = True
368 self._deltabothparents = True
373 self.index = []
369 self.index = []
374 # Mapping of partial identifiers to full nodes.
370 # Mapping of partial identifiers to full nodes.
375 self._pcache = {}
371 self._pcache = {}
376 # Mapping of revision integer to full node.
372 # Mapping of revision integer to full node.
377 self._nodecache = {nullid: nullrev}
373 self._nodecache = {nullid: nullrev}
378 self._nodepos = None
374 self._nodepos = None
379 self._compengine = 'zlib'
375 self._compengine = 'zlib'
380 self._compengineopts = {}
376 self._compengineopts = {}
381 self._maxdeltachainspan = -1
377 self._maxdeltachainspan = -1
382 self._withsparseread = False
378 self._withsparseread = False
383 self._sparserevlog = False
379 self._sparserevlog = False
384 self._srdensitythreshold = 0.50
380 self._srdensitythreshold = 0.50
385 self._srmingapsize = 262144
381 self._srmingapsize = 262144
386
382
387 # Make copy of flag processors so each revlog instance can support
383 # Make copy of flag processors so each revlog instance can support
388 # custom flags.
384 # custom flags.
389 self._flagprocessors = dict(_flagprocessors)
385 self._flagprocessors = dict(flagutil.flagprocessors)
390
386
391 # 2-tuple of file handles being used for active writing.
387 # 2-tuple of file handles being used for active writing.
392 self._writinghandles = None
388 self._writinghandles = None
393
389
394 self._loadindex()
390 self._loadindex()
395
391
396 def _loadindex(self):
392 def _loadindex(self):
397 mmapindexthreshold = None
393 mmapindexthreshold = None
398 opts = getattr(self.opener, 'options', {}) or {}
394 opts = getattr(self.opener, 'options', {}) or {}
399
395
400 if 'revlogv2' in opts:
396 if 'revlogv2' in opts:
401 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
397 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
402 elif 'revlogv1' in opts:
398 elif 'revlogv1' in opts:
403 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
399 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
404 if 'generaldelta' in opts:
400 if 'generaldelta' in opts:
405 newversionflags |= FLAG_GENERALDELTA
401 newversionflags |= FLAG_GENERALDELTA
406 elif getattr(self.opener, 'options', None) is not None:
402 elif getattr(self.opener, 'options', None) is not None:
407 # If options provided but no 'revlog*' found, the repository
403 # If options provided but no 'revlog*' found, the repository
408 # would have no 'requires' file in it, which means we have to
404 # would have no 'requires' file in it, which means we have to
409 # stick to the old format.
405 # stick to the old format.
410 newversionflags = REVLOGV0
406 newversionflags = REVLOGV0
411 else:
407 else:
412 newversionflags = REVLOG_DEFAULT_VERSION
408 newversionflags = REVLOG_DEFAULT_VERSION
413
409
414 if 'chunkcachesize' in opts:
410 if 'chunkcachesize' in opts:
415 self._chunkcachesize = opts['chunkcachesize']
411 self._chunkcachesize = opts['chunkcachesize']
416 if 'maxchainlen' in opts:
412 if 'maxchainlen' in opts:
417 self._maxchainlen = opts['maxchainlen']
413 self._maxchainlen = opts['maxchainlen']
418 if 'deltabothparents' in opts:
414 if 'deltabothparents' in opts:
419 self._deltabothparents = opts['deltabothparents']
415 self._deltabothparents = opts['deltabothparents']
420 self._lazydelta = bool(opts.get('lazydelta', True))
416 self._lazydelta = bool(opts.get('lazydelta', True))
421 self._lazydeltabase = False
417 self._lazydeltabase = False
422 if self._lazydelta:
418 if self._lazydelta:
423 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
419 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
424 if 'compengine' in opts:
420 if 'compengine' in opts:
425 self._compengine = opts['compengine']
421 self._compengine = opts['compengine']
426 if 'zlib.level' in opts:
422 if 'zlib.level' in opts:
427 self._compengineopts['zlib.level'] = opts['zlib.level']
423 self._compengineopts['zlib.level'] = opts['zlib.level']
428 if 'zstd.level' in opts:
424 if 'zstd.level' in opts:
429 self._compengineopts['zstd.level'] = opts['zstd.level']
425 self._compengineopts['zstd.level'] = opts['zstd.level']
430 if 'maxdeltachainspan' in opts:
426 if 'maxdeltachainspan' in opts:
431 self._maxdeltachainspan = opts['maxdeltachainspan']
427 self._maxdeltachainspan = opts['maxdeltachainspan']
432 if self._mmaplargeindex and 'mmapindexthreshold' in opts:
428 if self._mmaplargeindex and 'mmapindexthreshold' in opts:
433 mmapindexthreshold = opts['mmapindexthreshold']
429 mmapindexthreshold = opts['mmapindexthreshold']
434 self._sparserevlog = bool(opts.get('sparse-revlog', False))
430 self._sparserevlog = bool(opts.get('sparse-revlog', False))
435 withsparseread = bool(opts.get('with-sparse-read', False))
431 withsparseread = bool(opts.get('with-sparse-read', False))
436 # sparse-revlog forces sparse-read
432 # sparse-revlog forces sparse-read
437 self._withsparseread = self._sparserevlog or withsparseread
433 self._withsparseread = self._sparserevlog or withsparseread
438 if 'sparse-read-density-threshold' in opts:
434 if 'sparse-read-density-threshold' in opts:
439 self._srdensitythreshold = opts['sparse-read-density-threshold']
435 self._srdensitythreshold = opts['sparse-read-density-threshold']
440 if 'sparse-read-min-gap-size' in opts:
436 if 'sparse-read-min-gap-size' in opts:
441 self._srmingapsize = opts['sparse-read-min-gap-size']
437 self._srmingapsize = opts['sparse-read-min-gap-size']
442 if opts.get('enableellipsis'):
438 if opts.get('enableellipsis'):
443 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
439 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
444
440
445 # revlog v0 doesn't have flag processors
441 # revlog v0 doesn't have flag processors
446 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
442 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
447 _insertflagprocessor(flag, processor, self._flagprocessors)
443 _insertflagprocessor(flag, processor, self._flagprocessors)
448
444
449 if self._chunkcachesize <= 0:
445 if self._chunkcachesize <= 0:
450 raise error.RevlogError(_('revlog chunk cache size %r is not '
446 raise error.RevlogError(_('revlog chunk cache size %r is not '
451 'greater than 0') % self._chunkcachesize)
447 'greater than 0') % self._chunkcachesize)
452 elif self._chunkcachesize & (self._chunkcachesize - 1):
448 elif self._chunkcachesize & (self._chunkcachesize - 1):
453 raise error.RevlogError(_('revlog chunk cache size %r is not a '
449 raise error.RevlogError(_('revlog chunk cache size %r is not a '
454 'power of 2') % self._chunkcachesize)
450 'power of 2') % self._chunkcachesize)
455
451
456 indexdata = ''
452 indexdata = ''
457 self._initempty = True
453 self._initempty = True
458 try:
454 try:
459 with self._indexfp() as f:
455 with self._indexfp() as f:
460 if (mmapindexthreshold is not None and
456 if (mmapindexthreshold is not None and
461 self.opener.fstat(f).st_size >= mmapindexthreshold):
457 self.opener.fstat(f).st_size >= mmapindexthreshold):
462 # TODO: should .close() to release resources without
458 # TODO: should .close() to release resources without
463 # relying on Python GC
459 # relying on Python GC
464 indexdata = util.buffer(util.mmapread(f))
460 indexdata = util.buffer(util.mmapread(f))
465 else:
461 else:
466 indexdata = f.read()
462 indexdata = f.read()
467 if len(indexdata) > 0:
463 if len(indexdata) > 0:
468 versionflags = versionformat_unpack(indexdata[:4])[0]
464 versionflags = versionformat_unpack(indexdata[:4])[0]
469 self._initempty = False
465 self._initempty = False
470 else:
466 else:
471 versionflags = newversionflags
467 versionflags = newversionflags
472 except IOError as inst:
468 except IOError as inst:
473 if inst.errno != errno.ENOENT:
469 if inst.errno != errno.ENOENT:
474 raise
470 raise
475
471
476 versionflags = newversionflags
472 versionflags = newversionflags
477
473
478 self.version = versionflags
474 self.version = versionflags
479
475
480 flags = versionflags & ~0xFFFF
476 flags = versionflags & ~0xFFFF
481 fmt = versionflags & 0xFFFF
477 fmt = versionflags & 0xFFFF
482
478
483 if fmt == REVLOGV0:
479 if fmt == REVLOGV0:
484 if flags:
480 if flags:
485 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
481 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
486 'revlog %s') %
482 'revlog %s') %
487 (flags >> 16, fmt, self.indexfile))
483 (flags >> 16, fmt, self.indexfile))
488
484
489 self._inline = False
485 self._inline = False
490 self._generaldelta = False
486 self._generaldelta = False
491
487
492 elif fmt == REVLOGV1:
488 elif fmt == REVLOGV1:
493 if flags & ~REVLOGV1_FLAGS:
489 if flags & ~REVLOGV1_FLAGS:
494 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
490 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
495 'revlog %s') %
491 'revlog %s') %
496 (flags >> 16, fmt, self.indexfile))
492 (flags >> 16, fmt, self.indexfile))
497
493
498 self._inline = versionflags & FLAG_INLINE_DATA
494 self._inline = versionflags & FLAG_INLINE_DATA
499 self._generaldelta = versionflags & FLAG_GENERALDELTA
495 self._generaldelta = versionflags & FLAG_GENERALDELTA
500
496
501 elif fmt == REVLOGV2:
497 elif fmt == REVLOGV2:
502 if flags & ~REVLOGV2_FLAGS:
498 if flags & ~REVLOGV2_FLAGS:
503 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
499 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
504 'revlog %s') %
500 'revlog %s') %
505 (flags >> 16, fmt, self.indexfile))
501 (flags >> 16, fmt, self.indexfile))
506
502
507 self._inline = versionflags & FLAG_INLINE_DATA
503 self._inline = versionflags & FLAG_INLINE_DATA
508 # generaldelta implied by version 2 revlogs.
504 # generaldelta implied by version 2 revlogs.
509 self._generaldelta = True
505 self._generaldelta = True
510
506
511 else:
507 else:
512 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
508 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
513 (fmt, self.indexfile))
509 (fmt, self.indexfile))
514 # sparse-revlog can't be on without general-delta (issue6056)
510 # sparse-revlog can't be on without general-delta (issue6056)
515 if not self._generaldelta:
511 if not self._generaldelta:
516 self._sparserevlog = False
512 self._sparserevlog = False
517
513
518 self._storedeltachains = True
514 self._storedeltachains = True
519
515
520 self._io = revlogio()
516 self._io = revlogio()
521 if self.version == REVLOGV0:
517 if self.version == REVLOGV0:
522 self._io = revlogoldio()
518 self._io = revlogoldio()
523 try:
519 try:
524 d = self._io.parseindex(indexdata, self._inline)
520 d = self._io.parseindex(indexdata, self._inline)
525 except (ValueError, IndexError):
521 except (ValueError, IndexError):
526 raise error.RevlogError(_("index %s is corrupted") %
522 raise error.RevlogError(_("index %s is corrupted") %
527 self.indexfile)
523 self.indexfile)
528 self.index, nodemap, self._chunkcache = d
524 self.index, nodemap, self._chunkcache = d
529 if nodemap is not None:
525 if nodemap is not None:
530 self.nodemap = self._nodecache = nodemap
526 self.nodemap = self._nodecache = nodemap
531 if not self._chunkcache:
527 if not self._chunkcache:
532 self._chunkclear()
528 self._chunkclear()
533 # revnum -> (chain-length, sum-delta-length)
529 # revnum -> (chain-length, sum-delta-length)
534 self._chaininfocache = {}
530 self._chaininfocache = {}
535 # revlog header -> revlog compressor
531 # revlog header -> revlog compressor
536 self._decompressors = {}
532 self._decompressors = {}
537
533
538 @util.propertycache
534 @util.propertycache
539 def _compressor(self):
535 def _compressor(self):
540 engine = util.compengines[self._compengine]
536 engine = util.compengines[self._compengine]
541 return engine.revlogcompressor(self._compengineopts)
537 return engine.revlogcompressor(self._compengineopts)
542
538
543 def _indexfp(self, mode='r'):
539 def _indexfp(self, mode='r'):
544 """file object for the revlog's index file"""
540 """file object for the revlog's index file"""
545 args = {r'mode': mode}
541 args = {r'mode': mode}
546 if mode != 'r':
542 if mode != 'r':
547 args[r'checkambig'] = self._checkambig
543 args[r'checkambig'] = self._checkambig
548 if mode == 'w':
544 if mode == 'w':
549 args[r'atomictemp'] = True
545 args[r'atomictemp'] = True
550 return self.opener(self.indexfile, **args)
546 return self.opener(self.indexfile, **args)
551
547
552 def _datafp(self, mode='r'):
548 def _datafp(self, mode='r'):
553 """file object for the revlog's data file"""
549 """file object for the revlog's data file"""
554 return self.opener(self.datafile, mode=mode)
550 return self.opener(self.datafile, mode=mode)
555
551
556 @contextlib.contextmanager
552 @contextlib.contextmanager
557 def _datareadfp(self, existingfp=None):
553 def _datareadfp(self, existingfp=None):
558 """file object suitable to read data"""
554 """file object suitable to read data"""
559 # Use explicit file handle, if given.
555 # Use explicit file handle, if given.
560 if existingfp is not None:
556 if existingfp is not None:
561 yield existingfp
557 yield existingfp
562
558
563 # Use a file handle being actively used for writes, if available.
559 # Use a file handle being actively used for writes, if available.
564 # There is some danger to doing this because reads will seek the
560 # There is some danger to doing this because reads will seek the
565 # file. However, _writeentry() performs a SEEK_END before all writes,
561 # file. However, _writeentry() performs a SEEK_END before all writes,
566 # so we should be safe.
562 # so we should be safe.
567 elif self._writinghandles:
563 elif self._writinghandles:
568 if self._inline:
564 if self._inline:
569 yield self._writinghandles[0]
565 yield self._writinghandles[0]
570 else:
566 else:
571 yield self._writinghandles[1]
567 yield self._writinghandles[1]
572
568
573 # Otherwise open a new file handle.
569 # Otherwise open a new file handle.
574 else:
570 else:
575 if self._inline:
571 if self._inline:
576 func = self._indexfp
572 func = self._indexfp
577 else:
573 else:
578 func = self._datafp
574 func = self._datafp
579 with func() as fp:
575 with func() as fp:
580 yield fp
576 yield fp
581
577
582 def tip(self):
578 def tip(self):
583 return self.node(len(self.index) - 1)
579 return self.node(len(self.index) - 1)
584 def __contains__(self, rev):
580 def __contains__(self, rev):
585 return 0 <= rev < len(self)
581 return 0 <= rev < len(self)
586 def __len__(self):
582 def __len__(self):
587 return len(self.index)
583 return len(self.index)
588 def __iter__(self):
584 def __iter__(self):
589 return iter(pycompat.xrange(len(self)))
585 return iter(pycompat.xrange(len(self)))
590 def revs(self, start=0, stop=None):
586 def revs(self, start=0, stop=None):
591 """iterate over all rev in this revlog (from start to stop)"""
587 """iterate over all rev in this revlog (from start to stop)"""
592 return storageutil.iterrevs(len(self), start=start, stop=stop)
588 return storageutil.iterrevs(len(self), start=start, stop=stop)
593
589
594 @util.propertycache
590 @util.propertycache
595 def nodemap(self):
591 def nodemap(self):
596 if self.index:
592 if self.index:
597 # populate mapping down to the initial node
593 # populate mapping down to the initial node
598 node0 = self.index[0][7] # get around changelog filtering
594 node0 = self.index[0][7] # get around changelog filtering
599 self.rev(node0)
595 self.rev(node0)
600 return self._nodecache
596 return self._nodecache
601
597
602 def hasnode(self, node):
598 def hasnode(self, node):
603 try:
599 try:
604 self.rev(node)
600 self.rev(node)
605 return True
601 return True
606 except KeyError:
602 except KeyError:
607 return False
603 return False
608
604
609 def candelta(self, baserev, rev):
605 def candelta(self, baserev, rev):
610 """whether two revisions (baserev, rev) can be delta-ed or not"""
606 """whether two revisions (baserev, rev) can be delta-ed or not"""
611 # Disable delta if either rev requires a content-changing flag
607 # Disable delta if either rev requires a content-changing flag
612 # processor (ex. LFS). This is because such flag processor can alter
608 # processor (ex. LFS). This is because such flag processor can alter
613 # the rawtext content that the delta will be based on, and two clients
609 # the rawtext content that the delta will be based on, and two clients
614 # could have a same revlog node with different flags (i.e. different
610 # could have a same revlog node with different flags (i.e. different
615 # rawtext contents) and the delta could be incompatible.
611 # rawtext contents) and the delta could be incompatible.
616 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
612 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
617 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
613 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
618 return False
614 return False
619 return True
615 return True
620
616
621 def clearcaches(self):
617 def clearcaches(self):
622 self._revisioncache = None
618 self._revisioncache = None
623 self._chainbasecache.clear()
619 self._chainbasecache.clear()
624 self._chunkcache = (0, '')
620 self._chunkcache = (0, '')
625 self._pcache = {}
621 self._pcache = {}
626
622
627 try:
623 try:
628 # If we are using the native C version, you are in a fun case
624 # If we are using the native C version, you are in a fun case
629 # where self.index, self.nodemap and self._nodecaches is the same
625 # where self.index, self.nodemap and self._nodecaches is the same
630 # object.
626 # object.
631 self._nodecache.clearcaches()
627 self._nodecache.clearcaches()
632 except AttributeError:
628 except AttributeError:
633 self._nodecache = {nullid: nullrev}
629 self._nodecache = {nullid: nullrev}
634 self._nodepos = None
630 self._nodepos = None
635
631
636 def rev(self, node):
632 def rev(self, node):
637 try:
633 try:
638 return self._nodecache[node]
634 return self._nodecache[node]
639 except TypeError:
635 except TypeError:
640 raise
636 raise
641 except error.RevlogError:
637 except error.RevlogError:
642 # parsers.c radix tree lookup failed
638 # parsers.c radix tree lookup failed
643 if node == wdirid or node in wdirfilenodeids:
639 if node == wdirid or node in wdirfilenodeids:
644 raise error.WdirUnsupported
640 raise error.WdirUnsupported
645 raise error.LookupError(node, self.indexfile, _('no node'))
641 raise error.LookupError(node, self.indexfile, _('no node'))
646 except KeyError:
642 except KeyError:
647 # pure python cache lookup failed
643 # pure python cache lookup failed
648 n = self._nodecache
644 n = self._nodecache
649 i = self.index
645 i = self.index
650 p = self._nodepos
646 p = self._nodepos
651 if p is None:
647 if p is None:
652 p = len(i) - 1
648 p = len(i) - 1
653 else:
649 else:
654 assert p < len(i)
650 assert p < len(i)
655 for r in pycompat.xrange(p, -1, -1):
651 for r in pycompat.xrange(p, -1, -1):
656 v = i[r][7]
652 v = i[r][7]
657 n[v] = r
653 n[v] = r
658 if v == node:
654 if v == node:
659 self._nodepos = r - 1
655 self._nodepos = r - 1
660 return r
656 return r
661 if node == wdirid or node in wdirfilenodeids:
657 if node == wdirid or node in wdirfilenodeids:
662 raise error.WdirUnsupported
658 raise error.WdirUnsupported
663 raise error.LookupError(node, self.indexfile, _('no node'))
659 raise error.LookupError(node, self.indexfile, _('no node'))
664
660
665 # Accessors for index entries.
661 # Accessors for index entries.
666
662
667 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
663 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
668 # are flags.
664 # are flags.
669 def start(self, rev):
665 def start(self, rev):
670 return int(self.index[rev][0] >> 16)
666 return int(self.index[rev][0] >> 16)
671
667
672 def flags(self, rev):
668 def flags(self, rev):
673 return self.index[rev][0] & 0xFFFF
669 return self.index[rev][0] & 0xFFFF
674
670
675 def length(self, rev):
671 def length(self, rev):
676 return self.index[rev][1]
672 return self.index[rev][1]
677
673
678 def rawsize(self, rev):
674 def rawsize(self, rev):
679 """return the length of the uncompressed text for a given revision"""
675 """return the length of the uncompressed text for a given revision"""
680 l = self.index[rev][2]
676 l = self.index[rev][2]
681 if l >= 0:
677 if l >= 0:
682 return l
678 return l
683
679
684 t = self.revision(rev, raw=True)
680 t = self.revision(rev, raw=True)
685 return len(t)
681 return len(t)
686
682
687 def size(self, rev):
683 def size(self, rev):
688 """length of non-raw text (processed by a "read" flag processor)"""
684 """length of non-raw text (processed by a "read" flag processor)"""
689 # fast path: if no "read" flag processor could change the content,
685 # fast path: if no "read" flag processor could change the content,
690 # size is rawsize. note: ELLIPSIS is known to not change the content.
686 # size is rawsize. note: ELLIPSIS is known to not change the content.
691 flags = self.flags(rev)
687 flags = self.flags(rev)
692 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
688 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
693 return self.rawsize(rev)
689 return self.rawsize(rev)
694
690
695 return len(self.revision(rev, raw=False))
691 return len(self.revision(rev, raw=False))
696
692
697 def chainbase(self, rev):
693 def chainbase(self, rev):
698 base = self._chainbasecache.get(rev)
694 base = self._chainbasecache.get(rev)
699 if base is not None:
695 if base is not None:
700 return base
696 return base
701
697
702 index = self.index
698 index = self.index
703 iterrev = rev
699 iterrev = rev
704 base = index[iterrev][3]
700 base = index[iterrev][3]
705 while base != iterrev:
701 while base != iterrev:
706 iterrev = base
702 iterrev = base
707 base = index[iterrev][3]
703 base = index[iterrev][3]
708
704
709 self._chainbasecache[rev] = base
705 self._chainbasecache[rev] = base
710 return base
706 return base
711
707
712 def linkrev(self, rev):
708 def linkrev(self, rev):
713 return self.index[rev][4]
709 return self.index[rev][4]
714
710
715 def parentrevs(self, rev):
711 def parentrevs(self, rev):
716 try:
712 try:
717 entry = self.index[rev]
713 entry = self.index[rev]
718 except IndexError:
714 except IndexError:
719 if rev == wdirrev:
715 if rev == wdirrev:
720 raise error.WdirUnsupported
716 raise error.WdirUnsupported
721 raise
717 raise
722
718
723 return entry[5], entry[6]
719 return entry[5], entry[6]
724
720
725 # fast parentrevs(rev) where rev isn't filtered
721 # fast parentrevs(rev) where rev isn't filtered
726 _uncheckedparentrevs = parentrevs
722 _uncheckedparentrevs = parentrevs
727
723
728 def node(self, rev):
724 def node(self, rev):
729 try:
725 try:
730 return self.index[rev][7]
726 return self.index[rev][7]
731 except IndexError:
727 except IndexError:
732 if rev == wdirrev:
728 if rev == wdirrev:
733 raise error.WdirUnsupported
729 raise error.WdirUnsupported
734 raise
730 raise
735
731
736 # Derived from index values.
732 # Derived from index values.
737
733
738 def end(self, rev):
734 def end(self, rev):
739 return self.start(rev) + self.length(rev)
735 return self.start(rev) + self.length(rev)
740
736
741 def parents(self, node):
737 def parents(self, node):
742 i = self.index
738 i = self.index
743 d = i[self.rev(node)]
739 d = i[self.rev(node)]
744 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
740 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
745
741
746 def chainlen(self, rev):
742 def chainlen(self, rev):
747 return self._chaininfo(rev)[0]
743 return self._chaininfo(rev)[0]
748
744
749 def _chaininfo(self, rev):
745 def _chaininfo(self, rev):
750 chaininfocache = self._chaininfocache
746 chaininfocache = self._chaininfocache
751 if rev in chaininfocache:
747 if rev in chaininfocache:
752 return chaininfocache[rev]
748 return chaininfocache[rev]
753 index = self.index
749 index = self.index
754 generaldelta = self._generaldelta
750 generaldelta = self._generaldelta
755 iterrev = rev
751 iterrev = rev
756 e = index[iterrev]
752 e = index[iterrev]
757 clen = 0
753 clen = 0
758 compresseddeltalen = 0
754 compresseddeltalen = 0
759 while iterrev != e[3]:
755 while iterrev != e[3]:
760 clen += 1
756 clen += 1
761 compresseddeltalen += e[1]
757 compresseddeltalen += e[1]
762 if generaldelta:
758 if generaldelta:
763 iterrev = e[3]
759 iterrev = e[3]
764 else:
760 else:
765 iterrev -= 1
761 iterrev -= 1
766 if iterrev in chaininfocache:
762 if iterrev in chaininfocache:
767 t = chaininfocache[iterrev]
763 t = chaininfocache[iterrev]
768 clen += t[0]
764 clen += t[0]
769 compresseddeltalen += t[1]
765 compresseddeltalen += t[1]
770 break
766 break
771 e = index[iterrev]
767 e = index[iterrev]
772 else:
768 else:
773 # Add text length of base since decompressing that also takes
769 # Add text length of base since decompressing that also takes
774 # work. For cache hits the length is already included.
770 # work. For cache hits the length is already included.
775 compresseddeltalen += e[1]
771 compresseddeltalen += e[1]
776 r = (clen, compresseddeltalen)
772 r = (clen, compresseddeltalen)
777 chaininfocache[rev] = r
773 chaininfocache[rev] = r
778 return r
774 return r
779
775
780 def _deltachain(self, rev, stoprev=None):
776 def _deltachain(self, rev, stoprev=None):
781 """Obtain the delta chain for a revision.
777 """Obtain the delta chain for a revision.
782
778
783 ``stoprev`` specifies a revision to stop at. If not specified, we
779 ``stoprev`` specifies a revision to stop at. If not specified, we
784 stop at the base of the chain.
780 stop at the base of the chain.
785
781
786 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
782 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
787 revs in ascending order and ``stopped`` is a bool indicating whether
783 revs in ascending order and ``stopped`` is a bool indicating whether
788 ``stoprev`` was hit.
784 ``stoprev`` was hit.
789 """
785 """
790 # Try C implementation.
786 # Try C implementation.
791 try:
787 try:
792 return self.index.deltachain(rev, stoprev, self._generaldelta)
788 return self.index.deltachain(rev, stoprev, self._generaldelta)
793 except AttributeError:
789 except AttributeError:
794 pass
790 pass
795
791
796 chain = []
792 chain = []
797
793
798 # Alias to prevent attribute lookup in tight loop.
794 # Alias to prevent attribute lookup in tight loop.
799 index = self.index
795 index = self.index
800 generaldelta = self._generaldelta
796 generaldelta = self._generaldelta
801
797
802 iterrev = rev
798 iterrev = rev
803 e = index[iterrev]
799 e = index[iterrev]
804 while iterrev != e[3] and iterrev != stoprev:
800 while iterrev != e[3] and iterrev != stoprev:
805 chain.append(iterrev)
801 chain.append(iterrev)
806 if generaldelta:
802 if generaldelta:
807 iterrev = e[3]
803 iterrev = e[3]
808 else:
804 else:
809 iterrev -= 1
805 iterrev -= 1
810 e = index[iterrev]
806 e = index[iterrev]
811
807
812 if iterrev == stoprev:
808 if iterrev == stoprev:
813 stopped = True
809 stopped = True
814 else:
810 else:
815 chain.append(iterrev)
811 chain.append(iterrev)
816 stopped = False
812 stopped = False
817
813
818 chain.reverse()
814 chain.reverse()
819 return chain, stopped
815 return chain, stopped
820
816
821 def ancestors(self, revs, stoprev=0, inclusive=False):
817 def ancestors(self, revs, stoprev=0, inclusive=False):
822 """Generate the ancestors of 'revs' in reverse revision order.
818 """Generate the ancestors of 'revs' in reverse revision order.
823 Does not generate revs lower than stoprev.
819 Does not generate revs lower than stoprev.
824
820
825 See the documentation for ancestor.lazyancestors for more details."""
821 See the documentation for ancestor.lazyancestors for more details."""
826
822
827 # first, make sure start revisions aren't filtered
823 # first, make sure start revisions aren't filtered
828 revs = list(revs)
824 revs = list(revs)
829 checkrev = self.node
825 checkrev = self.node
830 for r in revs:
826 for r in revs:
831 checkrev(r)
827 checkrev(r)
832 # and we're sure ancestors aren't filtered as well
828 # and we're sure ancestors aren't filtered as well
833
829
834 if rustancestor is not None:
830 if rustancestor is not None:
835 lazyancestors = rustancestor.LazyAncestors
831 lazyancestors = rustancestor.LazyAncestors
836 arg = self.index
832 arg = self.index
837 elif util.safehasattr(parsers, 'rustlazyancestors'):
833 elif util.safehasattr(parsers, 'rustlazyancestors'):
838 lazyancestors = ancestor.rustlazyancestors
834 lazyancestors = ancestor.rustlazyancestors
839 arg = self.index
835 arg = self.index
840 else:
836 else:
841 lazyancestors = ancestor.lazyancestors
837 lazyancestors = ancestor.lazyancestors
842 arg = self._uncheckedparentrevs
838 arg = self._uncheckedparentrevs
843 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
839 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
844
840
845 def descendants(self, revs):
841 def descendants(self, revs):
846 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
842 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
847
843
848 def findcommonmissing(self, common=None, heads=None):
844 def findcommonmissing(self, common=None, heads=None):
849 """Return a tuple of the ancestors of common and the ancestors of heads
845 """Return a tuple of the ancestors of common and the ancestors of heads
850 that are not ancestors of common. In revset terminology, we return the
846 that are not ancestors of common. In revset terminology, we return the
851 tuple:
847 tuple:
852
848
853 ::common, (::heads) - (::common)
849 ::common, (::heads) - (::common)
854
850
855 The list is sorted by revision number, meaning it is
851 The list is sorted by revision number, meaning it is
856 topologically sorted.
852 topologically sorted.
857
853
858 'heads' and 'common' are both lists of node IDs. If heads is
854 'heads' and 'common' are both lists of node IDs. If heads is
859 not supplied, uses all of the revlog's heads. If common is not
855 not supplied, uses all of the revlog's heads. If common is not
860 supplied, uses nullid."""
856 supplied, uses nullid."""
861 if common is None:
857 if common is None:
862 common = [nullid]
858 common = [nullid]
863 if heads is None:
859 if heads is None:
864 heads = self.heads()
860 heads = self.heads()
865
861
866 common = [self.rev(n) for n in common]
862 common = [self.rev(n) for n in common]
867 heads = [self.rev(n) for n in heads]
863 heads = [self.rev(n) for n in heads]
868
864
869 # we want the ancestors, but inclusive
865 # we want the ancestors, but inclusive
870 class lazyset(object):
866 class lazyset(object):
871 def __init__(self, lazyvalues):
867 def __init__(self, lazyvalues):
872 self.addedvalues = set()
868 self.addedvalues = set()
873 self.lazyvalues = lazyvalues
869 self.lazyvalues = lazyvalues
874
870
875 def __contains__(self, value):
871 def __contains__(self, value):
876 return value in self.addedvalues or value in self.lazyvalues
872 return value in self.addedvalues or value in self.lazyvalues
877
873
878 def __iter__(self):
874 def __iter__(self):
879 added = self.addedvalues
875 added = self.addedvalues
880 for r in added:
876 for r in added:
881 yield r
877 yield r
882 for r in self.lazyvalues:
878 for r in self.lazyvalues:
883 if not r in added:
879 if not r in added:
884 yield r
880 yield r
885
881
886 def add(self, value):
882 def add(self, value):
887 self.addedvalues.add(value)
883 self.addedvalues.add(value)
888
884
889 def update(self, values):
885 def update(self, values):
890 self.addedvalues.update(values)
886 self.addedvalues.update(values)
891
887
892 has = lazyset(self.ancestors(common))
888 has = lazyset(self.ancestors(common))
893 has.add(nullrev)
889 has.add(nullrev)
894 has.update(common)
890 has.update(common)
895
891
896 # take all ancestors from heads that aren't in has
892 # take all ancestors from heads that aren't in has
897 missing = set()
893 missing = set()
898 visit = collections.deque(r for r in heads if r not in has)
894 visit = collections.deque(r for r in heads if r not in has)
899 while visit:
895 while visit:
900 r = visit.popleft()
896 r = visit.popleft()
901 if r in missing:
897 if r in missing:
902 continue
898 continue
903 else:
899 else:
904 missing.add(r)
900 missing.add(r)
905 for p in self.parentrevs(r):
901 for p in self.parentrevs(r):
906 if p not in has:
902 if p not in has:
907 visit.append(p)
903 visit.append(p)
908 missing = list(missing)
904 missing = list(missing)
909 missing.sort()
905 missing.sort()
910 return has, [self.node(miss) for miss in missing]
906 return has, [self.node(miss) for miss in missing]
911
907
912 def incrementalmissingrevs(self, common=None):
908 def incrementalmissingrevs(self, common=None):
913 """Return an object that can be used to incrementally compute the
909 """Return an object that can be used to incrementally compute the
914 revision numbers of the ancestors of arbitrary sets that are not
910 revision numbers of the ancestors of arbitrary sets that are not
915 ancestors of common. This is an ancestor.incrementalmissingancestors
911 ancestors of common. This is an ancestor.incrementalmissingancestors
916 object.
912 object.
917
913
918 'common' is a list of revision numbers. If common is not supplied, uses
914 'common' is a list of revision numbers. If common is not supplied, uses
919 nullrev.
915 nullrev.
920 """
916 """
921 if common is None:
917 if common is None:
922 common = [nullrev]
918 common = [nullrev]
923
919
924 if rustancestor is not None:
920 if rustancestor is not None:
925 return rustancestor.MissingAncestors(self.index, common)
921 return rustancestor.MissingAncestors(self.index, common)
926 return ancestor.incrementalmissingancestors(self.parentrevs, common)
922 return ancestor.incrementalmissingancestors(self.parentrevs, common)
927
923
928 def findmissingrevs(self, common=None, heads=None):
924 def findmissingrevs(self, common=None, heads=None):
929 """Return the revision numbers of the ancestors of heads that
925 """Return the revision numbers of the ancestors of heads that
930 are not ancestors of common.
926 are not ancestors of common.
931
927
932 More specifically, return a list of revision numbers corresponding to
928 More specifically, return a list of revision numbers corresponding to
933 nodes N such that every N satisfies the following constraints:
929 nodes N such that every N satisfies the following constraints:
934
930
935 1. N is an ancestor of some node in 'heads'
931 1. N is an ancestor of some node in 'heads'
936 2. N is not an ancestor of any node in 'common'
932 2. N is not an ancestor of any node in 'common'
937
933
938 The list is sorted by revision number, meaning it is
934 The list is sorted by revision number, meaning it is
939 topologically sorted.
935 topologically sorted.
940
936
941 'heads' and 'common' are both lists of revision numbers. If heads is
937 'heads' and 'common' are both lists of revision numbers. If heads is
942 not supplied, uses all of the revlog's heads. If common is not
938 not supplied, uses all of the revlog's heads. If common is not
943 supplied, uses nullid."""
939 supplied, uses nullid."""
944 if common is None:
940 if common is None:
945 common = [nullrev]
941 common = [nullrev]
946 if heads is None:
942 if heads is None:
947 heads = self.headrevs()
943 heads = self.headrevs()
948
944
949 inc = self.incrementalmissingrevs(common=common)
945 inc = self.incrementalmissingrevs(common=common)
950 return inc.missingancestors(heads)
946 return inc.missingancestors(heads)
951
947
952 def findmissing(self, common=None, heads=None):
948 def findmissing(self, common=None, heads=None):
953 """Return the ancestors of heads that are not ancestors of common.
949 """Return the ancestors of heads that are not ancestors of common.
954
950
955 More specifically, return a list of nodes N such that every N
951 More specifically, return a list of nodes N such that every N
956 satisfies the following constraints:
952 satisfies the following constraints:
957
953
958 1. N is an ancestor of some node in 'heads'
954 1. N is an ancestor of some node in 'heads'
959 2. N is not an ancestor of any node in 'common'
955 2. N is not an ancestor of any node in 'common'
960
956
961 The list is sorted by revision number, meaning it is
957 The list is sorted by revision number, meaning it is
962 topologically sorted.
958 topologically sorted.
963
959
964 'heads' and 'common' are both lists of node IDs. If heads is
960 'heads' and 'common' are both lists of node IDs. If heads is
965 not supplied, uses all of the revlog's heads. If common is not
961 not supplied, uses all of the revlog's heads. If common is not
966 supplied, uses nullid."""
962 supplied, uses nullid."""
967 if common is None:
963 if common is None:
968 common = [nullid]
964 common = [nullid]
969 if heads is None:
965 if heads is None:
970 heads = self.heads()
966 heads = self.heads()
971
967
972 common = [self.rev(n) for n in common]
968 common = [self.rev(n) for n in common]
973 heads = [self.rev(n) for n in heads]
969 heads = [self.rev(n) for n in heads]
974
970
975 inc = self.incrementalmissingrevs(common=common)
971 inc = self.incrementalmissingrevs(common=common)
976 return [self.node(r) for r in inc.missingancestors(heads)]
972 return [self.node(r) for r in inc.missingancestors(heads)]
977
973
978 def nodesbetween(self, roots=None, heads=None):
974 def nodesbetween(self, roots=None, heads=None):
979 """Return a topological path from 'roots' to 'heads'.
975 """Return a topological path from 'roots' to 'heads'.
980
976
981 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
977 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
982 topologically sorted list of all nodes N that satisfy both of
978 topologically sorted list of all nodes N that satisfy both of
983 these constraints:
979 these constraints:
984
980
985 1. N is a descendant of some node in 'roots'
981 1. N is a descendant of some node in 'roots'
986 2. N is an ancestor of some node in 'heads'
982 2. N is an ancestor of some node in 'heads'
987
983
988 Every node is considered to be both a descendant and an ancestor
984 Every node is considered to be both a descendant and an ancestor
989 of itself, so every reachable node in 'roots' and 'heads' will be
985 of itself, so every reachable node in 'roots' and 'heads' will be
990 included in 'nodes'.
986 included in 'nodes'.
991
987
992 'outroots' is the list of reachable nodes in 'roots', i.e., the
988 'outroots' is the list of reachable nodes in 'roots', i.e., the
993 subset of 'roots' that is returned in 'nodes'. Likewise,
989 subset of 'roots' that is returned in 'nodes'. Likewise,
994 'outheads' is the subset of 'heads' that is also in 'nodes'.
990 'outheads' is the subset of 'heads' that is also in 'nodes'.
995
991
996 'roots' and 'heads' are both lists of node IDs. If 'roots' is
992 'roots' and 'heads' are both lists of node IDs. If 'roots' is
997 unspecified, uses nullid as the only root. If 'heads' is
993 unspecified, uses nullid as the only root. If 'heads' is
998 unspecified, uses list of all of the revlog's heads."""
994 unspecified, uses list of all of the revlog's heads."""
999 nonodes = ([], [], [])
995 nonodes = ([], [], [])
1000 if roots is not None:
996 if roots is not None:
1001 roots = list(roots)
997 roots = list(roots)
1002 if not roots:
998 if not roots:
1003 return nonodes
999 return nonodes
1004 lowestrev = min([self.rev(n) for n in roots])
1000 lowestrev = min([self.rev(n) for n in roots])
1005 else:
1001 else:
1006 roots = [nullid] # Everybody's a descendant of nullid
1002 roots = [nullid] # Everybody's a descendant of nullid
1007 lowestrev = nullrev
1003 lowestrev = nullrev
1008 if (lowestrev == nullrev) and (heads is None):
1004 if (lowestrev == nullrev) and (heads is None):
1009 # We want _all_ the nodes!
1005 # We want _all_ the nodes!
1010 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1006 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1011 if heads is None:
1007 if heads is None:
1012 # All nodes are ancestors, so the latest ancestor is the last
1008 # All nodes are ancestors, so the latest ancestor is the last
1013 # node.
1009 # node.
1014 highestrev = len(self) - 1
1010 highestrev = len(self) - 1
1015 # Set ancestors to None to signal that every node is an ancestor.
1011 # Set ancestors to None to signal that every node is an ancestor.
1016 ancestors = None
1012 ancestors = None
1017 # Set heads to an empty dictionary for later discovery of heads
1013 # Set heads to an empty dictionary for later discovery of heads
1018 heads = {}
1014 heads = {}
1019 else:
1015 else:
1020 heads = list(heads)
1016 heads = list(heads)
1021 if not heads:
1017 if not heads:
1022 return nonodes
1018 return nonodes
1023 ancestors = set()
1019 ancestors = set()
1024 # Turn heads into a dictionary so we can remove 'fake' heads.
1020 # Turn heads into a dictionary so we can remove 'fake' heads.
1025 # Also, later we will be using it to filter out the heads we can't
1021 # Also, later we will be using it to filter out the heads we can't
1026 # find from roots.
1022 # find from roots.
1027 heads = dict.fromkeys(heads, False)
1023 heads = dict.fromkeys(heads, False)
1028 # Start at the top and keep marking parents until we're done.
1024 # Start at the top and keep marking parents until we're done.
1029 nodestotag = set(heads)
1025 nodestotag = set(heads)
1030 # Remember where the top was so we can use it as a limit later.
1026 # Remember where the top was so we can use it as a limit later.
1031 highestrev = max([self.rev(n) for n in nodestotag])
1027 highestrev = max([self.rev(n) for n in nodestotag])
1032 while nodestotag:
1028 while nodestotag:
1033 # grab a node to tag
1029 # grab a node to tag
1034 n = nodestotag.pop()
1030 n = nodestotag.pop()
1035 # Never tag nullid
1031 # Never tag nullid
1036 if n == nullid:
1032 if n == nullid:
1037 continue
1033 continue
1038 # A node's revision number represents its place in a
1034 # A node's revision number represents its place in a
1039 # topologically sorted list of nodes.
1035 # topologically sorted list of nodes.
1040 r = self.rev(n)
1036 r = self.rev(n)
1041 if r >= lowestrev:
1037 if r >= lowestrev:
1042 if n not in ancestors:
1038 if n not in ancestors:
1043 # If we are possibly a descendant of one of the roots
1039 # If we are possibly a descendant of one of the roots
1044 # and we haven't already been marked as an ancestor
1040 # and we haven't already been marked as an ancestor
1045 ancestors.add(n) # Mark as ancestor
1041 ancestors.add(n) # Mark as ancestor
1046 # Add non-nullid parents to list of nodes to tag.
1042 # Add non-nullid parents to list of nodes to tag.
1047 nodestotag.update([p for p in self.parents(n) if
1043 nodestotag.update([p for p in self.parents(n) if
1048 p != nullid])
1044 p != nullid])
1049 elif n in heads: # We've seen it before, is it a fake head?
1045 elif n in heads: # We've seen it before, is it a fake head?
1050 # So it is, real heads should not be the ancestors of
1046 # So it is, real heads should not be the ancestors of
1051 # any other heads.
1047 # any other heads.
1052 heads.pop(n)
1048 heads.pop(n)
1053 if not ancestors:
1049 if not ancestors:
1054 return nonodes
1050 return nonodes
1055 # Now that we have our set of ancestors, we want to remove any
1051 # Now that we have our set of ancestors, we want to remove any
1056 # roots that are not ancestors.
1052 # roots that are not ancestors.
1057
1053
1058 # If one of the roots was nullid, everything is included anyway.
1054 # If one of the roots was nullid, everything is included anyway.
1059 if lowestrev > nullrev:
1055 if lowestrev > nullrev:
1060 # But, since we weren't, let's recompute the lowest rev to not
1056 # But, since we weren't, let's recompute the lowest rev to not
1061 # include roots that aren't ancestors.
1057 # include roots that aren't ancestors.
1062
1058
1063 # Filter out roots that aren't ancestors of heads
1059 # Filter out roots that aren't ancestors of heads
1064 roots = [root for root in roots if root in ancestors]
1060 roots = [root for root in roots if root in ancestors]
1065 # Recompute the lowest revision
1061 # Recompute the lowest revision
1066 if roots:
1062 if roots:
1067 lowestrev = min([self.rev(root) for root in roots])
1063 lowestrev = min([self.rev(root) for root in roots])
1068 else:
1064 else:
1069 # No more roots? Return empty list
1065 # No more roots? Return empty list
1070 return nonodes
1066 return nonodes
1071 else:
1067 else:
1072 # We are descending from nullid, and don't need to care about
1068 # We are descending from nullid, and don't need to care about
1073 # any other roots.
1069 # any other roots.
1074 lowestrev = nullrev
1070 lowestrev = nullrev
1075 roots = [nullid]
1071 roots = [nullid]
1076 # Transform our roots list into a set.
1072 # Transform our roots list into a set.
1077 descendants = set(roots)
1073 descendants = set(roots)
1078 # Also, keep the original roots so we can filter out roots that aren't
1074 # Also, keep the original roots so we can filter out roots that aren't
1079 # 'real' roots (i.e. are descended from other roots).
1075 # 'real' roots (i.e. are descended from other roots).
1080 roots = descendants.copy()
1076 roots = descendants.copy()
1081 # Our topologically sorted list of output nodes.
1077 # Our topologically sorted list of output nodes.
1082 orderedout = []
1078 orderedout = []
1083 # Don't start at nullid since we don't want nullid in our output list,
1079 # Don't start at nullid since we don't want nullid in our output list,
1084 # and if nullid shows up in descendants, empty parents will look like
1080 # and if nullid shows up in descendants, empty parents will look like
1085 # they're descendants.
1081 # they're descendants.
1086 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1082 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1087 n = self.node(r)
1083 n = self.node(r)
1088 isdescendant = False
1084 isdescendant = False
1089 if lowestrev == nullrev: # Everybody is a descendant of nullid
1085 if lowestrev == nullrev: # Everybody is a descendant of nullid
1090 isdescendant = True
1086 isdescendant = True
1091 elif n in descendants:
1087 elif n in descendants:
1092 # n is already a descendant
1088 # n is already a descendant
1093 isdescendant = True
1089 isdescendant = True
1094 # This check only needs to be done here because all the roots
1090 # This check only needs to be done here because all the roots
1095 # will start being marked is descendants before the loop.
1091 # will start being marked is descendants before the loop.
1096 if n in roots:
1092 if n in roots:
1097 # If n was a root, check if it's a 'real' root.
1093 # If n was a root, check if it's a 'real' root.
1098 p = tuple(self.parents(n))
1094 p = tuple(self.parents(n))
1099 # If any of its parents are descendants, it's not a root.
1095 # If any of its parents are descendants, it's not a root.
1100 if (p[0] in descendants) or (p[1] in descendants):
1096 if (p[0] in descendants) or (p[1] in descendants):
1101 roots.remove(n)
1097 roots.remove(n)
1102 else:
1098 else:
1103 p = tuple(self.parents(n))
1099 p = tuple(self.parents(n))
1104 # A node is a descendant if either of its parents are
1100 # A node is a descendant if either of its parents are
1105 # descendants. (We seeded the dependents list with the roots
1101 # descendants. (We seeded the dependents list with the roots
1106 # up there, remember?)
1102 # up there, remember?)
1107 if (p[0] in descendants) or (p[1] in descendants):
1103 if (p[0] in descendants) or (p[1] in descendants):
1108 descendants.add(n)
1104 descendants.add(n)
1109 isdescendant = True
1105 isdescendant = True
1110 if isdescendant and ((ancestors is None) or (n in ancestors)):
1106 if isdescendant and ((ancestors is None) or (n in ancestors)):
1111 # Only include nodes that are both descendants and ancestors.
1107 # Only include nodes that are both descendants and ancestors.
1112 orderedout.append(n)
1108 orderedout.append(n)
1113 if (ancestors is not None) and (n in heads):
1109 if (ancestors is not None) and (n in heads):
1114 # We're trying to figure out which heads are reachable
1110 # We're trying to figure out which heads are reachable
1115 # from roots.
1111 # from roots.
1116 # Mark this head as having been reached
1112 # Mark this head as having been reached
1117 heads[n] = True
1113 heads[n] = True
1118 elif ancestors is None:
1114 elif ancestors is None:
1119 # Otherwise, we're trying to discover the heads.
1115 # Otherwise, we're trying to discover the heads.
1120 # Assume this is a head because if it isn't, the next step
1116 # Assume this is a head because if it isn't, the next step
1121 # will eventually remove it.
1117 # will eventually remove it.
1122 heads[n] = True
1118 heads[n] = True
1123 # But, obviously its parents aren't.
1119 # But, obviously its parents aren't.
1124 for p in self.parents(n):
1120 for p in self.parents(n):
1125 heads.pop(p, None)
1121 heads.pop(p, None)
1126 heads = [head for head, flag in heads.iteritems() if flag]
1122 heads = [head for head, flag in heads.iteritems() if flag]
1127 roots = list(roots)
1123 roots = list(roots)
1128 assert orderedout
1124 assert orderedout
1129 assert roots
1125 assert roots
1130 assert heads
1126 assert heads
1131 return (orderedout, roots, heads)
1127 return (orderedout, roots, heads)
1132
1128
1133 def headrevs(self, revs=None):
1129 def headrevs(self, revs=None):
1134 if revs is None:
1130 if revs is None:
1135 try:
1131 try:
1136 return self.index.headrevs()
1132 return self.index.headrevs()
1137 except AttributeError:
1133 except AttributeError:
1138 return self._headrevs()
1134 return self._headrevs()
1139 if rustdagop is not None:
1135 if rustdagop is not None:
1140 return rustdagop.headrevs(self.index, revs)
1136 return rustdagop.headrevs(self.index, revs)
1141 return dagop.headrevs(revs, self._uncheckedparentrevs)
1137 return dagop.headrevs(revs, self._uncheckedparentrevs)
1142
1138
1143 def computephases(self, roots):
1139 def computephases(self, roots):
1144 return self.index.computephasesmapsets(roots)
1140 return self.index.computephasesmapsets(roots)
1145
1141
1146 def _headrevs(self):
1142 def _headrevs(self):
1147 count = len(self)
1143 count = len(self)
1148 if not count:
1144 if not count:
1149 return [nullrev]
1145 return [nullrev]
1150 # we won't iter over filtered rev so nobody is a head at start
1146 # we won't iter over filtered rev so nobody is a head at start
1151 ishead = [0] * (count + 1)
1147 ishead = [0] * (count + 1)
1152 index = self.index
1148 index = self.index
1153 for r in self:
1149 for r in self:
1154 ishead[r] = 1 # I may be an head
1150 ishead[r] = 1 # I may be an head
1155 e = index[r]
1151 e = index[r]
1156 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1152 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1157 return [r for r, val in enumerate(ishead) if val]
1153 return [r for r, val in enumerate(ishead) if val]
1158
1154
1159 def heads(self, start=None, stop=None):
1155 def heads(self, start=None, stop=None):
1160 """return the list of all nodes that have no children
1156 """return the list of all nodes that have no children
1161
1157
1162 if start is specified, only heads that are descendants of
1158 if start is specified, only heads that are descendants of
1163 start will be returned
1159 start will be returned
1164 if stop is specified, it will consider all the revs from stop
1160 if stop is specified, it will consider all the revs from stop
1165 as if they had no children
1161 as if they had no children
1166 """
1162 """
1167 if start is None and stop is None:
1163 if start is None and stop is None:
1168 if not len(self):
1164 if not len(self):
1169 return [nullid]
1165 return [nullid]
1170 return [self.node(r) for r in self.headrevs()]
1166 return [self.node(r) for r in self.headrevs()]
1171
1167
1172 if start is None:
1168 if start is None:
1173 start = nullrev
1169 start = nullrev
1174 else:
1170 else:
1175 start = self.rev(start)
1171 start = self.rev(start)
1176
1172
1177 stoprevs = set(self.rev(n) for n in stop or [])
1173 stoprevs = set(self.rev(n) for n in stop or [])
1178
1174
1179 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1175 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1180 stoprevs=stoprevs)
1176 stoprevs=stoprevs)
1181
1177
1182 return [self.node(rev) for rev in revs]
1178 return [self.node(rev) for rev in revs]
1183
1179
1184 def children(self, node):
1180 def children(self, node):
1185 """find the children of a given node"""
1181 """find the children of a given node"""
1186 c = []
1182 c = []
1187 p = self.rev(node)
1183 p = self.rev(node)
1188 for r in self.revs(start=p + 1):
1184 for r in self.revs(start=p + 1):
1189 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1185 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1190 if prevs:
1186 if prevs:
1191 for pr in prevs:
1187 for pr in prevs:
1192 if pr == p:
1188 if pr == p:
1193 c.append(self.node(r))
1189 c.append(self.node(r))
1194 elif p == nullrev:
1190 elif p == nullrev:
1195 c.append(self.node(r))
1191 c.append(self.node(r))
1196 return c
1192 return c
1197
1193
1198 def commonancestorsheads(self, a, b):
1194 def commonancestorsheads(self, a, b):
1199 """calculate all the heads of the common ancestors of nodes a and b"""
1195 """calculate all the heads of the common ancestors of nodes a and b"""
1200 a, b = self.rev(a), self.rev(b)
1196 a, b = self.rev(a), self.rev(b)
1201 ancs = self._commonancestorsheads(a, b)
1197 ancs = self._commonancestorsheads(a, b)
1202 return pycompat.maplist(self.node, ancs)
1198 return pycompat.maplist(self.node, ancs)
1203
1199
1204 def _commonancestorsheads(self, *revs):
1200 def _commonancestorsheads(self, *revs):
1205 """calculate all the heads of the common ancestors of revs"""
1201 """calculate all the heads of the common ancestors of revs"""
1206 try:
1202 try:
1207 ancs = self.index.commonancestorsheads(*revs)
1203 ancs = self.index.commonancestorsheads(*revs)
1208 except (AttributeError, OverflowError): # C implementation failed
1204 except (AttributeError, OverflowError): # C implementation failed
1209 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1205 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1210 return ancs
1206 return ancs
1211
1207
1212 def isancestor(self, a, b):
1208 def isancestor(self, a, b):
1213 """return True if node a is an ancestor of node b
1209 """return True if node a is an ancestor of node b
1214
1210
1215 A revision is considered an ancestor of itself."""
1211 A revision is considered an ancestor of itself."""
1216 a, b = self.rev(a), self.rev(b)
1212 a, b = self.rev(a), self.rev(b)
1217 return self.isancestorrev(a, b)
1213 return self.isancestorrev(a, b)
1218
1214
1219 def isancestorrev(self, a, b):
1215 def isancestorrev(self, a, b):
1220 """return True if revision a is an ancestor of revision b
1216 """return True if revision a is an ancestor of revision b
1221
1217
1222 A revision is considered an ancestor of itself.
1218 A revision is considered an ancestor of itself.
1223
1219
1224 The implementation of this is trivial but the use of
1220 The implementation of this is trivial but the use of
1225 reachableroots is not."""
1221 reachableroots is not."""
1226 if a == nullrev:
1222 if a == nullrev:
1227 return True
1223 return True
1228 elif a == b:
1224 elif a == b:
1229 return True
1225 return True
1230 elif a > b:
1226 elif a > b:
1231 return False
1227 return False
1232 return bool(self.reachableroots(a, [b], [a], includepath=False))
1228 return bool(self.reachableroots(a, [b], [a], includepath=False))
1233
1229
1234 def reachableroots(self, minroot, heads, roots, includepath=False):
1230 def reachableroots(self, minroot, heads, roots, includepath=False):
1235 """return (heads(::<roots> and <roots>::<heads>))
1231 """return (heads(::<roots> and <roots>::<heads>))
1236
1232
1237 If includepath is True, return (<roots>::<heads>)."""
1233 If includepath is True, return (<roots>::<heads>)."""
1238 try:
1234 try:
1239 return self.index.reachableroots2(minroot, heads, roots,
1235 return self.index.reachableroots2(minroot, heads, roots,
1240 includepath)
1236 includepath)
1241 except AttributeError:
1237 except AttributeError:
1242 return dagop._reachablerootspure(self.parentrevs,
1238 return dagop._reachablerootspure(self.parentrevs,
1243 minroot, roots, heads, includepath)
1239 minroot, roots, heads, includepath)
1244
1240
1245 def ancestor(self, a, b):
1241 def ancestor(self, a, b):
1246 """calculate the "best" common ancestor of nodes a and b"""
1242 """calculate the "best" common ancestor of nodes a and b"""
1247
1243
1248 a, b = self.rev(a), self.rev(b)
1244 a, b = self.rev(a), self.rev(b)
1249 try:
1245 try:
1250 ancs = self.index.ancestors(a, b)
1246 ancs = self.index.ancestors(a, b)
1251 except (AttributeError, OverflowError):
1247 except (AttributeError, OverflowError):
1252 ancs = ancestor.ancestors(self.parentrevs, a, b)
1248 ancs = ancestor.ancestors(self.parentrevs, a, b)
1253 if ancs:
1249 if ancs:
1254 # choose a consistent winner when there's a tie
1250 # choose a consistent winner when there's a tie
1255 return min(map(self.node, ancs))
1251 return min(map(self.node, ancs))
1256 return nullid
1252 return nullid
1257
1253
1258 def _match(self, id):
1254 def _match(self, id):
1259 if isinstance(id, int):
1255 if isinstance(id, int):
1260 # rev
1256 # rev
1261 return self.node(id)
1257 return self.node(id)
1262 if len(id) == 20:
1258 if len(id) == 20:
1263 # possibly a binary node
1259 # possibly a binary node
1264 # odds of a binary node being all hex in ASCII are 1 in 10**25
1260 # odds of a binary node being all hex in ASCII are 1 in 10**25
1265 try:
1261 try:
1266 node = id
1262 node = id
1267 self.rev(node) # quick search the index
1263 self.rev(node) # quick search the index
1268 return node
1264 return node
1269 except error.LookupError:
1265 except error.LookupError:
1270 pass # may be partial hex id
1266 pass # may be partial hex id
1271 try:
1267 try:
1272 # str(rev)
1268 # str(rev)
1273 rev = int(id)
1269 rev = int(id)
1274 if "%d" % rev != id:
1270 if "%d" % rev != id:
1275 raise ValueError
1271 raise ValueError
1276 if rev < 0:
1272 if rev < 0:
1277 rev = len(self) + rev
1273 rev = len(self) + rev
1278 if rev < 0 or rev >= len(self):
1274 if rev < 0 or rev >= len(self):
1279 raise ValueError
1275 raise ValueError
1280 return self.node(rev)
1276 return self.node(rev)
1281 except (ValueError, OverflowError):
1277 except (ValueError, OverflowError):
1282 pass
1278 pass
1283 if len(id) == 40:
1279 if len(id) == 40:
1284 try:
1280 try:
1285 # a full hex nodeid?
1281 # a full hex nodeid?
1286 node = bin(id)
1282 node = bin(id)
1287 self.rev(node)
1283 self.rev(node)
1288 return node
1284 return node
1289 except (TypeError, error.LookupError):
1285 except (TypeError, error.LookupError):
1290 pass
1286 pass
1291
1287
1292 def _partialmatch(self, id):
1288 def _partialmatch(self, id):
1293 # we don't care wdirfilenodeids as they should be always full hash
1289 # we don't care wdirfilenodeids as they should be always full hash
1294 maybewdir = wdirhex.startswith(id)
1290 maybewdir = wdirhex.startswith(id)
1295 try:
1291 try:
1296 partial = self.index.partialmatch(id)
1292 partial = self.index.partialmatch(id)
1297 if partial and self.hasnode(partial):
1293 if partial and self.hasnode(partial):
1298 if maybewdir:
1294 if maybewdir:
1299 # single 'ff...' match in radix tree, ambiguous with wdir
1295 # single 'ff...' match in radix tree, ambiguous with wdir
1300 raise error.RevlogError
1296 raise error.RevlogError
1301 return partial
1297 return partial
1302 if maybewdir:
1298 if maybewdir:
1303 # no 'ff...' match in radix tree, wdir identified
1299 # no 'ff...' match in radix tree, wdir identified
1304 raise error.WdirUnsupported
1300 raise error.WdirUnsupported
1305 return None
1301 return None
1306 except error.RevlogError:
1302 except error.RevlogError:
1307 # parsers.c radix tree lookup gave multiple matches
1303 # parsers.c radix tree lookup gave multiple matches
1308 # fast path: for unfiltered changelog, radix tree is accurate
1304 # fast path: for unfiltered changelog, radix tree is accurate
1309 if not getattr(self, 'filteredrevs', None):
1305 if not getattr(self, 'filteredrevs', None):
1310 raise error.AmbiguousPrefixLookupError(
1306 raise error.AmbiguousPrefixLookupError(
1311 id, self.indexfile, _('ambiguous identifier'))
1307 id, self.indexfile, _('ambiguous identifier'))
1312 # fall through to slow path that filters hidden revisions
1308 # fall through to slow path that filters hidden revisions
1313 except (AttributeError, ValueError):
1309 except (AttributeError, ValueError):
1314 # we are pure python, or key was too short to search radix tree
1310 # we are pure python, or key was too short to search radix tree
1315 pass
1311 pass
1316
1312
1317 if id in self._pcache:
1313 if id in self._pcache:
1318 return self._pcache[id]
1314 return self._pcache[id]
1319
1315
1320 if len(id) <= 40:
1316 if len(id) <= 40:
1321 try:
1317 try:
1322 # hex(node)[:...]
1318 # hex(node)[:...]
1323 l = len(id) // 2 # grab an even number of digits
1319 l = len(id) // 2 # grab an even number of digits
1324 prefix = bin(id[:l * 2])
1320 prefix = bin(id[:l * 2])
1325 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1321 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1326 nl = [n for n in nl if hex(n).startswith(id) and
1322 nl = [n for n in nl if hex(n).startswith(id) and
1327 self.hasnode(n)]
1323 self.hasnode(n)]
1328 if nullhex.startswith(id):
1324 if nullhex.startswith(id):
1329 nl.append(nullid)
1325 nl.append(nullid)
1330 if len(nl) > 0:
1326 if len(nl) > 0:
1331 if len(nl) == 1 and not maybewdir:
1327 if len(nl) == 1 and not maybewdir:
1332 self._pcache[id] = nl[0]
1328 self._pcache[id] = nl[0]
1333 return nl[0]
1329 return nl[0]
1334 raise error.AmbiguousPrefixLookupError(
1330 raise error.AmbiguousPrefixLookupError(
1335 id, self.indexfile, _('ambiguous identifier'))
1331 id, self.indexfile, _('ambiguous identifier'))
1336 if maybewdir:
1332 if maybewdir:
1337 raise error.WdirUnsupported
1333 raise error.WdirUnsupported
1338 return None
1334 return None
1339 except TypeError:
1335 except TypeError:
1340 pass
1336 pass
1341
1337
1342 def lookup(self, id):
1338 def lookup(self, id):
1343 """locate a node based on:
1339 """locate a node based on:
1344 - revision number or str(revision number)
1340 - revision number or str(revision number)
1345 - nodeid or subset of hex nodeid
1341 - nodeid or subset of hex nodeid
1346 """
1342 """
1347 n = self._match(id)
1343 n = self._match(id)
1348 if n is not None:
1344 if n is not None:
1349 return n
1345 return n
1350 n = self._partialmatch(id)
1346 n = self._partialmatch(id)
1351 if n:
1347 if n:
1352 return n
1348 return n
1353
1349
1354 raise error.LookupError(id, self.indexfile, _('no match found'))
1350 raise error.LookupError(id, self.indexfile, _('no match found'))
1355
1351
1356 def shortest(self, node, minlength=1):
1352 def shortest(self, node, minlength=1):
1357 """Find the shortest unambiguous prefix that matches node."""
1353 """Find the shortest unambiguous prefix that matches node."""
1358 def isvalid(prefix):
1354 def isvalid(prefix):
1359 try:
1355 try:
1360 matchednode = self._partialmatch(prefix)
1356 matchednode = self._partialmatch(prefix)
1361 except error.AmbiguousPrefixLookupError:
1357 except error.AmbiguousPrefixLookupError:
1362 return False
1358 return False
1363 except error.WdirUnsupported:
1359 except error.WdirUnsupported:
1364 # single 'ff...' match
1360 # single 'ff...' match
1365 return True
1361 return True
1366 if matchednode is None:
1362 if matchednode is None:
1367 raise error.LookupError(node, self.indexfile, _('no node'))
1363 raise error.LookupError(node, self.indexfile, _('no node'))
1368 return True
1364 return True
1369
1365
1370 def maybewdir(prefix):
1366 def maybewdir(prefix):
1371 return all(c == 'f' for c in pycompat.iterbytestr(prefix))
1367 return all(c == 'f' for c in pycompat.iterbytestr(prefix))
1372
1368
1373 hexnode = hex(node)
1369 hexnode = hex(node)
1374
1370
1375 def disambiguate(hexnode, minlength):
1371 def disambiguate(hexnode, minlength):
1376 """Disambiguate against wdirid."""
1372 """Disambiguate against wdirid."""
1377 for length in range(minlength, 41):
1373 for length in range(minlength, 41):
1378 prefix = hexnode[:length]
1374 prefix = hexnode[:length]
1379 if not maybewdir(prefix):
1375 if not maybewdir(prefix):
1380 return prefix
1376 return prefix
1381
1377
1382 if not getattr(self, 'filteredrevs', None):
1378 if not getattr(self, 'filteredrevs', None):
1383 try:
1379 try:
1384 length = max(self.index.shortest(node), minlength)
1380 length = max(self.index.shortest(node), minlength)
1385 return disambiguate(hexnode, length)
1381 return disambiguate(hexnode, length)
1386 except error.RevlogError:
1382 except error.RevlogError:
1387 if node != wdirid:
1383 if node != wdirid:
1388 raise error.LookupError(node, self.indexfile, _('no node'))
1384 raise error.LookupError(node, self.indexfile, _('no node'))
1389 except AttributeError:
1385 except AttributeError:
1390 # Fall through to pure code
1386 # Fall through to pure code
1391 pass
1387 pass
1392
1388
1393 if node == wdirid:
1389 if node == wdirid:
1394 for length in range(minlength, 41):
1390 for length in range(minlength, 41):
1395 prefix = hexnode[:length]
1391 prefix = hexnode[:length]
1396 if isvalid(prefix):
1392 if isvalid(prefix):
1397 return prefix
1393 return prefix
1398
1394
1399 for length in range(minlength, 41):
1395 for length in range(minlength, 41):
1400 prefix = hexnode[:length]
1396 prefix = hexnode[:length]
1401 if isvalid(prefix):
1397 if isvalid(prefix):
1402 return disambiguate(hexnode, length)
1398 return disambiguate(hexnode, length)
1403
1399
1404 def cmp(self, node, text):
1400 def cmp(self, node, text):
1405 """compare text with a given file revision
1401 """compare text with a given file revision
1406
1402
1407 returns True if text is different than what is stored.
1403 returns True if text is different than what is stored.
1408 """
1404 """
1409 p1, p2 = self.parents(node)
1405 p1, p2 = self.parents(node)
1410 return storageutil.hashrevisionsha1(text, p1, p2) != node
1406 return storageutil.hashrevisionsha1(text, p1, p2) != node
1411
1407
1412 def _cachesegment(self, offset, data):
1408 def _cachesegment(self, offset, data):
1413 """Add a segment to the revlog cache.
1409 """Add a segment to the revlog cache.
1414
1410
1415 Accepts an absolute offset and the data that is at that location.
1411 Accepts an absolute offset and the data that is at that location.
1416 """
1412 """
1417 o, d = self._chunkcache
1413 o, d = self._chunkcache
1418 # try to add to existing cache
1414 # try to add to existing cache
1419 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1415 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1420 self._chunkcache = o, d + data
1416 self._chunkcache = o, d + data
1421 else:
1417 else:
1422 self._chunkcache = offset, data
1418 self._chunkcache = offset, data
1423
1419
1424 def _readsegment(self, offset, length, df=None):
1420 def _readsegment(self, offset, length, df=None):
1425 """Load a segment of raw data from the revlog.
1421 """Load a segment of raw data from the revlog.
1426
1422
1427 Accepts an absolute offset, length to read, and an optional existing
1423 Accepts an absolute offset, length to read, and an optional existing
1428 file handle to read from.
1424 file handle to read from.
1429
1425
1430 If an existing file handle is passed, it will be seeked and the
1426 If an existing file handle is passed, it will be seeked and the
1431 original seek position will NOT be restored.
1427 original seek position will NOT be restored.
1432
1428
1433 Returns a str or buffer of raw byte data.
1429 Returns a str or buffer of raw byte data.
1434
1430
1435 Raises if the requested number of bytes could not be read.
1431 Raises if the requested number of bytes could not be read.
1436 """
1432 """
1437 # Cache data both forward and backward around the requested
1433 # Cache data both forward and backward around the requested
1438 # data, in a fixed size window. This helps speed up operations
1434 # data, in a fixed size window. This helps speed up operations
1439 # involving reading the revlog backwards.
1435 # involving reading the revlog backwards.
1440 cachesize = self._chunkcachesize
1436 cachesize = self._chunkcachesize
1441 realoffset = offset & ~(cachesize - 1)
1437 realoffset = offset & ~(cachesize - 1)
1442 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1438 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1443 - realoffset)
1439 - realoffset)
1444 with self._datareadfp(df) as df:
1440 with self._datareadfp(df) as df:
1445 df.seek(realoffset)
1441 df.seek(realoffset)
1446 d = df.read(reallength)
1442 d = df.read(reallength)
1447
1443
1448 self._cachesegment(realoffset, d)
1444 self._cachesegment(realoffset, d)
1449 if offset != realoffset or reallength != length:
1445 if offset != realoffset or reallength != length:
1450 startoffset = offset - realoffset
1446 startoffset = offset - realoffset
1451 if len(d) - startoffset < length:
1447 if len(d) - startoffset < length:
1452 raise error.RevlogError(
1448 raise error.RevlogError(
1453 _('partial read of revlog %s; expected %d bytes from '
1449 _('partial read of revlog %s; expected %d bytes from '
1454 'offset %d, got %d') %
1450 'offset %d, got %d') %
1455 (self.indexfile if self._inline else self.datafile,
1451 (self.indexfile if self._inline else self.datafile,
1456 length, realoffset, len(d) - startoffset))
1452 length, realoffset, len(d) - startoffset))
1457
1453
1458 return util.buffer(d, startoffset, length)
1454 return util.buffer(d, startoffset, length)
1459
1455
1460 if len(d) < length:
1456 if len(d) < length:
1461 raise error.RevlogError(
1457 raise error.RevlogError(
1462 _('partial read of revlog %s; expected %d bytes from offset '
1458 _('partial read of revlog %s; expected %d bytes from offset '
1463 '%d, got %d') %
1459 '%d, got %d') %
1464 (self.indexfile if self._inline else self.datafile,
1460 (self.indexfile if self._inline else self.datafile,
1465 length, offset, len(d)))
1461 length, offset, len(d)))
1466
1462
1467 return d
1463 return d
1468
1464
1469 def _getsegment(self, offset, length, df=None):
1465 def _getsegment(self, offset, length, df=None):
1470 """Obtain a segment of raw data from the revlog.
1466 """Obtain a segment of raw data from the revlog.
1471
1467
1472 Accepts an absolute offset, length of bytes to obtain, and an
1468 Accepts an absolute offset, length of bytes to obtain, and an
1473 optional file handle to the already-opened revlog. If the file
1469 optional file handle to the already-opened revlog. If the file
1474 handle is used, it's original seek position will not be preserved.
1470 handle is used, it's original seek position will not be preserved.
1475
1471
1476 Requests for data may be returned from a cache.
1472 Requests for data may be returned from a cache.
1477
1473
1478 Returns a str or a buffer instance of raw byte data.
1474 Returns a str or a buffer instance of raw byte data.
1479 """
1475 """
1480 o, d = self._chunkcache
1476 o, d = self._chunkcache
1481 l = len(d)
1477 l = len(d)
1482
1478
1483 # is it in the cache?
1479 # is it in the cache?
1484 cachestart = offset - o
1480 cachestart = offset - o
1485 cacheend = cachestart + length
1481 cacheend = cachestart + length
1486 if cachestart >= 0 and cacheend <= l:
1482 if cachestart >= 0 and cacheend <= l:
1487 if cachestart == 0 and cacheend == l:
1483 if cachestart == 0 and cacheend == l:
1488 return d # avoid a copy
1484 return d # avoid a copy
1489 return util.buffer(d, cachestart, cacheend - cachestart)
1485 return util.buffer(d, cachestart, cacheend - cachestart)
1490
1486
1491 return self._readsegment(offset, length, df=df)
1487 return self._readsegment(offset, length, df=df)
1492
1488
1493 def _getsegmentforrevs(self, startrev, endrev, df=None):
1489 def _getsegmentforrevs(self, startrev, endrev, df=None):
1494 """Obtain a segment of raw data corresponding to a range of revisions.
1490 """Obtain a segment of raw data corresponding to a range of revisions.
1495
1491
1496 Accepts the start and end revisions and an optional already-open
1492 Accepts the start and end revisions and an optional already-open
1497 file handle to be used for reading. If the file handle is read, its
1493 file handle to be used for reading. If the file handle is read, its
1498 seek position will not be preserved.
1494 seek position will not be preserved.
1499
1495
1500 Requests for data may be satisfied by a cache.
1496 Requests for data may be satisfied by a cache.
1501
1497
1502 Returns a 2-tuple of (offset, data) for the requested range of
1498 Returns a 2-tuple of (offset, data) for the requested range of
1503 revisions. Offset is the integer offset from the beginning of the
1499 revisions. Offset is the integer offset from the beginning of the
1504 revlog and data is a str or buffer of the raw byte data.
1500 revlog and data is a str or buffer of the raw byte data.
1505
1501
1506 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1502 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1507 to determine where each revision's data begins and ends.
1503 to determine where each revision's data begins and ends.
1508 """
1504 """
1509 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1505 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1510 # (functions are expensive).
1506 # (functions are expensive).
1511 index = self.index
1507 index = self.index
1512 istart = index[startrev]
1508 istart = index[startrev]
1513 start = int(istart[0] >> 16)
1509 start = int(istart[0] >> 16)
1514 if startrev == endrev:
1510 if startrev == endrev:
1515 end = start + istart[1]
1511 end = start + istart[1]
1516 else:
1512 else:
1517 iend = index[endrev]
1513 iend = index[endrev]
1518 end = int(iend[0] >> 16) + iend[1]
1514 end = int(iend[0] >> 16) + iend[1]
1519
1515
1520 if self._inline:
1516 if self._inline:
1521 start += (startrev + 1) * self._io.size
1517 start += (startrev + 1) * self._io.size
1522 end += (endrev + 1) * self._io.size
1518 end += (endrev + 1) * self._io.size
1523 length = end - start
1519 length = end - start
1524
1520
1525 return start, self._getsegment(start, length, df=df)
1521 return start, self._getsegment(start, length, df=df)
1526
1522
1527 def _chunk(self, rev, df=None):
1523 def _chunk(self, rev, df=None):
1528 """Obtain a single decompressed chunk for a revision.
1524 """Obtain a single decompressed chunk for a revision.
1529
1525
1530 Accepts an integer revision and an optional already-open file handle
1526 Accepts an integer revision and an optional already-open file handle
1531 to be used for reading. If used, the seek position of the file will not
1527 to be used for reading. If used, the seek position of the file will not
1532 be preserved.
1528 be preserved.
1533
1529
1534 Returns a str holding uncompressed data for the requested revision.
1530 Returns a str holding uncompressed data for the requested revision.
1535 """
1531 """
1536 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1532 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1537
1533
1538 def _chunks(self, revs, df=None, targetsize=None):
1534 def _chunks(self, revs, df=None, targetsize=None):
1539 """Obtain decompressed chunks for the specified revisions.
1535 """Obtain decompressed chunks for the specified revisions.
1540
1536
1541 Accepts an iterable of numeric revisions that are assumed to be in
1537 Accepts an iterable of numeric revisions that are assumed to be in
1542 ascending order. Also accepts an optional already-open file handle
1538 ascending order. Also accepts an optional already-open file handle
1543 to be used for reading. If used, the seek position of the file will
1539 to be used for reading. If used, the seek position of the file will
1544 not be preserved.
1540 not be preserved.
1545
1541
1546 This function is similar to calling ``self._chunk()`` multiple times,
1542 This function is similar to calling ``self._chunk()`` multiple times,
1547 but is faster.
1543 but is faster.
1548
1544
1549 Returns a list with decompressed data for each requested revision.
1545 Returns a list with decompressed data for each requested revision.
1550 """
1546 """
1551 if not revs:
1547 if not revs:
1552 return []
1548 return []
1553 start = self.start
1549 start = self.start
1554 length = self.length
1550 length = self.length
1555 inline = self._inline
1551 inline = self._inline
1556 iosize = self._io.size
1552 iosize = self._io.size
1557 buffer = util.buffer
1553 buffer = util.buffer
1558
1554
1559 l = []
1555 l = []
1560 ladd = l.append
1556 ladd = l.append
1561
1557
1562 if not self._withsparseread:
1558 if not self._withsparseread:
1563 slicedchunks = (revs,)
1559 slicedchunks = (revs,)
1564 else:
1560 else:
1565 slicedchunks = deltautil.slicechunk(self, revs,
1561 slicedchunks = deltautil.slicechunk(self, revs,
1566 targetsize=targetsize)
1562 targetsize=targetsize)
1567
1563
1568 for revschunk in slicedchunks:
1564 for revschunk in slicedchunks:
1569 firstrev = revschunk[0]
1565 firstrev = revschunk[0]
1570 # Skip trailing revisions with empty diff
1566 # Skip trailing revisions with empty diff
1571 for lastrev in revschunk[::-1]:
1567 for lastrev in revschunk[::-1]:
1572 if length(lastrev) != 0:
1568 if length(lastrev) != 0:
1573 break
1569 break
1574
1570
1575 try:
1571 try:
1576 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1572 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1577 except OverflowError:
1573 except OverflowError:
1578 # issue4215 - we can't cache a run of chunks greater than
1574 # issue4215 - we can't cache a run of chunks greater than
1579 # 2G on Windows
1575 # 2G on Windows
1580 return [self._chunk(rev, df=df) for rev in revschunk]
1576 return [self._chunk(rev, df=df) for rev in revschunk]
1581
1577
1582 decomp = self.decompress
1578 decomp = self.decompress
1583 for rev in revschunk:
1579 for rev in revschunk:
1584 chunkstart = start(rev)
1580 chunkstart = start(rev)
1585 if inline:
1581 if inline:
1586 chunkstart += (rev + 1) * iosize
1582 chunkstart += (rev + 1) * iosize
1587 chunklength = length(rev)
1583 chunklength = length(rev)
1588 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1584 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1589
1585
1590 return l
1586 return l
1591
1587
1592 def _chunkclear(self):
1588 def _chunkclear(self):
1593 """Clear the raw chunk cache."""
1589 """Clear the raw chunk cache."""
1594 self._chunkcache = (0, '')
1590 self._chunkcache = (0, '')
1595
1591
1596 def deltaparent(self, rev):
1592 def deltaparent(self, rev):
1597 """return deltaparent of the given revision"""
1593 """return deltaparent of the given revision"""
1598 base = self.index[rev][3]
1594 base = self.index[rev][3]
1599 if base == rev:
1595 if base == rev:
1600 return nullrev
1596 return nullrev
1601 elif self._generaldelta:
1597 elif self._generaldelta:
1602 return base
1598 return base
1603 else:
1599 else:
1604 return rev - 1
1600 return rev - 1
1605
1601
1606 def issnapshot(self, rev):
1602 def issnapshot(self, rev):
1607 """tells whether rev is a snapshot
1603 """tells whether rev is a snapshot
1608 """
1604 """
1609 if not self._sparserevlog:
1605 if not self._sparserevlog:
1610 return self.deltaparent(rev) == nullrev
1606 return self.deltaparent(rev) == nullrev
1611 elif util.safehasattr(self.index, 'issnapshot'):
1607 elif util.safehasattr(self.index, 'issnapshot'):
1612 # directly assign the method to cache the testing and access
1608 # directly assign the method to cache the testing and access
1613 self.issnapshot = self.index.issnapshot
1609 self.issnapshot = self.index.issnapshot
1614 return self.issnapshot(rev)
1610 return self.issnapshot(rev)
1615 if rev == nullrev:
1611 if rev == nullrev:
1616 return True
1612 return True
1617 entry = self.index[rev]
1613 entry = self.index[rev]
1618 base = entry[3]
1614 base = entry[3]
1619 if base == rev:
1615 if base == rev:
1620 return True
1616 return True
1621 if base == nullrev:
1617 if base == nullrev:
1622 return True
1618 return True
1623 p1 = entry[5]
1619 p1 = entry[5]
1624 p2 = entry[6]
1620 p2 = entry[6]
1625 if base == p1 or base == p2:
1621 if base == p1 or base == p2:
1626 return False
1622 return False
1627 return self.issnapshot(base)
1623 return self.issnapshot(base)
1628
1624
1629 def snapshotdepth(self, rev):
1625 def snapshotdepth(self, rev):
1630 """number of snapshot in the chain before this one"""
1626 """number of snapshot in the chain before this one"""
1631 if not self.issnapshot(rev):
1627 if not self.issnapshot(rev):
1632 raise error.ProgrammingError('revision %d not a snapshot')
1628 raise error.ProgrammingError('revision %d not a snapshot')
1633 return len(self._deltachain(rev)[0]) - 1
1629 return len(self._deltachain(rev)[0]) - 1
1634
1630
1635 def revdiff(self, rev1, rev2):
1631 def revdiff(self, rev1, rev2):
1636 """return or calculate a delta between two revisions
1632 """return or calculate a delta between two revisions
1637
1633
1638 The delta calculated is in binary form and is intended to be written to
1634 The delta calculated is in binary form and is intended to be written to
1639 revlog data directly. So this function needs raw revision data.
1635 revlog data directly. So this function needs raw revision data.
1640 """
1636 """
1641 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1637 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1642 return bytes(self._chunk(rev2))
1638 return bytes(self._chunk(rev2))
1643
1639
1644 return mdiff.textdiff(self.revision(rev1, raw=True),
1640 return mdiff.textdiff(self.revision(rev1, raw=True),
1645 self.revision(rev2, raw=True))
1641 self.revision(rev2, raw=True))
1646
1642
1647 def revision(self, nodeorrev, _df=None, raw=False):
1643 def revision(self, nodeorrev, _df=None, raw=False):
1648 """return an uncompressed revision of a given node or revision
1644 """return an uncompressed revision of a given node or revision
1649 number.
1645 number.
1650
1646
1651 _df - an existing file handle to read from. (internal-only)
1647 _df - an existing file handle to read from. (internal-only)
1652 raw - an optional argument specifying if the revision data is to be
1648 raw - an optional argument specifying if the revision data is to be
1653 treated as raw data when applying flag transforms. 'raw' should be set
1649 treated as raw data when applying flag transforms. 'raw' should be set
1654 to True when generating changegroups or in debug commands.
1650 to True when generating changegroups or in debug commands.
1655 """
1651 """
1656 return self._revisiondata(nodeorrev, _df, raw=raw)
1652 return self._revisiondata(nodeorrev, _df, raw=raw)
1657
1653
1658 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1654 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1659 if isinstance(nodeorrev, int):
1655 if isinstance(nodeorrev, int):
1660 rev = nodeorrev
1656 rev = nodeorrev
1661 node = self.node(rev)
1657 node = self.node(rev)
1662 else:
1658 else:
1663 node = nodeorrev
1659 node = nodeorrev
1664 rev = None
1660 rev = None
1665
1661
1666 cachedrev = None
1662 cachedrev = None
1667 flags = None
1663 flags = None
1668 rawtext = None
1664 rawtext = None
1669 if node == nullid:
1665 if node == nullid:
1670 return ""
1666 return ""
1671 if self._revisioncache:
1667 if self._revisioncache:
1672 if self._revisioncache[0] == node:
1668 if self._revisioncache[0] == node:
1673 # _cache only stores rawtext
1669 # _cache only stores rawtext
1674 if raw:
1670 if raw:
1675 return self._revisioncache[2]
1671 return self._revisioncache[2]
1676 # duplicated, but good for perf
1672 # duplicated, but good for perf
1677 if rev is None:
1673 if rev is None:
1678 rev = self.rev(node)
1674 rev = self.rev(node)
1679 if flags is None:
1675 if flags is None:
1680 flags = self.flags(rev)
1676 flags = self.flags(rev)
1681 # no extra flags set, no flag processor runs, text = rawtext
1677 # no extra flags set, no flag processor runs, text = rawtext
1682 if flags == REVIDX_DEFAULT_FLAGS:
1678 if flags == REVIDX_DEFAULT_FLAGS:
1683 return self._revisioncache[2]
1679 return self._revisioncache[2]
1684 # rawtext is reusable. need to run flag processor
1680 # rawtext is reusable. need to run flag processor
1685 rawtext = self._revisioncache[2]
1681 rawtext = self._revisioncache[2]
1686
1682
1687 cachedrev = self._revisioncache[1]
1683 cachedrev = self._revisioncache[1]
1688
1684
1689 # look up what we need to read
1685 # look up what we need to read
1690 if rawtext is None:
1686 if rawtext is None:
1691 if rev is None:
1687 if rev is None:
1692 rev = self.rev(node)
1688 rev = self.rev(node)
1693
1689
1694 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1690 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1695 if stopped:
1691 if stopped:
1696 rawtext = self._revisioncache[2]
1692 rawtext = self._revisioncache[2]
1697
1693
1698 # drop cache to save memory
1694 # drop cache to save memory
1699 self._revisioncache = None
1695 self._revisioncache = None
1700
1696
1701 targetsize = None
1697 targetsize = None
1702 rawsize = self.index[rev][2]
1698 rawsize = self.index[rev][2]
1703 if 0 <= rawsize:
1699 if 0 <= rawsize:
1704 targetsize = 4 * rawsize
1700 targetsize = 4 * rawsize
1705
1701
1706 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1702 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1707 if rawtext is None:
1703 if rawtext is None:
1708 rawtext = bytes(bins[0])
1704 rawtext = bytes(bins[0])
1709 bins = bins[1:]
1705 bins = bins[1:]
1710
1706
1711 rawtext = mdiff.patches(rawtext, bins)
1707 rawtext = mdiff.patches(rawtext, bins)
1712 self._revisioncache = (node, rev, rawtext)
1708 self._revisioncache = (node, rev, rawtext)
1713
1709
1714 if flags is None:
1710 if flags is None:
1715 if rev is None:
1711 if rev is None:
1716 rev = self.rev(node)
1712 rev = self.rev(node)
1717 flags = self.flags(rev)
1713 flags = self.flags(rev)
1718
1714
1719 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1715 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1720 if validatehash:
1716 if validatehash:
1721 self.checkhash(text, node, rev=rev)
1717 self.checkhash(text, node, rev=rev)
1722
1718
1723 return text
1719 return text
1724
1720
1725 def rawdata(self, nodeorrev, _df=None, raw=False):
1721 def rawdata(self, nodeorrev, _df=None, raw=False):
1726 """return an uncompressed raw data of a given node or revision number.
1722 """return an uncompressed raw data of a given node or revision number.
1727
1723
1728 _df - an existing file handle to read from. (internal-only)
1724 _df - an existing file handle to read from. (internal-only)
1729 """
1725 """
1730 return self._revisiondata(nodeorrev, _df, raw=True)
1726 return self._revisiondata(nodeorrev, _df, raw=True)
1731
1727
1732 def hash(self, text, p1, p2):
1728 def hash(self, text, p1, p2):
1733 """Compute a node hash.
1729 """Compute a node hash.
1734
1730
1735 Available as a function so that subclasses can replace the hash
1731 Available as a function so that subclasses can replace the hash
1736 as needed.
1732 as needed.
1737 """
1733 """
1738 return storageutil.hashrevisionsha1(text, p1, p2)
1734 return storageutil.hashrevisionsha1(text, p1, p2)
1739
1735
1740 def _processflags(self, text, flags, operation, raw=False):
1736 def _processflags(self, text, flags, operation, raw=False):
1741 """Inspect revision data flags and applies transforms defined by
1737 """Inspect revision data flags and applies transforms defined by
1742 registered flag processors.
1738 registered flag processors.
1743
1739
1744 ``text`` - the revision data to process
1740 ``text`` - the revision data to process
1745 ``flags`` - the revision flags
1741 ``flags`` - the revision flags
1746 ``operation`` - the operation being performed (read or write)
1742 ``operation`` - the operation being performed (read or write)
1747 ``raw`` - an optional argument describing if the raw transform should be
1743 ``raw`` - an optional argument describing if the raw transform should be
1748 applied.
1744 applied.
1749
1745
1750 This method processes the flags in the order (or reverse order if
1746 This method processes the flags in the order (or reverse order if
1751 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1747 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1752 flag processors registered for present flags. The order of flags defined
1748 flag processors registered for present flags. The order of flags defined
1753 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1749 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1754
1750
1755 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1751 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1756 processed text and ``validatehash`` is a bool indicating whether the
1752 processed text and ``validatehash`` is a bool indicating whether the
1757 returned text should be checked for hash integrity.
1753 returned text should be checked for hash integrity.
1758
1754
1759 Note: If the ``raw`` argument is set, it has precedence over the
1755 Note: If the ``raw`` argument is set, it has precedence over the
1760 operation and will only update the value of ``validatehash``.
1756 operation and will only update the value of ``validatehash``.
1761 """
1757 """
1762 # fast path: no flag processors will run
1758 # fast path: no flag processors will run
1763 if flags == 0:
1759 if flags == 0:
1764 return text, True
1760 return text, True
1765 if not operation in ('read', 'write'):
1761 if not operation in ('read', 'write'):
1766 raise error.ProgrammingError(_("invalid '%s' operation") %
1762 raise error.ProgrammingError(_("invalid '%s' operation") %
1767 operation)
1763 operation)
1768 # Check all flags are known.
1764 # Check all flags are known.
1769 if flags & ~REVIDX_KNOWN_FLAGS:
1765 if flags & ~REVIDX_KNOWN_FLAGS:
1770 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1766 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1771 (flags & ~REVIDX_KNOWN_FLAGS))
1767 (flags & ~REVIDX_KNOWN_FLAGS))
1772 validatehash = True
1768 validatehash = True
1773 # Depending on the operation (read or write), the order might be
1769 # Depending on the operation (read or write), the order might be
1774 # reversed due to non-commutative transforms.
1770 # reversed due to non-commutative transforms.
1775 orderedflags = REVIDX_FLAGS_ORDER
1771 orderedflags = REVIDX_FLAGS_ORDER
1776 if operation == 'write':
1772 if operation == 'write':
1777 orderedflags = reversed(orderedflags)
1773 orderedflags = reversed(orderedflags)
1778
1774
1779 for flag in orderedflags:
1775 for flag in orderedflags:
1780 # If a flagprocessor has been registered for a known flag, apply the
1776 # If a flagprocessor has been registered for a known flag, apply the
1781 # related operation transform and update result tuple.
1777 # related operation transform and update result tuple.
1782 if flag & flags:
1778 if flag & flags:
1783 vhash = True
1779 vhash = True
1784
1780
1785 if flag not in self._flagprocessors:
1781 if flag not in self._flagprocessors:
1786 message = _("missing processor for flag '%#x'") % (flag)
1782 message = _("missing processor for flag '%#x'") % (flag)
1787 raise error.RevlogError(message)
1783 raise error.RevlogError(message)
1788
1784
1789 processor = self._flagprocessors[flag]
1785 processor = self._flagprocessors[flag]
1790 if processor is not None:
1786 if processor is not None:
1791 readtransform, writetransform, rawtransform = processor
1787 readtransform, writetransform, rawtransform = processor
1792
1788
1793 if raw:
1789 if raw:
1794 vhash = rawtransform(self, text)
1790 vhash = rawtransform(self, text)
1795 elif operation == 'read':
1791 elif operation == 'read':
1796 text, vhash = readtransform(self, text)
1792 text, vhash = readtransform(self, text)
1797 else: # write operation
1793 else: # write operation
1798 text, vhash = writetransform(self, text)
1794 text, vhash = writetransform(self, text)
1799 validatehash = validatehash and vhash
1795 validatehash = validatehash and vhash
1800
1796
1801 return text, validatehash
1797 return text, validatehash
1802
1798
1803 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1799 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1804 """Check node hash integrity.
1800 """Check node hash integrity.
1805
1801
1806 Available as a function so that subclasses can extend hash mismatch
1802 Available as a function so that subclasses can extend hash mismatch
1807 behaviors as needed.
1803 behaviors as needed.
1808 """
1804 """
1809 try:
1805 try:
1810 if p1 is None and p2 is None:
1806 if p1 is None and p2 is None:
1811 p1, p2 = self.parents(node)
1807 p1, p2 = self.parents(node)
1812 if node != self.hash(text, p1, p2):
1808 if node != self.hash(text, p1, p2):
1813 # Clear the revision cache on hash failure. The revision cache
1809 # Clear the revision cache on hash failure. The revision cache
1814 # only stores the raw revision and clearing the cache does have
1810 # only stores the raw revision and clearing the cache does have
1815 # the side-effect that we won't have a cache hit when the raw
1811 # the side-effect that we won't have a cache hit when the raw
1816 # revision data is accessed. But this case should be rare and
1812 # revision data is accessed. But this case should be rare and
1817 # it is extra work to teach the cache about the hash
1813 # it is extra work to teach the cache about the hash
1818 # verification state.
1814 # verification state.
1819 if self._revisioncache and self._revisioncache[0] == node:
1815 if self._revisioncache and self._revisioncache[0] == node:
1820 self._revisioncache = None
1816 self._revisioncache = None
1821
1817
1822 revornode = rev
1818 revornode = rev
1823 if revornode is None:
1819 if revornode is None:
1824 revornode = templatefilters.short(hex(node))
1820 revornode = templatefilters.short(hex(node))
1825 raise error.RevlogError(_("integrity check failed on %s:%s")
1821 raise error.RevlogError(_("integrity check failed on %s:%s")
1826 % (self.indexfile, pycompat.bytestr(revornode)))
1822 % (self.indexfile, pycompat.bytestr(revornode)))
1827 except error.RevlogError:
1823 except error.RevlogError:
1828 if self._censorable and storageutil.iscensoredtext(text):
1824 if self._censorable and storageutil.iscensoredtext(text):
1829 raise error.CensoredNodeError(self.indexfile, node, text)
1825 raise error.CensoredNodeError(self.indexfile, node, text)
1830 raise
1826 raise
1831
1827
1832 def _enforceinlinesize(self, tr, fp=None):
1828 def _enforceinlinesize(self, tr, fp=None):
1833 """Check if the revlog is too big for inline and convert if so.
1829 """Check if the revlog is too big for inline and convert if so.
1834
1830
1835 This should be called after revisions are added to the revlog. If the
1831 This should be called after revisions are added to the revlog. If the
1836 revlog has grown too large to be an inline revlog, it will convert it
1832 revlog has grown too large to be an inline revlog, it will convert it
1837 to use multiple index and data files.
1833 to use multiple index and data files.
1838 """
1834 """
1839 tiprev = len(self) - 1
1835 tiprev = len(self) - 1
1840 if (not self._inline or
1836 if (not self._inline or
1841 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1837 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1842 return
1838 return
1843
1839
1844 trinfo = tr.find(self.indexfile)
1840 trinfo = tr.find(self.indexfile)
1845 if trinfo is None:
1841 if trinfo is None:
1846 raise error.RevlogError(_("%s not found in the transaction")
1842 raise error.RevlogError(_("%s not found in the transaction")
1847 % self.indexfile)
1843 % self.indexfile)
1848
1844
1849 trindex = trinfo[2]
1845 trindex = trinfo[2]
1850 if trindex is not None:
1846 if trindex is not None:
1851 dataoff = self.start(trindex)
1847 dataoff = self.start(trindex)
1852 else:
1848 else:
1853 # revlog was stripped at start of transaction, use all leftover data
1849 # revlog was stripped at start of transaction, use all leftover data
1854 trindex = len(self) - 1
1850 trindex = len(self) - 1
1855 dataoff = self.end(tiprev)
1851 dataoff = self.end(tiprev)
1856
1852
1857 tr.add(self.datafile, dataoff)
1853 tr.add(self.datafile, dataoff)
1858
1854
1859 if fp:
1855 if fp:
1860 fp.flush()
1856 fp.flush()
1861 fp.close()
1857 fp.close()
1862 # We can't use the cached file handle after close(). So prevent
1858 # We can't use the cached file handle after close(). So prevent
1863 # its usage.
1859 # its usage.
1864 self._writinghandles = None
1860 self._writinghandles = None
1865
1861
1866 with self._indexfp('r') as ifh, self._datafp('w') as dfh:
1862 with self._indexfp('r') as ifh, self._datafp('w') as dfh:
1867 for r in self:
1863 for r in self:
1868 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1864 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1869
1865
1870 with self._indexfp('w') as fp:
1866 with self._indexfp('w') as fp:
1871 self.version &= ~FLAG_INLINE_DATA
1867 self.version &= ~FLAG_INLINE_DATA
1872 self._inline = False
1868 self._inline = False
1873 io = self._io
1869 io = self._io
1874 for i in self:
1870 for i in self:
1875 e = io.packentry(self.index[i], self.node, self.version, i)
1871 e = io.packentry(self.index[i], self.node, self.version, i)
1876 fp.write(e)
1872 fp.write(e)
1877
1873
1878 # the temp file replace the real index when we exit the context
1874 # the temp file replace the real index when we exit the context
1879 # manager
1875 # manager
1880
1876
1881 tr.replace(self.indexfile, trindex * self._io.size)
1877 tr.replace(self.indexfile, trindex * self._io.size)
1882 self._chunkclear()
1878 self._chunkclear()
1883
1879
1884 def _nodeduplicatecallback(self, transaction, node):
1880 def _nodeduplicatecallback(self, transaction, node):
1885 """called when trying to add a node already stored.
1881 """called when trying to add a node already stored.
1886 """
1882 """
1887
1883
1888 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1884 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1889 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1885 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1890 """add a revision to the log
1886 """add a revision to the log
1891
1887
1892 text - the revision data to add
1888 text - the revision data to add
1893 transaction - the transaction object used for rollback
1889 transaction - the transaction object used for rollback
1894 link - the linkrev data to add
1890 link - the linkrev data to add
1895 p1, p2 - the parent nodeids of the revision
1891 p1, p2 - the parent nodeids of the revision
1896 cachedelta - an optional precomputed delta
1892 cachedelta - an optional precomputed delta
1897 node - nodeid of revision; typically node is not specified, and it is
1893 node - nodeid of revision; typically node is not specified, and it is
1898 computed by default as hash(text, p1, p2), however subclasses might
1894 computed by default as hash(text, p1, p2), however subclasses might
1899 use different hashing method (and override checkhash() in such case)
1895 use different hashing method (and override checkhash() in such case)
1900 flags - the known flags to set on the revision
1896 flags - the known flags to set on the revision
1901 deltacomputer - an optional deltacomputer instance shared between
1897 deltacomputer - an optional deltacomputer instance shared between
1902 multiple calls
1898 multiple calls
1903 """
1899 """
1904 if link == nullrev:
1900 if link == nullrev:
1905 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1901 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1906 % self.indexfile)
1902 % self.indexfile)
1907
1903
1908 if flags:
1904 if flags:
1909 node = node or self.hash(text, p1, p2)
1905 node = node or self.hash(text, p1, p2)
1910
1906
1911 rawtext, validatehash = self._processflags(text, flags, 'write')
1907 rawtext, validatehash = self._processflags(text, flags, 'write')
1912
1908
1913 # If the flag processor modifies the revision data, ignore any provided
1909 # If the flag processor modifies the revision data, ignore any provided
1914 # cachedelta.
1910 # cachedelta.
1915 if rawtext != text:
1911 if rawtext != text:
1916 cachedelta = None
1912 cachedelta = None
1917
1913
1918 if len(rawtext) > _maxentrysize:
1914 if len(rawtext) > _maxentrysize:
1919 raise error.RevlogError(
1915 raise error.RevlogError(
1920 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1916 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1921 % (self.indexfile, len(rawtext)))
1917 % (self.indexfile, len(rawtext)))
1922
1918
1923 node = node or self.hash(rawtext, p1, p2)
1919 node = node or self.hash(rawtext, p1, p2)
1924 if node in self.nodemap:
1920 if node in self.nodemap:
1925 return node
1921 return node
1926
1922
1927 if validatehash:
1923 if validatehash:
1928 self.checkhash(rawtext, node, p1=p1, p2=p2)
1924 self.checkhash(rawtext, node, p1=p1, p2=p2)
1929
1925
1930 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1926 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1931 flags, cachedelta=cachedelta,
1927 flags, cachedelta=cachedelta,
1932 deltacomputer=deltacomputer)
1928 deltacomputer=deltacomputer)
1933
1929
1934 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1930 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1935 cachedelta=None, deltacomputer=None):
1931 cachedelta=None, deltacomputer=None):
1936 """add a raw revision with known flags, node and parents
1932 """add a raw revision with known flags, node and parents
1937 useful when reusing a revision not stored in this revlog (ex: received
1933 useful when reusing a revision not stored in this revlog (ex: received
1938 over wire, or read from an external bundle).
1934 over wire, or read from an external bundle).
1939 """
1935 """
1940 dfh = None
1936 dfh = None
1941 if not self._inline:
1937 if not self._inline:
1942 dfh = self._datafp("a+")
1938 dfh = self._datafp("a+")
1943 ifh = self._indexfp("a+")
1939 ifh = self._indexfp("a+")
1944 try:
1940 try:
1945 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1941 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1946 flags, cachedelta, ifh, dfh,
1942 flags, cachedelta, ifh, dfh,
1947 deltacomputer=deltacomputer)
1943 deltacomputer=deltacomputer)
1948 finally:
1944 finally:
1949 if dfh:
1945 if dfh:
1950 dfh.close()
1946 dfh.close()
1951 ifh.close()
1947 ifh.close()
1952
1948
1953 def compress(self, data):
1949 def compress(self, data):
1954 """Generate a possibly-compressed representation of data."""
1950 """Generate a possibly-compressed representation of data."""
1955 if not data:
1951 if not data:
1956 return '', data
1952 return '', data
1957
1953
1958 compressed = self._compressor.compress(data)
1954 compressed = self._compressor.compress(data)
1959
1955
1960 if compressed:
1956 if compressed:
1961 # The revlog compressor added the header in the returned data.
1957 # The revlog compressor added the header in the returned data.
1962 return '', compressed
1958 return '', compressed
1963
1959
1964 if data[0:1] == '\0':
1960 if data[0:1] == '\0':
1965 return '', data
1961 return '', data
1966 return 'u', data
1962 return 'u', data
1967
1963
1968 def decompress(self, data):
1964 def decompress(self, data):
1969 """Decompress a revlog chunk.
1965 """Decompress a revlog chunk.
1970
1966
1971 The chunk is expected to begin with a header identifying the
1967 The chunk is expected to begin with a header identifying the
1972 format type so it can be routed to an appropriate decompressor.
1968 format type so it can be routed to an appropriate decompressor.
1973 """
1969 """
1974 if not data:
1970 if not data:
1975 return data
1971 return data
1976
1972
1977 # Revlogs are read much more frequently than they are written and many
1973 # Revlogs are read much more frequently than they are written and many
1978 # chunks only take microseconds to decompress, so performance is
1974 # chunks only take microseconds to decompress, so performance is
1979 # important here.
1975 # important here.
1980 #
1976 #
1981 # We can make a few assumptions about revlogs:
1977 # We can make a few assumptions about revlogs:
1982 #
1978 #
1983 # 1) the majority of chunks will be compressed (as opposed to inline
1979 # 1) the majority of chunks will be compressed (as opposed to inline
1984 # raw data).
1980 # raw data).
1985 # 2) decompressing *any* data will likely by at least 10x slower than
1981 # 2) decompressing *any* data will likely by at least 10x slower than
1986 # returning raw inline data.
1982 # returning raw inline data.
1987 # 3) we want to prioritize common and officially supported compression
1983 # 3) we want to prioritize common and officially supported compression
1988 # engines
1984 # engines
1989 #
1985 #
1990 # It follows that we want to optimize for "decompress compressed data
1986 # It follows that we want to optimize for "decompress compressed data
1991 # when encoded with common and officially supported compression engines"
1987 # when encoded with common and officially supported compression engines"
1992 # case over "raw data" and "data encoded by less common or non-official
1988 # case over "raw data" and "data encoded by less common or non-official
1993 # compression engines." That is why we have the inline lookup first
1989 # compression engines." That is why we have the inline lookup first
1994 # followed by the compengines lookup.
1990 # followed by the compengines lookup.
1995 #
1991 #
1996 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1992 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1997 # compressed chunks. And this matters for changelog and manifest reads.
1993 # compressed chunks. And this matters for changelog and manifest reads.
1998 t = data[0:1]
1994 t = data[0:1]
1999
1995
2000 if t == 'x':
1996 if t == 'x':
2001 try:
1997 try:
2002 return _zlibdecompress(data)
1998 return _zlibdecompress(data)
2003 except zlib.error as e:
1999 except zlib.error as e:
2004 raise error.RevlogError(_('revlog decompress error: %s') %
2000 raise error.RevlogError(_('revlog decompress error: %s') %
2005 stringutil.forcebytestr(e))
2001 stringutil.forcebytestr(e))
2006 # '\0' is more common than 'u' so it goes first.
2002 # '\0' is more common than 'u' so it goes first.
2007 elif t == '\0':
2003 elif t == '\0':
2008 return data
2004 return data
2009 elif t == 'u':
2005 elif t == 'u':
2010 return util.buffer(data, 1)
2006 return util.buffer(data, 1)
2011
2007
2012 try:
2008 try:
2013 compressor = self._decompressors[t]
2009 compressor = self._decompressors[t]
2014 except KeyError:
2010 except KeyError:
2015 try:
2011 try:
2016 engine = util.compengines.forrevlogheader(t)
2012 engine = util.compengines.forrevlogheader(t)
2017 compressor = engine.revlogcompressor(self._compengineopts)
2013 compressor = engine.revlogcompressor(self._compengineopts)
2018 self._decompressors[t] = compressor
2014 self._decompressors[t] = compressor
2019 except KeyError:
2015 except KeyError:
2020 raise error.RevlogError(_('unknown compression type %r') % t)
2016 raise error.RevlogError(_('unknown compression type %r') % t)
2021
2017
2022 return compressor.decompress(data)
2018 return compressor.decompress(data)
2023
2019
2024 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2020 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2025 cachedelta, ifh, dfh, alwayscache=False,
2021 cachedelta, ifh, dfh, alwayscache=False,
2026 deltacomputer=None):
2022 deltacomputer=None):
2027 """internal function to add revisions to the log
2023 """internal function to add revisions to the log
2028
2024
2029 see addrevision for argument descriptions.
2025 see addrevision for argument descriptions.
2030
2026
2031 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2027 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2032
2028
2033 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2029 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2034 be used.
2030 be used.
2035
2031
2036 invariants:
2032 invariants:
2037 - rawtext is optional (can be None); if not set, cachedelta must be set.
2033 - rawtext is optional (can be None); if not set, cachedelta must be set.
2038 if both are set, they must correspond to each other.
2034 if both are set, they must correspond to each other.
2039 """
2035 """
2040 if node == nullid:
2036 if node == nullid:
2041 raise error.RevlogError(_("%s: attempt to add null revision") %
2037 raise error.RevlogError(_("%s: attempt to add null revision") %
2042 self.indexfile)
2038 self.indexfile)
2043 if node == wdirid or node in wdirfilenodeids:
2039 if node == wdirid or node in wdirfilenodeids:
2044 raise error.RevlogError(_("%s: attempt to add wdir revision") %
2040 raise error.RevlogError(_("%s: attempt to add wdir revision") %
2045 self.indexfile)
2041 self.indexfile)
2046
2042
2047 if self._inline:
2043 if self._inline:
2048 fh = ifh
2044 fh = ifh
2049 else:
2045 else:
2050 fh = dfh
2046 fh = dfh
2051
2047
2052 btext = [rawtext]
2048 btext = [rawtext]
2053
2049
2054 curr = len(self)
2050 curr = len(self)
2055 prev = curr - 1
2051 prev = curr - 1
2056 offset = self.end(prev)
2052 offset = self.end(prev)
2057 p1r, p2r = self.rev(p1), self.rev(p2)
2053 p1r, p2r = self.rev(p1), self.rev(p2)
2058
2054
2059 # full versions are inserted when the needed deltas
2055 # full versions are inserted when the needed deltas
2060 # become comparable to the uncompressed text
2056 # become comparable to the uncompressed text
2061 if rawtext is None:
2057 if rawtext is None:
2062 # need rawtext size, before changed by flag processors, which is
2058 # need rawtext size, before changed by flag processors, which is
2063 # the non-raw size. use revlog explicitly to avoid filelog's extra
2059 # the non-raw size. use revlog explicitly to avoid filelog's extra
2064 # logic that might remove metadata size.
2060 # logic that might remove metadata size.
2065 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2061 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2066 cachedelta[1])
2062 cachedelta[1])
2067 else:
2063 else:
2068 textlen = len(rawtext)
2064 textlen = len(rawtext)
2069
2065
2070 if deltacomputer is None:
2066 if deltacomputer is None:
2071 deltacomputer = deltautil.deltacomputer(self)
2067 deltacomputer = deltautil.deltacomputer(self)
2072
2068
2073 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2069 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2074
2070
2075 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2071 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2076
2072
2077 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
2073 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
2078 deltainfo.base, link, p1r, p2r, node)
2074 deltainfo.base, link, p1r, p2r, node)
2079 self.index.append(e)
2075 self.index.append(e)
2080 self.nodemap[node] = curr
2076 self.nodemap[node] = curr
2081
2077
2082 # Reset the pure node cache start lookup offset to account for new
2078 # Reset the pure node cache start lookup offset to account for new
2083 # revision.
2079 # revision.
2084 if self._nodepos is not None:
2080 if self._nodepos is not None:
2085 self._nodepos = curr
2081 self._nodepos = curr
2086
2082
2087 entry = self._io.packentry(e, self.node, self.version, curr)
2083 entry = self._io.packentry(e, self.node, self.version, curr)
2088 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2084 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2089 link, offset)
2085 link, offset)
2090
2086
2091 rawtext = btext[0]
2087 rawtext = btext[0]
2092
2088
2093 if alwayscache and rawtext is None:
2089 if alwayscache and rawtext is None:
2094 rawtext = deltacomputer.buildtext(revinfo, fh)
2090 rawtext = deltacomputer.buildtext(revinfo, fh)
2095
2091
2096 if type(rawtext) == bytes: # only accept immutable objects
2092 if type(rawtext) == bytes: # only accept immutable objects
2097 self._revisioncache = (node, curr, rawtext)
2093 self._revisioncache = (node, curr, rawtext)
2098 self._chainbasecache[curr] = deltainfo.chainbase
2094 self._chainbasecache[curr] = deltainfo.chainbase
2099 return node
2095 return node
2100
2096
2101 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2097 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2102 # Files opened in a+ mode have inconsistent behavior on various
2098 # Files opened in a+ mode have inconsistent behavior on various
2103 # platforms. Windows requires that a file positioning call be made
2099 # platforms. Windows requires that a file positioning call be made
2104 # when the file handle transitions between reads and writes. See
2100 # when the file handle transitions between reads and writes. See
2105 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2101 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2106 # platforms, Python or the platform itself can be buggy. Some versions
2102 # platforms, Python or the platform itself can be buggy. Some versions
2107 # of Solaris have been observed to not append at the end of the file
2103 # of Solaris have been observed to not append at the end of the file
2108 # if the file was seeked to before the end. See issue4943 for more.
2104 # if the file was seeked to before the end. See issue4943 for more.
2109 #
2105 #
2110 # We work around this issue by inserting a seek() before writing.
2106 # We work around this issue by inserting a seek() before writing.
2111 # Note: This is likely not necessary on Python 3. However, because
2107 # Note: This is likely not necessary on Python 3. However, because
2112 # the file handle is reused for reads and may be seeked there, we need
2108 # the file handle is reused for reads and may be seeked there, we need
2113 # to be careful before changing this.
2109 # to be careful before changing this.
2114 ifh.seek(0, os.SEEK_END)
2110 ifh.seek(0, os.SEEK_END)
2115 if dfh:
2111 if dfh:
2116 dfh.seek(0, os.SEEK_END)
2112 dfh.seek(0, os.SEEK_END)
2117
2113
2118 curr = len(self) - 1
2114 curr = len(self) - 1
2119 if not self._inline:
2115 if not self._inline:
2120 transaction.add(self.datafile, offset)
2116 transaction.add(self.datafile, offset)
2121 transaction.add(self.indexfile, curr * len(entry))
2117 transaction.add(self.indexfile, curr * len(entry))
2122 if data[0]:
2118 if data[0]:
2123 dfh.write(data[0])
2119 dfh.write(data[0])
2124 dfh.write(data[1])
2120 dfh.write(data[1])
2125 ifh.write(entry)
2121 ifh.write(entry)
2126 else:
2122 else:
2127 offset += curr * self._io.size
2123 offset += curr * self._io.size
2128 transaction.add(self.indexfile, offset, curr)
2124 transaction.add(self.indexfile, offset, curr)
2129 ifh.write(entry)
2125 ifh.write(entry)
2130 ifh.write(data[0])
2126 ifh.write(data[0])
2131 ifh.write(data[1])
2127 ifh.write(data[1])
2132 self._enforceinlinesize(transaction, ifh)
2128 self._enforceinlinesize(transaction, ifh)
2133
2129
2134 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2130 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2135 """
2131 """
2136 add a delta group
2132 add a delta group
2137
2133
2138 given a set of deltas, add them to the revision log. the
2134 given a set of deltas, add them to the revision log. the
2139 first delta is against its parent, which should be in our
2135 first delta is against its parent, which should be in our
2140 log, the rest are against the previous delta.
2136 log, the rest are against the previous delta.
2141
2137
2142 If ``addrevisioncb`` is defined, it will be called with arguments of
2138 If ``addrevisioncb`` is defined, it will be called with arguments of
2143 this revlog and the node that was added.
2139 this revlog and the node that was added.
2144 """
2140 """
2145
2141
2146 if self._writinghandles:
2142 if self._writinghandles:
2147 raise error.ProgrammingError('cannot nest addgroup() calls')
2143 raise error.ProgrammingError('cannot nest addgroup() calls')
2148
2144
2149 nodes = []
2145 nodes = []
2150
2146
2151 r = len(self)
2147 r = len(self)
2152 end = 0
2148 end = 0
2153 if r:
2149 if r:
2154 end = self.end(r - 1)
2150 end = self.end(r - 1)
2155 ifh = self._indexfp("a+")
2151 ifh = self._indexfp("a+")
2156 isize = r * self._io.size
2152 isize = r * self._io.size
2157 if self._inline:
2153 if self._inline:
2158 transaction.add(self.indexfile, end + isize, r)
2154 transaction.add(self.indexfile, end + isize, r)
2159 dfh = None
2155 dfh = None
2160 else:
2156 else:
2161 transaction.add(self.indexfile, isize, r)
2157 transaction.add(self.indexfile, isize, r)
2162 transaction.add(self.datafile, end)
2158 transaction.add(self.datafile, end)
2163 dfh = self._datafp("a+")
2159 dfh = self._datafp("a+")
2164 def flush():
2160 def flush():
2165 if dfh:
2161 if dfh:
2166 dfh.flush()
2162 dfh.flush()
2167 ifh.flush()
2163 ifh.flush()
2168
2164
2169 self._writinghandles = (ifh, dfh)
2165 self._writinghandles = (ifh, dfh)
2170
2166
2171 try:
2167 try:
2172 deltacomputer = deltautil.deltacomputer(self)
2168 deltacomputer = deltautil.deltacomputer(self)
2173 # loop through our set of deltas
2169 # loop through our set of deltas
2174 for data in deltas:
2170 for data in deltas:
2175 node, p1, p2, linknode, deltabase, delta, flags = data
2171 node, p1, p2, linknode, deltabase, delta, flags = data
2176 link = linkmapper(linknode)
2172 link = linkmapper(linknode)
2177 flags = flags or REVIDX_DEFAULT_FLAGS
2173 flags = flags or REVIDX_DEFAULT_FLAGS
2178
2174
2179 nodes.append(node)
2175 nodes.append(node)
2180
2176
2181 if node in self.nodemap:
2177 if node in self.nodemap:
2182 self._nodeduplicatecallback(transaction, node)
2178 self._nodeduplicatecallback(transaction, node)
2183 # this can happen if two branches make the same change
2179 # this can happen if two branches make the same change
2184 continue
2180 continue
2185
2181
2186 for p in (p1, p2):
2182 for p in (p1, p2):
2187 if p not in self.nodemap:
2183 if p not in self.nodemap:
2188 raise error.LookupError(p, self.indexfile,
2184 raise error.LookupError(p, self.indexfile,
2189 _('unknown parent'))
2185 _('unknown parent'))
2190
2186
2191 if deltabase not in self.nodemap:
2187 if deltabase not in self.nodemap:
2192 raise error.LookupError(deltabase, self.indexfile,
2188 raise error.LookupError(deltabase, self.indexfile,
2193 _('unknown delta base'))
2189 _('unknown delta base'))
2194
2190
2195 baserev = self.rev(deltabase)
2191 baserev = self.rev(deltabase)
2196
2192
2197 if baserev != nullrev and self.iscensored(baserev):
2193 if baserev != nullrev and self.iscensored(baserev):
2198 # if base is censored, delta must be full replacement in a
2194 # if base is censored, delta must be full replacement in a
2199 # single patch operation
2195 # single patch operation
2200 hlen = struct.calcsize(">lll")
2196 hlen = struct.calcsize(">lll")
2201 oldlen = self.rawsize(baserev)
2197 oldlen = self.rawsize(baserev)
2202 newlen = len(delta) - hlen
2198 newlen = len(delta) - hlen
2203 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2199 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2204 raise error.CensoredBaseError(self.indexfile,
2200 raise error.CensoredBaseError(self.indexfile,
2205 self.node(baserev))
2201 self.node(baserev))
2206
2202
2207 if not flags and self._peek_iscensored(baserev, delta, flush):
2203 if not flags and self._peek_iscensored(baserev, delta, flush):
2208 flags |= REVIDX_ISCENSORED
2204 flags |= REVIDX_ISCENSORED
2209
2205
2210 # We assume consumers of addrevisioncb will want to retrieve
2206 # We assume consumers of addrevisioncb will want to retrieve
2211 # the added revision, which will require a call to
2207 # the added revision, which will require a call to
2212 # revision(). revision() will fast path if there is a cache
2208 # revision(). revision() will fast path if there is a cache
2213 # hit. So, we tell _addrevision() to always cache in this case.
2209 # hit. So, we tell _addrevision() to always cache in this case.
2214 # We're only using addgroup() in the context of changegroup
2210 # We're only using addgroup() in the context of changegroup
2215 # generation so the revision data can always be handled as raw
2211 # generation so the revision data can always be handled as raw
2216 # by the flagprocessor.
2212 # by the flagprocessor.
2217 self._addrevision(node, None, transaction, link,
2213 self._addrevision(node, None, transaction, link,
2218 p1, p2, flags, (baserev, delta),
2214 p1, p2, flags, (baserev, delta),
2219 ifh, dfh,
2215 ifh, dfh,
2220 alwayscache=bool(addrevisioncb),
2216 alwayscache=bool(addrevisioncb),
2221 deltacomputer=deltacomputer)
2217 deltacomputer=deltacomputer)
2222
2218
2223 if addrevisioncb:
2219 if addrevisioncb:
2224 addrevisioncb(self, node)
2220 addrevisioncb(self, node)
2225
2221
2226 if not dfh and not self._inline:
2222 if not dfh and not self._inline:
2227 # addrevision switched from inline to conventional
2223 # addrevision switched from inline to conventional
2228 # reopen the index
2224 # reopen the index
2229 ifh.close()
2225 ifh.close()
2230 dfh = self._datafp("a+")
2226 dfh = self._datafp("a+")
2231 ifh = self._indexfp("a+")
2227 ifh = self._indexfp("a+")
2232 self._writinghandles = (ifh, dfh)
2228 self._writinghandles = (ifh, dfh)
2233 finally:
2229 finally:
2234 self._writinghandles = None
2230 self._writinghandles = None
2235
2231
2236 if dfh:
2232 if dfh:
2237 dfh.close()
2233 dfh.close()
2238 ifh.close()
2234 ifh.close()
2239
2235
2240 return nodes
2236 return nodes
2241
2237
2242 def iscensored(self, rev):
2238 def iscensored(self, rev):
2243 """Check if a file revision is censored."""
2239 """Check if a file revision is censored."""
2244 if not self._censorable:
2240 if not self._censorable:
2245 return False
2241 return False
2246
2242
2247 return self.flags(rev) & REVIDX_ISCENSORED
2243 return self.flags(rev) & REVIDX_ISCENSORED
2248
2244
2249 def _peek_iscensored(self, baserev, delta, flush):
2245 def _peek_iscensored(self, baserev, delta, flush):
2250 """Quickly check if a delta produces a censored revision."""
2246 """Quickly check if a delta produces a censored revision."""
2251 if not self._censorable:
2247 if not self._censorable:
2252 return False
2248 return False
2253
2249
2254 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2250 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2255
2251
2256 def getstrippoint(self, minlink):
2252 def getstrippoint(self, minlink):
2257 """find the minimum rev that must be stripped to strip the linkrev
2253 """find the minimum rev that must be stripped to strip the linkrev
2258
2254
2259 Returns a tuple containing the minimum rev and a set of all revs that
2255 Returns a tuple containing the minimum rev and a set of all revs that
2260 have linkrevs that will be broken by this strip.
2256 have linkrevs that will be broken by this strip.
2261 """
2257 """
2262 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2258 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2263 self.headrevs(),
2259 self.headrevs(),
2264 self.linkrev, self.parentrevs)
2260 self.linkrev, self.parentrevs)
2265
2261
2266 def strip(self, minlink, transaction):
2262 def strip(self, minlink, transaction):
2267 """truncate the revlog on the first revision with a linkrev >= minlink
2263 """truncate the revlog on the first revision with a linkrev >= minlink
2268
2264
2269 This function is called when we're stripping revision minlink and
2265 This function is called when we're stripping revision minlink and
2270 its descendants from the repository.
2266 its descendants from the repository.
2271
2267
2272 We have to remove all revisions with linkrev >= minlink, because
2268 We have to remove all revisions with linkrev >= minlink, because
2273 the equivalent changelog revisions will be renumbered after the
2269 the equivalent changelog revisions will be renumbered after the
2274 strip.
2270 strip.
2275
2271
2276 So we truncate the revlog on the first of these revisions, and
2272 So we truncate the revlog on the first of these revisions, and
2277 trust that the caller has saved the revisions that shouldn't be
2273 trust that the caller has saved the revisions that shouldn't be
2278 removed and that it'll re-add them after this truncation.
2274 removed and that it'll re-add them after this truncation.
2279 """
2275 """
2280 if len(self) == 0:
2276 if len(self) == 0:
2281 return
2277 return
2282
2278
2283 rev, _ = self.getstrippoint(minlink)
2279 rev, _ = self.getstrippoint(minlink)
2284 if rev == len(self):
2280 if rev == len(self):
2285 return
2281 return
2286
2282
2287 # first truncate the files on disk
2283 # first truncate the files on disk
2288 end = self.start(rev)
2284 end = self.start(rev)
2289 if not self._inline:
2285 if not self._inline:
2290 transaction.add(self.datafile, end)
2286 transaction.add(self.datafile, end)
2291 end = rev * self._io.size
2287 end = rev * self._io.size
2292 else:
2288 else:
2293 end += rev * self._io.size
2289 end += rev * self._io.size
2294
2290
2295 transaction.add(self.indexfile, end)
2291 transaction.add(self.indexfile, end)
2296
2292
2297 # then reset internal state in memory to forget those revisions
2293 # then reset internal state in memory to forget those revisions
2298 self._revisioncache = None
2294 self._revisioncache = None
2299 self._chaininfocache = {}
2295 self._chaininfocache = {}
2300 self._chunkclear()
2296 self._chunkclear()
2301 for x in pycompat.xrange(rev, len(self)):
2297 for x in pycompat.xrange(rev, len(self)):
2302 del self.nodemap[self.node(x)]
2298 del self.nodemap[self.node(x)]
2303
2299
2304 del self.index[rev:-1]
2300 del self.index[rev:-1]
2305 self._nodepos = None
2301 self._nodepos = None
2306
2302
2307 def checksize(self):
2303 def checksize(self):
2308 """Check size of index and data files
2304 """Check size of index and data files
2309
2305
2310 return a (dd, di) tuple.
2306 return a (dd, di) tuple.
2311 - dd: extra bytes for the "data" file
2307 - dd: extra bytes for the "data" file
2312 - di: extra bytes for the "index" file
2308 - di: extra bytes for the "index" file
2313
2309
2314 A healthy revlog will return (0, 0).
2310 A healthy revlog will return (0, 0).
2315 """
2311 """
2316 expected = 0
2312 expected = 0
2317 if len(self):
2313 if len(self):
2318 expected = max(0, self.end(len(self) - 1))
2314 expected = max(0, self.end(len(self) - 1))
2319
2315
2320 try:
2316 try:
2321 with self._datafp() as f:
2317 with self._datafp() as f:
2322 f.seek(0, io.SEEK_END)
2318 f.seek(0, io.SEEK_END)
2323 actual = f.tell()
2319 actual = f.tell()
2324 dd = actual - expected
2320 dd = actual - expected
2325 except IOError as inst:
2321 except IOError as inst:
2326 if inst.errno != errno.ENOENT:
2322 if inst.errno != errno.ENOENT:
2327 raise
2323 raise
2328 dd = 0
2324 dd = 0
2329
2325
2330 try:
2326 try:
2331 f = self.opener(self.indexfile)
2327 f = self.opener(self.indexfile)
2332 f.seek(0, io.SEEK_END)
2328 f.seek(0, io.SEEK_END)
2333 actual = f.tell()
2329 actual = f.tell()
2334 f.close()
2330 f.close()
2335 s = self._io.size
2331 s = self._io.size
2336 i = max(0, actual // s)
2332 i = max(0, actual // s)
2337 di = actual - (i * s)
2333 di = actual - (i * s)
2338 if self._inline:
2334 if self._inline:
2339 databytes = 0
2335 databytes = 0
2340 for r in self:
2336 for r in self:
2341 databytes += max(0, self.length(r))
2337 databytes += max(0, self.length(r))
2342 dd = 0
2338 dd = 0
2343 di = actual - len(self) * s - databytes
2339 di = actual - len(self) * s - databytes
2344 except IOError as inst:
2340 except IOError as inst:
2345 if inst.errno != errno.ENOENT:
2341 if inst.errno != errno.ENOENT:
2346 raise
2342 raise
2347 di = 0
2343 di = 0
2348
2344
2349 return (dd, di)
2345 return (dd, di)
2350
2346
2351 def files(self):
2347 def files(self):
2352 res = [self.indexfile]
2348 res = [self.indexfile]
2353 if not self._inline:
2349 if not self._inline:
2354 res.append(self.datafile)
2350 res.append(self.datafile)
2355 return res
2351 return res
2356
2352
2357 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2353 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2358 assumehaveparentrevisions=False,
2354 assumehaveparentrevisions=False,
2359 deltamode=repository.CG_DELTAMODE_STD):
2355 deltamode=repository.CG_DELTAMODE_STD):
2360 if nodesorder not in ('nodes', 'storage', 'linear', None):
2356 if nodesorder not in ('nodes', 'storage', 'linear', None):
2361 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2357 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2362 nodesorder)
2358 nodesorder)
2363
2359
2364 if nodesorder is None and not self._generaldelta:
2360 if nodesorder is None and not self._generaldelta:
2365 nodesorder = 'storage'
2361 nodesorder = 'storage'
2366
2362
2367 if (not self._storedeltachains and
2363 if (not self._storedeltachains and
2368 deltamode != repository.CG_DELTAMODE_PREV):
2364 deltamode != repository.CG_DELTAMODE_PREV):
2369 deltamode = repository.CG_DELTAMODE_FULL
2365 deltamode = repository.CG_DELTAMODE_FULL
2370
2366
2371 return storageutil.emitrevisions(
2367 return storageutil.emitrevisions(
2372 self, nodes, nodesorder, revlogrevisiondelta,
2368 self, nodes, nodesorder, revlogrevisiondelta,
2373 deltaparentfn=self.deltaparent,
2369 deltaparentfn=self.deltaparent,
2374 candeltafn=self.candelta,
2370 candeltafn=self.candelta,
2375 rawsizefn=self.rawsize,
2371 rawsizefn=self.rawsize,
2376 revdifffn=self.revdiff,
2372 revdifffn=self.revdiff,
2377 flagsfn=self.flags,
2373 flagsfn=self.flags,
2378 deltamode=deltamode,
2374 deltamode=deltamode,
2379 revisiondata=revisiondata,
2375 revisiondata=revisiondata,
2380 assumehaveparentrevisions=assumehaveparentrevisions)
2376 assumehaveparentrevisions=assumehaveparentrevisions)
2381
2377
2382 DELTAREUSEALWAYS = 'always'
2378 DELTAREUSEALWAYS = 'always'
2383 DELTAREUSESAMEREVS = 'samerevs'
2379 DELTAREUSESAMEREVS = 'samerevs'
2384 DELTAREUSENEVER = 'never'
2380 DELTAREUSENEVER = 'never'
2385
2381
2386 DELTAREUSEFULLADD = 'fulladd'
2382 DELTAREUSEFULLADD = 'fulladd'
2387
2383
2388 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2384 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2389
2385
2390 def clone(self, tr, destrevlog, addrevisioncb=None,
2386 def clone(self, tr, destrevlog, addrevisioncb=None,
2391 deltareuse=DELTAREUSESAMEREVS, forcedeltabothparents=None):
2387 deltareuse=DELTAREUSESAMEREVS, forcedeltabothparents=None):
2392 """Copy this revlog to another, possibly with format changes.
2388 """Copy this revlog to another, possibly with format changes.
2393
2389
2394 The destination revlog will contain the same revisions and nodes.
2390 The destination revlog will contain the same revisions and nodes.
2395 However, it may not be bit-for-bit identical due to e.g. delta encoding
2391 However, it may not be bit-for-bit identical due to e.g. delta encoding
2396 differences.
2392 differences.
2397
2393
2398 The ``deltareuse`` argument control how deltas from the existing revlog
2394 The ``deltareuse`` argument control how deltas from the existing revlog
2399 are preserved in the destination revlog. The argument can have the
2395 are preserved in the destination revlog. The argument can have the
2400 following values:
2396 following values:
2401
2397
2402 DELTAREUSEALWAYS
2398 DELTAREUSEALWAYS
2403 Deltas will always be reused (if possible), even if the destination
2399 Deltas will always be reused (if possible), even if the destination
2404 revlog would not select the same revisions for the delta. This is the
2400 revlog would not select the same revisions for the delta. This is the
2405 fastest mode of operation.
2401 fastest mode of operation.
2406 DELTAREUSESAMEREVS
2402 DELTAREUSESAMEREVS
2407 Deltas will be reused if the destination revlog would pick the same
2403 Deltas will be reused if the destination revlog would pick the same
2408 revisions for the delta. This mode strikes a balance between speed
2404 revisions for the delta. This mode strikes a balance between speed
2409 and optimization.
2405 and optimization.
2410 DELTAREUSENEVER
2406 DELTAREUSENEVER
2411 Deltas will never be reused. This is the slowest mode of execution.
2407 Deltas will never be reused. This is the slowest mode of execution.
2412 This mode can be used to recompute deltas (e.g. if the diff/delta
2408 This mode can be used to recompute deltas (e.g. if the diff/delta
2413 algorithm changes).
2409 algorithm changes).
2414
2410
2415 Delta computation can be slow, so the choice of delta reuse policy can
2411 Delta computation can be slow, so the choice of delta reuse policy can
2416 significantly affect run time.
2412 significantly affect run time.
2417
2413
2418 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2414 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2419 two extremes. Deltas will be reused if they are appropriate. But if the
2415 two extremes. Deltas will be reused if they are appropriate. But if the
2420 delta could choose a better revision, it will do so. This means if you
2416 delta could choose a better revision, it will do so. This means if you
2421 are converting a non-generaldelta revlog to a generaldelta revlog,
2417 are converting a non-generaldelta revlog to a generaldelta revlog,
2422 deltas will be recomputed if the delta's parent isn't a parent of the
2418 deltas will be recomputed if the delta's parent isn't a parent of the
2423 revision.
2419 revision.
2424
2420
2425 In addition to the delta policy, the ``forcedeltabothparents``
2421 In addition to the delta policy, the ``forcedeltabothparents``
2426 argument controls whether to force compute deltas against both parents
2422 argument controls whether to force compute deltas against both parents
2427 for merges. By default, the current default is used.
2423 for merges. By default, the current default is used.
2428 """
2424 """
2429 if deltareuse not in self.DELTAREUSEALL:
2425 if deltareuse not in self.DELTAREUSEALL:
2430 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2426 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2431
2427
2432 if len(destrevlog):
2428 if len(destrevlog):
2433 raise ValueError(_('destination revlog is not empty'))
2429 raise ValueError(_('destination revlog is not empty'))
2434
2430
2435 if getattr(self, 'filteredrevs', None):
2431 if getattr(self, 'filteredrevs', None):
2436 raise ValueError(_('source revlog has filtered revisions'))
2432 raise ValueError(_('source revlog has filtered revisions'))
2437 if getattr(destrevlog, 'filteredrevs', None):
2433 if getattr(destrevlog, 'filteredrevs', None):
2438 raise ValueError(_('destination revlog has filtered revisions'))
2434 raise ValueError(_('destination revlog has filtered revisions'))
2439
2435
2440 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2436 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2441 # if possible.
2437 # if possible.
2442 oldlazydelta = destrevlog._lazydelta
2438 oldlazydelta = destrevlog._lazydelta
2443 oldlazydeltabase = destrevlog._lazydeltabase
2439 oldlazydeltabase = destrevlog._lazydeltabase
2444 oldamd = destrevlog._deltabothparents
2440 oldamd = destrevlog._deltabothparents
2445
2441
2446 try:
2442 try:
2447 if deltareuse == self.DELTAREUSEALWAYS:
2443 if deltareuse == self.DELTAREUSEALWAYS:
2448 destrevlog._lazydeltabase = True
2444 destrevlog._lazydeltabase = True
2449 destrevlog._lazydelta = True
2445 destrevlog._lazydelta = True
2450 elif deltareuse == self.DELTAREUSESAMEREVS:
2446 elif deltareuse == self.DELTAREUSESAMEREVS:
2451 destrevlog._lazydeltabase = False
2447 destrevlog._lazydeltabase = False
2452 destrevlog._lazydelta = True
2448 destrevlog._lazydelta = True
2453 elif deltareuse == self.DELTAREUSENEVER:
2449 elif deltareuse == self.DELTAREUSENEVER:
2454 destrevlog._lazydeltabase = False
2450 destrevlog._lazydeltabase = False
2455 destrevlog._lazydelta = False
2451 destrevlog._lazydelta = False
2456
2452
2457 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2453 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2458
2454
2459 deltacomputer = deltautil.deltacomputer(destrevlog)
2455 deltacomputer = deltautil.deltacomputer(destrevlog)
2460 index = self.index
2456 index = self.index
2461 for rev in self:
2457 for rev in self:
2462 entry = index[rev]
2458 entry = index[rev]
2463
2459
2464 # Some classes override linkrev to take filtered revs into
2460 # Some classes override linkrev to take filtered revs into
2465 # account. Use raw entry from index.
2461 # account. Use raw entry from index.
2466 flags = entry[0] & 0xffff
2462 flags = entry[0] & 0xffff
2467 linkrev = entry[4]
2463 linkrev = entry[4]
2468 p1 = index[entry[5]][7]
2464 p1 = index[entry[5]][7]
2469 p2 = index[entry[6]][7]
2465 p2 = index[entry[6]][7]
2470 node = entry[7]
2466 node = entry[7]
2471
2467
2472 # (Possibly) reuse the delta from the revlog if allowed and
2468 # (Possibly) reuse the delta from the revlog if allowed and
2473 # the revlog chunk is a delta.
2469 # the revlog chunk is a delta.
2474 cachedelta = None
2470 cachedelta = None
2475 rawtext = None
2471 rawtext = None
2476 if (deltareuse != self.DELTAREUSEFULLADD
2472 if (deltareuse != self.DELTAREUSEFULLADD
2477 and destrevlog._lazydelta):
2473 and destrevlog._lazydelta):
2478 dp = self.deltaparent(rev)
2474 dp = self.deltaparent(rev)
2479 if dp != nullrev:
2475 if dp != nullrev:
2480 cachedelta = (dp, bytes(self._chunk(rev)))
2476 cachedelta = (dp, bytes(self._chunk(rev)))
2481
2477
2482 if not cachedelta:
2478 if not cachedelta:
2483 rawtext = self.revision(rev, raw=True)
2479 rawtext = self.revision(rev, raw=True)
2484
2480
2485
2481
2486 if deltareuse == self.DELTAREUSEFULLADD:
2482 if deltareuse == self.DELTAREUSEFULLADD:
2487 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2483 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2488 cachedelta=cachedelta,
2484 cachedelta=cachedelta,
2489 node=node, flags=flags,
2485 node=node, flags=flags,
2490 deltacomputer=deltacomputer)
2486 deltacomputer=deltacomputer)
2491 else:
2487 else:
2492 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2488 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2493 checkambig=False)
2489 checkambig=False)
2494 dfh = None
2490 dfh = None
2495 if not destrevlog._inline:
2491 if not destrevlog._inline:
2496 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2492 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2497 try:
2493 try:
2498 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2494 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2499 p2, flags, cachedelta, ifh, dfh,
2495 p2, flags, cachedelta, ifh, dfh,
2500 deltacomputer=deltacomputer)
2496 deltacomputer=deltacomputer)
2501 finally:
2497 finally:
2502 if dfh:
2498 if dfh:
2503 dfh.close()
2499 dfh.close()
2504 ifh.close()
2500 ifh.close()
2505
2501
2506 if addrevisioncb:
2502 if addrevisioncb:
2507 addrevisioncb(self, rev, node)
2503 addrevisioncb(self, rev, node)
2508 finally:
2504 finally:
2509 destrevlog._lazydelta = oldlazydelta
2505 destrevlog._lazydelta = oldlazydelta
2510 destrevlog._lazydeltabase = oldlazydeltabase
2506 destrevlog._lazydeltabase = oldlazydeltabase
2511 destrevlog._deltabothparents = oldamd
2507 destrevlog._deltabothparents = oldamd
2512
2508
2513 def censorrevision(self, tr, censornode, tombstone=b''):
2509 def censorrevision(self, tr, censornode, tombstone=b''):
2514 if (self.version & 0xFFFF) == REVLOGV0:
2510 if (self.version & 0xFFFF) == REVLOGV0:
2515 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2511 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2516 self.version)
2512 self.version)
2517
2513
2518 censorrev = self.rev(censornode)
2514 censorrev = self.rev(censornode)
2519 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2515 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2520
2516
2521 if len(tombstone) > self.rawsize(censorrev):
2517 if len(tombstone) > self.rawsize(censorrev):
2522 raise error.Abort(_('censor tombstone must be no longer than '
2518 raise error.Abort(_('censor tombstone must be no longer than '
2523 'censored data'))
2519 'censored data'))
2524
2520
2525 # Rewriting the revlog in place is hard. Our strategy for censoring is
2521 # Rewriting the revlog in place is hard. Our strategy for censoring is
2526 # to create a new revlog, copy all revisions to it, then replace the
2522 # to create a new revlog, copy all revisions to it, then replace the
2527 # revlogs on transaction close.
2523 # revlogs on transaction close.
2528
2524
2529 newindexfile = self.indexfile + b'.tmpcensored'
2525 newindexfile = self.indexfile + b'.tmpcensored'
2530 newdatafile = self.datafile + b'.tmpcensored'
2526 newdatafile = self.datafile + b'.tmpcensored'
2531
2527
2532 # This is a bit dangerous. We could easily have a mismatch of state.
2528 # This is a bit dangerous. We could easily have a mismatch of state.
2533 newrl = revlog(self.opener, newindexfile, newdatafile,
2529 newrl = revlog(self.opener, newindexfile, newdatafile,
2534 censorable=True)
2530 censorable=True)
2535 newrl.version = self.version
2531 newrl.version = self.version
2536 newrl._generaldelta = self._generaldelta
2532 newrl._generaldelta = self._generaldelta
2537 newrl._io = self._io
2533 newrl._io = self._io
2538
2534
2539 for rev in self.revs():
2535 for rev in self.revs():
2540 node = self.node(rev)
2536 node = self.node(rev)
2541 p1, p2 = self.parents(node)
2537 p1, p2 = self.parents(node)
2542
2538
2543 if rev == censorrev:
2539 if rev == censorrev:
2544 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2540 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2545 p1, p2, censornode, REVIDX_ISCENSORED)
2541 p1, p2, censornode, REVIDX_ISCENSORED)
2546
2542
2547 if newrl.deltaparent(rev) != nullrev:
2543 if newrl.deltaparent(rev) != nullrev:
2548 raise error.Abort(_('censored revision stored as delta; '
2544 raise error.Abort(_('censored revision stored as delta; '
2549 'cannot censor'),
2545 'cannot censor'),
2550 hint=_('censoring of revlogs is not '
2546 hint=_('censoring of revlogs is not '
2551 'fully implemented; please report '
2547 'fully implemented; please report '
2552 'this bug'))
2548 'this bug'))
2553 continue
2549 continue
2554
2550
2555 if self.iscensored(rev):
2551 if self.iscensored(rev):
2556 if self.deltaparent(rev) != nullrev:
2552 if self.deltaparent(rev) != nullrev:
2557 raise error.Abort(_('cannot censor due to censored '
2553 raise error.Abort(_('cannot censor due to censored '
2558 'revision having delta stored'))
2554 'revision having delta stored'))
2559 rawtext = self._chunk(rev)
2555 rawtext = self._chunk(rev)
2560 else:
2556 else:
2561 rawtext = self.revision(rev, raw=True)
2557 rawtext = self.revision(rev, raw=True)
2562
2558
2563 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2559 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2564 self.flags(rev))
2560 self.flags(rev))
2565
2561
2566 tr.addbackup(self.indexfile, location='store')
2562 tr.addbackup(self.indexfile, location='store')
2567 if not self._inline:
2563 if not self._inline:
2568 tr.addbackup(self.datafile, location='store')
2564 tr.addbackup(self.datafile, location='store')
2569
2565
2570 self.opener.rename(newrl.indexfile, self.indexfile)
2566 self.opener.rename(newrl.indexfile, self.indexfile)
2571 if not self._inline:
2567 if not self._inline:
2572 self.opener.rename(newrl.datafile, self.datafile)
2568 self.opener.rename(newrl.datafile, self.datafile)
2573
2569
2574 self.clearcaches()
2570 self.clearcaches()
2575 self._loadindex()
2571 self._loadindex()
2576
2572
2577 def verifyintegrity(self, state):
2573 def verifyintegrity(self, state):
2578 """Verifies the integrity of the revlog.
2574 """Verifies the integrity of the revlog.
2579
2575
2580 Yields ``revlogproblem`` instances describing problems that are
2576 Yields ``revlogproblem`` instances describing problems that are
2581 found.
2577 found.
2582 """
2578 """
2583 dd, di = self.checksize()
2579 dd, di = self.checksize()
2584 if dd:
2580 if dd:
2585 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2581 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2586 if di:
2582 if di:
2587 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2583 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2588
2584
2589 version = self.version & 0xFFFF
2585 version = self.version & 0xFFFF
2590
2586
2591 # The verifier tells us what version revlog we should be.
2587 # The verifier tells us what version revlog we should be.
2592 if version != state['expectedversion']:
2588 if version != state['expectedversion']:
2593 yield revlogproblem(
2589 yield revlogproblem(
2594 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2590 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2595 (self.indexfile, version, state['expectedversion']))
2591 (self.indexfile, version, state['expectedversion']))
2596
2592
2597 state['skipread'] = set()
2593 state['skipread'] = set()
2598
2594
2599 for rev in self:
2595 for rev in self:
2600 node = self.node(rev)
2596 node = self.node(rev)
2601
2597
2602 # Verify contents. 4 cases to care about:
2598 # Verify contents. 4 cases to care about:
2603 #
2599 #
2604 # common: the most common case
2600 # common: the most common case
2605 # rename: with a rename
2601 # rename: with a rename
2606 # meta: file content starts with b'\1\n', the metadata
2602 # meta: file content starts with b'\1\n', the metadata
2607 # header defined in filelog.py, but without a rename
2603 # header defined in filelog.py, but without a rename
2608 # ext: content stored externally
2604 # ext: content stored externally
2609 #
2605 #
2610 # More formally, their differences are shown below:
2606 # More formally, their differences are shown below:
2611 #
2607 #
2612 # | common | rename | meta | ext
2608 # | common | rename | meta | ext
2613 # -------------------------------------------------------
2609 # -------------------------------------------------------
2614 # flags() | 0 | 0 | 0 | not 0
2610 # flags() | 0 | 0 | 0 | not 0
2615 # renamed() | False | True | False | ?
2611 # renamed() | False | True | False | ?
2616 # rawtext[0:2]=='\1\n'| False | True | True | ?
2612 # rawtext[0:2]=='\1\n'| False | True | True | ?
2617 #
2613 #
2618 # "rawtext" means the raw text stored in revlog data, which
2614 # "rawtext" means the raw text stored in revlog data, which
2619 # could be retrieved by "revision(rev, raw=True)". "text"
2615 # could be retrieved by "revision(rev, raw=True)". "text"
2620 # mentioned below is "revision(rev, raw=False)".
2616 # mentioned below is "revision(rev, raw=False)".
2621 #
2617 #
2622 # There are 3 different lengths stored physically:
2618 # There are 3 different lengths stored physically:
2623 # 1. L1: rawsize, stored in revlog index
2619 # 1. L1: rawsize, stored in revlog index
2624 # 2. L2: len(rawtext), stored in revlog data
2620 # 2. L2: len(rawtext), stored in revlog data
2625 # 3. L3: len(text), stored in revlog data if flags==0, or
2621 # 3. L3: len(text), stored in revlog data if flags==0, or
2626 # possibly somewhere else if flags!=0
2622 # possibly somewhere else if flags!=0
2627 #
2623 #
2628 # L1 should be equal to L2. L3 could be different from them.
2624 # L1 should be equal to L2. L3 could be different from them.
2629 # "text" may or may not affect commit hash depending on flag
2625 # "text" may or may not affect commit hash depending on flag
2630 # processors (see revlog.addflagprocessor).
2626 # processors (see revlog.addflagprocessor).
2631 #
2627 #
2632 # | common | rename | meta | ext
2628 # | common | rename | meta | ext
2633 # -------------------------------------------------
2629 # -------------------------------------------------
2634 # rawsize() | L1 | L1 | L1 | L1
2630 # rawsize() | L1 | L1 | L1 | L1
2635 # size() | L1 | L2-LM | L1(*) | L1 (?)
2631 # size() | L1 | L2-LM | L1(*) | L1 (?)
2636 # len(rawtext) | L2 | L2 | L2 | L2
2632 # len(rawtext) | L2 | L2 | L2 | L2
2637 # len(text) | L2 | L2 | L2 | L3
2633 # len(text) | L2 | L2 | L2 | L3
2638 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2634 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2639 #
2635 #
2640 # LM: length of metadata, depending on rawtext
2636 # LM: length of metadata, depending on rawtext
2641 # (*): not ideal, see comment in filelog.size
2637 # (*): not ideal, see comment in filelog.size
2642 # (?): could be "- len(meta)" if the resolved content has
2638 # (?): could be "- len(meta)" if the resolved content has
2643 # rename metadata
2639 # rename metadata
2644 #
2640 #
2645 # Checks needed to be done:
2641 # Checks needed to be done:
2646 # 1. length check: L1 == L2, in all cases.
2642 # 1. length check: L1 == L2, in all cases.
2647 # 2. hash check: depending on flag processor, we may need to
2643 # 2. hash check: depending on flag processor, we may need to
2648 # use either "text" (external), or "rawtext" (in revlog).
2644 # use either "text" (external), or "rawtext" (in revlog).
2649
2645
2650 try:
2646 try:
2651 skipflags = state.get('skipflags', 0)
2647 skipflags = state.get('skipflags', 0)
2652 if skipflags:
2648 if skipflags:
2653 skipflags &= self.flags(rev)
2649 skipflags &= self.flags(rev)
2654
2650
2655 if skipflags:
2651 if skipflags:
2656 state['skipread'].add(node)
2652 state['skipread'].add(node)
2657 else:
2653 else:
2658 # Side-effect: read content and verify hash.
2654 # Side-effect: read content and verify hash.
2659 self.revision(node)
2655 self.revision(node)
2660
2656
2661 l1 = self.rawsize(rev)
2657 l1 = self.rawsize(rev)
2662 l2 = len(self.revision(node, raw=True))
2658 l2 = len(self.revision(node, raw=True))
2663
2659
2664 if l1 != l2:
2660 if l1 != l2:
2665 yield revlogproblem(
2661 yield revlogproblem(
2666 error=_('unpacked size is %d, %d expected') % (l2, l1),
2662 error=_('unpacked size is %d, %d expected') % (l2, l1),
2667 node=node)
2663 node=node)
2668
2664
2669 except error.CensoredNodeError:
2665 except error.CensoredNodeError:
2670 if state['erroroncensored']:
2666 if state['erroroncensored']:
2671 yield revlogproblem(error=_('censored file data'),
2667 yield revlogproblem(error=_('censored file data'),
2672 node=node)
2668 node=node)
2673 state['skipread'].add(node)
2669 state['skipread'].add(node)
2674 except Exception as e:
2670 except Exception as e:
2675 yield revlogproblem(
2671 yield revlogproblem(
2676 error=_('unpacking %s: %s') % (short(node),
2672 error=_('unpacking %s: %s') % (short(node),
2677 stringutil.forcebytestr(e)),
2673 stringutil.forcebytestr(e)),
2678 node=node)
2674 node=node)
2679 state['skipread'].add(node)
2675 state['skipread'].add(node)
2680
2676
2681 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2677 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2682 revisionscount=False, trackedsize=False,
2678 revisionscount=False, trackedsize=False,
2683 storedsize=False):
2679 storedsize=False):
2684 d = {}
2680 d = {}
2685
2681
2686 if exclusivefiles:
2682 if exclusivefiles:
2687 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2683 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2688 if not self._inline:
2684 if not self._inline:
2689 d['exclusivefiles'].append((self.opener, self.datafile))
2685 d['exclusivefiles'].append((self.opener, self.datafile))
2690
2686
2691 if sharedfiles:
2687 if sharedfiles:
2692 d['sharedfiles'] = []
2688 d['sharedfiles'] = []
2693
2689
2694 if revisionscount:
2690 if revisionscount:
2695 d['revisionscount'] = len(self)
2691 d['revisionscount'] = len(self)
2696
2692
2697 if trackedsize:
2693 if trackedsize:
2698 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2694 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2699
2695
2700 if storedsize:
2696 if storedsize:
2701 d['storedsize'] = sum(self.opener.stat(path).st_size
2697 d['storedsize'] = sum(self.opener.stat(path).st_size
2702 for path in self.files())
2698 for path in self.files())
2703
2699
2704 return d
2700 return d
@@ -1,31 +1,35
1 # flagutils.py - code to deal with revlog flags and their processors
1 # flagutils.py - code to deal with revlog flags and their processors
2 #
2 #
3 # Copyright 2016 Remi Chaintron <remi@fb.com>
3 # Copyright 2016 Remi Chaintron <remi@fb.com>
4 # Copyright 2016-2019 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Copyright 2016-2019 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 from .constants import (
11 from .constants import (
12 REVIDX_DEFAULT_FLAGS,
12 REVIDX_DEFAULT_FLAGS,
13 REVIDX_ELLIPSIS,
13 REVIDX_ELLIPSIS,
14 REVIDX_EXTSTORED,
14 REVIDX_EXTSTORED,
15 REVIDX_FLAGS_ORDER,
15 REVIDX_FLAGS_ORDER,
16 REVIDX_ISCENSORED,
16 REVIDX_ISCENSORED,
17 REVIDX_KNOWN_FLAGS,
17 REVIDX_KNOWN_FLAGS,
18 REVIDX_RAWTEXT_CHANGING_FLAGS,
18 REVIDX_RAWTEXT_CHANGING_FLAGS,
19 )
19 )
20
20
21 # blanked usage of all the name to prevent pyflakes constraints
21 # blanked usage of all the name to prevent pyflakes constraints
22 # We need these name available in the module for extensions.
22 # We need these name available in the module for extensions.
23 REVIDX_ISCENSORED
23 REVIDX_ISCENSORED
24 REVIDX_ELLIPSIS
24 REVIDX_ELLIPSIS
25 REVIDX_EXTSTORED
25 REVIDX_EXTSTORED
26 REVIDX_DEFAULT_FLAGS
26 REVIDX_DEFAULT_FLAGS
27 REVIDX_FLAGS_ORDER
27 REVIDX_FLAGS_ORDER
28 REVIDX_KNOWN_FLAGS
28 REVIDX_KNOWN_FLAGS
29 REVIDX_RAWTEXT_CHANGING_FLAGS
29 REVIDX_RAWTEXT_CHANGING_FLAGS
30
30
31 # Store flag processors (cf. 'addflagprocessor()' to register)
32 flagprocessors = {
33 REVIDX_ISCENSORED: None,
34 }
31
35
@@ -1,304 +1,304
1 # Create server
1 # Create server
2 $ hg init server
2 $ hg init server
3 $ cd server
3 $ cd server
4 $ cat >> .hg/hgrc << EOF
4 $ cat >> .hg/hgrc << EOF
5 > [extensions]
5 > [extensions]
6 > extension=$TESTDIR/flagprocessorext.py
6 > extension=$TESTDIR/flagprocessorext.py
7 > EOF
7 > EOF
8 $ cd ../
8 $ cd ../
9
9
10 # Clone server and enable extensions
10 # Clone server and enable extensions
11 $ hg clone -q server client
11 $ hg clone -q server client
12 $ cd client
12 $ cd client
13 $ cat >> .hg/hgrc << EOF
13 $ cat >> .hg/hgrc << EOF
14 > [extensions]
14 > [extensions]
15 > extension=$TESTDIR/flagprocessorext.py
15 > extension=$TESTDIR/flagprocessorext.py
16 > EOF
16 > EOF
17
17
18 # Commit file that will trigger the noop extension
18 # Commit file that will trigger the noop extension
19 $ echo '[NOOP]' > noop
19 $ echo '[NOOP]' > noop
20 $ hg commit -Aqm "noop"
20 $ hg commit -Aqm "noop"
21
21
22 # Commit file that will trigger the base64 extension
22 # Commit file that will trigger the base64 extension
23 $ echo '[BASE64]' > base64
23 $ echo '[BASE64]' > base64
24 $ hg commit -Aqm 'base64'
24 $ hg commit -Aqm 'base64'
25
25
26 # Commit file that will trigger the gzip extension
26 # Commit file that will trigger the gzip extension
27 $ echo '[GZIP]' > gzip
27 $ echo '[GZIP]' > gzip
28 $ hg commit -Aqm 'gzip'
28 $ hg commit -Aqm 'gzip'
29
29
30 # Commit file that will trigger noop and base64
30 # Commit file that will trigger noop and base64
31 $ echo '[NOOP][BASE64]' > noop-base64
31 $ echo '[NOOP][BASE64]' > noop-base64
32 $ hg commit -Aqm 'noop+base64'
32 $ hg commit -Aqm 'noop+base64'
33
33
34 # Commit file that will trigger noop and gzip
34 # Commit file that will trigger noop and gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
36 $ hg commit -Aqm 'noop+gzip'
36 $ hg commit -Aqm 'noop+gzip'
37
37
38 # Commit file that will trigger base64 and gzip
38 # Commit file that will trigger base64 and gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
40 $ hg commit -Aqm 'base64+gzip'
40 $ hg commit -Aqm 'base64+gzip'
41
41
42 # Commit file that will trigger base64, gzip and noop
42 # Commit file that will trigger base64, gzip and noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 $ hg commit -Aqm 'base64+gzip+noop'
44 $ hg commit -Aqm 'base64+gzip+noop'
45
45
46 # TEST: ensure the revision data is consistent
46 # TEST: ensure the revision data is consistent
47 $ hg cat noop
47 $ hg cat noop
48 [NOOP]
48 [NOOP]
49 $ hg debugdata noop 0
49 $ hg debugdata noop 0
50 [NOOP]
50 [NOOP]
51
51
52 $ hg cat -r . base64
52 $ hg cat -r . base64
53 [BASE64]
53 [BASE64]
54 $ hg debugdata base64 0
54 $ hg debugdata base64 0
55 W0JBU0U2NF0K (no-eol)
55 W0JBU0U2NF0K (no-eol)
56
56
57 $ hg cat -r . gzip
57 $ hg cat -r . gzip
58 [GZIP]
58 [GZIP]
59 $ hg debugdata gzip 0
59 $ hg debugdata gzip 0
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61
61
62 $ hg cat -r . noop-base64
62 $ hg cat -r . noop-base64
63 [NOOP][BASE64]
63 [NOOP][BASE64]
64 $ hg debugdata noop-base64 0
64 $ hg debugdata noop-base64 0
65 W05PT1BdW0JBU0U2NF0K (no-eol)
65 W05PT1BdW0JBU0U2NF0K (no-eol)
66
66
67 $ hg cat -r . noop-gzip
67 $ hg cat -r . noop-gzip
68 [NOOP][GZIP]
68 [NOOP][GZIP]
69 $ hg debugdata noop-gzip 0
69 $ hg debugdata noop-gzip 0
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71
71
72 $ hg cat -r . base64-gzip
72 $ hg cat -r . base64-gzip
73 [BASE64][GZIP]
73 [BASE64][GZIP]
74 $ hg debugdata base64-gzip 0
74 $ hg debugdata base64-gzip 0
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76
76
77 $ hg cat -r . base64-gzip-noop
77 $ hg cat -r . base64-gzip-noop
78 [BASE64][GZIP][NOOP]
78 [BASE64][GZIP][NOOP]
79 $ hg debugdata base64-gzip-noop 0
79 $ hg debugdata base64-gzip-noop 0
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81
81
82 # Push to the server
82 # Push to the server
83 $ hg push
83 $ hg push
84 pushing to $TESTTMP/server
84 pushing to $TESTTMP/server
85 searching for changes
85 searching for changes
86 adding changesets
86 adding changesets
87 adding manifests
87 adding manifests
88 adding file changes
88 adding file changes
89 added 7 changesets with 7 changes to 7 files
89 added 7 changesets with 7 changes to 7 files
90
90
91 Ensure the data got to the server OK
91 Ensure the data got to the server OK
92
92
93 $ cd ../server
93 $ cd ../server
94 $ hg cat -r 6e48f4215d24 noop
94 $ hg cat -r 6e48f4215d24 noop
95 [NOOP]
95 [NOOP]
96 $ hg debugdata noop 0
96 $ hg debugdata noop 0
97 [NOOP]
97 [NOOP]
98
98
99 $ hg cat -r 6e48f4215d24 base64
99 $ hg cat -r 6e48f4215d24 base64
100 [BASE64]
100 [BASE64]
101 $ hg debugdata base64 0
101 $ hg debugdata base64 0
102 W0JBU0U2NF0K (no-eol)
102 W0JBU0U2NF0K (no-eol)
103
103
104 $ hg cat -r 6e48f4215d24 gzip
104 $ hg cat -r 6e48f4215d24 gzip
105 [GZIP]
105 [GZIP]
106 $ hg debugdata gzip 0
106 $ hg debugdata gzip 0
107 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
107 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
108
108
109 $ hg cat -r 6e48f4215d24 noop-base64
109 $ hg cat -r 6e48f4215d24 noop-base64
110 [NOOP][BASE64]
110 [NOOP][BASE64]
111 $ hg debugdata noop-base64 0
111 $ hg debugdata noop-base64 0
112 W05PT1BdW0JBU0U2NF0K (no-eol)
112 W05PT1BdW0JBU0U2NF0K (no-eol)
113
113
114 $ hg cat -r 6e48f4215d24 noop-gzip
114 $ hg cat -r 6e48f4215d24 noop-gzip
115 [NOOP][GZIP]
115 [NOOP][GZIP]
116 $ hg debugdata noop-gzip 0
116 $ hg debugdata noop-gzip 0
117 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
117 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
118
118
119 $ hg cat -r 6e48f4215d24 base64-gzip
119 $ hg cat -r 6e48f4215d24 base64-gzip
120 [BASE64][GZIP]
120 [BASE64][GZIP]
121 $ hg debugdata base64-gzip 0
121 $ hg debugdata base64-gzip 0
122 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
122 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
123
123
124 $ hg cat -r 6e48f4215d24 base64-gzip-noop
124 $ hg cat -r 6e48f4215d24 base64-gzip-noop
125 [BASE64][GZIP][NOOP]
125 [BASE64][GZIP][NOOP]
126 $ hg debugdata base64-gzip-noop 0
126 $ hg debugdata base64-gzip-noop 0
127 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
127 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
128
128
129 # Initialize new client (not cloning) and setup extension
129 # Initialize new client (not cloning) and setup extension
130 $ cd ..
130 $ cd ..
131 $ hg init client2
131 $ hg init client2
132 $ cd client2
132 $ cd client2
133 $ cat >> .hg/hgrc << EOF
133 $ cat >> .hg/hgrc << EOF
134 > [paths]
134 > [paths]
135 > default = $TESTTMP/server
135 > default = $TESTTMP/server
136 > [extensions]
136 > [extensions]
137 > extension=$TESTDIR/flagprocessorext.py
137 > extension=$TESTDIR/flagprocessorext.py
138 > EOF
138 > EOF
139
139
140 # Pull from server and update to latest revision
140 # Pull from server and update to latest revision
141 $ hg pull default
141 $ hg pull default
142 pulling from $TESTTMP/server
142 pulling from $TESTTMP/server
143 requesting all changes
143 requesting all changes
144 adding changesets
144 adding changesets
145 adding manifests
145 adding manifests
146 adding file changes
146 adding file changes
147 added 7 changesets with 7 changes to 7 files
147 added 7 changesets with 7 changes to 7 files
148 new changesets 07b1b9442c5b:6e48f4215d24
148 new changesets 07b1b9442c5b:6e48f4215d24
149 (run 'hg update' to get a working copy)
149 (run 'hg update' to get a working copy)
150 $ hg update
150 $ hg update
151 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
152
152
153 # TEST: ensure the revision data is consistent
153 # TEST: ensure the revision data is consistent
154 $ hg cat noop
154 $ hg cat noop
155 [NOOP]
155 [NOOP]
156 $ hg debugdata noop 0
156 $ hg debugdata noop 0
157 [NOOP]
157 [NOOP]
158
158
159 $ hg cat -r . base64
159 $ hg cat -r . base64
160 [BASE64]
160 [BASE64]
161 $ hg debugdata base64 0
161 $ hg debugdata base64 0
162 W0JBU0U2NF0K (no-eol)
162 W0JBU0U2NF0K (no-eol)
163
163
164 $ hg cat -r . gzip
164 $ hg cat -r . gzip
165 [GZIP]
165 [GZIP]
166 $ hg debugdata gzip 0
166 $ hg debugdata gzip 0
167 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
167 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
168
168
169 $ hg cat -r . noop-base64
169 $ hg cat -r . noop-base64
170 [NOOP][BASE64]
170 [NOOP][BASE64]
171 $ hg debugdata noop-base64 0
171 $ hg debugdata noop-base64 0
172 W05PT1BdW0JBU0U2NF0K (no-eol)
172 W05PT1BdW0JBU0U2NF0K (no-eol)
173
173
174 $ hg cat -r . noop-gzip
174 $ hg cat -r . noop-gzip
175 [NOOP][GZIP]
175 [NOOP][GZIP]
176 $ hg debugdata noop-gzip 0
176 $ hg debugdata noop-gzip 0
177 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
177 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
178
178
179 $ hg cat -r . base64-gzip
179 $ hg cat -r . base64-gzip
180 [BASE64][GZIP]
180 [BASE64][GZIP]
181 $ hg debugdata base64-gzip 0
181 $ hg debugdata base64-gzip 0
182 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
182 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
183
183
184 $ hg cat -r . base64-gzip-noop
184 $ hg cat -r . base64-gzip-noop
185 [BASE64][GZIP][NOOP]
185 [BASE64][GZIP][NOOP]
186 $ hg debugdata base64-gzip-noop 0
186 $ hg debugdata base64-gzip-noop 0
187 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
187 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
188
188
189 # TEST: ensure a missing processor is handled
189 # TEST: ensure a missing processor is handled
190 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
190 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
191 $ hg commit -Aqm 'fail+base64+gzip+noop'
191 $ hg commit -Aqm 'fail+base64+gzip+noop'
192 abort: missing processor for flag '0x1'!
192 abort: missing processor for flag '0x1'!
193 [255]
193 [255]
194 $ rm fail-base64-gzip-noop
194 $ rm fail-base64-gzip-noop
195
195
196 # TEST: ensure we cannot register several flag processors on the same flag
196 # TEST: ensure we cannot register several flag processors on the same flag
197 $ cat >> .hg/hgrc << EOF
197 $ cat >> .hg/hgrc << EOF
198 > [extensions]
198 > [extensions]
199 > extension=$TESTDIR/flagprocessorext.py
199 > extension=$TESTDIR/flagprocessorext.py
200 > duplicate=$TESTDIR/flagprocessorext.py
200 > duplicate=$TESTDIR/flagprocessorext.py
201 > EOF
201 > EOF
202 $ hg debugrebuilddirstate
202 $ hg debugrebuilddirstate
203 Traceback (most recent call last):
203 Traceback (most recent call last):
204 File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
204 File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
205 extsetup(ui)
205 extsetup(ui)
206 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
206 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
207 validatehash,
207 validatehash,
208 File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
208 File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
209 _insertflagprocessor(flag, processor, _flagprocessors)
209 _insertflagprocessor(flag, processor, flagutil.flagprocessors)
210 File "*/mercurial/revlog.py", line *, in _insertflagprocessor (glob)
210 File "*/mercurial/revlog.py", line *, in _insertflagprocessor (glob)
211 raise error.Abort(msg)
211 raise error.Abort(msg)
212 mercurial.error.Abort: b"cannot register multiple processors on flag '0x8'." (py3 !)
212 mercurial.error.Abort: b"cannot register multiple processors on flag '0x8'." (py3 !)
213 Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
213 Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
214 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
214 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
215 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
215 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
216 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
216 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
217 mercurial.error.Abort: b"cannot register multiple processors on flag '0x8'." (py3 !)
217 mercurial.error.Abort: b"cannot register multiple processors on flag '0x8'." (py3 !)
218 Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
218 Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
219 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
219 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
220 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
220 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
221
221
222 $ cd ..
222 $ cd ..
223
223
224 # TEST: bundle repo
224 # TEST: bundle repo
225 $ hg init bundletest
225 $ hg init bundletest
226 $ cd bundletest
226 $ cd bundletest
227
227
228 $ cat >> .hg/hgrc << EOF
228 $ cat >> .hg/hgrc << EOF
229 > [extensions]
229 > [extensions]
230 > flagprocessor=$TESTDIR/flagprocessorext.py
230 > flagprocessor=$TESTDIR/flagprocessorext.py
231 > EOF
231 > EOF
232
232
233 $ for i in 0 single two three 4; do
233 $ for i in 0 single two three 4; do
234 > echo '[BASE64]a-bit-longer-'$i > base64
234 > echo '[BASE64]a-bit-longer-'$i > base64
235 > hg commit -m base64-$i -A base64
235 > hg commit -m base64-$i -A base64
236 > done
236 > done
237
237
238 $ hg update 2 -q
238 $ hg update 2 -q
239 $ echo '[BASE64]a-bit-longer-branching' > base64
239 $ echo '[BASE64]a-bit-longer-branching' > base64
240 $ hg commit -q -m branching
240 $ hg commit -q -m branching
241
241
242 #if repobundlerepo
242 #if repobundlerepo
243 $ hg bundle --base 1 bundle.hg
243 $ hg bundle --base 1 bundle.hg
244 4 changesets found
244 4 changesets found
245 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
245 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
246 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
246 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
247 5 branching
247 5 branching
248 base64 | 2 +-
248 base64 | 2 +-
249 1 files changed, 1 insertions(+), 1 deletions(-)
249 1 files changed, 1 insertions(+), 1 deletions(-)
250
250
251 4 base64-4
251 4 base64-4
252 base64 | 2 +-
252 base64 | 2 +-
253 1 files changed, 1 insertions(+), 1 deletions(-)
253 1 files changed, 1 insertions(+), 1 deletions(-)
254
254
255 3 base64-three
255 3 base64-three
256 base64 | 2 +-
256 base64 | 2 +-
257 1 files changed, 1 insertions(+), 1 deletions(-)
257 1 files changed, 1 insertions(+), 1 deletions(-)
258
258
259 2 base64-two
259 2 base64-two
260 base64 | 2 +-
260 base64 | 2 +-
261 1 files changed, 1 insertions(+), 1 deletions(-)
261 1 files changed, 1 insertions(+), 1 deletions(-)
262
262
263 1 base64-single
263 1 base64-single
264 base64 | 2 +-
264 base64 | 2 +-
265 1 files changed, 1 insertions(+), 1 deletions(-)
265 1 files changed, 1 insertions(+), 1 deletions(-)
266
266
267 0 base64-0
267 0 base64-0
268 base64 | 1 +
268 base64 | 1 +
269 1 files changed, 1 insertions(+), 0 deletions(-)
269 1 files changed, 1 insertions(+), 0 deletions(-)
270
270
271
271
272 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
272 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
273 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
273 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
274 5 branching
274 5 branching
275 base64 | 2 +-
275 base64 | 2 +-
276 1 files changed, 1 insertions(+), 1 deletions(-)
276 1 files changed, 1 insertions(+), 1 deletions(-)
277
277
278 4 base64-4
278 4 base64-4
279 base64 | 2 +-
279 base64 | 2 +-
280 1 files changed, 1 insertions(+), 1 deletions(-)
280 1 files changed, 1 insertions(+), 1 deletions(-)
281
281
282 3 base64-three
282 3 base64-three
283 base64 | 2 +-
283 base64 | 2 +-
284 1 files changed, 1 insertions(+), 1 deletions(-)
284 1 files changed, 1 insertions(+), 1 deletions(-)
285
285
286 2 base64-two
286 2 base64-two
287 base64 | 2 +-
287 base64 | 2 +-
288 1 files changed, 1 insertions(+), 1 deletions(-)
288 1 files changed, 1 insertions(+), 1 deletions(-)
289
289
290 1 base64-single
290 1 base64-single
291 base64 | 2 +-
291 base64 | 2 +-
292 1 files changed, 1 insertions(+), 1 deletions(-)
292 1 files changed, 1 insertions(+), 1 deletions(-)
293
293
294 0 base64-0
294 0 base64-0
295 base64 | 1 +
295 base64 | 1 +
296 1 files changed, 1 insertions(+), 0 deletions(-)
296 1 files changed, 1 insertions(+), 0 deletions(-)
297
297
298 $ rm bundle.hg bundle-again.hg
298 $ rm bundle.hg bundle-again.hg
299 #endif
299 #endif
300
300
301 # TEST: hg status
301 # TEST: hg status
302
302
303 $ hg status
303 $ hg status
304 $ hg diff
304 $ hg diff
General Comments 0
You need to be logged in to leave comments. Login now