##// END OF EJS Templates
bundlerepo: don't insert index tuples with full nodes as linkrev...
Joerg Sonnenberger -
r46418:88d5abec default
parent child Browse files
Show More
@@ -1,669 +1,672 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
19 19 from .i18n import _
20 20 from .node import nullid, nullrev
21 21
22 22 from . import (
23 23 bundle2,
24 24 changegroup,
25 25 changelog,
26 26 cmdutil,
27 27 discovery,
28 28 encoding,
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
45 45 class bundlerevlog(revlog.revlog):
46 46 def __init__(self, opener, indexfile, cgunpacker, linkmapper):
47 47 # How it works:
48 48 # To retrieve a revision, we need to know the offset of the revision in
49 49 # the bundle (an unbundle object). We store this offset in the index
50 50 # (start). The base of the delta is stored in the base field.
51 51 #
52 52 # To differentiate a rev in the bundle from a rev in the revlog, we
53 53 # check revision against repotiprev.
54 54 opener = vfsmod.readonlyvfs(opener)
55 55 revlog.revlog.__init__(self, opener, indexfile)
56 56 self.bundle = cgunpacker
57 57 n = len(self)
58 58 self.repotiprev = n - 1
59 59 self.bundlerevs = set() # used by 'bundle()' revset expression
60 60 for deltadata in cgunpacker.deltaiter():
61 61 node, p1, p2, cs, deltabase, delta, flags = deltadata
62 62
63 63 size = len(delta)
64 64 start = cgunpacker.tell() - size
65 65
66 link = linkmapper(cs)
67 66 if self.index.has_node(node):
68 67 # this can happen if two branches make the same change
69 68 self.bundlerevs.add(self.index.rev(node))
70 69 continue
70 if cs == node:
71 linkrev = nullrev
72 else:
73 linkrev = linkmapper(cs)
71 74
72 75 for p in (p1, p2):
73 76 if not self.index.has_node(p):
74 77 raise error.LookupError(
75 78 p, self.indexfile, _(b"unknown parent")
76 79 )
77 80
78 81 if not self.index.has_node(deltabase):
79 82 raise LookupError(
80 83 deltabase, self.indexfile, _(b'unknown delta base')
81 84 )
82 85
83 86 baserev = self.rev(deltabase)
84 87 # start, size, full unc. size, base (unused), link, p1, p2, node
85 88 e = (
86 89 revlog.offset_type(start, flags),
87 90 size,
88 91 -1,
89 92 baserev,
90 link,
93 linkrev,
91 94 self.rev(p1),
92 95 self.rev(p2),
93 96 node,
94 97 )
95 98 self.index.append(e)
96 99 self.bundlerevs.add(n)
97 100 n += 1
98 101
99 102 def _chunk(self, rev, df=None):
100 103 # Warning: in case of bundle, the diff is against what we stored as
101 104 # delta base, not against rev - 1
102 105 # XXX: could use some caching
103 106 if rev <= self.repotiprev:
104 107 return revlog.revlog._chunk(self, rev)
105 108 self.bundle.seek(self.start(rev))
106 109 return self.bundle.read(self.length(rev))
107 110
108 111 def revdiff(self, rev1, rev2):
109 112 """return or calculate a delta between two revisions"""
110 113 if rev1 > self.repotiprev and rev2 > self.repotiprev:
111 114 # hot path for bundle
112 115 revb = self.index[rev2][3]
113 116 if revb == rev1:
114 117 return self._chunk(rev2)
115 118 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
116 119 return revlog.revlog.revdiff(self, rev1, rev2)
117 120
118 121 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
119 122
120 123 def _rawtext(self, node, rev, _df=None):
121 124 if rev is None:
122 125 rev = self.rev(node)
123 126 validated = False
124 127 rawtext = None
125 128 chain = []
126 129 iterrev = rev
127 130 # reconstruct the revision if it is from a changegroup
128 131 while iterrev > self.repotiprev:
129 132 if self._revisioncache and self._revisioncache[1] == iterrev:
130 133 rawtext = self._revisioncache[2]
131 134 break
132 135 chain.append(iterrev)
133 136 iterrev = self.index[iterrev][3]
134 137 if iterrev == nullrev:
135 138 rawtext = b''
136 139 elif rawtext is None:
137 140 r = super(bundlerevlog, self)._rawtext(
138 141 self.node(iterrev), iterrev, _df=_df
139 142 )
140 143 __, rawtext, validated = r
141 144 if chain:
142 145 validated = False
143 146 while chain:
144 147 delta = self._chunk(chain.pop())
145 148 rawtext = mdiff.patches(rawtext, [delta])
146 149 return rev, rawtext, validated
147 150
148 151 def addrevision(self, *args, **kwargs):
149 152 raise NotImplementedError
150 153
151 154 def addgroup(self, *args, **kwargs):
152 155 raise NotImplementedError
153 156
154 157 def strip(self, *args, **kwargs):
155 158 raise NotImplementedError
156 159
157 160 def checksize(self):
158 161 raise NotImplementedError
159 162
160 163
161 164 class bundlechangelog(bundlerevlog, changelog.changelog):
162 165 def __init__(self, opener, cgunpacker):
163 166 changelog.changelog.__init__(self, opener)
164 167 linkmapper = lambda x: x
165 168 bundlerevlog.__init__(
166 169 self, opener, self.indexfile, cgunpacker, linkmapper
167 170 )
168 171
169 172
170 173 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
171 174 def __init__(
172 175 self, opener, cgunpacker, linkmapper, dirlogstarts=None, dir=b''
173 176 ):
174 177 manifest.manifestrevlog.__init__(self, opener, tree=dir)
175 178 bundlerevlog.__init__(
176 179 self, opener, self.indexfile, cgunpacker, linkmapper
177 180 )
178 181 if dirlogstarts is None:
179 182 dirlogstarts = {}
180 183 if self.bundle.version == b"03":
181 184 dirlogstarts = _getfilestarts(self.bundle)
182 185 self._dirlogstarts = dirlogstarts
183 186 self._linkmapper = linkmapper
184 187
185 188 def dirlog(self, d):
186 189 if d in self._dirlogstarts:
187 190 self.bundle.seek(self._dirlogstarts[d])
188 191 return bundlemanifest(
189 192 self.opener,
190 193 self.bundle,
191 194 self._linkmapper,
192 195 self._dirlogstarts,
193 196 dir=d,
194 197 )
195 198 return super(bundlemanifest, self).dirlog(d)
196 199
197 200
198 201 class bundlefilelog(filelog.filelog):
199 202 def __init__(self, opener, path, cgunpacker, linkmapper):
200 203 filelog.filelog.__init__(self, opener, path)
201 204 self._revlog = bundlerevlog(
202 205 opener, self.indexfile, cgunpacker, linkmapper
203 206 )
204 207
205 208
206 209 class bundlepeer(localrepo.localpeer):
207 210 def canpush(self):
208 211 return False
209 212
210 213
211 214 class bundlephasecache(phases.phasecache):
212 215 def __init__(self, *args, **kwargs):
213 216 super(bundlephasecache, self).__init__(*args, **kwargs)
214 217 if util.safehasattr(self, 'opener'):
215 218 self.opener = vfsmod.readonlyvfs(self.opener)
216 219
217 220 def write(self):
218 221 raise NotImplementedError
219 222
220 223 def _write(self, fp):
221 224 raise NotImplementedError
222 225
223 226 def _updateroots(self, phase, newroots, tr):
224 227 self.phaseroots[phase] = newroots
225 228 self.invalidate()
226 229 self.dirty = True
227 230
228 231
229 232 def _getfilestarts(cgunpacker):
230 233 filespos = {}
231 234 for chunkdata in iter(cgunpacker.filelogheader, {}):
232 235 fname = chunkdata[b'filename']
233 236 filespos[fname] = cgunpacker.tell()
234 237 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
235 238 pass
236 239 return filespos
237 240
238 241
239 242 class bundlerepository(object):
240 243 """A repository instance that is a union of a local repo and a bundle.
241 244
242 245 Instances represent a read-only repository composed of a local repository
243 246 with the contents of a bundle file applied. The repository instance is
244 247 conceptually similar to the state of a repository after an
245 248 ``hg unbundle`` operation. However, the contents of the bundle are never
246 249 applied to the actual base repository.
247 250
248 251 Instances constructed directly are not usable as repository objects.
249 252 Use instance() or makebundlerepository() to create instances.
250 253 """
251 254
252 255 def __init__(self, bundlepath, url, tempparent):
253 256 self._tempparent = tempparent
254 257 self._url = url
255 258
256 259 self.ui.setconfig(b'phases', b'publish', False, b'bundlerepo')
257 260
258 261 self.tempfile = None
259 262 f = util.posixfile(bundlepath, b"rb")
260 263 bundle = exchange.readbundle(self.ui, f, bundlepath)
261 264
262 265 if isinstance(bundle, bundle2.unbundle20):
263 266 self._bundlefile = bundle
264 267 self._cgunpacker = None
265 268
266 269 cgpart = None
267 270 for part in bundle.iterparts(seekable=True):
268 271 if part.type == b'changegroup':
269 272 if cgpart:
270 273 raise NotImplementedError(
271 274 b"can't process multiple changegroups"
272 275 )
273 276 cgpart = part
274 277
275 278 self._handlebundle2part(bundle, part)
276 279
277 280 if not cgpart:
278 281 raise error.Abort(_(b"No changegroups found"))
279 282
280 283 # This is required to placate a later consumer, which expects
281 284 # the payload offset to be at the beginning of the changegroup.
282 285 # We need to do this after the iterparts() generator advances
283 286 # because iterparts() will seek to end of payload after the
284 287 # generator returns control to iterparts().
285 288 cgpart.seek(0, os.SEEK_SET)
286 289
287 290 elif isinstance(bundle, changegroup.cg1unpacker):
288 291 if bundle.compressed():
289 292 f = self._writetempbundle(
290 293 bundle.read, b'.hg10un', header=b'HG10UN'
291 294 )
292 295 bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
293 296
294 297 self._bundlefile = bundle
295 298 self._cgunpacker = bundle
296 299 else:
297 300 raise error.Abort(
298 301 _(b'bundle type %s cannot be read') % type(bundle)
299 302 )
300 303
301 304 # dict with the mapping 'filename' -> position in the changegroup.
302 305 self._cgfilespos = {}
303 306
304 307 self.firstnewrev = self.changelog.repotiprev + 1
305 308 phases.retractboundary(
306 309 self,
307 310 None,
308 311 phases.draft,
309 312 [ctx.node() for ctx in self[self.firstnewrev :]],
310 313 )
311 314
312 315 def _handlebundle2part(self, bundle, part):
313 316 if part.type != b'changegroup':
314 317 return
315 318
316 319 cgstream = part
317 320 version = part.params.get(b'version', b'01')
318 321 legalcgvers = changegroup.supportedincomingversions(self)
319 322 if version not in legalcgvers:
320 323 msg = _(b'Unsupported changegroup version: %s')
321 324 raise error.Abort(msg % version)
322 325 if bundle.compressed():
323 326 cgstream = self._writetempbundle(part.read, b'.cg%sun' % version)
324 327
325 328 self._cgunpacker = changegroup.getunbundler(version, cgstream, b'UN')
326 329
327 330 def _writetempbundle(self, readfn, suffix, header=b''):
328 331 """Write a temporary file to disk
329 332 """
330 333 fdtemp, temp = self.vfs.mkstemp(prefix=b"hg-bundle-", suffix=suffix)
331 334 self.tempfile = temp
332 335
333 336 with os.fdopen(fdtemp, 'wb') as fptemp:
334 337 fptemp.write(header)
335 338 while True:
336 339 chunk = readfn(2 ** 18)
337 340 if not chunk:
338 341 break
339 342 fptemp.write(chunk)
340 343
341 344 return self.vfs.open(self.tempfile, mode=b"rb")
342 345
343 346 @localrepo.unfilteredpropertycache
344 347 def _phasecache(self):
345 348 return bundlephasecache(self, self._phasedefaults)
346 349
347 350 @localrepo.unfilteredpropertycache
348 351 def changelog(self):
349 352 # consume the header if it exists
350 353 self._cgunpacker.changelogheader()
351 354 c = bundlechangelog(self.svfs, self._cgunpacker)
352 355 self.manstart = self._cgunpacker.tell()
353 356 return c
354 357
355 358 def _refreshchangelog(self):
356 359 # changelog for bundle repo are not filecache, this method is not
357 360 # applicable.
358 361 pass
359 362
360 363 @localrepo.unfilteredpropertycache
361 364 def manifestlog(self):
362 365 self._cgunpacker.seek(self.manstart)
363 366 # consume the header if it exists
364 367 self._cgunpacker.manifestheader()
365 368 linkmapper = self.unfiltered().changelog.rev
366 369 rootstore = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
367 370 self.filestart = self._cgunpacker.tell()
368 371
369 372 return manifest.manifestlog(
370 373 self.svfs, self, rootstore, self.narrowmatch()
371 374 )
372 375
373 376 def _consumemanifest(self):
374 377 """Consumes the manifest portion of the bundle, setting filestart so the
375 378 file portion can be read."""
376 379 self._cgunpacker.seek(self.manstart)
377 380 self._cgunpacker.manifestheader()
378 381 for delta in self._cgunpacker.deltaiter():
379 382 pass
380 383 self.filestart = self._cgunpacker.tell()
381 384
382 385 @localrepo.unfilteredpropertycache
383 386 def manstart(self):
384 387 self.changelog
385 388 return self.manstart
386 389
387 390 @localrepo.unfilteredpropertycache
388 391 def filestart(self):
389 392 self.manifestlog
390 393
391 394 # If filestart was not set by self.manifestlog, that means the
392 395 # manifestlog implementation did not consume the manifests from the
393 396 # changegroup (ex: it might be consuming trees from a separate bundle2
394 397 # part instead). So we need to manually consume it.
395 398 if 'filestart' not in self.__dict__:
396 399 self._consumemanifest()
397 400
398 401 return self.filestart
399 402
400 403 def url(self):
401 404 return self._url
402 405
403 406 def file(self, f):
404 407 if not self._cgfilespos:
405 408 self._cgunpacker.seek(self.filestart)
406 409 self._cgfilespos = _getfilestarts(self._cgunpacker)
407 410
408 411 if f in self._cgfilespos:
409 412 self._cgunpacker.seek(self._cgfilespos[f])
410 413 linkmapper = self.unfiltered().changelog.rev
411 414 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
412 415 else:
413 416 return super(bundlerepository, self).file(f)
414 417
415 418 def close(self):
416 419 """Close assigned bundle file immediately."""
417 420 self._bundlefile.close()
418 421 if self.tempfile is not None:
419 422 self.vfs.unlink(self.tempfile)
420 423 if self._tempparent:
421 424 shutil.rmtree(self._tempparent, True)
422 425
423 426 def cancopy(self):
424 427 return False
425 428
426 429 def peer(self):
427 430 return bundlepeer(self)
428 431
429 432 def getcwd(self):
430 433 return encoding.getcwd() # always outside the repo
431 434
432 435 # Check if parents exist in localrepo before setting
433 436 def setparents(self, p1, p2=nullid):
434 437 p1rev = self.changelog.rev(p1)
435 438 p2rev = self.changelog.rev(p2)
436 439 msg = _(b"setting parent to node %s that only exists in the bundle\n")
437 440 if self.changelog.repotiprev < p1rev:
438 441 self.ui.warn(msg % nodemod.hex(p1))
439 442 if self.changelog.repotiprev < p2rev:
440 443 self.ui.warn(msg % nodemod.hex(p2))
441 444 return super(bundlerepository, self).setparents(p1, p2)
442 445
443 446
444 447 def instance(ui, path, create, intents=None, createopts=None):
445 448 if create:
446 449 raise error.Abort(_(b'cannot create new bundle repository'))
447 450 # internal config: bundle.mainreporoot
448 451 parentpath = ui.config(b"bundle", b"mainreporoot")
449 452 if not parentpath:
450 453 # try to find the correct path to the working directory repo
451 454 parentpath = cmdutil.findrepo(encoding.getcwd())
452 455 if parentpath is None:
453 456 parentpath = b''
454 457 if parentpath:
455 458 # Try to make the full path relative so we get a nice, short URL.
456 459 # In particular, we don't want temp dir names in test outputs.
457 460 cwd = encoding.getcwd()
458 461 if parentpath == cwd:
459 462 parentpath = b''
460 463 else:
461 464 cwd = pathutil.normasprefix(cwd)
462 465 if parentpath.startswith(cwd):
463 466 parentpath = parentpath[len(cwd) :]
464 467 u = util.url(path)
465 468 path = u.localpath()
466 469 if u.scheme == b'bundle':
467 470 s = path.split(b"+", 1)
468 471 if len(s) == 1:
469 472 repopath, bundlename = parentpath, s[0]
470 473 else:
471 474 repopath, bundlename = s
472 475 else:
473 476 repopath, bundlename = parentpath, path
474 477
475 478 return makebundlerepository(ui, repopath, bundlename)
476 479
477 480
478 481 def makebundlerepository(ui, repopath, bundlepath):
479 482 """Make a bundle repository object based on repo and bundle paths."""
480 483 if repopath:
481 484 url = b'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
482 485 else:
483 486 url = b'bundle:%s' % bundlepath
484 487
485 488 # Because we can't make any guarantees about the type of the base
486 489 # repository, we can't have a static class representing the bundle
487 490 # repository. We also can't make any guarantees about how to even
488 491 # call the base repository's constructor!
489 492 #
490 493 # So, our strategy is to go through ``localrepo.instance()`` to construct
491 494 # a repo instance. Then, we dynamically create a new type derived from
492 495 # both it and our ``bundlerepository`` class which overrides some
493 496 # functionality. We then change the type of the constructed repository
494 497 # to this new type and initialize the bundle-specific bits of it.
495 498
496 499 try:
497 500 repo = localrepo.instance(ui, repopath, create=False)
498 501 tempparent = None
499 502 except error.RepoError:
500 503 tempparent = pycompat.mkdtemp()
501 504 try:
502 505 repo = localrepo.instance(ui, tempparent, create=True)
503 506 except Exception:
504 507 shutil.rmtree(tempparent)
505 508 raise
506 509
507 510 class derivedbundlerepository(bundlerepository, repo.__class__):
508 511 pass
509 512
510 513 repo.__class__ = derivedbundlerepository
511 514 bundlerepository.__init__(repo, bundlepath, url, tempparent)
512 515
513 516 return repo
514 517
515 518
516 519 class bundletransactionmanager(object):
517 520 def transaction(self):
518 521 return None
519 522
520 523 def close(self):
521 524 raise NotImplementedError
522 525
523 526 def release(self):
524 527 raise NotImplementedError
525 528
526 529
527 530 def getremotechanges(
528 531 ui, repo, peer, onlyheads=None, bundlename=None, force=False
529 532 ):
530 533 '''obtains a bundle of changes incoming from peer
531 534
532 535 "onlyheads" restricts the returned changes to those reachable from the
533 536 specified heads.
534 537 "bundlename", if given, stores the bundle to this file path permanently;
535 538 otherwise it's stored to a temp file and gets deleted again when you call
536 539 the returned "cleanupfn".
537 540 "force" indicates whether to proceed on unrelated repos.
538 541
539 542 Returns a tuple (local, csets, cleanupfn):
540 543
541 544 "local" is a local repo from which to obtain the actual incoming
542 545 changesets; it is a bundlerepo for the obtained bundle when the
543 546 original "peer" is remote.
544 547 "csets" lists the incoming changeset node ids.
545 548 "cleanupfn" must be called without arguments when you're done processing
546 549 the changes; it closes both the original "peer" and the one returned
547 550 here.
548 551 '''
549 552 tmp = discovery.findcommonincoming(repo, peer, heads=onlyheads, force=force)
550 553 common, incoming, rheads = tmp
551 554 if not incoming:
552 555 try:
553 556 if bundlename:
554 557 os.unlink(bundlename)
555 558 except OSError:
556 559 pass
557 560 return repo, [], peer.close
558 561
559 562 commonset = set(common)
560 563 rheads = [x for x in rheads if x not in commonset]
561 564
562 565 bundle = None
563 566 bundlerepo = None
564 567 localrepo = peer.local()
565 568 if bundlename or not localrepo:
566 569 # create a bundle (uncompressed if peer repo is not local)
567 570
568 571 # developer config: devel.legacy.exchange
569 572 legexc = ui.configlist(b'devel', b'legacy.exchange')
570 573 forcebundle1 = b'bundle2' not in legexc and b'bundle1' in legexc
571 574 canbundle2 = (
572 575 not forcebundle1
573 576 and peer.capable(b'getbundle')
574 577 and peer.capable(b'bundle2')
575 578 )
576 579 if canbundle2:
577 580 with peer.commandexecutor() as e:
578 581 b2 = e.callcommand(
579 582 b'getbundle',
580 583 {
581 584 b'source': b'incoming',
582 585 b'common': common,
583 586 b'heads': rheads,
584 587 b'bundlecaps': exchange.caps20to10(
585 588 repo, role=b'client'
586 589 ),
587 590 b'cg': True,
588 591 },
589 592 ).result()
590 593
591 594 fname = bundle = changegroup.writechunks(
592 595 ui, b2._forwardchunks(), bundlename
593 596 )
594 597 else:
595 598 if peer.capable(b'getbundle'):
596 599 with peer.commandexecutor() as e:
597 600 cg = e.callcommand(
598 601 b'getbundle',
599 602 {
600 603 b'source': b'incoming',
601 604 b'common': common,
602 605 b'heads': rheads,
603 606 },
604 607 ).result()
605 608 elif onlyheads is None and not peer.capable(b'changegroupsubset'):
606 609 # compat with older servers when pulling all remote heads
607 610
608 611 with peer.commandexecutor() as e:
609 612 cg = e.callcommand(
610 613 b'changegroup',
611 614 {b'nodes': incoming, b'source': b'incoming',},
612 615 ).result()
613 616
614 617 rheads = None
615 618 else:
616 619 with peer.commandexecutor() as e:
617 620 cg = e.callcommand(
618 621 b'changegroupsubset',
619 622 {
620 623 b'bases': incoming,
621 624 b'heads': rheads,
622 625 b'source': b'incoming',
623 626 },
624 627 ).result()
625 628
626 629 if localrepo:
627 630 bundletype = b"HG10BZ"
628 631 else:
629 632 bundletype = b"HG10UN"
630 633 fname = bundle = bundle2.writebundle(ui, cg, bundlename, bundletype)
631 634 # keep written bundle?
632 635 if bundlename:
633 636 bundle = None
634 637 if not localrepo:
635 638 # use the created uncompressed bundlerepo
636 639 localrepo = bundlerepo = makebundlerepository(
637 640 repo.baseui, repo.root, fname
638 641 )
639 642
640 643 # this repo contains local and peer now, so filter out local again
641 644 common = repo.heads()
642 645 if localrepo:
643 646 # Part of common may be remotely filtered
644 647 # So use an unfiltered version
645 648 # The discovery process probably need cleanup to avoid that
646 649 localrepo = localrepo.unfiltered()
647 650
648 651 csets = localrepo.changelog.findmissing(common, rheads)
649 652
650 653 if bundlerepo:
651 654 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev :]]
652 655
653 656 with peer.commandexecutor() as e:
654 657 remotephases = e.callcommand(
655 658 b'listkeys', {b'namespace': b'phases',}
656 659 ).result()
657 660
658 661 pullop = exchange.pulloperation(bundlerepo, peer, heads=reponodes)
659 662 pullop.trmanager = bundletransactionmanager()
660 663 exchange._pullapplyphases(pullop, remotephases)
661 664
662 665 def cleanup():
663 666 if bundlerepo:
664 667 bundlerepo.close()
665 668 if bundle:
666 669 os.unlink(bundle)
667 670 peer.close()
668 671
669 672 return (localrepo, csets, cleanup)
General Comments 0
You need to be logged in to leave comments. Login now