From: Junio C Hamano Date: Wed, 15 Aug 2018 22:08:25 +0000 (-0700) Subject: Merge branch 'jt/tag-following-with-proto-v2-fix' X-Git-Tag: v2.19.0-rc0~70 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/d6628c99faf0de4d34c44f815d2aaa7dee38a300?hp=-c Merge branch 'jt/tag-following-with-proto-v2-fix' The wire-protocol v2 relies on the client to send "ref prefixes" to limit the bandwidth spent on the initial ref advertisement. "git fetch $remote branch:branch" that asks tags that point into the history leading to the "branch" automatically followed sent to narrow prefix and broke the tag following, which has been fixed. * jt/tag-following-with-proto-v2-fix: fetch: send "refs/tags/" prefix upon CLI refspecs t5702: test fetch with multiple refspecs at a time --- d6628c99faf0de4d34c44f815d2aaa7dee38a300 diff --combined builtin/fetch.c index 34d2bd123b,1f447f1e8c..e03a1db1a3 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@@ -6,7 -6,6 +6,7 @@@ #include "repository.h" #include "refs.h" #include "refspec.h" +#include "object-store.h" #include "commit.h" #include "builtin.h" #include "string-list.h" @@@ -64,7 -63,6 +64,7 @@@ static int shown_url = 0 static struct refspec refmap = REFSPEC_INIT_FETCH; static struct list_objects_filter_options filter_options; static struct string_list server_options = STRING_LIST_INIT_DUP; +static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; static int git_fetch_config(const char *k, const char *v, void *cb) { @@@ -95,6 -93,19 +95,6 @@@ return git_default_config(k, v, cb); } -static int gitmodules_fetch_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "submodule.fetchjobs")) { - max_children = parse_submodule_fetchjobs(var, value); - return 0; - } else if (!strcmp(var, "fetch.recursesubmodules")) { - recurse_submodules = parse_fetch_recurse_submodules_arg(var, value); - return 0; - } - - return 0; -} - static int parse_refmap_arg(const struct option *opt, const char *arg, int unset) { /* @@@ -163,8 -174,6 +163,8 @@@ static struct option builtin_fetch_opti TRANSPORT_FAMILY_IPV4), OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), TRANSPORT_FAMILY_IPV6), + OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), + N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; @@@ -245,9 -254,9 +245,9 @@@ static int will_fetch(struct ref **head return 0; } -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail) +static void find_non_local_tags(const struct ref *refs, + struct ref **head, + struct ref ***tail) { struct string_list existing_refs = STRING_LIST_INIT_DUP; struct string_list remote_refs = STRING_LIST_INIT_NODUP; @@@ -255,7 -264,7 +255,7 @@@ struct string_list_item *item = NULL; for_each_ref(add_existing, &existing_refs); - for (ref = transport_get_remote_refs(transport, NULL); ref; ref = ref->next) { + for (ref = refs; ref; ref = ref->next) { if (!starts_with(ref->name, "refs/tags/")) continue; @@@ -329,8 -338,7 +329,8 @@@ string_list_clear(&remote_refs, 0); } -static struct ref *get_ref_map(struct transport *transport, +static struct ref *get_ref_map(struct remote *remote, + const struct ref *remote_refs, struct refspec *rs, int tags, int *autotags) { @@@ -338,11 -346,26 +338,11 @@@ struct ref *rm; struct ref *ref_map = NULL; struct ref **tail = &ref_map; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; /* opportunistically-updated references: */ struct ref *orefs = NULL, **oref_tail = &orefs; - const struct ref *remote_refs; - - if (rs->nr) - refspec_ref_prefixes(rs, &ref_prefixes); - else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); - - if (ref_prefixes.argc && - (tags == TAGS_SET || tags == TAGS_DEFAULT)) { - argv_array_push(&ref_prefixes, "refs/tags/"); - } - - remote_refs = transport_get_remote_refs(transport, &ref_prefixes); - - argv_array_clear(&ref_prefixes); + struct string_list existing_refs = STRING_LIST_INIT_DUP; if (rs->nr) { struct refspec *fetch_refspec; @@@ -379,7 -402,7 +379,7 @@@ if (refmap.nr) fetch_refspec = &refmap; else - fetch_refspec = &transport->remote->fetch; + fetch_refspec = &remote->fetch; for (i = 0; i < fetch_refspec->nr; i++) get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1); @@@ -387,6 -410,7 +387,6 @@@ die("--refmap option is only meaningful with command-line refspec(s)."); } else { /* Use the defaults */ - struct remote *remote = transport->remote; struct branch *branch = branch_get(NULL); int has_merge = branch_has_merge_config(branch); if (remote && @@@ -425,7 -449,7 +425,7 @@@ /* also fetch all tags */ get_fetch_map(remote_refs, tag_refspec, &tail, 0); else if (tags == TAGS_DEFAULT && *autotags) - find_non_local_tags(transport, &ref_map, &tail); + find_non_local_tags(remote_refs, &ref_map, &tail); /* Now append any refs to be updated opportunistically: */ *tail = orefs; @@@ -434,23 -458,7 +434,23 @@@ tail = &rm->next; } - return ref_remove_duplicates(ref_map); + ref_map = ref_remove_duplicates(ref_map); + + for_each_ref(add_existing, &existing_refs); + for (rm = ref_map; rm; rm = rm->next) { + if (rm->peer_ref) { + struct string_list_item *peer_item = + string_list_lookup(&existing_refs, + rm->peer_ref->name); + if (peer_item) { + struct object_id *old_oid = peer_item->util; + oidcpy(&rm->peer_ref->old_oid, old_oid); + } + } + } + string_list_clear(&existing_refs, 1); + + return ref_map; } #define STORE_REF_ERROR_OTHER 1 @@@ -675,10 -683,8 +675,10 @@@ static int update_local_ref(struct ref return r; } - current = lookup_commit_reference_gently(&ref->old_oid, 1); - updated = lookup_commit_reference_gently(&ref->new_oid, 1); + current = lookup_commit_reference_gently(the_repository, + &ref->old_oid, 1); + updated = lookup_commit_reference_gently(the_repository, + &ref->new_oid, 1); if (!current || !updated) { const char *msg; const char *what; @@@ -762,7 -768,7 +762,7 @@@ static int iterate_ref_map(void *cb_dat } static int store_updated_refs(const char *raw_url, const char *remote_name, - struct ref *ref_map) + int connectivity_checked, struct ref *ref_map) { FILE *fp; struct commit *commit; @@@ -771,7 -777,7 +771,7 @@@ const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(); + const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository); int want_status; int summary_width = transport_summary_width(ref_map); @@@ -784,12 -790,10 +784,12 @@@ else url = xstrdup("foreign"); - rm = ref_map; - if (check_connected(iterate_ref_map, &rm, NULL)) { - rc = error(_("%s did not send all necessary objects\n"), url); - goto abort; + if (!connectivity_checked) { + rm = ref_map; + if (check_connected(iterate_ref_map, &rm, NULL)) { + rc = error(_("%s did not send all necessary objects\n"), url); + goto abort; + } } prepare_format_display(ref_map); @@@ -813,8 -817,7 +813,8 @@@ continue; } - commit = lookup_commit_reference_gently(&rm->old_oid, + commit = lookup_commit_reference_gently(the_repository, + &rm->old_oid, 1); if (!commit) rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; @@@ -942,32 -945,15 +942,32 @@@ static int quickfetch(struct ref *ref_m return check_connected(iterate_ref_map, &rm, &opt); } -static int fetch_refs(struct transport *transport, struct ref *ref_map) +static int fetch_refs(struct transport *transport, struct ref *ref_map, + struct ref **updated_remote_refs) { int ret = quickfetch(ref_map); if (ret) - ret = transport_fetch_refs(transport, ref_map); + ret = transport_fetch_refs(transport, ref_map, + updated_remote_refs); if (!ret) - ret |= store_updated_refs(transport->url, - transport->remote->name, - ref_map); + /* + * Keep the new pack's ".keep" file around to allow the caller + * time to update refs to reference the new objects. + */ + return 0; + transport_unlock_pack(transport); + return ret; +} + +/* Update local refs based on the ref values fetched from a remote */ +static int consume_refs(struct transport *transport, struct ref *ref_map) +{ + int connectivity_checked = transport->smart_options + ? transport->smart_options->connectivity_checked : 0; + int ret = store_updated_refs(transport->url, + transport->remote->name, + connectivity_checked, + ref_map); transport_unlock_pack(transport); return ret; } @@@ -1043,7 -1029,7 +1043,7 @@@ static void check_not_current_branch(st static int truncate_fetch_head(void) { - const char *filename = git_path_fetch_head(); + const char *filename = git_path_fetch_head(the_repository); FILE *fp = fopen_for_writing(filename); if (!fp) @@@ -1063,40 -1049,6 +1063,40 @@@ static void set_option(struct transpor name, transport->url); } + +static int add_oid(const char *refname, const struct object_id *oid, int flags, + void *cb_data) +{ + struct oid_array *oids = cb_data; + + oid_array_append(oids, oid); + return 0; +} + +static void add_negotiation_tips(struct git_transport_options *smart_options) +{ + struct oid_array *oids = xcalloc(1, sizeof(*oids)); + int i; + + for (i = 0; i < negotiation_tip.nr; i++) { + const char *s = negotiation_tip.items[i].string; + int old_nr; + if (!has_glob_specials(s)) { + struct object_id oid; + if (get_oid(s, &oid)) + die("%s is not a valid object", s); + oid_array_append(oids, &oid); + continue; + } + old_nr = oids->nr; + for_each_glob_ref(add_oid, s, oids); + if (old_nr == oids->nr) + warning("Ignoring --negotiation-tip=%s because it does not match any refs", + s); + } + smart_options->negotiation_tips = oids; +} + static struct transport *prepare_transport(struct remote *remote, int deepen) { struct transport *transport; @@@ -1123,12 -1075,6 +1123,12 @@@ filter_options.filter_spec); set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); } + if (negotiation_tip.nr) { + if (transport->smart_options) + add_negotiation_tips(transport->smart_options); + else + warning("Ignoring --negotiation-tip because the protocol does not support it."); + } return transport; } @@@ -1153,8 -1099,7 +1153,8 @@@ static void backfill_tags(struct transp transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - fetch_refs(transport, ref_map); + if (!fetch_refs(transport, ref_map, NULL)) + consume_refs(transport, ref_map); if (gsecondary) { transport_disconnect(gsecondary); @@@ -1165,12 -1110,13 +1165,12 @@@ static int do_fetch(struct transport *transport, struct refspec *rs) { - struct string_list existing_refs = STRING_LIST_INIT_DUP; struct ref *ref_map; - struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; - - for_each_ref(add_existing, &existing_refs); + const struct ref *remote_refs; + struct ref *updated_remote_refs = NULL; + struct argv_array ref_prefixes = ARGV_ARRAY_INIT; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@@ -1186,24 -1132,22 +1186,24 @@@ goto cleanup; } - ref_map = get_ref_map(transport, rs, tags, &autotags); - if (!update_head_ok) - check_not_current_branch(ref_map); + if (rs->nr) + refspec_ref_prefixes(rs, &ref_prefixes); + else if (transport->remote && transport->remote->fetch.nr) + refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); - for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) { - struct string_list_item *peer_item = - string_list_lookup(&existing_refs, - rm->peer_ref->name); - if (peer_item) { - struct object_id *old_oid = peer_item->util; - oidcpy(&rm->peer_ref->old_oid, old_oid); - } - } + if (ref_prefixes.argc && - (tags == TAGS_SET || (tags == TAGS_DEFAULT && !rs->nr))) { ++ (tags == TAGS_SET || (tags == TAGS_DEFAULT))) { + argv_array_push(&ref_prefixes, "refs/tags/"); } + remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + argv_array_clear(&ref_prefixes); + + ref_map = get_ref_map(transport->remote, remote_refs, rs, + tags, &autotags); + if (!update_head_ok) + check_not_current_branch(ref_map); + if (tags == TAGS_DEFAULT && autotags) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); if (prune) { @@@ -1220,24 -1164,7 +1220,24 @@@ transport->url); } } - if (fetch_refs(transport, ref_map)) { + + if (fetch_refs(transport, ref_map, &updated_remote_refs)) { + free_refs(ref_map); + retcode = 1; + goto cleanup; + } + if (updated_remote_refs) { + /* + * Regenerate ref_map using the updated remote refs. This is + * to account for additional information which may be provided + * by the transport (e.g. shallow info). + */ + free_refs(ref_map); + ref_map = get_ref_map(transport->remote, updated_remote_refs, rs, + tags, &autotags); + free_refs(updated_remote_refs); + } + if (consume_refs(transport, ref_map)) { free_refs(ref_map); retcode = 1; goto cleanup; @@@ -1249,13 -1176,14 +1249,13 @@@ if (tags == TAGS_DEFAULT && autotags) { struct ref **tail = &ref_map; ref_map = NULL; - find_non_local_tags(transport, &ref_map, &tail); + find_non_local_tags(remote_refs, &ref_map, &tail); if (ref_map) backfill_tags(transport, ref_map); free_refs(ref_map); } cleanup: - string_list_clear(&existing_refs, 1); return retcode; } @@@ -1505,7 -1433,7 +1505,7 @@@ int cmd_fetch(int argc, const char **ar for (i = 1; i < argc; i++) strbuf_addf(&default_rla, " %s", argv[i]); - config_from_gitmodules(gitmodules_fetch_config, NULL); + fetch_config_from_gitmodules(&max_children, &recurse_submodules); git_config(git_fetch_config, NULL); argc = parse_options(argc, argv, prefix, @@@ -1521,7 -1449,7 +1521,7 @@@ if (unshallow) { if (depth) die(_("--depth and --unshallow cannot be used together")); - else if (!is_repository_shallow()) + else if (!is_repository_shallow(the_repository)) die(_("--unshallow on a complete repository does not make sense")); else depth = xstrfmt("%d", INFINITE_DEPTH); diff --combined t/t5702-protocol-v2.sh index 9ae560eb2a,fdca1383dd..3beeed4546 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@@ -181,12 -181,7 +181,12 @@@ test_expect_success 'clone with file:/ test_cmp expect actual && # Server responded using protocol v2 - grep "clone< version 2" log + grep "clone< version 2" log && + + # Client sent ref-prefixes to filter the ref-advertisement + grep "ref-prefix HEAD" log && + grep "ref-prefix refs/heads/" log && + grep "ref-prefix refs/tags/" log ' test_expect_success 'fetch with file:// using protocol v2' ' @@@ -209,6 -204,7 +209,7 @@@ test_expect_success 'ref advertisment i test_when_finished "rm -f log" && test_commit -C file_parent three && + git -C file_parent branch unwanted-branch three && GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ fetch origin master && @@@ -217,9 -213,8 +218,8 @@@ git -C file_parent log -1 --format=%s >expect && test_cmp expect actual && - ! grep "refs/tags/one" log && - ! grep "refs/tags/two" log && - ! grep "refs/tags/three" log + grep "refs/heads/master" log && + ! grep "refs/heads/unwanted-branch" log ' test_expect_success 'server-options are sent when fetching' ' @@@ -364,6 -359,71 +364,71 @@@ test_expect_success 'default refspec i grep "ref-prefix refs/tags/" log ' + test_expect_success 'fetch supports various ways of have lines' ' + rm -rf server client trace && + git init server && + test_commit -C server dwim && + TREE=$(git -C server rev-parse HEAD^{tree}) && + git -C server tag exact \ + $(git -C server commit-tree -m a "$TREE") && + git -C server tag dwim-unwanted \ + $(git -C server commit-tree -m b "$TREE") && + git -C server tag exact-unwanted \ + $(git -C server commit-tree -m c "$TREE") && + git -C server tag prefix1 \ + $(git -C server commit-tree -m d "$TREE") && + git -C server tag prefix2 \ + $(git -C server commit-tree -m e "$TREE") && + git -C server tag fetch-by-sha1 \ + $(git -C server commit-tree -m f "$TREE") && + git -C server tag completely-unrelated \ + $(git -C server commit-tree -m g "$TREE") && + + git init client && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch "file://$(pwd)/server" \ + dwim \ + refs/tags/exact \ + refs/tags/prefix*:refs/tags/prefix* \ + "$(git -C server rev-parse fetch-by-sha1)" && + + # Ensure that the appropriate prefixes are sent (using a sample) + grep "fetch> ref-prefix dwim" trace && + grep "fetch> ref-prefix refs/heads/dwim" trace && + grep "fetch> ref-prefix refs/tags/prefix" trace && + + # Ensure that the correct objects are returned + git -C client cat-file -e $(git -C server rev-parse dwim) && + git -C client cat-file -e $(git -C server rev-parse exact) && + git -C client cat-file -e $(git -C server rev-parse prefix1) && + git -C client cat-file -e $(git -C server rev-parse prefix2) && + git -C client cat-file -e $(git -C server rev-parse fetch-by-sha1) && + test_must_fail git -C client cat-file -e \ + $(git -C server rev-parse dwim-unwanted) && + test_must_fail git -C client cat-file -e \ + $(git -C server rev-parse exact-unwanted) && + test_must_fail git -C client cat-file -e \ + $(git -C server rev-parse completely-unrelated) + ' + + test_expect_success 'fetch supports include-tag and tag following' ' + rm -rf server client trace && + git init server && + + test_commit -C server to_fetch && + git -C server tag -a annotated_tag -m message && + + git init client && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch "$(pwd)/server" to_fetch:to_fetch && + + grep "fetch> ref-prefix to_fetch" trace && + grep "fetch> ref-prefix refs/tags/" trace && + grep "fetch> include-tag" trace && + + git -C client cat-file -e $(git -C client rev-parse annotated_tag) + ' + # Test protocol v2 with 'http://' transport # . "$TEST_DIRECTORY"/lib-httpd.sh