Merge branch 'jt/connectivity-check-after-unshallow'
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Aug 2018 22:08:28 +0000 (15:08 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Aug 2018 22:08:28 +0000 (15:08 -0700)
"git fetch" sometimes failed to update the remote-tracking refs,
which has been corrected.

* jt/connectivity-check-after-unshallow:
fetch-pack: unify ref in and out param

1  2 
builtin/clone.c
builtin/fetch.c
fetch-pack.c
t/t5551-http-fetch-smart.sh
transport-helper.c
transport.c
transport.h
diff --combined builtin/clone.c
index 26fb0da42dff2124e90999a352c9dc9cdf94d6ac,99e73dae8595d37f704d0fe7f7fd84a190edfcf6..fd2c3ef090146058651af5e411dd304153ee114d
@@@ -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"
@@@ -696,8 -695,7 +696,8 @@@ static void update_head(const struct re
                        install_branch_config(0, head, option_origin, our->name);
                }
        } else if (our) {
 -              struct commit *c = lookup_commit_reference(&our->old_oid);
 +              struct commit *c = lookup_commit_reference(the_repository,
 +                                                         &our->old_oid);
                /* --branch specifies a non-branch (i.e. tags), detach HEAD */
                update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
                           UPDATE_REFS_DIE_ON_ERR);
@@@ -897,8 -895,7 +897,8 @@@ int cmd_clone(int argc, const char **ar
        int err = 0, complete_refs_before_fetch = 1;
        int submodule_progress;
  
 -      struct refspec_item refspec;
 +      struct refspec rs = REFSPEC_INIT_FETCH;
 +      struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
  
        fetch_if_missing = 0;
  
        if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
  
 -      refspec_item_init(&refspec, value.buf, REFSPEC_FETCH);
 +      refspec_append(&rs, value.buf);
  
        strbuf_reset(&value);
  
        if (transport->smart_options && !deepen && !filter_options.choice)
                transport->smart_options->check_self_contained_and_connected = 1;
  
 -      refs = transport_get_remote_refs(transport, NULL);
 +
 +      argv_array_push(&ref_prefixes, "HEAD");
 +      refspec_ref_prefixes(&rs, &ref_prefixes);
 +      if (option_branch)
 +              expand_ref_prefix(&ref_prefixes, option_branch);
 +      if (!option_no_tags)
 +              argv_array_push(&ref_prefixes, "refs/tags/");
 +
 +      refs = transport_get_remote_refs(transport, &ref_prefixes);
  
        if (refs) {
 -              mapped_refs = wanted_peer_refs(refs, &refspec);
 +              mapped_refs = wanted_peer_refs(refs, &rs.items[0]);
                /*
                 * transport_get_remote_refs() may return refs with null sha-1
                 * in mapped_refs (see struct transport->get_refs_list
                        }
  
                if (!is_local && !complete_refs_before_fetch)
-                       transport_fetch_refs(transport, mapped_refs, NULL);
+                       transport_fetch_refs(transport, mapped_refs);
  
                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, NULL);
+               transport_fetch_refs(transport, mapped_refs);
  
        update_remote_refs(refs, mapped_refs, remote_head_points_at,
                           branch_top.buf, reflog_msg.buf, transport,
 -                         !is_local && !filter_options.choice);
 +                         !is_local);
  
        update_head(our_head_points_at, remote_head, reflog_msg.buf);
  
        strbuf_release(&value);
        junk_mode = JUNK_LEAVE_ALL;
  
 -      refspec_item_clear(&refspec);
 +      refspec_clear(&rs);
 +      argv_array_clear(&ref_prefixes);
        return err;
  }
diff --combined builtin/fetch.c
index e03a1db1a392d20bf45eac693ba3dd01f75da044,8f68ba8309e2cd30997d32009518666d0928de26..61bec5d213d47141d14162bd1a2ca88861c22ef7
@@@ -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()
  };
@@@ -675,10 -684,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;
@@@ -771,7 -778,7 +771,7 @@@ static int store_updated_refs(const cha
        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);
  
                                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,13 -948,11 +942,11 @@@ 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,
-                     struct ref **updated_remote_refs)
+ static int fetch_refs(struct transport *transport, struct ref *ref_map)
  {
        int ret = quickfetch(ref_map);
        if (ret)
-               ret = transport_fetch_refs(transport, ref_map,
-                                          updated_remote_refs);
+               ret = transport_fetch_refs(transport, ref_map);
        if (!ret)
                /*
                 * Keep the new pack's ".keep" file around to allow the caller
@@@ -1043,7 -1047,7 +1041,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 -1067,6 +1061,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,7 -1117,7 +1151,7 @@@ 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);
-       if (!fetch_refs(transport, ref_map, NULL))
+       if (!fetch_refs(transport, ref_map))
                consume_refs(transport, ref_map);
  
        if (gsecondary) {
@@@ -1169,7 -1133,6 +1167,6 @@@ static int do_fetch(struct transport *t
        int autotags = (transport->remote->fetch_tags == 1);
        int retcode = 0;
        const struct ref *remote_refs;
-       struct ref *updated_remote_refs = NULL;
        struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
  
        if (tags == TAGS_DEFAULT) {
                refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
  
        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/");
        }
  
                                   transport->url);
                }
        }
-       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)) {
+       if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) {
                free_refs(ref_map);
                retcode = 1;
                goto cleanup;
@@@ -1505,7 -1451,7 +1485,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
index 1ab0b2d37e78456db4e54bd83ade79299a71d821,660259e6b2a875cd49208d7c01de0708c57a67a4..f80a7acdf3b3edd7425a56434c9fae6adcc4f9bd
  #include "connect.h"
  #include "transport.h"
  #include "version.h"
 -#include "prio-queue.h"
  #include "sha1-array.h"
  #include "oidset.h"
  #include "packfile.h"
 +#include "object-store.h"
  #include "connected.h"
 +#include "fetch-negotiator.h"
  
  static int transfer_unpack_limit = -1;
  static int fetch_unpack_limit = -1;
@@@ -35,11 -34,16 +35,11 @@@ static int agent_supported
  static int server_supports_filtering;
  static struct lock_file shallow_lock;
  static const char *alternate_shallow_file;
 +static char *negotiation_algorithm;
  
  /* Remember to update object flag allocation in object.h */
  #define COMPLETE      (1U << 0)
 -#define COMMON                (1U << 1)
 -#define COMMON_REF    (1U << 2)
 -#define SEEN          (1U << 3)
 -#define POPPED                (1U << 4)
 -#define ALTERNATE     (1U << 5)
 -
 -static int marked;
 +#define ALTERNATE     (1U << 1)
  
  /*
   * After sending this many "have"s if we do not get any new ACK , we
@@@ -47,7 -51,8 +47,7 @@@
   */
  #define MAX_IN_VAIN 256
  
 -static struct prio_queue rev_list = { compare_commits_by_commit_date };
 -static int non_common_revs, multi_ack, use_sideband;
 +static int multi_ack, use_sideband;
  /* Allow specifying sha1 if it is a ref tip. */
  #define ALLOW_TIP_SHA1        01
  /* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */
@@@ -79,7 -84,7 +79,7 @@@ static void cache_one_alternate(const c
                                void *vcache)
  {
        struct alternate_object_cache *cache = vcache;
 -      struct object *obj = parse_object(oid);
 +      struct object *obj = parse_object(the_repository, oid);
  
        if (!obj || (obj->flags & ALTERNATE))
                return;
@@@ -89,9 -94,7 +89,9 @@@
        cache->items[cache->nr++] = obj;
  }
  
 -static void for_each_cached_alternate(void (*cb)(struct object *))
 +static void for_each_cached_alternate(struct fetch_negotiator *negotiator,
 +                                    void (*cb)(struct fetch_negotiator *,
 +                                               struct object *))
  {
        static int initialized;
        static struct alternate_object_cache cache;
        }
  
        for (i = 0; i < cache.nr; i++)
 -              cb(cache.items[i]);
 +              cb(negotiator, cache.items[i]);
  }
  
 -static void rev_list_push(struct commit *commit, int mark)
 +static int rev_list_insert_ref(struct fetch_negotiator *negotiator,
 +                             const char *refname,
 +                             const struct object_id *oid)
  {
 -      if (!(commit->object.flags & mark)) {
 -              commit->object.flags |= mark;
 -
 -              if (parse_commit(commit))
 -                      return;
 -
 -              prio_queue_put(&rev_list, commit);
 -
 -              if (!(commit->object.flags & COMMON))
 -                      non_common_revs++;
 -      }
 -}
 -
 -static int rev_list_insert_ref(const char *refname, const struct object_id *oid)
 -{
 -      struct object *o = deref_tag(parse_object(oid), refname, 0);
 +      struct object *o = deref_tag(the_repository,
 +                                   parse_object(the_repository, oid),
 +                                   refname, 0);
  
        if (o && o->type == OBJ_COMMIT)
 -              rev_list_push((struct commit *)o, SEEN);
 +              negotiator->add_tip(negotiator, (struct commit *)o);
  
        return 0;
  }
  static int rev_list_insert_ref_oid(const char *refname, const struct object_id *oid,
                                   int flag, void *cb_data)
  {
 -      return rev_list_insert_ref(refname, oid);
 -}
 -
 -static int clear_marks(const char *refname, const struct object_id *oid,
 -                     int flag, void *cb_data)
 -{
 -      struct object *o = deref_tag(parse_object(oid), refname, 0);
 -
 -      if (o && o->type == OBJ_COMMIT)
 -              clear_commit_marks((struct commit *)o,
 -                                 COMMON | COMMON_REF | SEEN | POPPED);
 -      return 0;
 -}
 -
 -/*
 -   This function marks a rev and its ancestors as common.
 -   In some cases, it is desirable to mark only the ancestors (for example
 -   when only the server does not yet know that they are common).
 -*/
 -
 -static void mark_common(struct commit *commit,
 -              int ancestors_only, int dont_parse)
 -{
 -      if (commit != NULL && !(commit->object.flags & COMMON)) {
 -              struct object *o = (struct object *)commit;
 -
 -              if (!ancestors_only)
 -                      o->flags |= COMMON;
 -
 -              if (!(o->flags & SEEN))
 -                      rev_list_push(commit, SEEN);
 -              else {
 -                      struct commit_list *parents;
 -
 -                      if (!ancestors_only && !(o->flags & POPPED))
 -                              non_common_revs--;
 -                      if (!o->parsed && !dont_parse)
 -                              if (parse_commit(commit))
 -                                      return;
 -
 -                      for (parents = commit->parents;
 -                                      parents;
 -                                      parents = parents->next)
 -                              mark_common(parents->item, 0, dont_parse);
 -              }
 -      }
 -}
 -
 -/*
 -  Get the next rev to send, ignoring the common.
 -*/
 -
 -static const struct object_id *get_rev(void)
 -{
 -      struct commit *commit = NULL;
 -
 -      while (commit == NULL) {
 -              unsigned int mark;
 -              struct commit_list *parents;
 -
 -              if (rev_list.nr == 0 || non_common_revs == 0)
 -                      return NULL;
 -
 -              commit = prio_queue_get(&rev_list);
 -              parse_commit(commit);
 -              parents = commit->parents;
 -
 -              commit->object.flags |= POPPED;
 -              if (!(commit->object.flags & COMMON))
 -                      non_common_revs--;
 -
 -              if (commit->object.flags & COMMON) {
 -                      /* do not send "have", and ignore ancestors */
 -                      commit = NULL;
 -                      mark = COMMON | SEEN;
 -              } else if (commit->object.flags & COMMON_REF)
 -                      /* send "have", and ignore ancestors */
 -                      mark = COMMON | SEEN;
 -              else
 -                      /* send "have", also for its ancestors */
 -                      mark = SEEN;
 -
 -              while (parents) {
 -                      if (!(parents->item->object.flags & SEEN))
 -                              rev_list_push(parents->item, mark);
 -                      if (mark & COMMON)
 -                              mark_common(parents->item, 1, 0);
 -                      parents = parents->next;
 -              }
 -      }
 -
 -      return &commit->object.oid;
 +      return rev_list_insert_ref(cb_data, refname, oid);
  }
  
  enum ack_type {
@@@ -192,10 -297,9 +192,10 @@@ static void send_request(struct fetch_p
                write_or_die(fd, buf->buf, buf->len);
  }
  
 -static void insert_one_alternate_object(struct object *obj)
 +static void insert_one_alternate_object(struct fetch_negotiator *negotiator,
 +                                      struct object *obj)
  {
 -      rev_list_insert_ref(NULL, &obj->oid);
 +      rev_list_insert_ref(negotiator, NULL, &obj->oid);
  }
  
  #define INITIAL_FLUSH 16
@@@ -218,24 -322,7 +218,24 @@@ static int next_flush(int stateless_rpc
        return count;
  }
  
 -static int find_common(struct fetch_pack_args *args,
 +static void mark_tips(struct fetch_negotiator *negotiator,
 +                    const struct oid_array *negotiation_tips)
 +{
 +      int i;
 +
 +      if (!negotiation_tips) {
 +              for_each_ref(rev_list_insert_ref_oid, negotiator);
 +              return;
 +      }
 +
 +      for (i = 0; i < negotiation_tips->nr; i++)
 +              rev_list_insert_ref(negotiator, NULL,
 +                                  &negotiation_tips->oid[i]);
 +      return;
 +}
 +
 +static int find_common(struct fetch_negotiator *negotiator,
 +                     struct fetch_pack_args *args,
                       int fd[2], struct object_id *result_oid,
                       struct ref *refs)
  {
  
        if (args->stateless_rpc && multi_ack == 1)
                die(_("--stateless-rpc requires multi_ack_detailed"));
 -      if (marked)
 -              for_each_ref(clear_marks, NULL);
 -      marked = 1;
  
 -      for_each_ref(rev_list_insert_ref_oid, NULL);
 -      for_each_cached_alternate(insert_one_alternate_object);
 +      mark_tips(negotiator, args->negotiation_tips);
 +      for_each_cached_alternate(negotiator, insert_one_alternate_object);
  
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
                 * interested in the case we *know* the object is
                 * reachable and we have already scanned it.
                 */
 -              if (((o = lookup_object(remote->hash)) != NULL) &&
 +              if (((o = lookup_object(the_repository, remote->hash)) != NULL) &&
                                (o->flags & COMPLETE)) {
                        continue;
                }
                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)) {
                                if (get_oid_hex(arg, &oid))
                                        die(_("invalid unshallow line: %s"), line);
 -                              if (!lookup_object(oid.hash))
 +                              if (!lookup_object(the_repository, oid.hash))
                                        die(_("object not found: %s"), line);
                                /* make sure that it is parsed as shallow */
 -                              if (!parse_object(&oid))
 +                              if (!parse_object(the_repository, &oid))
                                        die(_("error in object: %s"), line);
                                if (unregister_shallow(&oid))
                                        die(_("no shallow found: %s"), line);
        retval = -1;
        if (args->no_dependents)
                goto done;
 -      while ((oid = get_rev())) {
 +      while ((oid = negotiator->next(negotiator))) {
                packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
                print_verbose(args, "have %s", oid_to_hex(oid));
                in_vain++;
                                case ACK_ready:
                                case ACK_continue: {
                                        struct commit *commit =
 -                                              lookup_commit(result_oid);
 +                                              lookup_commit(the_repository,
 +                                                            result_oid);
 +                                      int was_common;
 +
                                        if (!commit)
                                                die(_("invalid commit %s"), oid_to_hex(result_oid));
 +                                      was_common = negotiator->ack(negotiator, commit);
                                        if (args->stateless_rpc
                                         && ack == ACK_common
 -                                       && !(commit->object.flags & COMMON)) {
 +                                       && !was_common) {
                                                /* We need to replay the have for this object
                                                 * on the next RPC request so the peer knows
                                                 * it is in common with us.
                                        } else if (!args->stateless_rpc
                                                   || ack != ACK_common)
                                                in_vain = 0;
 -                                      mark_common(commit, 0, 1);
                                        retval = 0;
                                        got_continue = 1;
 -                                      if (ack == ACK_ready) {
 -                                              clear_prio_queue(&rev_list);
 +                                      if (ack == ACK_ready)
                                                got_ready = 1;
 -                                      }
                                        break;
                                        }
                                }
                                print_verbose(args, _("giving up"));
                                break; /* give up */
                        }
 +                      if (got_ready)
 +                              break;
                }
        }
  done:
@@@ -483,14 -570,14 +483,14 @@@ static struct commit_list *complete
  
  static int mark_complete(const struct object_id *oid)
  {
 -      struct object *o = parse_object(oid);
 +      struct object *o = parse_object(the_repository, oid);
  
        while (o && o->type == OBJ_TAG) {
                struct tag *t = (struct tag *) o;
                if (!t->tagged)
                        break; /* broken repository */
                o->flags |= COMPLETE;
 -              o = parse_object(&t->tagged->oid);
 +              o = parse_object(the_repository, &t->tagged->oid);
        }
        if (o && o->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)o;
@@@ -571,11 -658,11 +571,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;
        *refs = newlist;
  }
  
 -static void mark_alternate_complete(struct object *obj)
 +static void mark_alternate_complete(struct fetch_negotiator *unused,
 +                                  struct object *obj)
  {
        mark_complete(&obj->oid);
  }
@@@ -649,21 -735,12 +649,21 @@@ static int add_loose_objects_to_set(con
        return 0;
  }
  
 -static int everything_local(struct fetch_pack_args *args,
 -                          struct ref **refs,
 -                          struct ref **sought, int nr_sought)
 +/*
 + * Mark recent commits available locally and reachable from a local ref as
 + * COMPLETE. If args->no_dependents is false, also mark COMPLETE remote refs as
 + * COMMON_REF (otherwise, we are not planning to participate in negotiation, and
 + * thus do not need COMMON_REF marks).
 + *
 + * The cutoff time for recency is determined by this heuristic: it is the
 + * earliest commit time of the objects in refs that are commits and that we know
 + * the commit time of.
 + */
 +static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
 +                                       struct fetch_pack_args *args,
 +                                       struct ref **refs)
  {
        struct ref *ref;
 -      int retval;
        int old_save_commit_buffer = save_commit_buffer;
        timestamp_t cutoff = 0;
        struct oidset loose_oid_set = OIDSET_INIT;
  
                if (!has_object_file_with_flags(&ref->old_oid, flags))
                        continue;
 -              o = parse_object(&ref->old_oid);
 +              o = parse_object(the_repository, &ref->old_oid);
                if (!o)
                        continue;
  
        if (!args->no_dependents) {
                if (!args->deepen) {
                        for_each_ref(mark_complete_oid, NULL);
 -                      for_each_cached_alternate(mark_alternate_complete);
 +                      for_each_cached_alternate(NULL, mark_alternate_complete);
                        commit_list_sort_by_date(&complete);
                        if (cutoff)
                                mark_recent_complete_commits(args, cutoff);
                 * Don't mark them common yet; the server has to be told so first.
                 */
                for (ref = *refs; ref; ref = ref->next) {
 -                      struct object *o = deref_tag(lookup_object(ref->old_oid.hash),
 +                      struct object *o = deref_tag(the_repository,
 +                                                   lookup_object(the_repository,
 +                                                   ref->old_oid.hash),
                                                     NULL, 0);
  
                        if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
                                continue;
  
 -                      if (!(o->flags & SEEN)) {
 -                              rev_list_push((struct commit *)o, COMMON_REF | SEEN);
 -
 -                              mark_common((struct commit *)o, 1, 1);
 -                      }
 +                      negotiator->known_common(negotiator,
 +                                               (struct commit *)o);
                }
        }
  
 -      filter_refs(args, refs, sought, nr_sought);
 +      save_commit_buffer = old_save_commit_buffer;
 +}
 +
 +/*
 + * Returns 1 if every object pointed to by the given remote refs is available
 + * locally and reachable from a local ref, and 0 otherwise.
 + */
 +static int everything_local(struct fetch_pack_args *args,
 +                          struct ref **refs)
 +{
 +      struct ref *ref;
 +      int retval;
  
        for (retval = 1, ref = *refs; ref ; ref = ref->next) {
                const struct object_id *remote = &ref->old_oid;
                struct object *o;
  
 -              o = lookup_object(remote->hash);
 +              o = lookup_object(the_repository, remote->hash);
                if (!o || !(o->flags & COMPLETE)) {
                        retval = 0;
                        print_verbose(args, "want %s (%s)", oid_to_hex(remote),
                              ref->name);
        }
  
 -      save_commit_buffer = old_save_commit_buffer;
 -
        return retval;
  }
  
@@@ -913,13 -982,11 +913,13 @@@ static struct ref *do_fetch_pack(struc
        struct object_id oid;
        const char *agent_feature;
        int agent_len;
 +      struct fetch_negotiator negotiator;
 +      fetch_negotiator_init(&negotiator, negotiation_algorithm);
  
        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;
        if (!server_supports("deepen-relative") && args->deepen_relative)
                die(_("Server does not support --deepen"));
  
 -      if (everything_local(args, &ref, sought, nr_sought)) {
 +      mark_complete_and_common_ref(&negotiator, args, &ref);
 +      filter_refs(args, &ref, sought, nr_sought);
 +      if (everything_local(args, &ref)) {
                packet_flush(fd[1]);
                goto all_done;
        }
 -      if (find_common(args, fd, &oid, ref) < 0)
 +      if (find_common(&negotiator, args, fd, &oid, ref) < 0)
                if (!args->keep_pack)
                        /* When cloning, it is not unusual to have
                         * no common commit.
                die(_("git fetch-pack: fetch failed."));
  
   all_done:
 +      negotiator.release(&negotiator);
        return ref;
  }
  
  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);
@@@ -1055,7 -1119,7 +1055,7 @@@ static void add_wants(const struct ref 
                 * interested in the case we *know* the object is
                 * reachable and we have already scanned it.
                 */
 -              if (((o = lookup_object(remote->hash)) != NULL) &&
 +              if (((o = lookup_object(the_repository, remote->hash)) != NULL) &&
                    (o->flags & COMPLETE)) {
                        continue;
                }
@@@ -1078,15 -1142,13 +1078,15 @@@ static void add_common(struct strbuf *r
        }
  }
  
 -static int add_haves(struct strbuf *req_buf, int *haves_to_send, int *in_vain)
 +static int add_haves(struct fetch_negotiator *negotiator,
 +                   struct strbuf *req_buf,
 +                   int *haves_to_send, int *in_vain)
  {
        int ret = 0;
        int haves_added = 0;
        const struct object_id *oid;
  
 -      while ((oid = get_rev())) {
 +      while ((oid = negotiator->next(negotiator))) {
                packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
                if (++haves_added >= *haves_to_send)
                        break;
        return ret;
  }
  
 -static int send_fetch_request(int fd_out, const struct fetch_pack_args *args,
 +static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
 +                            const struct fetch_pack_args *args,
                              const struct ref *wants, struct oidset *common,
                              int *haves_to_send, int *in_vain)
  {
        /* 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 */
                add_common(&req_buf, common);
  
                /* Add initial haves */
 -              ret = add_haves(&req_buf, haves_to_send, in_vain);
 +              ret = add_haves(negotiator, &req_buf, haves_to_send, in_vain);
        }
  
        /* Send request */
@@@ -1185,13 -1246,13 +1185,13 @@@ static int process_section_header(struc
        int ret;
  
        if (packet_reader_peek(reader) != PACKET_READ_NORMAL)
 -              die("error reading section header '%s'", section);
 +              die(_("error reading section header '%s'"), section);
  
        ret = !strcmp(reader->line, section);
  
        if (!peek) {
                if (!ret)
 -                      die("expected '%s', received '%s'",
 +                      die(_("expected '%s', received '%s'"),
                            section, reader->line);
                packet_reader_read(reader);
        }
        return ret;
  }
  
 -static int process_acks(struct packet_reader *reader, struct oidset *common)
 +static int process_acks(struct fetch_negotiator *negotiator,
 +                      struct packet_reader *reader,
 +                      struct oidset *common)
  {
        /* received */
        int received_ready = 0;
                        if (!get_oid_hex(arg, &oid)) {
                                struct commit *commit;
                                oidset_insert(common, &oid);
 -                              commit = lookup_commit(&oid);
 -                              mark_common(commit, 0, 1);
 +                              commit = lookup_commit(the_repository, &oid);
 +                              negotiator->ack(negotiator, commit);
                        }
                        continue;
                }
  
                if (!strcmp(reader->line, "ready")) {
 -                      clear_prio_queue(&rev_list);
                        received_ready = 1;
                        continue;
                }
  
 -              die("unexpected acknowledgment line: '%s'", reader->line);
 +              die(_("unexpected acknowledgment line: '%s'"), reader->line);
        }
  
        if (reader->status != PACKET_READ_FLUSH &&
            reader->status != PACKET_READ_DELIM)
 -              die("error processing acks: %d", reader->status);
 +              die(_("error processing acks: %d"), reader->status);
  
        /* return 0 if no common, 1 if there are common, or 2 if ready */
        return received_ready ? 2 : (received_ack ? 1 : 0);
@@@ -1252,16 -1312,16 +1252,16 @@@ 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)) {
                        if (get_oid_hex(arg, &oid))
                                die(_("invalid unshallow line: %s"), reader->line);
 -                      if (!lookup_object(oid.hash))
 +                      if (!lookup_object(the_repository, oid.hash))
                                die(_("object not found: %s"), reader->line);
                        /* make sure that it is parsed as shallow */
 -                      if (!parse_object(&oid))
 +                      if (!parse_object(the_repository, &oid))
                                die(_("error in object: %s"), reader->line);
                        if (unregister_shallow(&oid))
                                die(_("no shallow found: %s"), reader->line);
  
        if (reader->status != PACKET_READ_FLUSH &&
            reader->status != PACKET_READ_DELIM)
 -              die("error processing shallow info: %d", reader->status);
 +              die(_("error processing shallow info: %d"), reader->status);
  
        setup_alternate_shallow(&shallow_lock, &alternate_shallow_file, NULL);
        args->deepen = 1;
  }
  
- static void receive_wanted_refs(struct packet_reader *reader, struct ref *refs)
+ static void receive_wanted_refs(struct packet_reader *reader,
+                               struct ref **sought, int nr_sought)
  {
        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;
+               int i;
  
                if (parse_oid_hex(reader->line, &oid, &end) || *end++ != ' ')
 -                      die("expected wanted-ref, got '%s'", reader->line);
 +                      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);
+               for (i = 0; i < nr_sought; i++) {
+                       if (!strcmp(end, sought[i]->name)) {
+                               oidcpy(&sought[i]->old_oid, &oid);
                                break;
                        }
                }
  
-               if (!r)
+               if (i == nr_sought)
 -                      die("unexpected wanted-ref: '%s'", reader->line);
 +                      die(_("unexpected wanted-ref: '%s'"), reader->line);
        }
  
        if (reader->status != PACKET_READ_DELIM)
 -              die("error processing wanted refs: %d", reader->status);
 +              die(_("error processing wanted refs: %d"), reader->status);
  }
  
  enum fetch_state {
@@@ -1324,8 -1385,6 +1325,8 @@@ static struct ref *do_fetch_pack_v2(str
        struct packet_reader reader;
        int in_vain = 0;
        int haves_to_send = INITIAL_FLUSH;
 +      struct fetch_negotiator negotiator;
 +      fetch_negotiator_init(&negotiator, negotiation_algorithm);
        packet_reader_init(&reader, fd[0], NULL, 0,
                           PACKET_READ_CHOMP_NEWLINE);
  
                        if (args->depth > 0 || args->deepen_since || args->deepen_not)
                                args->deepen = 1;
  
 -                      if (marked)
 -                              for_each_ref(clear_marks, NULL);
 -                      marked = 1;
 -
 -                      for_each_ref(rev_list_insert_ref_oid, NULL);
 -                      for_each_cached_alternate(insert_one_alternate_object);
 -
                        /* Filter 'ref' by 'sought' and those that aren't local */
 -                      if (everything_local(args, &ref, sought, nr_sought))
 +                      mark_complete_and_common_ref(&negotiator, args, &ref);
 +                      filter_refs(args, &ref, sought, nr_sought);
 +                      if (everything_local(args, &ref))
                                state = FETCH_DONE;
                        else
                                state = FETCH_SEND_REQUEST;
 +
 +                      mark_tips(&negotiator, args->negotiation_tips);
 +                      for_each_cached_alternate(&negotiator,
 +                                                insert_one_alternate_object);
                        break;
                case FETCH_SEND_REQUEST:
 -                      if (send_fetch_request(fd[1], args, ref, &common,
 +                      if (send_fetch_request(&negotiator, fd[1], args, ref,
 +                                             &common,
                                               &haves_to_send, &in_vain))
                                state = FETCH_GET_PACK;
                        else
                        break;
                case FETCH_PROCESS_ACKS:
                        /* Process ACKs/NAKs */
 -                      switch (process_acks(&reader, &common)) {
 +                      switch (process_acks(&negotiator, &reader, &common)) {
                        case 2:
                                state = FETCH_GET_PACK;
                                break;
                                receive_shallow_info(args, &reader);
  
                        if (process_section_header(&reader, "wanted-refs", 1))
-                               receive_wanted_refs(&reader, ref);
+                               receive_wanted_refs(&reader, sought, nr_sought);
  
                        /* get the pack */
                        process_section_header(&reader, "packfile", 0);
                }
        }
  
 +      negotiator.release(&negotiator);
        oidset_clear(&common);
        return ref;
  }
@@@ -1407,8 -1465,6 +1408,8 @@@ static void fetch_pack_config(void
        git_config_get_bool("repack.usedeltabaseoffset", &prefer_ofs_delta);
        git_config_get_bool("fetch.fsckobjects", &fetch_fsck_objects);
        git_config_get_bool("transfer.fsckobjects", &transfer_fsck_objects);
 +      git_config_get_string("fetch.negotiationalgorithm",
 +                            &negotiation_algorithm);
  
        git_config(git_default_config, NULL);
  }
@@@ -1448,17 -1504,16 +1449,16 @@@ static int remove_duplicates_in_refs(st
  }
  
  static void update_shallow(struct fetch_pack_args *args,
-                          struct ref *refs,
+                          struct ref **sought, int nr_sought,
                           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 (r = refs; r; r = r->next)
-               oid_array_append(&ref, &r->old_oid);
+       for (i = 0; i < nr_sought; i++)
+               oid_array_append(&ref, &sought[i]->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(ref.nr, sizeof(*status));
+       status = xcalloc(nr_sought, sizeof(*status));
        assign_shallow_commits_to_refs(si, NULL, status);
        if (si->nr_ours || si->nr_theirs) {
-               for (r = refs, i = 0; r; r = r->next, i++)
+               for (i = 0; i < nr_sought; i++)
                        if (status[i])
-                               r->status = REF_STATUS_REJECT_SHALLOW;
+                               sought[i]->status = REF_STATUS_REJECT_SHALLOW;
        }
        free(status);
        oid_array_clear(&ref);
@@@ -1599,7 -1654,7 +1599,7 @@@ struct ref *fetch_pack(struct fetch_pac
                args->connectivity_checked = 1;
        }
  
-       update_shallow(args, ref_cpy, &si);
+       update_shallow(args, sought, nr_sought, &si);
  cleanup:
        clear_shallow_info(&si);
        return ref_cpy;
index 3aab44bdcbbc8939c0f80c84f7d44f50bc3ede16,989d034accba41e9fa4d13256642882bb75a7e3a..771f36f9ff473d31507af374f1c96fac03192048
@@@ -103,7 -103,13 +103,7 @@@ GET  /smart/repo.git/info/refs?service=
  POST /smart/repo.git/git-upload-pack HTTP/1.1 200
  EOF
  test_expect_success 'used upload-pack service' '
 -      sed -e "
 -              s/^.* \"//
 -              s/\"//
 -              s/ [1-9][0-9]*\$//
 -              s/^GET /GET  /
 -      " >act <"$HTTPD_ROOT_PATH"/access.log &&
 -      test_cmp exp act
 +      check_access_log exp
  '
  
  test_expect_success 'follow redirects (301)' '
@@@ -363,6 -369,24 +363,24 @@@ test_expect_success 'custom http header
                submodule update sub
  '
  
+ test_expect_success 'using fetch command in remote-curl updates refs' '
+       SERVER="$HTTPD_DOCUMENT_ROOT_PATH/twobranch" &&
+       rm -rf "$SERVER" client &&
+       git init "$SERVER" &&
+       test_commit -C "$SERVER" foo &&
+       git -C "$SERVER" update-ref refs/heads/anotherbranch foo &&
+       git clone $HTTPD_URL/smart/twobranch client &&
+       test_commit -C "$SERVER" bar &&
+       git -C client -c protocol.version=0 fetch &&
+       git -C "$SERVER" rev-parse master >expect &&
+       git -C client rev-parse origin/master >actual &&
+       test_cmp expect actual
+ '
  test_expect_success 'GIT_REDACT_COOKIES redacts cookies' '
        rm -rf clone &&
        echo "Set-Cookie: Foo=1" >cookies &&
diff --combined transport-helper.c
index 0a7192910505cee35347daf2115690bb02dfbaa4,1f8ff7e9424e5d780578a6ab00623f814d8f836a..143ca008c8f20384c8c4636e5372817946e66e14
@@@ -48,7 -48,7 +48,7 @@@ static void sendline(struct helper_dat
        if (debug)
                fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
        if (write_in_full(helper->helper->in, buffer->buf, buffer->len) < 0)
 -              die_errno("Full write to remote helper failed");
 +              die_errno(_("full write to remote helper failed"));
  }
  
  static int recvline_fh(FILE *helper, struct strbuf *buffer)
@@@ -77,7 -77,7 +77,7 @@@ static void write_constant(int fd, cons
        if (debug)
                fprintf(stderr, "Debug: Remote helper: -> %s", str);
        if (write_in_full(fd, str, strlen(str)) < 0)
 -              die_errno("Full write to remote helper failed");
 +              die_errno(_("full write to remote helper failed"));
  }
  
  static const char *remove_ext_force(const char *url)
@@@ -129,7 -129,7 +129,7 @@@ static struct child_process *get_helper
  
        code = start_command(helper);
        if (code < 0 && errno == ENOENT)
 -              die("Unable to find remote helper for '%s'", data->name);
 +              die(_("unable to find remote helper for '%s'"), data->name);
        else if (code != 0)
                exit(code);
  
         */
        duped = dup(helper->out);
        if (duped < 0)
 -              die_errno("Can't dup helper output fd");
 +              die_errno(_("can't dup helper output fd"));
        data->out = xfdopen(duped, "r");
  
        write_constant(helper->in, "capabilities\n");
                } else if (starts_with(capname, "no-private-update")) {
                        data->no_private_update = 1;
                } else if (mandatory) {
 -                      die("Unknown mandatory capability %s. This remote "
 -                          "helper probably needs newer version of Git.",
 +                      die(_("unknown mandatory capability %s; this remote "
 +                            "helper probably needs newer version of Git"),
                            capname);
                }
        }
        if (!data->rs.nr && (data->import || data->bidi_import || data->export)) {
 -              warning("This remote helper should implement refspec capability.");
 +              warning(_("this remote helper should implement refspec capability"));
        }
        strbuf_release(&buf);
        if (debug)
@@@ -269,7 -269,7 +269,7 @@@ static int strbuf_set_helper_option(str
        else if (!strcmp(buf->buf, "unsupported"))
                ret = 1;
        else {
 -              warning("%s unexpectedly said: '%s'", data->name, buf->buf);
 +              warning(_("%s unexpectedly said: '%s'"), data->name, buf->buf);
                ret = 1;
        }
        return ret;
@@@ -398,7 -398,7 +398,7 @@@ static int fetch_with_fetch(struct tran
                if (starts_with(buf.buf, "lock ")) {
                        const char *name = buf.buf + 5;
                        if (transport->pack_lockfile)
 -                              warning("%s also locked %s", data->name, name);
 +                              warning(_("%s also locked %s"), data->name, name);
                        else
                                transport->pack_lockfile = xstrdup(name);
                }
                else if (!buf.len)
                        break;
                else
 -                      warning("%s unexpectedly said: '%s'", data->name, buf.buf);
 +                      warning(_("%s unexpectedly said: '%s'"), data->name, buf.buf);
        }
        strbuf_release(&buf);
        return 0;
@@@ -476,7 -476,7 +476,7 @@@ static int fetch_with_import(struct tra
        get_helper(transport);
  
        if (get_importer(transport, &fastimport))
 -              die("Couldn't run fast-import");
 +              die(_("couldn't run fast-import"));
  
        for (i = 0; i < nr_heads; i++) {
                posn = to_fetch[i];
         */
  
        if (finish_command(&fastimport))
 -              die("Error while running fast-import");
 +              die(_("error while running fast-import"));
  
        /*
         * The fast-import stream of a remote helper that advertises
                        private = xstrdup(name);
                if (private) {
                        if (read_ref(private, &posn->old_oid) < 0)
 -                              die("Could not read ref %s", private);
 +                              die(_("could not read ref %s"), private);
                        free(private);
                }
        }
@@@ -554,7 -554,7 +554,7 @@@ static int run_connect(struct transpor
         */
        duped = dup(helper->out);
        if (duped < 0)
 -              die_errno("Can't dup helper output fd");
 +              die_errno(_("can't dup helper output fd"));
        input = xfdopen(duped, "r");
        setvbuf(input, NULL, _IONBF, 0);
  
                        fprintf(stderr, "Debug: Falling back to dumb "
                                "transport.\n");
        } else {
 -              die("Unknown response to connect: %s",
 -                      cmdbuf->buf);
 +              die(_(_("unknown response to connect: %s")),
 +                  cmdbuf->buf);
        }
  
        fclose(input);
@@@ -595,9 -595,9 +595,9 @@@ static int process_connect_service(stru
        if (strcmp(name, exec)) {
                int r = set_helper_option(transport, "servpath", exec);
                if (r > 0)
 -                      warning("Setting remote service path not supported by protocol.");
 +                      warning(_("setting remote service path not supported by protocol"));
                else if (r < 0)
 -                      warning("Invalid remote service path.");
 +                      warning(_("invalid remote service path"));
        }
  
        if (data->connect) {
@@@ -640,10 -640,10 +640,10 @@@ static int connect_helper(struct transp
        /* Get_helper so connect is inited. */
        get_helper(transport);
        if (!data->connect)
 -              die("Operation not supported by protocol.");
 +              die(_("operation not supported by protocol"));
  
        if (!process_connect_service(transport, name, exec))
 -              die("Can't connect to subservice %s.", name);
 +              die(_("can't connect to subservice %s"), name);
  
        fd[0] = data->helper->out;
        fd[1] = data->helper->in;
  }
  
  static int fetch(struct transport *transport,
-                int nr_heads, struct ref **to_fetch,
-                struct ref **fetched_refs)
+                int nr_heads, struct ref **to_fetch)
  {
        struct helper_data *data = transport->data;
        int i, count;
  
        if (process_connect(transport, 0)) {
                do_take_over(transport);
-               return transport->vtable->fetch(transport, nr_heads, to_fetch,
-                                               fetched_refs);
+               return transport->vtable->fetch(transport, nr_heads, to_fetch);
        }
  
        count = 0;
                        transport, "filter",
                        data->transport_options.filter_options.filter_spec);
  
 +      if (data->transport_options.negotiation_tips)
 +              warning("Ignoring --negotiation-tip because the protocol does not support it.");
 +
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
  
@@@ -712,7 -707,7 +710,7 @@@ static int push_update_ref_status(struc
                status = REF_STATUS_REMOTE_REJECT;
                refname = buf->buf + 6;
        } else
 -              die("expected ok/error, helper said '%s'", buf->buf);
 +              die(_("expected ok/error, helper said '%s'"), buf->buf);
  
        msg = strchr(refname, ' ');
        if (msg) {
        if (!*ref)
                *ref = find_ref_by_name(remote_refs, refname);
        if (!*ref) {
 -              warning("helper reported unexpected status of %s", refname);
 +              warning(_("helper reported unexpected status of %s"), refname);
                return 1;
        }
  
@@@ -826,20 -821,20 +824,20 @@@ static void set_common_push_options(str
  {
        if (flags & TRANSPORT_PUSH_DRY_RUN) {
                if (set_helper_option(transport, "dry-run", "true") != 0)
 -                      die("helper %s does not support dry-run", name);
 +                      die(_("helper %s does not support dry-run"), name);
        } else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) {
                if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
 -                      die("helper %s does not support --signed", name);
 +                      die(_("helper %s does not support --signed"), name);
        } else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) {
                if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "if-asked") != 0)
 -                      die("helper %s does not support --signed=if-asked", name);
 +                      die(_("helper %s does not support --signed=if-asked"), name);
        }
  
        if (flags & TRANSPORT_PUSH_OPTIONS) {
                struct string_list_item *item;
                for_each_string_list_item(item, transport->push_options)
                        if (set_helper_option(transport, "push-option", item->string) != 0)
 -                              die("helper %s does not support 'push-option'", name);
 +                              die(_("helper %s does not support 'push-option'"), name);
        }
  }
  
@@@ -931,12 -926,12 +929,12 @@@ static int push_refs_with_export(struc
        struct strbuf buf = STRBUF_INIT;
  
        if (!data->rs.nr)
 -              die("remote-helper doesn't support push; refspec needed");
 +              die(_("remote-helper doesn't support push; refspec needed"));
  
        set_common_push_options(transport, data->name, flags);
        if (flags & TRANSPORT_PUSH_FORCE) {
                if (set_helper_option(transport, "force", "true") != 0)
 -                      warning("helper %s does not support 'force'", data->name);
 +                      warning(_("helper %s does not support 'force'"), data->name);
        }
  
        helper = get_helper(transport);
        }
  
        if (get_exporter(transport, &exporter, &revlist_args))
 -              die("Couldn't run fast-export");
 +              die(_("couldn't run fast-export"));
  
        string_list_clear(&revlist_args, 1);
  
        if (finish_command(&exporter))
 -              die("Error while running fast-export");
 +              die(_("error while running fast-export"));
        if (push_update_refs_status(data, remote_refs, flags))
                return 1;
  
@@@ -1012,9 -1007,8 +1010,9 @@@ static int push_refs(struct transport *
        }
  
        if (!remote_refs) {
 -              fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
 -                      "Perhaps you should specify a branch such as 'master'.\n");
 +              fprintf(stderr,
 +                      _("No refs in common and none specified; doing nothing.\n"
 +                        "Perhaps you should specify a branch such as 'master'.\n"));
                return 0;
        }
  
@@@ -1076,7 -1070,7 +1074,7 @@@ static struct ref *get_refs_list(struc
  
                eov = strchr(buf.buf, ' ');
                if (!eov)
 -                      die("Malformed response in ref list: %s", buf.buf);
 +                      die(_("malformed response in ref list: %s"), buf.buf);
                eon = strchr(eov + 1, ' ');
                *eov = '\0';
                if (eon)
                        if (has_attribute(eon + 1, "unchanged")) {
                                (*tail)->status |= REF_STATUS_UPTODATE;
                                if (read_ref((*tail)->name, &(*tail)->old_oid) < 0)
 -                                      die(_("Could not read ref %s"),
 +                                      die(_("could not read ref %s"),
                                            (*tail)->name);
                        }
                }
@@@ -1229,7 -1223,7 +1227,7 @@@ static int udt_do_read(struct unidirect
        bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
        if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
                errno != EINTR) {
 -              error_errno("read(%s) failed", t->src_name);
 +              error_errno(_("read(%s) failed"), t->src_name);
                return -1;
        } else if (bytes == 0) {
                transfer_debug("%s EOF (with %i bytes in buffer)",
@@@ -1256,7 -1250,7 +1254,7 @@@ static int udt_do_write(struct unidirec
        transfer_debug("%s is writable", t->dest_name);
        bytes = xwrite(t->dest, t->buf, t->bufuse);
        if (bytes < 0 && errno != EWOULDBLOCK) {
 -              error_errno("write(%s) failed", t->dest_name);
 +              error_errno(_("write(%s) failed"), t->dest_name);
                return -1;
        } else if (bytes > 0) {
                t->bufuse -= bytes;
@@@ -1305,11 -1299,11 +1303,11 @@@ static int tloop_join(pthread_t thread
        void *tret;
        err = pthread_join(thread, &tret);
        if (!tret) {
 -              error("%s thread failed", name);
 +              error(_("%s thread failed"), name);
                return 1;
        }
        if (err) {
 -              error("%s thread failed to join: %s", name, strerror(err));
 +              error(_("%s thread failed to join: %s"), name, strerror(err));
                return 1;
        }
        return 0;
@@@ -1328,11 -1322,11 +1326,11 @@@ static int tloop_spawnwait_tasks(struc
        err = pthread_create(&gtp_thread, NULL, udt_copy_task_routine,
                &s->gtp);
        if (err)
 -              die("Can't start thread for copying data: %s", strerror(err));
 +              die(_("can't start thread for copying data: %s"), strerror(err));
        err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine,
                &s->ptg);
        if (err)
 -              die("Can't start thread for copying data: %s", strerror(err));
 +              die(_("can't start thread for copying data: %s"), strerror(err));
  
        ret |= tloop_join(gtp_thread, "Git to program copy");
        ret |= tloop_join(ptg_thread, "Program to git copy");
@@@ -1369,11 -1363,11 +1367,11 @@@ static int tloop_join(pid_t pid, const 
  {
        int tret;
        if (waitpid(pid, &tret, 0) < 0) {
 -              error_errno("%s process failed to wait", name);
 +              error_errno(_("%s process failed to wait"), name);
                return 1;
        }
        if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
 -              error("%s process failed", name);
 +              error(_("%s process failed"), name);
                return 1;
        }
        return 0;
@@@ -1391,7 -1385,7 +1389,7 @@@ static int tloop_spawnwait_tasks(struc
        /* Fork thread #1: git to program. */
        pid1 = fork();
        if (pid1 < 0)
 -              die_errno("Can't start thread for copying data");
 +              die_errno(_("can't start thread for copying data"));
        else if (pid1 == 0) {
                udt_kill_transfer(&s->ptg);
                exit(udt_copy_task_routine(&s->gtp) ? 0 : 1);
        /* Fork thread #2: program to git. */
        pid2 = fork();
        if (pid2 < 0)
 -              die_errno("Can't start thread for copying data");
 +              die_errno(_("can't start thread for copying data"));
        else if (pid2 == 0) {
                udt_kill_transfer(&s->gtp);
                exit(udt_copy_task_routine(&s->ptg) ? 0 : 1);
diff --combined transport.c
index 64c38bdeed3d7363c3ef6291bb6ce7ae6805f30e,af6e692db2a92f637ec6645c130dd3a8fcbd2dff..06ffea277460d68694aa4700b269601b56dd46ce
@@@ -139,7 -139,7 +139,7 @@@ static struct ref *get_refs_from_bundle
                close(data->fd);
        data->fd = read_bundle_header(transport->url, &data->header);
        if (data->fd < 0)
 -              die ("Could not read bundle '%s'.", transport->url);
 +              die(_("could not read bundle '%s'"), transport->url);
        for (i = 0; i < data->header.references.nr; i++) {
                struct ref_list_entry *e = data->header.references.list + i;
                struct ref *ref = alloc_ref(e->name);
  }
  
  static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, struct ref **to_fetch,
-                              struct ref **fetched_refs)
+                              int nr_heads, struct ref **to_fetch)
  {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd,
@@@ -288,8 -287,7 +287,7 @@@ static struct ref *get_refs_via_connect
  }
  
  static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, struct ref **to_fetch,
-                              struct ref **fetched_refs)
+                              int nr_heads, struct ref **to_fetch)
  {
        int ret = 0;
        struct git_transport_data *data = transport->data;
        args.filter_options = data->options.filter_options;
        args.stateless_rpc = transport->stateless_rpc;
        args.server_options = transport->server_options;
 +      args.negotiation_tips = data->options.negotiation_tips;
  
        if (!data->got_remote_heads)
                refs_tmp = get_refs_via_connect(transport, 0, NULL);
        if (report_unmatched_refs(to_fetch, nr_heads))
                ret = -1;
  
-       if (fetched_refs)
-               *fetched_refs = refs;
-       else
-               free_refs(refs);
        free_refs(refs_tmp);
+       free_refs(refs);
        free(dest);
        return ret;
  }
@@@ -662,7 -655,7 +656,7 @@@ static int git_transport_push(struct tr
  
        switch (data->version) {
        case protocol_v2:
 -              die("support for protocol v2 not implemented yet");
 +              die(_("support for protocol v2 not implemented yet"));
                break;
        case protocol_v1:
        case protocol_v0:
@@@ -788,7 -781,7 +782,7 @@@ static enum protocol_allow_config parse
        else if (!strcasecmp(value, "user"))
                return PROTOCOL_ALLOW_USER_ONLY;
  
 -      die("unknown value for config '%s': %s", key, value);
 +      die(_("unknown value for config '%s': %s"), key, value);
  }
  
  static enum protocol_allow_config get_protocol_config(const char *type)
@@@ -854,7 -847,7 +848,7 @@@ int is_transport_allowed(const char *ty
  void transport_check_allowed(const char *type)
  {
        if (!is_transport_allowed(type, -1))
 -              die("transport '%s' not allowed", type);
 +              die(_("transport '%s' not allowed"), type);
  }
  
  static struct transport_vtable bundle_vtable = {
@@@ -883,7 -876,7 +877,7 @@@ struct transport *transport_get(struct 
        ret->progress = isatty(2);
  
        if (!remote)
 -              die("No remote provided to transport_get()");
 +              BUG("No remote provided to transport_get()");
  
        ret->got_remote_refs = 0;
        ret->remote = remote;
        if (helper) {
                transport_helper_init(ret, helper);
        } else if (starts_with(url, "rsync:")) {
 -              die("git-over-rsync is no longer supported");
 +              die(_("git-over-rsync is no longer supported"));
        } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                transport_check_allowed("file");
@@@ -1151,7 -1144,7 +1145,7 @@@ int transport_push(struct transport *tr
                                                      transport->push_options,
                                                      pretend)) {
                                oid_array_clear(&commits);
 -                              die("Failed to push all needed submodules!");
 +                              die(_("failed to push all needed submodules"));
                        }
                        oid_array_clear(&commits);
                }
@@@ -1223,31 -1216,19 +1217,19 @@@ const struct ref *transport_get_remote_
        return transport->remote_refs;
  }
  
- int transport_fetch_refs(struct transport *transport, struct ref *refs,
-                        struct ref **fetched_refs)
+ int transport_fetch_refs(struct transport *transport, struct ref *refs)
  {
        int rc;
        int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
        struct ref **heads = NULL;
-       struct ref *nop_head = NULL, **nop_tail = &nop_head;
        struct ref *rm;
  
        for (rm = refs; rm; rm = rm->next) {
                nr_refs++;
                if (rm->peer_ref &&
                    !is_null_oid(&rm->old_oid) &&
-                   !oidcmp(&rm->peer_ref->old_oid, &rm->old_oid)) {
-                       /*
-                        * These need to be reported as fetched, but we don't
-                        * actually need to fetch them.
-                        */
-                       if (fetched_refs) {
-                               struct ref *nop_ref = copy_ref(rm);
-                               *nop_tail = nop_ref;
-                               nop_tail = &nop_ref->next;
-                       }
+                   !oidcmp(&rm->peer_ref->old_oid, &rm->old_oid))
                        continue;
-               }
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
                heads[nr_heads++] = rm;
        }
                        heads[nr_heads++] = rm;
        }
  
-       rc = transport->vtable->fetch(transport, nr_heads, heads, fetched_refs);
-       if (fetched_refs && nop_head) {
-               *nop_tail = *fetched_refs;
-               *fetched_refs = nop_head;
-       }
+       rc = transport->vtable->fetch(transport, nr_heads, heads);
  
        free(heads);
        return rc;
@@@ -1289,7 -1266,7 +1267,7 @@@ int transport_connect(struct transport 
        if (transport->vtable->connect)
                return transport->vtable->connect(transport, name, exec, fd);
        else
 -              die("Operation not supported by protocol");
 +              die(_("operation not supported by protocol"));
  }
  
  int transport_disconnect(struct transport *transport)
@@@ -1371,7 -1348,7 +1349,7 @@@ static void read_alternate_refs(const c
  
                if (get_oid_hex(line.buf, &oid) ||
                    line.buf[GIT_SHA1_HEXSZ] != ' ') {
 -                      warning("invalid line while parsing alternate refs: %s",
 +                      warning(_("invalid line while parsing alternate refs: %s"),
                                line.buf);
                        break;
                }
diff --combined transport.h
index 113530ea5482f783046baa8ba87388ced46bf6be,c057c44d358739c3e319034f8886e7e8867773f3..01e717c29ee6e0f0724b5757ebc7538a72e7e807
@@@ -36,16 -36,6 +36,16 @@@ struct git_transport_options 
        const char *receivepack;
        struct push_cas_option *cas;
        struct list_objects_filter_options filter_options;
 +
 +      /*
 +       * This is only used during fetch. See the documentation of
 +       * negotiation_tips in struct fetch_pack_args.
 +       *
 +       * This field is only supported by transports that support connect or
 +       * stateless_connect. Set this field directly instead of using
 +       * transport_set_option().
 +       */
 +      struct oid_array *negotiation_tips;
  };
  
  enum transport_family {
@@@ -239,8 -229,7 +239,7 @@@ int transport_push(struct transport *co
  const struct ref *transport_get_remote_refs(struct transport *transport,
                                            const struct argv_array *ref_prefixes);
  
- int transport_fetch_refs(struct transport *transport, struct ref *refs,
-                        struct ref **fetched_refs);
+ int transport_fetch_refs(struct transport *transport, struct ref *refs);
  void transport_unlock_pack(struct transport *transport);
  int transport_disconnect(struct transport *transport);
  char *transport_anonymize_url(const char *url);