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