##// END OF EJS Templates
bundlerepo: indent some code to prepare next patch...
Pierre-Yves David -
r26543:a018cbab default
parent child Browse files
Show More
@@ -1,503 +1,505
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 __future__ import absolute_import
15 15
16 16 import os
17 17 import shutil
18 18 import tempfile
19 19
20 20 from .i18n import _
21 21 from .node import nullid
22 22
23 23 from . import (
24 24 bundle2,
25 25 changegroup,
26 26 changelog,
27 27 cmdutil,
28 28 discovery,
29 29 error,
30 30 exchange,
31 31 filelog,
32 32 localrepo,
33 33 manifest,
34 34 mdiff,
35 35 pathutil,
36 36 phases,
37 37 revlog,
38 38 scmutil,
39 39 util,
40 40 )
41 41
42 42 class bundlerevlog(revlog.revlog):
43 43 def __init__(self, opener, indexfile, bundle, linkmapper):
44 44 # How it works:
45 45 # To retrieve a revision, we need to know the offset of the revision in
46 46 # the bundle (an unbundle object). We store this offset in the index
47 47 # (start). The base of the delta is stored in the base field.
48 48 #
49 49 # To differentiate a rev in the bundle from a rev in the revlog, we
50 50 # check revision against repotiprev.
51 51 opener = scmutil.readonlyvfs(opener)
52 52 revlog.revlog.__init__(self, opener, indexfile)
53 53 self.bundle = bundle
54 54 n = len(self)
55 55 self.repotiprev = n - 1
56 56 chain = None
57 57 self.bundlerevs = set() # used by 'bundle()' revset expression
58 58 while True:
59 59 chunkdata = bundle.deltachunk(chain)
60 60 if not chunkdata:
61 61 break
62 62 node = chunkdata['node']
63 63 p1 = chunkdata['p1']
64 64 p2 = chunkdata['p2']
65 65 cs = chunkdata['cs']
66 66 deltabase = chunkdata['deltabase']
67 67 delta = chunkdata['delta']
68 68
69 69 size = len(delta)
70 70 start = bundle.tell() - size
71 71
72 72 link = linkmapper(cs)
73 73 if node in self.nodemap:
74 74 # this can happen if two branches make the same change
75 75 chain = node
76 76 self.bundlerevs.add(self.nodemap[node])
77 77 continue
78 78
79 79 for p in (p1, p2):
80 80 if p not in self.nodemap:
81 81 raise error.LookupError(p, self.indexfile,
82 82 _("unknown parent"))
83 83
84 84 if deltabase not in self.nodemap:
85 85 raise LookupError(deltabase, self.indexfile,
86 86 _('unknown delta base'))
87 87
88 88 baserev = self.rev(deltabase)
89 89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 91 self.rev(p1), self.rev(p2), node)
92 92 self.index.insert(-1, e)
93 93 self.nodemap[node] = n
94 94 self.bundlerevs.add(n)
95 95 chain = node
96 96 n += 1
97 97
98 98 def _chunk(self, rev):
99 99 # Warning: in case of bundle, the diff is against what we stored as
100 100 # delta base, not against rev - 1
101 101 # XXX: could use some caching
102 102 if rev <= self.repotiprev:
103 103 return revlog.revlog._chunk(self, rev)
104 104 self.bundle.seek(self.start(rev))
105 105 return self.bundle.read(self.length(rev))
106 106
107 107 def revdiff(self, rev1, rev2):
108 108 """return or calculate a delta between two revisions"""
109 109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 110 # hot path for bundle
111 111 revb = self.index[rev2][3]
112 112 if revb == rev1:
113 113 return self._chunk(rev2)
114 114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 115 return revlog.revlog.revdiff(self, rev1, rev2)
116 116
117 117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 118 self.revision(self.node(rev2)))
119 119
120 120 def revision(self, nodeorrev):
121 121 """return an uncompressed revision of a given node or revision
122 122 number.
123 123 """
124 124 if isinstance(nodeorrev, int):
125 125 rev = nodeorrev
126 126 node = self.node(rev)
127 127 else:
128 128 node = nodeorrev
129 129 rev = self.rev(node)
130 130
131 131 if node == nullid:
132 132 return ""
133 133
134 134 text = None
135 135 chain = []
136 136 iterrev = rev
137 137 # reconstruct the revision if it is from a changegroup
138 138 while iterrev > self.repotiprev:
139 139 if self._cache and self._cache[1] == iterrev:
140 140 text = self._cache[2]
141 141 break
142 142 chain.append(iterrev)
143 143 iterrev = self.index[iterrev][3]
144 144 if text is None:
145 145 text = self.baserevision(iterrev)
146 146
147 147 while chain:
148 148 delta = self._chunk(chain.pop())
149 149 text = mdiff.patches(text, [delta])
150 150
151 151 self._checkhash(text, node, rev)
152 152 self._cache = (node, rev, text)
153 153 return text
154 154
155 155 def baserevision(self, nodeorrev):
156 156 # Revlog subclasses may override 'revision' method to modify format of
157 157 # content retrieved from revlog. To use bundlerevlog with such class one
158 158 # needs to override 'baserevision' and make more specific call here.
159 159 return revlog.revlog.revision(self, nodeorrev)
160 160
161 161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 162 raise NotImplementedError
163 163 def addgroup(self, revs, linkmapper, transaction):
164 164 raise NotImplementedError
165 165 def strip(self, rev, minlink):
166 166 raise NotImplementedError
167 167 def checksize(self):
168 168 raise NotImplementedError
169 169
170 170 class bundlechangelog(bundlerevlog, changelog.changelog):
171 171 def __init__(self, opener, bundle):
172 172 changelog.changelog.__init__(self, opener)
173 173 linkmapper = lambda x: x
174 174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 175 linkmapper)
176 176
177 177 def baserevision(self, nodeorrev):
178 178 # Although changelog doesn't override 'revision' method, some extensions
179 179 # may replace this class with another that does. Same story with
180 180 # manifest and filelog classes.
181 181
182 182 # This bypasses filtering on changelog.node() and rev() because we need
183 183 # revision text of the bundle base even if it is hidden.
184 184 oldfilter = self.filteredrevs
185 185 try:
186 186 self.filteredrevs = ()
187 187 return changelog.changelog.revision(self, nodeorrev)
188 188 finally:
189 189 self.filteredrevs = oldfilter
190 190
191 191 class bundlemanifest(bundlerevlog, manifest.manifest):
192 192 def __init__(self, opener, bundle, linkmapper):
193 193 manifest.manifest.__init__(self, opener)
194 194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 195 linkmapper)
196 196
197 197 def baserevision(self, nodeorrev):
198 198 node = nodeorrev
199 199 if isinstance(node, int):
200 200 node = self.node(node)
201 201
202 202 if node in self._mancache:
203 203 result = self._mancache[node][0].text()
204 204 else:
205 205 result = manifest.manifest.revision(self, nodeorrev)
206 206 return result
207 207
208 208 class bundlefilelog(bundlerevlog, filelog.filelog):
209 209 def __init__(self, opener, path, bundle, linkmapper):
210 210 filelog.filelog.__init__(self, opener, path)
211 211 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
212 212 linkmapper)
213 213
214 214 def baserevision(self, nodeorrev):
215 215 return filelog.filelog.revision(self, nodeorrev)
216 216
217 217 class bundlepeer(localrepo.localpeer):
218 218 def canpush(self):
219 219 return False
220 220
221 221 class bundlephasecache(phases.phasecache):
222 222 def __init__(self, *args, **kwargs):
223 223 super(bundlephasecache, self).__init__(*args, **kwargs)
224 224 if util.safehasattr(self, 'opener'):
225 225 self.opener = scmutil.readonlyvfs(self.opener)
226 226
227 227 def write(self):
228 228 raise NotImplementedError
229 229
230 230 def _write(self, fp):
231 231 raise NotImplementedError
232 232
233 233 def _updateroots(self, phase, newroots, tr):
234 234 self.phaseroots[phase] = newroots
235 235 self.invalidate()
236 236 self.dirty = True
237 237
238 238 class bundlerepository(localrepo.localrepository):
239 239 def __init__(self, ui, path, bundlename):
240 240 self._tempparent = None
241 241 try:
242 242 localrepo.localrepository.__init__(self, ui, path)
243 243 except error.RepoError:
244 244 self._tempparent = tempfile.mkdtemp()
245 245 localrepo.instance(ui, self._tempparent, 1)
246 246 localrepo.localrepository.__init__(self, ui, self._tempparent)
247 247 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
248 248
249 249 if path:
250 250 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
251 251 else:
252 252 self._url = 'bundle:' + bundlename
253 253
254 254 self.tempfile = None
255 255 f = util.posixfile(bundlename, "rb")
256 256 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
257 257 if self.bundle.compressed():
258 258 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
259 259 suffix=".hg10un")
260 260 self.tempfile = temp
261 261 fptemp = os.fdopen(fdtemp, 'wb')
262 262
263 263 try:
264 264 fptemp.write("HG10UN")
265 265 while True:
266 266 chunk = self.bundle.read(2**18)
267 267 if not chunk:
268 268 break
269 269 fptemp.write(chunk)
270 270 finally:
271 271 fptemp.close()
272 272
273 273 f = self.vfs.open(self.tempfile, mode="rb")
274 274 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
275 275 bundlename,
276 276 self.vfs)
277 277
278 278 if isinstance(self.bundle, bundle2.unbundle20):
279 279 cgparts = [part for part in self.bundle.iterparts()
280 280 if (part.type == 'changegroup')
281 281 and (part.params.get('version', '01')
282 282 in changegroup.packermap)]
283 283
284 284 if not cgparts:
285 285 raise util.Abort('No changegroups found')
286 286 version = cgparts[0].params.get('version', '01')
287 287 cgparts = [p for p in cgparts
288 288 if p.params.get('version', '01') == version]
289 289 if len(cgparts) > 1:
290 290 raise NotImplementedError("Can't process multiple changegroups")
291 291 part = cgparts[0]
292 292
293 293 part.seek(0)
294 294 self.bundle = changegroup.packermap[version][1](part, 'UN')
295 295
296 296 # dict with the mapping 'filename' -> position in the bundle
297 297 self.bundlefilespos = {}
298 298
299 299 self.firstnewrev = self.changelog.repotiprev + 1
300 300 phases.retractboundary(self, None, phases.draft,
301 301 [ctx.node() for ctx in self[self.firstnewrev:]])
302 302
303 303 @localrepo.unfilteredpropertycache
304 304 def _phasecache(self):
305 305 return bundlephasecache(self, self._phasedefaults)
306 306
307 307 @localrepo.unfilteredpropertycache
308 308 def changelog(self):
309 309 # consume the header if it exists
310 310 self.bundle.changelogheader()
311 311 c = bundlechangelog(self.svfs, self.bundle)
312 312 self.manstart = self.bundle.tell()
313 313 return c
314 314
315 315 @localrepo.unfilteredpropertycache
316 316 def manifest(self):
317 317 self.bundle.seek(self.manstart)
318 318 # consume the header if it exists
319 319 self.bundle.manifestheader()
320 320 m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev)
321 321 self.filestart = self.bundle.tell()
322 322 return m
323 323
324 324 @localrepo.unfilteredpropertycache
325 325 def manstart(self):
326 326 self.changelog
327 327 return self.manstart
328 328
329 329 @localrepo.unfilteredpropertycache
330 330 def filestart(self):
331 331 self.manifest
332 332 return self.filestart
333 333
334 334 def url(self):
335 335 return self._url
336 336
337 337 def file(self, f):
338 338 if not self.bundlefilespos:
339 339 self.bundle.seek(self.filestart)
340 340 while True:
341 341 chunkdata = self.bundle.filelogheader()
342 342 if not chunkdata:
343 343 break
344 344 fname = chunkdata['filename']
345 345 self.bundlefilespos[fname] = self.bundle.tell()
346 346 while True:
347 347 c = self.bundle.deltachunk(None)
348 348 if not c:
349 349 break
350 350
351 351 if f in self.bundlefilespos:
352 352 self.bundle.seek(self.bundlefilespos[f])
353 353 return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev)
354 354 else:
355 355 return filelog.filelog(self.svfs, f)
356 356
357 357 def close(self):
358 358 """Close assigned bundle file immediately."""
359 359 self.bundlefile.close()
360 360 if self.tempfile is not None:
361 361 self.vfs.unlink(self.tempfile)
362 362 if self._tempparent:
363 363 shutil.rmtree(self._tempparent, True)
364 364
365 365 def cancopy(self):
366 366 return False
367 367
368 368 def peer(self):
369 369 return bundlepeer(self)
370 370
371 371 def getcwd(self):
372 372 return os.getcwd() # always outside the repo
373 373
374 374
375 375 def instance(ui, path, create):
376 376 if create:
377 377 raise util.Abort(_('cannot create new bundle repository'))
378 378 # internal config: bundle.mainreporoot
379 379 parentpath = ui.config("bundle", "mainreporoot", "")
380 380 if not parentpath:
381 381 # try to find the correct path to the working directory repo
382 382 parentpath = cmdutil.findrepo(os.getcwd())
383 383 if parentpath is None:
384 384 parentpath = ''
385 385 if parentpath:
386 386 # Try to make the full path relative so we get a nice, short URL.
387 387 # In particular, we don't want temp dir names in test outputs.
388 388 cwd = os.getcwd()
389 389 if parentpath == cwd:
390 390 parentpath = ''
391 391 else:
392 392 cwd = pathutil.normasprefix(cwd)
393 393 if parentpath.startswith(cwd):
394 394 parentpath = parentpath[len(cwd):]
395 395 u = util.url(path)
396 396 path = u.localpath()
397 397 if u.scheme == 'bundle':
398 398 s = path.split("+", 1)
399 399 if len(s) == 1:
400 400 repopath, bundlename = parentpath, s[0]
401 401 else:
402 402 repopath, bundlename = s
403 403 else:
404 404 repopath, bundlename = parentpath, path
405 405 return bundlerepository(ui, repopath, bundlename)
406 406
407 407 class bundletransactionmanager(object):
408 408 def transaction(self):
409 409 return None
410 410
411 411 def close(self):
412 412 raise NotImplementedError
413 413
414 414 def release(self):
415 415 raise NotImplementedError
416 416
417 417 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
418 418 force=False):
419 419 '''obtains a bundle of changes incoming from other
420 420
421 421 "onlyheads" restricts the returned changes to those reachable from the
422 422 specified heads.
423 423 "bundlename", if given, stores the bundle to this file path permanently;
424 424 otherwise it's stored to a temp file and gets deleted again when you call
425 425 the returned "cleanupfn".
426 426 "force" indicates whether to proceed on unrelated repos.
427 427
428 428 Returns a tuple (local, csets, cleanupfn):
429 429
430 430 "local" is a local repo from which to obtain the actual incoming
431 431 changesets; it is a bundlerepo for the obtained bundle when the
432 432 original "other" is remote.
433 433 "csets" lists the incoming changeset node ids.
434 434 "cleanupfn" must be called without arguments when you're done processing
435 435 the changes; it closes both the original "other" and the one returned
436 436 here.
437 437 '''
438 438 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
439 439 force=force)
440 440 common, incoming, rheads = tmp
441 441 if not incoming:
442 442 try:
443 443 if bundlename:
444 444 os.unlink(bundlename)
445 445 except OSError:
446 446 pass
447 447 return repo, [], other.close
448 448
449 449 commonset = set(common)
450 450 rheads = [x for x in rheads if x not in commonset]
451 451
452 452 bundle = None
453 453 bundlerepo = None
454 454 localrepo = other.local()
455 455 if bundlename or not localrepo:
456 456 # create a bundle (uncompressed if other repo is not local)
457 457
458 if True:
458 459 if other.capable('getbundle'):
459 460 cg = other.getbundle('incoming', common=common, heads=rheads)
460 461 elif onlyheads is None and not other.capable('changegroupsubset'):
461 462 # compat with older servers when pulling all remote heads
462 463 cg = other.changegroup(incoming, "incoming")
463 464 rheads = None
464 465 else:
465 466 cg = other.changegroupsubset(incoming, rheads, 'incoming')
466 467 if localrepo:
467 468 bundletype = "HG10BZ"
468 469 else:
469 470 bundletype = "HG10UN"
470 fname = bundle = changegroup.writebundle(ui, cg, bundlename, bundletype)
471 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
472 bundletype)
471 473 # keep written bundle?
472 474 if bundlename:
473 475 bundle = None
474 476 if not localrepo:
475 477 # use the created uncompressed bundlerepo
476 478 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
477 479 fname)
478 480 # this repo contains local and other now, so filter out local again
479 481 common = repo.heads()
480 482 if localrepo:
481 483 # Part of common may be remotely filtered
482 484 # So use an unfiltered version
483 485 # The discovery process probably need cleanup to avoid that
484 486 localrepo = localrepo.unfiltered()
485 487
486 488 csets = localrepo.changelog.findmissing(common, rheads)
487 489
488 490 if bundlerepo:
489 491 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
490 492 remotephases = other.listkeys('phases')
491 493
492 494 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
493 495 pullop.trmanager = bundletransactionmanager()
494 496 exchange._pullapplyphases(pullop, remotephases)
495 497
496 498 def cleanup():
497 499 if bundlerepo:
498 500 bundlerepo.close()
499 501 if bundle:
500 502 os.unlink(bundle)
501 503 other.close()
502 504
503 505 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now