Merge branch 'bc/object-id'
authorJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:17:37 +0000 (12:17 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:17:37 +0000 (12:17 -0700)
for_each_ref() callback functions were taught to name the objects
not with "unsigned char sha1[20]" but with "struct object_id".

* bc/object-id: (56 commits)
struct ref_lock: convert old_sha1 member to object_id
warn_if_dangling_symref(): convert local variable "junk" to object_id
each_ref_fn_adapter(): remove adapter
rev_list_insert_ref(): remove unneeded arguments
rev_list_insert_ref_oid(): new function, taking an object_oid
mark_complete(): remove unneeded arguments
mark_complete_oid(): new function, taking an object_oid
clear_marks(): rewrite to take an object_id argument
mark_complete(): rewrite to take an object_id argument
send_ref(): convert local variable "peeled" to object_id
upload-pack: rewrite functions to take object_id arguments
find_symref(): convert local variable "unused" to object_id
find_symref(): rewrite to take an object_id argument
write_one_ref(): rewrite to take an object_id argument
write_refs_to_temp_dir(): convert local variable sha1 to object_id
submodule: rewrite to take an object_id argument
shallow: rewrite functions to take object_id arguments
handle_one_ref(): rewrite to take an object_id argument
add_info_ref(): rewrite to take an object_id argument
handle_one_reflog(): rewrite to take an object_id argument
...

1  2 
builtin/branch.c
builtin/for-each-ref.c
fetch-pack.c
help.c
http-backend.c
remote.c
sha1_name.c
submodule.c
upload-pack.c
diff --combined builtin/branch.c
index c70085c35f79f9e87aeadb9abc8aa62e3dc1f1b9,0d3b9af6e9ebc57944def4ce0f7b0feadf4b66c2..b42e5b6dbc76016ca18e5ddd7ded2af613013eb7
@@@ -123,12 -123,14 +123,12 @@@ static int branch_merged(int kind, cons
  
        if (kind == REF_LOCAL_BRANCH) {
                struct branch *branch = branch_get(name);
 +              const char *upstream = branch_get_upstream(branch, NULL);
                unsigned char sha1[20];
  
 -              if (branch &&
 -                  branch->merge &&
 -                  branch->merge[0] &&
 -                  branch->merge[0]->dst &&
 +              if (upstream &&
                    (reference_name = reference_name_to_free =
 -                   resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
 +                   resolve_refdup(upstream, RESOLVE_REF_READING,
                                    sha1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
        }
@@@ -326,7 -328,7 +326,7 @@@ static int match_patterns(const char **
        return 0;
  }
  
- static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+ static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
  {
        struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
        struct ref_list *ref_list = cb->ref_list;
  
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
-               commit = lookup_commit_reference_gently(sha1, 1);
+               commit = lookup_commit_reference_gently(oid->hash, 1);
                if (!commit) {
                        cb->ret = error(_("branch '%s' does not point at a commit"), refname);
                        return 0;
@@@ -425,19 -427,25 +425,19 @@@ static void fill_tracking_info(struct s
        int ours, theirs;
        char *ref = NULL;
        struct branch *branch = branch_get(branch_name);
 +      const char *upstream;
        struct strbuf fancy = STRBUF_INIT;
        int upstream_is_gone = 0;
        int added_decoration = 1;
  
 -      switch (stat_tracking_info(branch, &ours, &theirs)) {
 -      case 0:
 -              /* no base */
 -              return;
 -      case -1:
 -              /* with "gone" base */
 +      if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
 +              if (!upstream)
 +                      return;
                upstream_is_gone = 1;
 -              break;
 -      default:
 -              /* with base */
 -              break;
        }
  
        if (show_upstream_ref) {
 -              ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
 +              ref = shorten_unambiguous_ref(upstream, 0);
                if (want_color(branch_use_color))
                        strbuf_addf(&fancy, "%s%s%s",
                                        branch_get_color(BRANCH_COLOR_UPSTREAM),
diff --combined builtin/for-each-ref.c
index 05dd23d2a35b30eab9d7ef3c01e00c3e99cf5577,05ce28cebbe3f6e5c64bcd1087caef4449d366b1..f7e51a7fadc40b1e4484a657fc55af8a3043967c
@@@ -74,7 -74,6 +74,7 @@@ static struct 
        { "contents:body" },
        { "contents:signature" },
        { "upstream" },
 +      { "push" },
        { "symref" },
        { "flag" },
        { "HEAD" },
@@@ -660,26 -659,15 +660,26 @@@ static void populate_value(struct refin
                else if (starts_with(name, "symref"))
                        refname = ref->symref ? ref->symref : "";
                else if (starts_with(name, "upstream")) {
 +                      const char *branch_name;
                        /* only local branches may have an upstream */
 -                      if (!starts_with(ref->refname, "refs/heads/"))
 +                      if (!skip_prefix(ref->refname, "refs/heads/",
 +                                       &branch_name))
                                continue;
 -                      branch = branch_get(ref->refname + 11);
 +                      branch = branch_get(branch_name);
  
 -                      if (!branch || !branch->merge || !branch->merge[0] ||
 -                          !branch->merge[0]->dst)
 +                      refname = branch_get_upstream(branch, NULL);
 +                      if (!refname)
 +                              continue;
 +              } else if (starts_with(name, "push")) {
 +                      const char *branch_name;
 +                      if (!skip_prefix(ref->refname, "refs/heads/",
 +                                       &branch_name))
 +                              continue;
 +                      branch = branch_get(branch_name);
 +
 +                      refname = branch_get_push(branch, NULL);
 +                      if (!refname)
                                continue;
 -                      refname = branch->merge[0]->dst;
                } else if (starts_with(name, "color:")) {
                        char color[COLOR_MAXLEN] = "";
  
                                refname = shorten_unambiguous_ref(refname,
                                                      warn_ambiguous_refs);
                        else if (!strcmp(formatp, "track") &&
 -                               starts_with(name, "upstream")) {
 +                               (starts_with(name, "upstream") ||
 +                                starts_with(name, "push"))) {
                                char buf[40];
  
                                if (stat_tracking_info(branch, &num_ours,
 -                                                     &num_theirs) != 1)
 +                                                     &num_theirs, NULL))
                                        continue;
  
                                if (!num_ours && !num_theirs)
                                }
                                continue;
                        } else if (!strcmp(formatp, "trackshort") &&
 -                                 starts_with(name, "upstream")) {
 +                                 (starts_with(name, "upstream") ||
 +                                  starts_with(name, "push"))) {
                                assert(branch);
  
                                if (stat_tracking_info(branch, &num_ours,
 -                                                      &num_theirs) != 1)
 +                                                      &num_theirs, NULL))
                                        continue;
  
                                if (!num_ours && !num_theirs)
@@@ -854,7 -840,8 +854,8 @@@ struct grab_ref_cbdata 
   * A call-back given to for_each_ref().  Filter refs and keep them for
   * later object processing.
   */
- static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int grab_single_ref(const char *refname, const struct object_id *oid,
+                          int flag, void *cb_data)
  {
        struct grab_ref_cbdata *cb = cb_data;
        struct refinfo *ref;
         */
        ref = xcalloc(1, sizeof(*ref));
        ref->refname = xstrdup(refname);
-       hashcpy(ref->objectname, sha1);
+       hashcpy(ref->objectname, oid->hash);
        ref->flag = flag;
  
        cnt = cb->grab_cnt;
diff --combined fetch-pack.c
index ff8a13b8c4d8ac18d87fa30ddfde27b07a742b4c,a1dcb27f72ddb7d0bed7951176f5d7b3e8f180d7..a912935a63c225b49eb67174d4142de41758dbe8
@@@ -43,12 -43,7 +43,12 @@@ static int marked
  #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, allow_tip_sha1_in_want;
 +static int non_common_revs, 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). */
 +#define ALLOW_REACHABLE_SHA1  02
 +static unsigned int allow_unadvertised_object_request;
  
  static void rev_list_push(struct commit *commit, int mark)
  {
@@@ -65,7 -60,7 +65,7 @@@
        }
  }
  
- static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int rev_list_insert_ref(const char *refname, const unsigned char *sha1)
  {
        struct object *o = deref_tag(parse_object(sha1), refname, 0);
  
        return 0;
  }
  
- static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int rev_list_insert_ref_oid(const char *refname, const struct object_id *oid,
+                                  int flag, void *cb_data)
  {
-       struct object *o = deref_tag(parse_object(sha1), refname, 0);
+       return rev_list_insert_ref(refname, oid->hash);
+ }
+ 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->hash), refname, 0);
  
        if (o && o->type == OBJ_COMMIT)
                clear_commit_marks((struct commit *)o,
@@@ -231,7 -233,7 +238,7 @@@ static void send_request(struct fetch_p
  
  static void insert_one_alternate_ref(const struct ref *ref, void *unused)
  {
-       rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
+       rev_list_insert_ref(NULL, ref->old_sha1);
  }
  
  #define INITIAL_FLUSH 16
@@@ -268,7 -270,7 +275,7 @@@ static int find_common(struct fetch_pac
                for_each_ref(clear_marks, NULL);
        marked = 1;
  
-       for_each_ref(rev_list_insert_ref, NULL);
+       for_each_ref(rev_list_insert_ref_oid, NULL);
        for_each_alternate_ref(insert_one_alternate_ref, NULL);
  
        fetching = 0;
@@@ -471,7 -473,7 +478,7 @@@ done
  
  static struct commit_list *complete;
  
- static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int mark_complete(const unsigned char *sha1)
  {
        struct object *o = parse_object(sha1);
  
        return 0;
  }
  
+ static int mark_complete_oid(const char *refname, const struct object_id *oid,
+                            int flag, void *cb_data)
+ {
+       return mark_complete(oid->hash);
+ }
  static void mark_recent_complete_commits(struct fetch_pack_args *args,
                                         unsigned long cutoff)
  {
@@@ -547,8 -555,7 +560,8 @@@ static void filter_refs(struct fetch_pa
        }
  
        /* Append unmatched requests to the list */
 -      if (allow_tip_sha1_in_want) {
 +      if ((allow_unadvertised_object_request &
 +          (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) {
                for (i = 0; i < nr_sought; i++) {
                        unsigned char sha1[20];
  
  
  static void mark_alternate_complete(const struct ref *ref, void *unused)
  {
-       mark_complete(NULL, ref->old_sha1, 0, NULL);
+       mark_complete(ref->old_sha1);
  }
  
  static int everything_local(struct fetch_pack_args *args,
        }
  
        if (!args->depth) {
-               for_each_ref(mark_complete, NULL);
+               for_each_ref(mark_complete_oid, NULL);
                for_each_alternate_ref(mark_alternate_complete, NULL);
                commit_list_sort_by_date(&complete);
                if (cutoff)
@@@ -827,12 -834,7 +840,12 @@@ static struct ref *do_fetch_pack(struc
        if (server_supports("allow-tip-sha1-in-want")) {
                if (args->verbose)
                        fprintf(stderr, "Server supports allow-tip-sha1-in-want\n");
 -              allow_tip_sha1_in_want = 1;
 +              allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
 +      }
 +      if (server_supports("allow-reachable-sha1-in-want")) {
 +              if (args->verbose)
 +                      fprintf(stderr, "Server supports allow-reachable-sha1-in-want\n");
 +              allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
        }
        if (!server_supports("thin-pack"))
                args->use_thin_pack = 0;
diff --combined help.c
index 8f72051ae09665fc019fd5623a5c836da76d3e9a,6f3415b0fcf43b1270783fd1bc7d40d27f8df8a2..80ca8ee68d5af25b43314d351bc7ad09d9d9d03b
--- 1/help.c
--- 2/help.c
+++ b/help.c
@@@ -218,39 -218,17 +218,39 @@@ void list_commands(unsigned int colopts
        }
  }
  
 +static int cmd_group_cmp(const void *elem1, const void *elem2)
 +{
 +      const struct cmdname_help *e1 = elem1;
 +      const struct cmdname_help *e2 = elem2;
 +
 +      if (e1->group < e2->group)
 +              return -1;
 +      if (e1->group > e2->group)
 +              return 1;
 +      return strcmp(e1->name, e2->name);
 +}
 +
  void list_common_cmds_help(void)
  {
        int i, longest = 0;
 +      int current_grp = -1;
  
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
                if (longest < strlen(common_cmds[i].name))
                        longest = strlen(common_cmds[i].name);
        }
  
 -      puts(_("The most commonly used git commands are:"));
 +      qsort(common_cmds, ARRAY_SIZE(common_cmds),
 +              sizeof(common_cmds[0]), cmd_group_cmp);
 +
 +      puts(_("These are common Git commands used in various situations:"));
 +
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 +              if (common_cmds[i].group != current_grp) {
 +                      printf("\n%s\n", _(common_cmd_groups[common_cmds[i].group]));
 +                      current_grp = common_cmds[i].group;
 +              }
 +
                printf("   %s   ", common_cmds[i].name);
                mput_char(' ', longest - strlen(common_cmds[i].name));
                puts(_(common_cmds[i].help));
@@@ -429,7 -407,7 +429,7 @@@ struct similar_ref_cb 
        struct string_list *similar_refs;
  };
  
- static int append_similar_ref(const char *refname, const unsigned char *sha1,
+ static int append_similar_ref(const char *refname, const struct object_id *oid,
                              int flags, void *cb_data)
  {
        struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);
diff --combined http-backend.c
index 6bf139b76873bd8553652a4658e6629b24aeeacb,55353ad66a27532844c6cc4d63aaa8b0d4c89c7d..501bf797c0658e33aa8215562c44f0dec9eea75e
@@@ -13,20 -13,18 +13,20 @@@ static const char content_type[] = "Con
  static const char content_length[] = "Content-Length";
  static const char last_modified[] = "Last-Modified";
  static int getanyfile = 1;
 +static unsigned long max_request_buffer = 10 * 1024 * 1024;
  
  static struct string_list *query_params;
  
  struct rpc_service {
        const char *name;
        const char *config_name;
 +      unsigned buffer_input : 1;
        signed enabled : 2;
  };
  
  static struct rpc_service rpc_service[] = {
 -      { "upload-pack", "uploadpack", 1 },
 -      { "receive-pack", "receivepack", -1 },
 +      { "upload-pack", "uploadpack", 1, 1 },
 +      { "receive-pack", "receivepack", 0, -1 },
  };
  
  static struct string_list *get_parameters(void)
@@@ -227,7 -225,6 +227,7 @@@ static void http_config(void
        struct strbuf var = STRBUF_INIT;
  
        git_config_get_bool("http.getanyfile", &getanyfile);
 +      git_config_get_ulong("http.maxrequestbuffer", &max_request_buffer);
  
        for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
                struct rpc_service *svc = &rpc_service[i];
@@@ -269,52 -266,9 +269,52 @@@ static struct rpc_service *select_servi
        return svc;
  }
  
 -static void inflate_request(const char *prog_name, int out)
 +/*
 + * This is basically strbuf_read(), except that if we
 + * hit max_request_buffer we die (we'd rather reject a
 + * maliciously large request than chew up infinite memory).
 + */
 +static ssize_t read_request(int fd, unsigned char **out)
 +{
 +      size_t len = 0, alloc = 8192;
 +      unsigned char *buf = xmalloc(alloc);
 +
 +      if (max_request_buffer < alloc)
 +              max_request_buffer = alloc;
 +
 +      while (1) {
 +              ssize_t cnt;
 +
 +              cnt = read_in_full(fd, buf + len, alloc - len);
 +              if (cnt < 0) {
 +                      free(buf);
 +                      return -1;
 +              }
 +
 +              /* partial read from read_in_full means we hit EOF */
 +              len += cnt;
 +              if (len < alloc) {
 +                      *out = buf;
 +                      return len;
 +              }
 +
 +              /* otherwise, grow and try again (if we can) */
 +              if (alloc == max_request_buffer)
 +                      die("request was larger than our maximum size (%lu);"
 +                          " try setting GIT_HTTP_MAX_REQUEST_BUFFER",
 +                          max_request_buffer);
 +
 +              alloc = alloc_nr(alloc);
 +              if (alloc > max_request_buffer)
 +                      alloc = max_request_buffer;
 +              REALLOC_ARRAY(buf, alloc);
 +      }
 +}
 +
 +static void inflate_request(const char *prog_name, int out, int buffer_input)
  {
        git_zstream stream;
 +      unsigned char *full_request = NULL;
        unsigned char in_buf[8192];
        unsigned char out_buf[8192];
        unsigned long cnt = 0;
        git_inflate_init_gzip_only(&stream);
  
        while (1) {
 -              ssize_t n = xread(0, in_buf, sizeof(in_buf));
 +              ssize_t n;
 +
 +              if (buffer_input) {
 +                      if (full_request)
 +                              n = 0; /* nothing left to read */
 +                      else
 +                              n = read_request(0, &full_request);
 +                      stream.next_in = full_request;
 +              } else {
 +                      n = xread(0, in_buf, sizeof(in_buf));
 +                      stream.next_in = in_buf;
 +              }
 +
                if (n <= 0)
                        die("request ended in the middle of the gzip stream");
 -
 -              stream.next_in = in_buf;
                stream.avail_in = n;
  
                while (0 < stream.avail_in) {
  done:
        git_inflate_end(&stream);
        close(out);
 +      free(full_request);
 +}
 +
 +static void copy_request(const char *prog_name, int out)
 +{
 +      unsigned char *buf;
 +      ssize_t n = read_request(0, &buf);
 +      if (n < 0)
 +              die_errno("error reading request body");
 +      if (write_in_full(out, buf, n) != n)
 +              die("%s aborted reading request", prog_name);
 +      close(out);
 +      free(buf);
  }
  
 -static void run_service(const char **argv)
 +static void run_service(const char **argv, int buffer_input)
  {
        const char *encoding = getenv("HTTP_CONTENT_ENCODING");
        const char *user = getenv("REMOTE_USER");
                                 "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
  
        cld.argv = argv;
 -      if (gzipped_request)
 +      if (buffer_input || gzipped_request)
                cld.in = -1;
        cld.git_cmd = 1;
        if (start_command(&cld))
  
        close(1);
        if (gzipped_request)
 -              inflate_request(argv[0], cld.in);
 +              inflate_request(argv[0], cld.in, buffer_input);
 +      else if (buffer_input)
 +              copy_request(argv[0], cld.in);
        else
                close(0);
  
                exit(1);
  }
  
- static int show_text_ref(const char *name, const unsigned char *sha1,
-       int flag, void *cb_data)
+ static int show_text_ref(const char *name, const struct object_id *oid,
+                        int flag, void *cb_data)
  {
        const char *name_nons = strip_namespace(name);
        struct strbuf *buf = cb_data;
-       struct object *o = parse_object(sha1);
+       struct object *o = parse_object(oid->hash);
        if (!o)
                return 0;
  
-       strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name_nons);
+       strbuf_addf(buf, "%s\t%s\n", oid_to_hex(oid), name_nons);
        if (o->type == OBJ_TAG) {
                o = deref_tag(o, name, 0);
                if (!o)
@@@ -463,7 -392,7 +463,7 @@@ static void get_info_refs(char *arg
                packet_flush(1);
  
                argv[0] = svc->name;
 -              run_service(argv);
 +              run_service(argv, 0);
  
        } else {
                select_getanyfile();
        strbuf_release(&buf);
  }
  
- static int show_head_ref(const char *refname, const unsigned char *sha1,
-       int flag, void *cb_data)
+ static int show_head_ref(const char *refname, const struct object_id *oid,
+                        int flag, void *cb_data)
  {
        struct strbuf *buf = cb_data;
  
        if (flag & REF_ISSYMREF) {
-               unsigned char unused[20];
+               struct object_id unused;
                const char *target = resolve_ref_unsafe(refname,
                                                        RESOLVE_REF_READING,
-                                                       unused, NULL);
+                                                       unused.hash, NULL);
                const char *target_nons = strip_namespace(target);
  
                strbuf_addf(buf, "ref: %s\n", target_nons);
        } else {
-               strbuf_addf(buf, "%s\n", sha1_to_hex(sha1));
+               strbuf_addf(buf, "%s\n", oid_to_hex(oid));
        }
  
        return 0;
@@@ -567,28 -496,25 +567,28 @@@ static void service_rpc(char *service_n
        end_headers();
  
        argv[0] = svc->name;
 -      run_service(argv);
 +      run_service(argv, svc->buffer_input);
        strbuf_release(&buf);
  }
  
 +static int dead;
  static NORETURN void die_webcgi(const char *err, va_list params)
  {
 -      static int dead;
 +      if (dead <= 1) {
 +              vreportf("fatal: ", err, params);
  
 -      if (!dead) {
 -              dead = 1;
                http_status(500, "Internal Server Error");
                hdr_nocache();
                end_headers();
 -
 -              vreportf("fatal: ", err, params);
        }
        exit(0); /* we successfully reported a failure ;-) */
  }
  
 +static int die_webcgi_recursing(void)
 +{
 +      return dead++ > 1;
 +}
 +
  static char* getdir(void)
  {
        struct strbuf buf = STRBUF_INIT;
@@@ -643,7 -569,6 +643,7 @@@ int main(int argc, char **argv
  
        git_extract_argv0_path(argv[0]);
        set_die_routine(die_webcgi);
 +      set_die_is_recursing_routine(die_webcgi_recursing);
  
        if (!method)
                die("No REQUEST_METHOD from server");
                not_found("Repository not exported: '%s'", dir);
  
        http_config();
 +      max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER",
 +                                         max_request_buffer);
 +
        cmd->imp(cmd_arg);
        return 0;
  }
diff --combined remote.c
index a467d4ff077bfe50195c78f2f0348d6dfd922c1f,1623eae2ff4537e3566c067ce745945744648a93..26504b744786c65ea4d6e1e0abbf5c6409af5358
+++ b/remote.c
@@@ -49,7 -49,10 +49,7 @@@ static int branches_alloc
  static int branches_nr;
  
  static struct branch *current_branch;
 -static const char *default_remote_name;
 -static const char *branch_pushremote_name;
  static const char *pushremote_name;
 -static int explicit_default_remote_name;
  
  static struct rewrites rewrites;
  static struct rewrites rewrites_push;
@@@ -364,9 -367,16 +364,9 @@@ static int handle_config(const char *ke
                        return 0;
                branch = make_branch(name, subkey - name);
                if (!strcmp(subkey, ".remote")) {
 -                      if (git_config_string(&branch->remote_name, key, value))
 -                              return -1;
 -                      if (branch == current_branch) {
 -                              default_remote_name = branch->remote_name;
 -                              explicit_default_remote_name = 1;
 -                      }
 +                      return git_config_string(&branch->remote_name, key, value);
                } else if (!strcmp(subkey, ".pushremote")) {
 -                      if (branch == current_branch)
 -                              if (git_config_string(&branch_pushremote_name, key, value))
 -                                      return -1;
 +                      return git_config_string(&branch->pushremote_name, key, value);
                } else if (!strcmp(subkey, ".merge")) {
                        if (!value)
                                return config_error_nonbool(key);
@@@ -491,15 -501,12 +491,15 @@@ static void alias_all_urls(void
  
  static void read_config(void)
  {
 +      static int loaded;
        unsigned char sha1[20];
        const char *head_ref;
        int flag;
 -      if (default_remote_name) /* did this already */
 +
 +      if (loaded)
                return;
 -      default_remote_name = "origin";
 +      loaded = 1;
 +
        current_branch = NULL;
        head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
                current_branch = make_branch(head_ref, 0);
        }
        git_config(handle_config, NULL);
 -      if (branch_pushremote_name) {
 -              free((char *)pushremote_name);
 -              pushremote_name = branch_pushremote_name;
 -      }
        alias_all_urls();
  }
  
@@@ -685,45 -696,22 +685,45 @@@ static int valid_remote_nick(const cha
        return !strchr(name, '/'); /* no slash */
  }
  
 -static struct remote *remote_get_1(const char *name, const char *pushremote_name)
 +const char *remote_for_branch(struct branch *branch, int *explicit)
 +{
 +      if (branch && branch->remote_name) {
 +              if (explicit)
 +                      *explicit = 1;
 +              return branch->remote_name;
 +      }
 +      if (explicit)
 +              *explicit = 0;
 +      return "origin";
 +}
 +
 +const char *pushremote_for_branch(struct branch *branch, int *explicit)
 +{
 +      if (branch && branch->pushremote_name) {
 +              if (explicit)
 +                      *explicit = 1;
 +              return branch->pushremote_name;
 +      }
 +      if (pushremote_name) {
 +              if (explicit)
 +                      *explicit = 1;
 +              return pushremote_name;
 +      }
 +      return remote_for_branch(branch, explicit);
 +}
 +
 +static struct remote *remote_get_1(const char *name,
 +                                 const char *(*get_default)(struct branch *, int *))
  {
        struct remote *ret;
        int name_given = 0;
  
 +      read_config();
 +
        if (name)
                name_given = 1;
 -      else {
 -              if (pushremote_name) {
 -                      name = pushremote_name;
 -                      name_given = 1;
 -              } else {
 -                      name = default_remote_name;
 -                      name_given = explicit_default_remote_name;
 -              }
 -      }
 +      else
 +              name = get_default(current_branch, &name_given);
  
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
  
  struct remote *remote_get(const char *name)
  {
 -      read_config();
 -      return remote_get_1(name, NULL);
 +      return remote_get_1(name, remote_for_branch);
  }
  
  struct remote *pushremote_get(const char *name)
  {
 -      read_config();
 -      return remote_get_1(name, pushremote_name);
 +      return remote_get_1(name, pushremote_for_branch);
  }
  
  int remote_is_configured(const char *name)
@@@ -1643,31 -1633,15 +1643,31 @@@ void set_ref_status_for_push(struct re
  
  static void set_merge(struct branch *ret)
  {
 +      struct remote *remote;
        char *ref;
        unsigned char sha1[20];
        int i;
  
 +      if (!ret)
 +              return; /* no branch */
 +      if (ret->merge)
 +              return; /* already run */
 +      if (!ret->remote_name || !ret->merge_nr) {
 +              /*
 +               * no merge config; let's make sure we don't confuse callers
 +               * with a non-zero merge_nr but a NULL merge
 +               */
 +              ret->merge_nr = 0;
 +              return;
 +      }
 +
 +      remote = remote_get(ret->remote_name);
 +
        ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
        for (i = 0; i < ret->merge_nr; i++) {
                ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
                ret->merge[i]->src = xstrdup(ret->merge_name[i]);
 -              if (!remote_find_tracking(ret->remote, ret->merge[i]) ||
 +              if (!remote_find_tracking(remote, ret->merge[i]) ||
                    strcmp(ret->remote_name, "."))
                        continue;
                if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
@@@ -1687,7 -1661,11 +1687,7 @@@ struct branch *branch_get(const char *n
                ret = current_branch;
        else
                ret = make_branch(name, 0);
 -      if (ret && ret->remote_name) {
 -              ret->remote = remote_get(ret->remote_name);
 -              if (ret->merge_nr)
 -                      set_merge(ret);
 -      }
 +      set_merge(ret);
        return ret;
  }
  
@@@ -1705,130 -1683,6 +1705,130 @@@ int branch_merge_matches(struct branch 
        return refname_match(branch->merge[i]->src, refname);
  }
  
 +__attribute((format (printf,2,3)))
 +static const char *error_buf(struct strbuf *err, const char *fmt, ...)
 +{
 +      if (err) {
 +              va_list ap;
 +              va_start(ap, fmt);
 +              strbuf_vaddf(err, fmt, ap);
 +              va_end(ap);
 +      }
 +      return NULL;
 +}
 +
 +const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
 +{
 +      if (!branch)
 +              return error_buf(err, _("HEAD does not point to a branch"));
 +
 +      if (!branch->merge || !branch->merge[0]) {
 +              /*
 +               * no merge config; is it because the user didn't define any,
 +               * or because it is not a real branch, and get_branch
 +               * auto-vivified it?
 +               */
 +              if (!ref_exists(branch->refname))
 +                      return error_buf(err, _("no such branch: '%s'"),
 +                                       branch->name);
 +              return error_buf(err,
 +                               _("no upstream configured for branch '%s'"),
 +                               branch->name);
 +      }
 +
 +      if (!branch->merge[0]->dst)
 +              return error_buf(err,
 +                               _("upstream branch '%s' not stored as a remote-tracking branch"),
 +                               branch->merge[0]->src);
 +
 +      return branch->merge[0]->dst;
 +}
 +
 +static const char *tracking_for_push_dest(struct remote *remote,
 +                                        const char *refname,
 +                                        struct strbuf *err)
 +{
 +      char *ret;
 +
 +      ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
 +      if (!ret)
 +              return error_buf(err,
 +                               _("push destination '%s' on remote '%s' has no local tracking branch"),
 +                               refname, remote->name);
 +      return ret;
 +}
 +
 +static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
 +{
 +      struct remote *remote;
 +
 +      if (!branch)
 +              return error_buf(err, _("HEAD does not point to a branch"));
 +
 +      remote = remote_get(pushremote_for_branch(branch, NULL));
 +      if (!remote)
 +              return error_buf(err,
 +                               _("branch '%s' has no remote for pushing"),
 +                               branch->name);
 +
 +      if (remote->push_refspec_nr) {
 +              char *dst;
 +              const char *ret;
 +
 +              dst = apply_refspecs(remote->push, remote->push_refspec_nr,
 +                                   branch->refname);
 +              if (!dst)
 +                      return error_buf(err,
 +                                       _("push refspecs for '%s' do not include '%s'"),
 +                                       remote->name, branch->name);
 +
 +              ret = tracking_for_push_dest(remote, dst, err);
 +              free(dst);
 +              return ret;
 +      }
 +
 +      if (remote->mirror)
 +              return tracking_for_push_dest(remote, branch->refname, err);
 +
 +      switch (push_default) {
 +      case PUSH_DEFAULT_NOTHING:
 +              return error_buf(err, _("push has no destination (push.default is 'nothing')"));
 +
 +      case PUSH_DEFAULT_MATCHING:
 +      case PUSH_DEFAULT_CURRENT:
 +              return tracking_for_push_dest(remote, branch->refname, err);
 +
 +      case PUSH_DEFAULT_UPSTREAM:
 +              return branch_get_upstream(branch, err);
 +
 +      case PUSH_DEFAULT_UNSPECIFIED:
 +      case PUSH_DEFAULT_SIMPLE:
 +              {
 +                      const char *up, *cur;
 +
 +                      up = branch_get_upstream(branch, err);
 +                      if (!up)
 +                              return NULL;
 +                      cur = tracking_for_push_dest(remote, branch->refname, err);
 +                      if (!cur)
 +                              return NULL;
 +                      if (strcmp(cur, up))
 +                              return error_buf(err,
 +                                               _("cannot resolve 'simple' push to a single destination"));
 +                      return cur;
 +              }
 +      }
 +
 +      die("BUG: unhandled push situation");
 +}
 +
 +const char *branch_get_push(struct branch *branch, struct strbuf *err)
 +{
 +      if (!branch->push_tracking_ref)
 +              branch->push_tracking_ref = branch_get_push_1(branch, err);
 +      return branch->push_tracking_ref;
 +}
 +
  static int ignore_symref_update(const char *refname)
  {
        unsigned char sha1[20];
@@@ -2023,15 -1877,12 +2023,15 @@@ int ref_newer(const unsigned char *new_
  
  /*
   * Compare a branch with its upstream, and save their differences (number
 - * of commits) in *num_ours and *num_theirs.
 + * of commits) in *num_ours and *num_theirs. The name of the upstream branch
 + * (or NULL if no upstream is defined) is returned via *upstream_name, if it
 + * is not itself NULL.
   *
 - * Return 0 if branch has no upstream (no base), -1 if upstream is missing
 - * (with "gone" base), otherwise 1 (with base).
 + * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
 + * upstream defined, or ref does not exist), 0 otherwise.
   */
 -int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
 +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 +                     const char **upstream_name)
  {
        unsigned char sha1[20];
        struct commit *ours, *theirs;
        int rev_argc;
  
        /* Cannot stat unless we are marked to build on top of somebody else. */
 -      if (!branch ||
 -          !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
 -              return 0;
 +      base = branch_get_upstream(branch, NULL);
 +      if (upstream_name)
 +              *upstream_name = base;
 +      if (!base)
 +              return -1;
  
        /* Cannot stat if what we used to build on no longer exists */
 -      base = branch->merge[0]->dst;
        if (read_ref(base, sha1))
                return -1;
        theirs = lookup_commit_reference(sha1);
        /* are we the same? */
        if (theirs == ours) {
                *num_theirs = *num_ours = 0;
 -              return 1;
 +              return 0;
        }
  
        /* Run "rev-list --left-right ours...theirs" internally... */
        /* clear object flags smudged by the above traversal */
        clear_commit_marks(ours, ALL_REV_FLAGS);
        clear_commit_marks(theirs, ALL_REV_FLAGS);
 -      return 1;
 +      return 0;
  }
  
  /*
  int format_tracking_info(struct branch *branch, struct strbuf *sb)
  {
        int ours, theirs;
 +      const char *full_base;
        char *base;
        int upstream_is_gone = 0;
  
 -      switch (stat_tracking_info(branch, &ours, &theirs)) {
 -      case 0:
 -              /* no base */
 -              return 0;
 -      case -1:
 -              /* with "gone" base */
 +      if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
 +              if (!full_base)
 +                      return 0;
                upstream_is_gone = 1;
 -              break;
 -      default:
 -              /* with base */
 -              break;
        }
  
 -      base = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
 +      base = shorten_unambiguous_ref(full_base, 0);
        if (upstream_is_gone) {
                strbuf_addf(sb,
                        _("Your branch is based on '%s', but the upstream is gone.\n"),
        return 1;
  }
  
- static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int one_local_ref(const char *refname, const struct object_id *oid,
+                        int flag, void *cb_data)
  {
        struct ref ***local_tail = cb_data;
        struct ref *ref;
  
        len = strlen(refname) + 1;
        ref = xcalloc(1, sizeof(*ref) + len);
-       hashcpy(ref->new_sha1, sha1);
+       hashcpy(ref->new_sha1, oid->hash);
        memcpy(ref->name, refname, len);
        **local_tail = ref;
        *local_tail = &ref->next;
  struct ref *get_local_heads(void)
  {
        struct ref *local_refs = NULL, **local_tail = &local_refs;
        for_each_ref(one_local_ref, &local_tail);
        return local_refs;
  }
@@@ -2242,8 -2100,8 +2244,8 @@@ struct stale_heads_info 
        int ref_count;
  };
  
- static int get_stale_heads_cb(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
+ static int get_stale_heads_cb(const char *refname, const struct object_id *oid,
+                             int flags, void *cb_data)
  {
        struct stale_heads_info *info = cb_data;
        struct string_list matches = STRING_LIST_INIT_DUP;
  
        if (stale) {
                struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
-               hashcpy(ref->new_sha1, sha1);
+               hashcpy(ref->new_sha1, oid->hash);
        }
  
  clean_exit:
@@@ -2285,6 -2143,7 +2287,7 @@@ struct ref *get_stale_heads(struct refs
        struct ref *ref, *stale_refs = NULL;
        struct string_list ref_names = STRING_LIST_INIT_NODUP;
        struct stale_heads_info info;
        info.ref_names = &ref_names;
        info.stale_refs_tail = &stale_refs;
        info.refs = refs;
diff --combined sha1_name.c
index 4f3c142c7aa5d5d1ff9f86a0d52d43d9013b4eac,1cb810877af3eee2690f094897e5895da7af41fc..e57513e61032fb851a669255c30d8417f66c81f9
@@@ -6,7 -6,6 +6,7 @@@
  #include "tree-walk.h"
  #include "refs.h"
  #include "remote.h"
 +#include "dir.h"
  
  static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
  
@@@ -416,12 -415,12 +416,12 @@@ static int ambiguous_path(const char *p
        return slash;
  }
  
 -static inline int upstream_mark(const char *string, int len)
 +static inline int at_mark(const char *string, int len,
 +                        const char **suffix, int nr)
  {
 -      const char *suffix[] = { "@{upstream}", "@{u}" };
        int i;
  
 -      for (i = 0; i < ARRAY_SIZE(suffix); i++) {
 +      for (i = 0; i < nr; i++) {
                int suffix_len = strlen(suffix[i]);
                if (suffix_len <= len
                    && !memcmp(string, suffix[i], suffix_len))
        return 0;
  }
  
 +static inline int upstream_mark(const char *string, int len)
 +{
 +      const char *suffix[] = { "@{upstream}", "@{u}" };
 +      return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
 +}
 +
 +static inline int push_mark(const char *string, int len)
 +{
 +      const char *suffix[] = { "@{push}" };
 +      return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
 +}
 +
  static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
  static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
  
@@@ -489,8 -476,7 +489,8 @@@ static int get_sha1_basic(const char *s
                                        nth_prior = 1;
                                        continue;
                                }
 -                              if (!upstream_mark(str + at, len - at)) {
 +                              if (!upstream_mark(str + at, len - at) &&
 +                                  !push_mark(str + at, len - at)) {
                                        reflog_len = (len-1) - (at+2);
                                        len = at;
                                }
@@@ -845,11 -831,11 +845,11 @@@ static int get_sha1_1(const char *name
  /* Remember to update object flag allocation in object.h */
  #define ONELINE_SEEN (1u<<20)
  
- static int handle_one_ref(const char *path,
-               const unsigned char *sha1, int flag, void *cb_data)
+ static int handle_one_ref(const char *path, const struct object_id *oid,
+                         int flag, void *cb_data)
  {
        struct commit_list **list = cb_data;
-       struct object *object = parse_object(sha1);
+       struct object *object = parse_object(oid->hash);
        if (!object)
                return 0;
        if (object->type == OBJ_TAG) {
@@@ -1069,36 -1055,46 +1069,36 @@@ static void set_shortened_ref(struct st
        free(s);
  }
  
 -static const char *get_upstream_branch(const char *branch_buf, int len)
 -{
 -      char *branch = xstrndup(branch_buf, len);
 -      struct branch *upstream = branch_get(*branch ? branch : NULL);
 -
 -      /*
 -       * Upstream can be NULL only if branch refers to HEAD and HEAD
 -       * points to something different than a branch.
 -       */
 -      if (!upstream)
 -              die(_("HEAD does not point to a branch"));
 -      if (!upstream->merge || !upstream->merge[0]->dst) {
 -              if (!ref_exists(upstream->refname))
 -                      die(_("No such branch: '%s'"), branch);
 -              if (!upstream->merge) {
 -                      die(_("No upstream configured for branch '%s'"),
 -                              upstream->name);
 -              }
 -              die(
 -                      _("Upstream branch '%s' not stored as a remote-tracking branch"),
 -                      upstream->merge[0]->src);
 -      }
 -      free(branch);
 -
 -      return upstream->merge[0]->dst;
 -}
 -
 -static int interpret_upstream_mark(const char *name, int namelen,
 -                                 int at, struct strbuf *buf)
 +static int interpret_branch_mark(const char *name, int namelen,
 +                               int at, struct strbuf *buf,
 +                               int (*get_mark)(const char *, int),
 +                               const char *(*get_data)(struct branch *,
 +                                                       struct strbuf *))
  {
        int len;
 +      struct branch *branch;
 +      struct strbuf err = STRBUF_INIT;
 +      const char *value;
  
 -      len = upstream_mark(name + at, namelen - at);
 +      len = get_mark(name + at, namelen - at);
        if (!len)
                return -1;
  
        if (memchr(name, ':', at))
                return -1;
  
 -      set_shortened_ref(buf, get_upstream_branch(name, at));
 +      if (at) {
 +              char *name_str = xmemdupz(name, at);
 +              branch = branch_get(name_str);
 +              free(name_str);
 +      } else
 +              branch = branch_get(NULL);
 +
 +      value = get_data(branch, &err);
 +      if (!value)
 +              die("%s", err.buf);
 +
 +      set_shortened_ref(buf, value);
        return len + at;
  }
  
@@@ -1149,13 -1145,7 +1149,13 @@@ int interpret_branch_name(const char *n
                if (len > 0)
                        return reinterpret(name, namelen, len, buf);
  
 -              len = interpret_upstream_mark(name, namelen, at - name, buf);
 +              len = interpret_branch_mark(name, namelen, at - name, buf,
 +                                          upstream_mark, branch_get_upstream);
 +              if (len > 0)
 +                      return len;
 +
 +              len = interpret_branch_mark(name, namelen, at - name, buf,
 +                                          push_mark, branch_get_push);
                if (len > 0)
                        return len;
        }
@@@ -1247,13 -1237,14 +1247,13 @@@ static void diagnose_invalid_sha1_path(
                                       const char *object_name,
                                       int object_name_len)
  {
 -      struct stat st;
        unsigned char sha1[20];
        unsigned mode;
  
        if (!prefix)
                prefix = "";
  
 -      if (!lstat(filename, &st))
 +      if (file_exists(filename))
                die("Path '%s' exists on disk, but not in '%.*s'.",
                    filename, object_name_len, object_name);
        if (errno == ENOENT || errno == ENOTDIR) {
@@@ -1280,6 -1271,7 +1280,6 @@@ static void diagnose_invalid_index_path
                                        const char *prefix,
                                        const char *filename)
  {
 -      struct stat st;
        const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
                            ce_stage(ce), filename);
        }
  
 -      if (!lstat(filename, &st))
 +      if (file_exists(filename))
                die("Path '%s' exists on disk, but not in the index.", filename);
        if (errno == ENOENT || errno == ENOTDIR)
                die("Path '%s' does not exist (neither on disk nor in the index).",
@@@ -1379,6 -1371,7 +1379,7 @@@ static int get_sha1_with_context_1(cons
                int pos;
                if (!only_to_die && namelen > 2 && name[1] == '/') {
                        struct commit_list *list = NULL;
                        for_each_ref(handle_one_ref, &list);
                        commit_list_sort_by_date(&list);
                        return get_sha1_oneline(name + 2, sha1, list);
                        new_filename = resolve_relative_path(filename);
                        if (new_filename)
                                filename = new_filename;
 -                      ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
 -                      if (ret && only_to_die) {
 -                              diagnose_invalid_sha1_path(prefix, filename,
 -                                                         tree_sha1,
 -                                                         name, len);
 +                      if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
 +                              ret = get_tree_entry_follow_symlinks(tree_sha1,
 +                                      filename, sha1, &oc->symlink_path,
 +                                      &oc->mode);
 +                      } else {
 +                              ret = get_tree_entry(tree_sha1, filename,
 +                                                   sha1, &oc->mode);
 +                              if (ret && only_to_die) {
 +                                      diagnose_invalid_sha1_path(prefix,
 +                                                                 filename,
 +                                                                 tree_sha1,
 +                                                                 name, len);
 +                              }
                        }
                        hashcpy(oc->tree, tree_sha1);
                        strlcpy(oc->path, filename, sizeof(oc->path));
@@@ -1485,7 -1470,5 +1486,7 @@@ void maybe_die_on_misspelt_object_name(
  
  int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
  {
 +      if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
 +              die("BUG: incompatible flags for get_sha1_with_context");
        return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
  }
diff --combined submodule.c
index b8747f5c2607485fe616f875f819db2030037ae3,e4c59df5ac60996d144cab43b5bb833bcf64b67c..15e90d1c10ebc9ecb0825f731b33d81cf3d9b4d5
@@@ -422,7 -422,8 +422,8 @@@ void set_config_fetch_recurse_submodule
        config_fetch_recurse_submodules = value;
  }
  
- static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+ static int has_remote(const char *refname, const struct object_id *oid,
+                     int flags, void *cb_data)
  {
        return 1;
  }
@@@ -616,10 -617,10 +617,10 @@@ static void submodule_collect_changed_c
        }
  }
  
- static int add_sha1_to_array(const char *ref, const unsigned char *sha1,
+ static int add_sha1_to_array(const char *ref, const struct object_id *oid,
                             int flags, void *data)
  {
-       sha1_array_append(data, sha1);
+       sha1_array_append(data, oid->hash);
        return 0;
  }
  
@@@ -891,6 -892,7 +892,6 @@@ int submodule_uses_gitfile(const char *
  
  int ok_to_remove_submodule(const char *path)
  {
 -      struct stat st;
        ssize_t len;
        struct child_process cp = CHILD_PROCESS_INIT;
        const char *argv[] = {
        struct strbuf buf = STRBUF_INIT;
        int ok_to_remove = 1;
  
 -      if ((lstat(path, &st) < 0) || is_empty_dir(path))
 +      if (!file_exists(path) || is_empty_dir(path))
                return 1;
  
        if (!submodule_uses_gitfile(path))
diff --combined upload-pack.c
index 640eae1bbe7e3837bd06b8b5675299f29f7e6021,1cb9a948aa05ac4ba274c1e729e64bdf09c483ea..89e832b64a0548ec79802dfc6911eff9f5c353be
@@@ -35,11 -35,7 +35,11 @@@ static int multi_ack
  static int no_done;
  static int use_thin_pack, use_ofs_delta, use_include_tag;
  static int no_progress, daemon_mode;
 -static int allow_tip_sha1_in_want;
 +/* 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). */
 +#define ALLOW_REACHABLE_SHA1  02
 +static unsigned int allow_unadvertised_object_request;
  static int shallow_nr;
  static struct object_array have_obj;
  static struct object_array want_obj;
@@@ -446,9 -442,8 +446,9 @@@ static int get_common_commits(void
  
  static int is_our_ref(struct object *o)
  {
 -      return o->flags &
 -              ((allow_tip_sha1_in_want ? HIDDEN_REF : 0) | OUR_REF);
 +      int allow_hidden_ref = (allow_unadvertised_object_request &
 +                      (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
 +      return o->flags & ((allow_hidden_ref ? HIDDEN_REF : 0) | OUR_REF);
  }
  
  static void check_non_tip(void)
        char namebuf[42]; /* ^ + SHA-1 + LF */
        int i;
  
 -      /* In the normal in-process case non-tip request can never happen */
 -      if (!stateless_rpc)
 +      /*
 +       * In the normal in-process case without
 +       * uploadpack.allowReachableSHA1InWant,
 +       * non-tip requests can never happen.
 +       */
 +      if (!stateless_rpc && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
                goto error;
  
        cmd.argv = argv;
@@@ -690,9 -681,9 +690,9 @@@ static void receive_needs(void
  }
  
  /* return non-zero if the ref is hidden, otherwise 0 */
- static int mark_our_ref(const char *refname, const unsigned char *sha1)
+ static int mark_our_ref(const char *refname, const struct object_id *oid)
  {
-       struct object *o = lookup_unknown_object(sha1);
+       struct object *o = lookup_unknown_object(oid->hash);
  
        if (ref_is_hidden(refname)) {
                o->flags |= HIDDEN_REF;
        return 0;
  }
  
- static int check_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int check_ref(const char *refname, const struct object_id *oid,
+                    int flag, void *cb_data)
  {
-       mark_our_ref(refname, sha1);
+       mark_our_ref(refname, oid);
        return 0;
  }
  
@@@ -718,51 -710,49 +719,52 @@@ static void format_symref_info(struct s
                strbuf_addf(buf, " symref=%s:%s", item->string, (char *)item->util);
  }
  
- static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ static int send_ref(const char *refname, const struct object_id *oid,
+                   int flag, void *cb_data)
  {
        static const char *capabilities = "multi_ack thin-pack side-band"
                " side-band-64k ofs-delta shallow no-progress"
                " include-tag multi_ack_detailed";
        const char *refname_nons = strip_namespace(refname);
-       unsigned char peeled[20];
+       struct object_id peeled;
  
-       if (mark_our_ref(refname, sha1))
+       if (mark_our_ref(refname, oid))
                return 0;
  
        if (capabilities) {
                struct strbuf symref_info = STRBUF_INIT;
  
                format_symref_info(&symref_info, cb_data);
 -              packet_write(1, "%s %s%c%s%s%s%s agent=%s\n",
 +              packet_write(1, "%s %s%c%s%s%s%s%s agent=%s\n",
-                            sha1_to_hex(sha1), refname_nons,
+                            oid_to_hex(oid), refname_nons,
                             0, capabilities,
 -                           allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
 +                           (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ?
 +                                   " allow-tip-sha1-in-want" : "",
 +                           (allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1) ?
 +                                   " allow-reachable-sha1-in-want" : "",
                             stateless_rpc ? " no-done" : "",
                             symref_info.buf,
                             git_user_agent_sanitized());
                strbuf_release(&symref_info);
        } else {
-               packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
+               packet_write(1, "%s %s\n", oid_to_hex(oid), refname_nons);
        }
        capabilities = NULL;
-       if (!peel_ref(refname, peeled))
-               packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons);
+       if (!peel_ref(refname, peeled.hash))
+               packet_write(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
        return 0;
  }
  
- static int find_symref(const char *refname, const unsigned char *sha1, int flag,
-                      void *cb_data)
+ static int find_symref(const char *refname, const struct object_id *oid,
+                      int flag, void *cb_data)
  {
        const char *symref_target;
        struct string_list_item *item;
-       unsigned char unused[20];
+       struct object_id unused;
  
        if ((flag & REF_ISSYMREF) == 0)
                return 0;
-       symref_target = resolve_ref_unsafe(refname, 0, unused, &flag);
+       symref_target = resolve_ref_unsafe(refname, 0, unused.hash, &flag);
        if (!symref_target || (flag & REF_ISSYMREF) == 0)
                die("'%s' is a symref but it is not?", refname);
        item = string_list_append(cb_data, refname);
@@@ -799,17 -789,9 +801,17 @@@ static void upload_pack(void
  
  static int upload_pack_config(const char *var, const char *value, void *unused)
  {
 -      if (!strcmp("uploadpack.allowtipsha1inwant", var))
 -              allow_tip_sha1_in_want = git_config_bool(var, value);
 -      else if (!strcmp("uploadpack.keepalive", var)) {
 +      if (!strcmp("uploadpack.allowtipsha1inwant", var)) {
 +              if (git_config_bool(var, value))
 +                      allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
 +              else
 +                      allow_unadvertised_object_request &= ~ALLOW_TIP_SHA1;
 +      } else if (!strcmp("uploadpack.allowreachablesha1inwant", var)) {
 +              if (git_config_bool(var, value))
 +                      allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
 +              else
 +                      allow_unadvertised_object_request &= ~ALLOW_REACHABLE_SHA1;
 +      } else if (!strcmp("uploadpack.keepalive", var)) {
                keepalive = git_config_int(var, value);
                if (!keepalive)
                        keepalive = -1;