Show More
@@ -267,6 +267,198 b' class cg1unpacker(object):' | |||||
267 | pos = next |
|
267 | pos = next | |
268 | yield closechunk() |
|
268 | yield closechunk() | |
269 |
|
269 | |||
|
270 | def apply(self, repo, srctype, url, emptyok=False, | |||
|
271 | targetphase=phases.draft, expectedtotal=None): | |||
|
272 | """Add the changegroup returned by source.read() to this repo. | |||
|
273 | srctype is a string like 'push', 'pull', or 'unbundle'. url is | |||
|
274 | the URL of the repo where this changegroup is coming from. | |||
|
275 | ||||
|
276 | Return an integer summarizing the change to this repo: | |||
|
277 | - nothing changed or no source: 0 | |||
|
278 | - more heads than before: 1+added heads (2..n) | |||
|
279 | - fewer heads than before: -1-removed heads (-2..-n) | |||
|
280 | - number of heads stays the same: 1 | |||
|
281 | """ | |||
|
282 | repo = repo.unfiltered() | |||
|
283 | def csmap(x): | |||
|
284 | repo.ui.debug("add changeset %s\n" % short(x)) | |||
|
285 | return len(cl) | |||
|
286 | ||||
|
287 | def revmap(x): | |||
|
288 | return cl.rev(x) | |||
|
289 | ||||
|
290 | changesets = files = revisions = 0 | |||
|
291 | ||||
|
292 | tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) | |||
|
293 | # The transaction could have been created before and already | |||
|
294 | # carries source information. In this case we use the top | |||
|
295 | # level data. We overwrite the argument because we need to use | |||
|
296 | # the top level value (if they exist) in this function. | |||
|
297 | srctype = tr.hookargs.setdefault('source', srctype) | |||
|
298 | url = tr.hookargs.setdefault('url', url) | |||
|
299 | ||||
|
300 | # write changelog data to temp files so concurrent readers will not see | |||
|
301 | # inconsistent view | |||
|
302 | cl = repo.changelog | |||
|
303 | cl.delayupdate(tr) | |||
|
304 | oldheads = cl.heads() | |||
|
305 | try: | |||
|
306 | repo.hook('prechangegroup', throw=True, **tr.hookargs) | |||
|
307 | ||||
|
308 | trp = weakref.proxy(tr) | |||
|
309 | # pull off the changeset group | |||
|
310 | repo.ui.status(_("adding changesets\n")) | |||
|
311 | clstart = len(cl) | |||
|
312 | class prog(object): | |||
|
313 | def __init__(self, step, total): | |||
|
314 | self._step = step | |||
|
315 | self._total = total | |||
|
316 | self._count = 1 | |||
|
317 | def __call__(self): | |||
|
318 | repo.ui.progress(self._step, self._count, unit=_('chunks'), | |||
|
319 | total=self._total) | |||
|
320 | self._count += 1 | |||
|
321 | self.callback = prog(_('changesets'), expectedtotal) | |||
|
322 | ||||
|
323 | efiles = set() | |||
|
324 | def onchangelog(cl, node): | |||
|
325 | efiles.update(cl.read(node)[3]) | |||
|
326 | ||||
|
327 | self.changelogheader() | |||
|
328 | srccontent = cl.addgroup(self, csmap, trp, | |||
|
329 | addrevisioncb=onchangelog) | |||
|
330 | efiles = len(efiles) | |||
|
331 | ||||
|
332 | if not (srccontent or emptyok): | |||
|
333 | raise error.Abort(_("received changelog group is empty")) | |||
|
334 | clend = len(cl) | |||
|
335 | changesets = clend - clstart | |||
|
336 | repo.ui.progress(_('changesets'), None) | |||
|
337 | ||||
|
338 | # pull off the manifest group | |||
|
339 | repo.ui.status(_("adding manifests\n")) | |||
|
340 | # manifests <= changesets | |||
|
341 | self.callback = prog(_('manifests'), changesets) | |||
|
342 | # no need to check for empty manifest group here: | |||
|
343 | # if the result of the merge of 1 and 2 is the same in 3 and 4, | |||
|
344 | # no new manifest will be created and the manifest group will | |||
|
345 | # be empty during the pull | |||
|
346 | self.manifestheader() | |||
|
347 | repo.manifest.addgroup(self, revmap, trp) | |||
|
348 | repo.ui.progress(_('manifests'), None) | |||
|
349 | ||||
|
350 | needfiles = {} | |||
|
351 | if repo.ui.configbool('server', 'validate', default=False): | |||
|
352 | # validate incoming csets have their manifests | |||
|
353 | for cset in xrange(clstart, clend): | |||
|
354 | mfnode = repo.changelog.read(repo.changelog.node(cset))[0] | |||
|
355 | mfest = repo.manifest.readdelta(mfnode) | |||
|
356 | # store file nodes we must see | |||
|
357 | for f, n in mfest.iteritems(): | |||
|
358 | needfiles.setdefault(f, set()).add(n) | |||
|
359 | ||||
|
360 | # process the files | |||
|
361 | repo.ui.status(_("adding file changes\n")) | |||
|
362 | self.callback = None | |||
|
363 | pr = prog(_('files'), efiles) | |||
|
364 | newrevs, newfiles = addchangegroupfiles(repo, self, revmap, trp, pr, | |||
|
365 | needfiles) | |||
|
366 | revisions += newrevs | |||
|
367 | files += newfiles | |||
|
368 | ||||
|
369 | dh = 0 | |||
|
370 | if oldheads: | |||
|
371 | heads = cl.heads() | |||
|
372 | dh = len(heads) - len(oldheads) | |||
|
373 | for h in heads: | |||
|
374 | if h not in oldheads and repo[h].closesbranch(): | |||
|
375 | dh -= 1 | |||
|
376 | htext = "" | |||
|
377 | if dh: | |||
|
378 | htext = _(" (%+d heads)") % dh | |||
|
379 | ||||
|
380 | repo.ui.status(_("added %d changesets" | |||
|
381 | " with %d changes to %d files%s\n") | |||
|
382 | % (changesets, revisions, files, htext)) | |||
|
383 | repo.invalidatevolatilesets() | |||
|
384 | ||||
|
385 | if changesets > 0: | |||
|
386 | p = lambda: tr.writepending() and repo.root or "" | |||
|
387 | if 'node' not in tr.hookargs: | |||
|
388 | tr.hookargs['node'] = hex(cl.node(clstart)) | |||
|
389 | hookargs = dict(tr.hookargs) | |||
|
390 | else: | |||
|
391 | hookargs = dict(tr.hookargs) | |||
|
392 | hookargs['node'] = hex(cl.node(clstart)) | |||
|
393 | repo.hook('pretxnchangegroup', throw=True, pending=p, | |||
|
394 | **hookargs) | |||
|
395 | ||||
|
396 | added = [cl.node(r) for r in xrange(clstart, clend)] | |||
|
397 | publishing = repo.publishing() | |||
|
398 | if srctype in ('push', 'serve'): | |||
|
399 | # Old servers can not push the boundary themselves. | |||
|
400 | # New servers won't push the boundary if changeset already | |||
|
401 | # exists locally as secret | |||
|
402 | # | |||
|
403 | # We should not use added here but the list of all change in | |||
|
404 | # the bundle | |||
|
405 | if publishing: | |||
|
406 | phases.advanceboundary(repo, tr, phases.public, srccontent) | |||
|
407 | else: | |||
|
408 | # Those changesets have been pushed from the outside, their | |||
|
409 | # phases are going to be pushed alongside. Therefor | |||
|
410 | # `targetphase` is ignored. | |||
|
411 | phases.advanceboundary(repo, tr, phases.draft, srccontent) | |||
|
412 | phases.retractboundary(repo, tr, phases.draft, added) | |||
|
413 | elif srctype != 'strip': | |||
|
414 | # publishing only alter behavior during push | |||
|
415 | # | |||
|
416 | # strip should not touch boundary at all | |||
|
417 | phases.retractboundary(repo, tr, targetphase, added) | |||
|
418 | ||||
|
419 | if changesets > 0: | |||
|
420 | if srctype != 'strip': | |||
|
421 | # During strip, branchcache is invalid but coming call to | |||
|
422 | # `destroyed` will repair it. | |||
|
423 | # In other case we can safely update cache on disk. | |||
|
424 | branchmap.updatecache(repo.filtered('served')) | |||
|
425 | ||||
|
426 | def runhooks(): | |||
|
427 | # These hooks run when the lock releases, not when the | |||
|
428 | # transaction closes. So it's possible for the changelog | |||
|
429 | # to have changed since we last saw it. | |||
|
430 | if clstart >= len(repo): | |||
|
431 | return | |||
|
432 | ||||
|
433 | # forcefully update the on-disk branch cache | |||
|
434 | repo.ui.debug("updating the branch cache\n") | |||
|
435 | repo.hook("changegroup", **hookargs) | |||
|
436 | ||||
|
437 | for n in added: | |||
|
438 | args = hookargs.copy() | |||
|
439 | args['node'] = hex(n) | |||
|
440 | repo.hook("incoming", **args) | |||
|
441 | ||||
|
442 | newheads = [h for h in repo.heads() if h not in oldheads] | |||
|
443 | repo.ui.log("incoming", | |||
|
444 | "%s incoming changes - new heads: %s\n", | |||
|
445 | len(added), | |||
|
446 | ', '.join([hex(c[:6]) for c in newheads])) | |||
|
447 | ||||
|
448 | tr.addpostclose('changegroup-runhooks-%020i' % clstart, | |||
|
449 | lambda tr: repo._afterlock(runhooks)) | |||
|
450 | ||||
|
451 | tr.close() | |||
|
452 | ||||
|
453 | finally: | |||
|
454 | tr.release() | |||
|
455 | repo.ui.flush() | |||
|
456 | # never return 0 here: | |||
|
457 | if dh < 0: | |||
|
458 | return dh - 1 | |||
|
459 | else: | |||
|
460 | return dh + 1 | |||
|
461 | ||||
270 | class cg2unpacker(cg1unpacker): |
|
462 | class cg2unpacker(cg1unpacker): | |
271 | deltaheader = _CHANGEGROUPV2_DELTA_HEADER |
|
463 | deltaheader = _CHANGEGROUPV2_DELTA_HEADER | |
272 | deltaheadersize = struct.calcsize(deltaheader) |
|
464 | deltaheadersize = struct.calcsize(deltaheader) | |
@@ -720,194 +912,9 b' def addchangegroupfiles(repo, source, re' | |||||
720 |
|
912 | |||
721 | def addchangegroup(repo, source, srctype, url, emptyok=False, |
|
913 | def addchangegroup(repo, source, srctype, url, emptyok=False, | |
722 | targetphase=phases.draft, expectedtotal=None): |
|
914 | targetphase=phases.draft, expectedtotal=None): | |
723 | """Add the changegroup returned by source.read() to this repo. |
|
915 | """Legacy forwarding method to cg?unpacker.apply() to be removed soon.""" | |
724 | srctype is a string like 'push', 'pull', or 'unbundle'. url is |
|
|||
725 | the URL of the repo where this changegroup is coming from. |
|
|||
726 |
|
||||
727 | Return an integer summarizing the change to this repo: |
|
|||
728 | - nothing changed or no source: 0 |
|
|||
729 | - more heads than before: 1+added heads (2..n) |
|
|||
730 | - fewer heads than before: -1-removed heads (-2..-n) |
|
|||
731 | - number of heads stays the same: 1 |
|
|||
732 | """ |
|
|||
733 | if not source: |
|
916 | if not source: | |
734 | return 0 |
|
917 | return 0 | |
735 |
|
918 | |||
736 | repo = repo.unfiltered() |
|
919 | return source.apply(repo, srctype, url, emptyok=emptyok, | |
737 | def csmap(x): |
|
920 | targetphase=targetphase, expectedtotal=expectedtotal) | |
738 | repo.ui.debug("add changeset %s\n" % short(x)) |
|
|||
739 | return len(cl) |
|
|||
740 |
|
||||
741 | def revmap(x): |
|
|||
742 | return cl.rev(x) |
|
|||
743 |
|
||||
744 | changesets = files = revisions = 0 |
|
|||
745 |
|
||||
746 | tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) |
|
|||
747 | # The transaction could have been created before and already carries source |
|
|||
748 | # information. In this case we use the top level data. We overwrite the |
|
|||
749 | # argument because we need to use the top level value (if they exist) in |
|
|||
750 | # this function. |
|
|||
751 | srctype = tr.hookargs.setdefault('source', srctype) |
|
|||
752 | url = tr.hookargs.setdefault('url', url) |
|
|||
753 |
|
||||
754 | # write changelog data to temp files so concurrent readers will not see |
|
|||
755 | # inconsistent view |
|
|||
756 | cl = repo.changelog |
|
|||
757 | cl.delayupdate(tr) |
|
|||
758 | oldheads = cl.heads() |
|
|||
759 | try: |
|
|||
760 | repo.hook('prechangegroup', throw=True, **tr.hookargs) |
|
|||
761 |
|
||||
762 | trp = weakref.proxy(tr) |
|
|||
763 | # pull off the changeset group |
|
|||
764 | repo.ui.status(_("adding changesets\n")) |
|
|||
765 | clstart = len(cl) |
|
|||
766 | class prog(object): |
|
|||
767 | def __init__(self, step, total): |
|
|||
768 | self._step = step |
|
|||
769 | self._total = total |
|
|||
770 | self._count = 1 |
|
|||
771 | def __call__(self): |
|
|||
772 | repo.ui.progress(self._step, self._count, unit=_('chunks'), |
|
|||
773 | total=self._total) |
|
|||
774 | self._count += 1 |
|
|||
775 | source.callback = prog(_('changesets'), expectedtotal) |
|
|||
776 |
|
||||
777 | efiles = set() |
|
|||
778 | def onchangelog(cl, node): |
|
|||
779 | efiles.update(cl.read(node)[3]) |
|
|||
780 |
|
||||
781 | source.changelogheader() |
|
|||
782 | srccontent = cl.addgroup(source, csmap, trp, |
|
|||
783 | addrevisioncb=onchangelog) |
|
|||
784 | efiles = len(efiles) |
|
|||
785 |
|
||||
786 | if not (srccontent or emptyok): |
|
|||
787 | raise error.Abort(_("received changelog group is empty")) |
|
|||
788 | clend = len(cl) |
|
|||
789 | changesets = clend - clstart |
|
|||
790 | repo.ui.progress(_('changesets'), None) |
|
|||
791 |
|
||||
792 | # pull off the manifest group |
|
|||
793 | repo.ui.status(_("adding manifests\n")) |
|
|||
794 | # manifests <= changesets |
|
|||
795 | source.callback = prog(_('manifests'), changesets) |
|
|||
796 | # no need to check for empty manifest group here: |
|
|||
797 | # if the result of the merge of 1 and 2 is the same in 3 and 4, |
|
|||
798 | # no new manifest will be created and the manifest group will |
|
|||
799 | # be empty during the pull |
|
|||
800 | source.manifestheader() |
|
|||
801 | repo.manifest.addgroup(source, revmap, trp) |
|
|||
802 | repo.ui.progress(_('manifests'), None) |
|
|||
803 |
|
||||
804 | needfiles = {} |
|
|||
805 | if repo.ui.configbool('server', 'validate', default=False): |
|
|||
806 | # validate incoming csets have their manifests |
|
|||
807 | for cset in xrange(clstart, clend): |
|
|||
808 | mfnode = repo.changelog.read(repo.changelog.node(cset))[0] |
|
|||
809 | mfest = repo.manifest.readdelta(mfnode) |
|
|||
810 | # store file nodes we must see |
|
|||
811 | for f, n in mfest.iteritems(): |
|
|||
812 | needfiles.setdefault(f, set()).add(n) |
|
|||
813 |
|
||||
814 | # process the files |
|
|||
815 | repo.ui.status(_("adding file changes\n")) |
|
|||
816 | source.callback = None |
|
|||
817 | pr = prog(_('files'), efiles) |
|
|||
818 | newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr, |
|
|||
819 | needfiles) |
|
|||
820 | revisions += newrevs |
|
|||
821 | files += newfiles |
|
|||
822 |
|
||||
823 | dh = 0 |
|
|||
824 | if oldheads: |
|
|||
825 | heads = cl.heads() |
|
|||
826 | dh = len(heads) - len(oldheads) |
|
|||
827 | for h in heads: |
|
|||
828 | if h not in oldheads and repo[h].closesbranch(): |
|
|||
829 | dh -= 1 |
|
|||
830 | htext = "" |
|
|||
831 | if dh: |
|
|||
832 | htext = _(" (%+d heads)") % dh |
|
|||
833 |
|
||||
834 | repo.ui.status(_("added %d changesets" |
|
|||
835 | " with %d changes to %d files%s\n") |
|
|||
836 | % (changesets, revisions, files, htext)) |
|
|||
837 | repo.invalidatevolatilesets() |
|
|||
838 |
|
||||
839 | if changesets > 0: |
|
|||
840 | p = lambda: tr.writepending() and repo.root or "" |
|
|||
841 | if 'node' not in tr.hookargs: |
|
|||
842 | tr.hookargs['node'] = hex(cl.node(clstart)) |
|
|||
843 | hookargs = dict(tr.hookargs) |
|
|||
844 | else: |
|
|||
845 | hookargs = dict(tr.hookargs) |
|
|||
846 | hookargs['node'] = hex(cl.node(clstart)) |
|
|||
847 | repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs) |
|
|||
848 |
|
||||
849 | added = [cl.node(r) for r in xrange(clstart, clend)] |
|
|||
850 | publishing = repo.publishing() |
|
|||
851 | if srctype in ('push', 'serve'): |
|
|||
852 | # Old servers can not push the boundary themselves. |
|
|||
853 | # New servers won't push the boundary if changeset already |
|
|||
854 | # exists locally as secret |
|
|||
855 | # |
|
|||
856 | # We should not use added here but the list of all change in |
|
|||
857 | # the bundle |
|
|||
858 | if publishing: |
|
|||
859 | phases.advanceboundary(repo, tr, phases.public, srccontent) |
|
|||
860 | else: |
|
|||
861 | # Those changesets have been pushed from the outside, their |
|
|||
862 | # phases are going to be pushed alongside. Therefor |
|
|||
863 | # `targetphase` is ignored. |
|
|||
864 | phases.advanceboundary(repo, tr, phases.draft, srccontent) |
|
|||
865 | phases.retractboundary(repo, tr, phases.draft, added) |
|
|||
866 | elif srctype != 'strip': |
|
|||
867 | # publishing only alter behavior during push |
|
|||
868 | # |
|
|||
869 | # strip should not touch boundary at all |
|
|||
870 | phases.retractboundary(repo, tr, targetphase, added) |
|
|||
871 |
|
||||
872 | if changesets > 0: |
|
|||
873 | if srctype != 'strip': |
|
|||
874 | # During strip, branchcache is invalid but coming call to |
|
|||
875 | # `destroyed` will repair it. |
|
|||
876 | # In other case we can safely update cache on disk. |
|
|||
877 | branchmap.updatecache(repo.filtered('served')) |
|
|||
878 |
|
||||
879 | def runhooks(): |
|
|||
880 | # These hooks run when the lock releases, not when the |
|
|||
881 | # transaction closes. So it's possible for the changelog |
|
|||
882 | # to have changed since we last saw it. |
|
|||
883 | if clstart >= len(repo): |
|
|||
884 | return |
|
|||
885 |
|
||||
886 | # forcefully update the on-disk branch cache |
|
|||
887 | repo.ui.debug("updating the branch cache\n") |
|
|||
888 | repo.hook("changegroup", **hookargs) |
|
|||
889 |
|
||||
890 | for n in added: |
|
|||
891 | args = hookargs.copy() |
|
|||
892 | args['node'] = hex(n) |
|
|||
893 | repo.hook("incoming", **args) |
|
|||
894 |
|
||||
895 | newheads = [h for h in repo.heads() if h not in oldheads] |
|
|||
896 | repo.ui.log("incoming", |
|
|||
897 | "%s incoming changes - new heads: %s\n", |
|
|||
898 | len(added), |
|
|||
899 | ', '.join([hex(c[:6]) for c in newheads])) |
|
|||
900 |
|
||||
901 | tr.addpostclose('changegroup-runhooks-%020i' % clstart, |
|
|||
902 | lambda tr: repo._afterlock(runhooks)) |
|
|||
903 |
|
||||
904 | tr.close() |
|
|||
905 |
|
||||
906 | finally: |
|
|||
907 | tr.release() |
|
|||
908 | repo.ui.flush() |
|
|||
909 | # never return 0 here: |
|
|||
910 | if dh < 0: |
|
|||
911 | return dh - 1 |
|
|||
912 | else: |
|
|||
913 | return dh + 1 |
|
General Comments 0
You need to be logged in to leave comments.
Login now