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