##// END OF EJS Templates
revlog: add optional stoprev arg to revlog.ancestors()...
Joshua Redstone -
r16868:eb88ed42 default
parent child Browse files
Show More
@@ -1,1321 +1,1324 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, getattr(index, 'nodemap', None), cache
177 return index, getattr(index, 'nodemap', 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 hasnode(self, node):
291 def hasnode(self, node):
292 try:
292 try:
293 self.rev(node)
293 self.rev(node)
294 return True
294 return True
295 except KeyError:
295 except KeyError:
296 return False
296 return False
297
297
298 def clearcaches(self):
298 def clearcaches(self):
299 try:
299 try:
300 self._nodecache.clearcaches()
300 self._nodecache.clearcaches()
301 except AttributeError:
301 except AttributeError:
302 self._nodecache = {nullid: nullrev}
302 self._nodecache = {nullid: nullrev}
303 self._nodepos = None
303 self._nodepos = None
304
304
305 def rev(self, node):
305 def rev(self, node):
306 try:
306 try:
307 return self._nodecache[node]
307 return self._nodecache[node]
308 except RevlogError:
308 except RevlogError:
309 # parsers.c radix tree lookup failed
309 # parsers.c radix tree lookup failed
310 raise LookupError(node, self.indexfile, _('no node'))
310 raise LookupError(node, self.indexfile, _('no node'))
311 except KeyError:
311 except KeyError:
312 # pure python cache lookup failed
312 # pure python cache lookup failed
313 n = self._nodecache
313 n = self._nodecache
314 i = self.index
314 i = self.index
315 p = self._nodepos
315 p = self._nodepos
316 if p is None:
316 if p is None:
317 p = len(i) - 2
317 p = len(i) - 2
318 for r in xrange(p, -1, -1):
318 for r in xrange(p, -1, -1):
319 v = i[r][7]
319 v = i[r][7]
320 n[v] = r
320 n[v] = r
321 if v == node:
321 if v == node:
322 self._nodepos = r - 1
322 self._nodepos = r - 1
323 return r
323 return r
324 raise LookupError(node, self.indexfile, _('no node'))
324 raise LookupError(node, self.indexfile, _('no node'))
325
325
326 def node(self, rev):
326 def node(self, rev):
327 return self.index[rev][7]
327 return self.index[rev][7]
328 def linkrev(self, rev):
328 def linkrev(self, rev):
329 return self.index[rev][4]
329 return self.index[rev][4]
330 def parents(self, node):
330 def parents(self, node):
331 i = self.index
331 i = self.index
332 d = i[self.rev(node)]
332 d = i[self.rev(node)]
333 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
333 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
334 def parentrevs(self, rev):
334 def parentrevs(self, rev):
335 return self.index[rev][5:7]
335 return self.index[rev][5:7]
336 def start(self, rev):
336 def start(self, rev):
337 return int(self.index[rev][0] >> 16)
337 return int(self.index[rev][0] >> 16)
338 def end(self, rev):
338 def end(self, rev):
339 return self.start(rev) + self.length(rev)
339 return self.start(rev) + self.length(rev)
340 def length(self, rev):
340 def length(self, rev):
341 return self.index[rev][1]
341 return self.index[rev][1]
342 def chainbase(self, rev):
342 def chainbase(self, rev):
343 index = self.index
343 index = self.index
344 base = index[rev][3]
344 base = index[rev][3]
345 while base != rev:
345 while base != rev:
346 rev = base
346 rev = base
347 base = index[rev][3]
347 base = index[rev][3]
348 return base
348 return base
349 def flags(self, rev):
349 def flags(self, rev):
350 return self.index[rev][0] & 0xFFFF
350 return self.index[rev][0] & 0xFFFF
351 def rawsize(self, rev):
351 def rawsize(self, rev):
352 """return the length of the uncompressed text for a given revision"""
352 """return the length of the uncompressed text for a given revision"""
353 l = self.index[rev][2]
353 l = self.index[rev][2]
354 if l >= 0:
354 if l >= 0:
355 return l
355 return l
356
356
357 t = self.revision(self.node(rev))
357 t = self.revision(self.node(rev))
358 return len(t)
358 return len(t)
359 size = rawsize
359 size = rawsize
360
360
361 def reachable(self, node, stop=None):
361 def reachable(self, node, stop=None):
362 """return the set of all nodes ancestral to a given node, including
362 """return the set of all nodes ancestral to a given node, including
363 the node itself, stopping when stop is matched"""
363 the node itself, stopping when stop is matched"""
364 reachable = set((node,))
364 reachable = set((node,))
365 visit = util.deque([node])
365 visit = util.deque([node])
366 if stop:
366 if stop:
367 stopn = self.rev(stop)
367 stopn = self.rev(stop)
368 else:
368 else:
369 stopn = 0
369 stopn = 0
370 while visit:
370 while visit:
371 n = visit.popleft()
371 n = visit.popleft()
372 if n == stop:
372 if n == stop:
373 continue
373 continue
374 if n == nullid:
374 if n == nullid:
375 continue
375 continue
376 for p in self.parents(n):
376 for p in self.parents(n):
377 if self.rev(p) < stopn:
377 if self.rev(p) < stopn:
378 continue
378 continue
379 if p not in reachable:
379 if p not in reachable:
380 reachable.add(p)
380 reachable.add(p)
381 visit.append(p)
381 visit.append(p)
382 return reachable
382 return reachable
383
383
384 def ancestors(self, revs):
384 def ancestors(self, revs, stoprev=0):
385 """Generate the ancestors of 'revs' in reverse topological order.
385 """Generate the ancestors of 'revs' in reverse topological order.
386 Does not generate revs lower than stoprev.
386
387
387 Yield a sequence of revision numbers starting with the parents
388 Yield a sequence of revision numbers starting with the parents
388 of each revision in revs, i.e., each revision is *not* considered
389 of each revision in revs, i.e., each revision is *not* considered
389 an ancestor of itself. Results are in breadth-first order:
390 an ancestor of itself. Results are in breadth-first order:
390 parents of each rev in revs, then parents of those, etc. Result
391 parents of each rev in revs, then parents of those, etc. Result
391 does not include the null revision."""
392 does not include the null revision."""
392 visit = util.deque(revs)
393 visit = util.deque(revs)
393 seen = set([nullrev])
394 seen = set([nullrev])
394 while visit:
395 while visit:
395 for parent in self.parentrevs(visit.popleft()):
396 for parent in self.parentrevs(visit.popleft()):
397 if parent < stoprev:
398 continue
396 if parent not in seen:
399 if parent not in seen:
397 visit.append(parent)
400 visit.append(parent)
398 seen.add(parent)
401 seen.add(parent)
399 yield parent
402 yield parent
400
403
401 def descendants(self, revs):
404 def descendants(self, revs):
402 """Generate the descendants of 'revs' in revision order.
405 """Generate the descendants of 'revs' in revision order.
403
406
404 Yield a sequence of revision numbers starting with a child of
407 Yield a sequence of revision numbers starting with a child of
405 some rev in revs, i.e., each revision is *not* considered a
408 some rev in revs, i.e., each revision is *not* considered a
406 descendant of itself. Results are ordered by revision number (a
409 descendant of itself. Results are ordered by revision number (a
407 topological sort)."""
410 topological sort)."""
408 first = min(revs)
411 first = min(revs)
409 if first == nullrev:
412 if first == nullrev:
410 for i in self:
413 for i in self:
411 yield i
414 yield i
412 return
415 return
413
416
414 seen = set(revs)
417 seen = set(revs)
415 for i in xrange(first + 1, len(self)):
418 for i in xrange(first + 1, len(self)):
416 for x in self.parentrevs(i):
419 for x in self.parentrevs(i):
417 if x != nullrev and x in seen:
420 if x != nullrev and x in seen:
418 seen.add(i)
421 seen.add(i)
419 yield i
422 yield i
420 break
423 break
421
424
422 def findcommonmissing(self, common=None, heads=None):
425 def findcommonmissing(self, common=None, heads=None):
423 """Return a tuple of the ancestors of common and the ancestors of heads
426 """Return a tuple of the ancestors of common and the ancestors of heads
424 that are not ancestors of common. In revset terminology, we return the
427 that are not ancestors of common. In revset terminology, we return the
425 tuple:
428 tuple:
426
429
427 ::common, (::heads) - (::common)
430 ::common, (::heads) - (::common)
428
431
429 The list is sorted by revision number, meaning it is
432 The list is sorted by revision number, meaning it is
430 topologically sorted.
433 topologically sorted.
431
434
432 'heads' and 'common' are both lists of node IDs. If heads is
435 'heads' and 'common' are both lists of node IDs. If heads is
433 not supplied, uses all of the revlog's heads. If common is not
436 not supplied, uses all of the revlog's heads. If common is not
434 supplied, uses nullid."""
437 supplied, uses nullid."""
435 if common is None:
438 if common is None:
436 common = [nullid]
439 common = [nullid]
437 if heads is None:
440 if heads is None:
438 heads = self.heads()
441 heads = self.heads()
439
442
440 common = [self.rev(n) for n in common]
443 common = [self.rev(n) for n in common]
441 heads = [self.rev(n) for n in heads]
444 heads = [self.rev(n) for n in heads]
442
445
443 # we want the ancestors, but inclusive
446 # we want the ancestors, but inclusive
444 has = set(self.ancestors(common))
447 has = set(self.ancestors(common))
445 has.add(nullrev)
448 has.add(nullrev)
446 has.update(common)
449 has.update(common)
447
450
448 # take all ancestors from heads that aren't in has
451 # take all ancestors from heads that aren't in has
449 missing = set()
452 missing = set()
450 visit = util.deque(r for r in heads if r not in has)
453 visit = util.deque(r for r in heads if r not in has)
451 while visit:
454 while visit:
452 r = visit.popleft()
455 r = visit.popleft()
453 if r in missing:
456 if r in missing:
454 continue
457 continue
455 else:
458 else:
456 missing.add(r)
459 missing.add(r)
457 for p in self.parentrevs(r):
460 for p in self.parentrevs(r):
458 if p not in has:
461 if p not in has:
459 visit.append(p)
462 visit.append(p)
460 missing = list(missing)
463 missing = list(missing)
461 missing.sort()
464 missing.sort()
462 return has, [self.node(r) for r in missing]
465 return has, [self.node(r) for r in missing]
463
466
464 def findmissing(self, common=None, heads=None):
467 def findmissing(self, common=None, heads=None):
465 """Return the ancestors of heads that are not ancestors of common.
468 """Return the ancestors of heads that are not ancestors of common.
466
469
467 More specifically, return a list of nodes N such that every N
470 More specifically, return a list of nodes N such that every N
468 satisfies the following constraints:
471 satisfies the following constraints:
469
472
470 1. N is an ancestor of some node in 'heads'
473 1. N is an ancestor of some node in 'heads'
471 2. N is not an ancestor of any node in 'common'
474 2. N is not an ancestor of any node in 'common'
472
475
473 The list is sorted by revision number, meaning it is
476 The list is sorted by revision number, meaning it is
474 topologically sorted.
477 topologically sorted.
475
478
476 'heads' and 'common' are both lists of node IDs. If heads is
479 'heads' and 'common' are both lists of node IDs. If heads is
477 not supplied, uses all of the revlog's heads. If common is not
480 not supplied, uses all of the revlog's heads. If common is not
478 supplied, uses nullid."""
481 supplied, uses nullid."""
479 _common, missing = self.findcommonmissing(common, heads)
482 _common, missing = self.findcommonmissing(common, heads)
480 return missing
483 return missing
481
484
482 def nodesbetween(self, roots=None, heads=None):
485 def nodesbetween(self, roots=None, heads=None):
483 """Return a topological path from 'roots' to 'heads'.
486 """Return a topological path from 'roots' to 'heads'.
484
487
485 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
488 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
486 topologically sorted list of all nodes N that satisfy both of
489 topologically sorted list of all nodes N that satisfy both of
487 these constraints:
490 these constraints:
488
491
489 1. N is a descendant of some node in 'roots'
492 1. N is a descendant of some node in 'roots'
490 2. N is an ancestor of some node in 'heads'
493 2. N is an ancestor of some node in 'heads'
491
494
492 Every node is considered to be both a descendant and an ancestor
495 Every node is considered to be both a descendant and an ancestor
493 of itself, so every reachable node in 'roots' and 'heads' will be
496 of itself, so every reachable node in 'roots' and 'heads' will be
494 included in 'nodes'.
497 included in 'nodes'.
495
498
496 'outroots' is the list of reachable nodes in 'roots', i.e., the
499 'outroots' is the list of reachable nodes in 'roots', i.e., the
497 subset of 'roots' that is returned in 'nodes'. Likewise,
500 subset of 'roots' that is returned in 'nodes'. Likewise,
498 'outheads' is the subset of 'heads' that is also in 'nodes'.
501 'outheads' is the subset of 'heads' that is also in 'nodes'.
499
502
500 'roots' and 'heads' are both lists of node IDs. If 'roots' is
503 'roots' and 'heads' are both lists of node IDs. If 'roots' is
501 unspecified, uses nullid as the only root. If 'heads' is
504 unspecified, uses nullid as the only root. If 'heads' is
502 unspecified, uses list of all of the revlog's heads."""
505 unspecified, uses list of all of the revlog's heads."""
503 nonodes = ([], [], [])
506 nonodes = ([], [], [])
504 if roots is not None:
507 if roots is not None:
505 roots = list(roots)
508 roots = list(roots)
506 if not roots:
509 if not roots:
507 return nonodes
510 return nonodes
508 lowestrev = min([self.rev(n) for n in roots])
511 lowestrev = min([self.rev(n) for n in roots])
509 else:
512 else:
510 roots = [nullid] # Everybody's a descendant of nullid
513 roots = [nullid] # Everybody's a descendant of nullid
511 lowestrev = nullrev
514 lowestrev = nullrev
512 if (lowestrev == nullrev) and (heads is None):
515 if (lowestrev == nullrev) and (heads is None):
513 # We want _all_ the nodes!
516 # We want _all_ the nodes!
514 return ([self.node(r) for r in self], [nullid], list(self.heads()))
517 return ([self.node(r) for r in self], [nullid], list(self.heads()))
515 if heads is None:
518 if heads is None:
516 # All nodes are ancestors, so the latest ancestor is the last
519 # All nodes are ancestors, so the latest ancestor is the last
517 # node.
520 # node.
518 highestrev = len(self) - 1
521 highestrev = len(self) - 1
519 # Set ancestors to None to signal that every node is an ancestor.
522 # Set ancestors to None to signal that every node is an ancestor.
520 ancestors = None
523 ancestors = None
521 # Set heads to an empty dictionary for later discovery of heads
524 # Set heads to an empty dictionary for later discovery of heads
522 heads = {}
525 heads = {}
523 else:
526 else:
524 heads = list(heads)
527 heads = list(heads)
525 if not heads:
528 if not heads:
526 return nonodes
529 return nonodes
527 ancestors = set()
530 ancestors = set()
528 # Turn heads into a dictionary so we can remove 'fake' heads.
531 # Turn heads into a dictionary so we can remove 'fake' heads.
529 # Also, later we will be using it to filter out the heads we can't
532 # Also, later we will be using it to filter out the heads we can't
530 # find from roots.
533 # find from roots.
531 heads = dict.fromkeys(heads, False)
534 heads = dict.fromkeys(heads, False)
532 # Start at the top and keep marking parents until we're done.
535 # Start at the top and keep marking parents until we're done.
533 nodestotag = set(heads)
536 nodestotag = set(heads)
534 # Remember where the top was so we can use it as a limit later.
537 # Remember where the top was so we can use it as a limit later.
535 highestrev = max([self.rev(n) for n in nodestotag])
538 highestrev = max([self.rev(n) for n in nodestotag])
536 while nodestotag:
539 while nodestotag:
537 # grab a node to tag
540 # grab a node to tag
538 n = nodestotag.pop()
541 n = nodestotag.pop()
539 # Never tag nullid
542 # Never tag nullid
540 if n == nullid:
543 if n == nullid:
541 continue
544 continue
542 # A node's revision number represents its place in a
545 # A node's revision number represents its place in a
543 # topologically sorted list of nodes.
546 # topologically sorted list of nodes.
544 r = self.rev(n)
547 r = self.rev(n)
545 if r >= lowestrev:
548 if r >= lowestrev:
546 if n not in ancestors:
549 if n not in ancestors:
547 # If we are possibly a descendant of one of the roots
550 # If we are possibly a descendant of one of the roots
548 # and we haven't already been marked as an ancestor
551 # and we haven't already been marked as an ancestor
549 ancestors.add(n) # Mark as ancestor
552 ancestors.add(n) # Mark as ancestor
550 # Add non-nullid parents to list of nodes to tag.
553 # Add non-nullid parents to list of nodes to tag.
551 nodestotag.update([p for p in self.parents(n) if
554 nodestotag.update([p for p in self.parents(n) if
552 p != nullid])
555 p != nullid])
553 elif n in heads: # We've seen it before, is it a fake head?
556 elif n in heads: # We've seen it before, is it a fake head?
554 # So it is, real heads should not be the ancestors of
557 # So it is, real heads should not be the ancestors of
555 # any other heads.
558 # any other heads.
556 heads.pop(n)
559 heads.pop(n)
557 if not ancestors:
560 if not ancestors:
558 return nonodes
561 return nonodes
559 # Now that we have our set of ancestors, we want to remove any
562 # Now that we have our set of ancestors, we want to remove any
560 # roots that are not ancestors.
563 # roots that are not ancestors.
561
564
562 # If one of the roots was nullid, everything is included anyway.
565 # If one of the roots was nullid, everything is included anyway.
563 if lowestrev > nullrev:
566 if lowestrev > nullrev:
564 # But, since we weren't, let's recompute the lowest rev to not
567 # But, since we weren't, let's recompute the lowest rev to not
565 # include roots that aren't ancestors.
568 # include roots that aren't ancestors.
566
569
567 # Filter out roots that aren't ancestors of heads
570 # Filter out roots that aren't ancestors of heads
568 roots = [n for n in roots if n in ancestors]
571 roots = [n for n in roots if n in ancestors]
569 # Recompute the lowest revision
572 # Recompute the lowest revision
570 if roots:
573 if roots:
571 lowestrev = min([self.rev(n) for n in roots])
574 lowestrev = min([self.rev(n) for n in roots])
572 else:
575 else:
573 # No more roots? Return empty list
576 # No more roots? Return empty list
574 return nonodes
577 return nonodes
575 else:
578 else:
576 # We are descending from nullid, and don't need to care about
579 # We are descending from nullid, and don't need to care about
577 # any other roots.
580 # any other roots.
578 lowestrev = nullrev
581 lowestrev = nullrev
579 roots = [nullid]
582 roots = [nullid]
580 # Transform our roots list into a set.
583 # Transform our roots list into a set.
581 descendants = set(roots)
584 descendants = set(roots)
582 # Also, keep the original roots so we can filter out roots that aren't
585 # Also, keep the original roots so we can filter out roots that aren't
583 # 'real' roots (i.e. are descended from other roots).
586 # 'real' roots (i.e. are descended from other roots).
584 roots = descendants.copy()
587 roots = descendants.copy()
585 # Our topologically sorted list of output nodes.
588 # Our topologically sorted list of output nodes.
586 orderedout = []
589 orderedout = []
587 # Don't start at nullid since we don't want nullid in our output list,
590 # Don't start at nullid since we don't want nullid in our output list,
588 # and if nullid shows up in descedents, empty parents will look like
591 # and if nullid shows up in descedents, empty parents will look like
589 # they're descendants.
592 # they're descendants.
590 for r in xrange(max(lowestrev, 0), highestrev + 1):
593 for r in xrange(max(lowestrev, 0), highestrev + 1):
591 n = self.node(r)
594 n = self.node(r)
592 isdescendant = False
595 isdescendant = False
593 if lowestrev == nullrev: # Everybody is a descendant of nullid
596 if lowestrev == nullrev: # Everybody is a descendant of nullid
594 isdescendant = True
597 isdescendant = True
595 elif n in descendants:
598 elif n in descendants:
596 # n is already a descendant
599 # n is already a descendant
597 isdescendant = True
600 isdescendant = True
598 # This check only needs to be done here because all the roots
601 # This check only needs to be done here because all the roots
599 # will start being marked is descendants before the loop.
602 # will start being marked is descendants before the loop.
600 if n in roots:
603 if n in roots:
601 # If n was a root, check if it's a 'real' root.
604 # If n was a root, check if it's a 'real' root.
602 p = tuple(self.parents(n))
605 p = tuple(self.parents(n))
603 # If any of its parents are descendants, it's not a root.
606 # If any of its parents are descendants, it's not a root.
604 if (p[0] in descendants) or (p[1] in descendants):
607 if (p[0] in descendants) or (p[1] in descendants):
605 roots.remove(n)
608 roots.remove(n)
606 else:
609 else:
607 p = tuple(self.parents(n))
610 p = tuple(self.parents(n))
608 # A node is a descendant if either of its parents are
611 # A node is a descendant if either of its parents are
609 # descendants. (We seeded the dependents list with the roots
612 # descendants. (We seeded the dependents list with the roots
610 # up there, remember?)
613 # up there, remember?)
611 if (p[0] in descendants) or (p[1] in descendants):
614 if (p[0] in descendants) or (p[1] in descendants):
612 descendants.add(n)
615 descendants.add(n)
613 isdescendant = True
616 isdescendant = True
614 if isdescendant and ((ancestors is None) or (n in ancestors)):
617 if isdescendant and ((ancestors is None) or (n in ancestors)):
615 # Only include nodes that are both descendants and ancestors.
618 # Only include nodes that are both descendants and ancestors.
616 orderedout.append(n)
619 orderedout.append(n)
617 if (ancestors is not None) and (n in heads):
620 if (ancestors is not None) and (n in heads):
618 # We're trying to figure out which heads are reachable
621 # We're trying to figure out which heads are reachable
619 # from roots.
622 # from roots.
620 # Mark this head as having been reached
623 # Mark this head as having been reached
621 heads[n] = True
624 heads[n] = True
622 elif ancestors is None:
625 elif ancestors is None:
623 # Otherwise, we're trying to discover the heads.
626 # Otherwise, we're trying to discover the heads.
624 # Assume this is a head because if it isn't, the next step
627 # Assume this is a head because if it isn't, the next step
625 # will eventually remove it.
628 # will eventually remove it.
626 heads[n] = True
629 heads[n] = True
627 # But, obviously its parents aren't.
630 # But, obviously its parents aren't.
628 for p in self.parents(n):
631 for p in self.parents(n):
629 heads.pop(p, None)
632 heads.pop(p, None)
630 heads = [n for n, flag in heads.iteritems() if flag]
633 heads = [n for n, flag in heads.iteritems() if flag]
631 roots = list(roots)
634 roots = list(roots)
632 assert orderedout
635 assert orderedout
633 assert roots
636 assert roots
634 assert heads
637 assert heads
635 return (orderedout, roots, heads)
638 return (orderedout, roots, heads)
636
639
637 def headrevs(self):
640 def headrevs(self):
638 try:
641 try:
639 return self.index.headrevs()
642 return self.index.headrevs()
640 except AttributeError:
643 except AttributeError:
641 pass
644 pass
642 count = len(self)
645 count = len(self)
643 if not count:
646 if not count:
644 return [nullrev]
647 return [nullrev]
645 ishead = [1] * (count + 1)
648 ishead = [1] * (count + 1)
646 index = self.index
649 index = self.index
647 for r in xrange(count):
650 for r in xrange(count):
648 e = index[r]
651 e = index[r]
649 ishead[e[5]] = ishead[e[6]] = 0
652 ishead[e[5]] = ishead[e[6]] = 0
650 return [r for r in xrange(count) if ishead[r]]
653 return [r for r in xrange(count) if ishead[r]]
651
654
652 def heads(self, start=None, stop=None):
655 def heads(self, start=None, stop=None):
653 """return the list of all nodes that have no children
656 """return the list of all nodes that have no children
654
657
655 if start is specified, only heads that are descendants of
658 if start is specified, only heads that are descendants of
656 start will be returned
659 start will be returned
657 if stop is specified, it will consider all the revs from stop
660 if stop is specified, it will consider all the revs from stop
658 as if they had no children
661 as if they had no children
659 """
662 """
660 if start is None and stop is None:
663 if start is None and stop is None:
661 if not len(self):
664 if not len(self):
662 return [nullid]
665 return [nullid]
663 return [self.node(r) for r in self.headrevs()]
666 return [self.node(r) for r in self.headrevs()]
664
667
665 if start is None:
668 if start is None:
666 start = nullid
669 start = nullid
667 if stop is None:
670 if stop is None:
668 stop = []
671 stop = []
669 stoprevs = set([self.rev(n) for n in stop])
672 stoprevs = set([self.rev(n) for n in stop])
670 startrev = self.rev(start)
673 startrev = self.rev(start)
671 reachable = set((startrev,))
674 reachable = set((startrev,))
672 heads = set((startrev,))
675 heads = set((startrev,))
673
676
674 parentrevs = self.parentrevs
677 parentrevs = self.parentrevs
675 for r in xrange(startrev + 1, len(self)):
678 for r in xrange(startrev + 1, len(self)):
676 for p in parentrevs(r):
679 for p in parentrevs(r):
677 if p in reachable:
680 if p in reachable:
678 if r not in stoprevs:
681 if r not in stoprevs:
679 reachable.add(r)
682 reachable.add(r)
680 heads.add(r)
683 heads.add(r)
681 if p in heads and p not in stoprevs:
684 if p in heads and p not in stoprevs:
682 heads.remove(p)
685 heads.remove(p)
683
686
684 return [self.node(r) for r in heads]
687 return [self.node(r) for r in heads]
685
688
686 def children(self, node):
689 def children(self, node):
687 """find the children of a given node"""
690 """find the children of a given node"""
688 c = []
691 c = []
689 p = self.rev(node)
692 p = self.rev(node)
690 for r in range(p + 1, len(self)):
693 for r in range(p + 1, len(self)):
691 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
694 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
692 if prevs:
695 if prevs:
693 for pr in prevs:
696 for pr in prevs:
694 if pr == p:
697 if pr == p:
695 c.append(self.node(r))
698 c.append(self.node(r))
696 elif p == nullrev:
699 elif p == nullrev:
697 c.append(self.node(r))
700 c.append(self.node(r))
698 return c
701 return c
699
702
700 def descendant(self, start, end):
703 def descendant(self, start, end):
701 if start == nullrev:
704 if start == nullrev:
702 return True
705 return True
703 for i in self.descendants([start]):
706 for i in self.descendants([start]):
704 if i == end:
707 if i == end:
705 return True
708 return True
706 elif i > end:
709 elif i > end:
707 break
710 break
708 return False
711 return False
709
712
710 def ancestor(self, a, b):
713 def ancestor(self, a, b):
711 """calculate the least common ancestor of nodes a and b"""
714 """calculate the least common ancestor of nodes a and b"""
712
715
713 # fast path, check if it is a descendant
716 # fast path, check if it is a descendant
714 a, b = self.rev(a), self.rev(b)
717 a, b = self.rev(a), self.rev(b)
715 start, end = sorted((a, b))
718 start, end = sorted((a, b))
716 if self.descendant(start, end):
719 if self.descendant(start, end):
717 return self.node(start)
720 return self.node(start)
718
721
719 def parents(rev):
722 def parents(rev):
720 return [p for p in self.parentrevs(rev) if p != nullrev]
723 return [p for p in self.parentrevs(rev) if p != nullrev]
721
724
722 c = ancestor.ancestor(a, b, parents)
725 c = ancestor.ancestor(a, b, parents)
723 if c is None:
726 if c is None:
724 return nullid
727 return nullid
725
728
726 return self.node(c)
729 return self.node(c)
727
730
728 def _match(self, id):
731 def _match(self, id):
729 if isinstance(id, int):
732 if isinstance(id, int):
730 # rev
733 # rev
731 return self.node(id)
734 return self.node(id)
732 if len(id) == 20:
735 if len(id) == 20:
733 # possibly a binary node
736 # possibly a binary node
734 # odds of a binary node being all hex in ASCII are 1 in 10**25
737 # odds of a binary node being all hex in ASCII are 1 in 10**25
735 try:
738 try:
736 node = id
739 node = id
737 self.rev(node) # quick search the index
740 self.rev(node) # quick search the index
738 return node
741 return node
739 except LookupError:
742 except LookupError:
740 pass # may be partial hex id
743 pass # may be partial hex id
741 try:
744 try:
742 # str(rev)
745 # str(rev)
743 rev = int(id)
746 rev = int(id)
744 if str(rev) != id:
747 if str(rev) != id:
745 raise ValueError
748 raise ValueError
746 if rev < 0:
749 if rev < 0:
747 rev = len(self) + rev
750 rev = len(self) + rev
748 if rev < 0 or rev >= len(self):
751 if rev < 0 or rev >= len(self):
749 raise ValueError
752 raise ValueError
750 return self.node(rev)
753 return self.node(rev)
751 except (ValueError, OverflowError):
754 except (ValueError, OverflowError):
752 pass
755 pass
753 if len(id) == 40:
756 if len(id) == 40:
754 try:
757 try:
755 # a full hex nodeid?
758 # a full hex nodeid?
756 node = bin(id)
759 node = bin(id)
757 self.rev(node)
760 self.rev(node)
758 return node
761 return node
759 except (TypeError, LookupError):
762 except (TypeError, LookupError):
760 pass
763 pass
761
764
762 def _partialmatch(self, id):
765 def _partialmatch(self, id):
763 try:
766 try:
764 return self.index.partialmatch(id)
767 return self.index.partialmatch(id)
765 except RevlogError:
768 except RevlogError:
766 # parsers.c radix tree lookup gave multiple matches
769 # parsers.c radix tree lookup gave multiple matches
767 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
770 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
768 except (AttributeError, ValueError):
771 except (AttributeError, ValueError):
769 # we are pure python, or key was too short to search radix tree
772 # we are pure python, or key was too short to search radix tree
770 pass
773 pass
771
774
772 if id in self._pcache:
775 if id in self._pcache:
773 return self._pcache[id]
776 return self._pcache[id]
774
777
775 if len(id) < 40:
778 if len(id) < 40:
776 try:
779 try:
777 # hex(node)[:...]
780 # hex(node)[:...]
778 l = len(id) // 2 # grab an even number of digits
781 l = len(id) // 2 # grab an even number of digits
779 prefix = bin(id[:l * 2])
782 prefix = bin(id[:l * 2])
780 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
783 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
781 nl = [n for n in nl if hex(n).startswith(id)]
784 nl = [n for n in nl if hex(n).startswith(id)]
782 if len(nl) > 0:
785 if len(nl) > 0:
783 if len(nl) == 1:
786 if len(nl) == 1:
784 self._pcache[id] = nl[0]
787 self._pcache[id] = nl[0]
785 return nl[0]
788 return nl[0]
786 raise LookupError(id, self.indexfile,
789 raise LookupError(id, self.indexfile,
787 _('ambiguous identifier'))
790 _('ambiguous identifier'))
788 return None
791 return None
789 except TypeError:
792 except TypeError:
790 pass
793 pass
791
794
792 def lookup(self, id):
795 def lookup(self, id):
793 """locate a node based on:
796 """locate a node based on:
794 - revision number or str(revision number)
797 - revision number or str(revision number)
795 - nodeid or subset of hex nodeid
798 - nodeid or subset of hex nodeid
796 """
799 """
797 n = self._match(id)
800 n = self._match(id)
798 if n is not None:
801 if n is not None:
799 return n
802 return n
800 n = self._partialmatch(id)
803 n = self._partialmatch(id)
801 if n:
804 if n:
802 return n
805 return n
803
806
804 raise LookupError(id, self.indexfile, _('no match found'))
807 raise LookupError(id, self.indexfile, _('no match found'))
805
808
806 def cmp(self, node, text):
809 def cmp(self, node, text):
807 """compare text with a given file revision
810 """compare text with a given file revision
808
811
809 returns True if text is different than what is stored.
812 returns True if text is different than what is stored.
810 """
813 """
811 p1, p2 = self.parents(node)
814 p1, p2 = self.parents(node)
812 return hash(text, p1, p2) != node
815 return hash(text, p1, p2) != node
813
816
814 def _addchunk(self, offset, data):
817 def _addchunk(self, offset, data):
815 o, d = self._chunkcache
818 o, d = self._chunkcache
816 # try to add to existing cache
819 # try to add to existing cache
817 if o + len(d) == offset and len(d) + len(data) < _chunksize:
820 if o + len(d) == offset and len(d) + len(data) < _chunksize:
818 self._chunkcache = o, d + data
821 self._chunkcache = o, d + data
819 else:
822 else:
820 self._chunkcache = offset, data
823 self._chunkcache = offset, data
821
824
822 def _loadchunk(self, offset, length):
825 def _loadchunk(self, offset, length):
823 if self._inline:
826 if self._inline:
824 df = self.opener(self.indexfile)
827 df = self.opener(self.indexfile)
825 else:
828 else:
826 df = self.opener(self.datafile)
829 df = self.opener(self.datafile)
827
830
828 readahead = max(65536, length)
831 readahead = max(65536, length)
829 df.seek(offset)
832 df.seek(offset)
830 d = df.read(readahead)
833 d = df.read(readahead)
831 df.close()
834 df.close()
832 self._addchunk(offset, d)
835 self._addchunk(offset, d)
833 if readahead > length:
836 if readahead > length:
834 return util.buffer(d, 0, length)
837 return util.buffer(d, 0, length)
835 return d
838 return d
836
839
837 def _getchunk(self, offset, length):
840 def _getchunk(self, offset, length):
838 o, d = self._chunkcache
841 o, d = self._chunkcache
839 l = len(d)
842 l = len(d)
840
843
841 # is it in the cache?
844 # is it in the cache?
842 cachestart = offset - o
845 cachestart = offset - o
843 cacheend = cachestart + length
846 cacheend = cachestart + length
844 if cachestart >= 0 and cacheend <= l:
847 if cachestart >= 0 and cacheend <= l:
845 if cachestart == 0 and cacheend == l:
848 if cachestart == 0 and cacheend == l:
846 return d # avoid a copy
849 return d # avoid a copy
847 return util.buffer(d, cachestart, cacheend - cachestart)
850 return util.buffer(d, cachestart, cacheend - cachestart)
848
851
849 return self._loadchunk(offset, length)
852 return self._loadchunk(offset, length)
850
853
851 def _chunkraw(self, startrev, endrev):
854 def _chunkraw(self, startrev, endrev):
852 start = self.start(startrev)
855 start = self.start(startrev)
853 length = self.end(endrev) - start
856 length = self.end(endrev) - start
854 if self._inline:
857 if self._inline:
855 start += (startrev + 1) * self._io.size
858 start += (startrev + 1) * self._io.size
856 return self._getchunk(start, length)
859 return self._getchunk(start, length)
857
860
858 def _chunk(self, rev):
861 def _chunk(self, rev):
859 return decompress(self._chunkraw(rev, rev))
862 return decompress(self._chunkraw(rev, rev))
860
863
861 def _chunkbase(self, rev):
864 def _chunkbase(self, rev):
862 return self._chunk(rev)
865 return self._chunk(rev)
863
866
864 def _chunkclear(self):
867 def _chunkclear(self):
865 self._chunkcache = (0, '')
868 self._chunkcache = (0, '')
866
869
867 def deltaparent(self, rev):
870 def deltaparent(self, rev):
868 """return deltaparent of the given revision"""
871 """return deltaparent of the given revision"""
869 base = self.index[rev][3]
872 base = self.index[rev][3]
870 if base == rev:
873 if base == rev:
871 return nullrev
874 return nullrev
872 elif self._generaldelta:
875 elif self._generaldelta:
873 return base
876 return base
874 else:
877 else:
875 return rev - 1
878 return rev - 1
876
879
877 def revdiff(self, rev1, rev2):
880 def revdiff(self, rev1, rev2):
878 """return or calculate a delta between two revisions"""
881 """return or calculate a delta between two revisions"""
879 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
882 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
880 return str(self._chunk(rev2))
883 return str(self._chunk(rev2))
881
884
882 return mdiff.textdiff(self.revision(rev1),
885 return mdiff.textdiff(self.revision(rev1),
883 self.revision(rev2))
886 self.revision(rev2))
884
887
885 def revision(self, nodeorrev):
888 def revision(self, nodeorrev):
886 """return an uncompressed revision of a given node or revision
889 """return an uncompressed revision of a given node or revision
887 number.
890 number.
888 """
891 """
889 if isinstance(nodeorrev, int):
892 if isinstance(nodeorrev, int):
890 rev = nodeorrev
893 rev = nodeorrev
891 node = self.node(rev)
894 node = self.node(rev)
892 else:
895 else:
893 node = nodeorrev
896 node = nodeorrev
894 rev = None
897 rev = None
895
898
896 cachedrev = None
899 cachedrev = None
897 if node == nullid:
900 if node == nullid:
898 return ""
901 return ""
899 if self._cache:
902 if self._cache:
900 if self._cache[0] == node:
903 if self._cache[0] == node:
901 return self._cache[2]
904 return self._cache[2]
902 cachedrev = self._cache[1]
905 cachedrev = self._cache[1]
903
906
904 # look up what we need to read
907 # look up what we need to read
905 text = None
908 text = None
906 if rev is None:
909 if rev is None:
907 rev = self.rev(node)
910 rev = self.rev(node)
908
911
909 # check rev flags
912 # check rev flags
910 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
913 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
911 raise RevlogError(_('incompatible revision flag %x') %
914 raise RevlogError(_('incompatible revision flag %x') %
912 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
915 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
913
916
914 # build delta chain
917 # build delta chain
915 chain = []
918 chain = []
916 index = self.index # for performance
919 index = self.index # for performance
917 generaldelta = self._generaldelta
920 generaldelta = self._generaldelta
918 iterrev = rev
921 iterrev = rev
919 e = index[iterrev]
922 e = index[iterrev]
920 while iterrev != e[3] and iterrev != cachedrev:
923 while iterrev != e[3] and iterrev != cachedrev:
921 chain.append(iterrev)
924 chain.append(iterrev)
922 if generaldelta:
925 if generaldelta:
923 iterrev = e[3]
926 iterrev = e[3]
924 else:
927 else:
925 iterrev -= 1
928 iterrev -= 1
926 e = index[iterrev]
929 e = index[iterrev]
927 chain.reverse()
930 chain.reverse()
928 base = iterrev
931 base = iterrev
929
932
930 if iterrev == cachedrev:
933 if iterrev == cachedrev:
931 # cache hit
934 # cache hit
932 text = self._cache[2]
935 text = self._cache[2]
933
936
934 # drop cache to save memory
937 # drop cache to save memory
935 self._cache = None
938 self._cache = None
936
939
937 self._chunkraw(base, rev)
940 self._chunkraw(base, rev)
938 if text is None:
941 if text is None:
939 text = str(self._chunkbase(base))
942 text = str(self._chunkbase(base))
940
943
941 bins = [self._chunk(r) for r in chain]
944 bins = [self._chunk(r) for r in chain]
942 text = mdiff.patches(text, bins)
945 text = mdiff.patches(text, bins)
943
946
944 text = self._checkhash(text, node, rev)
947 text = self._checkhash(text, node, rev)
945
948
946 self._cache = (node, rev, text)
949 self._cache = (node, rev, text)
947 return text
950 return text
948
951
949 def _checkhash(self, text, node, rev):
952 def _checkhash(self, text, node, rev):
950 p1, p2 = self.parents(node)
953 p1, p2 = self.parents(node)
951 if node != hash(text, p1, p2):
954 if node != hash(text, p1, p2):
952 raise RevlogError(_("integrity check failed on %s:%d")
955 raise RevlogError(_("integrity check failed on %s:%d")
953 % (self.indexfile, rev))
956 % (self.indexfile, rev))
954 return text
957 return text
955
958
956 def checkinlinesize(self, tr, fp=None):
959 def checkinlinesize(self, tr, fp=None):
957 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
960 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
958 return
961 return
959
962
960 trinfo = tr.find(self.indexfile)
963 trinfo = tr.find(self.indexfile)
961 if trinfo is None:
964 if trinfo is None:
962 raise RevlogError(_("%s not found in the transaction")
965 raise RevlogError(_("%s not found in the transaction")
963 % self.indexfile)
966 % self.indexfile)
964
967
965 trindex = trinfo[2]
968 trindex = trinfo[2]
966 dataoff = self.start(trindex)
969 dataoff = self.start(trindex)
967
970
968 tr.add(self.datafile, dataoff)
971 tr.add(self.datafile, dataoff)
969
972
970 if fp:
973 if fp:
971 fp.flush()
974 fp.flush()
972 fp.close()
975 fp.close()
973
976
974 df = self.opener(self.datafile, 'w')
977 df = self.opener(self.datafile, 'w')
975 try:
978 try:
976 for r in self:
979 for r in self:
977 df.write(self._chunkraw(r, r))
980 df.write(self._chunkraw(r, r))
978 finally:
981 finally:
979 df.close()
982 df.close()
980
983
981 fp = self.opener(self.indexfile, 'w', atomictemp=True)
984 fp = self.opener(self.indexfile, 'w', atomictemp=True)
982 self.version &= ~(REVLOGNGINLINEDATA)
985 self.version &= ~(REVLOGNGINLINEDATA)
983 self._inline = False
986 self._inline = False
984 for i in self:
987 for i in self:
985 e = self._io.packentry(self.index[i], self.node, self.version, i)
988 e = self._io.packentry(self.index[i], self.node, self.version, i)
986 fp.write(e)
989 fp.write(e)
987
990
988 # if we don't call close, the temp file will never replace the
991 # if we don't call close, the temp file will never replace the
989 # real index
992 # real index
990 fp.close()
993 fp.close()
991
994
992 tr.replace(self.indexfile, trindex * self._io.size)
995 tr.replace(self.indexfile, trindex * self._io.size)
993 self._chunkclear()
996 self._chunkclear()
994
997
995 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
998 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
996 """add a revision to the log
999 """add a revision to the log
997
1000
998 text - the revision data to add
1001 text - the revision data to add
999 transaction - the transaction object used for rollback
1002 transaction - the transaction object used for rollback
1000 link - the linkrev data to add
1003 link - the linkrev data to add
1001 p1, p2 - the parent nodeids of the revision
1004 p1, p2 - the parent nodeids of the revision
1002 cachedelta - an optional precomputed delta
1005 cachedelta - an optional precomputed delta
1003 """
1006 """
1004 node = hash(text, p1, p2)
1007 node = hash(text, p1, p2)
1005 if node in self.nodemap:
1008 if node in self.nodemap:
1006 return node
1009 return node
1007
1010
1008 dfh = None
1011 dfh = None
1009 if not self._inline:
1012 if not self._inline:
1010 dfh = self.opener(self.datafile, "a")
1013 dfh = self.opener(self.datafile, "a")
1011 ifh = self.opener(self.indexfile, "a+")
1014 ifh = self.opener(self.indexfile, "a+")
1012 try:
1015 try:
1013 return self._addrevision(node, text, transaction, link, p1, p2,
1016 return self._addrevision(node, text, transaction, link, p1, p2,
1014 cachedelta, ifh, dfh)
1017 cachedelta, ifh, dfh)
1015 finally:
1018 finally:
1016 if dfh:
1019 if dfh:
1017 dfh.close()
1020 dfh.close()
1018 ifh.close()
1021 ifh.close()
1019
1022
1020 def _addrevision(self, node, text, transaction, link, p1, p2,
1023 def _addrevision(self, node, text, transaction, link, p1, p2,
1021 cachedelta, ifh, dfh):
1024 cachedelta, ifh, dfh):
1022 """internal function to add revisions to the log
1025 """internal function to add revisions to the log
1023
1026
1024 see addrevision for argument descriptions.
1027 see addrevision for argument descriptions.
1025 invariants:
1028 invariants:
1026 - text is optional (can be None); if not set, cachedelta must be set.
1029 - text is optional (can be None); if not set, cachedelta must be set.
1027 if both are set, they must correspond to eachother.
1030 if both are set, they must correspond to eachother.
1028 """
1031 """
1029 btext = [text]
1032 btext = [text]
1030 def buildtext():
1033 def buildtext():
1031 if btext[0] is not None:
1034 if btext[0] is not None:
1032 return btext[0]
1035 return btext[0]
1033 # flush any pending writes here so we can read it in revision
1036 # flush any pending writes here so we can read it in revision
1034 if dfh:
1037 if dfh:
1035 dfh.flush()
1038 dfh.flush()
1036 ifh.flush()
1039 ifh.flush()
1037 basetext = self.revision(self.node(cachedelta[0]))
1040 basetext = self.revision(self.node(cachedelta[0]))
1038 btext[0] = mdiff.patch(basetext, cachedelta[1])
1041 btext[0] = mdiff.patch(basetext, cachedelta[1])
1039 chk = hash(btext[0], p1, p2)
1042 chk = hash(btext[0], p1, p2)
1040 if chk != node:
1043 if chk != node:
1041 raise RevlogError(_("consistency error in delta"))
1044 raise RevlogError(_("consistency error in delta"))
1042 return btext[0]
1045 return btext[0]
1043
1046
1044 def builddelta(rev):
1047 def builddelta(rev):
1045 # can we use the cached delta?
1048 # can we use the cached delta?
1046 if cachedelta and cachedelta[0] == rev:
1049 if cachedelta and cachedelta[0] == rev:
1047 delta = cachedelta[1]
1050 delta = cachedelta[1]
1048 else:
1051 else:
1049 t = buildtext()
1052 t = buildtext()
1050 ptext = self.revision(self.node(rev))
1053 ptext = self.revision(self.node(rev))
1051 delta = mdiff.textdiff(ptext, t)
1054 delta = mdiff.textdiff(ptext, t)
1052 data = compress(delta)
1055 data = compress(delta)
1053 l = len(data[1]) + len(data[0])
1056 l = len(data[1]) + len(data[0])
1054 if basecache[0] == rev:
1057 if basecache[0] == rev:
1055 chainbase = basecache[1]
1058 chainbase = basecache[1]
1056 else:
1059 else:
1057 chainbase = self.chainbase(rev)
1060 chainbase = self.chainbase(rev)
1058 dist = l + offset - self.start(chainbase)
1061 dist = l + offset - self.start(chainbase)
1059 if self._generaldelta:
1062 if self._generaldelta:
1060 base = rev
1063 base = rev
1061 else:
1064 else:
1062 base = chainbase
1065 base = chainbase
1063 return dist, l, data, base, chainbase
1066 return dist, l, data, base, chainbase
1064
1067
1065 curr = len(self)
1068 curr = len(self)
1066 prev = curr - 1
1069 prev = curr - 1
1067 base = chainbase = curr
1070 base = chainbase = curr
1068 offset = self.end(prev)
1071 offset = self.end(prev)
1069 flags = 0
1072 flags = 0
1070 d = None
1073 d = None
1071 basecache = self._basecache
1074 basecache = self._basecache
1072 p1r, p2r = self.rev(p1), self.rev(p2)
1075 p1r, p2r = self.rev(p1), self.rev(p2)
1073
1076
1074 # should we try to build a delta?
1077 # should we try to build a delta?
1075 if prev != nullrev:
1078 if prev != nullrev:
1076 if self._generaldelta:
1079 if self._generaldelta:
1077 if p1r >= basecache[1]:
1080 if p1r >= basecache[1]:
1078 d = builddelta(p1r)
1081 d = builddelta(p1r)
1079 elif p2r >= basecache[1]:
1082 elif p2r >= basecache[1]:
1080 d = builddelta(p2r)
1083 d = builddelta(p2r)
1081 else:
1084 else:
1082 d = builddelta(prev)
1085 d = builddelta(prev)
1083 else:
1086 else:
1084 d = builddelta(prev)
1087 d = builddelta(prev)
1085 dist, l, data, base, chainbase = d
1088 dist, l, data, base, chainbase = d
1086
1089
1087 # full versions are inserted when the needed deltas
1090 # full versions are inserted when the needed deltas
1088 # become comparable to the uncompressed text
1091 # become comparable to the uncompressed text
1089 if text is None:
1092 if text is None:
1090 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1093 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1091 cachedelta[1])
1094 cachedelta[1])
1092 else:
1095 else:
1093 textlen = len(text)
1096 textlen = len(text)
1094 if d is None or dist > textlen * 2:
1097 if d is None or dist > textlen * 2:
1095 text = buildtext()
1098 text = buildtext()
1096 data = compress(text)
1099 data = compress(text)
1097 l = len(data[1]) + len(data[0])
1100 l = len(data[1]) + len(data[0])
1098 base = chainbase = curr
1101 base = chainbase = curr
1099
1102
1100 e = (offset_type(offset, flags), l, textlen,
1103 e = (offset_type(offset, flags), l, textlen,
1101 base, link, p1r, p2r, node)
1104 base, link, p1r, p2r, node)
1102 self.index.insert(-1, e)
1105 self.index.insert(-1, e)
1103 self.nodemap[node] = curr
1106 self.nodemap[node] = curr
1104
1107
1105 entry = self._io.packentry(e, self.node, self.version, curr)
1108 entry = self._io.packentry(e, self.node, self.version, curr)
1106 if not self._inline:
1109 if not self._inline:
1107 transaction.add(self.datafile, offset)
1110 transaction.add(self.datafile, offset)
1108 transaction.add(self.indexfile, curr * len(entry))
1111 transaction.add(self.indexfile, curr * len(entry))
1109 if data[0]:
1112 if data[0]:
1110 dfh.write(data[0])
1113 dfh.write(data[0])
1111 dfh.write(data[1])
1114 dfh.write(data[1])
1112 dfh.flush()
1115 dfh.flush()
1113 ifh.write(entry)
1116 ifh.write(entry)
1114 else:
1117 else:
1115 offset += curr * self._io.size
1118 offset += curr * self._io.size
1116 transaction.add(self.indexfile, offset, curr)
1119 transaction.add(self.indexfile, offset, curr)
1117 ifh.write(entry)
1120 ifh.write(entry)
1118 ifh.write(data[0])
1121 ifh.write(data[0])
1119 ifh.write(data[1])
1122 ifh.write(data[1])
1120 self.checkinlinesize(transaction, ifh)
1123 self.checkinlinesize(transaction, ifh)
1121
1124
1122 if type(text) == str: # only accept immutable objects
1125 if type(text) == str: # only accept immutable objects
1123 self._cache = (node, curr, text)
1126 self._cache = (node, curr, text)
1124 self._basecache = (curr, chainbase)
1127 self._basecache = (curr, chainbase)
1125 return node
1128 return node
1126
1129
1127 def group(self, nodelist, bundler, reorder=None):
1130 def group(self, nodelist, bundler, reorder=None):
1128 """Calculate a delta group, yielding a sequence of changegroup chunks
1131 """Calculate a delta group, yielding a sequence of changegroup chunks
1129 (strings).
1132 (strings).
1130
1133
1131 Given a list of changeset revs, return a set of deltas and
1134 Given a list of changeset revs, return a set of deltas and
1132 metadata corresponding to nodes. The first delta is
1135 metadata corresponding to nodes. The first delta is
1133 first parent(nodelist[0]) -> nodelist[0], the receiver is
1136 first parent(nodelist[0]) -> nodelist[0], the receiver is
1134 guaranteed to have this parent as it has all history before
1137 guaranteed to have this parent as it has all history before
1135 these changesets. In the case firstparent is nullrev the
1138 these changesets. In the case firstparent is nullrev the
1136 changegroup starts with a full revision.
1139 changegroup starts with a full revision.
1137 """
1140 """
1138
1141
1139 # if we don't have any revisions touched by these changesets, bail
1142 # if we don't have any revisions touched by these changesets, bail
1140 if len(nodelist) == 0:
1143 if len(nodelist) == 0:
1141 yield bundler.close()
1144 yield bundler.close()
1142 return
1145 return
1143
1146
1144 # for generaldelta revlogs, we linearize the revs; this will both be
1147 # for generaldelta revlogs, we linearize the revs; this will both be
1145 # much quicker and generate a much smaller bundle
1148 # much quicker and generate a much smaller bundle
1146 if (self._generaldelta and reorder is not False) or reorder:
1149 if (self._generaldelta and reorder is not False) or reorder:
1147 dag = dagutil.revlogdag(self)
1150 dag = dagutil.revlogdag(self)
1148 revs = set(self.rev(n) for n in nodelist)
1151 revs = set(self.rev(n) for n in nodelist)
1149 revs = dag.linearize(revs)
1152 revs = dag.linearize(revs)
1150 else:
1153 else:
1151 revs = sorted([self.rev(n) for n in nodelist])
1154 revs = sorted([self.rev(n) for n in nodelist])
1152
1155
1153 # add the parent of the first rev
1156 # add the parent of the first rev
1154 p = self.parentrevs(revs[0])[0]
1157 p = self.parentrevs(revs[0])[0]
1155 revs.insert(0, p)
1158 revs.insert(0, p)
1156
1159
1157 # build deltas
1160 # build deltas
1158 for r in xrange(len(revs) - 1):
1161 for r in xrange(len(revs) - 1):
1159 prev, curr = revs[r], revs[r + 1]
1162 prev, curr = revs[r], revs[r + 1]
1160 for c in bundler.revchunk(self, curr, prev):
1163 for c in bundler.revchunk(self, curr, prev):
1161 yield c
1164 yield c
1162
1165
1163 yield bundler.close()
1166 yield bundler.close()
1164
1167
1165 def addgroup(self, bundle, linkmapper, transaction):
1168 def addgroup(self, bundle, linkmapper, transaction):
1166 """
1169 """
1167 add a delta group
1170 add a delta group
1168
1171
1169 given a set of deltas, add them to the revision log. the
1172 given a set of deltas, add them to the revision log. the
1170 first delta is against its parent, which should be in our
1173 first delta is against its parent, which should be in our
1171 log, the rest are against the previous delta.
1174 log, the rest are against the previous delta.
1172 """
1175 """
1173
1176
1174 # track the base of the current delta log
1177 # track the base of the current delta log
1175 content = []
1178 content = []
1176 node = None
1179 node = None
1177
1180
1178 r = len(self)
1181 r = len(self)
1179 end = 0
1182 end = 0
1180 if r:
1183 if r:
1181 end = self.end(r - 1)
1184 end = self.end(r - 1)
1182 ifh = self.opener(self.indexfile, "a+")
1185 ifh = self.opener(self.indexfile, "a+")
1183 isize = r * self._io.size
1186 isize = r * self._io.size
1184 if self._inline:
1187 if self._inline:
1185 transaction.add(self.indexfile, end + isize, r)
1188 transaction.add(self.indexfile, end + isize, r)
1186 dfh = None
1189 dfh = None
1187 else:
1190 else:
1188 transaction.add(self.indexfile, isize, r)
1191 transaction.add(self.indexfile, isize, r)
1189 transaction.add(self.datafile, end)
1192 transaction.add(self.datafile, end)
1190 dfh = self.opener(self.datafile, "a")
1193 dfh = self.opener(self.datafile, "a")
1191
1194
1192 try:
1195 try:
1193 # loop through our set of deltas
1196 # loop through our set of deltas
1194 chain = None
1197 chain = None
1195 while True:
1198 while True:
1196 chunkdata = bundle.deltachunk(chain)
1199 chunkdata = bundle.deltachunk(chain)
1197 if not chunkdata:
1200 if not chunkdata:
1198 break
1201 break
1199 node = chunkdata['node']
1202 node = chunkdata['node']
1200 p1 = chunkdata['p1']
1203 p1 = chunkdata['p1']
1201 p2 = chunkdata['p2']
1204 p2 = chunkdata['p2']
1202 cs = chunkdata['cs']
1205 cs = chunkdata['cs']
1203 deltabase = chunkdata['deltabase']
1206 deltabase = chunkdata['deltabase']
1204 delta = chunkdata['delta']
1207 delta = chunkdata['delta']
1205
1208
1206 content.append(node)
1209 content.append(node)
1207
1210
1208 link = linkmapper(cs)
1211 link = linkmapper(cs)
1209 if node in self.nodemap:
1212 if node in self.nodemap:
1210 # this can happen if two branches make the same change
1213 # this can happen if two branches make the same change
1211 chain = node
1214 chain = node
1212 continue
1215 continue
1213
1216
1214 for p in (p1, p2):
1217 for p in (p1, p2):
1215 if p not in self.nodemap:
1218 if p not in self.nodemap:
1216 raise LookupError(p, self.indexfile,
1219 raise LookupError(p, self.indexfile,
1217 _('unknown parent'))
1220 _('unknown parent'))
1218
1221
1219 if deltabase not in self.nodemap:
1222 if deltabase not in self.nodemap:
1220 raise LookupError(deltabase, self.indexfile,
1223 raise LookupError(deltabase, self.indexfile,
1221 _('unknown delta base'))
1224 _('unknown delta base'))
1222
1225
1223 baserev = self.rev(deltabase)
1226 baserev = self.rev(deltabase)
1224 chain = self._addrevision(node, None, transaction, link,
1227 chain = self._addrevision(node, None, transaction, link,
1225 p1, p2, (baserev, delta), ifh, dfh)
1228 p1, p2, (baserev, delta), ifh, dfh)
1226 if not dfh and not self._inline:
1229 if not dfh and not self._inline:
1227 # addrevision switched from inline to conventional
1230 # addrevision switched from inline to conventional
1228 # reopen the index
1231 # reopen the index
1229 ifh.close()
1232 ifh.close()
1230 dfh = self.opener(self.datafile, "a")
1233 dfh = self.opener(self.datafile, "a")
1231 ifh = self.opener(self.indexfile, "a")
1234 ifh = self.opener(self.indexfile, "a")
1232 finally:
1235 finally:
1233 if dfh:
1236 if dfh:
1234 dfh.close()
1237 dfh.close()
1235 ifh.close()
1238 ifh.close()
1236
1239
1237 return content
1240 return content
1238
1241
1239 def strip(self, minlink, transaction):
1242 def strip(self, minlink, transaction):
1240 """truncate the revlog on the first revision with a linkrev >= minlink
1243 """truncate the revlog on the first revision with a linkrev >= minlink
1241
1244
1242 This function is called when we're stripping revision minlink and
1245 This function is called when we're stripping revision minlink and
1243 its descendants from the repository.
1246 its descendants from the repository.
1244
1247
1245 We have to remove all revisions with linkrev >= minlink, because
1248 We have to remove all revisions with linkrev >= minlink, because
1246 the equivalent changelog revisions will be renumbered after the
1249 the equivalent changelog revisions will be renumbered after the
1247 strip.
1250 strip.
1248
1251
1249 So we truncate the revlog on the first of these revisions, and
1252 So we truncate the revlog on the first of these revisions, and
1250 trust that the caller has saved the revisions that shouldn't be
1253 trust that the caller has saved the revisions that shouldn't be
1251 removed and that it'll re-add them after this truncation.
1254 removed and that it'll re-add them after this truncation.
1252 """
1255 """
1253 if len(self) == 0:
1256 if len(self) == 0:
1254 return
1257 return
1255
1258
1256 for rev in self:
1259 for rev in self:
1257 if self.index[rev][4] >= minlink:
1260 if self.index[rev][4] >= minlink:
1258 break
1261 break
1259 else:
1262 else:
1260 return
1263 return
1261
1264
1262 # first truncate the files on disk
1265 # first truncate the files on disk
1263 end = self.start(rev)
1266 end = self.start(rev)
1264 if not self._inline:
1267 if not self._inline:
1265 transaction.add(self.datafile, end)
1268 transaction.add(self.datafile, end)
1266 end = rev * self._io.size
1269 end = rev * self._io.size
1267 else:
1270 else:
1268 end += rev * self._io.size
1271 end += rev * self._io.size
1269
1272
1270 transaction.add(self.indexfile, end)
1273 transaction.add(self.indexfile, end)
1271
1274
1272 # then reset internal state in memory to forget those revisions
1275 # then reset internal state in memory to forget those revisions
1273 self._cache = None
1276 self._cache = None
1274 self._chunkclear()
1277 self._chunkclear()
1275 for x in xrange(rev, len(self)):
1278 for x in xrange(rev, len(self)):
1276 del self.nodemap[self.node(x)]
1279 del self.nodemap[self.node(x)]
1277
1280
1278 del self.index[rev:-1]
1281 del self.index[rev:-1]
1279
1282
1280 def checksize(self):
1283 def checksize(self):
1281 expected = 0
1284 expected = 0
1282 if len(self):
1285 if len(self):
1283 expected = max(0, self.end(len(self) - 1))
1286 expected = max(0, self.end(len(self) - 1))
1284
1287
1285 try:
1288 try:
1286 f = self.opener(self.datafile)
1289 f = self.opener(self.datafile)
1287 f.seek(0, 2)
1290 f.seek(0, 2)
1288 actual = f.tell()
1291 actual = f.tell()
1289 f.close()
1292 f.close()
1290 dd = actual - expected
1293 dd = actual - expected
1291 except IOError, inst:
1294 except IOError, inst:
1292 if inst.errno != errno.ENOENT:
1295 if inst.errno != errno.ENOENT:
1293 raise
1296 raise
1294 dd = 0
1297 dd = 0
1295
1298
1296 try:
1299 try:
1297 f = self.opener(self.indexfile)
1300 f = self.opener(self.indexfile)
1298 f.seek(0, 2)
1301 f.seek(0, 2)
1299 actual = f.tell()
1302 actual = f.tell()
1300 f.close()
1303 f.close()
1301 s = self._io.size
1304 s = self._io.size
1302 i = max(0, actual // s)
1305 i = max(0, actual // s)
1303 di = actual - (i * s)
1306 di = actual - (i * s)
1304 if self._inline:
1307 if self._inline:
1305 databytes = 0
1308 databytes = 0
1306 for r in self:
1309 for r in self:
1307 databytes += max(0, self.length(r))
1310 databytes += max(0, self.length(r))
1308 dd = 0
1311 dd = 0
1309 di = actual - len(self) * s - databytes
1312 di = actual - len(self) * s - databytes
1310 except IOError, inst:
1313 except IOError, inst:
1311 if inst.errno != errno.ENOENT:
1314 if inst.errno != errno.ENOENT:
1312 raise
1315 raise
1313 di = 0
1316 di = 0
1314
1317
1315 return (dd, di)
1318 return (dd, di)
1316
1319
1317 def files(self):
1320 def files(self):
1318 res = [self.indexfile]
1321 res = [self.indexfile]
1319 if not self._inline:
1322 if not self._inline:
1320 res.append(self.datafile)
1323 res.append(self.datafile)
1321 return res
1324 return res
@@ -1,73 +1,77 b''
1 import os
1 import os
2 from mercurial import hg, ui, merge
2 from mercurial import hg, ui, merge
3
3
4 u = ui.ui()
4 u = ui.ui()
5
5
6 repo = hg.repository(u, 'test1', create=1)
6 repo = hg.repository(u, 'test1', create=1)
7 os.chdir('test1')
7 os.chdir('test1')
8
8
9 def commit(text, time):
9 def commit(text, time):
10 repo.commit(text=text, date="%d 0" % time)
10 repo.commit(text=text, date="%d 0" % time)
11
11
12 def addcommit(name, time):
12 def addcommit(name, time):
13 f = open(name, 'w')
13 f = open(name, 'w')
14 f.write('%s\n' % name)
14 f.write('%s\n' % name)
15 f.close()
15 f.close()
16 repo[None].add([name])
16 repo[None].add([name])
17 commit(name, time)
17 commit(name, time)
18
18
19 def update(rev):
19 def update(rev):
20 merge.update(repo, rev, False, True, False)
20 merge.update(repo, rev, False, True, False)
21
21
22 def merge_(rev):
22 def merge_(rev):
23 merge.update(repo, rev, True, False, False)
23 merge.update(repo, rev, True, False, False)
24
24
25 if __name__ == '__main__':
25 if __name__ == '__main__':
26 addcommit("A", 0)
26 addcommit("A", 0)
27 addcommit("B", 1)
27 addcommit("B", 1)
28
28
29 update(0)
29 update(0)
30 addcommit("C", 2)
30 addcommit("C", 2)
31
31
32 merge_(1)
32 merge_(1)
33 commit("D", 3)
33 commit("D", 3)
34
34
35 update(2)
35 update(2)
36 addcommit("E", 4)
36 addcommit("E", 4)
37 addcommit("F", 5)
37 addcommit("F", 5)
38
38
39 update(3)
39 update(3)
40 addcommit("G", 6)
40 addcommit("G", 6)
41
41
42 merge_(5)
42 merge_(5)
43 commit("H", 7)
43 commit("H", 7)
44
44
45 update(5)
45 update(5)
46 addcommit("I", 8)
46 addcommit("I", 8)
47
47
48 # Ancestors
48 # Ancestors
49 print 'Ancestors of 5'
49 print 'Ancestors of 5'
50 for r in repo.changelog.ancestors([5]):
50 for r in repo.changelog.ancestors([5]):
51 print r,
51 print r,
52
52
53 print '\nAncestors of 6 and 5'
53 print '\nAncestors of 6 and 5'
54 for r in repo.changelog.ancestors([6, 5]):
54 for r in repo.changelog.ancestors([6, 5]):
55 print r,
55 print r,
56
56
57 print '\nAncestors of 5 and 4'
57 print '\nAncestors of 5 and 4'
58 for r in repo.changelog.ancestors([5, 4]):
58 for r in repo.changelog.ancestors([5, 4]):
59 print r,
59 print r,
60
60
61 print '\nAncestors of 7, stop at 6'
62 for r in repo.changelog.ancestors([7], 6):
63 print r,
64
61 # Descendants
65 # Descendants
62 print '\n\nDescendants of 5'
66 print '\n\nDescendants of 5'
63 for r in repo.changelog.descendants([5]):
67 for r in repo.changelog.descendants([5]):
64 print r,
68 print r,
65
69
66 print '\nDescendants of 5 and 3'
70 print '\nDescendants of 5 and 3'
67 for r in repo.changelog.descendants([5, 3]):
71 for r in repo.changelog.descendants([5, 3]):
68 print r,
72 print r,
69
73
70 print '\nDescendants of 5 and 4'
74 print '\nDescendants of 5 and 4'
71 for r in repo.changelog.descendants([5, 4]):
75 for r in repo.changelog.descendants([5, 4]):
72 print r,
76 print r,
73
77
@@ -1,13 +1,15 b''
1 Ancestors of 5
1 Ancestors of 5
2 4 2 0
2 4 2 0
3 Ancestors of 6 and 5
3 Ancestors of 6 and 5
4 3 4 2 1 0
4 3 4 2 1 0
5 Ancestors of 5 and 4
5 Ancestors of 5 and 4
6 4 2 0
6 4 2 0
7 Ancestors of 7, stop at 6
8 6
7
9
8 Descendants of 5
10 Descendants of 5
9 7 8
11 7 8
10 Descendants of 5 and 3
12 Descendants of 5 and 3
11 6 7 8
13 6 7 8
12 Descendants of 5 and 4
14 Descendants of 5 and 4
13 5 7 8
15 5 7 8
General Comments 0
You need to be logged in to leave comments. Login now