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