Merge branch 'jt/tag-following-with-proto-v2-fix'
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Aug 2018 22:08:25 +0000 (15:08 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Aug 2018 22:08:25 +0000 (15:08 -0700)
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

1  2 
builtin/fetch.c
t/t5702-protocol-v2.sh
diff --combined builtin/fetch.c
index 34d2bd123b3303a096edb99ab4f82c52a89b35f5,1f447f1e8cbe0927edab032c6e0d7286a9ef02ce..e03a1db1a392d20bf45eac693ba3dd01f75da044
@@@ -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)
  {
        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;
        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;
  
        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)
  {
        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;
                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);
                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 &&
                /* 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;
                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;
        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);
  
        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);
                                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;
                           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);
  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)
                        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) {
                                   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;
        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,
        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 9ae560eb2a7238b9ec7d7fca27e10715956b6a00,fdca1383dda5c23c2240c7ff5cb4b2638fc0ae5f..3beeed4546cee6c7793de94ba2735b0e6d63b073
@@@ -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 &&
        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