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