264 while pos < len(chunk): |
264 while pos < len(chunk): |
265 next = pos + 2**20 |
265 next = pos + 2**20 |
266 yield chunk[pos:next] |
266 yield chunk[pos:next] |
267 pos = next |
267 pos = next |
268 yield closechunk() |
268 yield closechunk() |
|
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 |
269 |
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) |
273 version = '02' |
465 version = '02' |
718 |
910 |
719 return revisions, files |
911 return revisions, files |
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 |
|