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