diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index 93e5e0d9b55eb4..2d3fea92761443 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -89,3 +89,13 @@ variable. The fields are checked only if the `promisor.acceptFromServer` config variable is not set to "None". If set to "None", this config variable has no effect. See linkgit:gitprotocol-v2[5]. + +promisor.createRemotes:: + If set to "true", linkgit:git-clone[1] will create entries for + any promisor remotes advertised by the server via the + "promisor-remote" capability. Each remote is added to the new + repository's configuration with its advertised URL, marked as a + promisor remote, and given a default fetch refspec + (`+refs/heads/*:refs/remotes//*`). Optional fields such as + `partialCloneFilter` and `token` are recorded when present. The + remote is only created if it is not already configured locally. diff --git a/builtin/clone.c b/builtin/clone.c index c990f398ef6f37..755436300b4bf5 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -47,6 +47,8 @@ #include "hook.h" #include "bundle.h" #include "bundle-uri.h" +#include "promisor-remote.h" +#include "connect.h" /* * Overall FIXMEs: @@ -1585,15 +1587,23 @@ int cmd_clone(int argc, free(to_free); } - if (!option_rev) - write_refspec_config(src_ref_prefix, our_head_points_at, - remote_head_points_at, &branch_top); + if (!option_rev) + write_refspec_config(src_ref_prefix, our_head_points_at, + remote_head_points_at, &branch_top); + + { + const char *promisor_remote_info; + + if (server_feature_v2("promisor-remote", &promisor_remote_info)) + promisor_remote_configure_from_info(the_repository, + promisor_remote_info); + } - if (filter_options.choice) - partial_clone_register(remote_name, &filter_options); + if (filter_options.choice) + partial_clone_register(remote_name, &filter_options); - if (is_local) - clone_local(path, git_dir); + if (is_local) + clone_local(path, git_dir); else if (mapped_refs && complete_refs_before_fetch) { if (transport_fetch_refs(transport, mapped_refs)) die(_("remote transport reported error")); diff --git a/promisor-remote.c b/promisor-remote.c index 77ebf537e2b3ee..14aa5752e09d90 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -9,6 +9,7 @@ #include "trace2.h" #include "transport.h" #include "strvec.h" +#include "strbuf.h" #include "packfile.h" #include "environment.h" #include "url.h" @@ -769,23 +770,110 @@ char *promisor_remote_reply(const char *info) void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes) { - struct string_list accepted_remotes = STRING_LIST_INIT_DUP; - struct string_list_item *item; + struct string_list accepted_remotes = STRING_LIST_INIT_DUP; + struct string_list_item *item; - string_list_split(&accepted_remotes, remotes, ";", -1); + string_list_split(&accepted_remotes, remotes, ";", -1); - for_each_string_list_item(item, &accepted_remotes) { - char *decoded_remote = url_percent_decode(item->string); - struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote); + for_each_string_list_item(item, &accepted_remotes) { + char *decoded_remote = url_percent_decode(item->string); + struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote); - if (p) - p->accepted = 1; - else - warning(_("accepted promisor remote '%s' not found"), - decoded_remote); + if (p) + p->accepted = 1; + else + warning(_("accepted promisor remote '%s' not found"), + decoded_remote); - free(decoded_remote); - } + free(decoded_remote); + } + + string_list_clear(&accepted_remotes, 0); +} + +static int remote_is_already_configured(struct repository *repo, const char *remote_name) +{ + const char *value; + char *key = xstrfmt("remote.%s.url", remote_name); + int configured = 0; + + if (!repo_config_get_string_tmp(repo, key, &value) && value && *value) + configured = 1; + + free(key); + return configured; +} + +static void set_remote_config_value(struct repository *repo, const char *remote_name, + const char *suffix, const char *value) +{ + char *key = xstrfmt("remote.%s.%s", remote_name, suffix); + + repo_config_set(repo, key, value); + free(key); +} + +static void set_remote_fetch_default(struct repository *repo, const char *remote_name) +{ + char *key = xstrfmt("remote.%s.fetch", remote_name); + struct strbuf value = STRBUF_INIT; + + strbuf_addf(&value, "+refs/heads/*:refs/remotes/%s/*", remote_name); + repo_config_set_multivar(repo, key, value.buf, "^$", 0); + + free(key); + strbuf_release(&value); +} + +static void configure_remote_from_advertisement(struct repository *repo, + struct promisor_info *info) +{ + set_remote_config_value(repo, info->name, "url", info->url); + set_remote_config_value(repo, info->name, "promisor", "true"); + set_remote_fetch_default(repo, info->name); + + if (info->filter) + set_remote_config_value(repo, info->name, + promisor_field_filter, info->filter); + + if (info->token) + set_remote_config_value(repo, info->name, + promisor_field_token, info->token); +} + +void promisor_remote_configure_from_info(struct repository *repo, const char *info) +{ + struct string_list remote_info = STRING_LIST_INIT_DUP; + struct string_list_item *item; + int create_remotes; + int created = 0; + + if (!info || !*info) + return; + + if (repo_config_get_bool(repo, "promisor.createremotes", &create_remotes) || + !create_remotes) + return; + + string_list_split(&remote_info, info, ";", -1); + + for_each_string_list_item(item, &remote_info) { + struct promisor_info *advertised = + parse_one_advertised_remote(item->string); + + if (!advertised) + continue; + + if (!remote_is_already_configured(repo, advertised->name)) { + configure_remote_from_advertisement(repo, advertised); + created = 1; + } + + promisor_info_free(advertised); + } + + string_list_clear(&remote_info, 0); - string_list_clear(&accepted_remotes, 0); + if (created) + repo_promisor_remote_reinit(repo); } diff --git a/promisor-remote.h b/promisor-remote.h index 263d331a551b6c..a7d570207f5287 100644 --- a/promisor-remote.h +++ b/promisor-remote.h @@ -67,4 +67,10 @@ void mark_promisor_remotes_as_accepted(struct repository *repo, const char *remo */ int repo_has_accepted_promisor_remote(struct repository *r); +/* + * Create promisor remotes in the configuration based on the + * "promisor-remote" capability advertisement received from a server. + */ +void promisor_remote_configure_from_info(struct repository *repo, const char *info); + #endif /* PROMISOR_REMOTE_H */ diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh index 023735d6a84ea8..1ab05832908519 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh @@ -327,10 +327,10 @@ test_expect_success "clone with promisor.sendFields" ' ' test_expect_success "clone with promisor.checkFields" ' - git -C server config promisor.advertise true && - test_when_finished "rm -rf client" && + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && - git -C server remote add otherLop "https://invalid.invalid" && + git -C server remote add otherLop "https://invalid.invalid" && git -C server config remote.otherLop.token "fooBar" && git -C server config remote.otherLop.stuff "baz" && git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" && @@ -357,14 +357,38 @@ test_expect_success "clone with promisor.checkFields" ' test_grep ! "clone> promisor-remote=lop;otherLop" trace && # Check that the largest object is still missing on the server - check_missing_objects server 1 "$oid" + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone creates promisor remotes from hints" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + test_when_finished "git -C server config --unset-all promisor.sendFields" && + test_config -C server promisor.sendFields "partialCloneFilter, token" && + git -C server config remote.lop.partialCloneFilter "blob:none" && + git -C server config remote.lop.token "lop-token" && + test_when_finished "git -C server config --unset remote.lop.token" && + + GIT_NO_LAZY_FETCH=0 git clone \ + -c promisor.acceptfromserver=All \ + -c promisor.createRemotes=true \ + --no-local --filter="blob:limit=5k" server client && + + test_cmp_config -C client "file://$(pwd)/lop" remote.lop.url && + test_cmp_config -C client true remote.lop.promisor && + test_cmp_config -C client "+refs/heads/*:refs/remotes/lop/*" remote.lop.fetch && + test_cmp_config -C client blob:none remote.lop.partialCloneFilter && + test_cmp_config -C client lop-token remote.lop.token && + + check_missing_objects server 1 "$oid" ' test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" ' - git -C server config promisor.advertise true && + git -C server config promisor.advertise true && - # Clone from server to create a client - GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ + # Clone from server to create a client + GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ -c remote.lop.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=All \