936 # only accept optional args from the known set |
936 # only accept optional args from the known set |
937 opts = options('debugwireargs', ['three', 'four'], others) |
937 opts = options('debugwireargs', ['three', 'four'], others) |
938 return wireprototypes.bytesresponse(repo.debugwireargs( |
938 return wireprototypes.bytesresponse(repo.debugwireargs( |
939 one, two, **pycompat.strkwargs(opts))) |
939 one, two, **pycompat.strkwargs(opts))) |
940 |
940 |
|
941 def find_pullbundle(repo, proto, opts, clheads, heads, common): |
|
942 """Return a file object for the first matching pullbundle. |
|
943 |
|
944 Pullbundles are specified in .hg/pullbundles.manifest similar to |
|
945 clonebundles. |
|
946 For each entry, the bundle specification is checked for compatibility: |
|
947 - Client features vs the BUNDLESPEC. |
|
948 - Revisions shared with the clients vs base revisions of the bundle. |
|
949 A bundle can be applied only if all its base revisions are known by |
|
950 the client. |
|
951 - At least one leaf of the bundle's DAG is missing on the client. |
|
952 - Every leaf of the bundle's DAG is part of node set the client wants. |
|
953 E.g. do not send a bundle of all changes if the client wants only |
|
954 one specific branch of many. |
|
955 """ |
|
956 def decodehexstring(s): |
|
957 return set([h.decode('hex') for h in s.split(';')]) |
|
958 |
|
959 manifest = repo.vfs.tryread('pullbundles.manifest') |
|
960 if not manifest: |
|
961 return None |
|
962 res = exchange.parseclonebundlesmanifest(repo, manifest) |
|
963 res = exchange.filterclonebundleentries(repo, res) |
|
964 if not res: |
|
965 return None |
|
966 cl = repo.changelog |
|
967 heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True) |
|
968 common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True) |
|
969 compformats = clientcompressionsupport(proto) |
|
970 for entry in res: |
|
971 if 'COMPRESSION' in entry and entry['COMPRESSION'] not in compformats: |
|
972 continue |
|
973 # No test yet for VERSION, since V2 is supported by any client |
|
974 # that advertises partial pulls |
|
975 if 'heads' in entry: |
|
976 try: |
|
977 bundle_heads = decodehexstring(entry['heads']) |
|
978 except TypeError: |
|
979 # Bad heads entry |
|
980 continue |
|
981 if bundle_heads.issubset(common): |
|
982 continue # Nothing new |
|
983 if all(cl.rev(rev) in common_anc for rev in bundle_heads): |
|
984 continue # Still nothing new |
|
985 if any(cl.rev(rev) not in heads_anc and |
|
986 cl.rev(rev) not in common_anc for rev in bundle_heads): |
|
987 continue |
|
988 if 'bases' in entry: |
|
989 try: |
|
990 bundle_bases = decodehexstring(entry['bases']) |
|
991 except TypeError: |
|
992 # Bad bases entry |
|
993 continue |
|
994 if not all(cl.rev(rev) in common_anc for rev in bundle_bases): |
|
995 continue |
|
996 path = entry['URL'] |
|
997 repo.ui.debug('sending pullbundle "%s"\n' % path) |
|
998 try: |
|
999 return repo.vfs.open(path) |
|
1000 except IOError: |
|
1001 repo.ui.debug('pullbundle "%s" not accessible\n' % path) |
|
1002 continue |
|
1003 return None |
|
1004 |
941 @wireprotocommand('getbundle', '*', permission='pull') |
1005 @wireprotocommand('getbundle', '*', permission='pull') |
942 def getbundle(repo, proto, others): |
1006 def getbundle(repo, proto, others): |
943 opts = options('getbundle', gboptsmap.keys(), others) |
1007 opts = options('getbundle', gboptsmap.keys(), others) |
944 for k, v in opts.iteritems(): |
1008 for k, v in opts.iteritems(): |
945 keytype = gboptsmap[k] |
1009 keytype = gboptsmap[k] |
968 hint=bundle2requiredhint) |
1032 hint=bundle2requiredhint) |
969 |
1033 |
970 prefercompressed = True |
1034 prefercompressed = True |
971 |
1035 |
972 try: |
1036 try: |
|
1037 clheads = set(repo.changelog.heads()) |
|
1038 heads = set(opts.get('heads', set())) |
|
1039 common = set(opts.get('common', set())) |
|
1040 common.discard(nullid) |
|
1041 if (repo.ui.configbool('server', 'pullbundle') and |
|
1042 'partial-pull' in proto.getprotocaps()): |
|
1043 # Check if a pre-built bundle covers this request. |
|
1044 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common) |
|
1045 if bundle: |
|
1046 return wireprototypes.streamres(gen=util.filechunkiter(bundle), |
|
1047 prefer_uncompressed=True) |
|
1048 |
973 if repo.ui.configbool('server', 'disablefullbundle'): |
1049 if repo.ui.configbool('server', 'disablefullbundle'): |
974 # Check to see if this is a full clone. |
1050 # Check to see if this is a full clone. |
975 clheads = set(repo.changelog.heads()) |
|
976 changegroup = opts.get('cg', True) |
1051 changegroup = opts.get('cg', True) |
977 heads = set(opts.get('heads', set())) |
|
978 common = set(opts.get('common', set())) |
|
979 common.discard(nullid) |
|
980 if changegroup and not common and clheads == heads: |
1052 if changegroup and not common and clheads == heads: |
981 raise error.Abort( |
1053 raise error.Abort( |
982 _('server has pull-based clones disabled'), |
1054 _('server has pull-based clones disabled'), |
983 hint=_('remove --pull if specified or upgrade Mercurial')) |
1055 hint=_('remove --pull if specified or upgrade Mercurial')) |
984 |
1056 |