Mercurial > public > mercurial-scm > hg
comparison mercurial/bundle2.py @ 23029:149fc8a44184
bundle2: client side support for a part to import external bundles
Bundle2 opens doors to advanced features allowing to reduce load on
mercurial servers, and improve clone experience for users on unstable or
slow networks.
For instance, it could be possible to pre-generate a bundle of a
repository, and give a pointer to it to clients cloning the repository,
followed by another changegroup with the remainder. For significantly
big repositories, this could come as several base bundles with e.g. 10k
changesets, which, combined with checkpoints (not part of this change),
would prevent users with flaky networks from starting over any time
their connection fails.
While the server-side support for those features doesn't exist yet, it
is preferable to have client-side support for this early-on, allowing
experiments on servers only requiring a vanilla client with bundle2
enabled.
author | Mike Hommey <mh@glandium.org> |
---|---|
date | Fri, 17 Oct 2014 09:57:05 +0900 |
parents | 006a81d07e57 |
children | ad144882318d |
comparison
equal
deleted
inserted
replaced
23028:e4aeb14248ca | 23029:149fc8a44184 |
---|---|
149 import struct | 149 import struct |
150 import urllib | 150 import urllib |
151 import string | 151 import string |
152 import obsolete | 152 import obsolete |
153 import pushkey | 153 import pushkey |
154 import url | |
154 | 155 |
155 import changegroup, error | 156 import changegroup, error |
156 from i18n import _ | 157 from i18n import _ |
157 | 158 |
158 _pack = struct.pack | 159 _pack = struct.pack |
795 | 796 |
796 capabilities = {'HG2Y': (), | 797 capabilities = {'HG2Y': (), |
797 'b2x:listkeys': (), | 798 'b2x:listkeys': (), |
798 'b2x:pushkey': (), | 799 'b2x:pushkey': (), |
799 'b2x:changegroup': (), | 800 'b2x:changegroup': (), |
801 'digests': tuple(sorted(util.DIGESTS.keys())), | |
802 'b2x:remote-changegroup': ('http', 'https'), | |
800 } | 803 } |
801 | 804 |
802 def getrepocaps(repo): | 805 def getrepocaps(repo): |
803 """return the bundle2 capabilities for a given repo | 806 """return the bundle2 capabilities for a given repo |
804 | 807 |
848 part = op.reply.newpart('b2x:reply:changegroup') | 851 part = op.reply.newpart('b2x:reply:changegroup') |
849 part.addparam('in-reply-to', str(inpart.id), mandatory=False) | 852 part.addparam('in-reply-to', str(inpart.id), mandatory=False) |
850 part.addparam('return', '%i' % ret, mandatory=False) | 853 part.addparam('return', '%i' % ret, mandatory=False) |
851 assert not inpart.read() | 854 assert not inpart.read() |
852 | 855 |
856 _remotechangegroupparams = tuple(['url', 'size', 'digests'] + | |
857 ['digest:%s' % k for k in util.DIGESTS.keys()]) | |
858 @parthandler('b2x:remote-changegroup', _remotechangegroupparams) | |
859 def handleremotechangegroup(op, inpart): | |
860 """apply a bundle10 on the repo, given an url and validation information | |
861 | |
862 All the information about the remote bundle to import are given as | |
863 parameters. The parameters include: | |
864 - url: the url to the bundle10. | |
865 - size: the bundle10 file size. It is used to validate what was | |
866 retrieved by the client matches the server knowledge about the bundle. | |
867 - digests: a space separated list of the digest types provided as | |
868 parameters. | |
869 - digest:<digest-type>: the hexadecimal representation of the digest with | |
870 that name. Like the size, it is used to validate what was retrieved by | |
871 the client matches what the server knows about the bundle. | |
872 | |
873 When multiple digest types are given, all of them are checked. | |
874 """ | |
875 try: | |
876 raw_url = inpart.params['url'] | |
877 except KeyError: | |
878 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url') | |
879 parsed_url = util.url(raw_url) | |
880 if parsed_url.scheme not in capabilities['b2x:remote-changegroup']: | |
881 raise util.Abort(_('remote-changegroup does not support %s urls') % | |
882 parsed_url.scheme) | |
883 | |
884 try: | |
885 size = int(inpart.params['size']) | |
886 except ValueError: | |
887 raise util.Abort(_('remote-changegroup: invalid value for param "%s"') | |
888 % 'size') | |
889 except KeyError: | |
890 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size') | |
891 | |
892 digests = {} | |
893 for typ in inpart.params.get('digests', '').split(): | |
894 param = 'digest:%s' % typ | |
895 try: | |
896 value = inpart.params[param] | |
897 except KeyError: | |
898 raise util.Abort(_('remote-changegroup: missing "%s" param') % | |
899 param) | |
900 digests[typ] = value | |
901 | |
902 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests) | |
903 | |
904 # Make sure we trigger a transaction creation | |
905 # | |
906 # The addchangegroup function will get a transaction object by itself, but | |
907 # we need to make sure we trigger the creation of a transaction object used | |
908 # for the whole processing scope. | |
909 op.gettransaction() | |
910 import exchange | |
911 cg = exchange.readbundle(op.repo.ui, real_part, raw_url) | |
912 if not isinstance(cg, changegroup.cg1unpacker): | |
913 raise util.Abort(_('%s: not a bundle version 1.0') % | |
914 util.hidepassword(raw_url)) | |
915 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2') | |
916 op.records.add('changegroup', {'return': ret}) | |
917 if op.reply is not None: | |
918 # This is definitly not the final form of this | |
919 # return. But one need to start somewhere. | |
920 part = op.reply.newpart('b2x:reply:changegroup') | |
921 part.addparam('in-reply-to', str(inpart.id), mandatory=False) | |
922 part.addparam('return', '%i' % ret, mandatory=False) | |
923 try: | |
924 real_part.validate() | |
925 except util.Abort, e: | |
926 raise util.Abort(_('bundle at %s is corrupted:\n%s') % | |
927 (util.hidepassword(raw_url), str(e))) | |
928 assert not inpart.read() | |
929 | |
853 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to')) | 930 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to')) |
854 def handlereplychangegroup(op, inpart): | 931 def handlereplychangegroup(op, inpart): |
855 ret = int(inpart.params['return']) | 932 ret = int(inpart.params['return']) |
856 replyto = int(inpart.params['in-reply-to']) | 933 replyto = int(inpart.params['in-reply-to']) |
857 op.records.add('changegroup', {'return': ret}, replyto) | 934 op.records.add('changegroup', {'return': ret}, replyto) |