##// END OF EJS Templates
bundlerepo: port to new bundle API
Benoit Boissinot -
r14142:cb91ea6a default
parent child Browse files
Show More
@@ -1,322 +1,319
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, struct, tempfile, shutil
16 import os, struct, tempfile, shutil
17 import changegroup, util, mdiff, discovery
17 import changegroup, util, mdiff, discovery
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,
21 def __init__(self, opener, indexfile, bundle, linkmapper):
22 linkmapper=None):
23 # How it works:
22 # How it works:
24 # to retrieve a revision, we need to know the offset of
23 # to retrieve a revision, we need to know the offset of
25 # the revision in the bundle (an unbundle object).
24 # the revision in the bundle (an unbundle object).
26 #
25 #
27 # We store this offset in the index (start), to differentiate a
26 # We store this offset in the index (start), to differentiate a
28 # 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
29 # 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
30 # (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)
31 #
30 #
32 revlog.revlog.__init__(self, opener, indexfile)
31 revlog.revlog.__init__(self, opener, indexfile)
33 self.bundle = bundle
32 self.bundle = bundle
34 self.basemap = {}
33 self.basemap = {}
35 def chunkpositer():
36 while 1:
37 chunk = bundle.chunk()
38 if not chunk:
39 break
40 pos = bundle.tell()
41 yield chunk, pos - len(chunk)
42 n = len(self)
34 n = len(self)
43 prev = None
35 chain = None
44 for chunk, start in chunkpositer():
36 while 1:
45 size = len(chunk)
37 chunkdata = bundle.parsechunk(chain)
46 if size < 80:
38 if not chunkdata:
47 raise util.Abort(_("invalid changegroup"))
39 break
48 start += 80
40 node = chunkdata['node']
49 size -= 80
41 p1 = chunkdata['p1']
50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
42 p2 = chunkdata['p2']
43 cs = chunkdata['cs']
44 deltabase = chunkdata['deltabase']
45 delta = chunkdata['delta']
46
47 size = len(delta)
48 start = bundle.tell() - size
49
50 link = linkmapper(cs)
51 if node in self.nodemap:
51 if node in self.nodemap:
52 prev = node
52 # this can happen if two branches make the same change
53 chain = node
53 continue
54 continue
55
54 for p in (p1, p2):
56 for p in (p1, p2):
55 if not p in self.nodemap:
57 if not p in self.nodemap:
56 raise error.LookupError(p, self.indexfile,
58 raise error.LookupError(p, self.indexfile,
57 _("unknown parent"))
59 _("unknown parent"))
58 if linkmapper is None:
59 link = n
60 else:
61 link = linkmapper(cs)
62
63 if not prev:
64 prev = p1
65 # start, size, full unc. size, base (unused), link, p1, p2, node
60 # start, size, full unc. size, base (unused), link, p1, p2, node
66 e = (revlog.offset_type(start, 0), size, -1, -1, link,
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
67 self.rev(p1), self.rev(p2), node)
62 self.rev(p1), self.rev(p2), node)
68 self.basemap[n] = prev
63 self.basemap[n] = deltabase
69 self.index.insert(-1, e)
64 self.index.insert(-1, e)
70 self.nodemap[node] = n
65 self.nodemap[node] = n
71 prev = node
66 chain = node
72 n += 1
67 n += 1
73
68
74 def inbundle(self, rev):
69 def inbundle(self, rev):
75 """is rev from the bundle"""
70 """is rev from the bundle"""
76 if rev < 0:
71 if rev < 0:
77 return False
72 return False
78 return rev in self.basemap
73 return rev in self.basemap
79 def bundlebase(self, rev):
74 def bundlebase(self, rev):
80 return self.basemap[rev]
75 return self.basemap[rev]
81 def _chunk(self, rev):
76 def _chunk(self, rev):
82 # Warning: in case of bundle, the diff is against bundlebase,
77 # Warning: in case of bundle, the diff is against bundlebase,
83 # not against rev - 1
78 # not against rev - 1
84 # XXX: could use some caching
79 # XXX: could use some caching
85 if not self.inbundle(rev):
80 if not self.inbundle(rev):
86 return revlog.revlog._chunk(self, rev)
81 return revlog.revlog._chunk(self, rev)
87 self.bundle.seek(self.start(rev))
82 self.bundle.seek(self.start(rev))
88 return self.bundle.read(self.length(rev))
83 return self.bundle.read(self.length(rev))
89
84
90 def revdiff(self, rev1, rev2):
85 def revdiff(self, rev1, rev2):
91 """return or calculate a delta between two revisions"""
86 """return or calculate a delta between two revisions"""
92 if self.inbundle(rev1) and self.inbundle(rev2):
87 if self.inbundle(rev1) and self.inbundle(rev2):
93 # hot path for bundle
88 # hot path for bundle
94 revb = self.rev(self.bundlebase(rev2))
89 revb = self.rev(self.bundlebase(rev2))
95 if revb == rev1:
90 if revb == rev1:
96 return self._chunk(rev2)
91 return self._chunk(rev2)
97 elif not self.inbundle(rev1) and not self.inbundle(rev2):
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
98 return revlog.revlog.revdiff(self, rev1, rev2)
93 return revlog.revlog.revdiff(self, rev1, rev2)
99
94
100 return mdiff.textdiff(self.revision(self.node(rev1)),
95 return mdiff.textdiff(self.revision(self.node(rev1)),
101 self.revision(self.node(rev2)))
96 self.revision(self.node(rev2)))
102
97
103 def revision(self, node):
98 def revision(self, node):
104 """return an uncompressed revision of a given"""
99 """return an uncompressed revision of a given"""
105 if node == nullid:
100 if node == nullid:
106 return ""
101 return ""
107
102
108 text = None
103 text = None
109 chain = []
104 chain = []
110 iter_node = node
105 iter_node = node
111 rev = self.rev(iter_node)
106 rev = self.rev(iter_node)
112 # reconstruct the revision if it is from a changegroup
107 # reconstruct the revision if it is from a changegroup
113 while self.inbundle(rev):
108 while self.inbundle(rev):
114 if self._cache and self._cache[0] == iter_node:
109 if self._cache and self._cache[0] == iter_node:
115 text = self._cache[2]
110 text = self._cache[2]
116 break
111 break
117 chain.append(rev)
112 chain.append(rev)
118 iter_node = self.bundlebase(rev)
113 iter_node = self.bundlebase(rev)
119 rev = self.rev(iter_node)
114 rev = self.rev(iter_node)
120 if text is None:
115 if text is None:
121 text = revlog.revlog.revision(self, iter_node)
116 text = revlog.revlog.revision(self, iter_node)
122
117
123 while chain:
118 while chain:
124 delta = self._chunk(chain.pop())
119 delta = self._chunk(chain.pop())
125 text = mdiff.patches(text, [delta])
120 text = mdiff.patches(text, [delta])
126
121
127 p1, p2 = self.parents(node)
122 p1, p2 = self.parents(node)
128 if node != revlog.hash(text, p1, p2):
123 if node != revlog.hash(text, p1, p2):
129 raise error.RevlogError(_("integrity check failed on %s:%d")
124 raise error.RevlogError(_("integrity check failed on %s:%d")
130 % (self.datafile, self.rev(node)))
125 % (self.datafile, self.rev(node)))
131
126
132 self._cache = (node, self.rev(node), text)
127 self._cache = (node, self.rev(node), text)
133 return text
128 return text
134
129
135 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
136 raise NotImplementedError
131 raise NotImplementedError
137 def addgroup(self, revs, linkmapper, transaction):
132 def addgroup(self, revs, linkmapper, transaction):
138 raise NotImplementedError
133 raise NotImplementedError
139 def strip(self, rev, minlink):
134 def strip(self, rev, minlink):
140 raise NotImplementedError
135 raise NotImplementedError
141 def checksize(self):
136 def checksize(self):
142 raise NotImplementedError
137 raise NotImplementedError
143
138
144 class bundlechangelog(bundlerevlog, changelog.changelog):
139 class bundlechangelog(bundlerevlog, changelog.changelog):
145 def __init__(self, opener, bundle):
140 def __init__(self, opener, bundle):
146 changelog.changelog.__init__(self, opener)
141 changelog.changelog.__init__(self, opener)
147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
142 linkmapper = lambda x: x
143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
144 linkmapper)
148
145
149 class bundlemanifest(bundlerevlog, manifest.manifest):
146 class bundlemanifest(bundlerevlog, manifest.manifest):
150 def __init__(self, opener, bundle, linkmapper):
147 def __init__(self, opener, bundle, linkmapper):
151 manifest.manifest.__init__(self, opener)
148 manifest.manifest.__init__(self, opener)
152 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
153 linkmapper)
150 linkmapper)
154
151
155 class bundlefilelog(bundlerevlog, filelog.filelog):
152 class bundlefilelog(bundlerevlog, filelog.filelog):
156 def __init__(self, opener, path, bundle, linkmapper):
153 def __init__(self, opener, path, bundle, linkmapper):
157 filelog.filelog.__init__(self, opener, path)
154 filelog.filelog.__init__(self, opener, path)
158 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
159 linkmapper)
156 linkmapper)
160
157
161 class bundlerepository(localrepo.localrepository):
158 class bundlerepository(localrepo.localrepository):
162 def __init__(self, ui, path, bundlename):
159 def __init__(self, ui, path, bundlename):
163 self._tempparent = None
160 self._tempparent = None
164 try:
161 try:
165 localrepo.localrepository.__init__(self, ui, path)
162 localrepo.localrepository.__init__(self, ui, path)
166 except error.RepoError:
163 except error.RepoError:
167 self._tempparent = tempfile.mkdtemp()
164 self._tempparent = tempfile.mkdtemp()
168 localrepo.instance(ui, self._tempparent, 1)
165 localrepo.instance(ui, self._tempparent, 1)
169 localrepo.localrepository.__init__(self, ui, self._tempparent)
166 localrepo.localrepository.__init__(self, ui, self._tempparent)
170
167
171 if path:
168 if path:
172 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
169 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
173 else:
170 else:
174 self._url = 'bundle:' + bundlename
171 self._url = 'bundle:' + bundlename
175
172
176 self.tempfile = None
173 self.tempfile = None
177 f = util.posixfile(bundlename, "rb")
174 f = util.posixfile(bundlename, "rb")
178 self.bundle = changegroup.readbundle(f, bundlename)
175 self.bundle = changegroup.readbundle(f, bundlename)
179 if self.bundle.compressed():
176 if self.bundle.compressed():
180 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
181 suffix=".hg10un", dir=self.path)
178 suffix=".hg10un", dir=self.path)
182 self.tempfile = temp
179 self.tempfile = temp
183 fptemp = os.fdopen(fdtemp, 'wb')
180 fptemp = os.fdopen(fdtemp, 'wb')
184
181
185 try:
182 try:
186 fptemp.write("HG10UN")
183 fptemp.write("HG10UN")
187 while 1:
184 while 1:
188 chunk = self.bundle.read(2**18)
185 chunk = self.bundle.read(2**18)
189 if not chunk:
186 if not chunk:
190 break
187 break
191 fptemp.write(chunk)
188 fptemp.write(chunk)
192 finally:
189 finally:
193 fptemp.close()
190 fptemp.close()
194
191
195 f = util.posixfile(self.tempfile, "rb")
192 f = util.posixfile(self.tempfile, "rb")
196 self.bundle = changegroup.readbundle(f, bundlename)
193 self.bundle = changegroup.readbundle(f, bundlename)
197
194
198 # dict with the mapping 'filename' -> position in the bundle
195 # dict with the mapping 'filename' -> position in the bundle
199 self.bundlefilespos = {}
196 self.bundlefilespos = {}
200
197
201 @util.propertycache
198 @util.propertycache
202 def changelog(self):
199 def changelog(self):
203 c = bundlechangelog(self.sopener, self.bundle)
200 c = bundlechangelog(self.sopener, self.bundle)
204 self.manstart = self.bundle.tell()
201 self.manstart = self.bundle.tell()
205 return c
202 return c
206
203
207 @util.propertycache
204 @util.propertycache
208 def manifest(self):
205 def manifest(self):
209 self.bundle.seek(self.manstart)
206 self.bundle.seek(self.manstart)
210 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
207 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
211 self.filestart = self.bundle.tell()
208 self.filestart = self.bundle.tell()
212 return m
209 return m
213
210
214 @util.propertycache
211 @util.propertycache
215 def manstart(self):
212 def manstart(self):
216 self.changelog
213 self.changelog
217 return self.manstart
214 return self.manstart
218
215
219 @util.propertycache
216 @util.propertycache
220 def filestart(self):
217 def filestart(self):
221 self.manifest
218 self.manifest
222 return self.filestart
219 return self.filestart
223
220
224 def url(self):
221 def url(self):
225 return self._url
222 return self._url
226
223
227 def file(self, f):
224 def file(self, f):
228 if not self.bundlefilespos:
225 if not self.bundlefilespos:
229 self.bundle.seek(self.filestart)
226 self.bundle.seek(self.filestart)
230 while 1:
227 while 1:
231 chunk = self.bundle.chunk()
228 chunk = self.bundle.chunk()
232 if not chunk:
229 if not chunk:
233 break
230 break
234 self.bundlefilespos[chunk] = self.bundle.tell()
231 self.bundlefilespos[chunk] = self.bundle.tell()
235 while 1:
232 while 1:
236 c = self.bundle.chunk()
233 c = self.bundle.chunk()
237 if not c:
234 if not c:
238 break
235 break
239
236
240 if f[0] == '/':
237 if f[0] == '/':
241 f = f[1:]
238 f = f[1:]
242 if f in self.bundlefilespos:
239 if f in self.bundlefilespos:
243 self.bundle.seek(self.bundlefilespos[f])
240 self.bundle.seek(self.bundlefilespos[f])
244 return bundlefilelog(self.sopener, f, self.bundle,
241 return bundlefilelog(self.sopener, f, self.bundle,
245 self.changelog.rev)
242 self.changelog.rev)
246 else:
243 else:
247 return filelog.filelog(self.sopener, f)
244 return filelog.filelog(self.sopener, f)
248
245
249 def close(self):
246 def close(self):
250 """Close assigned bundle file immediately."""
247 """Close assigned bundle file immediately."""
251 self.bundle.close()
248 self.bundle.close()
252 if self.tempfile is not None:
249 if self.tempfile is not None:
253 os.unlink(self.tempfile)
250 os.unlink(self.tempfile)
254 if self._tempparent:
251 if self._tempparent:
255 shutil.rmtree(self._tempparent, True)
252 shutil.rmtree(self._tempparent, True)
256
253
257 def cancopy(self):
254 def cancopy(self):
258 return False
255 return False
259
256
260 def getcwd(self):
257 def getcwd(self):
261 return os.getcwd() # always outside the repo
258 return os.getcwd() # always outside the repo
262
259
263 def instance(ui, path, create):
260 def instance(ui, path, create):
264 if create:
261 if create:
265 raise util.Abort(_('cannot create new bundle repository'))
262 raise util.Abort(_('cannot create new bundle repository'))
266 parentpath = ui.config("bundle", "mainreporoot", "")
263 parentpath = ui.config("bundle", "mainreporoot", "")
267 if parentpath:
264 if parentpath:
268 # Try to make the full path relative so we get a nice, short URL.
265 # Try to make the full path relative so we get a nice, short URL.
269 # In particular, we don't want temp dir names in test outputs.
266 # In particular, we don't want temp dir names in test outputs.
270 cwd = os.getcwd()
267 cwd = os.getcwd()
271 if parentpath == cwd:
268 if parentpath == cwd:
272 parentpath = ''
269 parentpath = ''
273 else:
270 else:
274 cwd = os.path.join(cwd,'')
271 cwd = os.path.join(cwd,'')
275 if parentpath.startswith(cwd):
272 if parentpath.startswith(cwd):
276 parentpath = parentpath[len(cwd):]
273 parentpath = parentpath[len(cwd):]
277 u = util.url(path)
274 u = util.url(path)
278 path = u.localpath()
275 path = u.localpath()
279 if u.scheme == 'bundle':
276 if u.scheme == 'bundle':
280 s = path.split("+", 1)
277 s = path.split("+", 1)
281 if len(s) == 1:
278 if len(s) == 1:
282 repopath, bundlename = parentpath, s[0]
279 repopath, bundlename = parentpath, s[0]
283 else:
280 else:
284 repopath, bundlename = s
281 repopath, bundlename = s
285 else:
282 else:
286 repopath, bundlename = parentpath, path
283 repopath, bundlename = parentpath, path
287 return bundlerepository(ui, repopath, bundlename)
284 return bundlerepository(ui, repopath, bundlename)
288
285
289 def getremotechanges(ui, repo, other, revs=None, bundlename=None,
286 def getremotechanges(ui, repo, other, revs=None, bundlename=None,
290 force=False):
287 force=False):
291 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
288 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
292 common, incoming, rheads = tmp
289 common, incoming, rheads = tmp
293 if not incoming:
290 if not incoming:
294 try:
291 try:
295 os.unlink(bundlename)
292 os.unlink(bundlename)
296 except OSError:
293 except OSError:
297 pass
294 pass
298 return other, None, None, None
295 return other, None, None, None
299
296
300 bundle = None
297 bundle = None
301 if bundlename or not other.local():
298 if bundlename or not other.local():
302 # create a bundle (uncompressed if other repo is not local)
299 # create a bundle (uncompressed if other repo is not local)
303
300
304 if revs is None and other.capable('changegroupsubset'):
301 if revs is None and other.capable('changegroupsubset'):
305 revs = rheads
302 revs = rheads
306
303
307 if other.capable('getbundle'):
304 if other.capable('getbundle'):
308 cg = other.getbundle('incoming', common=common, heads=revs)
305 cg = other.getbundle('incoming', common=common, heads=revs)
309 elif revs is None:
306 elif revs is None:
310 cg = other.changegroup(incoming, "incoming")
307 cg = other.changegroup(incoming, "incoming")
311 else:
308 else:
312 cg = other.changegroupsubset(incoming, revs, 'incoming')
309 cg = other.changegroupsubset(incoming, revs, 'incoming')
313 bundletype = other.local() and "HG10BZ" or "HG10UN"
310 bundletype = other.local() and "HG10BZ" or "HG10UN"
314 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
311 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
315 # keep written bundle?
312 # keep written bundle?
316 if bundlename:
313 if bundlename:
317 bundle = None
314 bundle = None
318 if not other.local():
315 if not other.local():
319 # use the created uncompressed bundlerepo
316 # use the created uncompressed bundlerepo
320 other = bundlerepository(ui, repo.root, fname)
317 other = bundlerepository(ui, repo.root, fname)
321 return (other, common, incoming, bundle)
318 return (other, common, incoming, bundle)
322
319
General Comments 0
You need to be logged in to leave comments. Login now