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)