##// END OF EJS Templates
bundlerepo: remove duplication of bundle decompressors
Matt Mackall -
r12044:bcc71395 default
parent child Browse files
Show More
@@ -1,305 +1,289 b''
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, bz2, zlib, tempfile, shutil
16 import os, struct, tempfile, shutil
17 import changegroup, util, mdiff
17 import changegroup, util, mdiff
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, bundlefile,
21 def __init__(self, opener, indexfile, bundlefile,
22 linkmapper=None):
22 linkmapper=None):
23 # How it works:
23 # How it works:
24 # to retrieve a revision, we need to know the offset of
24 # to retrieve a revision, we need to know the offset of
25 # the revision in the bundlefile (an opened file).
25 # the revision in the bundlefile (an opened file).
26 #
26 #
27 # We store this offset in the index (start), to differentiate a
27 # 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
28 # 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
29 # 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)
30 # (it is bigger since we store the node to which the delta is)
31 #
31 #
32 revlog.revlog.__init__(self, opener, indexfile)
32 revlog.revlog.__init__(self, opener, indexfile)
33 self.bundlefile = bundlefile
33 self.bundlefile = bundlefile
34 self.basemap = {}
34 self.basemap = {}
35 def chunkpositer():
35 def chunkpositer():
36 for chunk in changegroup.chunkiter(bundlefile):
36 for chunk in changegroup.chunkiter(bundlefile):
37 pos = bundlefile.tell()
37 pos = bundlefile.tell()
38 yield chunk, pos - len(chunk)
38 yield chunk, pos - len(chunk)
39 n = len(self)
39 n = len(self)
40 prev = None
40 prev = None
41 for chunk, start in chunkpositer():
41 for chunk, start in chunkpositer():
42 size = len(chunk)
42 size = len(chunk)
43 if size < 80:
43 if size < 80:
44 raise util.Abort(_("invalid changegroup"))
44 raise util.Abort(_("invalid changegroup"))
45 start += 80
45 start += 80
46 size -= 80
46 size -= 80
47 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
47 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
48 if node in self.nodemap:
48 if node in self.nodemap:
49 prev = node
49 prev = node
50 continue
50 continue
51 for p in (p1, p2):
51 for p in (p1, p2):
52 if not p in self.nodemap:
52 if not p in self.nodemap:
53 raise error.LookupError(p, self.indexfile,
53 raise error.LookupError(p, self.indexfile,
54 _("unknown parent"))
54 _("unknown parent"))
55 if linkmapper is None:
55 if linkmapper is None:
56 link = n
56 link = n
57 else:
57 else:
58 link = linkmapper(cs)
58 link = linkmapper(cs)
59
59
60 if not prev:
60 if not prev:
61 prev = p1
61 prev = p1
62 # start, size, full unc. size, base (unused), link, p1, p2, node
62 # start, size, full unc. size, base (unused), link, p1, p2, node
63 e = (revlog.offset_type(start, 0), size, -1, -1, link,
63 e = (revlog.offset_type(start, 0), size, -1, -1, link,
64 self.rev(p1), self.rev(p2), node)
64 self.rev(p1), self.rev(p2), node)
65 self.basemap[n] = prev
65 self.basemap[n] = prev
66 self.index.insert(-1, e)
66 self.index.insert(-1, e)
67 self.nodemap[node] = n
67 self.nodemap[node] = n
68 prev = node
68 prev = node
69 n += 1
69 n += 1
70
70
71 def bundle(self, rev):
71 def bundle(self, rev):
72 """is rev from the bundle"""
72 """is rev from the bundle"""
73 if rev < 0:
73 if rev < 0:
74 return False
74 return False
75 return rev in self.basemap
75 return rev in self.basemap
76 def bundlebase(self, rev):
76 def bundlebase(self, rev):
77 return self.basemap[rev]
77 return self.basemap[rev]
78 def _chunk(self, rev):
78 def _chunk(self, rev):
79 # Warning: in case of bundle, the diff is against bundlebase,
79 # Warning: in case of bundle, the diff is against bundlebase,
80 # not against rev - 1
80 # not against rev - 1
81 # XXX: could use some caching
81 # XXX: could use some caching
82 if not self.bundle(rev):
82 if not self.bundle(rev):
83 return revlog.revlog._chunk(self, rev)
83 return revlog.revlog._chunk(self, rev)
84 self.bundlefile.seek(self.start(rev))
84 self.bundlefile.seek(self.start(rev))
85 return self.bundlefile.read(self.length(rev))
85 return self.bundlefile.read(self.length(rev))
86
86
87 def revdiff(self, rev1, rev2):
87 def revdiff(self, rev1, rev2):
88 """return or calculate a delta between two revisions"""
88 """return or calculate a delta between two revisions"""
89 if self.bundle(rev1) and self.bundle(rev2):
89 if self.bundle(rev1) and self.bundle(rev2):
90 # hot path for bundle
90 # hot path for bundle
91 revb = self.rev(self.bundlebase(rev2))
91 revb = self.rev(self.bundlebase(rev2))
92 if revb == rev1:
92 if revb == rev1:
93 return self._chunk(rev2)
93 return self._chunk(rev2)
94 elif not self.bundle(rev1) and not self.bundle(rev2):
94 elif not self.bundle(rev1) and not self.bundle(rev2):
95 return revlog.revlog.revdiff(self, rev1, rev2)
95 return revlog.revlog.revdiff(self, rev1, rev2)
96
96
97 return mdiff.textdiff(self.revision(self.node(rev1)),
97 return mdiff.textdiff(self.revision(self.node(rev1)),
98 self.revision(self.node(rev2)))
98 self.revision(self.node(rev2)))
99
99
100 def revision(self, node):
100 def revision(self, node):
101 """return an uncompressed revision of a given"""
101 """return an uncompressed revision of a given"""
102 if node == nullid:
102 if node == nullid:
103 return ""
103 return ""
104
104
105 text = None
105 text = None
106 chain = []
106 chain = []
107 iter_node = node
107 iter_node = node
108 rev = self.rev(iter_node)
108 rev = self.rev(iter_node)
109 # reconstruct the revision if it is from a changegroup
109 # reconstruct the revision if it is from a changegroup
110 while self.bundle(rev):
110 while self.bundle(rev):
111 if self._cache and self._cache[0] == iter_node:
111 if self._cache and self._cache[0] == iter_node:
112 text = self._cache[2]
112 text = self._cache[2]
113 break
113 break
114 chain.append(rev)
114 chain.append(rev)
115 iter_node = self.bundlebase(rev)
115 iter_node = self.bundlebase(rev)
116 rev = self.rev(iter_node)
116 rev = self.rev(iter_node)
117 if text is None:
117 if text is None:
118 text = revlog.revlog.revision(self, iter_node)
118 text = revlog.revlog.revision(self, iter_node)
119
119
120 while chain:
120 while chain:
121 delta = self._chunk(chain.pop())
121 delta = self._chunk(chain.pop())
122 text = mdiff.patches(text, [delta])
122 text = mdiff.patches(text, [delta])
123
123
124 p1, p2 = self.parents(node)
124 p1, p2 = self.parents(node)
125 if node != revlog.hash(text, p1, p2):
125 if node != revlog.hash(text, p1, p2):
126 raise error.RevlogError(_("integrity check failed on %s:%d")
126 raise error.RevlogError(_("integrity check failed on %s:%d")
127 % (self.datafile, self.rev(node)))
127 % (self.datafile, self.rev(node)))
128
128
129 self._cache = (node, self.rev(node), text)
129 self._cache = (node, self.rev(node), text)
130 return text
130 return text
131
131
132 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
132 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
133 raise NotImplementedError
133 raise NotImplementedError
134 def addgroup(self, revs, linkmapper, transaction):
134 def addgroup(self, revs, linkmapper, transaction):
135 raise NotImplementedError
135 raise NotImplementedError
136 def strip(self, rev, minlink):
136 def strip(self, rev, minlink):
137 raise NotImplementedError
137 raise NotImplementedError
138 def checksize(self):
138 def checksize(self):
139 raise NotImplementedError
139 raise NotImplementedError
140
140
141 class bundlechangelog(bundlerevlog, changelog.changelog):
141 class bundlechangelog(bundlerevlog, changelog.changelog):
142 def __init__(self, opener, bundlefile):
142 def __init__(self, opener, bundlefile):
143 changelog.changelog.__init__(self, opener)
143 changelog.changelog.__init__(self, opener)
144 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
144 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
145
145
146 class bundlemanifest(bundlerevlog, manifest.manifest):
146 class bundlemanifest(bundlerevlog, manifest.manifest):
147 def __init__(self, opener, bundlefile, linkmapper):
147 def __init__(self, opener, bundlefile, linkmapper):
148 manifest.manifest.__init__(self, opener)
148 manifest.manifest.__init__(self, opener)
149 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
149 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
150 linkmapper)
150 linkmapper)
151
151
152 class bundlefilelog(bundlerevlog, filelog.filelog):
152 class bundlefilelog(bundlerevlog, filelog.filelog):
153 def __init__(self, opener, path, bundlefile, linkmapper):
153 def __init__(self, opener, path, bundlefile, linkmapper):
154 filelog.filelog.__init__(self, opener, path)
154 filelog.filelog.__init__(self, opener, path)
155 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
155 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
156 linkmapper)
156 linkmapper)
157
157
158 class bundlerepository(localrepo.localrepository):
158 class bundlerepository(localrepo.localrepository):
159 def __init__(self, ui, path, bundlename):
159 def __init__(self, ui, path, bundlename):
160 self._tempparent = None
160 self._tempparent = None
161 try:
161 try:
162 localrepo.localrepository.__init__(self, ui, path)
162 localrepo.localrepository.__init__(self, ui, path)
163 except error.RepoError:
163 except error.RepoError:
164 self._tempparent = tempfile.mkdtemp()
164 self._tempparent = tempfile.mkdtemp()
165 localrepo.instance(ui, self._tempparent, 1)
165 localrepo.instance(ui, self._tempparent, 1)
166 localrepo.localrepository.__init__(self, ui, self._tempparent)
166 localrepo.localrepository.__init__(self, ui, self._tempparent)
167
167
168 if path:
168 if path:
169 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
169 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
170 else:
170 else:
171 self._url = 'bundle:' + bundlename
171 self._url = 'bundle:' + bundlename
172
172
173 self.tempfile = None
173 self.tempfile = None
174 self.bundlefile = open(bundlename, "rb")
174 self.bundlefile = open(bundlename, "rb")
175 header = self.bundlefile.read(6)
175 b = changegroup.readbundle(self.bundlefile, bundlename)
176 if not header.startswith("HG"):
176 if b.compressed():
177 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
178 elif not header.startswith("HG10"):
179 raise util.Abort(_("%s: unknown bundle version") % bundlename)
180 elif (header == "HG10BZ") or (header == "HG10GZ"):
181 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
182 suffix=".hg10un", dir=self.path)
178 suffix=".hg10un", dir=self.path)
183 self.tempfile = temp
179 self.tempfile = temp
184 fptemp = os.fdopen(fdtemp, 'wb')
180 fptemp = os.fdopen(fdtemp, 'wb')
185 def generator(f):
186 if header == "HG10BZ":
187 zd = bz2.BZ2Decompressor()
188 zd.decompress("BZ")
189 elif header == "HG10GZ":
190 zd = zlib.decompressobj()
191 for chunk in f:
192 yield zd.decompress(chunk)
193 gen = generator(util.filechunkiter(self.bundlefile, 4096))
194
181
195 try:
182 try:
196 fptemp.write("HG10UN")
183 fptemp.write("HG10UN")
197 for chunk in gen:
184 while 1:
185 chunk = b.read(2**18)
186 if not chunk:
187 break
198 fptemp.write(chunk)
188 fptemp.write(chunk)
199 finally:
189 finally:
200 fptemp.close()
190 fptemp.close()
201 self.bundlefile.close()
191 self.bundlefile.close()
202
192
203 self.bundlefile = open(self.tempfile, "rb")
193 self.bundlefile = open(self.tempfile, "rb")
204 # seek right after the header
205 self.bundlefile.seek(6)
194 self.bundlefile.seek(6)
206 elif header == "HG10UN":
195
207 # nothing to do
208 pass
209 else:
210 raise util.Abort(_("%s: unknown bundle compression type")
211 % bundlename)
212 # dict with the mapping 'filename' -> position in the bundle
196 # dict with the mapping 'filename' -> position in the bundle
213 self.bundlefilespos = {}
197 self.bundlefilespos = {}
214
198
215 @util.propertycache
199 @util.propertycache
216 def changelog(self):
200 def changelog(self):
217 c = bundlechangelog(self.sopener, self.bundlefile)
201 c = bundlechangelog(self.sopener, self.bundlefile)
218 self.manstart = self.bundlefile.tell()
202 self.manstart = self.bundlefile.tell()
219 return c
203 return c
220
204
221 @util.propertycache
205 @util.propertycache
222 def manifest(self):
206 def manifest(self):
223 self.bundlefile.seek(self.manstart)
207 self.bundlefile.seek(self.manstart)
224 m = bundlemanifest(self.sopener, self.bundlefile, self.changelog.rev)
208 m = bundlemanifest(self.sopener, self.bundlefile, self.changelog.rev)
225 self.filestart = self.bundlefile.tell()
209 self.filestart = self.bundlefile.tell()
226 return m
210 return m
227
211
228 @util.propertycache
212 @util.propertycache
229 def manstart(self):
213 def manstart(self):
230 self.changelog
214 self.changelog
231 return self.manstart
215 return self.manstart
232
216
233 @util.propertycache
217 @util.propertycache
234 def filestart(self):
218 def filestart(self):
235 self.manifest
219 self.manifest
236 return self.filestart
220 return self.filestart
237
221
238 def url(self):
222 def url(self):
239 return self._url
223 return self._url
240
224
241 def file(self, f):
225 def file(self, f):
242 if not self.bundlefilespos:
226 if not self.bundlefilespos:
243 self.bundlefile.seek(self.filestart)
227 self.bundlefile.seek(self.filestart)
244 while 1:
228 while 1:
245 chunk = changegroup.getchunk(self.bundlefile)
229 chunk = changegroup.getchunk(self.bundlefile)
246 if not chunk:
230 if not chunk:
247 break
231 break
248 self.bundlefilespos[chunk] = self.bundlefile.tell()
232 self.bundlefilespos[chunk] = self.bundlefile.tell()
249 for c in changegroup.chunkiter(self.bundlefile):
233 for c in changegroup.chunkiter(self.bundlefile):
250 pass
234 pass
251
235
252 if f[0] == '/':
236 if f[0] == '/':
253 f = f[1:]
237 f = f[1:]
254 if f in self.bundlefilespos:
238 if f in self.bundlefilespos:
255 self.bundlefile.seek(self.bundlefilespos[f])
239 self.bundlefile.seek(self.bundlefilespos[f])
256 return bundlefilelog(self.sopener, f, self.bundlefile,
240 return bundlefilelog(self.sopener, f, self.bundlefile,
257 self.changelog.rev)
241 self.changelog.rev)
258 else:
242 else:
259 return filelog.filelog(self.sopener, f)
243 return filelog.filelog(self.sopener, f)
260
244
261 def close(self):
245 def close(self):
262 """Close assigned bundle file immediately."""
246 """Close assigned bundle file immediately."""
263 self.bundlefile.close()
247 self.bundlefile.close()
264
248
265 def __del__(self):
249 def __del__(self):
266 bundlefile = getattr(self, 'bundlefile', None)
250 bundlefile = getattr(self, 'bundlefile', None)
267 if bundlefile and not bundlefile.closed:
251 if bundlefile and not bundlefile.closed:
268 bundlefile.close()
252 bundlefile.close()
269 tempfile = getattr(self, 'tempfile', None)
253 tempfile = getattr(self, 'tempfile', None)
270 if tempfile is not None:
254 if tempfile is not None:
271 os.unlink(tempfile)
255 os.unlink(tempfile)
272 if self._tempparent:
256 if self._tempparent:
273 shutil.rmtree(self._tempparent, True)
257 shutil.rmtree(self._tempparent, True)
274
258
275 def cancopy(self):
259 def cancopy(self):
276 return False
260 return False
277
261
278 def getcwd(self):
262 def getcwd(self):
279 return os.getcwd() # always outside the repo
263 return os.getcwd() # always outside the repo
280
264
281 def instance(ui, path, create):
265 def instance(ui, path, create):
282 if create:
266 if create:
283 raise util.Abort(_('cannot create new bundle repository'))
267 raise util.Abort(_('cannot create new bundle repository'))
284 parentpath = ui.config("bundle", "mainreporoot", "")
268 parentpath = ui.config("bundle", "mainreporoot", "")
285 if parentpath:
269 if parentpath:
286 # Try to make the full path relative so we get a nice, short URL.
270 # Try to make the full path relative so we get a nice, short URL.
287 # In particular, we don't want temp dir names in test outputs.
271 # In particular, we don't want temp dir names in test outputs.
288 cwd = os.getcwd()
272 cwd = os.getcwd()
289 if parentpath == cwd:
273 if parentpath == cwd:
290 parentpath = ''
274 parentpath = ''
291 else:
275 else:
292 cwd = os.path.join(cwd,'')
276 cwd = os.path.join(cwd,'')
293 if parentpath.startswith(cwd):
277 if parentpath.startswith(cwd):
294 parentpath = parentpath[len(cwd):]
278 parentpath = parentpath[len(cwd):]
295 path = util.drop_scheme('file', path)
279 path = util.drop_scheme('file', path)
296 if path.startswith('bundle:'):
280 if path.startswith('bundle:'):
297 path = util.drop_scheme('bundle', path)
281 path = util.drop_scheme('bundle', path)
298 s = path.split("+", 1)
282 s = path.split("+", 1)
299 if len(s) == 1:
283 if len(s) == 1:
300 repopath, bundlename = parentpath, s[0]
284 repopath, bundlename = parentpath, s[0]
301 else:
285 else:
302 repopath, bundlename = s
286 repopath, bundlename = s
303 else:
287 else:
304 repopath, bundlename = parentpath, path
288 repopath, bundlename = parentpath, path
305 return bundlerepository(ui, repopath, bundlename)
289 return bundlerepository(ui, repopath, bundlename)
@@ -1,167 +1,170 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 from i18n import _
8 from i18n import _
9 import util
9 import util
10 import struct, os, bz2, zlib, tempfile
10 import struct, os, bz2, zlib, tempfile
11
11
12 def getchunk(source):
12 def getchunk(source):
13 """return the next chunk from changegroup 'source' as a string"""
13 """return the next chunk from changegroup 'source' as a string"""
14 d = source.read(4)
14 d = source.read(4)
15 if not d:
15 if not d:
16 return ""
16 return ""
17 l = struct.unpack(">l", d)[0]
17 l = struct.unpack(">l", d)[0]
18 if l <= 4:
18 if l <= 4:
19 return ""
19 return ""
20 d = source.read(l - 4)
20 d = source.read(l - 4)
21 if len(d) < l - 4:
21 if len(d) < l - 4:
22 raise util.Abort(_("premature EOF reading chunk"
22 raise util.Abort(_("premature EOF reading chunk"
23 " (got %d bytes, expected %d)")
23 " (got %d bytes, expected %d)")
24 % (len(d), l - 4))
24 % (len(d), l - 4))
25 return d
25 return d
26
26
27 def chunkiter(source, progress=None):
27 def chunkiter(source, progress=None):
28 """iterate through the chunks in source, yielding a sequence of chunks
28 """iterate through the chunks in source, yielding a sequence of chunks
29 (strings)"""
29 (strings)"""
30 while 1:
30 while 1:
31 c = getchunk(source)
31 c = getchunk(source)
32 if not c:
32 if not c:
33 break
33 break
34 elif progress is not None:
34 elif progress is not None:
35 progress()
35 progress()
36 yield c
36 yield c
37
37
38 def chunkheader(length):
38 def chunkheader(length):
39 """return a changegroup chunk header (string)"""
39 """return a changegroup chunk header (string)"""
40 return struct.pack(">l", length + 4)
40 return struct.pack(">l", length + 4)
41
41
42 def closechunk():
42 def closechunk():
43 """return a changegroup chunk header (string) for a zero-length chunk"""
43 """return a changegroup chunk header (string) for a zero-length chunk"""
44 return struct.pack(">l", 0)
44 return struct.pack(">l", 0)
45
45
46 class nocompress(object):
46 class nocompress(object):
47 def compress(self, x):
47 def compress(self, x):
48 return x
48 return x
49 def flush(self):
49 def flush(self):
50 return ""
50 return ""
51
51
52 bundletypes = {
52 bundletypes = {
53 "": ("", nocompress),
53 "": ("", nocompress),
54 "HG10UN": ("HG10UN", nocompress),
54 "HG10UN": ("HG10UN", nocompress),
55 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
55 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
56 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
56 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
57 }
57 }
58
58
59 def collector(cl, mmfs, files):
59 def collector(cl, mmfs, files):
60 # Gather information about changeset nodes going out in a bundle.
60 # Gather information about changeset nodes going out in a bundle.
61 # We want to gather manifests needed and filelogs affected.
61 # We want to gather manifests needed and filelogs affected.
62 def collect(node):
62 def collect(node):
63 c = cl.read(node)
63 c = cl.read(node)
64 files.update(c[3])
64 files.update(c[3])
65 mmfs.setdefault(c[0], node)
65 mmfs.setdefault(c[0], node)
66 return collect
66 return collect
67
67
68 # hgweb uses this list to communicate its preferred type
68 # hgweb uses this list to communicate its preferred type
69 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
69 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
70
70
71 def writebundle(cg, filename, bundletype):
71 def writebundle(cg, filename, bundletype):
72 """Write a bundle file and return its filename.
72 """Write a bundle file and return its filename.
73
73
74 Existing files will not be overwritten.
74 Existing files will not be overwritten.
75 If no filename is specified, a temporary file is created.
75 If no filename is specified, a temporary file is created.
76 bz2 compression can be turned off.
76 bz2 compression can be turned off.
77 The bundle file will be deleted in case of errors.
77 The bundle file will be deleted in case of errors.
78 """
78 """
79
79
80 fh = None
80 fh = None
81 cleanup = None
81 cleanup = None
82 try:
82 try:
83 if filename:
83 if filename:
84 fh = open(filename, "wb")
84 fh = open(filename, "wb")
85 else:
85 else:
86 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
86 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
87 fh = os.fdopen(fd, "wb")
87 fh = os.fdopen(fd, "wb")
88 cleanup = filename
88 cleanup = filename
89
89
90 header, compressor = bundletypes[bundletype]
90 header, compressor = bundletypes[bundletype]
91 fh.write(header)
91 fh.write(header)
92 z = compressor()
92 z = compressor()
93
93
94 # parse the changegroup data, otherwise we will block
94 # parse the changegroup data, otherwise we will block
95 # in case of sshrepo because we don't know the end of the stream
95 # in case of sshrepo because we don't know the end of the stream
96
96
97 # an empty chunkiter is the end of the changegroup
97 # an empty chunkiter is the end of the changegroup
98 # a changegroup has at least 2 chunkiters (changelog and manifest).
98 # a changegroup has at least 2 chunkiters (changelog and manifest).
99 # after that, an empty chunkiter is the end of the changegroup
99 # after that, an empty chunkiter is the end of the changegroup
100 empty = False
100 empty = False
101 count = 0
101 count = 0
102 while not empty or count <= 2:
102 while not empty or count <= 2:
103 empty = True
103 empty = True
104 count += 1
104 count += 1
105 for chunk in chunkiter(cg):
105 for chunk in chunkiter(cg):
106 empty = False
106 empty = False
107 fh.write(z.compress(chunkheader(len(chunk))))
107 fh.write(z.compress(chunkheader(len(chunk))))
108 pos = 0
108 pos = 0
109 while pos < len(chunk):
109 while pos < len(chunk):
110 next = pos + 2**20
110 next = pos + 2**20
111 fh.write(z.compress(chunk[pos:next]))
111 fh.write(z.compress(chunk[pos:next]))
112 pos = next
112 pos = next
113 fh.write(z.compress(closechunk()))
113 fh.write(z.compress(closechunk()))
114 fh.write(z.flush())
114 fh.write(z.flush())
115 cleanup = None
115 cleanup = None
116 return filename
116 return filename
117 finally:
117 finally:
118 if fh is not None:
118 if fh is not None:
119 fh.close()
119 fh.close()
120 if cleanup is not None:
120 if cleanup is not None:
121 os.unlink(cleanup)
121 os.unlink(cleanup)
122
122
123 def decompressor(fh, alg):
123 def decompressor(fh, alg):
124 if alg == 'UN':
124 if alg == 'UN':
125 return fh
125 return fh
126 elif alg == 'GZ':
126 elif alg == 'GZ':
127 def generator(f):
127 def generator(f):
128 zd = zlib.decompressobj()
128 zd = zlib.decompressobj()
129 for chunk in f:
129 for chunk in f:
130 yield zd.decompress(chunk)
130 yield zd.decompress(chunk)
131 elif alg == 'BZ':
131 elif alg == 'BZ':
132 def generator(f):
132 def generator(f):
133 zd = bz2.BZ2Decompressor()
133 zd = bz2.BZ2Decompressor()
134 zd.decompress("BZ")
134 zd.decompress("BZ")
135 for chunk in util.filechunkiter(f, 4096):
135 for chunk in util.filechunkiter(f, 4096):
136 yield zd.decompress(chunk)
136 yield zd.decompress(chunk)
137 else:
137 else:
138 raise util.Abort("unknown bundle compression '%s'" % alg)
138 raise util.Abort("unknown bundle compression '%s'" % alg)
139 return generator(fh)
139 return generator(fh)
140
140
141 class unbundle10(object):
141 class unbundle10(object):
142 def __init__(self, fh, alg):
142 def __init__(self, fh, alg):
143 self._stream = util.chunkbuffer(decompressor(fh, alg))
143 self._stream = util.chunkbuffer(decompressor(fh, alg))
144 self._type = alg
145 def compressed(self):
146 return self._type != 'UN'
144 def read(self, l):
147 def read(self, l):
145 return self._stream.read(l)
148 return self._stream.read(l)
146
149
147 def readbundle(fh, fname):
150 def readbundle(fh, fname):
148 header = fh.read(6)
151 header = fh.read(6)
149
152
150 if not fname:
153 if not fname:
151 fname = "stream"
154 fname = "stream"
152 if not header.startswith('HG') and header.startswith('\0'):
155 if not header.startswith('HG') and header.startswith('\0'):
153 # headerless bundle, clean things up
156 # headerless bundle, clean things up
154 def fixup(f, h):
157 def fixup(f, h):
155 yield h
158 yield h
156 for x in f:
159 for x in f:
157 yield x
160 yield x
158 fh = fixup(fh, header)
161 fh = fixup(fh, header)
159 header = "HG10UN"
162 header = "HG10UN"
160
163
161 magic, version, alg = header[0:2], header[2:4], header[4:6]
164 magic, version, alg = header[0:2], header[2:4], header[4:6]
162
165
163 if magic != 'HG':
166 if magic != 'HG':
164 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
167 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
165 if version != '10':
168 if version != '10':
166 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
169 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
167 return unbundle10(fh, alg)
170 return unbundle10(fh, alg)
@@ -1,99 +1,99 b''
1 bundle w/o type option
1 bundle w/o type option
2
2
3 $ hg init t1
3 $ hg init t1
4 $ hg init t2
4 $ hg init t2
5 $ cd t1
5 $ cd t1
6 $ echo blablablablabla > file.txt
6 $ echo blablablablabla > file.txt
7 $ hg ci -Ama
7 $ hg ci -Ama
8 adding file.txt
8 adding file.txt
9 $ hg log | grep summary
9 $ hg log | grep summary
10 summary: a
10 summary: a
11 $ hg bundle ../b1 ../t2
11 $ hg bundle ../b1 ../t2
12 searching for changes
12 searching for changes
13 1 changesets found
13 1 changesets found
14
14
15 $ cd ../t2
15 $ cd ../t2
16 $ hg pull ../b1
16 $ hg pull ../b1
17 pulling from ../b1
17 pulling from ../b1
18 requesting all changes
18 requesting all changes
19 adding changesets
19 adding changesets
20 adding manifests
20 adding manifests
21 adding file changes
21 adding file changes
22 added 1 changesets with 1 changes to 1 files
22 added 1 changesets with 1 changes to 1 files
23 (run 'hg update' to get a working copy)
23 (run 'hg update' to get a working copy)
24 $ hg up
24 $ hg up
25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 $ hg log | grep summary
26 $ hg log | grep summary
27 summary: a
27 summary: a
28 $ cd ..
28 $ cd ..
29
29
30 test bundle types
30 test bundle types
31
31
32 $ for t in "None" "bzip2" "gzip"; do
32 $ for t in "None" "bzip2" "gzip"; do
33 > echo % test bundle type $t
33 > echo % test bundle type $t
34 > hg init t$t
34 > hg init t$t
35 > cd t1
35 > cd t1
36 > hg bundle -t $t ../b$t ../t$t
36 > hg bundle -t $t ../b$t ../t$t
37 > cut -b 1-6 ../b$t | head -n 1
37 > cut -b 1-6 ../b$t | head -n 1
38 > cd ../t$t
38 > cd ../t$t
39 > hg pull ../b$t
39 > hg pull ../b$t
40 > hg up
40 > hg up
41 > hg log | grep summary
41 > hg log | grep summary
42 > cd ..
42 > cd ..
43 > done
43 > done
44 % test bundle type None
44 % test bundle type None
45 searching for changes
45 searching for changes
46 1 changesets found
46 1 changesets found
47 HG10UN
47 HG10UN
48 pulling from ../bNone
48 pulling from ../bNone
49 requesting all changes
49 requesting all changes
50 adding changesets
50 adding changesets
51 adding manifests
51 adding manifests
52 adding file changes
52 adding file changes
53 added 1 changesets with 1 changes to 1 files
53 added 1 changesets with 1 changes to 1 files
54 (run 'hg update' to get a working copy)
54 (run 'hg update' to get a working copy)
55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 summary: a
56 summary: a
57 % test bundle type bzip2
57 % test bundle type bzip2
58 searching for changes
58 searching for changes
59 1 changesets found
59 1 changesets found
60 HG10BZ
60 HG10BZ
61 pulling from ../bbzip2
61 pulling from ../bbzip2
62 requesting all changes
62 requesting all changes
63 adding changesets
63 adding changesets
64 adding manifests
64 adding manifests
65 adding file changes
65 adding file changes
66 added 1 changesets with 1 changes to 1 files
66 added 1 changesets with 1 changes to 1 files
67 (run 'hg update' to get a working copy)
67 (run 'hg update' to get a working copy)
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 summary: a
69 summary: a
70 % test bundle type gzip
70 % test bundle type gzip
71 searching for changes
71 searching for changes
72 1 changesets found
72 1 changesets found
73 HG10GZ
73 HG10GZ
74 pulling from ../bgzip
74 pulling from ../bgzip
75 requesting all changes
75 requesting all changes
76 adding changesets
76 adding changesets
77 adding manifests
77 adding manifests
78 adding file changes
78 adding file changes
79 added 1 changesets with 1 changes to 1 files
79 added 1 changesets with 1 changes to 1 files
80 (run 'hg update' to get a working copy)
80 (run 'hg update' to get a working copy)
81 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 summary: a
82 summary: a
83
83
84 test garbage file
84 test garbage file
85
85
86 $ echo garbage > bgarbage
86 $ echo garbage > bgarbage
87 $ hg init tgarbage
87 $ hg init tgarbage
88 $ cd tgarbage
88 $ cd tgarbage
89 $ hg pull ../bgarbage
89 $ hg pull ../bgarbage
90 abort: ../bgarbage: not a Mercurial bundle file
90 abort: ../bgarbage: not a Mercurial bundle
91 $ cd ..
91 $ cd ..
92
92
93 test invalid bundle type
93 test invalid bundle type
94
94
95 $ cd t1
95 $ cd t1
96 $ hg bundle -a -t garbage ../bgarbage
96 $ hg bundle -a -t garbage ../bgarbage
97 1 changesets found
97 1 changesets found
98 abort: unknown bundle type specified with --type
98 abort: unknown bundle type specified with --type
99 $ cd ..
99 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now