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