##// END OF EJS Templates
revlog: always add the magic nullid/nullrev entry in parseindex
Benoit Boissinot -
r13265:04b302ce default
parent child Browse files
Show More
@@ -1,1259 +1,1257 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 # import stuff from node for others to import from revlog
14 # import stuff from node for others to import from revlog
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 from i18n import _
16 from i18n import _
17 import changegroup, ancestor, mdiff, parsers, error, util
17 import changegroup, ancestor, mdiff, parsers, error, util
18 import struct, zlib, errno
18 import struct, zlib, errno
19
19
20 _pack = struct.pack
20 _pack = struct.pack
21 _unpack = struct.unpack
21 _unpack = struct.unpack
22 _compress = zlib.compress
22 _compress = zlib.compress
23 _decompress = zlib.decompress
23 _decompress = zlib.decompress
24 _sha = util.sha1
24 _sha = util.sha1
25
25
26 # revlog header flags
26 # revlog header flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOGSHALLOW = (1 << 17)
30 REVLOGSHALLOW = (1 << 17)
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGSHALLOW
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGSHALLOW
35
35
36 # revlog index flags
36 # revlog index flags
37 REVIDX_PARENTDELTA = 1
37 REVIDX_PARENTDELTA = 1
38 REVIDX_PUNCHED_FLAG = 2
38 REVIDX_PUNCHED_FLAG = 2
39 REVIDX_KNOWN_FLAGS = REVIDX_PUNCHED_FLAG | REVIDX_PARENTDELTA
39 REVIDX_KNOWN_FLAGS = REVIDX_PUNCHED_FLAG | REVIDX_PARENTDELTA
40
40
41 # max size of revlog with inline data
41 # max size of revlog with inline data
42 _maxinline = 131072
42 _maxinline = 131072
43 _chunksize = 1048576
43 _chunksize = 1048576
44
44
45 RevlogError = error.RevlogError
45 RevlogError = error.RevlogError
46 LookupError = error.LookupError
46 LookupError = error.LookupError
47
47
48 def getoffset(q):
48 def getoffset(q):
49 return int(q >> 16)
49 return int(q >> 16)
50
50
51 def gettype(q):
51 def gettype(q):
52 return int(q & 0xFFFF)
52 return int(q & 0xFFFF)
53
53
54 def offset_type(offset, type):
54 def offset_type(offset, type):
55 return long(long(offset) << 16 | type)
55 return long(long(offset) << 16 | type)
56
56
57 nullhash = _sha(nullid)
57 nullhash = _sha(nullid)
58
58
59 def hash(text, p1, p2):
59 def hash(text, p1, p2):
60 """generate a hash from the given text and its parent hashes
60 """generate a hash from the given text and its parent hashes
61
61
62 This hash combines both the current file contents and its history
62 This hash combines both the current file contents and its history
63 in a manner that makes it easy to distinguish nodes with the same
63 in a manner that makes it easy to distinguish nodes with the same
64 content in the revision graph.
64 content in the revision graph.
65 """
65 """
66 # As of now, if one of the parent node is null, p2 is null
66 # As of now, if one of the parent node is null, p2 is null
67 if p2 == nullid:
67 if p2 == nullid:
68 # deep copy of a hash is faster than creating one
68 # deep copy of a hash is faster than creating one
69 s = nullhash.copy()
69 s = nullhash.copy()
70 s.update(p1)
70 s.update(p1)
71 else:
71 else:
72 # none of the parent nodes are nullid
72 # none of the parent nodes are nullid
73 l = [p1, p2]
73 l = [p1, p2]
74 l.sort()
74 l.sort()
75 s = _sha(l[0])
75 s = _sha(l[0])
76 s.update(l[1])
76 s.update(l[1])
77 s.update(text)
77 s.update(text)
78 return s.digest()
78 return s.digest()
79
79
80 def compress(text):
80 def compress(text):
81 """ generate a possibly-compressed representation of text """
81 """ generate a possibly-compressed representation of text """
82 if not text:
82 if not text:
83 return ("", text)
83 return ("", text)
84 l = len(text)
84 l = len(text)
85 bin = None
85 bin = None
86 if l < 44:
86 if l < 44:
87 pass
87 pass
88 elif l > 1000000:
88 elif l > 1000000:
89 # zlib makes an internal copy, thus doubling memory usage for
89 # zlib makes an internal copy, thus doubling memory usage for
90 # large files, so lets do this in pieces
90 # large files, so lets do this in pieces
91 z = zlib.compressobj()
91 z = zlib.compressobj()
92 p = []
92 p = []
93 pos = 0
93 pos = 0
94 while pos < l:
94 while pos < l:
95 pos2 = pos + 2**20
95 pos2 = pos + 2**20
96 p.append(z.compress(text[pos:pos2]))
96 p.append(z.compress(text[pos:pos2]))
97 pos = pos2
97 pos = pos2
98 p.append(z.flush())
98 p.append(z.flush())
99 if sum(map(len, p)) < l:
99 if sum(map(len, p)) < l:
100 bin = "".join(p)
100 bin = "".join(p)
101 else:
101 else:
102 bin = _compress(text)
102 bin = _compress(text)
103 if bin is None or len(bin) > l:
103 if bin is None or len(bin) > l:
104 if text[0] == '\0':
104 if text[0] == '\0':
105 return ("", text)
105 return ("", text)
106 return ('u', text)
106 return ('u', text)
107 return ("", bin)
107 return ("", bin)
108
108
109 def decompress(bin):
109 def decompress(bin):
110 """ decompress the given input """
110 """ decompress the given input """
111 if not bin:
111 if not bin:
112 return bin
112 return bin
113 t = bin[0]
113 t = bin[0]
114 if t == '\0':
114 if t == '\0':
115 return bin
115 return bin
116 if t == 'x':
116 if t == 'x':
117 return _decompress(bin)
117 return _decompress(bin)
118 if t == 'u':
118 if t == 'u':
119 return bin[1:]
119 return bin[1:]
120 raise RevlogError(_("unknown compression type %r") % t)
120 raise RevlogError(_("unknown compression type %r") % t)
121
121
122 indexformatv0 = ">4l20s20s20s"
122 indexformatv0 = ">4l20s20s20s"
123 v0shaoffset = 56
123 v0shaoffset = 56
124
124
125 class revlogoldio(object):
125 class revlogoldio(object):
126 def __init__(self):
126 def __init__(self):
127 self.size = struct.calcsize(indexformatv0)
127 self.size = struct.calcsize(indexformatv0)
128
128
129 def parseindex(self, data, inline):
129 def parseindex(self, data, inline):
130 s = self.size
130 s = self.size
131 index = []
131 index = []
132 nodemap = {nullid: nullrev}
132 nodemap = {nullid: nullrev}
133 n = off = 0
133 n = off = 0
134 l = len(data)
134 l = len(data)
135 while off + s <= l:
135 while off + s <= l:
136 cur = data[off:off + s]
136 cur = data[off:off + s]
137 off += s
137 off += s
138 e = _unpack(indexformatv0, cur)
138 e = _unpack(indexformatv0, cur)
139 # transform to revlogv1 format
139 # transform to revlogv1 format
140 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
140 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
141 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
141 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
142 index.append(e2)
142 index.append(e2)
143 nodemap[e[6]] = n
143 nodemap[e[6]] = n
144 n += 1
144 n += 1
145
145
146 # add the magic null revision at -1
147 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
148
146 return index, nodemap, None
149 return index, nodemap, None
147
150
148 def packentry(self, entry, node, version, rev):
151 def packentry(self, entry, node, version, rev):
149 if gettype(entry[0]):
152 if gettype(entry[0]):
150 raise RevlogError(_("index entry flags need RevlogNG"))
153 raise RevlogError(_("index entry flags need RevlogNG"))
151 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
154 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
152 node(entry[5]), node(entry[6]), entry[7])
155 node(entry[5]), node(entry[6]), entry[7])
153 return _pack(indexformatv0, *e2)
156 return _pack(indexformatv0, *e2)
154
157
155 # index ng:
158 # index ng:
156 # 6 bytes: offset
159 # 6 bytes: offset
157 # 2 bytes: flags
160 # 2 bytes: flags
158 # 4 bytes: compressed length
161 # 4 bytes: compressed length
159 # 4 bytes: uncompressed length
162 # 4 bytes: uncompressed length
160 # 4 bytes: base rev
163 # 4 bytes: base rev
161 # 4 bytes: link rev
164 # 4 bytes: link rev
162 # 4 bytes: parent 1 rev
165 # 4 bytes: parent 1 rev
163 # 4 bytes: parent 2 rev
166 # 4 bytes: parent 2 rev
164 # 32 bytes: nodeid
167 # 32 bytes: nodeid
165 indexformatng = ">Qiiiiii20s12x"
168 indexformatng = ">Qiiiiii20s12x"
166 ngshaoffset = 32
169 ngshaoffset = 32
167 versionformat = ">I"
170 versionformat = ">I"
168
171
169 class revlogio(object):
172 class revlogio(object):
170 def __init__(self):
173 def __init__(self):
171 self.size = struct.calcsize(indexformatng)
174 self.size = struct.calcsize(indexformatng)
172
175
173 def parseindex(self, data, inline):
176 def parseindex(self, data, inline):
174 # call the C implementation to parse the index data
177 # call the C implementation to parse the index data
175 index, cache = parsers.parse_index2(data, inline)
178 index, cache = parsers.parse_index2(data, inline)
176 return index, None, cache
179 return index, None, cache
177
180
178 def packentry(self, entry, node, version, rev):
181 def packentry(self, entry, node, version, rev):
179 p = _pack(indexformatng, *entry)
182 p = _pack(indexformatng, *entry)
180 if rev == 0:
183 if rev == 0:
181 p = _pack(versionformat, version) + p[4:]
184 p = _pack(versionformat, version) + p[4:]
182 return p
185 return p
183
186
184 class revlog(object):
187 class revlog(object):
185 """
188 """
186 the underlying revision storage object
189 the underlying revision storage object
187
190
188 A revlog consists of two parts, an index and the revision data.
191 A revlog consists of two parts, an index and the revision data.
189
192
190 The index is a file with a fixed record size containing
193 The index is a file with a fixed record size containing
191 information on each revision, including its nodeid (hash), the
194 information on each revision, including its nodeid (hash), the
192 nodeids of its parents, the position and offset of its data within
195 nodeids of its parents, the position and offset of its data within
193 the data file, and the revision it's based on. Finally, each entry
196 the data file, and the revision it's based on. Finally, each entry
194 contains a linkrev entry that can serve as a pointer to external
197 contains a linkrev entry that can serve as a pointer to external
195 data.
198 data.
196
199
197 The revision data itself is a linear collection of data chunks.
200 The revision data itself is a linear collection of data chunks.
198 Each chunk represents a revision and is usually represented as a
201 Each chunk represents a revision and is usually represented as a
199 delta against the previous chunk. To bound lookup time, runs of
202 delta against the previous chunk. To bound lookup time, runs of
200 deltas are limited to about 2 times the length of the original
203 deltas are limited to about 2 times the length of the original
201 version data. This makes retrieval of a version proportional to
204 version data. This makes retrieval of a version proportional to
202 its size, or O(1) relative to the number of revisions.
205 its size, or O(1) relative to the number of revisions.
203
206
204 Both pieces of the revlog are written to in an append-only
207 Both pieces of the revlog are written to in an append-only
205 fashion, which means we never need to rewrite a file to insert or
208 fashion, which means we never need to rewrite a file to insert or
206 remove data, and can use some simple techniques to avoid the need
209 remove data, and can use some simple techniques to avoid the need
207 for locking while reading.
210 for locking while reading.
208 """
211 """
209 def __init__(self, opener, indexfile, shallowroot=None):
212 def __init__(self, opener, indexfile, shallowroot=None):
210 """
213 """
211 create a revlog object
214 create a revlog object
212
215
213 opener is a function that abstracts the file opening operation
216 opener is a function that abstracts the file opening operation
214 and can be used to implement COW semantics or the like.
217 and can be used to implement COW semantics or the like.
215 """
218 """
216 self.indexfile = indexfile
219 self.indexfile = indexfile
217 self.datafile = indexfile[:-2] + ".d"
220 self.datafile = indexfile[:-2] + ".d"
218 self.opener = opener
221 self.opener = opener
219 self._cache = None
222 self._cache = None
220 self._chunkcache = (0, '')
223 self._chunkcache = (0, '')
221 self.index = []
224 self.index = []
222 self._shallowroot = shallowroot
225 self._shallowroot = shallowroot
223 self._parentdelta = 0
226 self._parentdelta = 0
224 self._pcache = {}
227 self._pcache = {}
225
228
226 v = REVLOG_DEFAULT_VERSION
229 v = REVLOG_DEFAULT_VERSION
227 if hasattr(opener, 'options') and 'defversion' in opener.options:
230 if hasattr(opener, 'options') and 'defversion' in opener.options:
228 v = opener.options['defversion']
231 v = opener.options['defversion']
229 if v & REVLOGNG:
232 if v & REVLOGNG:
230 v |= REVLOGNGINLINEDATA
233 v |= REVLOGNGINLINEDATA
231 if v & REVLOGNG and 'parentdelta' in opener.options:
234 if v & REVLOGNG and 'parentdelta' in opener.options:
232 self._parentdelta = 1
235 self._parentdelta = 1
233
236
234 if shallowroot:
237 if shallowroot:
235 v |= REVLOGSHALLOW
238 v |= REVLOGSHALLOW
236
239
237 i = ''
240 i = ''
238 try:
241 try:
239 f = self.opener(self.indexfile)
242 f = self.opener(self.indexfile)
240 i = f.read()
243 i = f.read()
241 if len(i) > 0:
244 if len(i) > 0:
242 v = struct.unpack(versionformat, i[:4])[0]
245 v = struct.unpack(versionformat, i[:4])[0]
243 except IOError, inst:
246 except IOError, inst:
244 if inst.errno != errno.ENOENT:
247 if inst.errno != errno.ENOENT:
245 raise
248 raise
246
249
247 self.version = v
250 self.version = v
248 self._inline = v & REVLOGNGINLINEDATA
251 self._inline = v & REVLOGNGINLINEDATA
249 self._shallow = v & REVLOGSHALLOW
252 self._shallow = v & REVLOGSHALLOW
250 flags = v & ~0xFFFF
253 flags = v & ~0xFFFF
251 fmt = v & 0xFFFF
254 fmt = v & 0xFFFF
252 if fmt == REVLOGV0 and flags:
255 if fmt == REVLOGV0 and flags:
253 raise RevlogError(_("index %s unknown flags %#04x for format v0")
256 raise RevlogError(_("index %s unknown flags %#04x for format v0")
254 % (self.indexfile, flags >> 16))
257 % (self.indexfile, flags >> 16))
255 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
258 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
256 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
259 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
257 % (self.indexfile, flags >> 16))
260 % (self.indexfile, flags >> 16))
258 elif fmt > REVLOGNG:
261 elif fmt > REVLOGNG:
259 raise RevlogError(_("index %s unknown format %d")
262 raise RevlogError(_("index %s unknown format %d")
260 % (self.indexfile, fmt))
263 % (self.indexfile, fmt))
261
264
262 self._io = revlogio()
265 self._io = revlogio()
263 if self.version == REVLOGV0:
266 if self.version == REVLOGV0:
264 self._io = revlogoldio()
267 self._io = revlogoldio()
265 if i:
268 try:
266 try:
269 d = self._io.parseindex(i, self._inline)
267 d = self._io.parseindex(i, self._inline)
270 except (ValueError, IndexError):
268 except (ValueError, IndexError):
271 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
269 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
272 self.index, n, self._chunkcache = d
270 self.index, n, self._chunkcache = d
273 if n:
271 if n:
274 self.nodemap = n
272 self.nodemap = n
275 if not self._chunkcache:
273 if not self._chunkcache:
276 self._chunkclear()
274 self._chunkclear()
275
276 # add the magic null revision at -1 (if it hasn't been done already)
277 if self.index == [] or self.index[-1][7] != nullid:
278 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
279
277
280 @util.propertycache
278 @util.propertycache
281 def nodemap(self):
279 def nodemap(self):
282 n = {nullid: nullrev}
280 n = {nullid: nullrev}
283 i = self.index
281 i = self.index
284 for r in xrange(len(i) - 1):
282 for r in xrange(len(i) - 1):
285 n[i[r][7]] = r
283 n[i[r][7]] = r
286 self.rev = self._revmap
284 self.rev = self._revmap
287 return n
285 return n
288
286
289 def tip(self):
287 def tip(self):
290 return self.node(len(self.index) - 2)
288 return self.node(len(self.index) - 2)
291 def __len__(self):
289 def __len__(self):
292 return len(self.index) - 1
290 return len(self.index) - 1
293 def __iter__(self):
291 def __iter__(self):
294 for i in xrange(len(self)):
292 for i in xrange(len(self)):
295 yield i
293 yield i
296 def _revmap(self, node):
294 def _revmap(self, node):
297 try:
295 try:
298 return self.nodemap[node]
296 return self.nodemap[node]
299 except KeyError:
297 except KeyError:
300 raise LookupError(node, self.indexfile, _('no node'))
298 raise LookupError(node, self.indexfile, _('no node'))
301
299
302 def rev(self, node):
300 def rev(self, node):
303 if node == nullid:
301 if node == nullid:
304 return nullrev
302 return nullrev
305 i = self.index
303 i = self.index
306 for r in xrange(len(i) - 2, -1, -1):
304 for r in xrange(len(i) - 2, -1, -1):
307 if i[r][7] == node:
305 if i[r][7] == node:
308 return r
306 return r
309 raise LookupError(node, self.indexfile, _('no node'))
307 raise LookupError(node, self.indexfile, _('no node'))
310 def node(self, rev):
308 def node(self, rev):
311 return self.index[rev][7]
309 return self.index[rev][7]
312 def linkrev(self, rev):
310 def linkrev(self, rev):
313 return self.index[rev][4]
311 return self.index[rev][4]
314 def parents(self, node):
312 def parents(self, node):
315 i = self.index
313 i = self.index
316 d = i[self.rev(node)]
314 d = i[self.rev(node)]
317 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
315 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
318 def parentrevs(self, rev):
316 def parentrevs(self, rev):
319 return self.index[rev][5:7]
317 return self.index[rev][5:7]
320 def start(self, rev):
318 def start(self, rev):
321 return int(self.index[rev][0] >> 16)
319 return int(self.index[rev][0] >> 16)
322 def end(self, rev):
320 def end(self, rev):
323 return self.start(rev) + self.length(rev)
321 return self.start(rev) + self.length(rev)
324 def length(self, rev):
322 def length(self, rev):
325 return self.index[rev][1]
323 return self.index[rev][1]
326 def base(self, rev):
324 def base(self, rev):
327 return self.index[rev][3]
325 return self.index[rev][3]
328 def flags(self, rev):
326 def flags(self, rev):
329 return self.index[rev][0] & 0xFFFF
327 return self.index[rev][0] & 0xFFFF
330 def rawsize(self, rev):
328 def rawsize(self, rev):
331 """return the length of the uncompressed text for a given revision"""
329 """return the length of the uncompressed text for a given revision"""
332 l = self.index[rev][2]
330 l = self.index[rev][2]
333 if l >= 0:
331 if l >= 0:
334 return l
332 return l
335
333
336 t = self.revision(self.node(rev))
334 t = self.revision(self.node(rev))
337 return len(t)
335 return len(t)
338 size = rawsize
336 size = rawsize
339
337
340 def reachable(self, node, stop=None):
338 def reachable(self, node, stop=None):
341 """return the set of all nodes ancestral to a given node, including
339 """return the set of all nodes ancestral to a given node, including
342 the node itself, stopping when stop is matched"""
340 the node itself, stopping when stop is matched"""
343 reachable = set((node,))
341 reachable = set((node,))
344 visit = [node]
342 visit = [node]
345 if stop:
343 if stop:
346 stopn = self.rev(stop)
344 stopn = self.rev(stop)
347 else:
345 else:
348 stopn = 0
346 stopn = 0
349 while visit:
347 while visit:
350 n = visit.pop(0)
348 n = visit.pop(0)
351 if n == stop:
349 if n == stop:
352 continue
350 continue
353 if n == nullid:
351 if n == nullid:
354 continue
352 continue
355 for p in self.parents(n):
353 for p in self.parents(n):
356 if self.rev(p) < stopn:
354 if self.rev(p) < stopn:
357 continue
355 continue
358 if p not in reachable:
356 if p not in reachable:
359 reachable.add(p)
357 reachable.add(p)
360 visit.append(p)
358 visit.append(p)
361 return reachable
359 return reachable
362
360
363 def ancestors(self, *revs):
361 def ancestors(self, *revs):
364 """Generate the ancestors of 'revs' in reverse topological order.
362 """Generate the ancestors of 'revs' in reverse topological order.
365
363
366 Yield a sequence of revision numbers starting with the parents
364 Yield a sequence of revision numbers starting with the parents
367 of each revision in revs, i.e., each revision is *not* considered
365 of each revision in revs, i.e., each revision is *not* considered
368 an ancestor of itself. Results are in breadth-first order:
366 an ancestor of itself. Results are in breadth-first order:
369 parents of each rev in revs, then parents of those, etc. Result
367 parents of each rev in revs, then parents of those, etc. Result
370 does not include the null revision."""
368 does not include the null revision."""
371 visit = list(revs)
369 visit = list(revs)
372 seen = set([nullrev])
370 seen = set([nullrev])
373 while visit:
371 while visit:
374 for parent in self.parentrevs(visit.pop(0)):
372 for parent in self.parentrevs(visit.pop(0)):
375 if parent not in seen:
373 if parent not in seen:
376 visit.append(parent)
374 visit.append(parent)
377 seen.add(parent)
375 seen.add(parent)
378 yield parent
376 yield parent
379
377
380 def descendants(self, *revs):
378 def descendants(self, *revs):
381 """Generate the descendants of 'revs' in revision order.
379 """Generate the descendants of 'revs' in revision order.
382
380
383 Yield a sequence of revision numbers starting with a child of
381 Yield a sequence of revision numbers starting with a child of
384 some rev in revs, i.e., each revision is *not* considered a
382 some rev in revs, i.e., each revision is *not* considered a
385 descendant of itself. Results are ordered by revision number (a
383 descendant of itself. Results are ordered by revision number (a
386 topological sort)."""
384 topological sort)."""
387 first = min(revs)
385 first = min(revs)
388 if first == nullrev:
386 if first == nullrev:
389 for i in self:
387 for i in self:
390 yield i
388 yield i
391 return
389 return
392
390
393 seen = set(revs)
391 seen = set(revs)
394 for i in xrange(first + 1, len(self)):
392 for i in xrange(first + 1, len(self)):
395 for x in self.parentrevs(i):
393 for x in self.parentrevs(i):
396 if x != nullrev and x in seen:
394 if x != nullrev and x in seen:
397 seen.add(i)
395 seen.add(i)
398 yield i
396 yield i
399 break
397 break
400
398
401 def findmissing(self, common=None, heads=None):
399 def findmissing(self, common=None, heads=None):
402 """Return the ancestors of heads that are not ancestors of common.
400 """Return the ancestors of heads that are not ancestors of common.
403
401
404 More specifically, return a list of nodes N such that every N
402 More specifically, return a list of nodes N such that every N
405 satisfies the following constraints:
403 satisfies the following constraints:
406
404
407 1. N is an ancestor of some node in 'heads'
405 1. N is an ancestor of some node in 'heads'
408 2. N is not an ancestor of any node in 'common'
406 2. N is not an ancestor of any node in 'common'
409
407
410 The list is sorted by revision number, meaning it is
408 The list is sorted by revision number, meaning it is
411 topologically sorted.
409 topologically sorted.
412
410
413 'heads' and 'common' are both lists of node IDs. If heads is
411 'heads' and 'common' are both lists of node IDs. If heads is
414 not supplied, uses all of the revlog's heads. If common is not
412 not supplied, uses all of the revlog's heads. If common is not
415 supplied, uses nullid."""
413 supplied, uses nullid."""
416 if common is None:
414 if common is None:
417 common = [nullid]
415 common = [nullid]
418 if heads is None:
416 if heads is None:
419 heads = self.heads()
417 heads = self.heads()
420
418
421 common = [self.rev(n) for n in common]
419 common = [self.rev(n) for n in common]
422 heads = [self.rev(n) for n in heads]
420 heads = [self.rev(n) for n in heads]
423
421
424 # we want the ancestors, but inclusive
422 # we want the ancestors, but inclusive
425 has = set(self.ancestors(*common))
423 has = set(self.ancestors(*common))
426 has.add(nullrev)
424 has.add(nullrev)
427 has.update(common)
425 has.update(common)
428
426
429 # take all ancestors from heads that aren't in has
427 # take all ancestors from heads that aren't in has
430 missing = set()
428 missing = set()
431 visit = [r for r in heads if r not in has]
429 visit = [r for r in heads if r not in has]
432 while visit:
430 while visit:
433 r = visit.pop(0)
431 r = visit.pop(0)
434 if r in missing:
432 if r in missing:
435 continue
433 continue
436 else:
434 else:
437 missing.add(r)
435 missing.add(r)
438 for p in self.parentrevs(r):
436 for p in self.parentrevs(r):
439 if p not in has:
437 if p not in has:
440 visit.append(p)
438 visit.append(p)
441 missing = list(missing)
439 missing = list(missing)
442 missing.sort()
440 missing.sort()
443 return [self.node(r) for r in missing]
441 return [self.node(r) for r in missing]
444
442
445 def nodesbetween(self, roots=None, heads=None):
443 def nodesbetween(self, roots=None, heads=None):
446 """Return a topological path from 'roots' to 'heads'.
444 """Return a topological path from 'roots' to 'heads'.
447
445
448 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
446 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
449 topologically sorted list of all nodes N that satisfy both of
447 topologically sorted list of all nodes N that satisfy both of
450 these constraints:
448 these constraints:
451
449
452 1. N is a descendant of some node in 'roots'
450 1. N is a descendant of some node in 'roots'
453 2. N is an ancestor of some node in 'heads'
451 2. N is an ancestor of some node in 'heads'
454
452
455 Every node is considered to be both a descendant and an ancestor
453 Every node is considered to be both a descendant and an ancestor
456 of itself, so every reachable node in 'roots' and 'heads' will be
454 of itself, so every reachable node in 'roots' and 'heads' will be
457 included in 'nodes'.
455 included in 'nodes'.
458
456
459 'outroots' is the list of reachable nodes in 'roots', i.e., the
457 'outroots' is the list of reachable nodes in 'roots', i.e., the
460 subset of 'roots' that is returned in 'nodes'. Likewise,
458 subset of 'roots' that is returned in 'nodes'. Likewise,
461 'outheads' is the subset of 'heads' that is also in 'nodes'.
459 'outheads' is the subset of 'heads' that is also in 'nodes'.
462
460
463 'roots' and 'heads' are both lists of node IDs. If 'roots' is
461 'roots' and 'heads' are both lists of node IDs. If 'roots' is
464 unspecified, uses nullid as the only root. If 'heads' is
462 unspecified, uses nullid as the only root. If 'heads' is
465 unspecified, uses list of all of the revlog's heads."""
463 unspecified, uses list of all of the revlog's heads."""
466 nonodes = ([], [], [])
464 nonodes = ([], [], [])
467 if roots is not None:
465 if roots is not None:
468 roots = list(roots)
466 roots = list(roots)
469 if not roots:
467 if not roots:
470 return nonodes
468 return nonodes
471 lowestrev = min([self.rev(n) for n in roots])
469 lowestrev = min([self.rev(n) for n in roots])
472 else:
470 else:
473 roots = [nullid] # Everybody's a descendent of nullid
471 roots = [nullid] # Everybody's a descendent of nullid
474 lowestrev = nullrev
472 lowestrev = nullrev
475 if (lowestrev == nullrev) and (heads is None):
473 if (lowestrev == nullrev) and (heads is None):
476 # We want _all_ the nodes!
474 # We want _all_ the nodes!
477 return ([self.node(r) for r in self], [nullid], list(self.heads()))
475 return ([self.node(r) for r in self], [nullid], list(self.heads()))
478 if heads is None:
476 if heads is None:
479 # All nodes are ancestors, so the latest ancestor is the last
477 # All nodes are ancestors, so the latest ancestor is the last
480 # node.
478 # node.
481 highestrev = len(self) - 1
479 highestrev = len(self) - 1
482 # Set ancestors to None to signal that every node is an ancestor.
480 # Set ancestors to None to signal that every node is an ancestor.
483 ancestors = None
481 ancestors = None
484 # Set heads to an empty dictionary for later discovery of heads
482 # Set heads to an empty dictionary for later discovery of heads
485 heads = {}
483 heads = {}
486 else:
484 else:
487 heads = list(heads)
485 heads = list(heads)
488 if not heads:
486 if not heads:
489 return nonodes
487 return nonodes
490 ancestors = set()
488 ancestors = set()
491 # Turn heads into a dictionary so we can remove 'fake' heads.
489 # Turn heads into a dictionary so we can remove 'fake' heads.
492 # Also, later we will be using it to filter out the heads we can't
490 # Also, later we will be using it to filter out the heads we can't
493 # find from roots.
491 # find from roots.
494 heads = dict.fromkeys(heads, 0)
492 heads = dict.fromkeys(heads, 0)
495 # Start at the top and keep marking parents until we're done.
493 # Start at the top and keep marking parents until we're done.
496 nodestotag = set(heads)
494 nodestotag = set(heads)
497 # Remember where the top was so we can use it as a limit later.
495 # Remember where the top was so we can use it as a limit later.
498 highestrev = max([self.rev(n) for n in nodestotag])
496 highestrev = max([self.rev(n) for n in nodestotag])
499 while nodestotag:
497 while nodestotag:
500 # grab a node to tag
498 # grab a node to tag
501 n = nodestotag.pop()
499 n = nodestotag.pop()
502 # Never tag nullid
500 # Never tag nullid
503 if n == nullid:
501 if n == nullid:
504 continue
502 continue
505 # A node's revision number represents its place in a
503 # A node's revision number represents its place in a
506 # topologically sorted list of nodes.
504 # topologically sorted list of nodes.
507 r = self.rev(n)
505 r = self.rev(n)
508 if r >= lowestrev:
506 if r >= lowestrev:
509 if n not in ancestors:
507 if n not in ancestors:
510 # If we are possibly a descendent of one of the roots
508 # If we are possibly a descendent of one of the roots
511 # and we haven't already been marked as an ancestor
509 # and we haven't already been marked as an ancestor
512 ancestors.add(n) # Mark as ancestor
510 ancestors.add(n) # Mark as ancestor
513 # Add non-nullid parents to list of nodes to tag.
511 # Add non-nullid parents to list of nodes to tag.
514 nodestotag.update([p for p in self.parents(n) if
512 nodestotag.update([p for p in self.parents(n) if
515 p != nullid])
513 p != nullid])
516 elif n in heads: # We've seen it before, is it a fake head?
514 elif n in heads: # We've seen it before, is it a fake head?
517 # So it is, real heads should not be the ancestors of
515 # So it is, real heads should not be the ancestors of
518 # any other heads.
516 # any other heads.
519 heads.pop(n)
517 heads.pop(n)
520 if not ancestors:
518 if not ancestors:
521 return nonodes
519 return nonodes
522 # Now that we have our set of ancestors, we want to remove any
520 # Now that we have our set of ancestors, we want to remove any
523 # roots that are not ancestors.
521 # roots that are not ancestors.
524
522
525 # If one of the roots was nullid, everything is included anyway.
523 # If one of the roots was nullid, everything is included anyway.
526 if lowestrev > nullrev:
524 if lowestrev > nullrev:
527 # But, since we weren't, let's recompute the lowest rev to not
525 # But, since we weren't, let's recompute the lowest rev to not
528 # include roots that aren't ancestors.
526 # include roots that aren't ancestors.
529
527
530 # Filter out roots that aren't ancestors of heads
528 # Filter out roots that aren't ancestors of heads
531 roots = [n for n in roots if n in ancestors]
529 roots = [n for n in roots if n in ancestors]
532 # Recompute the lowest revision
530 # Recompute the lowest revision
533 if roots:
531 if roots:
534 lowestrev = min([self.rev(n) for n in roots])
532 lowestrev = min([self.rev(n) for n in roots])
535 else:
533 else:
536 # No more roots? Return empty list
534 # No more roots? Return empty list
537 return nonodes
535 return nonodes
538 else:
536 else:
539 # We are descending from nullid, and don't need to care about
537 # We are descending from nullid, and don't need to care about
540 # any other roots.
538 # any other roots.
541 lowestrev = nullrev
539 lowestrev = nullrev
542 roots = [nullid]
540 roots = [nullid]
543 # Transform our roots list into a set.
541 # Transform our roots list into a set.
544 descendents = set(roots)
542 descendents = set(roots)
545 # Also, keep the original roots so we can filter out roots that aren't
543 # Also, keep the original roots so we can filter out roots that aren't
546 # 'real' roots (i.e. are descended from other roots).
544 # 'real' roots (i.e. are descended from other roots).
547 roots = descendents.copy()
545 roots = descendents.copy()
548 # Our topologically sorted list of output nodes.
546 # Our topologically sorted list of output nodes.
549 orderedout = []
547 orderedout = []
550 # Don't start at nullid since we don't want nullid in our output list,
548 # Don't start at nullid since we don't want nullid in our output list,
551 # and if nullid shows up in descedents, empty parents will look like
549 # and if nullid shows up in descedents, empty parents will look like
552 # they're descendents.
550 # they're descendents.
553 for r in xrange(max(lowestrev, 0), highestrev + 1):
551 for r in xrange(max(lowestrev, 0), highestrev + 1):
554 n = self.node(r)
552 n = self.node(r)
555 isdescendent = False
553 isdescendent = False
556 if lowestrev == nullrev: # Everybody is a descendent of nullid
554 if lowestrev == nullrev: # Everybody is a descendent of nullid
557 isdescendent = True
555 isdescendent = True
558 elif n in descendents:
556 elif n in descendents:
559 # n is already a descendent
557 # n is already a descendent
560 isdescendent = True
558 isdescendent = True
561 # This check only needs to be done here because all the roots
559 # This check only needs to be done here because all the roots
562 # will start being marked is descendents before the loop.
560 # will start being marked is descendents before the loop.
563 if n in roots:
561 if n in roots:
564 # If n was a root, check if it's a 'real' root.
562 # If n was a root, check if it's a 'real' root.
565 p = tuple(self.parents(n))
563 p = tuple(self.parents(n))
566 # If any of its parents are descendents, it's not a root.
564 # If any of its parents are descendents, it's not a root.
567 if (p[0] in descendents) or (p[1] in descendents):
565 if (p[0] in descendents) or (p[1] in descendents):
568 roots.remove(n)
566 roots.remove(n)
569 else:
567 else:
570 p = tuple(self.parents(n))
568 p = tuple(self.parents(n))
571 # A node is a descendent if either of its parents are
569 # A node is a descendent if either of its parents are
572 # descendents. (We seeded the dependents list with the roots
570 # descendents. (We seeded the dependents list with the roots
573 # up there, remember?)
571 # up there, remember?)
574 if (p[0] in descendents) or (p[1] in descendents):
572 if (p[0] in descendents) or (p[1] in descendents):
575 descendents.add(n)
573 descendents.add(n)
576 isdescendent = True
574 isdescendent = True
577 if isdescendent and ((ancestors is None) or (n in ancestors)):
575 if isdescendent and ((ancestors is None) or (n in ancestors)):
578 # Only include nodes that are both descendents and ancestors.
576 # Only include nodes that are both descendents and ancestors.
579 orderedout.append(n)
577 orderedout.append(n)
580 if (ancestors is not None) and (n in heads):
578 if (ancestors is not None) and (n in heads):
581 # We're trying to figure out which heads are reachable
579 # We're trying to figure out which heads are reachable
582 # from roots.
580 # from roots.
583 # Mark this head as having been reached
581 # Mark this head as having been reached
584 heads[n] = 1
582 heads[n] = 1
585 elif ancestors is None:
583 elif ancestors is None:
586 # Otherwise, we're trying to discover the heads.
584 # Otherwise, we're trying to discover the heads.
587 # Assume this is a head because if it isn't, the next step
585 # Assume this is a head because if it isn't, the next step
588 # will eventually remove it.
586 # will eventually remove it.
589 heads[n] = 1
587 heads[n] = 1
590 # But, obviously its parents aren't.
588 # But, obviously its parents aren't.
591 for p in self.parents(n):
589 for p in self.parents(n):
592 heads.pop(p, None)
590 heads.pop(p, None)
593 heads = [n for n in heads.iterkeys() if heads[n] != 0]
591 heads = [n for n in heads.iterkeys() if heads[n] != 0]
594 roots = list(roots)
592 roots = list(roots)
595 assert orderedout
593 assert orderedout
596 assert roots
594 assert roots
597 assert heads
595 assert heads
598 return (orderedout, roots, heads)
596 return (orderedout, roots, heads)
599
597
600 def heads(self, start=None, stop=None):
598 def heads(self, start=None, stop=None):
601 """return the list of all nodes that have no children
599 """return the list of all nodes that have no children
602
600
603 if start is specified, only heads that are descendants of
601 if start is specified, only heads that are descendants of
604 start will be returned
602 start will be returned
605 if stop is specified, it will consider all the revs from stop
603 if stop is specified, it will consider all the revs from stop
606 as if they had no children
604 as if they had no children
607 """
605 """
608 if start is None and stop is None:
606 if start is None and stop is None:
609 count = len(self)
607 count = len(self)
610 if not count:
608 if not count:
611 return [nullid]
609 return [nullid]
612 ishead = [1] * (count + 1)
610 ishead = [1] * (count + 1)
613 index = self.index
611 index = self.index
614 for r in xrange(count):
612 for r in xrange(count):
615 e = index[r]
613 e = index[r]
616 ishead[e[5]] = ishead[e[6]] = 0
614 ishead[e[5]] = ishead[e[6]] = 0
617 return [self.node(r) for r in xrange(count) if ishead[r]]
615 return [self.node(r) for r in xrange(count) if ishead[r]]
618
616
619 if start is None:
617 if start is None:
620 start = nullid
618 start = nullid
621 if stop is None:
619 if stop is None:
622 stop = []
620 stop = []
623 stoprevs = set([self.rev(n) for n in stop])
621 stoprevs = set([self.rev(n) for n in stop])
624 startrev = self.rev(start)
622 startrev = self.rev(start)
625 reachable = set((startrev,))
623 reachable = set((startrev,))
626 heads = set((startrev,))
624 heads = set((startrev,))
627
625
628 parentrevs = self.parentrevs
626 parentrevs = self.parentrevs
629 for r in xrange(startrev + 1, len(self)):
627 for r in xrange(startrev + 1, len(self)):
630 for p in parentrevs(r):
628 for p in parentrevs(r):
631 if p in reachable:
629 if p in reachable:
632 if r not in stoprevs:
630 if r not in stoprevs:
633 reachable.add(r)
631 reachable.add(r)
634 heads.add(r)
632 heads.add(r)
635 if p in heads and p not in stoprevs:
633 if p in heads and p not in stoprevs:
636 heads.remove(p)
634 heads.remove(p)
637
635
638 return [self.node(r) for r in heads]
636 return [self.node(r) for r in heads]
639
637
640 def children(self, node):
638 def children(self, node):
641 """find the children of a given node"""
639 """find the children of a given node"""
642 c = []
640 c = []
643 p = self.rev(node)
641 p = self.rev(node)
644 for r in range(p + 1, len(self)):
642 for r in range(p + 1, len(self)):
645 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
643 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
646 if prevs:
644 if prevs:
647 for pr in prevs:
645 for pr in prevs:
648 if pr == p:
646 if pr == p:
649 c.append(self.node(r))
647 c.append(self.node(r))
650 elif p == nullrev:
648 elif p == nullrev:
651 c.append(self.node(r))
649 c.append(self.node(r))
652 return c
650 return c
653
651
654 def descendant(self, start, end):
652 def descendant(self, start, end):
655 if start == nullrev:
653 if start == nullrev:
656 return True
654 return True
657 for i in self.descendants(start):
655 for i in self.descendants(start):
658 if i == end:
656 if i == end:
659 return True
657 return True
660 elif i > end:
658 elif i > end:
661 break
659 break
662 return False
660 return False
663
661
664 def ancestor(self, a, b):
662 def ancestor(self, a, b):
665 """calculate the least common ancestor of nodes a and b"""
663 """calculate the least common ancestor of nodes a and b"""
666
664
667 # fast path, check if it is a descendant
665 # fast path, check if it is a descendant
668 a, b = self.rev(a), self.rev(b)
666 a, b = self.rev(a), self.rev(b)
669 start, end = sorted((a, b))
667 start, end = sorted((a, b))
670 if self.descendant(start, end):
668 if self.descendant(start, end):
671 return self.node(start)
669 return self.node(start)
672
670
673 def parents(rev):
671 def parents(rev):
674 return [p for p in self.parentrevs(rev) if p != nullrev]
672 return [p for p in self.parentrevs(rev) if p != nullrev]
675
673
676 c = ancestor.ancestor(a, b, parents)
674 c = ancestor.ancestor(a, b, parents)
677 if c is None:
675 if c is None:
678 return nullid
676 return nullid
679
677
680 return self.node(c)
678 return self.node(c)
681
679
682 def _match(self, id):
680 def _match(self, id):
683 if isinstance(id, (long, int)):
681 if isinstance(id, (long, int)):
684 # rev
682 # rev
685 return self.node(id)
683 return self.node(id)
686 if len(id) == 20:
684 if len(id) == 20:
687 # possibly a binary node
685 # possibly a binary node
688 # odds of a binary node being all hex in ASCII are 1 in 10**25
686 # odds of a binary node being all hex in ASCII are 1 in 10**25
689 try:
687 try:
690 node = id
688 node = id
691 self.rev(node) # quick search the index
689 self.rev(node) # quick search the index
692 return node
690 return node
693 except LookupError:
691 except LookupError:
694 pass # may be partial hex id
692 pass # may be partial hex id
695 try:
693 try:
696 # str(rev)
694 # str(rev)
697 rev = int(id)
695 rev = int(id)
698 if str(rev) != id:
696 if str(rev) != id:
699 raise ValueError
697 raise ValueError
700 if rev < 0:
698 if rev < 0:
701 rev = len(self) + rev
699 rev = len(self) + rev
702 if rev < 0 or rev >= len(self):
700 if rev < 0 or rev >= len(self):
703 raise ValueError
701 raise ValueError
704 return self.node(rev)
702 return self.node(rev)
705 except (ValueError, OverflowError):
703 except (ValueError, OverflowError):
706 pass
704 pass
707 if len(id) == 40:
705 if len(id) == 40:
708 try:
706 try:
709 # a full hex nodeid?
707 # a full hex nodeid?
710 node = bin(id)
708 node = bin(id)
711 self.rev(node)
709 self.rev(node)
712 return node
710 return node
713 except (TypeError, LookupError):
711 except (TypeError, LookupError):
714 pass
712 pass
715
713
716 def _partialmatch(self, id):
714 def _partialmatch(self, id):
717 if id in self._pcache:
715 if id in self._pcache:
718 return self._pcache[id]
716 return self._pcache[id]
719
717
720 if len(id) < 40:
718 if len(id) < 40:
721 try:
719 try:
722 # hex(node)[:...]
720 # hex(node)[:...]
723 l = len(id) // 2 # grab an even number of digits
721 l = len(id) // 2 # grab an even number of digits
724 prefix = bin(id[:l * 2])
722 prefix = bin(id[:l * 2])
725 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
723 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
726 nl = [n for n in nl if hex(n).startswith(id)]
724 nl = [n for n in nl if hex(n).startswith(id)]
727 if len(nl) > 0:
725 if len(nl) > 0:
728 if len(nl) == 1:
726 if len(nl) == 1:
729 self._pcache[id] = nl[0]
727 self._pcache[id] = nl[0]
730 return nl[0]
728 return nl[0]
731 raise LookupError(id, self.indexfile,
729 raise LookupError(id, self.indexfile,
732 _('ambiguous identifier'))
730 _('ambiguous identifier'))
733 return None
731 return None
734 except TypeError:
732 except TypeError:
735 pass
733 pass
736
734
737 def lookup(self, id):
735 def lookup(self, id):
738 """locate a node based on:
736 """locate a node based on:
739 - revision number or str(revision number)
737 - revision number or str(revision number)
740 - nodeid or subset of hex nodeid
738 - nodeid or subset of hex nodeid
741 """
739 """
742 n = self._match(id)
740 n = self._match(id)
743 if n is not None:
741 if n is not None:
744 return n
742 return n
745 n = self._partialmatch(id)
743 n = self._partialmatch(id)
746 if n:
744 if n:
747 return n
745 return n
748
746
749 raise LookupError(id, self.indexfile, _('no match found'))
747 raise LookupError(id, self.indexfile, _('no match found'))
750
748
751 def cmp(self, node, text):
749 def cmp(self, node, text):
752 """compare text with a given file revision
750 """compare text with a given file revision
753
751
754 returns True if text is different than what is stored.
752 returns True if text is different than what is stored.
755 """
753 """
756 p1, p2 = self.parents(node)
754 p1, p2 = self.parents(node)
757 return hash(text, p1, p2) != node
755 return hash(text, p1, p2) != node
758
756
759 def _addchunk(self, offset, data):
757 def _addchunk(self, offset, data):
760 o, d = self._chunkcache
758 o, d = self._chunkcache
761 # try to add to existing cache
759 # try to add to existing cache
762 if o + len(d) == offset and len(d) + len(data) < _chunksize:
760 if o + len(d) == offset and len(d) + len(data) < _chunksize:
763 self._chunkcache = o, d + data
761 self._chunkcache = o, d + data
764 else:
762 else:
765 self._chunkcache = offset, data
763 self._chunkcache = offset, data
766
764
767 def _loadchunk(self, offset, length):
765 def _loadchunk(self, offset, length):
768 if self._inline:
766 if self._inline:
769 df = self.opener(self.indexfile)
767 df = self.opener(self.indexfile)
770 else:
768 else:
771 df = self.opener(self.datafile)
769 df = self.opener(self.datafile)
772
770
773 readahead = max(65536, length)
771 readahead = max(65536, length)
774 df.seek(offset)
772 df.seek(offset)
775 d = df.read(readahead)
773 d = df.read(readahead)
776 self._addchunk(offset, d)
774 self._addchunk(offset, d)
777 if readahead > length:
775 if readahead > length:
778 return d[:length]
776 return d[:length]
779 return d
777 return d
780
778
781 def _getchunk(self, offset, length):
779 def _getchunk(self, offset, length):
782 o, d = self._chunkcache
780 o, d = self._chunkcache
783 l = len(d)
781 l = len(d)
784
782
785 # is it in the cache?
783 # is it in the cache?
786 cachestart = offset - o
784 cachestart = offset - o
787 cacheend = cachestart + length
785 cacheend = cachestart + length
788 if cachestart >= 0 and cacheend <= l:
786 if cachestart >= 0 and cacheend <= l:
789 if cachestart == 0 and cacheend == l:
787 if cachestart == 0 and cacheend == l:
790 return d # avoid a copy
788 return d # avoid a copy
791 return d[cachestart:cacheend]
789 return d[cachestart:cacheend]
792
790
793 return self._loadchunk(offset, length)
791 return self._loadchunk(offset, length)
794
792
795 def _chunkraw(self, startrev, endrev):
793 def _chunkraw(self, startrev, endrev):
796 start = self.start(startrev)
794 start = self.start(startrev)
797 length = self.end(endrev) - start
795 length = self.end(endrev) - start
798 if self._inline:
796 if self._inline:
799 start += (startrev + 1) * self._io.size
797 start += (startrev + 1) * self._io.size
800 return self._getchunk(start, length)
798 return self._getchunk(start, length)
801
799
802 def _chunk(self, rev):
800 def _chunk(self, rev):
803 return decompress(self._chunkraw(rev, rev))
801 return decompress(self._chunkraw(rev, rev))
804
802
805 def _chunkclear(self):
803 def _chunkclear(self):
806 self._chunkcache = (0, '')
804 self._chunkcache = (0, '')
807
805
808 def deltaparent(self, rev):
806 def deltaparent(self, rev):
809 """return previous revision or parentrev according to flags"""
807 """return previous revision or parentrev according to flags"""
810 if self.flags(rev) & REVIDX_PARENTDELTA:
808 if self.flags(rev) & REVIDX_PARENTDELTA:
811 return self.parentrevs(rev)[0]
809 return self.parentrevs(rev)[0]
812 else:
810 else:
813 return rev - 1
811 return rev - 1
814
812
815 def revdiff(self, rev1, rev2):
813 def revdiff(self, rev1, rev2):
816 """return or calculate a delta between two revisions"""
814 """return or calculate a delta between two revisions"""
817 if self.base(rev2) != rev2 and self.deltaparent(rev2) == rev1:
815 if self.base(rev2) != rev2 and self.deltaparent(rev2) == rev1:
818 return self._chunk(rev2)
816 return self._chunk(rev2)
819
817
820 return mdiff.textdiff(self.revision(self.node(rev1)),
818 return mdiff.textdiff(self.revision(self.node(rev1)),
821 self.revision(self.node(rev2)))
819 self.revision(self.node(rev2)))
822
820
823 def revision(self, node):
821 def revision(self, node):
824 """return an uncompressed revision of a given node"""
822 """return an uncompressed revision of a given node"""
825 cachedrev = None
823 cachedrev = None
826 if node == nullid:
824 if node == nullid:
827 return ""
825 return ""
828 if self._cache:
826 if self._cache:
829 if self._cache[0] == node:
827 if self._cache[0] == node:
830 return self._cache[2]
828 return self._cache[2]
831 cachedrev = self._cache[1]
829 cachedrev = self._cache[1]
832
830
833 # look up what we need to read
831 # look up what we need to read
834 text = None
832 text = None
835 rev = self.rev(node)
833 rev = self.rev(node)
836 base = self.base(rev)
834 base = self.base(rev)
837
835
838 # check rev flags
836 # check rev flags
839 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
837 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
840 raise RevlogError(_('incompatible revision flag %x') %
838 raise RevlogError(_('incompatible revision flag %x') %
841 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
839 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
842
840
843 # build delta chain
841 # build delta chain
844 chain = []
842 chain = []
845 index = self.index # for performance
843 index = self.index # for performance
846 iterrev = rev
844 iterrev = rev
847 e = index[iterrev]
845 e = index[iterrev]
848 while iterrev != base and iterrev != cachedrev:
846 while iterrev != base and iterrev != cachedrev:
849 chain.append(iterrev)
847 chain.append(iterrev)
850 if e[0] & REVIDX_PARENTDELTA:
848 if e[0] & REVIDX_PARENTDELTA:
851 iterrev = e[5]
849 iterrev = e[5]
852 else:
850 else:
853 iterrev -= 1
851 iterrev -= 1
854 e = index[iterrev]
852 e = index[iterrev]
855 chain.reverse()
853 chain.reverse()
856 base = iterrev
854 base = iterrev
857
855
858 if iterrev == cachedrev:
856 if iterrev == cachedrev:
859 # cache hit
857 # cache hit
860 text = self._cache[2]
858 text = self._cache[2]
861
859
862 # drop cache to save memory
860 # drop cache to save memory
863 self._cache = None
861 self._cache = None
864
862
865 self._chunkraw(base, rev)
863 self._chunkraw(base, rev)
866 if text is None:
864 if text is None:
867 text = self._chunk(base)
865 text = self._chunk(base)
868
866
869 bins = [self._chunk(r) for r in chain]
867 bins = [self._chunk(r) for r in chain]
870 text = mdiff.patches(text, bins)
868 text = mdiff.patches(text, bins)
871
869
872 text = self._checkhash(text, node)
870 text = self._checkhash(text, node)
873
871
874 self._cache = (node, rev, text)
872 self._cache = (node, rev, text)
875 return text
873 return text
876
874
877 def _checkhash(self, text, node):
875 def _checkhash(self, text, node):
878 p1, p2 = self.parents(node)
876 p1, p2 = self.parents(node)
879 if (node != hash(text, p1, p2) and
877 if (node != hash(text, p1, p2) and
880 not (self.flags(rev) & REVIDX_PUNCHED_FLAG)):
878 not (self.flags(rev) & REVIDX_PUNCHED_FLAG)):
881 raise RevlogError(_("integrity check failed on %s:%d")
879 raise RevlogError(_("integrity check failed on %s:%d")
882 % (self.indexfile, rev))
880 % (self.indexfile, rev))
883 return text
881 return text
884
882
885 def checkinlinesize(self, tr, fp=None):
883 def checkinlinesize(self, tr, fp=None):
886 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
884 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
887 return
885 return
888
886
889 trinfo = tr.find(self.indexfile)
887 trinfo = tr.find(self.indexfile)
890 if trinfo is None:
888 if trinfo is None:
891 raise RevlogError(_("%s not found in the transaction")
889 raise RevlogError(_("%s not found in the transaction")
892 % self.indexfile)
890 % self.indexfile)
893
891
894 trindex = trinfo[2]
892 trindex = trinfo[2]
895 dataoff = self.start(trindex)
893 dataoff = self.start(trindex)
896
894
897 tr.add(self.datafile, dataoff)
895 tr.add(self.datafile, dataoff)
898
896
899 if fp:
897 if fp:
900 fp.flush()
898 fp.flush()
901 fp.close()
899 fp.close()
902
900
903 df = self.opener(self.datafile, 'w')
901 df = self.opener(self.datafile, 'w')
904 try:
902 try:
905 for r in self:
903 for r in self:
906 df.write(self._chunkraw(r, r))
904 df.write(self._chunkraw(r, r))
907 finally:
905 finally:
908 df.close()
906 df.close()
909
907
910 fp = self.opener(self.indexfile, 'w', atomictemp=True)
908 fp = self.opener(self.indexfile, 'w', atomictemp=True)
911 self.version &= ~(REVLOGNGINLINEDATA)
909 self.version &= ~(REVLOGNGINLINEDATA)
912 self._inline = False
910 self._inline = False
913 for i in self:
911 for i in self:
914 e = self._io.packentry(self.index[i], self.node, self.version, i)
912 e = self._io.packentry(self.index[i], self.node, self.version, i)
915 fp.write(e)
913 fp.write(e)
916
914
917 # if we don't call rename, the temp file will never replace the
915 # if we don't call rename, the temp file will never replace the
918 # real index
916 # real index
919 fp.rename()
917 fp.rename()
920
918
921 tr.replace(self.indexfile, trindex * self._io.size)
919 tr.replace(self.indexfile, trindex * self._io.size)
922 self._chunkclear()
920 self._chunkclear()
923
921
924 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
922 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
925 """add a revision to the log
923 """add a revision to the log
926
924
927 text - the revision data to add
925 text - the revision data to add
928 transaction - the transaction object used for rollback
926 transaction - the transaction object used for rollback
929 link - the linkrev data to add
927 link - the linkrev data to add
930 p1, p2 - the parent nodeids of the revision
928 p1, p2 - the parent nodeids of the revision
931 cachedelta - an optional precomputed delta
929 cachedelta - an optional precomputed delta
932 """
930 """
933 node = hash(text, p1, p2)
931 node = hash(text, p1, p2)
934 if (node in self.nodemap and
932 if (node in self.nodemap and
935 (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)):
933 (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)):
936 return node
934 return node
937
935
938 dfh = None
936 dfh = None
939 if not self._inline:
937 if not self._inline:
940 dfh = self.opener(self.datafile, "a")
938 dfh = self.opener(self.datafile, "a")
941 ifh = self.opener(self.indexfile, "a+")
939 ifh = self.opener(self.indexfile, "a+")
942 try:
940 try:
943 return self._addrevision(node, text, transaction, link, p1, p2,
941 return self._addrevision(node, text, transaction, link, p1, p2,
944 cachedelta, ifh, dfh)
942 cachedelta, ifh, dfh)
945 finally:
943 finally:
946 if dfh:
944 if dfh:
947 dfh.close()
945 dfh.close()
948 ifh.close()
946 ifh.close()
949
947
950 def _addrevision(self, node, text, transaction, link, p1, p2,
948 def _addrevision(self, node, text, transaction, link, p1, p2,
951 cachedelta, ifh, dfh):
949 cachedelta, ifh, dfh):
952
950
953 btext = [text]
951 btext = [text]
954 def buildtext():
952 def buildtext():
955 if btext[0] is not None:
953 if btext[0] is not None:
956 return btext[0]
954 return btext[0]
957 # flush any pending writes here so we can read it in revision
955 # flush any pending writes here so we can read it in revision
958 if dfh:
956 if dfh:
959 dfh.flush()
957 dfh.flush()
960 ifh.flush()
958 ifh.flush()
961 basetext = self.revision(self.node(cachedelta[0]))
959 basetext = self.revision(self.node(cachedelta[0]))
962 btext[0] = mdiff.patch(basetext, cachedelta[1])
960 btext[0] = mdiff.patch(basetext, cachedelta[1])
963 chk = hash(btext[0], p1, p2)
961 chk = hash(btext[0], p1, p2)
964 if chk != node:
962 if chk != node:
965 raise RevlogError(_("consistency error in delta"))
963 raise RevlogError(_("consistency error in delta"))
966 return btext[0]
964 return btext[0]
967
965
968 def builddelta(rev):
966 def builddelta(rev):
969 # can we use the cached delta?
967 # can we use the cached delta?
970 if cachedelta and cachedelta[0] == rev:
968 if cachedelta and cachedelta[0] == rev:
971 delta = cachedelta[1]
969 delta = cachedelta[1]
972 else:
970 else:
973 t = buildtext()
971 t = buildtext()
974 ptext = self.revision(self.node(rev))
972 ptext = self.revision(self.node(rev))
975 delta = mdiff.textdiff(ptext, t)
973 delta = mdiff.textdiff(ptext, t)
976 data = compress(delta)
974 data = compress(delta)
977 l = len(data[1]) + len(data[0])
975 l = len(data[1]) + len(data[0])
978 base = self.base(rev)
976 base = self.base(rev)
979 dist = l + offset - self.start(base)
977 dist = l + offset - self.start(base)
980 return dist, l, data, base
978 return dist, l, data, base
981
979
982 curr = len(self)
980 curr = len(self)
983 prev = curr - 1
981 prev = curr - 1
984 base = curr
982 base = curr
985 offset = self.end(prev)
983 offset = self.end(prev)
986 flags = 0
984 flags = 0
987 d = None
985 d = None
988 p1r, p2r = self.rev(p1), self.rev(p2)
986 p1r, p2r = self.rev(p1), self.rev(p2)
989
987
990 # should we try to build a delta?
988 # should we try to build a delta?
991 if prev != nullrev:
989 if prev != nullrev:
992 d = builddelta(prev)
990 d = builddelta(prev)
993 if self._parentdelta and prev != p1r:
991 if self._parentdelta and prev != p1r:
994 d2 = builddelta(p1r)
992 d2 = builddelta(p1r)
995 if d2 < d:
993 if d2 < d:
996 d = d2
994 d = d2
997 flags = REVIDX_PARENTDELTA
995 flags = REVIDX_PARENTDELTA
998 dist, l, data, base = d
996 dist, l, data, base = d
999
997
1000 # full versions are inserted when the needed deltas
998 # full versions are inserted when the needed deltas
1001 # become comparable to the uncompressed text
999 # become comparable to the uncompressed text
1002 # or the base revision is punched
1000 # or the base revision is punched
1003 if text is None:
1001 if text is None:
1004 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1002 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1005 cachedelta[1])
1003 cachedelta[1])
1006 else:
1004 else:
1007 textlen = len(text)
1005 textlen = len(text)
1008 if (d is None or dist > textlen * 2 or
1006 if (d is None or dist > textlen * 2 or
1009 (self.flags(base) & REVIDX_PUNCHED_FLAG)):
1007 (self.flags(base) & REVIDX_PUNCHED_FLAG)):
1010 text = buildtext()
1008 text = buildtext()
1011 data = compress(text)
1009 data = compress(text)
1012 l = len(data[1]) + len(data[0])
1010 l = len(data[1]) + len(data[0])
1013 base = curr
1011 base = curr
1014
1012
1015 e = (offset_type(offset, flags), l, textlen,
1013 e = (offset_type(offset, flags), l, textlen,
1016 base, link, p1r, p2r, node)
1014 base, link, p1r, p2r, node)
1017 self.index.insert(-1, e)
1015 self.index.insert(-1, e)
1018 self.nodemap[node] = curr
1016 self.nodemap[node] = curr
1019
1017
1020 entry = self._io.packentry(e, self.node, self.version, curr)
1018 entry = self._io.packentry(e, self.node, self.version, curr)
1021 if not self._inline:
1019 if not self._inline:
1022 transaction.add(self.datafile, offset)
1020 transaction.add(self.datafile, offset)
1023 transaction.add(self.indexfile, curr * len(entry))
1021 transaction.add(self.indexfile, curr * len(entry))
1024 if data[0]:
1022 if data[0]:
1025 dfh.write(data[0])
1023 dfh.write(data[0])
1026 dfh.write(data[1])
1024 dfh.write(data[1])
1027 dfh.flush()
1025 dfh.flush()
1028 ifh.write(entry)
1026 ifh.write(entry)
1029 else:
1027 else:
1030 offset += curr * self._io.size
1028 offset += curr * self._io.size
1031 transaction.add(self.indexfile, offset, curr)
1029 transaction.add(self.indexfile, offset, curr)
1032 ifh.write(entry)
1030 ifh.write(entry)
1033 ifh.write(data[0])
1031 ifh.write(data[0])
1034 ifh.write(data[1])
1032 ifh.write(data[1])
1035 self.checkinlinesize(transaction, ifh)
1033 self.checkinlinesize(transaction, ifh)
1036
1034
1037 if type(text) == str: # only accept immutable objects
1035 if type(text) == str: # only accept immutable objects
1038 self._cache = (node, curr, text)
1036 self._cache = (node, curr, text)
1039 return node
1037 return node
1040
1038
1041 def group(self, nodelist, lookup, infocollect=None, fullrev=False):
1039 def group(self, nodelist, lookup, infocollect=None, fullrev=False):
1042 """Calculate a delta group, yielding a sequence of changegroup chunks
1040 """Calculate a delta group, yielding a sequence of changegroup chunks
1043 (strings).
1041 (strings).
1044
1042
1045 Given a list of changeset revs, return a set of deltas and
1043 Given a list of changeset revs, return a set of deltas and
1046 metadata corresponding to nodes. The first delta is
1044 metadata corresponding to nodes. The first delta is
1047 first parent(nodelist[0]) -> nodelist[0], the receiver is
1045 first parent(nodelist[0]) -> nodelist[0], the receiver is
1048 guaranteed to have this parent as it has all history before
1046 guaranteed to have this parent as it has all history before
1049 these changesets. In the case firstparent is nullrev the
1047 these changesets. In the case firstparent is nullrev the
1050 changegroup starts with a full revision.
1048 changegroup starts with a full revision.
1051 fullrev forces the insertion of the full revision, necessary
1049 fullrev forces the insertion of the full revision, necessary
1052 in the case of shallow clones where the first parent might
1050 in the case of shallow clones where the first parent might
1053 not exist at the reciever.
1051 not exist at the reciever.
1054 """
1052 """
1055
1053
1056 revs = [self.rev(n) for n in nodelist]
1054 revs = [self.rev(n) for n in nodelist]
1057
1055
1058 # if we don't have any revisions touched by these changesets, bail
1056 # if we don't have any revisions touched by these changesets, bail
1059 if not revs:
1057 if not revs:
1060 yield changegroup.closechunk()
1058 yield changegroup.closechunk()
1061 return
1059 return
1062
1060
1063 # add the parent of the first rev
1061 # add the parent of the first rev
1064 p = self.parentrevs(revs[0])[0]
1062 p = self.parentrevs(revs[0])[0]
1065 revs.insert(0, p)
1063 revs.insert(0, p)
1066 if p == nullrev:
1064 if p == nullrev:
1067 fullrev = True
1065 fullrev = True
1068
1066
1069 # build deltas
1067 # build deltas
1070 for d in xrange(len(revs) - 1):
1068 for d in xrange(len(revs) - 1):
1071 a, b = revs[d], revs[d + 1]
1069 a, b = revs[d], revs[d + 1]
1072 nb = self.node(b)
1070 nb = self.node(b)
1073
1071
1074 if infocollect is not None:
1072 if infocollect is not None:
1075 infocollect(nb)
1073 infocollect(nb)
1076
1074
1077 p = self.parents(nb)
1075 p = self.parents(nb)
1078 meta = nb + p[0] + p[1] + lookup(nb)
1076 meta = nb + p[0] + p[1] + lookup(nb)
1079 if fullrev:
1077 if fullrev:
1080 d = self.revision(nb)
1078 d = self.revision(nb)
1081 meta += mdiff.trivialdiffheader(len(d))
1079 meta += mdiff.trivialdiffheader(len(d))
1082 fullrev = False
1080 fullrev = False
1083 else:
1081 else:
1084 d = self.revdiff(a, b)
1082 d = self.revdiff(a, b)
1085 yield changegroup.chunkheader(len(meta) + len(d))
1083 yield changegroup.chunkheader(len(meta) + len(d))
1086 yield meta
1084 yield meta
1087 yield d
1085 yield d
1088
1086
1089 yield changegroup.closechunk()
1087 yield changegroup.closechunk()
1090
1088
1091 def addgroup(self, bundle, linkmapper, transaction):
1089 def addgroup(self, bundle, linkmapper, transaction):
1092 """
1090 """
1093 add a delta group
1091 add a delta group
1094
1092
1095 given a set of deltas, add them to the revision log. the
1093 given a set of deltas, add them to the revision log. the
1096 first delta is against its parent, which should be in our
1094 first delta is against its parent, which should be in our
1097 log, the rest are against the previous delta.
1095 log, the rest are against the previous delta.
1098 """
1096 """
1099
1097
1100 # track the base of the current delta log
1098 # track the base of the current delta log
1101 node = None
1099 node = None
1102
1100
1103 r = len(self)
1101 r = len(self)
1104 end = 0
1102 end = 0
1105 if r:
1103 if r:
1106 end = self.end(r - 1)
1104 end = self.end(r - 1)
1107 ifh = self.opener(self.indexfile, "a+")
1105 ifh = self.opener(self.indexfile, "a+")
1108 isize = r * self._io.size
1106 isize = r * self._io.size
1109 if self._inline:
1107 if self._inline:
1110 transaction.add(self.indexfile, end + isize, r)
1108 transaction.add(self.indexfile, end + isize, r)
1111 dfh = None
1109 dfh = None
1112 else:
1110 else:
1113 transaction.add(self.indexfile, isize, r)
1111 transaction.add(self.indexfile, isize, r)
1114 transaction.add(self.datafile, end)
1112 transaction.add(self.datafile, end)
1115 dfh = self.opener(self.datafile, "a")
1113 dfh = self.opener(self.datafile, "a")
1116
1114
1117 try:
1115 try:
1118 # loop through our set of deltas
1116 # loop through our set of deltas
1119 chain = None
1117 chain = None
1120 while 1:
1118 while 1:
1121 chunkdata = bundle.parsechunk()
1119 chunkdata = bundle.parsechunk()
1122 if not chunkdata:
1120 if not chunkdata:
1123 break
1121 break
1124 node = chunkdata['node']
1122 node = chunkdata['node']
1125 p1 = chunkdata['p1']
1123 p1 = chunkdata['p1']
1126 p2 = chunkdata['p2']
1124 p2 = chunkdata['p2']
1127 cs = chunkdata['cs']
1125 cs = chunkdata['cs']
1128 delta = chunkdata['data']
1126 delta = chunkdata['data']
1129
1127
1130 link = linkmapper(cs)
1128 link = linkmapper(cs)
1131 if (node in self.nodemap and
1129 if (node in self.nodemap and
1132 (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)):
1130 (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)):
1133 # this can happen if two branches make the same change
1131 # this can happen if two branches make the same change
1134 chain = node
1132 chain = node
1135 continue
1133 continue
1136
1134
1137 for p in (p1, p2):
1135 for p in (p1, p2):
1138 if not p in self.nodemap:
1136 if not p in self.nodemap:
1139 if self._shallow:
1137 if self._shallow:
1140 # add null entries for missing parents
1138 # add null entries for missing parents
1141 # XXX FIXME
1139 # XXX FIXME
1142 #if base == nullrev:
1140 #if base == nullrev:
1143 # base = len(self)
1141 # base = len(self)
1144 #e = (offset_type(end, REVIDX_PUNCHED_FLAG),
1142 #e = (offset_type(end, REVIDX_PUNCHED_FLAG),
1145 # 0, 0, base, nullrev, nullrev, nullrev, p)
1143 # 0, 0, base, nullrev, nullrev, nullrev, p)
1146 #self.index.insert(-1, e)
1144 #self.index.insert(-1, e)
1147 #self.nodemap[p] = r
1145 #self.nodemap[p] = r
1148 #entry = self._io.packentry(e, self.node,
1146 #entry = self._io.packentry(e, self.node,
1149 # self.version, r)
1147 # self.version, r)
1150 #ifh.write(entry)
1148 #ifh.write(entry)
1151 #t, r = r, r + 1
1149 #t, r = r, r + 1
1152 raise LookupError(p, self.indexfile,
1150 raise LookupError(p, self.indexfile,
1153 _('unknown parent'))
1151 _('unknown parent'))
1154 else:
1152 else:
1155 raise LookupError(p, self.indexfile,
1153 raise LookupError(p, self.indexfile,
1156 _('unknown parent'))
1154 _('unknown parent'))
1157
1155
1158 if not chain:
1156 if not chain:
1159 # retrieve the parent revision of the delta chain
1157 # retrieve the parent revision of the delta chain
1160 chain = p1
1158 chain = p1
1161 if not chain in self.nodemap:
1159 if not chain in self.nodemap:
1162 raise LookupError(chain, self.indexfile, _('unknown base'))
1160 raise LookupError(chain, self.indexfile, _('unknown base'))
1163
1161
1164 chainrev = self.rev(chain)
1162 chainrev = self.rev(chain)
1165 chain = self._addrevision(node, None, transaction, link,
1163 chain = self._addrevision(node, None, transaction, link,
1166 p1, p2, (chainrev, delta), ifh, dfh)
1164 p1, p2, (chainrev, delta), ifh, dfh)
1167 if not dfh and not self._inline:
1165 if not dfh and not self._inline:
1168 # addrevision switched from inline to conventional
1166 # addrevision switched from inline to conventional
1169 # reopen the index
1167 # reopen the index
1170 dfh = self.opener(self.datafile, "a")
1168 dfh = self.opener(self.datafile, "a")
1171 ifh = self.opener(self.indexfile, "a")
1169 ifh = self.opener(self.indexfile, "a")
1172 finally:
1170 finally:
1173 if dfh:
1171 if dfh:
1174 dfh.close()
1172 dfh.close()
1175 ifh.close()
1173 ifh.close()
1176
1174
1177 return node
1175 return node
1178
1176
1179 def strip(self, minlink, transaction):
1177 def strip(self, minlink, transaction):
1180 """truncate the revlog on the first revision with a linkrev >= minlink
1178 """truncate the revlog on the first revision with a linkrev >= minlink
1181
1179
1182 This function is called when we're stripping revision minlink and
1180 This function is called when we're stripping revision minlink and
1183 its descendants from the repository.
1181 its descendants from the repository.
1184
1182
1185 We have to remove all revisions with linkrev >= minlink, because
1183 We have to remove all revisions with linkrev >= minlink, because
1186 the equivalent changelog revisions will be renumbered after the
1184 the equivalent changelog revisions will be renumbered after the
1187 strip.
1185 strip.
1188
1186
1189 So we truncate the revlog on the first of these revisions, and
1187 So we truncate the revlog on the first of these revisions, and
1190 trust that the caller has saved the revisions that shouldn't be
1188 trust that the caller has saved the revisions that shouldn't be
1191 removed and that it'll readd them after this truncation.
1189 removed and that it'll readd them after this truncation.
1192 """
1190 """
1193 if len(self) == 0:
1191 if len(self) == 0:
1194 return
1192 return
1195
1193
1196 for rev in self:
1194 for rev in self:
1197 if self.index[rev][4] >= minlink:
1195 if self.index[rev][4] >= minlink:
1198 break
1196 break
1199 else:
1197 else:
1200 return
1198 return
1201
1199
1202 # first truncate the files on disk
1200 # first truncate the files on disk
1203 end = self.start(rev)
1201 end = self.start(rev)
1204 if not self._inline:
1202 if not self._inline:
1205 transaction.add(self.datafile, end)
1203 transaction.add(self.datafile, end)
1206 end = rev * self._io.size
1204 end = rev * self._io.size
1207 else:
1205 else:
1208 end += rev * self._io.size
1206 end += rev * self._io.size
1209
1207
1210 transaction.add(self.indexfile, end)
1208 transaction.add(self.indexfile, end)
1211
1209
1212 # then reset internal state in memory to forget those revisions
1210 # then reset internal state in memory to forget those revisions
1213 self._cache = None
1211 self._cache = None
1214 self._chunkclear()
1212 self._chunkclear()
1215 for x in xrange(rev, len(self)):
1213 for x in xrange(rev, len(self)):
1216 del self.nodemap[self.node(x)]
1214 del self.nodemap[self.node(x)]
1217
1215
1218 del self.index[rev:-1]
1216 del self.index[rev:-1]
1219
1217
1220 def checksize(self):
1218 def checksize(self):
1221 expected = 0
1219 expected = 0
1222 if len(self):
1220 if len(self):
1223 expected = max(0, self.end(len(self) - 1))
1221 expected = max(0, self.end(len(self) - 1))
1224
1222
1225 try:
1223 try:
1226 f = self.opener(self.datafile)
1224 f = self.opener(self.datafile)
1227 f.seek(0, 2)
1225 f.seek(0, 2)
1228 actual = f.tell()
1226 actual = f.tell()
1229 dd = actual - expected
1227 dd = actual - expected
1230 except IOError, inst:
1228 except IOError, inst:
1231 if inst.errno != errno.ENOENT:
1229 if inst.errno != errno.ENOENT:
1232 raise
1230 raise
1233 dd = 0
1231 dd = 0
1234
1232
1235 try:
1233 try:
1236 f = self.opener(self.indexfile)
1234 f = self.opener(self.indexfile)
1237 f.seek(0, 2)
1235 f.seek(0, 2)
1238 actual = f.tell()
1236 actual = f.tell()
1239 s = self._io.size
1237 s = self._io.size
1240 i = max(0, actual // s)
1238 i = max(0, actual // s)
1241 di = actual - (i * s)
1239 di = actual - (i * s)
1242 if self._inline:
1240 if self._inline:
1243 databytes = 0
1241 databytes = 0
1244 for r in self:
1242 for r in self:
1245 databytes += max(0, self.length(r))
1243 databytes += max(0, self.length(r))
1246 dd = 0
1244 dd = 0
1247 di = actual - len(self) * s - databytes
1245 di = actual - len(self) * s - databytes
1248 except IOError, inst:
1246 except IOError, inst:
1249 if inst.errno != errno.ENOENT:
1247 if inst.errno != errno.ENOENT:
1250 raise
1248 raise
1251 di = 0
1249 di = 0
1252
1250
1253 return (dd, di)
1251 return (dd, di)
1254
1252
1255 def files(self):
1253 def files(self):
1256 res = [self.indexfile]
1254 res = [self.indexfile]
1257 if not self._inline:
1255 if not self._inline:
1258 res.append(self.datafile)
1256 res.append(self.datafile)
1259 return res
1257 return res
General Comments 0
You need to be logged in to leave comments. Login now