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