##// END OF EJS Templates
bundlerepo: use bundle objects everywhere
Matt Mackall -
r12332:680fe77a default
parent child Browse files
Show More
@@ -1,286 +1,282
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
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, bundle,
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 bundle (an unbundle object).
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.bundle = bundle
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(bundle):
37 pos = bundlefile.tell()
37 pos = bundle.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 inbundle(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.inbundle(rev):
83 return revlog.revlog._chunk(self, rev)
83 return revlog.revlog._chunk(self, rev)
84 self.bundlefile.seek(self.start(rev))
84 self.bundle.seek(self.start(rev))
85 return self.bundlefile.read(self.length(rev))
85 return self.bundle.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.inbundle(rev1) and self.inbundle(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.inbundle(rev1) and not self.inbundle(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.inbundle(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, bundle):
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, bundle)
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, bundle, 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, bundle,
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, bundle, 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, bundle,
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 f = open(bundlename, "rb")
175 b = changegroup.readbundle(self.bundlefile, bundlename)
175 self.bundle = changegroup.readbundle(f, bundlename)
176 if b.compressed():
176 if self.bundle.compressed():
177 # we need a seekable, decompressed bundle
177 # we need a seekable, decompressed bundle
178 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
178 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
179 suffix=".hg10un", dir=self.path)
179 suffix=".hg10un", dir=self.path)
180 self.tempfile = temp
180 self.tempfile = temp
181 fptemp = os.fdopen(fdtemp, 'wb')
181 fptemp = os.fdopen(fdtemp, 'wb')
182
182
183 try:
183 try:
184 fptemp.write("HG10UN")
184 fptemp.write("HG10UN")
185 while 1:
185 while 1:
186 chunk = b.read(2**18)
186 chunk = self.bundle.read(2**18)
187 if not chunk:
187 if not chunk:
188 break
188 break
189 fptemp.write(chunk)
189 fptemp.write(chunk)
190 finally:
190 finally:
191 fptemp.close()
191 fptemp.close()
192 self.bundlefile.close()
193
192
194 self.bundlefile = open(self.tempfile, "rb")
193 f = open(self.tempfile, "rb")
195 self.bundlefile.seek(6)
194 self.bundle = changegroup.readbundle(f, bundlename)
196
195
197 # dict with the mapping 'filename' -> position in the bundle
196 # dict with the mapping 'filename' -> position in the bundle
198 self.bundlefilespos = {}
197 self.bundlefilespos = {}
199
198
200 @util.propertycache
199 @util.propertycache
201 def changelog(self):
200 def changelog(self):
202 c = bundlechangelog(self.sopener, self.bundlefile)
201 c = bundlechangelog(self.sopener, self.bundle)
203 self.manstart = self.bundlefile.tell()
202 self.manstart = self.bundle.tell()
204 return c
203 return c
205
204
206 @util.propertycache
205 @util.propertycache
207 def manifest(self):
206 def manifest(self):
208 self.bundlefile.seek(self.manstart)
207 self.bundle.seek(self.manstart)
209 m = bundlemanifest(self.sopener, self.bundlefile, self.changelog.rev)
208 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
210 self.filestart = self.bundlefile.tell()
209 self.filestart = self.bundle.tell()
211 return m
210 return m
212
211
213 @util.propertycache
212 @util.propertycache
214 def manstart(self):
213 def manstart(self):
215 self.changelog
214 self.changelog
216 return self.manstart
215 return self.manstart
217
216
218 @util.propertycache
217 @util.propertycache
219 def filestart(self):
218 def filestart(self):
220 self.manifest
219 self.manifest
221 return self.filestart
220 return self.filestart
222
221
223 def url(self):
222 def url(self):
224 return self._url
223 return self._url
225
224
226 def file(self, f):
225 def file(self, f):
227 if not self.bundlefilespos:
226 if not self.bundlefilespos:
228 self.bundlefile.seek(self.filestart)
227 self.bundle.seek(self.filestart)
229 while 1:
228 while 1:
230 chunk = changegroup.getchunk(self.bundlefile)
229 chunk = changegroup.getchunk(self.bundle)
231 if not chunk:
230 if not chunk:
232 break
231 break
233 self.bundlefilespos[chunk] = self.bundlefile.tell()
232 self.bundlefilespos[chunk] = self.bundle.tell()
234 for c in changegroup.chunkiter(self.bundlefile):
233 for c in changegroup.chunkiter(self.bundle):
235 pass
234 pass
236
235
237 if f[0] == '/':
236 if f[0] == '/':
238 f = f[1:]
237 f = f[1:]
239 if f in self.bundlefilespos:
238 if f in self.bundlefilespos:
240 self.bundlefile.seek(self.bundlefilespos[f])
239 self.bundle.seek(self.bundlefilespos[f])
241 return bundlefilelog(self.sopener, f, self.bundlefile,
240 return bundlefilelog(self.sopener, f, self.bundle,
242 self.changelog.rev)
241 self.changelog.rev)
243 else:
242 else:
244 return filelog.filelog(self.sopener, f)
243 return filelog.filelog(self.sopener, f)
245
244
246 def __del__(self):
245 def __del__(self):
247 bundlefile = getattr(self, 'bundlefile', None)
246 del self.bundle
248 if bundlefile and not bundlefile.closed:
249 bundlefile.close()
250 tempfile = getattr(self, 'tempfile', None)
251 if tempfile is not None:
247 if tempfile is not None:
252 os.unlink(tempfile)
248 os.unlink(tempfile)
253 if self._tempparent:
249 if self._tempparent:
254 shutil.rmtree(self._tempparent, True)
250 shutil.rmtree(self._tempparent, True)
255
251
256 def cancopy(self):
252 def cancopy(self):
257 return False
253 return False
258
254
259 def getcwd(self):
255 def getcwd(self):
260 return os.getcwd() # always outside the repo
256 return os.getcwd() # always outside the repo
261
257
262 def instance(ui, path, create):
258 def instance(ui, path, create):
263 if create:
259 if create:
264 raise util.Abort(_('cannot create new bundle repository'))
260 raise util.Abort(_('cannot create new bundle repository'))
265 parentpath = ui.config("bundle", "mainreporoot", "")
261 parentpath = ui.config("bundle", "mainreporoot", "")
266 if parentpath:
262 if parentpath:
267 # Try to make the full path relative so we get a nice, short URL.
263 # Try to make the full path relative so we get a nice, short URL.
268 # In particular, we don't want temp dir names in test outputs.
264 # In particular, we don't want temp dir names in test outputs.
269 cwd = os.getcwd()
265 cwd = os.getcwd()
270 if parentpath == cwd:
266 if parentpath == cwd:
271 parentpath = ''
267 parentpath = ''
272 else:
268 else:
273 cwd = os.path.join(cwd,'')
269 cwd = os.path.join(cwd,'')
274 if parentpath.startswith(cwd):
270 if parentpath.startswith(cwd):
275 parentpath = parentpath[len(cwd):]
271 parentpath = parentpath[len(cwd):]
276 path = util.drop_scheme('file', path)
272 path = util.drop_scheme('file', path)
277 if path.startswith('bundle:'):
273 if path.startswith('bundle:'):
278 path = util.drop_scheme('bundle', path)
274 path = util.drop_scheme('bundle', path)
279 s = path.split("+", 1)
275 s = path.split("+", 1)
280 if len(s) == 1:
276 if len(s) == 1:
281 repopath, bundlename = parentpath, s[0]
277 repopath, bundlename = parentpath, s[0]
282 else:
278 else:
283 repopath, bundlename = s
279 repopath, bundlename = s
284 else:
280 else:
285 repopath, bundlename = parentpath, path
281 repopath, bundlename = parentpath, path
286 return bundlerepository(ui, repopath, bundlename)
282 return bundlerepository(ui, repopath, bundlename)
@@ -1,181 +1,181
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 util.chunkbuffer(generator(fh))
139 return util.chunkbuffer(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 = decompressor(fh, alg)
143 self._stream = decompressor(fh, alg)
144 self._type = alg
144 self._type = alg
145 def compressed(self):
145 def compressed(self):
146 return self._type != 'UN'
146 return self._type != 'UN'
147 def read(self, l):
147 def read(self, l):
148 return self._stream.read(l)
148 return self._stream.read(l)
149 def seek(self, pos):
149 def seek(self, pos):
150 return self._stream.seek(pos)
150 return self._stream.seek(pos)
151 def tell(self):
151 def tell(self):
152 return self._stream.tell(pos)
152 return self._stream.tell()
153
153
154 class headerlessfixup(object):
154 class headerlessfixup(object):
155 def __init__(self, fh, h):
155 def __init__(self, fh, h):
156 self._h = h
156 self._h = h
157 self._fh = fh
157 self._fh = fh
158 def read(self, n):
158 def read(self, n):
159 if self._h:
159 if self._h:
160 d, self._h = self._h[:n], self._h[n:]
160 d, self._h = self._h[:n], self._h[n:]
161 if len(d) < n:
161 if len(d) < n:
162 d += self._fh.read(n - len(d))
162 d += self._fh.read(n - len(d))
163 return d
163 return d
164 return self._fh.read(n)
164 return self._fh.read(n)
165
165
166 def readbundle(fh, fname):
166 def readbundle(fh, fname):
167 header = fh.read(6)
167 header = fh.read(6)
168
168
169 if not fname:
169 if not fname:
170 fname = "stream"
170 fname = "stream"
171 if not header.startswith('HG') and header.startswith('\0'):
171 if not header.startswith('HG') and header.startswith('\0'):
172 fh = headerlessfixup(fh, header)
172 fh = headerlessfixup(fh, header)
173 header = "HG10UN"
173 header = "HG10UN"
174
174
175 magic, version, alg = header[0:2], header[2:4], header[4:6]
175 magic, version, alg = header[0:2], header[2:4], header[4:6]
176
176
177 if magic != 'HG':
177 if magic != 'HG':
178 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
178 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
179 if version != '10':
179 if version != '10':
180 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
180 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
181 return unbundle10(fh, alg)
181 return unbundle10(fh, alg)
General Comments 0
You need to be logged in to leave comments. Login now