##// END OF EJS Templates
bundlerepo: drop bundlebase wrapper function for basemap
Mads Kiilerich -
r18413:0ccb16f5 default
parent child Browse files
Show More
@@ -1,388 +1,387 b''
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, tempfile, shutil
17 17 import changegroup, util, mdiff, discovery, cmdutil, scmutil
18 18 import localrepo, changelog, manifest, filelog, revlog, error
19 19
20 20 class bundlerevlog(revlog.revlog):
21 21 def __init__(self, opener, indexfile, bundle, linkmapper):
22 22 # How it works:
23 23 # To retrieve a revision, we need to know the offset of the revision in
24 24 # the bundle (an unbundle object). We store this offset in the index
25 25 # (start).
26 26 #
27 27 # basemap is indexed with revisions coming from the bundle, and it
28 28 # maps to the node that is the base of the corresponding delta.
29 29 #
30 30 # To differentiate a rev in the bundle from a rev in the revlog, we
31 31 # check revision against basemap.
32 32 opener = scmutil.readonlyvfs(opener)
33 33 revlog.revlog.__init__(self, opener, indexfile)
34 34 self.bundle = bundle
35 35 self.basemap = {}
36 36 n = len(self)
37 37 chain = None
38 38 self.bundlerevs = set() # used by 'bundle()' revset expression
39 39 while True:
40 40 chunkdata = bundle.deltachunk(chain)
41 41 if not chunkdata:
42 42 break
43 43 node = chunkdata['node']
44 44 p1 = chunkdata['p1']
45 45 p2 = chunkdata['p2']
46 46 cs = chunkdata['cs']
47 47 deltabase = chunkdata['deltabase']
48 48 delta = chunkdata['delta']
49 49
50 50 size = len(delta)
51 51 start = bundle.tell() - size
52 52
53 53 link = linkmapper(cs)
54 54 if node in self.nodemap:
55 55 # this can happen if two branches make the same change
56 56 chain = node
57 57 self.bundlerevs.add(self.nodemap[node])
58 58 continue
59 59
60 60 for p in (p1, p2):
61 61 if p not in self.nodemap:
62 62 raise error.LookupError(p, self.indexfile,
63 63 _("unknown parent"))
64 64 # start, size, full unc. size, base (unused), link, p1, p2, node
65 65 e = (revlog.offset_type(start, 0), size, -1, -1, link,
66 66 self.rev(p1), self.rev(p2), node)
67 67 self.basemap[n] = deltabase
68 68 self.index.insert(-1, e)
69 69 self.nodemap[node] = n
70 70 self.bundlerevs.add(n)
71 71 chain = node
72 72 n += 1
73 73
74 74 def inbundle(self, rev):
75 75 """is rev from the bundle"""
76 76 if rev < 0:
77 77 return False
78 78 return rev in self.basemap
79 def bundlebase(self, rev):
80 return self.basemap[rev]
79
81 80 def _chunk(self, rev):
82 # Warning: in case of bundle, the diff is against bundlebase,
81 # Warning: in case of bundle, the diff is against self.basemap,
83 82 # not against rev - 1
84 83 # XXX: could use some caching
85 84 if not self.inbundle(rev):
86 85 return revlog.revlog._chunk(self, rev)
87 86 self.bundle.seek(self.start(rev))
88 87 return self.bundle.read(self.length(rev))
89 88
90 89 def revdiff(self, rev1, rev2):
91 90 """return or calculate a delta between two revisions"""
92 91 if self.inbundle(rev1) and self.inbundle(rev2):
93 92 # hot path for bundle
94 revb = self.rev(self.bundlebase(rev2))
93 revb = self.rev(self.basemap[rev2])
95 94 if revb == rev1:
96 95 return self._chunk(rev2)
97 96 elif not self.inbundle(rev1) and not self.inbundle(rev2):
98 97 return revlog.revlog.revdiff(self, rev1, rev2)
99 98
100 99 return mdiff.textdiff(self.revision(self.node(rev1)),
101 self.revision(self.node(rev2)))
100 self.revision(self.node(rev2)))
102 101
103 102 def revision(self, nodeorrev):
104 103 """return an uncompressed revision of a given node or revision
105 104 number.
106 105 """
107 106 if isinstance(nodeorrev, int):
108 107 rev = nodeorrev
109 108 node = self.node(rev)
110 109 else:
111 110 node = nodeorrev
112 111 rev = self.rev(node)
113 112
114 113 if node == nullid:
115 114 return ""
116 115
117 116 text = None
118 117 chain = []
119 118 iter_node = node
120 119 # reconstruct the revision if it is from a changegroup
121 120 while self.inbundle(rev):
122 121 if self._cache and self._cache[0] == iter_node:
123 122 text = self._cache[2]
124 123 break
125 124 chain.append(rev)
126 iter_node = self.bundlebase(rev)
125 iter_node = self.basemap[rev]
127 126 rev = self.rev(iter_node)
128 127 if text is None:
129 128 text = revlog.revlog.revision(self, iter_node)
130 129
131 130 while chain:
132 131 delta = self._chunk(chain.pop())
133 132 text = mdiff.patches(text, [delta])
134 133
135 134 p1, p2 = self.parents(node)
136 135 if node != revlog.hash(text, p1, p2):
137 136 raise error.RevlogError(_("integrity check failed on %s:%d")
138 137 % (self.datafile, self.rev(node)))
139 138
140 139 self._cache = (node, self.rev(node), text)
141 140 return text
142 141
143 142 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
144 143 raise NotImplementedError
145 144 def addgroup(self, revs, linkmapper, transaction):
146 145 raise NotImplementedError
147 146 def strip(self, rev, minlink):
148 147 raise NotImplementedError
149 148 def checksize(self):
150 149 raise NotImplementedError
151 150
152 151 class bundlechangelog(bundlerevlog, changelog.changelog):
153 152 def __init__(self, opener, bundle):
154 153 changelog.changelog.__init__(self, opener)
155 154 linkmapper = lambda x: x
156 155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
157 156 linkmapper)
158 157
159 158 class bundlemanifest(bundlerevlog, manifest.manifest):
160 159 def __init__(self, opener, bundle, linkmapper):
161 160 manifest.manifest.__init__(self, opener)
162 161 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
163 162 linkmapper)
164 163
165 164 class bundlefilelog(bundlerevlog, filelog.filelog):
166 165 def __init__(self, opener, path, bundle, linkmapper, repo):
167 166 filelog.filelog.__init__(self, opener, path)
168 167 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
169 168 linkmapper)
170 169 self._repo = repo
171 170
172 171 def _file(self, f):
173 172 self._repo.file(f)
174 173
175 174 class bundlepeer(localrepo.localpeer):
176 175 def canpush(self):
177 176 return False
178 177
179 178 class bundlerepository(localrepo.localrepository):
180 179 def __init__(self, ui, path, bundlename):
181 180 self._tempparent = None
182 181 try:
183 182 localrepo.localrepository.__init__(self, ui, path)
184 183 except error.RepoError:
185 184 self._tempparent = tempfile.mkdtemp()
186 185 localrepo.instance(ui, self._tempparent, 1)
187 186 localrepo.localrepository.__init__(self, ui, self._tempparent)
188 187 self.ui.setconfig('phases', 'publish', False)
189 188
190 189 if path:
191 190 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
192 191 else:
193 192 self._url = 'bundle:' + bundlename
194 193
195 194 self.tempfile = None
196 195 f = util.posixfile(bundlename, "rb")
197 196 self.bundle = changegroup.readbundle(f, bundlename)
198 197 if self.bundle.compressed():
199 198 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
200 199 suffix=".hg10un", dir=self.path)
201 200 self.tempfile = temp
202 201 fptemp = os.fdopen(fdtemp, 'wb')
203 202
204 203 try:
205 204 fptemp.write("HG10UN")
206 205 while True:
207 206 chunk = self.bundle.read(2**18)
208 207 if not chunk:
209 208 break
210 209 fptemp.write(chunk)
211 210 finally:
212 211 fptemp.close()
213 212
214 213 f = util.posixfile(self.tempfile, "rb")
215 214 self.bundle = changegroup.readbundle(f, bundlename)
216 215
217 216 # dict with the mapping 'filename' -> position in the bundle
218 217 self.bundlefilespos = {}
219 218
220 219 @localrepo.unfilteredpropertycache
221 220 def changelog(self):
222 221 # consume the header if it exists
223 222 self.bundle.changelogheader()
224 223 c = bundlechangelog(self.sopener, self.bundle)
225 224 self.manstart = self.bundle.tell()
226 225 return c
227 226
228 227 @localrepo.unfilteredpropertycache
229 228 def manifest(self):
230 229 self.bundle.seek(self.manstart)
231 230 # consume the header if it exists
232 231 self.bundle.manifestheader()
233 232 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
234 233 self.filestart = self.bundle.tell()
235 234 return m
236 235
237 236 @localrepo.unfilteredpropertycache
238 237 def manstart(self):
239 238 self.changelog
240 239 return self.manstart
241 240
242 241 @localrepo.unfilteredpropertycache
243 242 def filestart(self):
244 243 self.manifest
245 244 return self.filestart
246 245
247 246 def url(self):
248 247 return self._url
249 248
250 249 def file(self, f):
251 250 if not self.bundlefilespos:
252 251 self.bundle.seek(self.filestart)
253 252 while True:
254 253 chunkdata = self.bundle.filelogheader()
255 254 if not chunkdata:
256 255 break
257 256 fname = chunkdata['filename']
258 257 self.bundlefilespos[fname] = self.bundle.tell()
259 258 while True:
260 259 c = self.bundle.deltachunk(None)
261 260 if not c:
262 261 break
263 262
264 263 if f[0] == '/':
265 264 f = f[1:]
266 265 if f in self.bundlefilespos:
267 266 self.bundle.seek(self.bundlefilespos[f])
268 267 return bundlefilelog(self.sopener, f, self.bundle,
269 268 self.changelog.rev, self)
270 269 else:
271 270 return filelog.filelog(self.sopener, f)
272 271
273 272 def close(self):
274 273 """Close assigned bundle file immediately."""
275 274 self.bundle.close()
276 275 if self.tempfile is not None:
277 276 os.unlink(self.tempfile)
278 277 if self._tempparent:
279 278 shutil.rmtree(self._tempparent, True)
280 279
281 280 def cancopy(self):
282 281 return False
283 282
284 283 def peer(self):
285 284 return bundlepeer(self)
286 285
287 286 def getcwd(self):
288 287 return os.getcwd() # always outside the repo
289 288
290 289
291 290 def instance(ui, path, create):
292 291 if create:
293 292 raise util.Abort(_('cannot create new bundle repository'))
294 293 parentpath = ui.config("bundle", "mainreporoot", "")
295 294 if not parentpath:
296 295 # try to find the correct path to the working directory repo
297 296 parentpath = cmdutil.findrepo(os.getcwd())
298 297 if parentpath is None:
299 298 parentpath = ''
300 299 if parentpath:
301 300 # Try to make the full path relative so we get a nice, short URL.
302 301 # In particular, we don't want temp dir names in test outputs.
303 302 cwd = os.getcwd()
304 303 if parentpath == cwd:
305 304 parentpath = ''
306 305 else:
307 306 cwd = os.path.join(cwd,'')
308 307 if parentpath.startswith(cwd):
309 308 parentpath = parentpath[len(cwd):]
310 309 u = util.url(path)
311 310 path = u.localpath()
312 311 if u.scheme == 'bundle':
313 312 s = path.split("+", 1)
314 313 if len(s) == 1:
315 314 repopath, bundlename = parentpath, s[0]
316 315 else:
317 316 repopath, bundlename = s
318 317 else:
319 318 repopath, bundlename = parentpath, path
320 319 return bundlerepository(ui, repopath, bundlename)
321 320
322 321 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
323 322 force=False):
324 323 '''obtains a bundle of changes incoming from other
325 324
326 325 "onlyheads" restricts the returned changes to those reachable from the
327 326 specified heads.
328 327 "bundlename", if given, stores the bundle to this file path permanently;
329 328 otherwise it's stored to a temp file and gets deleted again when you call
330 329 the returned "cleanupfn".
331 330 "force" indicates whether to proceed on unrelated repos.
332 331
333 332 Returns a tuple (local, csets, cleanupfn):
334 333
335 334 "local" is a local repo from which to obtain the actual incoming
336 335 changesets; it is a bundlerepo for the obtained bundle when the
337 336 original "other" is remote.
338 337 "csets" lists the incoming changeset node ids.
339 338 "cleanupfn" must be called without arguments when you're done processing
340 339 the changes; it closes both the original "other" and the one returned
341 340 here.
342 341 '''
343 342 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
344 343 force=force)
345 344 common, incoming, rheads = tmp
346 345 if not incoming:
347 346 try:
348 347 if bundlename:
349 348 os.unlink(bundlename)
350 349 except OSError:
351 350 pass
352 351 return repo, [], other.close
353 352
354 353 bundle = None
355 354 bundlerepo = None
356 355 localrepo = other.local()
357 356 if bundlename or not localrepo:
358 357 # create a bundle (uncompressed if other repo is not local)
359 358
360 359 if other.capable('getbundle'):
361 360 cg = other.getbundle('incoming', common=common, heads=rheads)
362 361 elif onlyheads is None and not other.capable('changegroupsubset'):
363 362 # compat with older servers when pulling all remote heads
364 363 cg = other.changegroup(incoming, "incoming")
365 364 rheads = None
366 365 else:
367 366 cg = other.changegroupsubset(incoming, rheads, 'incoming')
368 367 bundletype = localrepo and "HG10BZ" or "HG10UN"
369 368 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
370 369 # keep written bundle?
371 370 if bundlename:
372 371 bundle = None
373 372 if not localrepo:
374 373 # use the created uncompressed bundlerepo
375 374 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
376 375 # this repo contains local and other now, so filter out local again
377 376 common = repo.heads()
378 377
379 378 csets = localrepo.changelog.findmissing(common, rheads)
380 379
381 380 def cleanup():
382 381 if bundlerepo:
383 382 bundlerepo.close()
384 383 if bundle:
385 384 os.unlink(bundle)
386 385 other.close()
387 386
388 387 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now