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