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