##// END OF EJS Templates
bundlerepo: port to new bundle API
Benoit Boissinot -
r14142:cb91ea6a default
parent child Browse files
Show More
@@ -1,322 +1,319
1 1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 2 #
3 3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Repository class for viewing uncompressed bundles.
9 9
10 10 This provides a read-only repository interface to bundles as if they
11 11 were part of the actual repository.
12 12 """
13 13
14 14 from node import nullid
15 15 from i18n import _
16 16 import os, struct, tempfile, shutil
17 17 import changegroup, util, mdiff, discovery
18 18 import localrepo, changelog, manifest, filelog, revlog, error
19 19
20 20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, bundle,
22 linkmapper=None):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
23 22 # How it works:
24 23 # to retrieve a revision, we need to know the offset of
25 24 # the revision in the bundle (an unbundle object).
26 25 #
27 26 # We store this offset in the index (start), to differentiate a
28 27 # rev in the bundle and from a rev in the revlog, we check
29 28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
30 29 # (it is bigger since we store the node to which the delta is)
31 30 #
32 31 revlog.revlog.__init__(self, opener, indexfile)
33 32 self.bundle = bundle
34 33 self.basemap = {}
35 def chunkpositer():
36 while 1:
37 chunk = bundle.chunk()
38 if not chunk:
39 break
40 pos = bundle.tell()
41 yield chunk, pos - len(chunk)
42 34 n = len(self)
43 prev = None
44 for chunk, start in chunkpositer():
45 size = len(chunk)
46 if size < 80:
47 raise util.Abort(_("invalid changegroup"))
48 start += 80
49 size -= 80
50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
35 chain = None
36 while 1:
37 chunkdata = bundle.parsechunk(chain)
38 if not chunkdata:
39 break
40 node = chunkdata['node']
41 p1 = chunkdata['p1']
42 p2 = chunkdata['p2']
43 cs = chunkdata['cs']
44 deltabase = chunkdata['deltabase']
45 delta = chunkdata['delta']
46
47 size = len(delta)
48 start = bundle.tell() - size
49
50 link = linkmapper(cs)
51 51 if node in self.nodemap:
52 prev = node
52 # this can happen if two branches make the same change
53 chain = node
53 54 continue
55
54 56 for p in (p1, p2):
55 57 if not p in self.nodemap:
56 58 raise error.LookupError(p, self.indexfile,
57 59 _("unknown parent"))
58 if linkmapper is None:
59 link = n
60 else:
61 link = linkmapper(cs)
62
63 if not prev:
64 prev = p1
65 60 # start, size, full unc. size, base (unused), link, p1, p2, node
66 61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
67 62 self.rev(p1), self.rev(p2), node)
68 self.basemap[n] = prev
63 self.basemap[n] = deltabase
69 64 self.index.insert(-1, e)
70 65 self.nodemap[node] = n
71 prev = node
66 chain = node
72 67 n += 1
73 68
74 69 def inbundle(self, rev):
75 70 """is rev from the bundle"""
76 71 if rev < 0:
77 72 return False
78 73 return rev in self.basemap
79 74 def bundlebase(self, rev):
80 75 return self.basemap[rev]
81 76 def _chunk(self, rev):
82 77 # Warning: in case of bundle, the diff is against bundlebase,
83 78 # not against rev - 1
84 79 # XXX: could use some caching
85 80 if not self.inbundle(rev):
86 81 return revlog.revlog._chunk(self, rev)
87 82 self.bundle.seek(self.start(rev))
88 83 return self.bundle.read(self.length(rev))
89 84
90 85 def revdiff(self, rev1, rev2):
91 86 """return or calculate a delta between two revisions"""
92 87 if self.inbundle(rev1) and self.inbundle(rev2):
93 88 # hot path for bundle
94 89 revb = self.rev(self.bundlebase(rev2))
95 90 if revb == rev1:
96 91 return self._chunk(rev2)
97 92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
98 93 return revlog.revlog.revdiff(self, rev1, rev2)
99 94
100 95 return mdiff.textdiff(self.revision(self.node(rev1)),
101 96 self.revision(self.node(rev2)))
102 97
103 98 def revision(self, node):
104 99 """return an uncompressed revision of a given"""
105 100 if node == nullid:
106 101 return ""
107 102
108 103 text = None
109 104 chain = []
110 105 iter_node = node
111 106 rev = self.rev(iter_node)
112 107 # reconstruct the revision if it is from a changegroup
113 108 while self.inbundle(rev):
114 109 if self._cache and self._cache[0] == iter_node:
115 110 text = self._cache[2]
116 111 break
117 112 chain.append(rev)
118 113 iter_node = self.bundlebase(rev)
119 114 rev = self.rev(iter_node)
120 115 if text is None:
121 116 text = revlog.revlog.revision(self, iter_node)
122 117
123 118 while chain:
124 119 delta = self._chunk(chain.pop())
125 120 text = mdiff.patches(text, [delta])
126 121
127 122 p1, p2 = self.parents(node)
128 123 if node != revlog.hash(text, p1, p2):
129 124 raise error.RevlogError(_("integrity check failed on %s:%d")
130 125 % (self.datafile, self.rev(node)))
131 126
132 127 self._cache = (node, self.rev(node), text)
133 128 return text
134 129
135 130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
136 131 raise NotImplementedError
137 132 def addgroup(self, revs, linkmapper, transaction):
138 133 raise NotImplementedError
139 134 def strip(self, rev, minlink):
140 135 raise NotImplementedError
141 136 def checksize(self):
142 137 raise NotImplementedError
143 138
144 139 class bundlechangelog(bundlerevlog, changelog.changelog):
145 140 def __init__(self, opener, bundle):
146 141 changelog.changelog.__init__(self, opener)
147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
142 linkmapper = lambda x: x
143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
144 linkmapper)
148 145
149 146 class bundlemanifest(bundlerevlog, manifest.manifest):
150 147 def __init__(self, opener, bundle, linkmapper):
151 148 manifest.manifest.__init__(self, opener)
152 149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
153 150 linkmapper)
154 151
155 152 class bundlefilelog(bundlerevlog, filelog.filelog):
156 153 def __init__(self, opener, path, bundle, linkmapper):
157 154 filelog.filelog.__init__(self, opener, path)
158 155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
159 156 linkmapper)
160 157
161 158 class bundlerepository(localrepo.localrepository):
162 159 def __init__(self, ui, path, bundlename):
163 160 self._tempparent = None
164 161 try:
165 162 localrepo.localrepository.__init__(self, ui, path)
166 163 except error.RepoError:
167 164 self._tempparent = tempfile.mkdtemp()
168 165 localrepo.instance(ui, self._tempparent, 1)
169 166 localrepo.localrepository.__init__(self, ui, self._tempparent)
170 167
171 168 if path:
172 169 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
173 170 else:
174 171 self._url = 'bundle:' + bundlename
175 172
176 173 self.tempfile = None
177 174 f = util.posixfile(bundlename, "rb")
178 175 self.bundle = changegroup.readbundle(f, bundlename)
179 176 if self.bundle.compressed():
180 177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
181 178 suffix=".hg10un", dir=self.path)
182 179 self.tempfile = temp
183 180 fptemp = os.fdopen(fdtemp, 'wb')
184 181
185 182 try:
186 183 fptemp.write("HG10UN")
187 184 while 1:
188 185 chunk = self.bundle.read(2**18)
189 186 if not chunk:
190 187 break
191 188 fptemp.write(chunk)
192 189 finally:
193 190 fptemp.close()
194 191
195 192 f = util.posixfile(self.tempfile, "rb")
196 193 self.bundle = changegroup.readbundle(f, bundlename)
197 194
198 195 # dict with the mapping 'filename' -> position in the bundle
199 196 self.bundlefilespos = {}
200 197
201 198 @util.propertycache
202 199 def changelog(self):
203 200 c = bundlechangelog(self.sopener, self.bundle)
204 201 self.manstart = self.bundle.tell()
205 202 return c
206 203
207 204 @util.propertycache
208 205 def manifest(self):
209 206 self.bundle.seek(self.manstart)
210 207 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
211 208 self.filestart = self.bundle.tell()
212 209 return m
213 210
214 211 @util.propertycache
215 212 def manstart(self):
216 213 self.changelog
217 214 return self.manstart
218 215
219 216 @util.propertycache
220 217 def filestart(self):
221 218 self.manifest
222 219 return self.filestart
223 220
224 221 def url(self):
225 222 return self._url
226 223
227 224 def file(self, f):
228 225 if not self.bundlefilespos:
229 226 self.bundle.seek(self.filestart)
230 227 while 1:
231 228 chunk = self.bundle.chunk()
232 229 if not chunk:
233 230 break
234 231 self.bundlefilespos[chunk] = self.bundle.tell()
235 232 while 1:
236 233 c = self.bundle.chunk()
237 234 if not c:
238 235 break
239 236
240 237 if f[0] == '/':
241 238 f = f[1:]
242 239 if f in self.bundlefilespos:
243 240 self.bundle.seek(self.bundlefilespos[f])
244 241 return bundlefilelog(self.sopener, f, self.bundle,
245 242 self.changelog.rev)
246 243 else:
247 244 return filelog.filelog(self.sopener, f)
248 245
249 246 def close(self):
250 247 """Close assigned bundle file immediately."""
251 248 self.bundle.close()
252 249 if self.tempfile is not None:
253 250 os.unlink(self.tempfile)
254 251 if self._tempparent:
255 252 shutil.rmtree(self._tempparent, True)
256 253
257 254 def cancopy(self):
258 255 return False
259 256
260 257 def getcwd(self):
261 258 return os.getcwd() # always outside the repo
262 259
263 260 def instance(ui, path, create):
264 261 if create:
265 262 raise util.Abort(_('cannot create new bundle repository'))
266 263 parentpath = ui.config("bundle", "mainreporoot", "")
267 264 if parentpath:
268 265 # Try to make the full path relative so we get a nice, short URL.
269 266 # In particular, we don't want temp dir names in test outputs.
270 267 cwd = os.getcwd()
271 268 if parentpath == cwd:
272 269 parentpath = ''
273 270 else:
274 271 cwd = os.path.join(cwd,'')
275 272 if parentpath.startswith(cwd):
276 273 parentpath = parentpath[len(cwd):]
277 274 u = util.url(path)
278 275 path = u.localpath()
279 276 if u.scheme == 'bundle':
280 277 s = path.split("+", 1)
281 278 if len(s) == 1:
282 279 repopath, bundlename = parentpath, s[0]
283 280 else:
284 281 repopath, bundlename = s
285 282 else:
286 283 repopath, bundlename = parentpath, path
287 284 return bundlerepository(ui, repopath, bundlename)
288 285
289 286 def getremotechanges(ui, repo, other, revs=None, bundlename=None,
290 287 force=False):
291 288 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
292 289 common, incoming, rheads = tmp
293 290 if not incoming:
294 291 try:
295 292 os.unlink(bundlename)
296 293 except OSError:
297 294 pass
298 295 return other, None, None, None
299 296
300 297 bundle = None
301 298 if bundlename or not other.local():
302 299 # create a bundle (uncompressed if other repo is not local)
303 300
304 301 if revs is None and other.capable('changegroupsubset'):
305 302 revs = rheads
306 303
307 304 if other.capable('getbundle'):
308 305 cg = other.getbundle('incoming', common=common, heads=revs)
309 306 elif revs is None:
310 307 cg = other.changegroup(incoming, "incoming")
311 308 else:
312 309 cg = other.changegroupsubset(incoming, revs, 'incoming')
313 310 bundletype = other.local() and "HG10BZ" or "HG10UN"
314 311 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
315 312 # keep written bundle?
316 313 if bundlename:
317 314 bundle = None
318 315 if not other.local():
319 316 # use the created uncompressed bundlerepo
320 317 other = bundlerepository(ui, repo.root, fname)
321 318 return (other, common, incoming, bundle)
322 319
General Comments 0
You need to be logged in to leave comments. Login now