Merge branch 'jt/connectivity-check-after-unshallow'
authorJunio C Hamano <gitster@pobox.com>
Tue, 24 Jul 2018 21:50:44 +0000 (14:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Jul 2018 21:50:44 +0000 (14:50 -0700)
"git fetch" failed to correctly validate the set of objects it
received when making a shallow history deeper, which has been
corrected.

* jt/connectivity-check-after-unshallow:
  fetch-pack: write shallow, then check connectivity
  fetch-pack: implement ref-in-want
  fetch-pack: put shallow info in output parameter
  fetch: refactor to make function args narrower
  fetch: refactor fetch_refs into two functions
  fetch: refactor the population of peer ref OIDs
  upload-pack: test negotiation with changing repository
  upload-pack: implement ref-in-want
  test-pkt-line: add unpack-sideband subcommand

1  2 
Documentation/config.txt
Documentation/technical/protocol-v2.txt
builtin/clone.c
builtin/fetch.c
fetch-pack.c
remote.c
t/t5537-fetch-shallow.sh
upload-pack.c

diff --combined Documentation/config.txt
@@@ -354,7 -354,7 +354,7 @@@ advice.*:
                Advice on what to do when you've accidentally added one
                git repo inside of another.
        ignoredHook::
 -              Advice shown if an hook is ignored because the hook is not
 +              Advice shown if a hook is ignored because the hook is not
                set as executable.
        waitingForEditor::
                Print a message to the terminal whenever Git is waiting for
@@@ -390,19 -390,16 +390,19 @@@ core.hideDotFiles:
        default mode is 'dotGitOnly'.
  
  core.ignoreCase::
 -      If true, this option enables various workarounds to enable
 +      Internal variable which enables various workarounds to enable
        Git to work better on filesystems that are not case sensitive,
 -      like FAT. For example, if a directory listing finds
 -      "makefile" when Git expects "Makefile", Git will assume
 +      like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
 +      finds "makefile" when Git expects "Makefile", Git will assume
        it is really the same file, and continue to remember it as
        "Makefile".
  +
  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
  will probe and set core.ignoreCase true if appropriate when the repository
  is created.
 ++
 +Git relies on the proper configuration of this variable for your operating
 +and file system. Modifying this value may result in unexpected behavior.
  
  core.precomposeUnicode::
        This option is only used by Mac OS implementation of Git.
@@@ -1165,8 -1162,7 +1165,8 @@@ color.diff.<slot>:
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
        of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
 -      branches, remote-tracking branches, tags, stash and HEAD, respectively.
 +      branches, remote-tracking branches, tags, stash and HEAD, respectively
 +      and `grafted` for grafted commits.
  
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@@ -1185,10 -1181,8 +1185,10 @@@ color.grep.<slot>:
        filename prefix (when not using `-h`)
  `function`;;
        function name lines (when using `-p`)
 -`linenumber`;;
 +`lineNumber`;;
        line number prefix (when using `-n`)
 +`column`;;
 +      column number prefix (when using `--column`)
  `match`;;
        matching text (same as setting `matchContext` and `matchSelected`)
  `matchContext`;;
@@@ -1803,9 -1797,6 +1803,9 @@@ gitweb.snapshot:
  grep.lineNumber::
        If set to true, enable `-n` option by default.
  
 +grep.column::
 +      If set to true, enable the `--column` option by default.
 +
  grep.patternType::
        Set the default matching behavior. Using a value of 'basic', 'extended',
        'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
@@@ -3336,13 -3327,12 +3336,13 @@@ submodule.<name>.ignore:
  submodule.<name>.active::
        Boolean value indicating if the submodule is of interest to git
        commands.  This config option takes precedence over the
 -      submodule.active config option.
 +      submodule.active config option. See linkgit:gitsubmodules[7] for
 +      details.
  
  submodule.active::
        A repeated field which contains a pathspec used to match against a
        submodule's path to determine if the submodule is of interest to git
 -      commands.
 +      commands. See linkgit:gitsubmodules[7] for details.
  
  submodule.recurse::
        Specifies if commands recurse into submodules by default. This
@@@ -3489,6 -3479,13 +3489,13 @@@ Note that this configuration variable i
  repository-level config (this is a safety measure against fetching from
  untrusted repositories).
  
+ uploadpack.allowRefInWant::
+       If this option is set, `upload-pack` will support the `ref-in-want`
+       feature of the protocol version 2 `fetch` command.  This feature
+       is intended for the benefit of load-balanced servers which may
+       not have the same view of what OIDs their refs point to due to
+       replication delay.
  url.<base>.insteadOf::
        Any URL that starts with this value will be rewritten to
        start, instead, with <base>. In cases where some site serves a
@@@ -64,8 -64,9 +64,8 @@@ When using the http:// or https:// tran
  info/refs request as described in `http-protocol.txt` and requests that
  v2 be used by supplying "version=2" in the `Git-Protocol` header.
  
 -   C: Git-Protocol: version=2
 -   C:
     C: GET $GIT_URL/info/refs?service=git-upload-pack HTTP/1.0
 +   C: Git-Protocol: version=2
  
  A v2 server would reply:
  
@@@ -298,12 -299,21 +298,21 @@@ included in the client's request
        for use with partial clone and partial fetch operations. See
        `rev-list` for possible "filter-spec" values.
  
+ If the 'ref-in-want' feature is advertised, the following argument can
+ be included in the client's request as well as the potential addition of
+ the 'wanted-refs' section in the server's response as explained below.
+     want-ref <ref>
+       Indicates to the server that the client wants to retrieve a
+       particular ref, where <ref> is the full name of a ref on the
+       server.
  The response of `fetch` is broken into a number of sections separated by
  delimiter packets (0001), with each section beginning with its section
  header.
  
      output = *section
-     section = (acknowledgments | shallow-info | packfile)
+     section = (acknowledgments | shallow-info | wanted-refs | packfile)
              (flush-pkt | delim-pkt)
  
      acknowledgments = PKT-LINE("acknowledgments" LF)
      shallow = "shallow" SP obj-id
      unshallow = "unshallow" SP obj-id
  
+     wanted-refs = PKT-LINE("wanted-refs" LF)
+                 *PKT-LINE(wanted-ref LF)
+     wanted-ref = obj-id SP refname
      packfile = PKT-LINE("packfile" LF)
               *PKT-LINE(%x01-03 *%x00-ff)
  
        * This section is only included if a packfile section is also
          included in the response.
  
+     wanted-refs section
+       * This section is only included if the client has requested a
+         ref using a 'want-ref' line and if a packfile section is also
+         included in the response.
+       * Always begins with the section header "wanted-refs".
+       * The server will send a ref listing ("<oid> <refname>") for
+         each reference requested using 'want-ref' lines.
+       * The server MUST NOT send any refs which were not requested
+         using 'want-ref' lines.
      packfile section
        * This section is only included if the client has sent 'want'
          lines in its request and either requested that no more
diff --combined builtin/clone.c
@@@ -15,7 -15,6 +15,7 @@@
  #include "fetch-pack.h"
  #include "refs.h"
  #include "refspec.h"
 +#include "object-store.h"
  #include "tree.h"
  #include "tree-walk.h"
  #include "unpack-trees.h"
@@@ -1078,7 -1077,7 +1078,7 @@@ int cmd_clone(int argc, const char **ar
        if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
  
 -      refspec_item_init(&refspec, value.buf, REFSPEC_FETCH);
 +      refspec_item_init_or_die(&refspec, value.buf, REFSPEC_FETCH);
  
        strbuf_reset(&value);
  
                        }
  
                if (!is_local && !complete_refs_before_fetch)
-                       transport_fetch_refs(transport, mapped_refs);
+                       transport_fetch_refs(transport, mapped_refs, NULL);
  
                remote_head = find_ref_by_name(refs, "HEAD");
                remote_head_points_at =
        if (is_local)
                clone_local(path, git_dir);
        else if (refs && complete_refs_before_fetch)
-               transport_fetch_refs(transport, mapped_refs);
+               transport_fetch_refs(transport, mapped_refs, NULL);
  
        update_remote_refs(refs, mapped_refs, remote_head_points_at,
                           branch_top.buf, reflog_msg.buf, transport,
diff --combined 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"
@@@ -94,6 -93,19 +94,6 @@@ static int git_fetch_config(const char 
        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)
  {
        /*
@@@ -242,9 -254,9 +242,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 && !rs->nr))) {
-               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
@@@ -756,7 -769,7 +757,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);
@@@ -933,15 -948,32 +936,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;
  }
@@@ -1017,7 -1049,7 +1037,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)
@@@ -1087,7 -1119,8 +1107,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))) {
+               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;
  }
  
@@@ -1421,7 -1471,7 +1459,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 fetch-pack.c
@@@ -19,7 -19,7 +19,8 @@@
  #include "sha1-array.h"
  #include "oidset.h"
  #include "packfile.h"
 +#include "object-store.h"
+ #include "connected.h"
  
  static int transfer_unpack_limit = -1;
  static int fetch_unpack_limit = -1;
@@@ -397,7 -397,7 +398,7 @@@ static int find_common(struct fetch_pac
                return 1;
        }
  
 -      if (is_repository_shallow())
 +      if (is_repository_shallow(the_repository))
                write_shallow_commits(&req_buf, 1, NULL);
        if (args->depth > 0)
                packet_buf_write(&req_buf, "deepen %d", args->depth);
                        if (skip_prefix(line, "shallow ", &arg)) {
                                if (get_oid_hex(arg, &oid))
                                        die(_("invalid shallow line: %s"), line);
 -                              register_shallow(&oid);
 +                              register_shallow(the_repository, &oid);
                                continue;
                        }
                        if (skip_prefix(line, "unshallow ", &arg)) {
@@@ -658,11 -658,11 +659,11 @@@ static void filter_refs(struct fetch_pa
                                }
                                i++;
                        }
 -              }
  
 -              if (!keep && args->fetch_all &&
 -                  (!args->deepen || !starts_with(ref->name, "refs/tags/")))
 -                      keep = 1;
 +                      if (!keep && args->fetch_all &&
 +                          (!args->deepen || !starts_with(ref->name, "refs/tags/")))
 +                              keep = 1;
 +              }
  
                if (keep) {
                        *newtail = ref;
@@@ -986,7 -986,7 +987,7 @@@ static struct ref *do_fetch_pack(struc
        sort_ref_list(&ref, ref_compare_name);
        QSORT(sought, nr_sought, cmp_ref_by_name);
  
 -      if ((args->depth > 0 || is_repository_shallow()) && !server_supports("shallow"))
 +      if ((args->depth > 0 || is_repository_shallow(the_repository)) && !server_supports("shallow"))
                die(_("Server does not support shallow clients"));
        if (args->depth > 0 || args->deepen_since || args->deepen_not)
                args->deepen = 1;
  static void add_shallow_requests(struct strbuf *req_buf,
                                 const struct fetch_pack_args *args)
  {
 -      if (is_repository_shallow())
 +      if (is_repository_shallow(the_repository))
                write_shallow_commits(req_buf, 1, NULL);
        if (args->depth > 0)
                packet_buf_write(req_buf, "deepen %d", args->depth);
  
  static void add_wants(const struct ref *wants, struct strbuf *req_buf)
  {
+       int use_ref_in_want = server_supports_feature("fetch", "ref-in-want", 0);
        for ( ; wants ; wants = wants->next) {
                const struct object_id *remote = &wants->old_oid;
-               const char *remote_hex;
                struct object *o;
  
                /*
                        continue;
                }
  
-               remote_hex = oid_to_hex(remote);
-               packet_buf_write(req_buf, "want %s\n", remote_hex);
+               if (!use_ref_in_want || wants->exact_oid)
+                       packet_buf_write(req_buf, "want %s\n", oid_to_hex(remote));
+               else
+                       packet_buf_write(req_buf, "want-ref %s\n", wants->name);
        }
  }
  
@@@ -1196,7 -1199,7 +1200,7 @@@ static int send_fetch_request(int fd_ou
        /* Add shallow-info and deepen request */
        if (server_supports_feature("fetch", "shallow", 0))
                add_shallow_requests(&req_buf, args);
 -      else if (is_repository_shallow() || args->deepen)
 +      else if (is_repository_shallow(the_repository) || args->deepen)
                die(_("Server does not support shallow requests"));
  
        /* Add filter */
@@@ -1309,7 -1312,7 +1313,7 @@@ static void receive_shallow_info(struc
                if (skip_prefix(reader->line, "shallow ", &arg)) {
                        if (get_oid_hex(arg, &oid))
                                die(_("invalid shallow line: %s"), reader->line);
 -                      register_shallow(&oid);
 +                      register_shallow(the_repository, &oid);
                        continue;
                }
                if (skip_prefix(reader->line, "unshallow ", &arg)) {
        args->deepen = 1;
  }
  
+ static void receive_wanted_refs(struct packet_reader *reader, struct ref *refs)
+ {
+       process_section_header(reader, "wanted-refs", 0);
+       while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
+               struct object_id oid;
+               const char *end;
+               struct ref *r = NULL;
+               if (parse_oid_hex(reader->line, &oid, &end) || *end++ != ' ')
+                       die("expected wanted-ref, got '%s'", reader->line);
+               for (r = refs; r; r = r->next) {
+                       if (!strcmp(end, r->name)) {
+                               oidcpy(&r->old_oid, &oid);
+                               break;
+                       }
+               }
+               if (!r)
+                       die("unexpected wanted-ref: '%s'", reader->line);
+       }
+       if (reader->status != PACKET_READ_DELIM)
+               die("error processing wanted refs: %d", reader->status);
+ }
  enum fetch_state {
        FETCH_CHECK_LOCAL = 0,
        FETCH_SEND_REQUEST,
@@@ -1409,6 -1438,9 +1439,9 @@@ static struct ref *do_fetch_pack_v2(str
                        if (process_section_header(&reader, "shallow-info", 1))
                                receive_shallow_info(args, &reader);
  
+                       if (process_section_header(&reader, "wanted-refs", 1))
+                               receive_wanted_refs(&reader, ref);
                        /* get the pack */
                        process_section_header(&reader, "packfile", 0);
                        if (get_pack(args, fd, pack_lockfile))
@@@ -1471,16 -1503,17 +1504,17 @@@ static int remove_duplicates_in_refs(st
  }
  
  static void update_shallow(struct fetch_pack_args *args,
-                          struct ref **sought, int nr_sought,
+                          struct ref *refs,
                           struct shallow_info *si)
  {
        struct oid_array ref = OID_ARRAY_INIT;
        int *status;
        int i;
+       struct ref *r;
  
        if (args->deepen && alternate_shallow_file) {
                if (*alternate_shallow_file == '\0') { /* --unshallow */
 -                      unlink_or_warn(git_path_shallow());
 +                      unlink_or_warn(git_path_shallow(the_repository));
                        rollback_lock_file(&shallow_lock);
                } else
                        commit_lock_file(&shallow_lock);
        remove_nonexistent_theirs_shallow(si);
        if (!si->nr_ours && !si->nr_theirs)
                return;
-       for (i = 0; i < nr_sought; i++)
-               oid_array_append(&ref, &sought[i]->old_oid);
+       for (r = refs; r; r = r->next)
+               oid_array_append(&ref, &r->old_oid);
        si->ref = &ref;
  
        if (args->update_shallow) {
         * remote is also shallow, check what ref is safe to update
         * without updating .git/shallow
         */
-       status = xcalloc(nr_sought, sizeof(*status));
+       status = xcalloc(ref.nr, sizeof(*status));
        assign_shallow_commits_to_refs(si, NULL, status);
        if (si->nr_ours || si->nr_theirs) {
-               for (i = 0; i < nr_sought; i++)
+               for (r = refs, i = 0; r; r = r->next, i++)
                        if (status[i])
-                               sought[i]->status = REF_STATUS_REJECT_SHALLOW;
+                               r->status = REF_STATUS_REJECT_SHALLOW;
        }
        free(status);
        oid_array_clear(&ref);
  }
  
+ static int iterate_ref_map(void *cb_data, struct object_id *oid)
+ {
+       struct ref **rm = cb_data;
+       struct ref *ref = *rm;
+       if (!ref)
+               return -1; /* end of the list */
+       *rm = ref->next;
+       oidcpy(oid, &ref->old_oid);
+       return 0;
+ }
  struct ref *fetch_pack(struct fetch_pack_args *args,
                       int fd[], struct child_process *conn,
                       const struct ref *ref,
                ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
                                        &si, pack_lockfile);
        reprepare_packed_git(the_repository);
-       update_shallow(args, sought, nr_sought, &si);
+       if (!args->cloning && args->deepen) {
+               struct check_connected_options opt = CHECK_CONNECTED_INIT;
+               struct ref *iterator = ref_cpy;
+               opt.shallow_file = alternate_shallow_file;
+               if (args->deepen)
+                       opt.is_deepening_fetch = 1;
+               if (check_connected(iterate_ref_map, &iterator, &opt)) {
+                       error(_("remote did not send all necessary objects"));
+                       free_refs(ref_cpy);
+                       ref_cpy = NULL;
+                       rollback_lock_file(&shallow_lock);
+                       goto cleanup;
+               }
+               args->connectivity_checked = 1;
+       }
+       update_shallow(args, ref_cpy, &si);
+ cleanup:
        clear_shallow_info(&si);
        return ref_cpy;
  }
diff --combined remote.c
+++ b/remote.c
@@@ -3,7 -3,6 +3,7 @@@
  #include "remote.h"
  #include "refs.h"
  #include "refspec.h"
 +#include "object-store.h"
  #include "commit.h"
  #include "diff.h"
  #include "revision.h"
@@@ -1736,6 -1735,7 +1736,7 @@@ int get_fetch_map(const struct ref *rem
                if (refspec->exact_sha1) {
                        ref_map = alloc_ref(name);
                        get_oid_hex(name, &ref_map->old_oid);
+                       ref_map->exact_oid = 1;
                } else {
                        ref_map = get_remote_ref(remote_refs, name);
                }
diff --combined t/t5537-fetch-shallow.sh
@@@ -175,8 -175,8 +175,8 @@@ EO
  
  test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' '
        cp -R .git read-only.git &&
 -      find read-only.git -print | xargs chmod -w &&
        test_when_finished "find read-only.git -type d -print | xargs chmod +w" &&
 +      find read-only.git -print | xargs chmod -w &&
        git clone --no-local --depth=2 read-only.git from-read-only &&
        git --git-dir=from-read-only/.git log --format=%s >actual &&
        cat >expect <<EOF &&
@@@ -186,4 -186,47 +186,47 @@@ EO
        test_cmp expect actual
  '
  
+ . "$TEST_DIRECTORY"/lib-httpd.sh
+ start_httpd
+ REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo"
+ test_expect_success 'shallow fetches check connectivity before writing shallow file' '
+       rm -rf "$REPO" client &&
+       git init "$REPO" &&
+       test_commit -C "$REPO" one &&
+       test_commit -C "$REPO" two &&
+       test_commit -C "$REPO" three &&
+       git init client &&
+       # Use protocol v2 to ensure that shallow information is sent exactly
+       # once by the server, since we are planning to manipulate it.
+       git -C "$REPO" config protocol.version 2 &&
+       git -C client config protocol.version 2 &&
+       git -C client fetch --depth=2 "$HTTPD_URL/one_time_sed/repo" master:a_branch &&
+       # Craft a situation in which the server sends back an unshallow request
+       # with an empty packfile. This is done by refetching with a shorter
+       # depth (to ensure that the packfile is empty), and overwriting the
+       # shallow line in the response with the unshallow line we want.
+       printf "s/0034shallow %s/0036unshallow %s/" \
+              "$(git -C "$REPO" rev-parse HEAD)" \
+              "$(git -C "$REPO" rev-parse HEAD^)" \
+              >"$HTTPD_ROOT_PATH/one-time-sed" &&
+       test_must_fail git -C client fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \
+               master:a_branch &&
+       # Ensure that the one-time-sed script was used.
+       ! test -e "$HTTPD_ROOT_PATH/one-time-sed" &&
+       # Ensure that the resulting repo is consistent, despite our failure to
+       # fetch.
+       git -C client fsck
+ '
+ stop_httpd
  test_done
diff --combined upload-pack.c
@@@ -3,7 -3,6 +3,7 @@@
  #include "refs.h"
  #include "pkt-line.h"
  #include "sideband.h"
 +#include "object-store.h"
  #include "tag.h"
  #include "object.h"
  #include "commit.h"
@@@ -65,6 -64,7 +65,7 @@@ static const char *pack_objects_hook
  
  static int filter_capability_requested;
  static int allow_filter;
+ static int allow_ref_in_want;
  static struct list_objects_filter_options filter_options;
  
  static void reset_timeout(void)
@@@ -659,7 -659,7 +660,7 @@@ static void send_shallow(struct commit_
                if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) {
                        packet_write_fmt(1, "shallow %s",
                                         oid_to_hex(&object->oid));
 -                      register_shallow(&object->oid);
 +                      register_shallow(the_repository, &object->oid);
                        shallow_nr++;
                }
                result = result->next;
@@@ -696,14 -696,14 +697,14 @@@ static void send_unshallow(const struc
                        add_object_array(object, NULL, &extra_edge_obj);
                }
                /* make sure commit traversal conforms to client */
 -              register_shallow(&object->oid);
 +              register_shallow(the_repository, &object->oid);
        }
  }
  
  static void deepen(int depth, int deepen_relative,
                   struct object_array *shallows)
  {
 -      if (depth == INFINITE_DEPTH && !is_repository_shallow()) {
 +      if (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {
                int i;
  
                for (i = 0; i < shallows->nr; i++) {
@@@ -783,8 -783,7 +784,8 @@@ static int send_shallow_list(int depth
                if (shallows->nr > 0) {
                        int i;
                        for (i = 0; i < shallows->nr; i++)
 -                              register_shallow(&shallows->objects[i].item->oid);
 +                              register_shallow(the_repository,
 +                                               &shallows->objects[i].item->oid);
                }
        }
  
@@@ -1077,6 -1076,8 +1078,8 @@@ static int upload_pack_config(const cha
                        return git_config_string(&pack_objects_hook, var, value);
        } else if (!strcmp("uploadpack.allowfilter", var)) {
                allow_filter = git_config_bool(var, value);
+       } else if (!strcmp("uploadpack.allowrefinwant", var)) {
+               allow_ref_in_want = git_config_bool(var, value);
        }
        return parse_hide_refs_config(var, value, "uploadpack");
  }
@@@ -1116,6 -1117,7 +1119,7 @@@ void upload_pack(struct upload_pack_opt
  
  struct upload_pack_data {
        struct object_array wants;
+       struct string_list wanted_refs;
        struct oid_array haves;
  
        struct object_array shallows;
  static void upload_pack_data_init(struct upload_pack_data *data)
  {
        struct object_array wants = OBJECT_ARRAY_INIT;
+       struct string_list wanted_refs = STRING_LIST_INIT_DUP;
        struct oid_array haves = OID_ARRAY_INIT;
        struct object_array shallows = OBJECT_ARRAY_INIT;
        struct string_list deepen_not = STRING_LIST_INIT_DUP;
  
        memset(data, 0, sizeof(*data));
        data->wants = wants;
+       data->wanted_refs = wanted_refs;
        data->haves = haves;
        data->shallows = shallows;
        data->deepen_not = deepen_not;
  static void upload_pack_data_clear(struct upload_pack_data *data)
  {
        object_array_clear(&data->wants);
+       string_list_clear(&data->wanted_refs, 1);
        oid_array_clear(&data->haves);
        object_array_clear(&data->shallows);
        string_list_clear(&data->deepen_not, 0);
@@@ -1187,6 -1192,34 +1194,34 @@@ static int parse_want(const char *line
        return 0;
  }
  
+ static int parse_want_ref(const char *line, struct string_list *wanted_refs)
+ {
+       const char *arg;
+       if (skip_prefix(line, "want-ref ", &arg)) {
+               struct object_id oid;
+               struct string_list_item *item;
+               struct object *o;
+               if (read_ref(arg, &oid)) {
+                       packet_write_fmt(1, "ERR unknown ref %s", arg);
+                       die("unknown ref %s", arg);
+               }
+               item = string_list_append(wanted_refs, arg);
+               item->util = oiddup(&oid);
+               o = parse_object_or_die(&oid, arg);
+               if (!(o->flags & WANTED)) {
+                       o->flags |= WANTED;
+                       add_object_array(o, NULL, &want_obj);
+               }
+               return 1;
+       }
+       return 0;
+ }
  static int parse_have(const char *line, struct oid_array *haves)
  {
        const char *arg;
@@@ -1212,6 -1245,8 +1247,8 @@@ static void process_args(struct packet_
                /* process want */
                if (parse_want(arg))
                        continue;
+               if (allow_ref_in_want && parse_want_ref(arg, &data->wanted_refs))
+                       continue;
                /* process have line */
                if (parse_have(arg, &data->haves))
                        continue;
@@@ -1354,19 -1389,36 +1391,37 @@@ static int process_haves_and_send_acks(
        return ret;
  }
  
+ static void send_wanted_ref_info(struct upload_pack_data *data)
+ {
+       const struct string_list_item *item;
+       if (!data->wanted_refs.nr)
+               return;
+       packet_write_fmt(1, "wanted-refs\n");
+       for_each_string_list_item(item, &data->wanted_refs) {
+               packet_write_fmt(1, "%s %s\n",
+                                oid_to_hex(item->util),
+                                item->string);
+       }
+       packet_delim(1);
+ }
  static void send_shallow_info(struct upload_pack_data *data)
  {
        /* No shallow info needs to be sent */
        if (!data->depth && !data->deepen_rev_list && !data->shallows.nr &&
 -          !is_repository_shallow())
 +          !is_repository_shallow(the_repository))
                return;
  
        packet_write_fmt(1, "shallow-info\n");
  
        if (!send_shallow_list(data->depth, data->deepen_rev_list,
                               data->deepen_since, &data->deepen_not,
 -                             &data->shallows) && is_repository_shallow())
 +                             &data->shallows) &&
 +          is_repository_shallow(the_repository))
                deepen(INFINITE_DEPTH, data->deepen_relative, &data->shallows);
  
        packet_delim(1);
@@@ -1421,6 -1473,7 +1476,7 @@@ int upload_pack_v2(struct repository *r
                                state = FETCH_DONE;
                        break;
                case FETCH_SEND_PACK:
+                       send_wanted_ref_info(&data);
                        send_shallow_info(&data);
  
                        packet_write_fmt(1, "packfile\n");
@@@ -1441,12 -1494,22 +1497,22 @@@ int upload_pack_advertise(struct reposi
  {
        if (value) {
                int allow_filter_value;
+               int allow_ref_in_want;
                strbuf_addstr(value, "shallow");
                if (!repo_config_get_bool(the_repository,
                                         "uploadpack.allowfilter",
                                         &allow_filter_value) &&
                    allow_filter_value)
                        strbuf_addstr(value, " filter");
+               if (!repo_config_get_bool(the_repository,
+                                        "uploadpack.allowrefinwant",
+                                        &allow_ref_in_want) &&
+                   allow_ref_in_want)
+                       strbuf_addstr(value, " ref-in-want");
        }
        return 1;
  }