##// END OF EJS Templates
revlog: avoid unnecessary node -> rev conversion
Jun Wu -
r31722:4eb75c86 default
parent child Browse files
Show More
@@ -1,2102 +1,2102 b''
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 errno
17 import errno
18 import hashlib
18 import hashlib
19 import os
19 import os
20 import struct
20 import struct
21 import zlib
21 import zlib
22
22
23 # import stuff from node for others to import from revlog
23 # import stuff from node for others to import from revlog
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullid,
27 nullid,
28 nullrev,
28 nullrev,
29 )
29 )
30 from .i18n import _
30 from .i18n import _
31 from . import (
31 from . import (
32 ancestor,
32 ancestor,
33 error,
33 error,
34 mdiff,
34 mdiff,
35 parsers,
35 parsers,
36 pycompat,
36 pycompat,
37 templatefilters,
37 templatefilters,
38 util,
38 util,
39 )
39 )
40
40
41 _pack = struct.pack
41 _pack = struct.pack
42 _unpack = struct.unpack
42 _unpack = struct.unpack
43 # Aliased for performance.
43 # Aliased for performance.
44 _zlibdecompress = zlib.decompress
44 _zlibdecompress = zlib.decompress
45
45
46 # revlog header flags
46 # revlog header flags
47 REVLOGV0 = 0
47 REVLOGV0 = 0
48 REVLOGNG = 1
48 REVLOGNG = 1
49 REVLOGNGINLINEDATA = (1 << 16)
49 REVLOGNGINLINEDATA = (1 << 16)
50 REVLOGGENERALDELTA = (1 << 17)
50 REVLOGGENERALDELTA = (1 << 17)
51 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
51 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
52 REVLOG_DEFAULT_FORMAT = REVLOGNG
52 REVLOG_DEFAULT_FORMAT = REVLOGNG
53 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
53 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
54 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
54 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
55
55
56 # revlog index flags
56 # revlog index flags
57 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
57 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
58 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
58 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
59 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
59 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
60 REVIDX_DEFAULT_FLAGS = 0
60 REVIDX_DEFAULT_FLAGS = 0
61 # stable order in which flags need to be processed and their processors applied
61 # stable order in which flags need to be processed and their processors applied
62 REVIDX_FLAGS_ORDER = [
62 REVIDX_FLAGS_ORDER = [
63 REVIDX_ISCENSORED,
63 REVIDX_ISCENSORED,
64 REVIDX_ELLIPSIS,
64 REVIDX_ELLIPSIS,
65 REVIDX_EXTSTORED,
65 REVIDX_EXTSTORED,
66 ]
66 ]
67 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
67 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
68
68
69 # max size of revlog with inline data
69 # max size of revlog with inline data
70 _maxinline = 131072
70 _maxinline = 131072
71 _chunksize = 1048576
71 _chunksize = 1048576
72
72
73 RevlogError = error.RevlogError
73 RevlogError = error.RevlogError
74 LookupError = error.LookupError
74 LookupError = error.LookupError
75 CensoredNodeError = error.CensoredNodeError
75 CensoredNodeError = error.CensoredNodeError
76 ProgrammingError = error.ProgrammingError
76 ProgrammingError = error.ProgrammingError
77
77
78 # Store flag processors (cf. 'addflagprocessor()' to register)
78 # Store flag processors (cf. 'addflagprocessor()' to register)
79 _flagprocessors = {
79 _flagprocessors = {
80 REVIDX_ISCENSORED: None,
80 REVIDX_ISCENSORED: None,
81 }
81 }
82
82
83 def addflagprocessor(flag, processor):
83 def addflagprocessor(flag, processor):
84 """Register a flag processor on a revision data flag.
84 """Register a flag processor on a revision data flag.
85
85
86 Invariant:
86 Invariant:
87 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
87 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
88 - Only one flag processor can be registered on a specific flag.
88 - Only one flag processor can be registered on a specific flag.
89 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
89 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
90 following signatures:
90 following signatures:
91 - (read) f(self, text) -> newtext, bool
91 - (read) f(self, text) -> newtext, bool
92 - (write) f(self, text) -> newtext, bool
92 - (write) f(self, text) -> newtext, bool
93 - (raw) f(self, text) -> bool
93 - (raw) f(self, text) -> bool
94 The boolean returned by these transforms is used to determine whether
94 The boolean returned by these transforms is used to determine whether
95 'newtext' can be used for hash integrity checking.
95 'newtext' can be used for hash integrity checking.
96
96
97 Note: The 'raw' transform is used for changegroup generation and in some
97 Note: The 'raw' transform is used for changegroup generation and in some
98 debug commands. In this case the transform only indicates whether the
98 debug commands. In this case the transform only indicates whether the
99 contents can be used for hash integrity checks.
99 contents can be used for hash integrity checks.
100 """
100 """
101 if not flag & REVIDX_KNOWN_FLAGS:
101 if not flag & REVIDX_KNOWN_FLAGS:
102 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
102 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
103 raise ProgrammingError(msg)
103 raise ProgrammingError(msg)
104 if flag not in REVIDX_FLAGS_ORDER:
104 if flag not in REVIDX_FLAGS_ORDER:
105 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
105 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
106 raise ProgrammingError(msg)
106 raise ProgrammingError(msg)
107 if flag in _flagprocessors:
107 if flag in _flagprocessors:
108 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
108 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
109 raise error.Abort(msg)
109 raise error.Abort(msg)
110 _flagprocessors[flag] = processor
110 _flagprocessors[flag] = processor
111
111
112 def getoffset(q):
112 def getoffset(q):
113 return int(q >> 16)
113 return int(q >> 16)
114
114
115 def gettype(q):
115 def gettype(q):
116 return int(q & 0xFFFF)
116 return int(q & 0xFFFF)
117
117
118 def offset_type(offset, type):
118 def offset_type(offset, type):
119 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
119 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
120 raise ValueError('unknown revlog index flags')
120 raise ValueError('unknown revlog index flags')
121 return int(int(offset) << 16 | type)
121 return int(int(offset) << 16 | type)
122
122
123 _nullhash = hashlib.sha1(nullid)
123 _nullhash = hashlib.sha1(nullid)
124
124
125 def hash(text, p1, p2):
125 def hash(text, p1, p2):
126 """generate a hash from the given text and its parent hashes
126 """generate a hash from the given text and its parent hashes
127
127
128 This hash combines both the current file contents and its history
128 This hash combines both the current file contents and its history
129 in a manner that makes it easy to distinguish nodes with the same
129 in a manner that makes it easy to distinguish nodes with the same
130 content in the revision graph.
130 content in the revision graph.
131 """
131 """
132 # As of now, if one of the parent node is null, p2 is null
132 # As of now, if one of the parent node is null, p2 is null
133 if p2 == nullid:
133 if p2 == nullid:
134 # deep copy of a hash is faster than creating one
134 # deep copy of a hash is faster than creating one
135 s = _nullhash.copy()
135 s = _nullhash.copy()
136 s.update(p1)
136 s.update(p1)
137 else:
137 else:
138 # none of the parent nodes are nullid
138 # none of the parent nodes are nullid
139 l = [p1, p2]
139 l = [p1, p2]
140 l.sort()
140 l.sort()
141 s = hashlib.sha1(l[0])
141 s = hashlib.sha1(l[0])
142 s.update(l[1])
142 s.update(l[1])
143 s.update(text)
143 s.update(text)
144 return s.digest()
144 return s.digest()
145
145
146 # index v0:
146 # index v0:
147 # 4 bytes: offset
147 # 4 bytes: offset
148 # 4 bytes: compressed length
148 # 4 bytes: compressed length
149 # 4 bytes: base rev
149 # 4 bytes: base rev
150 # 4 bytes: link rev
150 # 4 bytes: link rev
151 # 20 bytes: parent 1 nodeid
151 # 20 bytes: parent 1 nodeid
152 # 20 bytes: parent 2 nodeid
152 # 20 bytes: parent 2 nodeid
153 # 20 bytes: nodeid
153 # 20 bytes: nodeid
154 indexformatv0 = ">4l20s20s20s"
154 indexformatv0 = ">4l20s20s20s"
155
155
156 class revlogoldio(object):
156 class revlogoldio(object):
157 def __init__(self):
157 def __init__(self):
158 self.size = struct.calcsize(indexformatv0)
158 self.size = struct.calcsize(indexformatv0)
159
159
160 def parseindex(self, data, inline):
160 def parseindex(self, data, inline):
161 s = self.size
161 s = self.size
162 index = []
162 index = []
163 nodemap = {nullid: nullrev}
163 nodemap = {nullid: nullrev}
164 n = off = 0
164 n = off = 0
165 l = len(data)
165 l = len(data)
166 while off + s <= l:
166 while off + s <= l:
167 cur = data[off:off + s]
167 cur = data[off:off + s]
168 off += s
168 off += s
169 e = _unpack(indexformatv0, cur)
169 e = _unpack(indexformatv0, cur)
170 # transform to revlogv1 format
170 # transform to revlogv1 format
171 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
171 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
172 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
172 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
173 index.append(e2)
173 index.append(e2)
174 nodemap[e[6]] = n
174 nodemap[e[6]] = n
175 n += 1
175 n += 1
176
176
177 # add the magic null revision at -1
177 # add the magic null revision at -1
178 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
178 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
179
179
180 return index, nodemap, None
180 return index, nodemap, None
181
181
182 def packentry(self, entry, node, version, rev):
182 def packentry(self, entry, node, version, rev):
183 if gettype(entry[0]):
183 if gettype(entry[0]):
184 raise RevlogError(_("index entry flags need RevlogNG"))
184 raise RevlogError(_("index entry flags need RevlogNG"))
185 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
185 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
186 node(entry[5]), node(entry[6]), entry[7])
186 node(entry[5]), node(entry[6]), entry[7])
187 return _pack(indexformatv0, *e2)
187 return _pack(indexformatv0, *e2)
188
188
189 # index ng:
189 # index ng:
190 # 6 bytes: offset
190 # 6 bytes: offset
191 # 2 bytes: flags
191 # 2 bytes: flags
192 # 4 bytes: compressed length
192 # 4 bytes: compressed length
193 # 4 bytes: uncompressed length
193 # 4 bytes: uncompressed length
194 # 4 bytes: base rev
194 # 4 bytes: base rev
195 # 4 bytes: link rev
195 # 4 bytes: link rev
196 # 4 bytes: parent 1 rev
196 # 4 bytes: parent 1 rev
197 # 4 bytes: parent 2 rev
197 # 4 bytes: parent 2 rev
198 # 32 bytes: nodeid
198 # 32 bytes: nodeid
199 indexformatng = ">Qiiiiii20s12x"
199 indexformatng = ">Qiiiiii20s12x"
200 versionformat = ">I"
200 versionformat = ">I"
201
201
202 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
202 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
203 # signed integer)
203 # signed integer)
204 _maxentrysize = 0x7fffffff
204 _maxentrysize = 0x7fffffff
205
205
206 class revlogio(object):
206 class revlogio(object):
207 def __init__(self):
207 def __init__(self):
208 self.size = struct.calcsize(indexformatng)
208 self.size = struct.calcsize(indexformatng)
209
209
210 def parseindex(self, data, inline):
210 def parseindex(self, data, inline):
211 # call the C implementation to parse the index data
211 # call the C implementation to parse the index data
212 index, cache = parsers.parse_index2(data, inline)
212 index, cache = parsers.parse_index2(data, inline)
213 return index, getattr(index, 'nodemap', None), cache
213 return index, getattr(index, 'nodemap', None), cache
214
214
215 def packentry(self, entry, node, version, rev):
215 def packentry(self, entry, node, version, rev):
216 p = _pack(indexformatng, *entry)
216 p = _pack(indexformatng, *entry)
217 if rev == 0:
217 if rev == 0:
218 p = _pack(versionformat, version) + p[4:]
218 p = _pack(versionformat, version) + p[4:]
219 return p
219 return p
220
220
221 class revlog(object):
221 class revlog(object):
222 """
222 """
223 the underlying revision storage object
223 the underlying revision storage object
224
224
225 A revlog consists of two parts, an index and the revision data.
225 A revlog consists of two parts, an index and the revision data.
226
226
227 The index is a file with a fixed record size containing
227 The index is a file with a fixed record size containing
228 information on each revision, including its nodeid (hash), the
228 information on each revision, including its nodeid (hash), the
229 nodeids of its parents, the position and offset of its data within
229 nodeids of its parents, the position and offset of its data within
230 the data file, and the revision it's based on. Finally, each entry
230 the data file, and the revision it's based on. Finally, each entry
231 contains a linkrev entry that can serve as a pointer to external
231 contains a linkrev entry that can serve as a pointer to external
232 data.
232 data.
233
233
234 The revision data itself is a linear collection of data chunks.
234 The revision data itself is a linear collection of data chunks.
235 Each chunk represents a revision and is usually represented as a
235 Each chunk represents a revision and is usually represented as a
236 delta against the previous chunk. To bound lookup time, runs of
236 delta against the previous chunk. To bound lookup time, runs of
237 deltas are limited to about 2 times the length of the original
237 deltas are limited to about 2 times the length of the original
238 version data. This makes retrieval of a version proportional to
238 version data. This makes retrieval of a version proportional to
239 its size, or O(1) relative to the number of revisions.
239 its size, or O(1) relative to the number of revisions.
240
240
241 Both pieces of the revlog are written to in an append-only
241 Both pieces of the revlog are written to in an append-only
242 fashion, which means we never need to rewrite a file to insert or
242 fashion, which means we never need to rewrite a file to insert or
243 remove data, and can use some simple techniques to avoid the need
243 remove data, and can use some simple techniques to avoid the need
244 for locking while reading.
244 for locking while reading.
245
245
246 If checkambig, indexfile is opened with checkambig=True at
246 If checkambig, indexfile is opened with checkambig=True at
247 writing, to avoid file stat ambiguity.
247 writing, to avoid file stat ambiguity.
248 """
248 """
249 def __init__(self, opener, indexfile, checkambig=False):
249 def __init__(self, opener, indexfile, checkambig=False):
250 """
250 """
251 create a revlog object
251 create a revlog object
252
252
253 opener is a function that abstracts the file opening operation
253 opener is a function that abstracts the file opening operation
254 and can be used to implement COW semantics or the like.
254 and can be used to implement COW semantics or the like.
255 """
255 """
256 self.indexfile = indexfile
256 self.indexfile = indexfile
257 self.datafile = indexfile[:-2] + ".d"
257 self.datafile = indexfile[:-2] + ".d"
258 self.opener = opener
258 self.opener = opener
259 # When True, indexfile is opened with checkambig=True at writing, to
259 # When True, indexfile is opened with checkambig=True at writing, to
260 # avoid file stat ambiguity.
260 # avoid file stat ambiguity.
261 self._checkambig = checkambig
261 self._checkambig = checkambig
262 # 3-tuple of (node, rev, text) for a raw revision.
262 # 3-tuple of (node, rev, text) for a raw revision.
263 self._cache = None
263 self._cache = None
264 # Maps rev to chain base rev.
264 # Maps rev to chain base rev.
265 self._chainbasecache = util.lrucachedict(100)
265 self._chainbasecache = util.lrucachedict(100)
266 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
266 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
267 self._chunkcache = (0, '')
267 self._chunkcache = (0, '')
268 # How much data to read and cache into the raw revlog data cache.
268 # How much data to read and cache into the raw revlog data cache.
269 self._chunkcachesize = 65536
269 self._chunkcachesize = 65536
270 self._maxchainlen = None
270 self._maxchainlen = None
271 self._aggressivemergedeltas = False
271 self._aggressivemergedeltas = False
272 self.index = []
272 self.index = []
273 # Mapping of partial identifiers to full nodes.
273 # Mapping of partial identifiers to full nodes.
274 self._pcache = {}
274 self._pcache = {}
275 # Mapping of revision integer to full node.
275 # Mapping of revision integer to full node.
276 self._nodecache = {nullid: nullrev}
276 self._nodecache = {nullid: nullrev}
277 self._nodepos = None
277 self._nodepos = None
278 self._compengine = 'zlib'
278 self._compengine = 'zlib'
279
279
280 v = REVLOG_DEFAULT_VERSION
280 v = REVLOG_DEFAULT_VERSION
281 opts = getattr(opener, 'options', None)
281 opts = getattr(opener, 'options', None)
282 if opts is not None:
282 if opts is not None:
283 if 'revlogv1' in opts:
283 if 'revlogv1' in opts:
284 if 'generaldelta' in opts:
284 if 'generaldelta' in opts:
285 v |= REVLOGGENERALDELTA
285 v |= REVLOGGENERALDELTA
286 else:
286 else:
287 v = 0
287 v = 0
288 if 'chunkcachesize' in opts:
288 if 'chunkcachesize' in opts:
289 self._chunkcachesize = opts['chunkcachesize']
289 self._chunkcachesize = opts['chunkcachesize']
290 if 'maxchainlen' in opts:
290 if 'maxchainlen' in opts:
291 self._maxchainlen = opts['maxchainlen']
291 self._maxchainlen = opts['maxchainlen']
292 if 'aggressivemergedeltas' in opts:
292 if 'aggressivemergedeltas' in opts:
293 self._aggressivemergedeltas = opts['aggressivemergedeltas']
293 self._aggressivemergedeltas = opts['aggressivemergedeltas']
294 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
294 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
295 if 'compengine' in opts:
295 if 'compengine' in opts:
296 self._compengine = opts['compengine']
296 self._compengine = opts['compengine']
297
297
298 if self._chunkcachesize <= 0:
298 if self._chunkcachesize <= 0:
299 raise RevlogError(_('revlog chunk cache size %r is not greater '
299 raise RevlogError(_('revlog chunk cache size %r is not greater '
300 'than 0') % self._chunkcachesize)
300 'than 0') % self._chunkcachesize)
301 elif self._chunkcachesize & (self._chunkcachesize - 1):
301 elif self._chunkcachesize & (self._chunkcachesize - 1):
302 raise RevlogError(_('revlog chunk cache size %r is not a power '
302 raise RevlogError(_('revlog chunk cache size %r is not a power '
303 'of 2') % self._chunkcachesize)
303 'of 2') % self._chunkcachesize)
304
304
305 indexdata = ''
305 indexdata = ''
306 self._initempty = True
306 self._initempty = True
307 try:
307 try:
308 f = self.opener(self.indexfile)
308 f = self.opener(self.indexfile)
309 indexdata = f.read()
309 indexdata = f.read()
310 f.close()
310 f.close()
311 if len(indexdata) > 0:
311 if len(indexdata) > 0:
312 v = struct.unpack(versionformat, indexdata[:4])[0]
312 v = struct.unpack(versionformat, indexdata[:4])[0]
313 self._initempty = False
313 self._initempty = False
314 except IOError as inst:
314 except IOError as inst:
315 if inst.errno != errno.ENOENT:
315 if inst.errno != errno.ENOENT:
316 raise
316 raise
317
317
318 self.version = v
318 self.version = v
319 self._inline = v & REVLOGNGINLINEDATA
319 self._inline = v & REVLOGNGINLINEDATA
320 self._generaldelta = v & REVLOGGENERALDELTA
320 self._generaldelta = v & REVLOGGENERALDELTA
321 flags = v & ~0xFFFF
321 flags = v & ~0xFFFF
322 fmt = v & 0xFFFF
322 fmt = v & 0xFFFF
323 if fmt == REVLOGV0 and flags:
323 if fmt == REVLOGV0 and flags:
324 raise RevlogError(_("index %s unknown flags %#04x for format v0")
324 raise RevlogError(_("index %s unknown flags %#04x for format v0")
325 % (self.indexfile, flags >> 16))
325 % (self.indexfile, flags >> 16))
326 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
326 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
327 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
327 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
328 % (self.indexfile, flags >> 16))
328 % (self.indexfile, flags >> 16))
329 elif fmt > REVLOGNG:
329 elif fmt > REVLOGNG:
330 raise RevlogError(_("index %s unknown format %d")
330 raise RevlogError(_("index %s unknown format %d")
331 % (self.indexfile, fmt))
331 % (self.indexfile, fmt))
332
332
333 self.storedeltachains = True
333 self.storedeltachains = True
334
334
335 self._io = revlogio()
335 self._io = revlogio()
336 if self.version == REVLOGV0:
336 if self.version == REVLOGV0:
337 self._io = revlogoldio()
337 self._io = revlogoldio()
338 try:
338 try:
339 d = self._io.parseindex(indexdata, self._inline)
339 d = self._io.parseindex(indexdata, self._inline)
340 except (ValueError, IndexError):
340 except (ValueError, IndexError):
341 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
341 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
342 self.index, nodemap, self._chunkcache = d
342 self.index, nodemap, self._chunkcache = d
343 if nodemap is not None:
343 if nodemap is not None:
344 self.nodemap = self._nodecache = nodemap
344 self.nodemap = self._nodecache = nodemap
345 if not self._chunkcache:
345 if not self._chunkcache:
346 self._chunkclear()
346 self._chunkclear()
347 # revnum -> (chain-length, sum-delta-length)
347 # revnum -> (chain-length, sum-delta-length)
348 self._chaininfocache = {}
348 self._chaininfocache = {}
349 # revlog header -> revlog compressor
349 # revlog header -> revlog compressor
350 self._decompressors = {}
350 self._decompressors = {}
351
351
352 @util.propertycache
352 @util.propertycache
353 def _compressor(self):
353 def _compressor(self):
354 return util.compengines[self._compengine].revlogcompressor()
354 return util.compengines[self._compengine].revlogcompressor()
355
355
356 def tip(self):
356 def tip(self):
357 return self.node(len(self.index) - 2)
357 return self.node(len(self.index) - 2)
358 def __contains__(self, rev):
358 def __contains__(self, rev):
359 return 0 <= rev < len(self)
359 return 0 <= rev < len(self)
360 def __len__(self):
360 def __len__(self):
361 return len(self.index) - 1
361 return len(self.index) - 1
362 def __iter__(self):
362 def __iter__(self):
363 return iter(xrange(len(self)))
363 return iter(xrange(len(self)))
364 def revs(self, start=0, stop=None):
364 def revs(self, start=0, stop=None):
365 """iterate over all rev in this revlog (from start to stop)"""
365 """iterate over all rev in this revlog (from start to stop)"""
366 step = 1
366 step = 1
367 if stop is not None:
367 if stop is not None:
368 if start > stop:
368 if start > stop:
369 step = -1
369 step = -1
370 stop += step
370 stop += step
371 else:
371 else:
372 stop = len(self)
372 stop = len(self)
373 return xrange(start, stop, step)
373 return xrange(start, stop, step)
374
374
375 @util.propertycache
375 @util.propertycache
376 def nodemap(self):
376 def nodemap(self):
377 self.rev(self.node(0))
377 self.rev(self.node(0))
378 return self._nodecache
378 return self._nodecache
379
379
380 def hasnode(self, node):
380 def hasnode(self, node):
381 try:
381 try:
382 self.rev(node)
382 self.rev(node)
383 return True
383 return True
384 except KeyError:
384 except KeyError:
385 return False
385 return False
386
386
387 def clearcaches(self):
387 def clearcaches(self):
388 self._cache = None
388 self._cache = None
389 self._chainbasecache.clear()
389 self._chainbasecache.clear()
390 self._chunkcache = (0, '')
390 self._chunkcache = (0, '')
391 self._pcache = {}
391 self._pcache = {}
392
392
393 try:
393 try:
394 self._nodecache.clearcaches()
394 self._nodecache.clearcaches()
395 except AttributeError:
395 except AttributeError:
396 self._nodecache = {nullid: nullrev}
396 self._nodecache = {nullid: nullrev}
397 self._nodepos = None
397 self._nodepos = None
398
398
399 def rev(self, node):
399 def rev(self, node):
400 try:
400 try:
401 return self._nodecache[node]
401 return self._nodecache[node]
402 except TypeError:
402 except TypeError:
403 raise
403 raise
404 except RevlogError:
404 except RevlogError:
405 # parsers.c radix tree lookup failed
405 # parsers.c radix tree lookup failed
406 raise LookupError(node, self.indexfile, _('no node'))
406 raise LookupError(node, self.indexfile, _('no node'))
407 except KeyError:
407 except KeyError:
408 # pure python cache lookup failed
408 # pure python cache lookup failed
409 n = self._nodecache
409 n = self._nodecache
410 i = self.index
410 i = self.index
411 p = self._nodepos
411 p = self._nodepos
412 if p is None:
412 if p is None:
413 p = len(i) - 2
413 p = len(i) - 2
414 for r in xrange(p, -1, -1):
414 for r in xrange(p, -1, -1):
415 v = i[r][7]
415 v = i[r][7]
416 n[v] = r
416 n[v] = r
417 if v == node:
417 if v == node:
418 self._nodepos = r - 1
418 self._nodepos = r - 1
419 return r
419 return r
420 raise LookupError(node, self.indexfile, _('no node'))
420 raise LookupError(node, self.indexfile, _('no node'))
421
421
422 # Accessors for index entries.
422 # Accessors for index entries.
423
423
424 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
424 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
425 # are flags.
425 # are flags.
426 def start(self, rev):
426 def start(self, rev):
427 return int(self.index[rev][0] >> 16)
427 return int(self.index[rev][0] >> 16)
428
428
429 def flags(self, rev):
429 def flags(self, rev):
430 return self.index[rev][0] & 0xFFFF
430 return self.index[rev][0] & 0xFFFF
431
431
432 def length(self, rev):
432 def length(self, rev):
433 return self.index[rev][1]
433 return self.index[rev][1]
434
434
435 def rawsize(self, rev):
435 def rawsize(self, rev):
436 """return the length of the uncompressed text for a given revision"""
436 """return the length of the uncompressed text for a given revision"""
437 l = self.index[rev][2]
437 l = self.index[rev][2]
438 if l >= 0:
438 if l >= 0:
439 return l
439 return l
440
440
441 t = self.revision(self.node(rev))
441 t = self.revision(rev)
442 return len(t)
442 return len(t)
443 size = rawsize
443 size = rawsize
444
444
445 def chainbase(self, rev):
445 def chainbase(self, rev):
446 base = self._chainbasecache.get(rev)
446 base = self._chainbasecache.get(rev)
447 if base is not None:
447 if base is not None:
448 return base
448 return base
449
449
450 index = self.index
450 index = self.index
451 base = index[rev][3]
451 base = index[rev][3]
452 while base != rev:
452 while base != rev:
453 rev = base
453 rev = base
454 base = index[rev][3]
454 base = index[rev][3]
455
455
456 self._chainbasecache[rev] = base
456 self._chainbasecache[rev] = base
457 return base
457 return base
458
458
459 def linkrev(self, rev):
459 def linkrev(self, rev):
460 return self.index[rev][4]
460 return self.index[rev][4]
461
461
462 def parentrevs(self, rev):
462 def parentrevs(self, rev):
463 return self.index[rev][5:7]
463 return self.index[rev][5:7]
464
464
465 def node(self, rev):
465 def node(self, rev):
466 return self.index[rev][7]
466 return self.index[rev][7]
467
467
468 # Derived from index values.
468 # Derived from index values.
469
469
470 def end(self, rev):
470 def end(self, rev):
471 return self.start(rev) + self.length(rev)
471 return self.start(rev) + self.length(rev)
472
472
473 def parents(self, node):
473 def parents(self, node):
474 i = self.index
474 i = self.index
475 d = i[self.rev(node)]
475 d = i[self.rev(node)]
476 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
476 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
477
477
478 def chainlen(self, rev):
478 def chainlen(self, rev):
479 return self._chaininfo(rev)[0]
479 return self._chaininfo(rev)[0]
480
480
481 def _chaininfo(self, rev):
481 def _chaininfo(self, rev):
482 chaininfocache = self._chaininfocache
482 chaininfocache = self._chaininfocache
483 if rev in chaininfocache:
483 if rev in chaininfocache:
484 return chaininfocache[rev]
484 return chaininfocache[rev]
485 index = self.index
485 index = self.index
486 generaldelta = self._generaldelta
486 generaldelta = self._generaldelta
487 iterrev = rev
487 iterrev = rev
488 e = index[iterrev]
488 e = index[iterrev]
489 clen = 0
489 clen = 0
490 compresseddeltalen = 0
490 compresseddeltalen = 0
491 while iterrev != e[3]:
491 while iterrev != e[3]:
492 clen += 1
492 clen += 1
493 compresseddeltalen += e[1]
493 compresseddeltalen += e[1]
494 if generaldelta:
494 if generaldelta:
495 iterrev = e[3]
495 iterrev = e[3]
496 else:
496 else:
497 iterrev -= 1
497 iterrev -= 1
498 if iterrev in chaininfocache:
498 if iterrev in chaininfocache:
499 t = chaininfocache[iterrev]
499 t = chaininfocache[iterrev]
500 clen += t[0]
500 clen += t[0]
501 compresseddeltalen += t[1]
501 compresseddeltalen += t[1]
502 break
502 break
503 e = index[iterrev]
503 e = index[iterrev]
504 else:
504 else:
505 # Add text length of base since decompressing that also takes
505 # Add text length of base since decompressing that also takes
506 # work. For cache hits the length is already included.
506 # work. For cache hits the length is already included.
507 compresseddeltalen += e[1]
507 compresseddeltalen += e[1]
508 r = (clen, compresseddeltalen)
508 r = (clen, compresseddeltalen)
509 chaininfocache[rev] = r
509 chaininfocache[rev] = r
510 return r
510 return r
511
511
512 def _deltachain(self, rev, stoprev=None):
512 def _deltachain(self, rev, stoprev=None):
513 """Obtain the delta chain for a revision.
513 """Obtain the delta chain for a revision.
514
514
515 ``stoprev`` specifies a revision to stop at. If not specified, we
515 ``stoprev`` specifies a revision to stop at. If not specified, we
516 stop at the base of the chain.
516 stop at the base of the chain.
517
517
518 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
518 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
519 revs in ascending order and ``stopped`` is a bool indicating whether
519 revs in ascending order and ``stopped`` is a bool indicating whether
520 ``stoprev`` was hit.
520 ``stoprev`` was hit.
521 """
521 """
522 chain = []
522 chain = []
523
523
524 # Alias to prevent attribute lookup in tight loop.
524 # Alias to prevent attribute lookup in tight loop.
525 index = self.index
525 index = self.index
526 generaldelta = self._generaldelta
526 generaldelta = self._generaldelta
527
527
528 iterrev = rev
528 iterrev = rev
529 e = index[iterrev]
529 e = index[iterrev]
530 while iterrev != e[3] and iterrev != stoprev:
530 while iterrev != e[3] and iterrev != stoprev:
531 chain.append(iterrev)
531 chain.append(iterrev)
532 if generaldelta:
532 if generaldelta:
533 iterrev = e[3]
533 iterrev = e[3]
534 else:
534 else:
535 iterrev -= 1
535 iterrev -= 1
536 e = index[iterrev]
536 e = index[iterrev]
537
537
538 if iterrev == stoprev:
538 if iterrev == stoprev:
539 stopped = True
539 stopped = True
540 else:
540 else:
541 chain.append(iterrev)
541 chain.append(iterrev)
542 stopped = False
542 stopped = False
543
543
544 chain.reverse()
544 chain.reverse()
545 return chain, stopped
545 return chain, stopped
546
546
547 def ancestors(self, revs, stoprev=0, inclusive=False):
547 def ancestors(self, revs, stoprev=0, inclusive=False):
548 """Generate the ancestors of 'revs' in reverse topological order.
548 """Generate the ancestors of 'revs' in reverse topological order.
549 Does not generate revs lower than stoprev.
549 Does not generate revs lower than stoprev.
550
550
551 See the documentation for ancestor.lazyancestors for more details."""
551 See the documentation for ancestor.lazyancestors for more details."""
552
552
553 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
553 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
554 inclusive=inclusive)
554 inclusive=inclusive)
555
555
556 def descendants(self, revs):
556 def descendants(self, revs):
557 """Generate the descendants of 'revs' in revision order.
557 """Generate the descendants of 'revs' in revision order.
558
558
559 Yield a sequence of revision numbers starting with a child of
559 Yield a sequence of revision numbers starting with a child of
560 some rev in revs, i.e., each revision is *not* considered a
560 some rev in revs, i.e., each revision is *not* considered a
561 descendant of itself. Results are ordered by revision number (a
561 descendant of itself. Results are ordered by revision number (a
562 topological sort)."""
562 topological sort)."""
563 first = min(revs)
563 first = min(revs)
564 if first == nullrev:
564 if first == nullrev:
565 for i in self:
565 for i in self:
566 yield i
566 yield i
567 return
567 return
568
568
569 seen = set(revs)
569 seen = set(revs)
570 for i in self.revs(start=first + 1):
570 for i in self.revs(start=first + 1):
571 for x in self.parentrevs(i):
571 for x in self.parentrevs(i):
572 if x != nullrev and x in seen:
572 if x != nullrev and x in seen:
573 seen.add(i)
573 seen.add(i)
574 yield i
574 yield i
575 break
575 break
576
576
577 def findcommonmissing(self, common=None, heads=None):
577 def findcommonmissing(self, common=None, heads=None):
578 """Return a tuple of the ancestors of common and the ancestors of heads
578 """Return a tuple of the ancestors of common and the ancestors of heads
579 that are not ancestors of common. In revset terminology, we return the
579 that are not ancestors of common. In revset terminology, we return the
580 tuple:
580 tuple:
581
581
582 ::common, (::heads) - (::common)
582 ::common, (::heads) - (::common)
583
583
584 The list is sorted by revision number, meaning it is
584 The list is sorted by revision number, meaning it is
585 topologically sorted.
585 topologically sorted.
586
586
587 'heads' and 'common' are both lists of node IDs. If heads is
587 'heads' and 'common' are both lists of node IDs. If heads is
588 not supplied, uses all of the revlog's heads. If common is not
588 not supplied, uses all of the revlog's heads. If common is not
589 supplied, uses nullid."""
589 supplied, uses nullid."""
590 if common is None:
590 if common is None:
591 common = [nullid]
591 common = [nullid]
592 if heads is None:
592 if heads is None:
593 heads = self.heads()
593 heads = self.heads()
594
594
595 common = [self.rev(n) for n in common]
595 common = [self.rev(n) for n in common]
596 heads = [self.rev(n) for n in heads]
596 heads = [self.rev(n) for n in heads]
597
597
598 # we want the ancestors, but inclusive
598 # we want the ancestors, but inclusive
599 class lazyset(object):
599 class lazyset(object):
600 def __init__(self, lazyvalues):
600 def __init__(self, lazyvalues):
601 self.addedvalues = set()
601 self.addedvalues = set()
602 self.lazyvalues = lazyvalues
602 self.lazyvalues = lazyvalues
603
603
604 def __contains__(self, value):
604 def __contains__(self, value):
605 return value in self.addedvalues or value in self.lazyvalues
605 return value in self.addedvalues or value in self.lazyvalues
606
606
607 def __iter__(self):
607 def __iter__(self):
608 added = self.addedvalues
608 added = self.addedvalues
609 for r in added:
609 for r in added:
610 yield r
610 yield r
611 for r in self.lazyvalues:
611 for r in self.lazyvalues:
612 if not r in added:
612 if not r in added:
613 yield r
613 yield r
614
614
615 def add(self, value):
615 def add(self, value):
616 self.addedvalues.add(value)
616 self.addedvalues.add(value)
617
617
618 def update(self, values):
618 def update(self, values):
619 self.addedvalues.update(values)
619 self.addedvalues.update(values)
620
620
621 has = lazyset(self.ancestors(common))
621 has = lazyset(self.ancestors(common))
622 has.add(nullrev)
622 has.add(nullrev)
623 has.update(common)
623 has.update(common)
624
624
625 # take all ancestors from heads that aren't in has
625 # take all ancestors from heads that aren't in has
626 missing = set()
626 missing = set()
627 visit = collections.deque(r for r in heads if r not in has)
627 visit = collections.deque(r for r in heads if r not in has)
628 while visit:
628 while visit:
629 r = visit.popleft()
629 r = visit.popleft()
630 if r in missing:
630 if r in missing:
631 continue
631 continue
632 else:
632 else:
633 missing.add(r)
633 missing.add(r)
634 for p in self.parentrevs(r):
634 for p in self.parentrevs(r):
635 if p not in has:
635 if p not in has:
636 visit.append(p)
636 visit.append(p)
637 missing = list(missing)
637 missing = list(missing)
638 missing.sort()
638 missing.sort()
639 return has, [self.node(miss) for miss in missing]
639 return has, [self.node(miss) for miss in missing]
640
640
641 def incrementalmissingrevs(self, common=None):
641 def incrementalmissingrevs(self, common=None):
642 """Return an object that can be used to incrementally compute the
642 """Return an object that can be used to incrementally compute the
643 revision numbers of the ancestors of arbitrary sets that are not
643 revision numbers of the ancestors of arbitrary sets that are not
644 ancestors of common. This is an ancestor.incrementalmissingancestors
644 ancestors of common. This is an ancestor.incrementalmissingancestors
645 object.
645 object.
646
646
647 'common' is a list of revision numbers. If common is not supplied, uses
647 'common' is a list of revision numbers. If common is not supplied, uses
648 nullrev.
648 nullrev.
649 """
649 """
650 if common is None:
650 if common is None:
651 common = [nullrev]
651 common = [nullrev]
652
652
653 return ancestor.incrementalmissingancestors(self.parentrevs, common)
653 return ancestor.incrementalmissingancestors(self.parentrevs, common)
654
654
655 def findmissingrevs(self, common=None, heads=None):
655 def findmissingrevs(self, common=None, heads=None):
656 """Return the revision numbers of the ancestors of heads that
656 """Return the revision numbers of the ancestors of heads that
657 are not ancestors of common.
657 are not ancestors of common.
658
658
659 More specifically, return a list of revision numbers corresponding to
659 More specifically, return a list of revision numbers corresponding to
660 nodes N such that every N satisfies the following constraints:
660 nodes N such that every N satisfies the following constraints:
661
661
662 1. N is an ancestor of some node in 'heads'
662 1. N is an ancestor of some node in 'heads'
663 2. N is not an ancestor of any node in 'common'
663 2. N is not an ancestor of any node in 'common'
664
664
665 The list is sorted by revision number, meaning it is
665 The list is sorted by revision number, meaning it is
666 topologically sorted.
666 topologically sorted.
667
667
668 'heads' and 'common' are both lists of revision numbers. If heads is
668 'heads' and 'common' are both lists of revision numbers. If heads is
669 not supplied, uses all of the revlog's heads. If common is not
669 not supplied, uses all of the revlog's heads. If common is not
670 supplied, uses nullid."""
670 supplied, uses nullid."""
671 if common is None:
671 if common is None:
672 common = [nullrev]
672 common = [nullrev]
673 if heads is None:
673 if heads is None:
674 heads = self.headrevs()
674 heads = self.headrevs()
675
675
676 inc = self.incrementalmissingrevs(common=common)
676 inc = self.incrementalmissingrevs(common=common)
677 return inc.missingancestors(heads)
677 return inc.missingancestors(heads)
678
678
679 def findmissing(self, common=None, heads=None):
679 def findmissing(self, common=None, heads=None):
680 """Return the ancestors of heads that are not ancestors of common.
680 """Return the ancestors of heads that are not ancestors of common.
681
681
682 More specifically, return a list of nodes N such that every N
682 More specifically, return a list of nodes N such that every N
683 satisfies the following constraints:
683 satisfies the following constraints:
684
684
685 1. N is an ancestor of some node in 'heads'
685 1. N is an ancestor of some node in 'heads'
686 2. N is not an ancestor of any node in 'common'
686 2. N is not an ancestor of any node in 'common'
687
687
688 The list is sorted by revision number, meaning it is
688 The list is sorted by revision number, meaning it is
689 topologically sorted.
689 topologically sorted.
690
690
691 'heads' and 'common' are both lists of node IDs. If heads is
691 'heads' and 'common' are both lists of node IDs. If heads is
692 not supplied, uses all of the revlog's heads. If common is not
692 not supplied, uses all of the revlog's heads. If common is not
693 supplied, uses nullid."""
693 supplied, uses nullid."""
694 if common is None:
694 if common is None:
695 common = [nullid]
695 common = [nullid]
696 if heads is None:
696 if heads is None:
697 heads = self.heads()
697 heads = self.heads()
698
698
699 common = [self.rev(n) for n in common]
699 common = [self.rev(n) for n in common]
700 heads = [self.rev(n) for n in heads]
700 heads = [self.rev(n) for n in heads]
701
701
702 inc = self.incrementalmissingrevs(common=common)
702 inc = self.incrementalmissingrevs(common=common)
703 return [self.node(r) for r in inc.missingancestors(heads)]
703 return [self.node(r) for r in inc.missingancestors(heads)]
704
704
705 def nodesbetween(self, roots=None, heads=None):
705 def nodesbetween(self, roots=None, heads=None):
706 """Return a topological path from 'roots' to 'heads'.
706 """Return a topological path from 'roots' to 'heads'.
707
707
708 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
708 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
709 topologically sorted list of all nodes N that satisfy both of
709 topologically sorted list of all nodes N that satisfy both of
710 these constraints:
710 these constraints:
711
711
712 1. N is a descendant of some node in 'roots'
712 1. N is a descendant of some node in 'roots'
713 2. N is an ancestor of some node in 'heads'
713 2. N is an ancestor of some node in 'heads'
714
714
715 Every node is considered to be both a descendant and an ancestor
715 Every node is considered to be both a descendant and an ancestor
716 of itself, so every reachable node in 'roots' and 'heads' will be
716 of itself, so every reachable node in 'roots' and 'heads' will be
717 included in 'nodes'.
717 included in 'nodes'.
718
718
719 'outroots' is the list of reachable nodes in 'roots', i.e., the
719 'outroots' is the list of reachable nodes in 'roots', i.e., the
720 subset of 'roots' that is returned in 'nodes'. Likewise,
720 subset of 'roots' that is returned in 'nodes'. Likewise,
721 'outheads' is the subset of 'heads' that is also in 'nodes'.
721 'outheads' is the subset of 'heads' that is also in 'nodes'.
722
722
723 'roots' and 'heads' are both lists of node IDs. If 'roots' is
723 'roots' and 'heads' are both lists of node IDs. If 'roots' is
724 unspecified, uses nullid as the only root. If 'heads' is
724 unspecified, uses nullid as the only root. If 'heads' is
725 unspecified, uses list of all of the revlog's heads."""
725 unspecified, uses list of all of the revlog's heads."""
726 nonodes = ([], [], [])
726 nonodes = ([], [], [])
727 if roots is not None:
727 if roots is not None:
728 roots = list(roots)
728 roots = list(roots)
729 if not roots:
729 if not roots:
730 return nonodes
730 return nonodes
731 lowestrev = min([self.rev(n) for n in roots])
731 lowestrev = min([self.rev(n) for n in roots])
732 else:
732 else:
733 roots = [nullid] # Everybody's a descendant of nullid
733 roots = [nullid] # Everybody's a descendant of nullid
734 lowestrev = nullrev
734 lowestrev = nullrev
735 if (lowestrev == nullrev) and (heads is None):
735 if (lowestrev == nullrev) and (heads is None):
736 # We want _all_ the nodes!
736 # We want _all_ the nodes!
737 return ([self.node(r) for r in self], [nullid], list(self.heads()))
737 return ([self.node(r) for r in self], [nullid], list(self.heads()))
738 if heads is None:
738 if heads is None:
739 # All nodes are ancestors, so the latest ancestor is the last
739 # All nodes are ancestors, so the latest ancestor is the last
740 # node.
740 # node.
741 highestrev = len(self) - 1
741 highestrev = len(self) - 1
742 # Set ancestors to None to signal that every node is an ancestor.
742 # Set ancestors to None to signal that every node is an ancestor.
743 ancestors = None
743 ancestors = None
744 # Set heads to an empty dictionary for later discovery of heads
744 # Set heads to an empty dictionary for later discovery of heads
745 heads = {}
745 heads = {}
746 else:
746 else:
747 heads = list(heads)
747 heads = list(heads)
748 if not heads:
748 if not heads:
749 return nonodes
749 return nonodes
750 ancestors = set()
750 ancestors = set()
751 # Turn heads into a dictionary so we can remove 'fake' heads.
751 # Turn heads into a dictionary so we can remove 'fake' heads.
752 # Also, later we will be using it to filter out the heads we can't
752 # Also, later we will be using it to filter out the heads we can't
753 # find from roots.
753 # find from roots.
754 heads = dict.fromkeys(heads, False)
754 heads = dict.fromkeys(heads, False)
755 # Start at the top and keep marking parents until we're done.
755 # Start at the top and keep marking parents until we're done.
756 nodestotag = set(heads)
756 nodestotag = set(heads)
757 # Remember where the top was so we can use it as a limit later.
757 # Remember where the top was so we can use it as a limit later.
758 highestrev = max([self.rev(n) for n in nodestotag])
758 highestrev = max([self.rev(n) for n in nodestotag])
759 while nodestotag:
759 while nodestotag:
760 # grab a node to tag
760 # grab a node to tag
761 n = nodestotag.pop()
761 n = nodestotag.pop()
762 # Never tag nullid
762 # Never tag nullid
763 if n == nullid:
763 if n == nullid:
764 continue
764 continue
765 # A node's revision number represents its place in a
765 # A node's revision number represents its place in a
766 # topologically sorted list of nodes.
766 # topologically sorted list of nodes.
767 r = self.rev(n)
767 r = self.rev(n)
768 if r >= lowestrev:
768 if r >= lowestrev:
769 if n not in ancestors:
769 if n not in ancestors:
770 # If we are possibly a descendant of one of the roots
770 # If we are possibly a descendant of one of the roots
771 # and we haven't already been marked as an ancestor
771 # and we haven't already been marked as an ancestor
772 ancestors.add(n) # Mark as ancestor
772 ancestors.add(n) # Mark as ancestor
773 # Add non-nullid parents to list of nodes to tag.
773 # Add non-nullid parents to list of nodes to tag.
774 nodestotag.update([p for p in self.parents(n) if
774 nodestotag.update([p for p in self.parents(n) if
775 p != nullid])
775 p != nullid])
776 elif n in heads: # We've seen it before, is it a fake head?
776 elif n in heads: # We've seen it before, is it a fake head?
777 # So it is, real heads should not be the ancestors of
777 # So it is, real heads should not be the ancestors of
778 # any other heads.
778 # any other heads.
779 heads.pop(n)
779 heads.pop(n)
780 if not ancestors:
780 if not ancestors:
781 return nonodes
781 return nonodes
782 # Now that we have our set of ancestors, we want to remove any
782 # Now that we have our set of ancestors, we want to remove any
783 # roots that are not ancestors.
783 # roots that are not ancestors.
784
784
785 # If one of the roots was nullid, everything is included anyway.
785 # If one of the roots was nullid, everything is included anyway.
786 if lowestrev > nullrev:
786 if lowestrev > nullrev:
787 # But, since we weren't, let's recompute the lowest rev to not
787 # But, since we weren't, let's recompute the lowest rev to not
788 # include roots that aren't ancestors.
788 # include roots that aren't ancestors.
789
789
790 # Filter out roots that aren't ancestors of heads
790 # Filter out roots that aren't ancestors of heads
791 roots = [root for root in roots if root in ancestors]
791 roots = [root for root in roots if root in ancestors]
792 # Recompute the lowest revision
792 # Recompute the lowest revision
793 if roots:
793 if roots:
794 lowestrev = min([self.rev(root) for root in roots])
794 lowestrev = min([self.rev(root) for root in roots])
795 else:
795 else:
796 # No more roots? Return empty list
796 # No more roots? Return empty list
797 return nonodes
797 return nonodes
798 else:
798 else:
799 # We are descending from nullid, and don't need to care about
799 # We are descending from nullid, and don't need to care about
800 # any other roots.
800 # any other roots.
801 lowestrev = nullrev
801 lowestrev = nullrev
802 roots = [nullid]
802 roots = [nullid]
803 # Transform our roots list into a set.
803 # Transform our roots list into a set.
804 descendants = set(roots)
804 descendants = set(roots)
805 # Also, keep the original roots so we can filter out roots that aren't
805 # Also, keep the original roots so we can filter out roots that aren't
806 # 'real' roots (i.e. are descended from other roots).
806 # 'real' roots (i.e. are descended from other roots).
807 roots = descendants.copy()
807 roots = descendants.copy()
808 # Our topologically sorted list of output nodes.
808 # Our topologically sorted list of output nodes.
809 orderedout = []
809 orderedout = []
810 # Don't start at nullid since we don't want nullid in our output list,
810 # Don't start at nullid since we don't want nullid in our output list,
811 # and if nullid shows up in descendants, empty parents will look like
811 # and if nullid shows up in descendants, empty parents will look like
812 # they're descendants.
812 # they're descendants.
813 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
813 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
814 n = self.node(r)
814 n = self.node(r)
815 isdescendant = False
815 isdescendant = False
816 if lowestrev == nullrev: # Everybody is a descendant of nullid
816 if lowestrev == nullrev: # Everybody is a descendant of nullid
817 isdescendant = True
817 isdescendant = True
818 elif n in descendants:
818 elif n in descendants:
819 # n is already a descendant
819 # n is already a descendant
820 isdescendant = True
820 isdescendant = True
821 # This check only needs to be done here because all the roots
821 # This check only needs to be done here because all the roots
822 # will start being marked is descendants before the loop.
822 # will start being marked is descendants before the loop.
823 if n in roots:
823 if n in roots:
824 # If n was a root, check if it's a 'real' root.
824 # If n was a root, check if it's a 'real' root.
825 p = tuple(self.parents(n))
825 p = tuple(self.parents(n))
826 # If any of its parents are descendants, it's not a root.
826 # If any of its parents are descendants, it's not a root.
827 if (p[0] in descendants) or (p[1] in descendants):
827 if (p[0] in descendants) or (p[1] in descendants):
828 roots.remove(n)
828 roots.remove(n)
829 else:
829 else:
830 p = tuple(self.parents(n))
830 p = tuple(self.parents(n))
831 # A node is a descendant if either of its parents are
831 # A node is a descendant if either of its parents are
832 # descendants. (We seeded the dependents list with the roots
832 # descendants. (We seeded the dependents list with the roots
833 # up there, remember?)
833 # up there, remember?)
834 if (p[0] in descendants) or (p[1] in descendants):
834 if (p[0] in descendants) or (p[1] in descendants):
835 descendants.add(n)
835 descendants.add(n)
836 isdescendant = True
836 isdescendant = True
837 if isdescendant and ((ancestors is None) or (n in ancestors)):
837 if isdescendant and ((ancestors is None) or (n in ancestors)):
838 # Only include nodes that are both descendants and ancestors.
838 # Only include nodes that are both descendants and ancestors.
839 orderedout.append(n)
839 orderedout.append(n)
840 if (ancestors is not None) and (n in heads):
840 if (ancestors is not None) and (n in heads):
841 # We're trying to figure out which heads are reachable
841 # We're trying to figure out which heads are reachable
842 # from roots.
842 # from roots.
843 # Mark this head as having been reached
843 # Mark this head as having been reached
844 heads[n] = True
844 heads[n] = True
845 elif ancestors is None:
845 elif ancestors is None:
846 # Otherwise, we're trying to discover the heads.
846 # Otherwise, we're trying to discover the heads.
847 # Assume this is a head because if it isn't, the next step
847 # Assume this is a head because if it isn't, the next step
848 # will eventually remove it.
848 # will eventually remove it.
849 heads[n] = True
849 heads[n] = True
850 # But, obviously its parents aren't.
850 # But, obviously its parents aren't.
851 for p in self.parents(n):
851 for p in self.parents(n):
852 heads.pop(p, None)
852 heads.pop(p, None)
853 heads = [head for head, flag in heads.iteritems() if flag]
853 heads = [head for head, flag in heads.iteritems() if flag]
854 roots = list(roots)
854 roots = list(roots)
855 assert orderedout
855 assert orderedout
856 assert roots
856 assert roots
857 assert heads
857 assert heads
858 return (orderedout, roots, heads)
858 return (orderedout, roots, heads)
859
859
860 def headrevs(self):
860 def headrevs(self):
861 try:
861 try:
862 return self.index.headrevs()
862 return self.index.headrevs()
863 except AttributeError:
863 except AttributeError:
864 return self._headrevs()
864 return self._headrevs()
865
865
866 def computephases(self, roots):
866 def computephases(self, roots):
867 return self.index.computephasesmapsets(roots)
867 return self.index.computephasesmapsets(roots)
868
868
869 def _headrevs(self):
869 def _headrevs(self):
870 count = len(self)
870 count = len(self)
871 if not count:
871 if not count:
872 return [nullrev]
872 return [nullrev]
873 # we won't iter over filtered rev so nobody is a head at start
873 # we won't iter over filtered rev so nobody is a head at start
874 ishead = [0] * (count + 1)
874 ishead = [0] * (count + 1)
875 index = self.index
875 index = self.index
876 for r in self:
876 for r in self:
877 ishead[r] = 1 # I may be an head
877 ishead[r] = 1 # I may be an head
878 e = index[r]
878 e = index[r]
879 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
879 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
880 return [r for r, val in enumerate(ishead) if val]
880 return [r for r, val in enumerate(ishead) if val]
881
881
882 def heads(self, start=None, stop=None):
882 def heads(self, start=None, stop=None):
883 """return the list of all nodes that have no children
883 """return the list of all nodes that have no children
884
884
885 if start is specified, only heads that are descendants of
885 if start is specified, only heads that are descendants of
886 start will be returned
886 start will be returned
887 if stop is specified, it will consider all the revs from stop
887 if stop is specified, it will consider all the revs from stop
888 as if they had no children
888 as if they had no children
889 """
889 """
890 if start is None and stop is None:
890 if start is None and stop is None:
891 if not len(self):
891 if not len(self):
892 return [nullid]
892 return [nullid]
893 return [self.node(r) for r in self.headrevs()]
893 return [self.node(r) for r in self.headrevs()]
894
894
895 if start is None:
895 if start is None:
896 start = nullid
896 start = nullid
897 if stop is None:
897 if stop is None:
898 stop = []
898 stop = []
899 stoprevs = set([self.rev(n) for n in stop])
899 stoprevs = set([self.rev(n) for n in stop])
900 startrev = self.rev(start)
900 startrev = self.rev(start)
901 reachable = set((startrev,))
901 reachable = set((startrev,))
902 heads = set((startrev,))
902 heads = set((startrev,))
903
903
904 parentrevs = self.parentrevs
904 parentrevs = self.parentrevs
905 for r in self.revs(start=startrev + 1):
905 for r in self.revs(start=startrev + 1):
906 for p in parentrevs(r):
906 for p in parentrevs(r):
907 if p in reachable:
907 if p in reachable:
908 if r not in stoprevs:
908 if r not in stoprevs:
909 reachable.add(r)
909 reachable.add(r)
910 heads.add(r)
910 heads.add(r)
911 if p in heads and p not in stoprevs:
911 if p in heads and p not in stoprevs:
912 heads.remove(p)
912 heads.remove(p)
913
913
914 return [self.node(r) for r in heads]
914 return [self.node(r) for r in heads]
915
915
916 def children(self, node):
916 def children(self, node):
917 """find the children of a given node"""
917 """find the children of a given node"""
918 c = []
918 c = []
919 p = self.rev(node)
919 p = self.rev(node)
920 for r in self.revs(start=p + 1):
920 for r in self.revs(start=p + 1):
921 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
921 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
922 if prevs:
922 if prevs:
923 for pr in prevs:
923 for pr in prevs:
924 if pr == p:
924 if pr == p:
925 c.append(self.node(r))
925 c.append(self.node(r))
926 elif p == nullrev:
926 elif p == nullrev:
927 c.append(self.node(r))
927 c.append(self.node(r))
928 return c
928 return c
929
929
930 def descendant(self, start, end):
930 def descendant(self, start, end):
931 if start == nullrev:
931 if start == nullrev:
932 return True
932 return True
933 for i in self.descendants([start]):
933 for i in self.descendants([start]):
934 if i == end:
934 if i == end:
935 return True
935 return True
936 elif i > end:
936 elif i > end:
937 break
937 break
938 return False
938 return False
939
939
940 def commonancestorsheads(self, a, b):
940 def commonancestorsheads(self, a, b):
941 """calculate all the heads of the common ancestors of nodes a and b"""
941 """calculate all the heads of the common ancestors of nodes a and b"""
942 a, b = self.rev(a), self.rev(b)
942 a, b = self.rev(a), self.rev(b)
943 try:
943 try:
944 ancs = self.index.commonancestorsheads(a, b)
944 ancs = self.index.commonancestorsheads(a, b)
945 except (AttributeError, OverflowError): # C implementation failed
945 except (AttributeError, OverflowError): # C implementation failed
946 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
946 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
947 return pycompat.maplist(self.node, ancs)
947 return pycompat.maplist(self.node, ancs)
948
948
949 def isancestor(self, a, b):
949 def isancestor(self, a, b):
950 """return True if node a is an ancestor of node b
950 """return True if node a is an ancestor of node b
951
951
952 The implementation of this is trivial but the use of
952 The implementation of this is trivial but the use of
953 commonancestorsheads is not."""
953 commonancestorsheads is not."""
954 return a in self.commonancestorsheads(a, b)
954 return a in self.commonancestorsheads(a, b)
955
955
956 def ancestor(self, a, b):
956 def ancestor(self, a, b):
957 """calculate the "best" common ancestor of nodes a and b"""
957 """calculate the "best" common ancestor of nodes a and b"""
958
958
959 a, b = self.rev(a), self.rev(b)
959 a, b = self.rev(a), self.rev(b)
960 try:
960 try:
961 ancs = self.index.ancestors(a, b)
961 ancs = self.index.ancestors(a, b)
962 except (AttributeError, OverflowError):
962 except (AttributeError, OverflowError):
963 ancs = ancestor.ancestors(self.parentrevs, a, b)
963 ancs = ancestor.ancestors(self.parentrevs, a, b)
964 if ancs:
964 if ancs:
965 # choose a consistent winner when there's a tie
965 # choose a consistent winner when there's a tie
966 return min(map(self.node, ancs))
966 return min(map(self.node, ancs))
967 return nullid
967 return nullid
968
968
969 def _match(self, id):
969 def _match(self, id):
970 if isinstance(id, int):
970 if isinstance(id, int):
971 # rev
971 # rev
972 return self.node(id)
972 return self.node(id)
973 if len(id) == 20:
973 if len(id) == 20:
974 # possibly a binary node
974 # possibly a binary node
975 # odds of a binary node being all hex in ASCII are 1 in 10**25
975 # odds of a binary node being all hex in ASCII are 1 in 10**25
976 try:
976 try:
977 node = id
977 node = id
978 self.rev(node) # quick search the index
978 self.rev(node) # quick search the index
979 return node
979 return node
980 except LookupError:
980 except LookupError:
981 pass # may be partial hex id
981 pass # may be partial hex id
982 try:
982 try:
983 # str(rev)
983 # str(rev)
984 rev = int(id)
984 rev = int(id)
985 if str(rev) != id:
985 if str(rev) != id:
986 raise ValueError
986 raise ValueError
987 if rev < 0:
987 if rev < 0:
988 rev = len(self) + rev
988 rev = len(self) + rev
989 if rev < 0 or rev >= len(self):
989 if rev < 0 or rev >= len(self):
990 raise ValueError
990 raise ValueError
991 return self.node(rev)
991 return self.node(rev)
992 except (ValueError, OverflowError):
992 except (ValueError, OverflowError):
993 pass
993 pass
994 if len(id) == 40:
994 if len(id) == 40:
995 try:
995 try:
996 # a full hex nodeid?
996 # a full hex nodeid?
997 node = bin(id)
997 node = bin(id)
998 self.rev(node)
998 self.rev(node)
999 return node
999 return node
1000 except (TypeError, LookupError):
1000 except (TypeError, LookupError):
1001 pass
1001 pass
1002
1002
1003 def _partialmatch(self, id):
1003 def _partialmatch(self, id):
1004 try:
1004 try:
1005 partial = self.index.partialmatch(id)
1005 partial = self.index.partialmatch(id)
1006 if partial and self.hasnode(partial):
1006 if partial and self.hasnode(partial):
1007 return partial
1007 return partial
1008 return None
1008 return None
1009 except RevlogError:
1009 except RevlogError:
1010 # parsers.c radix tree lookup gave multiple matches
1010 # parsers.c radix tree lookup gave multiple matches
1011 # fast path: for unfiltered changelog, radix tree is accurate
1011 # fast path: for unfiltered changelog, radix tree is accurate
1012 if not getattr(self, 'filteredrevs', None):
1012 if not getattr(self, 'filteredrevs', None):
1013 raise LookupError(id, self.indexfile,
1013 raise LookupError(id, self.indexfile,
1014 _('ambiguous identifier'))
1014 _('ambiguous identifier'))
1015 # fall through to slow path that filters hidden revisions
1015 # fall through to slow path that filters hidden revisions
1016 except (AttributeError, ValueError):
1016 except (AttributeError, ValueError):
1017 # we are pure python, or key was too short to search radix tree
1017 # we are pure python, or key was too short to search radix tree
1018 pass
1018 pass
1019
1019
1020 if id in self._pcache:
1020 if id in self._pcache:
1021 return self._pcache[id]
1021 return self._pcache[id]
1022
1022
1023 if len(id) < 40:
1023 if len(id) < 40:
1024 try:
1024 try:
1025 # hex(node)[:...]
1025 # hex(node)[:...]
1026 l = len(id) // 2 # grab an even number of digits
1026 l = len(id) // 2 # grab an even number of digits
1027 prefix = bin(id[:l * 2])
1027 prefix = bin(id[:l * 2])
1028 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1028 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1029 nl = [n for n in nl if hex(n).startswith(id) and
1029 nl = [n for n in nl if hex(n).startswith(id) and
1030 self.hasnode(n)]
1030 self.hasnode(n)]
1031 if len(nl) > 0:
1031 if len(nl) > 0:
1032 if len(nl) == 1:
1032 if len(nl) == 1:
1033 self._pcache[id] = nl[0]
1033 self._pcache[id] = nl[0]
1034 return nl[0]
1034 return nl[0]
1035 raise LookupError(id, self.indexfile,
1035 raise LookupError(id, self.indexfile,
1036 _('ambiguous identifier'))
1036 _('ambiguous identifier'))
1037 return None
1037 return None
1038 except TypeError:
1038 except TypeError:
1039 pass
1039 pass
1040
1040
1041 def lookup(self, id):
1041 def lookup(self, id):
1042 """locate a node based on:
1042 """locate a node based on:
1043 - revision number or str(revision number)
1043 - revision number or str(revision number)
1044 - nodeid or subset of hex nodeid
1044 - nodeid or subset of hex nodeid
1045 """
1045 """
1046 n = self._match(id)
1046 n = self._match(id)
1047 if n is not None:
1047 if n is not None:
1048 return n
1048 return n
1049 n = self._partialmatch(id)
1049 n = self._partialmatch(id)
1050 if n:
1050 if n:
1051 return n
1051 return n
1052
1052
1053 raise LookupError(id, self.indexfile, _('no match found'))
1053 raise LookupError(id, self.indexfile, _('no match found'))
1054
1054
1055 def cmp(self, node, text):
1055 def cmp(self, node, text):
1056 """compare text with a given file revision
1056 """compare text with a given file revision
1057
1057
1058 returns True if text is different than what is stored.
1058 returns True if text is different than what is stored.
1059 """
1059 """
1060 p1, p2 = self.parents(node)
1060 p1, p2 = self.parents(node)
1061 return hash(text, p1, p2) != node
1061 return hash(text, p1, p2) != node
1062
1062
1063 def _addchunk(self, offset, data):
1063 def _addchunk(self, offset, data):
1064 """Add a segment to the revlog cache.
1064 """Add a segment to the revlog cache.
1065
1065
1066 Accepts an absolute offset and the data that is at that location.
1066 Accepts an absolute offset and the data that is at that location.
1067 """
1067 """
1068 o, d = self._chunkcache
1068 o, d = self._chunkcache
1069 # try to add to existing cache
1069 # try to add to existing cache
1070 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1070 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1071 self._chunkcache = o, d + data
1071 self._chunkcache = o, d + data
1072 else:
1072 else:
1073 self._chunkcache = offset, data
1073 self._chunkcache = offset, data
1074
1074
1075 def _loadchunk(self, offset, length, df=None):
1075 def _loadchunk(self, offset, length, df=None):
1076 """Load a segment of raw data from the revlog.
1076 """Load a segment of raw data from the revlog.
1077
1077
1078 Accepts an absolute offset, length to read, and an optional existing
1078 Accepts an absolute offset, length to read, and an optional existing
1079 file handle to read from.
1079 file handle to read from.
1080
1080
1081 If an existing file handle is passed, it will be seeked and the
1081 If an existing file handle is passed, it will be seeked and the
1082 original seek position will NOT be restored.
1082 original seek position will NOT be restored.
1083
1083
1084 Returns a str or buffer of raw byte data.
1084 Returns a str or buffer of raw byte data.
1085 """
1085 """
1086 if df is not None:
1086 if df is not None:
1087 closehandle = False
1087 closehandle = False
1088 else:
1088 else:
1089 if self._inline:
1089 if self._inline:
1090 df = self.opener(self.indexfile)
1090 df = self.opener(self.indexfile)
1091 else:
1091 else:
1092 df = self.opener(self.datafile)
1092 df = self.opener(self.datafile)
1093 closehandle = True
1093 closehandle = True
1094
1094
1095 # Cache data both forward and backward around the requested
1095 # Cache data both forward and backward around the requested
1096 # data, in a fixed size window. This helps speed up operations
1096 # data, in a fixed size window. This helps speed up operations
1097 # involving reading the revlog backwards.
1097 # involving reading the revlog backwards.
1098 cachesize = self._chunkcachesize
1098 cachesize = self._chunkcachesize
1099 realoffset = offset & ~(cachesize - 1)
1099 realoffset = offset & ~(cachesize - 1)
1100 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1100 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1101 - realoffset)
1101 - realoffset)
1102 df.seek(realoffset)
1102 df.seek(realoffset)
1103 d = df.read(reallength)
1103 d = df.read(reallength)
1104 if closehandle:
1104 if closehandle:
1105 df.close()
1105 df.close()
1106 self._addchunk(realoffset, d)
1106 self._addchunk(realoffset, d)
1107 if offset != realoffset or reallength != length:
1107 if offset != realoffset or reallength != length:
1108 return util.buffer(d, offset - realoffset, length)
1108 return util.buffer(d, offset - realoffset, length)
1109 return d
1109 return d
1110
1110
1111 def _getchunk(self, offset, length, df=None):
1111 def _getchunk(self, offset, length, df=None):
1112 """Obtain a segment of raw data from the revlog.
1112 """Obtain a segment of raw data from the revlog.
1113
1113
1114 Accepts an absolute offset, length of bytes to obtain, and an
1114 Accepts an absolute offset, length of bytes to obtain, and an
1115 optional file handle to the already-opened revlog. If the file
1115 optional file handle to the already-opened revlog. If the file
1116 handle is used, it's original seek position will not be preserved.
1116 handle is used, it's original seek position will not be preserved.
1117
1117
1118 Requests for data may be returned from a cache.
1118 Requests for data may be returned from a cache.
1119
1119
1120 Returns a str or a buffer instance of raw byte data.
1120 Returns a str or a buffer instance of raw byte data.
1121 """
1121 """
1122 o, d = self._chunkcache
1122 o, d = self._chunkcache
1123 l = len(d)
1123 l = len(d)
1124
1124
1125 # is it in the cache?
1125 # is it in the cache?
1126 cachestart = offset - o
1126 cachestart = offset - o
1127 cacheend = cachestart + length
1127 cacheend = cachestart + length
1128 if cachestart >= 0 and cacheend <= l:
1128 if cachestart >= 0 and cacheend <= l:
1129 if cachestart == 0 and cacheend == l:
1129 if cachestart == 0 and cacheend == l:
1130 return d # avoid a copy
1130 return d # avoid a copy
1131 return util.buffer(d, cachestart, cacheend - cachestart)
1131 return util.buffer(d, cachestart, cacheend - cachestart)
1132
1132
1133 return self._loadchunk(offset, length, df=df)
1133 return self._loadchunk(offset, length, df=df)
1134
1134
1135 def _chunkraw(self, startrev, endrev, df=None):
1135 def _chunkraw(self, startrev, endrev, df=None):
1136 """Obtain a segment of raw data corresponding to a range of revisions.
1136 """Obtain a segment of raw data corresponding to a range of revisions.
1137
1137
1138 Accepts the start and end revisions and an optional already-open
1138 Accepts the start and end revisions and an optional already-open
1139 file handle to be used for reading. If the file handle is read, its
1139 file handle to be used for reading. If the file handle is read, its
1140 seek position will not be preserved.
1140 seek position will not be preserved.
1141
1141
1142 Requests for data may be satisfied by a cache.
1142 Requests for data may be satisfied by a cache.
1143
1143
1144 Returns a 2-tuple of (offset, data) for the requested range of
1144 Returns a 2-tuple of (offset, data) for the requested range of
1145 revisions. Offset is the integer offset from the beginning of the
1145 revisions. Offset is the integer offset from the beginning of the
1146 revlog and data is a str or buffer of the raw byte data.
1146 revlog and data is a str or buffer of the raw byte data.
1147
1147
1148 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1148 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1149 to determine where each revision's data begins and ends.
1149 to determine where each revision's data begins and ends.
1150 """
1150 """
1151 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1151 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1152 # (functions are expensive).
1152 # (functions are expensive).
1153 index = self.index
1153 index = self.index
1154 istart = index[startrev]
1154 istart = index[startrev]
1155 start = int(istart[0] >> 16)
1155 start = int(istart[0] >> 16)
1156 if startrev == endrev:
1156 if startrev == endrev:
1157 end = start + istart[1]
1157 end = start + istart[1]
1158 else:
1158 else:
1159 iend = index[endrev]
1159 iend = index[endrev]
1160 end = int(iend[0] >> 16) + iend[1]
1160 end = int(iend[0] >> 16) + iend[1]
1161
1161
1162 if self._inline:
1162 if self._inline:
1163 start += (startrev + 1) * self._io.size
1163 start += (startrev + 1) * self._io.size
1164 end += (endrev + 1) * self._io.size
1164 end += (endrev + 1) * self._io.size
1165 length = end - start
1165 length = end - start
1166
1166
1167 return start, self._getchunk(start, length, df=df)
1167 return start, self._getchunk(start, length, df=df)
1168
1168
1169 def _chunk(self, rev, df=None):
1169 def _chunk(self, rev, df=None):
1170 """Obtain a single decompressed chunk for a revision.
1170 """Obtain a single decompressed chunk for a revision.
1171
1171
1172 Accepts an integer revision and an optional already-open file handle
1172 Accepts an integer revision and an optional already-open file handle
1173 to be used for reading. If used, the seek position of the file will not
1173 to be used for reading. If used, the seek position of the file will not
1174 be preserved.
1174 be preserved.
1175
1175
1176 Returns a str holding uncompressed data for the requested revision.
1176 Returns a str holding uncompressed data for the requested revision.
1177 """
1177 """
1178 return self.decompress(self._chunkraw(rev, rev, df=df)[1])
1178 return self.decompress(self._chunkraw(rev, rev, df=df)[1])
1179
1179
1180 def _chunks(self, revs, df=None):
1180 def _chunks(self, revs, df=None):
1181 """Obtain decompressed chunks for the specified revisions.
1181 """Obtain decompressed chunks for the specified revisions.
1182
1182
1183 Accepts an iterable of numeric revisions that are assumed to be in
1183 Accepts an iterable of numeric revisions that are assumed to be in
1184 ascending order. Also accepts an optional already-open file handle
1184 ascending order. Also accepts an optional already-open file handle
1185 to be used for reading. If used, the seek position of the file will
1185 to be used for reading. If used, the seek position of the file will
1186 not be preserved.
1186 not be preserved.
1187
1187
1188 This function is similar to calling ``self._chunk()`` multiple times,
1188 This function is similar to calling ``self._chunk()`` multiple times,
1189 but is faster.
1189 but is faster.
1190
1190
1191 Returns a list with decompressed data for each requested revision.
1191 Returns a list with decompressed data for each requested revision.
1192 """
1192 """
1193 if not revs:
1193 if not revs:
1194 return []
1194 return []
1195 start = self.start
1195 start = self.start
1196 length = self.length
1196 length = self.length
1197 inline = self._inline
1197 inline = self._inline
1198 iosize = self._io.size
1198 iosize = self._io.size
1199 buffer = util.buffer
1199 buffer = util.buffer
1200
1200
1201 l = []
1201 l = []
1202 ladd = l.append
1202 ladd = l.append
1203
1203
1204 try:
1204 try:
1205 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1205 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1206 except OverflowError:
1206 except OverflowError:
1207 # issue4215 - we can't cache a run of chunks greater than
1207 # issue4215 - we can't cache a run of chunks greater than
1208 # 2G on Windows
1208 # 2G on Windows
1209 return [self._chunk(rev, df=df) for rev in revs]
1209 return [self._chunk(rev, df=df) for rev in revs]
1210
1210
1211 decomp = self.decompress
1211 decomp = self.decompress
1212 for rev in revs:
1212 for rev in revs:
1213 chunkstart = start(rev)
1213 chunkstart = start(rev)
1214 if inline:
1214 if inline:
1215 chunkstart += (rev + 1) * iosize
1215 chunkstart += (rev + 1) * iosize
1216 chunklength = length(rev)
1216 chunklength = length(rev)
1217 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1217 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1218
1218
1219 return l
1219 return l
1220
1220
1221 def _chunkclear(self):
1221 def _chunkclear(self):
1222 """Clear the raw chunk cache."""
1222 """Clear the raw chunk cache."""
1223 self._chunkcache = (0, '')
1223 self._chunkcache = (0, '')
1224
1224
1225 def deltaparent(self, rev):
1225 def deltaparent(self, rev):
1226 """return deltaparent of the given revision"""
1226 """return deltaparent of the given revision"""
1227 base = self.index[rev][3]
1227 base = self.index[rev][3]
1228 if base == rev:
1228 if base == rev:
1229 return nullrev
1229 return nullrev
1230 elif self._generaldelta:
1230 elif self._generaldelta:
1231 return base
1231 return base
1232 else:
1232 else:
1233 return rev - 1
1233 return rev - 1
1234
1234
1235 def revdiff(self, rev1, rev2):
1235 def revdiff(self, rev1, rev2):
1236 """return or calculate a delta between two revisions"""
1236 """return or calculate a delta between two revisions"""
1237 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1237 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1238 return bytes(self._chunk(rev2))
1238 return bytes(self._chunk(rev2))
1239
1239
1240 return mdiff.textdiff(self.revision(rev1),
1240 return mdiff.textdiff(self.revision(rev1),
1241 self.revision(rev2))
1241 self.revision(rev2))
1242
1242
1243 def revision(self, nodeorrev, _df=None, raw=False):
1243 def revision(self, nodeorrev, _df=None, raw=False):
1244 """return an uncompressed revision of a given node or revision
1244 """return an uncompressed revision of a given node or revision
1245 number.
1245 number.
1246
1246
1247 _df - an existing file handle to read from. (internal-only)
1247 _df - an existing file handle to read from. (internal-only)
1248 raw - an optional argument specifying if the revision data is to be
1248 raw - an optional argument specifying if the revision data is to be
1249 treated as raw data when applying flag transforms. 'raw' should be set
1249 treated as raw data when applying flag transforms. 'raw' should be set
1250 to True when generating changegroups or in debug commands.
1250 to True when generating changegroups or in debug commands.
1251 """
1251 """
1252 if isinstance(nodeorrev, int):
1252 if isinstance(nodeorrev, int):
1253 rev = nodeorrev
1253 rev = nodeorrev
1254 node = self.node(rev)
1254 node = self.node(rev)
1255 else:
1255 else:
1256 node = nodeorrev
1256 node = nodeorrev
1257 rev = None
1257 rev = None
1258
1258
1259 cachedrev = None
1259 cachedrev = None
1260 if node == nullid:
1260 if node == nullid:
1261 return ""
1261 return ""
1262 if self._cache:
1262 if self._cache:
1263 if self._cache[0] == node:
1263 if self._cache[0] == node:
1264 return self._cache[2]
1264 return self._cache[2]
1265 cachedrev = self._cache[1]
1265 cachedrev = self._cache[1]
1266
1266
1267 # look up what we need to read
1267 # look up what we need to read
1268 text = None
1268 text = None
1269 if rev is None:
1269 if rev is None:
1270 rev = self.rev(node)
1270 rev = self.rev(node)
1271
1271
1272 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1272 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1273 if stopped:
1273 if stopped:
1274 text = self._cache[2]
1274 text = self._cache[2]
1275
1275
1276 # drop cache to save memory
1276 # drop cache to save memory
1277 self._cache = None
1277 self._cache = None
1278
1278
1279 bins = self._chunks(chain, df=_df)
1279 bins = self._chunks(chain, df=_df)
1280 if text is None:
1280 if text is None:
1281 text = bytes(bins[0])
1281 text = bytes(bins[0])
1282 bins = bins[1:]
1282 bins = bins[1:]
1283
1283
1284 text = mdiff.patches(text, bins)
1284 text = mdiff.patches(text, bins)
1285
1285
1286 text, validatehash = self._processflags(text, self.flags(rev), 'read',
1286 text, validatehash = self._processflags(text, self.flags(rev), 'read',
1287 raw=raw)
1287 raw=raw)
1288 if validatehash:
1288 if validatehash:
1289 self.checkhash(text, node, rev=rev)
1289 self.checkhash(text, node, rev=rev)
1290
1290
1291 self._cache = (node, rev, text)
1291 self._cache = (node, rev, text)
1292 return text
1292 return text
1293
1293
1294 def hash(self, text, p1, p2):
1294 def hash(self, text, p1, p2):
1295 """Compute a node hash.
1295 """Compute a node hash.
1296
1296
1297 Available as a function so that subclasses can replace the hash
1297 Available as a function so that subclasses can replace the hash
1298 as needed.
1298 as needed.
1299 """
1299 """
1300 return hash(text, p1, p2)
1300 return hash(text, p1, p2)
1301
1301
1302 def _processflags(self, text, flags, operation, raw=False):
1302 def _processflags(self, text, flags, operation, raw=False):
1303 """Inspect revision data flags and applies transforms defined by
1303 """Inspect revision data flags and applies transforms defined by
1304 registered flag processors.
1304 registered flag processors.
1305
1305
1306 ``text`` - the revision data to process
1306 ``text`` - the revision data to process
1307 ``flags`` - the revision flags
1307 ``flags`` - the revision flags
1308 ``operation`` - the operation being performed (read or write)
1308 ``operation`` - the operation being performed (read or write)
1309 ``raw`` - an optional argument describing if the raw transform should be
1309 ``raw`` - an optional argument describing if the raw transform should be
1310 applied.
1310 applied.
1311
1311
1312 This method processes the flags in the order (or reverse order if
1312 This method processes the flags in the order (or reverse order if
1313 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1313 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1314 flag processors registered for present flags. The order of flags defined
1314 flag processors registered for present flags. The order of flags defined
1315 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1315 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1316
1316
1317 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1317 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1318 processed text and ``validatehash`` is a bool indicating whether the
1318 processed text and ``validatehash`` is a bool indicating whether the
1319 returned text should be checked for hash integrity.
1319 returned text should be checked for hash integrity.
1320
1320
1321 Note: If the ``raw`` argument is set, it has precedence over the
1321 Note: If the ``raw`` argument is set, it has precedence over the
1322 operation and will only update the value of ``validatehash``.
1322 operation and will only update the value of ``validatehash``.
1323 """
1323 """
1324 if not operation in ('read', 'write'):
1324 if not operation in ('read', 'write'):
1325 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1325 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1326 # Check all flags are known.
1326 # Check all flags are known.
1327 if flags & ~REVIDX_KNOWN_FLAGS:
1327 if flags & ~REVIDX_KNOWN_FLAGS:
1328 raise RevlogError(_("incompatible revision flag '%#x'") %
1328 raise RevlogError(_("incompatible revision flag '%#x'") %
1329 (flags & ~REVIDX_KNOWN_FLAGS))
1329 (flags & ~REVIDX_KNOWN_FLAGS))
1330 validatehash = True
1330 validatehash = True
1331 # Depending on the operation (read or write), the order might be
1331 # Depending on the operation (read or write), the order might be
1332 # reversed due to non-commutative transforms.
1332 # reversed due to non-commutative transforms.
1333 orderedflags = REVIDX_FLAGS_ORDER
1333 orderedflags = REVIDX_FLAGS_ORDER
1334 if operation == 'write':
1334 if operation == 'write':
1335 orderedflags = reversed(orderedflags)
1335 orderedflags = reversed(orderedflags)
1336
1336
1337 for flag in orderedflags:
1337 for flag in orderedflags:
1338 # If a flagprocessor has been registered for a known flag, apply the
1338 # If a flagprocessor has been registered for a known flag, apply the
1339 # related operation transform and update result tuple.
1339 # related operation transform and update result tuple.
1340 if flag & flags:
1340 if flag & flags:
1341 vhash = True
1341 vhash = True
1342
1342
1343 if flag not in _flagprocessors:
1343 if flag not in _flagprocessors:
1344 message = _("missing processor for flag '%#x'") % (flag)
1344 message = _("missing processor for flag '%#x'") % (flag)
1345 raise RevlogError(message)
1345 raise RevlogError(message)
1346
1346
1347 processor = _flagprocessors[flag]
1347 processor = _flagprocessors[flag]
1348 if processor is not None:
1348 if processor is not None:
1349 readtransform, writetransform, rawtransform = processor
1349 readtransform, writetransform, rawtransform = processor
1350
1350
1351 if raw:
1351 if raw:
1352 vhash = rawtransform(self, text)
1352 vhash = rawtransform(self, text)
1353 elif operation == 'read':
1353 elif operation == 'read':
1354 text, vhash = readtransform(self, text)
1354 text, vhash = readtransform(self, text)
1355 else: # write operation
1355 else: # write operation
1356 text, vhash = writetransform(self, text)
1356 text, vhash = writetransform(self, text)
1357 validatehash = validatehash and vhash
1357 validatehash = validatehash and vhash
1358
1358
1359 return text, validatehash
1359 return text, validatehash
1360
1360
1361 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1361 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1362 """Check node hash integrity.
1362 """Check node hash integrity.
1363
1363
1364 Available as a function so that subclasses can extend hash mismatch
1364 Available as a function so that subclasses can extend hash mismatch
1365 behaviors as needed.
1365 behaviors as needed.
1366 """
1366 """
1367 if p1 is None and p2 is None:
1367 if p1 is None and p2 is None:
1368 p1, p2 = self.parents(node)
1368 p1, p2 = self.parents(node)
1369 if node != self.hash(text, p1, p2):
1369 if node != self.hash(text, p1, p2):
1370 revornode = rev
1370 revornode = rev
1371 if revornode is None:
1371 if revornode is None:
1372 revornode = templatefilters.short(hex(node))
1372 revornode = templatefilters.short(hex(node))
1373 raise RevlogError(_("integrity check failed on %s:%s")
1373 raise RevlogError(_("integrity check failed on %s:%s")
1374 % (self.indexfile, revornode))
1374 % (self.indexfile, revornode))
1375
1375
1376 def checkinlinesize(self, tr, fp=None):
1376 def checkinlinesize(self, tr, fp=None):
1377 """Check if the revlog is too big for inline and convert if so.
1377 """Check if the revlog is too big for inline and convert if so.
1378
1378
1379 This should be called after revisions are added to the revlog. If the
1379 This should be called after revisions are added to the revlog. If the
1380 revlog has grown too large to be an inline revlog, it will convert it
1380 revlog has grown too large to be an inline revlog, it will convert it
1381 to use multiple index and data files.
1381 to use multiple index and data files.
1382 """
1382 """
1383 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1383 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1384 return
1384 return
1385
1385
1386 trinfo = tr.find(self.indexfile)
1386 trinfo = tr.find(self.indexfile)
1387 if trinfo is None:
1387 if trinfo is None:
1388 raise RevlogError(_("%s not found in the transaction")
1388 raise RevlogError(_("%s not found in the transaction")
1389 % self.indexfile)
1389 % self.indexfile)
1390
1390
1391 trindex = trinfo[2]
1391 trindex = trinfo[2]
1392 if trindex is not None:
1392 if trindex is not None:
1393 dataoff = self.start(trindex)
1393 dataoff = self.start(trindex)
1394 else:
1394 else:
1395 # revlog was stripped at start of transaction, use all leftover data
1395 # revlog was stripped at start of transaction, use all leftover data
1396 trindex = len(self) - 1
1396 trindex = len(self) - 1
1397 dataoff = self.end(-2)
1397 dataoff = self.end(-2)
1398
1398
1399 tr.add(self.datafile, dataoff)
1399 tr.add(self.datafile, dataoff)
1400
1400
1401 if fp:
1401 if fp:
1402 fp.flush()
1402 fp.flush()
1403 fp.close()
1403 fp.close()
1404
1404
1405 df = self.opener(self.datafile, 'w')
1405 df = self.opener(self.datafile, 'w')
1406 try:
1406 try:
1407 for r in self:
1407 for r in self:
1408 df.write(self._chunkraw(r, r)[1])
1408 df.write(self._chunkraw(r, r)[1])
1409 finally:
1409 finally:
1410 df.close()
1410 df.close()
1411
1411
1412 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1412 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1413 checkambig=self._checkambig)
1413 checkambig=self._checkambig)
1414 self.version &= ~(REVLOGNGINLINEDATA)
1414 self.version &= ~(REVLOGNGINLINEDATA)
1415 self._inline = False
1415 self._inline = False
1416 for i in self:
1416 for i in self:
1417 e = self._io.packentry(self.index[i], self.node, self.version, i)
1417 e = self._io.packentry(self.index[i], self.node, self.version, i)
1418 fp.write(e)
1418 fp.write(e)
1419
1419
1420 # if we don't call close, the temp file will never replace the
1420 # if we don't call close, the temp file will never replace the
1421 # real index
1421 # real index
1422 fp.close()
1422 fp.close()
1423
1423
1424 tr.replace(self.indexfile, trindex * self._io.size)
1424 tr.replace(self.indexfile, trindex * self._io.size)
1425 self._chunkclear()
1425 self._chunkclear()
1426
1426
1427 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1427 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1428 node=None, flags=REVIDX_DEFAULT_FLAGS):
1428 node=None, flags=REVIDX_DEFAULT_FLAGS):
1429 """add a revision to the log
1429 """add a revision to the log
1430
1430
1431 text - the revision data to add
1431 text - the revision data to add
1432 transaction - the transaction object used for rollback
1432 transaction - the transaction object used for rollback
1433 link - the linkrev data to add
1433 link - the linkrev data to add
1434 p1, p2 - the parent nodeids of the revision
1434 p1, p2 - the parent nodeids of the revision
1435 cachedelta - an optional precomputed delta
1435 cachedelta - an optional precomputed delta
1436 node - nodeid of revision; typically node is not specified, and it is
1436 node - nodeid of revision; typically node is not specified, and it is
1437 computed by default as hash(text, p1, p2), however subclasses might
1437 computed by default as hash(text, p1, p2), however subclasses might
1438 use different hashing method (and override checkhash() in such case)
1438 use different hashing method (and override checkhash() in such case)
1439 flags - the known flags to set on the revision
1439 flags - the known flags to set on the revision
1440 """
1440 """
1441 if link == nullrev:
1441 if link == nullrev:
1442 raise RevlogError(_("attempted to add linkrev -1 to %s")
1442 raise RevlogError(_("attempted to add linkrev -1 to %s")
1443 % self.indexfile)
1443 % self.indexfile)
1444
1444
1445 if flags:
1445 if flags:
1446 node = node or self.hash(text, p1, p2)
1446 node = node or self.hash(text, p1, p2)
1447
1447
1448 newtext, validatehash = self._processflags(text, flags, 'write')
1448 newtext, validatehash = self._processflags(text, flags, 'write')
1449
1449
1450 # If the flag processor modifies the revision data, ignore any provided
1450 # If the flag processor modifies the revision data, ignore any provided
1451 # cachedelta.
1451 # cachedelta.
1452 if newtext != text:
1452 if newtext != text:
1453 cachedelta = None
1453 cachedelta = None
1454 text = newtext
1454 text = newtext
1455
1455
1456 if len(text) > _maxentrysize:
1456 if len(text) > _maxentrysize:
1457 raise RevlogError(
1457 raise RevlogError(
1458 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1458 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1459 % (self.indexfile, len(text)))
1459 % (self.indexfile, len(text)))
1460
1460
1461 node = node or self.hash(text, p1, p2)
1461 node = node or self.hash(text, p1, p2)
1462 if node in self.nodemap:
1462 if node in self.nodemap:
1463 return node
1463 return node
1464
1464
1465 if validatehash:
1465 if validatehash:
1466 self.checkhash(text, node, p1=p1, p2=p2)
1466 self.checkhash(text, node, p1=p1, p2=p2)
1467
1467
1468 dfh = None
1468 dfh = None
1469 if not self._inline:
1469 if not self._inline:
1470 dfh = self.opener(self.datafile, "a+")
1470 dfh = self.opener(self.datafile, "a+")
1471 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1471 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1472 try:
1472 try:
1473 return self._addrevision(node, text, transaction, link, p1, p2,
1473 return self._addrevision(node, text, transaction, link, p1, p2,
1474 flags, cachedelta, ifh, dfh)
1474 flags, cachedelta, ifh, dfh)
1475 finally:
1475 finally:
1476 if dfh:
1476 if dfh:
1477 dfh.close()
1477 dfh.close()
1478 ifh.close()
1478 ifh.close()
1479
1479
1480 def compress(self, data):
1480 def compress(self, data):
1481 """Generate a possibly-compressed representation of data."""
1481 """Generate a possibly-compressed representation of data."""
1482 if not data:
1482 if not data:
1483 return '', data
1483 return '', data
1484
1484
1485 compressed = self._compressor.compress(data)
1485 compressed = self._compressor.compress(data)
1486
1486
1487 if compressed:
1487 if compressed:
1488 # The revlog compressor added the header in the returned data.
1488 # The revlog compressor added the header in the returned data.
1489 return '', compressed
1489 return '', compressed
1490
1490
1491 if data[0:1] == '\0':
1491 if data[0:1] == '\0':
1492 return '', data
1492 return '', data
1493 return 'u', data
1493 return 'u', data
1494
1494
1495 def decompress(self, data):
1495 def decompress(self, data):
1496 """Decompress a revlog chunk.
1496 """Decompress a revlog chunk.
1497
1497
1498 The chunk is expected to begin with a header identifying the
1498 The chunk is expected to begin with a header identifying the
1499 format type so it can be routed to an appropriate decompressor.
1499 format type so it can be routed to an appropriate decompressor.
1500 """
1500 """
1501 if not data:
1501 if not data:
1502 return data
1502 return data
1503
1503
1504 # Revlogs are read much more frequently than they are written and many
1504 # Revlogs are read much more frequently than they are written and many
1505 # chunks only take microseconds to decompress, so performance is
1505 # chunks only take microseconds to decompress, so performance is
1506 # important here.
1506 # important here.
1507 #
1507 #
1508 # We can make a few assumptions about revlogs:
1508 # We can make a few assumptions about revlogs:
1509 #
1509 #
1510 # 1) the majority of chunks will be compressed (as opposed to inline
1510 # 1) the majority of chunks will be compressed (as opposed to inline
1511 # raw data).
1511 # raw data).
1512 # 2) decompressing *any* data will likely by at least 10x slower than
1512 # 2) decompressing *any* data will likely by at least 10x slower than
1513 # returning raw inline data.
1513 # returning raw inline data.
1514 # 3) we want to prioritize common and officially supported compression
1514 # 3) we want to prioritize common and officially supported compression
1515 # engines
1515 # engines
1516 #
1516 #
1517 # It follows that we want to optimize for "decompress compressed data
1517 # It follows that we want to optimize for "decompress compressed data
1518 # when encoded with common and officially supported compression engines"
1518 # when encoded with common and officially supported compression engines"
1519 # case over "raw data" and "data encoded by less common or non-official
1519 # case over "raw data" and "data encoded by less common or non-official
1520 # compression engines." That is why we have the inline lookup first
1520 # compression engines." That is why we have the inline lookup first
1521 # followed by the compengines lookup.
1521 # followed by the compengines lookup.
1522 #
1522 #
1523 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1523 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1524 # compressed chunks. And this matters for changelog and manifest reads.
1524 # compressed chunks. And this matters for changelog and manifest reads.
1525 t = data[0:1]
1525 t = data[0:1]
1526
1526
1527 if t == 'x':
1527 if t == 'x':
1528 try:
1528 try:
1529 return _zlibdecompress(data)
1529 return _zlibdecompress(data)
1530 except zlib.error as e:
1530 except zlib.error as e:
1531 raise RevlogError(_('revlog decompress error: %s') % str(e))
1531 raise RevlogError(_('revlog decompress error: %s') % str(e))
1532 # '\0' is more common than 'u' so it goes first.
1532 # '\0' is more common than 'u' so it goes first.
1533 elif t == '\0':
1533 elif t == '\0':
1534 return data
1534 return data
1535 elif t == 'u':
1535 elif t == 'u':
1536 return util.buffer(data, 1)
1536 return util.buffer(data, 1)
1537
1537
1538 try:
1538 try:
1539 compressor = self._decompressors[t]
1539 compressor = self._decompressors[t]
1540 except KeyError:
1540 except KeyError:
1541 try:
1541 try:
1542 engine = util.compengines.forrevlogheader(t)
1542 engine = util.compengines.forrevlogheader(t)
1543 compressor = engine.revlogcompressor()
1543 compressor = engine.revlogcompressor()
1544 self._decompressors[t] = compressor
1544 self._decompressors[t] = compressor
1545 except KeyError:
1545 except KeyError:
1546 raise RevlogError(_('unknown compression type %r') % t)
1546 raise RevlogError(_('unknown compression type %r') % t)
1547
1547
1548 return compressor.decompress(data)
1548 return compressor.decompress(data)
1549
1549
1550 def _isgooddelta(self, d, textlen):
1550 def _isgooddelta(self, d, textlen):
1551 """Returns True if the given delta is good. Good means that it is within
1551 """Returns True if the given delta is good. Good means that it is within
1552 the disk span, disk size, and chain length bounds that we know to be
1552 the disk span, disk size, and chain length bounds that we know to be
1553 performant."""
1553 performant."""
1554 if d is None:
1554 if d is None:
1555 return False
1555 return False
1556
1556
1557 # - 'dist' is the distance from the base revision -- bounding it limits
1557 # - 'dist' is the distance from the base revision -- bounding it limits
1558 # the amount of I/O we need to do.
1558 # the amount of I/O we need to do.
1559 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1559 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1560 # to apply -- bounding it limits the amount of CPU we consume.
1560 # to apply -- bounding it limits the amount of CPU we consume.
1561 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1561 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1562 if (dist > textlen * 4 or l > textlen or
1562 if (dist > textlen * 4 or l > textlen or
1563 compresseddeltalen > textlen * 2 or
1563 compresseddeltalen > textlen * 2 or
1564 (self._maxchainlen and chainlen > self._maxchainlen)):
1564 (self._maxchainlen and chainlen > self._maxchainlen)):
1565 return False
1565 return False
1566
1566
1567 return True
1567 return True
1568
1568
1569 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1569 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1570 cachedelta, ifh, dfh, alwayscache=False, raw=False):
1570 cachedelta, ifh, dfh, alwayscache=False, raw=False):
1571 """internal function to add revisions to the log
1571 """internal function to add revisions to the log
1572
1572
1573 see addrevision for argument descriptions.
1573 see addrevision for argument descriptions.
1574 invariants:
1574 invariants:
1575 - text is optional (can be None); if not set, cachedelta must be set.
1575 - text is optional (can be None); if not set, cachedelta must be set.
1576 if both are set, they must correspond to each other.
1576 if both are set, they must correspond to each other.
1577 - raw is optional; if set to True, it indicates the revision data is to
1577 - raw is optional; if set to True, it indicates the revision data is to
1578 be treated by _processflags() as raw. It is usually set by changegroup
1578 be treated by _processflags() as raw. It is usually set by changegroup
1579 generation and debug commands.
1579 generation and debug commands.
1580 """
1580 """
1581 btext = [text]
1581 btext = [text]
1582 def buildtext():
1582 def buildtext():
1583 if btext[0] is not None:
1583 if btext[0] is not None:
1584 return btext[0]
1584 return btext[0]
1585 baserev = cachedelta[0]
1585 baserev = cachedelta[0]
1586 delta = cachedelta[1]
1586 delta = cachedelta[1]
1587 # special case deltas which replace entire base; no need to decode
1587 # special case deltas which replace entire base; no need to decode
1588 # base revision. this neatly avoids censored bases, which throw when
1588 # base revision. this neatly avoids censored bases, which throw when
1589 # they're decoded.
1589 # they're decoded.
1590 hlen = struct.calcsize(">lll")
1590 hlen = struct.calcsize(">lll")
1591 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1591 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1592 len(delta) - hlen):
1592 len(delta) - hlen):
1593 btext[0] = delta[hlen:]
1593 btext[0] = delta[hlen:]
1594 else:
1594 else:
1595 if self._inline:
1595 if self._inline:
1596 fh = ifh
1596 fh = ifh
1597 else:
1597 else:
1598 fh = dfh
1598 fh = dfh
1599 basetext = self.revision(self.node(baserev), _df=fh, raw=raw)
1599 basetext = self.revision(baserev, _df=fh, raw=raw)
1600 btext[0] = mdiff.patch(basetext, delta)
1600 btext[0] = mdiff.patch(basetext, delta)
1601
1601
1602 try:
1602 try:
1603 res = self._processflags(btext[0], flags, 'read', raw=raw)
1603 res = self._processflags(btext[0], flags, 'read', raw=raw)
1604 btext[0], validatehash = res
1604 btext[0], validatehash = res
1605 if validatehash:
1605 if validatehash:
1606 self.checkhash(btext[0], node, p1=p1, p2=p2)
1606 self.checkhash(btext[0], node, p1=p1, p2=p2)
1607 if flags & REVIDX_ISCENSORED:
1607 if flags & REVIDX_ISCENSORED:
1608 raise RevlogError(_('node %s is not censored') % node)
1608 raise RevlogError(_('node %s is not censored') % node)
1609 except CensoredNodeError:
1609 except CensoredNodeError:
1610 # must pass the censored index flag to add censored revisions
1610 # must pass the censored index flag to add censored revisions
1611 if not flags & REVIDX_ISCENSORED:
1611 if not flags & REVIDX_ISCENSORED:
1612 raise
1612 raise
1613 return btext[0]
1613 return btext[0]
1614
1614
1615 def builddelta(rev):
1615 def builddelta(rev):
1616 # can we use the cached delta?
1616 # can we use the cached delta?
1617 if cachedelta and cachedelta[0] == rev:
1617 if cachedelta and cachedelta[0] == rev:
1618 delta = cachedelta[1]
1618 delta = cachedelta[1]
1619 else:
1619 else:
1620 t = buildtext()
1620 t = buildtext()
1621 if self.iscensored(rev):
1621 if self.iscensored(rev):
1622 # deltas based on a censored revision must replace the
1622 # deltas based on a censored revision must replace the
1623 # full content in one patch, so delta works everywhere
1623 # full content in one patch, so delta works everywhere
1624 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1624 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1625 delta = header + t
1625 delta = header + t
1626 else:
1626 else:
1627 if self._inline:
1627 if self._inline:
1628 fh = ifh
1628 fh = ifh
1629 else:
1629 else:
1630 fh = dfh
1630 fh = dfh
1631 ptext = self.revision(self.node(rev), _df=fh)
1631 ptext = self.revision(rev, _df=fh)
1632 delta = mdiff.textdiff(ptext, t)
1632 delta = mdiff.textdiff(ptext, t)
1633 header, data = self.compress(delta)
1633 header, data = self.compress(delta)
1634 deltalen = len(header) + len(data)
1634 deltalen = len(header) + len(data)
1635 chainbase = self.chainbase(rev)
1635 chainbase = self.chainbase(rev)
1636 dist = deltalen + offset - self.start(chainbase)
1636 dist = deltalen + offset - self.start(chainbase)
1637 if self._generaldelta:
1637 if self._generaldelta:
1638 base = rev
1638 base = rev
1639 else:
1639 else:
1640 base = chainbase
1640 base = chainbase
1641 chainlen, compresseddeltalen = self._chaininfo(rev)
1641 chainlen, compresseddeltalen = self._chaininfo(rev)
1642 chainlen += 1
1642 chainlen += 1
1643 compresseddeltalen += deltalen
1643 compresseddeltalen += deltalen
1644 return (dist, deltalen, (header, data), base,
1644 return (dist, deltalen, (header, data), base,
1645 chainbase, chainlen, compresseddeltalen)
1645 chainbase, chainlen, compresseddeltalen)
1646
1646
1647 curr = len(self)
1647 curr = len(self)
1648 prev = curr - 1
1648 prev = curr - 1
1649 offset = self.end(prev)
1649 offset = self.end(prev)
1650 delta = None
1650 delta = None
1651 p1r, p2r = self.rev(p1), self.rev(p2)
1651 p1r, p2r = self.rev(p1), self.rev(p2)
1652
1652
1653 # full versions are inserted when the needed deltas
1653 # full versions are inserted when the needed deltas
1654 # become comparable to the uncompressed text
1654 # become comparable to the uncompressed text
1655 if text is None:
1655 if text is None:
1656 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1656 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1657 cachedelta[1])
1657 cachedelta[1])
1658 else:
1658 else:
1659 textlen = len(text)
1659 textlen = len(text)
1660
1660
1661 # should we try to build a delta?
1661 # should we try to build a delta?
1662 if prev != nullrev and self.storedeltachains:
1662 if prev != nullrev and self.storedeltachains:
1663 tested = set()
1663 tested = set()
1664 # This condition is true most of the time when processing
1664 # This condition is true most of the time when processing
1665 # changegroup data into a generaldelta repo. The only time it
1665 # changegroup data into a generaldelta repo. The only time it
1666 # isn't true is if this is the first revision in a delta chain
1666 # isn't true is if this is the first revision in a delta chain
1667 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1667 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1668 if cachedelta and self._generaldelta and self._lazydeltabase:
1668 if cachedelta and self._generaldelta and self._lazydeltabase:
1669 # Assume what we received from the server is a good choice
1669 # Assume what we received from the server is a good choice
1670 # build delta will reuse the cache
1670 # build delta will reuse the cache
1671 candidatedelta = builddelta(cachedelta[0])
1671 candidatedelta = builddelta(cachedelta[0])
1672 tested.add(cachedelta[0])
1672 tested.add(cachedelta[0])
1673 if self._isgooddelta(candidatedelta, textlen):
1673 if self._isgooddelta(candidatedelta, textlen):
1674 delta = candidatedelta
1674 delta = candidatedelta
1675 if delta is None and self._generaldelta:
1675 if delta is None and self._generaldelta:
1676 # exclude already lazy tested base if any
1676 # exclude already lazy tested base if any
1677 parents = [p for p in (p1r, p2r)
1677 parents = [p for p in (p1r, p2r)
1678 if p != nullrev and p not in tested]
1678 if p != nullrev and p not in tested]
1679 if parents and not self._aggressivemergedeltas:
1679 if parents and not self._aggressivemergedeltas:
1680 # Pick whichever parent is closer to us (to minimize the
1680 # Pick whichever parent is closer to us (to minimize the
1681 # chance of having to build a fulltext).
1681 # chance of having to build a fulltext).
1682 parents = [max(parents)]
1682 parents = [max(parents)]
1683 tested.update(parents)
1683 tested.update(parents)
1684 pdeltas = []
1684 pdeltas = []
1685 for p in parents:
1685 for p in parents:
1686 pd = builddelta(p)
1686 pd = builddelta(p)
1687 if self._isgooddelta(pd, textlen):
1687 if self._isgooddelta(pd, textlen):
1688 pdeltas.append(pd)
1688 pdeltas.append(pd)
1689 if pdeltas:
1689 if pdeltas:
1690 delta = min(pdeltas, key=lambda x: x[1])
1690 delta = min(pdeltas, key=lambda x: x[1])
1691 if delta is None and prev not in tested:
1691 if delta is None and prev not in tested:
1692 # other approach failed try against prev to hopefully save us a
1692 # other approach failed try against prev to hopefully save us a
1693 # fulltext.
1693 # fulltext.
1694 candidatedelta = builddelta(prev)
1694 candidatedelta = builddelta(prev)
1695 if self._isgooddelta(candidatedelta, textlen):
1695 if self._isgooddelta(candidatedelta, textlen):
1696 delta = candidatedelta
1696 delta = candidatedelta
1697 if delta is not None:
1697 if delta is not None:
1698 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1698 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1699 else:
1699 else:
1700 text = buildtext()
1700 text = buildtext()
1701 data = self.compress(text)
1701 data = self.compress(text)
1702 l = len(data[1]) + len(data[0])
1702 l = len(data[1]) + len(data[0])
1703 base = chainbase = curr
1703 base = chainbase = curr
1704
1704
1705 e = (offset_type(offset, flags), l, textlen,
1705 e = (offset_type(offset, flags), l, textlen,
1706 base, link, p1r, p2r, node)
1706 base, link, p1r, p2r, node)
1707 self.index.insert(-1, e)
1707 self.index.insert(-1, e)
1708 self.nodemap[node] = curr
1708 self.nodemap[node] = curr
1709
1709
1710 entry = self._io.packentry(e, self.node, self.version, curr)
1710 entry = self._io.packentry(e, self.node, self.version, curr)
1711 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1711 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1712
1712
1713 if alwayscache and text is None:
1713 if alwayscache and text is None:
1714 text = buildtext()
1714 text = buildtext()
1715
1715
1716 if type(text) == str: # only accept immutable objects
1716 if type(text) == str: # only accept immutable objects
1717 self._cache = (node, curr, text)
1717 self._cache = (node, curr, text)
1718 self._chainbasecache[curr] = chainbase
1718 self._chainbasecache[curr] = chainbase
1719 return node
1719 return node
1720
1720
1721 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1721 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1722 # Files opened in a+ mode have inconsistent behavior on various
1722 # Files opened in a+ mode have inconsistent behavior on various
1723 # platforms. Windows requires that a file positioning call be made
1723 # platforms. Windows requires that a file positioning call be made
1724 # when the file handle transitions between reads and writes. See
1724 # when the file handle transitions between reads and writes. See
1725 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1725 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1726 # platforms, Python or the platform itself can be buggy. Some versions
1726 # platforms, Python or the platform itself can be buggy. Some versions
1727 # of Solaris have been observed to not append at the end of the file
1727 # of Solaris have been observed to not append at the end of the file
1728 # if the file was seeked to before the end. See issue4943 for more.
1728 # if the file was seeked to before the end. See issue4943 for more.
1729 #
1729 #
1730 # We work around this issue by inserting a seek() before writing.
1730 # We work around this issue by inserting a seek() before writing.
1731 # Note: This is likely not necessary on Python 3.
1731 # Note: This is likely not necessary on Python 3.
1732 ifh.seek(0, os.SEEK_END)
1732 ifh.seek(0, os.SEEK_END)
1733 if dfh:
1733 if dfh:
1734 dfh.seek(0, os.SEEK_END)
1734 dfh.seek(0, os.SEEK_END)
1735
1735
1736 curr = len(self) - 1
1736 curr = len(self) - 1
1737 if not self._inline:
1737 if not self._inline:
1738 transaction.add(self.datafile, offset)
1738 transaction.add(self.datafile, offset)
1739 transaction.add(self.indexfile, curr * len(entry))
1739 transaction.add(self.indexfile, curr * len(entry))
1740 if data[0]:
1740 if data[0]:
1741 dfh.write(data[0])
1741 dfh.write(data[0])
1742 dfh.write(data[1])
1742 dfh.write(data[1])
1743 ifh.write(entry)
1743 ifh.write(entry)
1744 else:
1744 else:
1745 offset += curr * self._io.size
1745 offset += curr * self._io.size
1746 transaction.add(self.indexfile, offset, curr)
1746 transaction.add(self.indexfile, offset, curr)
1747 ifh.write(entry)
1747 ifh.write(entry)
1748 ifh.write(data[0])
1748 ifh.write(data[0])
1749 ifh.write(data[1])
1749 ifh.write(data[1])
1750 self.checkinlinesize(transaction, ifh)
1750 self.checkinlinesize(transaction, ifh)
1751
1751
1752 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1752 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1753 """
1753 """
1754 add a delta group
1754 add a delta group
1755
1755
1756 given a set of deltas, add them to the revision log. the
1756 given a set of deltas, add them to the revision log. the
1757 first delta is against its parent, which should be in our
1757 first delta is against its parent, which should be in our
1758 log, the rest are against the previous delta.
1758 log, the rest are against the previous delta.
1759
1759
1760 If ``addrevisioncb`` is defined, it will be called with arguments of
1760 If ``addrevisioncb`` is defined, it will be called with arguments of
1761 this revlog and the node that was added.
1761 this revlog and the node that was added.
1762 """
1762 """
1763
1763
1764 # track the base of the current delta log
1764 # track the base of the current delta log
1765 content = []
1765 content = []
1766 node = None
1766 node = None
1767
1767
1768 r = len(self)
1768 r = len(self)
1769 end = 0
1769 end = 0
1770 if r:
1770 if r:
1771 end = self.end(r - 1)
1771 end = self.end(r - 1)
1772 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1772 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1773 isize = r * self._io.size
1773 isize = r * self._io.size
1774 if self._inline:
1774 if self._inline:
1775 transaction.add(self.indexfile, end + isize, r)
1775 transaction.add(self.indexfile, end + isize, r)
1776 dfh = None
1776 dfh = None
1777 else:
1777 else:
1778 transaction.add(self.indexfile, isize, r)
1778 transaction.add(self.indexfile, isize, r)
1779 transaction.add(self.datafile, end)
1779 transaction.add(self.datafile, end)
1780 dfh = self.opener(self.datafile, "a+")
1780 dfh = self.opener(self.datafile, "a+")
1781 def flush():
1781 def flush():
1782 if dfh:
1782 if dfh:
1783 dfh.flush()
1783 dfh.flush()
1784 ifh.flush()
1784 ifh.flush()
1785 try:
1785 try:
1786 # loop through our set of deltas
1786 # loop through our set of deltas
1787 chain = None
1787 chain = None
1788 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1788 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1789 node = chunkdata['node']
1789 node = chunkdata['node']
1790 p1 = chunkdata['p1']
1790 p1 = chunkdata['p1']
1791 p2 = chunkdata['p2']
1791 p2 = chunkdata['p2']
1792 cs = chunkdata['cs']
1792 cs = chunkdata['cs']
1793 deltabase = chunkdata['deltabase']
1793 deltabase = chunkdata['deltabase']
1794 delta = chunkdata['delta']
1794 delta = chunkdata['delta']
1795 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1795 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1796
1796
1797 content.append(node)
1797 content.append(node)
1798
1798
1799 link = linkmapper(cs)
1799 link = linkmapper(cs)
1800 if node in self.nodemap:
1800 if node in self.nodemap:
1801 # this can happen if two branches make the same change
1801 # this can happen if two branches make the same change
1802 chain = node
1802 chain = node
1803 continue
1803 continue
1804
1804
1805 for p in (p1, p2):
1805 for p in (p1, p2):
1806 if p not in self.nodemap:
1806 if p not in self.nodemap:
1807 raise LookupError(p, self.indexfile,
1807 raise LookupError(p, self.indexfile,
1808 _('unknown parent'))
1808 _('unknown parent'))
1809
1809
1810 if deltabase not in self.nodemap:
1810 if deltabase not in self.nodemap:
1811 raise LookupError(deltabase, self.indexfile,
1811 raise LookupError(deltabase, self.indexfile,
1812 _('unknown delta base'))
1812 _('unknown delta base'))
1813
1813
1814 baserev = self.rev(deltabase)
1814 baserev = self.rev(deltabase)
1815
1815
1816 if baserev != nullrev and self.iscensored(baserev):
1816 if baserev != nullrev and self.iscensored(baserev):
1817 # if base is censored, delta must be full replacement in a
1817 # if base is censored, delta must be full replacement in a
1818 # single patch operation
1818 # single patch operation
1819 hlen = struct.calcsize(">lll")
1819 hlen = struct.calcsize(">lll")
1820 oldlen = self.rawsize(baserev)
1820 oldlen = self.rawsize(baserev)
1821 newlen = len(delta) - hlen
1821 newlen = len(delta) - hlen
1822 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1822 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1823 raise error.CensoredBaseError(self.indexfile,
1823 raise error.CensoredBaseError(self.indexfile,
1824 self.node(baserev))
1824 self.node(baserev))
1825
1825
1826 if not flags and self._peek_iscensored(baserev, delta, flush):
1826 if not flags and self._peek_iscensored(baserev, delta, flush):
1827 flags |= REVIDX_ISCENSORED
1827 flags |= REVIDX_ISCENSORED
1828
1828
1829 # We assume consumers of addrevisioncb will want to retrieve
1829 # We assume consumers of addrevisioncb will want to retrieve
1830 # the added revision, which will require a call to
1830 # the added revision, which will require a call to
1831 # revision(). revision() will fast path if there is a cache
1831 # revision(). revision() will fast path if there is a cache
1832 # hit. So, we tell _addrevision() to always cache in this case.
1832 # hit. So, we tell _addrevision() to always cache in this case.
1833 # We're only using addgroup() in the context of changegroup
1833 # We're only using addgroup() in the context of changegroup
1834 # generation so the revision data can always be handled as raw
1834 # generation so the revision data can always be handled as raw
1835 # by the flagprocessor.
1835 # by the flagprocessor.
1836 chain = self._addrevision(node, None, transaction, link,
1836 chain = self._addrevision(node, None, transaction, link,
1837 p1, p2, flags, (baserev, delta),
1837 p1, p2, flags, (baserev, delta),
1838 ifh, dfh,
1838 ifh, dfh,
1839 alwayscache=bool(addrevisioncb),
1839 alwayscache=bool(addrevisioncb),
1840 raw=True)
1840 raw=True)
1841
1841
1842 if addrevisioncb:
1842 if addrevisioncb:
1843 addrevisioncb(self, chain)
1843 addrevisioncb(self, chain)
1844
1844
1845 if not dfh and not self._inline:
1845 if not dfh and not self._inline:
1846 # addrevision switched from inline to conventional
1846 # addrevision switched from inline to conventional
1847 # reopen the index
1847 # reopen the index
1848 ifh.close()
1848 ifh.close()
1849 dfh = self.opener(self.datafile, "a+")
1849 dfh = self.opener(self.datafile, "a+")
1850 ifh = self.opener(self.indexfile, "a+",
1850 ifh = self.opener(self.indexfile, "a+",
1851 checkambig=self._checkambig)
1851 checkambig=self._checkambig)
1852 finally:
1852 finally:
1853 if dfh:
1853 if dfh:
1854 dfh.close()
1854 dfh.close()
1855 ifh.close()
1855 ifh.close()
1856
1856
1857 return content
1857 return content
1858
1858
1859 def iscensored(self, rev):
1859 def iscensored(self, rev):
1860 """Check if a file revision is censored."""
1860 """Check if a file revision is censored."""
1861 return False
1861 return False
1862
1862
1863 def _peek_iscensored(self, baserev, delta, flush):
1863 def _peek_iscensored(self, baserev, delta, flush):
1864 """Quickly check if a delta produces a censored revision."""
1864 """Quickly check if a delta produces a censored revision."""
1865 return False
1865 return False
1866
1866
1867 def getstrippoint(self, minlink):
1867 def getstrippoint(self, minlink):
1868 """find the minimum rev that must be stripped to strip the linkrev
1868 """find the minimum rev that must be stripped to strip the linkrev
1869
1869
1870 Returns a tuple containing the minimum rev and a set of all revs that
1870 Returns a tuple containing the minimum rev and a set of all revs that
1871 have linkrevs that will be broken by this strip.
1871 have linkrevs that will be broken by this strip.
1872 """
1872 """
1873 brokenrevs = set()
1873 brokenrevs = set()
1874 strippoint = len(self)
1874 strippoint = len(self)
1875
1875
1876 heads = {}
1876 heads = {}
1877 futurelargelinkrevs = set()
1877 futurelargelinkrevs = set()
1878 for head in self.headrevs():
1878 for head in self.headrevs():
1879 headlinkrev = self.linkrev(head)
1879 headlinkrev = self.linkrev(head)
1880 heads[head] = headlinkrev
1880 heads[head] = headlinkrev
1881 if headlinkrev >= minlink:
1881 if headlinkrev >= minlink:
1882 futurelargelinkrevs.add(headlinkrev)
1882 futurelargelinkrevs.add(headlinkrev)
1883
1883
1884 # This algorithm involves walking down the rev graph, starting at the
1884 # This algorithm involves walking down the rev graph, starting at the
1885 # heads. Since the revs are topologically sorted according to linkrev,
1885 # heads. Since the revs are topologically sorted according to linkrev,
1886 # once all head linkrevs are below the minlink, we know there are
1886 # once all head linkrevs are below the minlink, we know there are
1887 # no more revs that could have a linkrev greater than minlink.
1887 # no more revs that could have a linkrev greater than minlink.
1888 # So we can stop walking.
1888 # So we can stop walking.
1889 while futurelargelinkrevs:
1889 while futurelargelinkrevs:
1890 strippoint -= 1
1890 strippoint -= 1
1891 linkrev = heads.pop(strippoint)
1891 linkrev = heads.pop(strippoint)
1892
1892
1893 if linkrev < minlink:
1893 if linkrev < minlink:
1894 brokenrevs.add(strippoint)
1894 brokenrevs.add(strippoint)
1895 else:
1895 else:
1896 futurelargelinkrevs.remove(linkrev)
1896 futurelargelinkrevs.remove(linkrev)
1897
1897
1898 for p in self.parentrevs(strippoint):
1898 for p in self.parentrevs(strippoint):
1899 if p != nullrev:
1899 if p != nullrev:
1900 plinkrev = self.linkrev(p)
1900 plinkrev = self.linkrev(p)
1901 heads[p] = plinkrev
1901 heads[p] = plinkrev
1902 if plinkrev >= minlink:
1902 if plinkrev >= minlink:
1903 futurelargelinkrevs.add(plinkrev)
1903 futurelargelinkrevs.add(plinkrev)
1904
1904
1905 return strippoint, brokenrevs
1905 return strippoint, brokenrevs
1906
1906
1907 def strip(self, minlink, transaction):
1907 def strip(self, minlink, transaction):
1908 """truncate the revlog on the first revision with a linkrev >= minlink
1908 """truncate the revlog on the first revision with a linkrev >= minlink
1909
1909
1910 This function is called when we're stripping revision minlink and
1910 This function is called when we're stripping revision minlink and
1911 its descendants from the repository.
1911 its descendants from the repository.
1912
1912
1913 We have to remove all revisions with linkrev >= minlink, because
1913 We have to remove all revisions with linkrev >= minlink, because
1914 the equivalent changelog revisions will be renumbered after the
1914 the equivalent changelog revisions will be renumbered after the
1915 strip.
1915 strip.
1916
1916
1917 So we truncate the revlog on the first of these revisions, and
1917 So we truncate the revlog on the first of these revisions, and
1918 trust that the caller has saved the revisions that shouldn't be
1918 trust that the caller has saved the revisions that shouldn't be
1919 removed and that it'll re-add them after this truncation.
1919 removed and that it'll re-add them after this truncation.
1920 """
1920 """
1921 if len(self) == 0:
1921 if len(self) == 0:
1922 return
1922 return
1923
1923
1924 rev, _ = self.getstrippoint(minlink)
1924 rev, _ = self.getstrippoint(minlink)
1925 if rev == len(self):
1925 if rev == len(self):
1926 return
1926 return
1927
1927
1928 # first truncate the files on disk
1928 # first truncate the files on disk
1929 end = self.start(rev)
1929 end = self.start(rev)
1930 if not self._inline:
1930 if not self._inline:
1931 transaction.add(self.datafile, end)
1931 transaction.add(self.datafile, end)
1932 end = rev * self._io.size
1932 end = rev * self._io.size
1933 else:
1933 else:
1934 end += rev * self._io.size
1934 end += rev * self._io.size
1935
1935
1936 transaction.add(self.indexfile, end)
1936 transaction.add(self.indexfile, end)
1937
1937
1938 # then reset internal state in memory to forget those revisions
1938 # then reset internal state in memory to forget those revisions
1939 self._cache = None
1939 self._cache = None
1940 self._chaininfocache = {}
1940 self._chaininfocache = {}
1941 self._chunkclear()
1941 self._chunkclear()
1942 for x in xrange(rev, len(self)):
1942 for x in xrange(rev, len(self)):
1943 del self.nodemap[self.node(x)]
1943 del self.nodemap[self.node(x)]
1944
1944
1945 del self.index[rev:-1]
1945 del self.index[rev:-1]
1946
1946
1947 def checksize(self):
1947 def checksize(self):
1948 expected = 0
1948 expected = 0
1949 if len(self):
1949 if len(self):
1950 expected = max(0, self.end(len(self) - 1))
1950 expected = max(0, self.end(len(self) - 1))
1951
1951
1952 try:
1952 try:
1953 f = self.opener(self.datafile)
1953 f = self.opener(self.datafile)
1954 f.seek(0, 2)
1954 f.seek(0, 2)
1955 actual = f.tell()
1955 actual = f.tell()
1956 f.close()
1956 f.close()
1957 dd = actual - expected
1957 dd = actual - expected
1958 except IOError as inst:
1958 except IOError as inst:
1959 if inst.errno != errno.ENOENT:
1959 if inst.errno != errno.ENOENT:
1960 raise
1960 raise
1961 dd = 0
1961 dd = 0
1962
1962
1963 try:
1963 try:
1964 f = self.opener(self.indexfile)
1964 f = self.opener(self.indexfile)
1965 f.seek(0, 2)
1965 f.seek(0, 2)
1966 actual = f.tell()
1966 actual = f.tell()
1967 f.close()
1967 f.close()
1968 s = self._io.size
1968 s = self._io.size
1969 i = max(0, actual // s)
1969 i = max(0, actual // s)
1970 di = actual - (i * s)
1970 di = actual - (i * s)
1971 if self._inline:
1971 if self._inline:
1972 databytes = 0
1972 databytes = 0
1973 for r in self:
1973 for r in self:
1974 databytes += max(0, self.length(r))
1974 databytes += max(0, self.length(r))
1975 dd = 0
1975 dd = 0
1976 di = actual - len(self) * s - databytes
1976 di = actual - len(self) * s - databytes
1977 except IOError as inst:
1977 except IOError as inst:
1978 if inst.errno != errno.ENOENT:
1978 if inst.errno != errno.ENOENT:
1979 raise
1979 raise
1980 di = 0
1980 di = 0
1981
1981
1982 return (dd, di)
1982 return (dd, di)
1983
1983
1984 def files(self):
1984 def files(self):
1985 res = [self.indexfile]
1985 res = [self.indexfile]
1986 if not self._inline:
1986 if not self._inline:
1987 res.append(self.datafile)
1987 res.append(self.datafile)
1988 return res
1988 return res
1989
1989
1990 DELTAREUSEALWAYS = 'always'
1990 DELTAREUSEALWAYS = 'always'
1991 DELTAREUSESAMEREVS = 'samerevs'
1991 DELTAREUSESAMEREVS = 'samerevs'
1992 DELTAREUSENEVER = 'never'
1992 DELTAREUSENEVER = 'never'
1993
1993
1994 DELTAREUSEALL = set(['always', 'samerevs', 'never'])
1994 DELTAREUSEALL = set(['always', 'samerevs', 'never'])
1995
1995
1996 def clone(self, tr, destrevlog, addrevisioncb=None,
1996 def clone(self, tr, destrevlog, addrevisioncb=None,
1997 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
1997 deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
1998 """Copy this revlog to another, possibly with format changes.
1998 """Copy this revlog to another, possibly with format changes.
1999
1999
2000 The destination revlog will contain the same revisions and nodes.
2000 The destination revlog will contain the same revisions and nodes.
2001 However, it may not be bit-for-bit identical due to e.g. delta encoding
2001 However, it may not be bit-for-bit identical due to e.g. delta encoding
2002 differences.
2002 differences.
2003
2003
2004 The ``deltareuse`` argument control how deltas from the existing revlog
2004 The ``deltareuse`` argument control how deltas from the existing revlog
2005 are preserved in the destination revlog. The argument can have the
2005 are preserved in the destination revlog. The argument can have the
2006 following values:
2006 following values:
2007
2007
2008 DELTAREUSEALWAYS
2008 DELTAREUSEALWAYS
2009 Deltas will always be reused (if possible), even if the destination
2009 Deltas will always be reused (if possible), even if the destination
2010 revlog would not select the same revisions for the delta. This is the
2010 revlog would not select the same revisions for the delta. This is the
2011 fastest mode of operation.
2011 fastest mode of operation.
2012 DELTAREUSESAMEREVS
2012 DELTAREUSESAMEREVS
2013 Deltas will be reused if the destination revlog would pick the same
2013 Deltas will be reused if the destination revlog would pick the same
2014 revisions for the delta. This mode strikes a balance between speed
2014 revisions for the delta. This mode strikes a balance between speed
2015 and optimization.
2015 and optimization.
2016 DELTAREUSENEVER
2016 DELTAREUSENEVER
2017 Deltas will never be reused. This is the slowest mode of execution.
2017 Deltas will never be reused. This is the slowest mode of execution.
2018 This mode can be used to recompute deltas (e.g. if the diff/delta
2018 This mode can be used to recompute deltas (e.g. if the diff/delta
2019 algorithm changes).
2019 algorithm changes).
2020
2020
2021 Delta computation can be slow, so the choice of delta reuse policy can
2021 Delta computation can be slow, so the choice of delta reuse policy can
2022 significantly affect run time.
2022 significantly affect run time.
2023
2023
2024 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2024 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2025 two extremes. Deltas will be reused if they are appropriate. But if the
2025 two extremes. Deltas will be reused if they are appropriate. But if the
2026 delta could choose a better revision, it will do so. This means if you
2026 delta could choose a better revision, it will do so. This means if you
2027 are converting a non-generaldelta revlog to a generaldelta revlog,
2027 are converting a non-generaldelta revlog to a generaldelta revlog,
2028 deltas will be recomputed if the delta's parent isn't a parent of the
2028 deltas will be recomputed if the delta's parent isn't a parent of the
2029 revision.
2029 revision.
2030
2030
2031 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2031 In addition to the delta policy, the ``aggressivemergedeltas`` argument
2032 controls whether to compute deltas against both parents for merges.
2032 controls whether to compute deltas against both parents for merges.
2033 By default, the current default is used.
2033 By default, the current default is used.
2034 """
2034 """
2035 if deltareuse not in self.DELTAREUSEALL:
2035 if deltareuse not in self.DELTAREUSEALL:
2036 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2036 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2037
2037
2038 if len(destrevlog):
2038 if len(destrevlog):
2039 raise ValueError(_('destination revlog is not empty'))
2039 raise ValueError(_('destination revlog is not empty'))
2040
2040
2041 if getattr(self, 'filteredrevs', None):
2041 if getattr(self, 'filteredrevs', None):
2042 raise ValueError(_('source revlog has filtered revisions'))
2042 raise ValueError(_('source revlog has filtered revisions'))
2043 if getattr(destrevlog, 'filteredrevs', None):
2043 if getattr(destrevlog, 'filteredrevs', None):
2044 raise ValueError(_('destination revlog has filtered revisions'))
2044 raise ValueError(_('destination revlog has filtered revisions'))
2045
2045
2046 # lazydeltabase controls whether to reuse a cached delta, if possible.
2046 # lazydeltabase controls whether to reuse a cached delta, if possible.
2047 oldlazydeltabase = destrevlog._lazydeltabase
2047 oldlazydeltabase = destrevlog._lazydeltabase
2048 oldamd = destrevlog._aggressivemergedeltas
2048 oldamd = destrevlog._aggressivemergedeltas
2049
2049
2050 try:
2050 try:
2051 if deltareuse == self.DELTAREUSEALWAYS:
2051 if deltareuse == self.DELTAREUSEALWAYS:
2052 destrevlog._lazydeltabase = True
2052 destrevlog._lazydeltabase = True
2053 elif deltareuse == self.DELTAREUSESAMEREVS:
2053 elif deltareuse == self.DELTAREUSESAMEREVS:
2054 destrevlog._lazydeltabase = False
2054 destrevlog._lazydeltabase = False
2055
2055
2056 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2056 destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
2057
2057
2058 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2058 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2059 self.DELTAREUSESAMEREVS)
2059 self.DELTAREUSESAMEREVS)
2060
2060
2061 index = self.index
2061 index = self.index
2062 for rev in self:
2062 for rev in self:
2063 entry = index[rev]
2063 entry = index[rev]
2064
2064
2065 # Some classes override linkrev to take filtered revs into
2065 # Some classes override linkrev to take filtered revs into
2066 # account. Use raw entry from index.
2066 # account. Use raw entry from index.
2067 flags = entry[0] & 0xffff
2067 flags = entry[0] & 0xffff
2068 linkrev = entry[4]
2068 linkrev = entry[4]
2069 p1 = index[entry[5]][7]
2069 p1 = index[entry[5]][7]
2070 p2 = index[entry[6]][7]
2070 p2 = index[entry[6]][7]
2071 node = entry[7]
2071 node = entry[7]
2072
2072
2073 # (Possibly) reuse the delta from the revlog if allowed and
2073 # (Possibly) reuse the delta from the revlog if allowed and
2074 # the revlog chunk is a delta.
2074 # the revlog chunk is a delta.
2075 cachedelta = None
2075 cachedelta = None
2076 text = None
2076 text = None
2077 if populatecachedelta:
2077 if populatecachedelta:
2078 dp = self.deltaparent(rev)
2078 dp = self.deltaparent(rev)
2079 if dp != nullrev:
2079 if dp != nullrev:
2080 cachedelta = (dp, str(self._chunk(rev)))
2080 cachedelta = (dp, str(self._chunk(rev)))
2081
2081
2082 if not cachedelta:
2082 if not cachedelta:
2083 text = self.revision(rev)
2083 text = self.revision(rev)
2084
2084
2085 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2085 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2086 checkambig=False)
2086 checkambig=False)
2087 dfh = None
2087 dfh = None
2088 if not destrevlog._inline:
2088 if not destrevlog._inline:
2089 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2089 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2090 try:
2090 try:
2091 destrevlog._addrevision(node, text, tr, linkrev, p1, p2,
2091 destrevlog._addrevision(node, text, tr, linkrev, p1, p2,
2092 flags, cachedelta, ifh, dfh)
2092 flags, cachedelta, ifh, dfh)
2093 finally:
2093 finally:
2094 if dfh:
2094 if dfh:
2095 dfh.close()
2095 dfh.close()
2096 ifh.close()
2096 ifh.close()
2097
2097
2098 if addrevisioncb:
2098 if addrevisioncb:
2099 addrevisioncb(self, rev, node)
2099 addrevisioncb(self, rev, node)
2100 finally:
2100 finally:
2101 destrevlog._lazydeltabase = oldlazydeltabase
2101 destrevlog._lazydeltabase = oldlazydeltabase
2102 destrevlog._aggressivemergedeltas = oldamd
2102 destrevlog._aggressivemergedeltas = oldamd
@@ -1,80 +1,71 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ check_code="$TESTDIR"/../contrib/check-code.py
4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 $ cd "$TESTDIR"/..
5 $ cd "$TESTDIR"/..
6
6
7 New errors are not allowed. Warnings are strongly discouraged.
7 New errors are not allowed. Warnings are strongly discouraged.
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9
9
10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
11 > sed 's-\\-/-g' | xargs "$check_code" --warnings --per-file=0 || false
11 > sed 's-\\-/-g' | xargs "$check_code" --warnings --per-file=0 || false
12 contrib/perf.py:859:
12 contrib/perf.py:859:
13 > r.revision(r.node(x))
13 > r.revision(r.node(x))
14 don't covert rev to node before passing to revision(nodeorrev)
14 don't covert rev to node before passing to revision(nodeorrev)
15 Skipping i18n/polib.py it has no-che?k-code (glob)
15 Skipping i18n/polib.py it has no-che?k-code (glob)
16 mercurial/bundlerepo.py:117:
16 mercurial/bundlerepo.py:117:
17 > return mdiff.textdiff(self.revision(self.node(rev1)),
17 > return mdiff.textdiff(self.revision(self.node(rev1)),
18 don't covert rev to node before passing to revision(nodeorrev)
18 don't covert rev to node before passing to revision(nodeorrev)
19 mercurial/bundlerepo.py:118:
19 mercurial/bundlerepo.py:118:
20 > self.revision(self.node(rev2)))
20 > self.revision(self.node(rev2)))
21 don't covert rev to node before passing to revision(nodeorrev)
21 don't covert rev to node before passing to revision(nodeorrev)
22 mercurial/demandimport.py:312:
22 mercurial/demandimport.py:312:
23 > if os.environ.get('HGDEMANDIMPORT') != 'disable':
23 > if os.environ.get('HGDEMANDIMPORT') != 'disable':
24 use encoding.environ instead (py3)
24 use encoding.environ instead (py3)
25 mercurial/encoding.py:54:
25 mercurial/encoding.py:54:
26 > environ = os.environ
26 > environ = os.environ
27 use encoding.environ instead (py3)
27 use encoding.environ instead (py3)
28 mercurial/encoding.py:56:
28 mercurial/encoding.py:56:
29 > environ = os.environb
29 > environ = os.environb
30 use encoding.environ instead (py3)
30 use encoding.environ instead (py3)
31 mercurial/encoding.py:61:
31 mercurial/encoding.py:61:
32 > for k, v in os.environ.items())
32 > for k, v in os.environ.items())
33 use encoding.environ instead (py3)
33 use encoding.environ instead (py3)
34 mercurial/encoding.py:221:
34 mercurial/encoding.py:221:
35 > for k, v in os.environ.items())
35 > for k, v in os.environ.items())
36 use encoding.environ instead (py3)
36 use encoding.environ instead (py3)
37 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
37 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
38 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
38 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
39 mercurial/policy.py:46:
39 mercurial/policy.py:46:
40 > if 'HGMODULEPOLICY' in os.environ:
40 > if 'HGMODULEPOLICY' in os.environ:
41 use encoding.environ instead (py3)
41 use encoding.environ instead (py3)
42 mercurial/policy.py:47:
42 mercurial/policy.py:47:
43 > policy = os.environ['HGMODULEPOLICY'].encode('utf-8')
43 > policy = os.environ['HGMODULEPOLICY'].encode('utf-8')
44 use encoding.environ instead (py3)
44 use encoding.environ instead (py3)
45 mercurial/policy.py:49:
45 mercurial/policy.py:49:
46 > policy = os.environ.get('HGMODULEPOLICY', policy)
46 > policy = os.environ.get('HGMODULEPOLICY', policy)
47 use encoding.environ instead (py3)
47 use encoding.environ instead (py3)
48 mercurial/revlog.py:441:
49 > t = self.revision(self.node(rev))
50 don't covert rev to node before passing to revision(nodeorrev)
51 mercurial/revlog.py:1599:
52 > basetext = self.revision(self.node(baserev), _df=fh, raw=raw)
53 don't covert rev to node before passing to revision(nodeorrev)
54 mercurial/revlog.py:1631:
55 > ptext = self.revision(self.node(rev), _df=fh)
56 don't covert rev to node before passing to revision(nodeorrev)
57 Skipping mercurial/statprof.py it has no-che?k-code (glob)
48 Skipping mercurial/statprof.py it has no-che?k-code (glob)
58 mercurial/unionrepo.py:93:
49 mercurial/unionrepo.py:93:
59 > return mdiff.textdiff(self.revision(self.node(rev1)),
50 > return mdiff.textdiff(self.revision(self.node(rev1)),
60 don't covert rev to node before passing to revision(nodeorrev)
51 don't covert rev to node before passing to revision(nodeorrev)
61 mercurial/unionrepo.py:94:
52 mercurial/unionrepo.py:94:
62 > self.revision(self.node(rev2)))
53 > self.revision(self.node(rev2)))
63 don't covert rev to node before passing to revision(nodeorrev)
54 don't covert rev to node before passing to revision(nodeorrev)
64 [1]
55 [1]
65
56
66 @commands in debugcommands.py should be in alphabetical order.
57 @commands in debugcommands.py should be in alphabetical order.
67
58
68 >>> import re
59 >>> import re
69 >>> commands = []
60 >>> commands = []
70 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
61 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
71 ... for line in fh:
62 ... for line in fh:
72 ... m = re.match("^@command\('([a-z]+)", line)
63 ... m = re.match("^@command\('([a-z]+)", line)
73 ... if m:
64 ... if m:
74 ... commands.append(m.group(1))
65 ... commands.append(m.group(1))
75 >>> scommands = list(sorted(commands))
66 >>> scommands = list(sorted(commands))
76 >>> for i, command in enumerate(scommands):
67 >>> for i, command in enumerate(scommands):
77 ... if command != commands[i]:
68 ... if command != commands[i]:
78 ... print('commands in debugcommands.py not sorted; first differing '
69 ... print('commands in debugcommands.py not sorted; first differing '
79 ... 'command is %s; expected %s' % (commands[i], command))
70 ... 'command is %s; expected %s' % (commands[i], command))
80 ... break
71 ... break
General Comments 0
You need to be logged in to leave comments. Login now