##// END OF EJS Templates
Add ability to directly clone from all-history bundles...
John Mulligan -
r6314:9a1c5928 default
parent child Browse files
Show More
@@ -1,280 +1,288
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, mdiff
15 import changegroup, util, os, struct, bz2, tempfile, shutil, mdiff
16 import 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 localrepo.localrepository.__init__(self, ui, path)
156 self._tempparent = None
157 try:
158 localrepo.localrepository.__init__(self, ui, path)
159 except repo.RepoError:
160 self._tempparent = tempfile.mkdtemp()
161 tmprepo = localrepo.instance(ui,self._tempparent,1)
162 localrepo.localrepository.__init__(self, ui, self._tempparent)
157
163
158 if path:
164 if path:
159 self._url = 'bundle:' + path + '+' + bundlename
165 self._url = 'bundle:' + path + '+' + bundlename
160 else:
166 else:
161 self._url = 'bundle:' + bundlename
167 self._url = 'bundle:' + bundlename
162
168
163 self.tempfile = None
169 self.tempfile = None
164 self.bundlefile = open(bundlename, "rb")
170 self.bundlefile = open(bundlename, "rb")
165 header = self.bundlefile.read(6)
171 header = self.bundlefile.read(6)
166 if not header.startswith("HG"):
172 if not header.startswith("HG"):
167 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
173 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
168 elif not header.startswith("HG10"):
174 elif not header.startswith("HG10"):
169 raise util.Abort(_("%s: unknown bundle version") % bundlename)
175 raise util.Abort(_("%s: unknown bundle version") % bundlename)
170 elif header == "HG10BZ":
176 elif header == "HG10BZ":
171 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
172 suffix=".hg10un", dir=self.path)
178 suffix=".hg10un", dir=self.path)
173 self.tempfile = temp
179 self.tempfile = temp
174 fptemp = os.fdopen(fdtemp, 'wb')
180 fptemp = os.fdopen(fdtemp, 'wb')
175 def generator(f):
181 def generator(f):
176 zd = bz2.BZ2Decompressor()
182 zd = bz2.BZ2Decompressor()
177 zd.decompress("BZ")
183 zd.decompress("BZ")
178 for chunk in f:
184 for chunk in f:
179 yield zd.decompress(chunk)
185 yield zd.decompress(chunk)
180 gen = generator(util.filechunkiter(self.bundlefile, 4096))
186 gen = generator(util.filechunkiter(self.bundlefile, 4096))
181
187
182 try:
188 try:
183 fptemp.write("HG10UN")
189 fptemp.write("HG10UN")
184 for chunk in gen:
190 for chunk in gen:
185 fptemp.write(chunk)
191 fptemp.write(chunk)
186 finally:
192 finally:
187 fptemp.close()
193 fptemp.close()
188 self.bundlefile.close()
194 self.bundlefile.close()
189
195
190 self.bundlefile = open(self.tempfile, "rb")
196 self.bundlefile = open(self.tempfile, "rb")
191 # seek right after the header
197 # seek right after the header
192 self.bundlefile.seek(6)
198 self.bundlefile.seek(6)
193 elif header == "HG10UN":
199 elif header == "HG10UN":
194 # nothing to do
200 # nothing to do
195 pass
201 pass
196 else:
202 else:
197 raise util.Abort(_("%s: unknown bundle compression type")
203 raise util.Abort(_("%s: unknown bundle compression type")
198 % bundlename)
204 % bundlename)
199 # dict with the mapping 'filename' -> position in the bundle
205 # dict with the mapping 'filename' -> position in the bundle
200 self.bundlefilespos = {}
206 self.bundlefilespos = {}
201
207
202 def __getattr__(self, name):
208 def __getattr__(self, name):
203 if name == 'changelog':
209 if name == 'changelog':
204 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
210 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
205 self.manstart = self.bundlefile.tell()
211 self.manstart = self.bundlefile.tell()
206 return self.changelog
212 return self.changelog
207 if name == 'manifest':
213 if name == 'manifest':
208 self.bundlefile.seek(self.manstart)
214 self.bundlefile.seek(self.manstart)
209 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
215 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
210 self.changelog.rev)
216 self.changelog.rev)
211 self.filestart = self.bundlefile.tell()
217 self.filestart = self.bundlefile.tell()
212 return self.manifest
218 return self.manifest
213 if name == 'manstart':
219 if name == 'manstart':
214 self.changelog
220 self.changelog
215 return self.manstart
221 return self.manstart
216 if name == 'filestart':
222 if name == 'filestart':
217 self.manifest
223 self.manifest
218 return self.filestart
224 return self.filestart
219 return localrepo.localrepository.__getattr__(self, name)
225 return localrepo.localrepository.__getattr__(self, name)
220
226
221 def url(self):
227 def url(self):
222 return self._url
228 return self._url
223
229
224 def file(self, f):
230 def file(self, f):
225 if not self.bundlefilespos:
231 if not self.bundlefilespos:
226 self.bundlefile.seek(self.filestart)
232 self.bundlefile.seek(self.filestart)
227 while 1:
233 while 1:
228 chunk = changegroup.getchunk(self.bundlefile)
234 chunk = changegroup.getchunk(self.bundlefile)
229 if not chunk:
235 if not chunk:
230 break
236 break
231 self.bundlefilespos[chunk] = self.bundlefile.tell()
237 self.bundlefilespos[chunk] = self.bundlefile.tell()
232 for c in changegroup.chunkiter(self.bundlefile):
238 for c in changegroup.chunkiter(self.bundlefile):
233 pass
239 pass
234
240
235 if f[0] == '/':
241 if f[0] == '/':
236 f = f[1:]
242 f = f[1:]
237 if f in self.bundlefilespos:
243 if f in self.bundlefilespos:
238 self.bundlefile.seek(self.bundlefilespos[f])
244 self.bundlefile.seek(self.bundlefilespos[f])
239 return bundlefilelog(self.sopener, f, self.bundlefile,
245 return bundlefilelog(self.sopener, f, self.bundlefile,
240 self.changelog.rev)
246 self.changelog.rev)
241 else:
247 else:
242 return filelog.filelog(self.sopener, f)
248 return filelog.filelog(self.sopener, f)
243
249
244 def close(self):
250 def close(self):
245 """Close assigned bundle file immediately."""
251 """Close assigned bundle file immediately."""
246 self.bundlefile.close()
252 self.bundlefile.close()
247
253
248 def __del__(self):
254 def __del__(self):
249 bundlefile = getattr(self, 'bundlefile', None)
255 bundlefile = getattr(self, 'bundlefile', None)
250 if bundlefile and not bundlefile.closed:
256 if bundlefile and not bundlefile.closed:
251 bundlefile.close()
257 bundlefile.close()
252 tempfile = getattr(self, 'tempfile', None)
258 tempfile = getattr(self, 'tempfile', None)
253 if tempfile is not None:
259 if tempfile is not None:
254 os.unlink(tempfile)
260 os.unlink(tempfile)
261 if self._tempparent:
262 shutil.rmtree(self._tempparent, True)
255
263
256 def instance(ui, path, create):
264 def instance(ui, path, create):
257 if create:
265 if create:
258 raise util.Abort(_('cannot create new bundle repository'))
266 raise util.Abort(_('cannot create new bundle repository'))
259 parentpath = ui.config("bundle", "mainreporoot", "")
267 parentpath = ui.config("bundle", "mainreporoot", "")
260 if parentpath:
268 if parentpath:
261 # Try to make the full path relative so we get a nice, short URL.
269 # Try to make the full path relative so we get a nice, short URL.
262 # In particular, we don't want temp dir names in test outputs.
270 # In particular, we don't want temp dir names in test outputs.
263 cwd = os.getcwd()
271 cwd = os.getcwd()
264 if parentpath == cwd:
272 if parentpath == cwd:
265 parentpath = ''
273 parentpath = ''
266 else:
274 else:
267 cwd = os.path.join(cwd,'')
275 cwd = os.path.join(cwd,'')
268 if parentpath.startswith(cwd):
276 if parentpath.startswith(cwd):
269 parentpath = parentpath[len(cwd):]
277 parentpath = parentpath[len(cwd):]
270 path = util.drop_scheme('file', path)
278 path = util.drop_scheme('file', path)
271 if path.startswith('bundle:'):
279 if path.startswith('bundle:'):
272 path = util.drop_scheme('bundle', path)
280 path = util.drop_scheme('bundle', path)
273 s = path.split("+", 1)
281 s = path.split("+", 1)
274 if len(s) == 1:
282 if len(s) == 1:
275 repopath, bundlename = parentpath, s[0]
283 repopath, bundlename = parentpath, s[0]
276 else:
284 else:
277 repopath, bundlename = s
285 repopath, bundlename = s
278 else:
286 else:
279 repopath, bundlename = parentpath, path
287 repopath, bundlename = parentpath, path
280 return bundlerepository(ui, repopath, bundlename)
288 return bundlerepository(ui, repopath, bundlename)
General Comments 0
You need to be logged in to leave comments. Login now