##// END OF EJS Templates
add support for HG10GZ bundles to bundlerepo.bundlerevlog()
Benoit Allard -
r6569:c15bfe9c default
parent child Browse files
Show More
@@ -1,291 +1,294
1 """
1 """
2 bundlerepo.py - repository class for viewing uncompressed bundles
2 bundlerepo.py - repository class for viewing uncompressed bundles
3
3
4 This provides a read-only repository interface to bundles as if
4 This provides a read-only repository interface to bundles as if
5 they were part of the actual repository.
5 they were part of the actual repository.
6
6
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 from i18n import _
14 from i18n import _
15 import changegroup, util, os, struct, bz2, tempfile, shutil, mdiff
15 import changegroup, util, os, struct, bz2, zlib, tempfile, shutil, mdiff
16 import repo, localrepo, changelog, manifest, filelog, revlog
16 import repo, localrepo, changelog, manifest, filelog, revlog
17
17
18 class bundlerevlog(revlog.revlog):
18 class bundlerevlog(revlog.revlog):
19 def __init__(self, opener, indexfile, bundlefile,
19 def __init__(self, opener, indexfile, bundlefile,
20 linkmapper=None):
20 linkmapper=None):
21 # How it works:
21 # How it works:
22 # to retrieve a revision, we need to know the offset of
22 # to retrieve a revision, we need to know the offset of
23 # the revision in the bundlefile (an opened file).
23 # the revision in the bundlefile (an opened file).
24 #
24 #
25 # We store this offset in the index (start), to differentiate a
25 # We store this offset in the index (start), to differentiate a
26 # rev in the bundle and from a rev in the revlog, we check
26 # rev in the bundle and from a rev in the revlog, we check
27 # len(index[r]). If the tuple is bigger than 7, it is a bundle
27 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 # (it is bigger since we store the node to which the delta is)
28 # (it is bigger since we store the node to which the delta is)
29 #
29 #
30 revlog.revlog.__init__(self, opener, indexfile)
30 revlog.revlog.__init__(self, opener, indexfile)
31 self.bundlefile = bundlefile
31 self.bundlefile = bundlefile
32 self.basemap = {}
32 self.basemap = {}
33 def chunkpositer():
33 def chunkpositer():
34 for chunk in changegroup.chunkiter(bundlefile):
34 for chunk in changegroup.chunkiter(bundlefile):
35 pos = bundlefile.tell()
35 pos = bundlefile.tell()
36 yield chunk, pos - len(chunk)
36 yield chunk, pos - len(chunk)
37 n = self.count()
37 n = self.count()
38 prev = None
38 prev = None
39 for chunk, start in chunkpositer():
39 for chunk, start in chunkpositer():
40 size = len(chunk)
40 size = len(chunk)
41 if size < 80:
41 if size < 80:
42 raise util.Abort("invalid changegroup")
42 raise util.Abort("invalid changegroup")
43 start += 80
43 start += 80
44 size -= 80
44 size -= 80
45 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
45 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
46 if node in self.nodemap:
46 if node in self.nodemap:
47 prev = node
47 prev = node
48 continue
48 continue
49 for p in (p1, p2):
49 for p in (p1, p2):
50 if not p in self.nodemap:
50 if not p in self.nodemap:
51 raise revlog.LookupError(p1, self.indexfile,
51 raise revlog.LookupError(p1, self.indexfile,
52 _("unknown parent"))
52 _("unknown parent"))
53 if linkmapper is None:
53 if linkmapper is None:
54 link = n
54 link = n
55 else:
55 else:
56 link = linkmapper(cs)
56 link = linkmapper(cs)
57
57
58 if not prev:
58 if not prev:
59 prev = p1
59 prev = p1
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] = prev
63 self.basemap[n] = prev
64 self.index.insert(-1, e)
64 self.index.insert(-1, e)
65 self.nodemap[node] = n
65 self.nodemap[node] = n
66 prev = node
66 prev = node
67 n += 1
67 n += 1
68
68
69 def bundle(self, rev):
69 def bundle(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): return self.basemap[rev]
74 def bundlebase(self, rev): return self.basemap[rev]
75 def chunk(self, rev, df=None, cachelen=4096):
75 def chunk(self, rev, df=None, cachelen=4096):
76 # Warning: in case of bundle, the diff is against bundlebase,
76 # Warning: in case of bundle, the diff is against bundlebase,
77 # not against rev - 1
77 # not against rev - 1
78 # XXX: could use some caching
78 # XXX: could use some caching
79 if not self.bundle(rev):
79 if not self.bundle(rev):
80 return revlog.revlog.chunk(self, rev, df)
80 return revlog.revlog.chunk(self, rev, df)
81 self.bundlefile.seek(self.start(rev))
81 self.bundlefile.seek(self.start(rev))
82 return self.bundlefile.read(self.length(rev))
82 return self.bundlefile.read(self.length(rev))
83
83
84 def revdiff(self, rev1, rev2):
84 def revdiff(self, rev1, rev2):
85 """return or calculate a delta between two revisions"""
85 """return or calculate a delta between two revisions"""
86 if self.bundle(rev1) and self.bundle(rev2):
86 if self.bundle(rev1) and self.bundle(rev2):
87 # hot path for bundle
87 # hot path for bundle
88 revb = self.rev(self.bundlebase(rev2))
88 revb = self.rev(self.bundlebase(rev2))
89 if revb == rev1:
89 if revb == rev1:
90 return self.chunk(rev2)
90 return self.chunk(rev2)
91 elif not self.bundle(rev1) and not self.bundle(rev2):
91 elif not self.bundle(rev1) and not self.bundle(rev2):
92 return revlog.revlog.revdiff(self, rev1, rev2)
92 return revlog.revlog.revdiff(self, rev1, rev2)
93
93
94 return mdiff.textdiff(self.revision(self.node(rev1)),
94 return mdiff.textdiff(self.revision(self.node(rev1)),
95 self.revision(self.node(rev2)))
95 self.revision(self.node(rev2)))
96
96
97 def revision(self, node):
97 def revision(self, node):
98 """return an uncompressed revision of a given"""
98 """return an uncompressed revision of a given"""
99 if node == nullid: return ""
99 if node == nullid: return ""
100
100
101 text = None
101 text = None
102 chain = []
102 chain = []
103 iter_node = node
103 iter_node = node
104 rev = self.rev(iter_node)
104 rev = self.rev(iter_node)
105 # reconstruct the revision if it is from a changegroup
105 # reconstruct the revision if it is from a changegroup
106 while self.bundle(rev):
106 while self.bundle(rev):
107 if self._cache and self._cache[0] == iter_node:
107 if self._cache and self._cache[0] == iter_node:
108 text = self._cache[2]
108 text = self._cache[2]
109 break
109 break
110 chain.append(rev)
110 chain.append(rev)
111 iter_node = self.bundlebase(rev)
111 iter_node = self.bundlebase(rev)
112 rev = self.rev(iter_node)
112 rev = self.rev(iter_node)
113 if text is None:
113 if text is None:
114 text = revlog.revlog.revision(self, iter_node)
114 text = revlog.revlog.revision(self, iter_node)
115
115
116 while chain:
116 while chain:
117 delta = self.chunk(chain.pop())
117 delta = self.chunk(chain.pop())
118 text = mdiff.patches(text, [delta])
118 text = mdiff.patches(text, [delta])
119
119
120 p1, p2 = self.parents(node)
120 p1, p2 = self.parents(node)
121 if node != revlog.hash(text, p1, p2):
121 if node != revlog.hash(text, p1, p2):
122 raise revlog.RevlogError(_("integrity check failed on %s:%d")
122 raise revlog.RevlogError(_("integrity check failed on %s:%d")
123 % (self.datafile, self.rev(node)))
123 % (self.datafile, self.rev(node)))
124
124
125 self._cache = (node, self.rev(node), text)
125 self._cache = (node, self.rev(node), text)
126 return text
126 return text
127
127
128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 raise NotImplementedError
129 raise NotImplementedError
130 def addgroup(self, revs, linkmapper, transaction, unique=0):
130 def addgroup(self, revs, linkmapper, transaction, unique=0):
131 raise NotImplementedError
131 raise NotImplementedError
132 def strip(self, rev, minlink):
132 def strip(self, rev, minlink):
133 raise NotImplementedError
133 raise NotImplementedError
134 def checksize(self):
134 def checksize(self):
135 raise NotImplementedError
135 raise NotImplementedError
136
136
137 class bundlechangelog(bundlerevlog, changelog.changelog):
137 class bundlechangelog(bundlerevlog, changelog.changelog):
138 def __init__(self, opener, bundlefile):
138 def __init__(self, opener, bundlefile):
139 changelog.changelog.__init__(self, opener)
139 changelog.changelog.__init__(self, opener)
140 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
140 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
141
141
142 class bundlemanifest(bundlerevlog, manifest.manifest):
142 class bundlemanifest(bundlerevlog, manifest.manifest):
143 def __init__(self, opener, bundlefile, linkmapper):
143 def __init__(self, opener, bundlefile, linkmapper):
144 manifest.manifest.__init__(self, opener)
144 manifest.manifest.__init__(self, opener)
145 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
145 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
146 linkmapper)
146 linkmapper)
147
147
148 class bundlefilelog(bundlerevlog, filelog.filelog):
148 class bundlefilelog(bundlerevlog, filelog.filelog):
149 def __init__(self, opener, path, bundlefile, linkmapper):
149 def __init__(self, opener, path, bundlefile, linkmapper):
150 filelog.filelog.__init__(self, opener, path)
150 filelog.filelog.__init__(self, opener, path)
151 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
151 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
152 linkmapper)
152 linkmapper)
153
153
154 class bundlerepository(localrepo.localrepository):
154 class bundlerepository(localrepo.localrepository):
155 def __init__(self, ui, path, bundlename):
155 def __init__(self, ui, path, bundlename):
156 self._tempparent = None
156 self._tempparent = None
157 try:
157 try:
158 localrepo.localrepository.__init__(self, ui, path)
158 localrepo.localrepository.__init__(self, ui, path)
159 except repo.RepoError:
159 except repo.RepoError:
160 self._tempparent = tempfile.mkdtemp()
160 self._tempparent = tempfile.mkdtemp()
161 tmprepo = localrepo.instance(ui,self._tempparent,1)
161 tmprepo = localrepo.instance(ui,self._tempparent,1)
162 localrepo.localrepository.__init__(self, ui, self._tempparent)
162 localrepo.localrepository.__init__(self, ui, self._tempparent)
163
163
164 if path:
164 if path:
165 self._url = 'bundle:' + path + '+' + bundlename
165 self._url = 'bundle:' + path + '+' + bundlename
166 else:
166 else:
167 self._url = 'bundle:' + bundlename
167 self._url = 'bundle:' + bundlename
168
168
169 self.tempfile = None
169 self.tempfile = None
170 self.bundlefile = open(bundlename, "rb")
170 self.bundlefile = open(bundlename, "rb")
171 header = self.bundlefile.read(6)
171 header = self.bundlefile.read(6)
172 if not header.startswith("HG"):
172 if not header.startswith("HG"):
173 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
173 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
174 elif not header.startswith("HG10"):
174 elif not header.startswith("HG10"):
175 raise util.Abort(_("%s: unknown bundle version") % bundlename)
175 raise util.Abort(_("%s: unknown bundle version") % bundlename)
176 elif header == "HG10BZ":
176 elif (header == "HG10BZ") or (header == "HG10GZ"):
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
178 suffix=".hg10un", dir=self.path)
178 suffix=".hg10un", dir=self.path)
179 self.tempfile = temp
179 self.tempfile = temp
180 fptemp = os.fdopen(fdtemp, 'wb')
180 fptemp = os.fdopen(fdtemp, 'wb')
181 def generator(f):
181 def generator(f):
182 zd = bz2.BZ2Decompressor()
182 if header == "HG10BZ":
183 zd.decompress("BZ")
183 zd = bz2.BZ2Decompressor()
184 zd.decompress("BZ")
185 elif header == "HG10GZ":
186 zd = zlib.decompressobj()
184 for chunk in f:
187 for chunk in f:
185 yield zd.decompress(chunk)
188 yield zd.decompress(chunk)
186 gen = generator(util.filechunkiter(self.bundlefile, 4096))
189 gen = generator(util.filechunkiter(self.bundlefile, 4096))
187
190
188 try:
191 try:
189 fptemp.write("HG10UN")
192 fptemp.write("HG10UN")
190 for chunk in gen:
193 for chunk in gen:
191 fptemp.write(chunk)
194 fptemp.write(chunk)
192 finally:
195 finally:
193 fptemp.close()
196 fptemp.close()
194 self.bundlefile.close()
197 self.bundlefile.close()
195
198
196 self.bundlefile = open(self.tempfile, "rb")
199 self.bundlefile = open(self.tempfile, "rb")
197 # seek right after the header
200 # seek right after the header
198 self.bundlefile.seek(6)
201 self.bundlefile.seek(6)
199 elif header == "HG10UN":
202 elif header == "HG10UN":
200 # nothing to do
203 # nothing to do
201 pass
204 pass
202 else:
205 else:
203 raise util.Abort(_("%s: unknown bundle compression type")
206 raise util.Abort(_("%s: unknown bundle compression type")
204 % bundlename)
207 % bundlename)
205 # dict with the mapping 'filename' -> position in the bundle
208 # dict with the mapping 'filename' -> position in the bundle
206 self.bundlefilespos = {}
209 self.bundlefilespos = {}
207
210
208 def __getattr__(self, name):
211 def __getattr__(self, name):
209 if name == 'changelog':
212 if name == 'changelog':
210 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
213 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
211 self.manstart = self.bundlefile.tell()
214 self.manstart = self.bundlefile.tell()
212 return self.changelog
215 return self.changelog
213 if name == 'manifest':
216 if name == 'manifest':
214 self.bundlefile.seek(self.manstart)
217 self.bundlefile.seek(self.manstart)
215 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
218 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
216 self.changelog.rev)
219 self.changelog.rev)
217 self.filestart = self.bundlefile.tell()
220 self.filestart = self.bundlefile.tell()
218 return self.manifest
221 return self.manifest
219 if name == 'manstart':
222 if name == 'manstart':
220 self.changelog
223 self.changelog
221 return self.manstart
224 return self.manstart
222 if name == 'filestart':
225 if name == 'filestart':
223 self.manifest
226 self.manifest
224 return self.filestart
227 return self.filestart
225 return localrepo.localrepository.__getattr__(self, name)
228 return localrepo.localrepository.__getattr__(self, name)
226
229
227 def url(self):
230 def url(self):
228 return self._url
231 return self._url
229
232
230 def file(self, f):
233 def file(self, f):
231 if not self.bundlefilespos:
234 if not self.bundlefilespos:
232 self.bundlefile.seek(self.filestart)
235 self.bundlefile.seek(self.filestart)
233 while 1:
236 while 1:
234 chunk = changegroup.getchunk(self.bundlefile)
237 chunk = changegroup.getchunk(self.bundlefile)
235 if not chunk:
238 if not chunk:
236 break
239 break
237 self.bundlefilespos[chunk] = self.bundlefile.tell()
240 self.bundlefilespos[chunk] = self.bundlefile.tell()
238 for c in changegroup.chunkiter(self.bundlefile):
241 for c in changegroup.chunkiter(self.bundlefile):
239 pass
242 pass
240
243
241 if f[0] == '/':
244 if f[0] == '/':
242 f = f[1:]
245 f = f[1:]
243 if f in self.bundlefilespos:
246 if f in self.bundlefilespos:
244 self.bundlefile.seek(self.bundlefilespos[f])
247 self.bundlefile.seek(self.bundlefilespos[f])
245 return bundlefilelog(self.sopener, f, self.bundlefile,
248 return bundlefilelog(self.sopener, f, self.bundlefile,
246 self.changelog.rev)
249 self.changelog.rev)
247 else:
250 else:
248 return filelog.filelog(self.sopener, f)
251 return filelog.filelog(self.sopener, f)
249
252
250 def close(self):
253 def close(self):
251 """Close assigned bundle file immediately."""
254 """Close assigned bundle file immediately."""
252 self.bundlefile.close()
255 self.bundlefile.close()
253
256
254 def __del__(self):
257 def __del__(self):
255 bundlefile = getattr(self, 'bundlefile', None)
258 bundlefile = getattr(self, 'bundlefile', None)
256 if bundlefile and not bundlefile.closed:
259 if bundlefile and not bundlefile.closed:
257 bundlefile.close()
260 bundlefile.close()
258 tempfile = getattr(self, 'tempfile', None)
261 tempfile = getattr(self, 'tempfile', None)
259 if tempfile is not None:
262 if tempfile is not None:
260 os.unlink(tempfile)
263 os.unlink(tempfile)
261 if self._tempparent:
264 if self._tempparent:
262 shutil.rmtree(self._tempparent, True)
265 shutil.rmtree(self._tempparent, True)
263
266
264 def cancopy(self):
267 def cancopy(self):
265 return False
268 return False
266
269
267 def instance(ui, path, create):
270 def instance(ui, path, create):
268 if create:
271 if create:
269 raise util.Abort(_('cannot create new bundle repository'))
272 raise util.Abort(_('cannot create new bundle repository'))
270 parentpath = ui.config("bundle", "mainreporoot", "")
273 parentpath = ui.config("bundle", "mainreporoot", "")
271 if parentpath:
274 if parentpath:
272 # Try to make the full path relative so we get a nice, short URL.
275 # Try to make the full path relative so we get a nice, short URL.
273 # In particular, we don't want temp dir names in test outputs.
276 # In particular, we don't want temp dir names in test outputs.
274 cwd = os.getcwd()
277 cwd = os.getcwd()
275 if parentpath == cwd:
278 if parentpath == cwd:
276 parentpath = ''
279 parentpath = ''
277 else:
280 else:
278 cwd = os.path.join(cwd,'')
281 cwd = os.path.join(cwd,'')
279 if parentpath.startswith(cwd):
282 if parentpath.startswith(cwd):
280 parentpath = parentpath[len(cwd):]
283 parentpath = parentpath[len(cwd):]
281 path = util.drop_scheme('file', path)
284 path = util.drop_scheme('file', path)
282 if path.startswith('bundle:'):
285 if path.startswith('bundle:'):
283 path = util.drop_scheme('bundle', path)
286 path = util.drop_scheme('bundle', path)
284 s = path.split("+", 1)
287 s = path.split("+", 1)
285 if len(s) == 1:
288 if len(s) == 1:
286 repopath, bundlename = parentpath, s[0]
289 repopath, bundlename = parentpath, s[0]
287 else:
290 else:
288 repopath, bundlename = s
291 repopath, bundlename = s
289 else:
292 else:
290 repopath, bundlename = parentpath, path
293 repopath, bundlename = parentpath, path
291 return bundlerepository(ui, repopath, bundlename)
294 return bundlerepository(ui, repopath, bundlename)
General Comments 0
You need to be logged in to leave comments. Login now