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