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