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