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