Merge branch 'jk/quarantine-received-objects'
authorJunio C Hamano <gitster@pobox.com>
Mon, 24 Apr 2017 05:07:51 +0000 (22:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 24 Apr 2017 05:07:52 +0000 (22:07 -0700)
Add finishing touches to a recent topic.

* jk/quarantine-received-objects:
refs: reject ref updates while GIT_QUARANTINE_PATH is set
receive-pack: document user-visible quarantine effects
receive-pack: drop tmp_objdir_env from run_update_hook

1  2 
builtin/receive-pack.c
refs.c
t/t5547-push-quarantine.sh
diff --combined builtin/receive-pack.c
index 3cba3fd278fb05f86ec2e23483261d22bc4c04cd,a7408591d92789b0aefb6529db7e42d2dc6e2037..7f484e7f6b41be0555babb54ba8853d0bf1e4269
@@@ -21,7 -21,6 +21,7 @@@
  #include "sigchain.h"
  #include "fsck.h"
  #include "tmp-objdir.h"
 +#include "oidset.h"
  
  static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@@ -225,10 -224,10 +225,10 @@@ static int receive_pack_config(const ch
        return git_default_config(var, value, cb);
  }
  
 -static void show_ref(const char *path, const unsigned char *sha1)
 +static void show_ref(const char *path, const struct object_id *oid)
  {
        if (sent_capabilities) {
 -              packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
 +              packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), path);
        } else {
                struct strbuf cap = STRBUF_INIT;
  
                if (advertise_push_options)
                        strbuf_addstr(&cap, " push-options");
                strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
 -              packet_write(1, "%s %s%c%s\n",
 -                           sha1_to_hex(sha1), path, 0, cap.buf);
 +              packet_write_fmt(1, "%s %s%c%s\n",
 +                           oid_to_hex(oid), path, 0, cap.buf);
                strbuf_release(&cap);
                sent_capabilities = 1;
        }
  }
  
  static int show_ref_cb(const char *path_full, const struct object_id *oid,
 -                     int flag, void *unused)
 +                     int flag, void *data)
  {
 +      struct oidset *seen = data;
        const char *path = strip_namespace(path_full);
  
        if (ref_is_hidden(path, path_full))
        /*
         * Advertise refs outside our current namespace as ".have"
         * refs, so that the client can use them to minimize data
 -       * transfer but will otherwise ignore them. This happens to
 -       * cover ".have" that are thrown in by add_one_alternate_ref()
 -       * to mark histories that are complete in our alternates as
 -       * well.
 +       * transfer but will otherwise ignore them.
         */
 -      if (!path)
 +      if (!path) {
 +              if (oidset_insert(seen, oid))
 +                      return 0;
                path = ".have";
 -      show_ref(path, oid->hash);
 +      } else {
 +              oidset_insert(seen, oid);
 +      }
 +      show_ref(path, oid);
        return 0;
  }
  
 -static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
 +static void show_one_alternate_ref(const char *refname,
 +                                 const struct object_id *oid,
 +                                 void *data)
  {
 -      show_ref(".have", sha1);
 -}
 +      struct oidset *seen = data;
  
 -static void collect_one_alternate_ref(const struct ref *ref, void *data)
 -{
 -      struct sha1_array *sa = data;
 -      sha1_array_append(sa, ref->old_oid.hash);
 +      if (oidset_insert(seen, oid))
 +              return;
 +
 +      show_ref(".have", oid);
  }
  
  static void write_head_info(void)
  {
 -      struct sha1_array sa = SHA1_ARRAY_INIT;
 +      static struct oidset seen = OIDSET_INIT;
  
 -      for_each_alternate_ref(collect_one_alternate_ref, &sa);
 -      sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
 -      sha1_array_clear(&sa);
 -      for_each_ref(show_ref_cb, NULL);
 +      for_each_ref(show_ref_cb, &seen);
 +      for_each_alternate_ref(show_one_alternate_ref, &seen);
 +      oidset_clear(&seen);
        if (!sent_capabilities)
 -              show_ref("capabilities^{}", null_sha1);
 +              show_ref("capabilities^{}", &null_oid);
  
        advertise_shallow_grafts(1);
  
@@@ -309,8 -305,8 +309,8 @@@ struct command 
        unsigned int skip_update:1,
                     did_not_exist:1;
        int index;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 +      struct object_id old_oid;
 +      struct object_id new_oid;
        char ref_name[FLEX_ARRAY]; /* more */
  };
  
@@@ -723,7 -719,7 +723,7 @@@ static int feed_receive_hook(void *stat
                return -1; /* EOF */
        strbuf_reset(&state->buf);
        strbuf_addf(&state->buf, "%s %s %s\n",
 -                  sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
 +                  oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
                    cmd->ref_name);
        state->cmd = cmd->next;
        if (bufp) {
@@@ -764,15 -760,14 +764,14 @@@ static int run_update_hook(struct comma
                return 0;
  
        argv[1] = cmd->ref_name;
 -      argv[2] = sha1_to_hex(cmd->old_sha1);
 -      argv[3] = sha1_to_hex(cmd->new_sha1);
 +      argv[2] = oid_to_hex(&cmd->old_oid);
 +      argv[3] = oid_to_hex(&cmd->new_oid);
        argv[4] = NULL;
  
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
        proc.argv = argv;
-       proc.env = tmp_objdir_env(tmp_objdir);
  
        code = start_command(&proc);
        if (code)
@@@ -798,8 -793,8 +797,8 @@@ static char *refuse_unconfigured_deny_m
           "with what you pushed, and will require 'git reset --hard' to match\n"
           "the work tree to HEAD.\n"
           "\n"
 -         "You can set 'receive.denyCurrentBranch' configuration variable to\n"
 -         "'ignore' or 'warn' in the remote repository to allow pushing into\n"
 +         "You can set the 'receive.denyCurrentBranch' configuration variable\n"
 +         "to 'ignore' or 'warn' in the remote repository to allow pushing into\n"
           "its current branch; however, this is not recommended unless you\n"
           "arranged to update its work tree to match what you pushed in some\n"
           "other way.\n"
@@@ -831,7 -826,7 +830,7 @@@ static int command_singleton_iterator(v
  static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
  {
        static struct lock_file shallow_lock;
 -      struct sha1_array extra = SHA1_ARRAY_INIT;
 +      struct oid_array extra = OID_ARRAY_INIT;
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        uint32_t mask = 1 << (cmd->index % 32);
        int i;
                if (si->used_shallow[i] &&
                    (si->used_shallow[i][cmd->index / 32] & mask) &&
                    !delayed_reachability_test(si, i))
 -                      sha1_array_append(&extra, si->shallow->sha1[i]);
 +                      oid_array_append(&extra, &si->shallow->oid[i]);
  
        opt.env = tmp_objdir_env(tmp_objdir);
        setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
        if (check_connected(command_singleton_iterator, cmd, &opt)) {
                rollback_lock_file(&shallow_lock);
 -              sha1_array_clear(&extra);
 +              oid_array_clear(&extra);
                return -1;
        }
  
         * not lose these new roots..
         */
        for (i = 0; i < extra.nr; i++)
 -              register_shallow(extra.sha1[i]);
 +              register_shallow(extra.oid[i].hash);
  
        si->shallow_ref[cmd->index] = 0;
 -      sha1_array_clear(&extra);
 +      oid_array_clear(&extra);
        return 0;
  }
  
@@@ -988,8 -983,8 +987,8 @@@ static const char *update(struct comman
        const char *name = cmd->ref_name;
        struct strbuf namespaced_name_buf = STRBUF_INIT;
        const char *namespaced_name, *ret;
 -      unsigned char *old_sha1 = cmd->old_sha1;
 -      unsigned char *new_sha1 = cmd->new_sha1;
 +      struct object_id *old_oid = &cmd->old_oid;
 +      struct object_id *new_oid = &cmd->new_oid;
  
        /* only refs/... are allowed */
        if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
                                refuse_unconfigured_deny();
                        return "branch is currently checked out";
                case DENY_UPDATE_INSTEAD:
 -                      ret = update_worktree(new_sha1);
 +                      ret = update_worktree(new_oid->hash);
                        if (ret)
                                return ret;
                        break;
                }
        }
  
 -      if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
 +      if (!is_null_oid(new_oid) && !has_object_file(new_oid)) {
                error("unpack should have generated %s, "
 -                    "but I can't find it!", sha1_to_hex(new_sha1));
 +                    "but I can't find it!", oid_to_hex(new_oid));
                return "bad pack";
        }
  
 -      if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
 +      if (!is_null_oid(old_oid) && is_null_oid(new_oid)) {
                if (deny_deletes && starts_with(name, "refs/heads/")) {
                        rp_error("denying ref deletion for %s", name);
                        return "deletion prohibited";
                }
        }
  
 -      if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
 -          !is_null_sha1(old_sha1) &&
 +      if (deny_non_fast_forwards && !is_null_oid(new_oid) &&
 +          !is_null_oid(old_oid) &&
            starts_with(name, "refs/heads/")) {
                struct object *old_object, *new_object;
                struct commit *old_commit, *new_commit;
  
 -              old_object = parse_object(old_sha1);
 -              new_object = parse_object(new_sha1);
 +              old_object = parse_object(old_oid->hash);
 +              new_object = parse_object(new_oid->hash);
  
                if (!old_object || !new_object ||
                    old_object->type != OBJ_COMMIT ||
                return "hook declined";
        }
  
 -      if (is_null_sha1(new_sha1)) {
 +      if (is_null_oid(new_oid)) {
                struct strbuf err = STRBUF_INIT;
 -              if (!parse_object(old_sha1)) {
 -                      old_sha1 = NULL;
 +              if (!parse_object(old_oid->hash)) {
 +                      old_oid = NULL;
                        if (ref_exists(name)) {
                                rp_warning("Allowing deletion of corrupt ref.");
                        } else {
                }
                if (ref_transaction_delete(transaction,
                                           namespaced_name,
 -                                         old_sha1,
 +                                         old_oid->hash,
                                           0, "push", &err)) {
                        rp_error("%s", err.buf);
                        strbuf_release(&err);
  
                if (ref_transaction_update(transaction,
                                           namespaced_name,
 -                                         new_sha1, old_sha1,
 +                                         new_oid->hash, old_oid->hash,
                                           0, "push",
                                           &err)) {
                        rp_error("%s", err.buf);
  static void run_update_post_hook(struct command *commands)
  {
        struct command *cmd;
 -      int argc;
        struct child_process proc = CHILD_PROCESS_INIT;
        const char *hook;
  
        hook = find_hook("post-update");
 -      for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
 -              if (cmd->error_string || cmd->did_not_exist)
 -                      continue;
 -              argc++;
 -      }
 -      if (!argc || !hook)
 +      if (!hook)
                return;
  
 -      argv_array_push(&proc.args, hook);
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (cmd->error_string || cmd->did_not_exist)
                        continue;
 +              if (!proc.args.argc)
 +                      argv_array_push(&proc.args, hook);
                argv_array_push(&proc.args, cmd->ref_name);
        }
 +      if (!proc.args.argc)
 +              return;
  
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
@@@ -1162,7 -1160,11 +1161,7 @@@ static void check_aliased_update(struc
        const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
 -      unsigned char sha1[GIT_SHA1_RAWSZ];
 -      char cmd_oldh[GIT_SHA1_HEXSZ + 1],
 -           cmd_newh[GIT_SHA1_HEXSZ + 1],
 -           dst_oldh[GIT_SHA1_HEXSZ + 1],
 -           dst_newh[GIT_SHA1_HEXSZ + 1];
 +      unsigned char sha1[GIT_MAX_RAWSZ];
        int flag;
  
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
  
        dst_cmd = (struct command *) item->util;
  
 -      if (!hashcmp(cmd->old_sha1, dst_cmd->old_sha1) &&
 -          !hashcmp(cmd->new_sha1, dst_cmd->new_sha1))
 +      if (!oidcmp(&cmd->old_oid, &dst_cmd->old_oid) &&
 +          !oidcmp(&cmd->new_oid, &dst_cmd->new_oid))
                return;
  
        dst_cmd->skip_update = 1;
  
 -      find_unique_abbrev_r(cmd_oldh, cmd->old_sha1, DEFAULT_ABBREV);
 -      find_unique_abbrev_r(cmd_newh, cmd->new_sha1, DEFAULT_ABBREV);
 -      find_unique_abbrev_r(dst_oldh, dst_cmd->old_sha1, DEFAULT_ABBREV);
 -      find_unique_abbrev_r(dst_newh, dst_cmd->new_sha1, DEFAULT_ABBREV);
        rp_error("refusing inconsistent update between symref '%s' (%s..%s) and"
                 " its target '%s' (%s..%s)",
 -               cmd->ref_name, cmd_oldh, cmd_newh,
 -               dst_cmd->ref_name, dst_oldh, dst_newh);
 +               cmd->ref_name,
 +               find_unique_abbrev(cmd->old_oid.hash, DEFAULT_ABBREV),
 +               find_unique_abbrev(cmd->new_oid.hash, DEFAULT_ABBREV),
 +               dst_cmd->ref_name,
 +               find_unique_abbrev(dst_cmd->old_oid.hash, DEFAULT_ABBREV),
 +               find_unique_abbrev(dst_cmd->new_oid.hash, DEFAULT_ABBREV));
  
        cmd->error_string = dst_cmd->error_string =
                "inconsistent aliased update";
@@@ -1231,10 -1233,10 +1230,10 @@@ static int command_singleton_iterator(v
        struct command **cmd_list = cb_data;
        struct command *cmd = *cmd_list;
  
 -      if (!cmd || is_null_sha1(cmd->new_sha1))
 +      if (!cmd || is_null_oid(&cmd->new_oid))
                return -1; /* end of list */
        *cmd_list = NULL; /* this returns only one */
 -      hashcpy(sha1, cmd->new_sha1);
 +      hashcpy(sha1, cmd->new_oid.hash);
        return 0;
  }
  
@@@ -1275,8 -1277,8 +1274,8 @@@ static int iterate_receive_command_list
                if (shallow_update && data->si->shallow_ref[cmd->index])
                        /* to be checked in update_shallow_ref() */
                        continue;
 -              if (!is_null_sha1(cmd->new_sha1) && !cmd->skip_update) {
 -                      hashcpy(sha1, cmd->new_sha1);
 +              if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) {
 +                      hashcpy(sha1, cmd->new_oid.hash);
                        *cmd_list = cmd->next;
                        return 0;
                }
@@@ -1303,7 -1305,7 +1302,7 @@@ static void reject_updates_to_hidden(st
  
                if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
                        continue;
 -              if (is_null_sha1(cmd->new_sha1))
 +              if (is_null_oid(&cmd->new_oid))
                        cmd->error_string = "deny deleting a hidden ref";
                else
                        cmd->error_string = "deny updating a hidden ref";
@@@ -1414,7 -1416,7 +1413,7 @@@ static void execute_commands(struct com
  {
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        struct command *cmd;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct iterate_data data;
        struct async muxer;
        int err_fd = 0;
        check_aliased_updates(commands);
  
        free(head_name_to_free);
 -      head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
 +      head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL);
  
        if (use_atomic)
                execute_commands_atomic(commands, si);
@@@ -1486,23 -1488,23 +1485,23 @@@ static struct command **queue_command(s
                                      const char *line,
                                      int linelen)
  {
 -      unsigned char old_sha1[20], new_sha1[20];
 +      struct object_id old_oid, new_oid;
        struct command *cmd;
        const char *refname;
        int reflen;
 +      const char *p;
  
 -      if (linelen < 83 ||
 -          line[40] != ' ' ||
 -          line[81] != ' ' ||
 -          get_sha1_hex(line, old_sha1) ||
 -          get_sha1_hex(line + 41, new_sha1))
 +      if (parse_oid_hex(line, &old_oid, &p) ||
 +          *p++ != ' ' ||
 +          parse_oid_hex(p, &new_oid, &p) ||
 +          *p++ != ' ')
                die("protocol error: expected old/new/ref, got '%s'", line);
  
 -      refname = line + 82;
 -      reflen = linelen - 82;
 +      refname = p;
 +      reflen = linelen - (p - line);
        FLEX_ALLOC_MEM(cmd, ref_name, refname, reflen);
 -      hashcpy(cmd->old_sha1, old_sha1);
 -      hashcpy(cmd->new_sha1, new_sha1);
 +      oidcpy(&cmd->old_oid, &old_oid);
 +      oidcpy(&cmd->new_oid, &new_oid);
        *tail = cmd;
        return &cmd->next;
  }
@@@ -1524,12 -1526,12 +1523,12 @@@ static void queue_commands_from_cert(st
  
        while (boc < eoc) {
                const char *eol = memchr(boc, '\n', eoc - boc);
 -              tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol);
 +              tail = queue_command(tail, boc, eol ? eol - boc : eoc - boc);
                boc = eol ? eol + 1 : eoc;
        }
  }
  
 -static struct command *read_head_info(struct sha1_array *shallow)
 +static struct command *read_head_info(struct oid_array *shallow)
  {
        struct command *commands = NULL;
        struct command **p = &commands;
                if (!line)
                        break;
  
 -              if (len == 48 && starts_with(line, "shallow ")) {
 -                      unsigned char sha1[20];
 -                      if (get_sha1_hex(line + 8, sha1))
 +              if (len 8 && starts_with(line, "shallow ")) {
 +                      struct object_id oid;
 +                      if (get_oid_hex(line + 8, &oid))
                                die("protocol error: expected shallow sha, got '%s'",
                                    line + 8);
 -                      sha1_array_append(shallow, sha1);
 +                      oid_array_append(shallow, &oid);
                        continue;
                }
  
@@@ -1634,17 -1636,12 +1633,17 @@@ static const char *parse_pack_header(st
  
  static const char *pack_lockfile;
  
 +static void push_header_arg(struct argv_array *args, struct pack_header *hdr)
 +{
 +      argv_array_pushf(args, "--pack_header=%"PRIu32",%"PRIu32,
 +                      ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries));
 +}
 +
  static const char *unpack(int err_fd, struct shallow_info *si)
  {
        struct pack_header hdr;
        const char *hdr_err;
        int status;
 -      char hdr_arg[38];
        struct child_process child = CHILD_PROCESS_INIT;
        int fsck_objects = (receive_fsck_objects >= 0
                            ? receive_fsck_objects
                        close(err_fd);
                return hdr_err;
        }
 -      snprintf(hdr_arg, sizeof(hdr_arg),
 -                      "--pack_header=%"PRIu32",%"PRIu32,
 -                      ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
  
        if (si->nr_ours || si->nr_theirs) {
                alt_shallow_file = setup_temporary_shallow(si->shallow);
        }
  
        tmp_objdir = tmp_objdir_create();
 -      if (!tmp_objdir)
 +      if (!tmp_objdir) {
 +              if (err_fd > 0)
 +                      close(err_fd);
                return "unable to create temporary object directory";
 +      }
        child.env = tmp_objdir_env(tmp_objdir);
  
        /*
        tmp_objdir_add_as_alternate(tmp_objdir);
  
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
 -              argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
 +              argv_array_push(&child.args, "unpack-objects");
 +              push_header_arg(&child.args, &hdr);
                if (quiet)
                        argv_array_push(&child.args, "-q");
                if (fsck_objects)
        } else {
                char hostname[256];
  
 -              argv_array_pushl(&child.args, "index-pack",
 -                               "--stdin", hdr_arg, NULL);
 +              argv_array_pushl(&child.args, "index-pack", "--stdin", NULL);
 +              push_header_arg(&child.args, &hdr);
  
                if (gethostname(hostname, sizeof(hostname)))
                        xsnprintf(hostname, sizeof(hostname), "localhost");
@@@ -1807,7 -1803,7 +1806,7 @@@ static void prepare_shallow_update(stru
  
  static void update_shallow_info(struct command *commands,
                                struct shallow_info *si,
 -                              struct sha1_array *ref)
 +                              struct oid_array *ref)
  {
        struct command *cmd;
        int *ref_status;
        }
  
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (is_null_sha1(cmd->new_sha1))
 +              if (is_null_oid(&cmd->new_oid))
                        continue;
 -              sha1_array_append(ref, cmd->new_sha1);
 +              oid_array_append(ref, &cmd->new_oid);
                cmd->index = ref->nr - 1;
        }
        si->ref = ref;
        ALLOC_ARRAY(ref_status, ref->nr);
        assign_shallow_commits_to_refs(si, NULL, ref_status);
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (is_null_sha1(cmd->new_sha1))
 +              if (is_null_oid(&cmd->new_oid))
                        continue;
                if (ref_status[cmd->index]) {
                        cmd->error_string = "shallow update not allowed";
@@@ -1871,7 -1867,7 +1870,7 @@@ static int delete_only(struct command *
  {
        struct command *cmd;
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (!is_null_sha1(cmd->new_sha1))
 +              if (!is_null_oid(&cmd->new_oid))
                        return 0;
        }
        return 1;
@@@ -1881,8 -1877,8 +1880,8 @@@ int cmd_receive_pack(int argc, const ch
  {
        int advertise_refs = 0;
        struct command *commands;
 -      struct sha1_array shallow = SHA1_ARRAY_INIT;
 -      struct sha1_array ref = SHA1_ARRAY_INIT;
 +      struct oid_array shallow = OID_ARRAY_INIT;
 +      struct oid_array ref = OID_ARRAY_INIT;
        struct shallow_info si;
  
        struct option options[] = {
                run_receive_hook(commands, "post-receive", 1,
                                 &push_options);
                run_update_post_hook(commands);
 -              if (push_options.nr)
 -                      string_list_clear(&push_options, 0);
 +              string_list_clear(&push_options, 0);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {
                                "gc", "--auto", "--quiet", NULL,
        }
        if (use_sideband)
                packet_flush(1);
 -      sha1_array_clear(&shallow);
 -      sha1_array_clear(&ref);
 +      oid_array_clear(&shallow);
 +      oid_array_clear(&ref);
        free((void *)push_cert_nonce);
        return 0;
  }
diff --combined refs.c
index d1e1b4399b36746dedaabb38de2332481267ea48,916b0d5fa53fda935d1ef220c1652fb07212f4f6..a3d5f42e37345096628ab83fee629f22df1bb7b5
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -3,13 -3,11 +3,13 @@@
   */
  
  #include "cache.h"
 +#include "hashmap.h"
  #include "lockfile.h"
  #include "refs.h"
  #include "refs/refs-internal.h"
  #include "object.h"
  #include "tag.h"
 +#include "submodule.h"
  
  /*
   * List of all available backends
@@@ -171,23 -169,11 +171,23 @@@ int refname_is_safe(const char *refname
        return 1;
  }
  
 +char *refs_resolve_refdup(struct ref_store *refs,
 +                        const char *refname, int resolve_flags,
 +                        unsigned char *sha1, int *flags)
 +{
 +      const char *result;
 +
 +      result = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
 +                                       sha1, flags);
 +      return xstrdup_or_null(result);
 +}
 +
  char *resolve_refdup(const char *refname, int resolve_flags,
                     unsigned char *sha1, int *flags)
  {
 -      return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
 -                                                sha1, flags));
 +      return refs_resolve_refdup(get_main_ref_store(),
 +                                 refname, resolve_flags,
 +                                 sha1, flags);
  }
  
  /* The argument to filter_refs */
@@@ -197,20 -183,13 +197,20 @@@ struct ref_filter 
        void *cb_data;
  };
  
 -int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
 +int refs_read_ref_full(struct ref_store *refs, const char *refname,
 +                     int resolve_flags, unsigned char *sha1, int *flags)
  {
 -      if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))
 +      if (refs_resolve_ref_unsafe(refs, refname, resolve_flags, sha1, flags))
                return 0;
        return -1;
  }
  
 +int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
 +{
 +      return refs_read_ref_full(get_main_ref_store(), refname,
 +                                resolve_flags, sha1, flags);
 +}
 +
  int read_ref(const char *refname, unsigned char *sha1)
  {
        return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
@@@ -305,52 -284,34 +305,52 @@@ void warn_dangling_symrefs(FILE *fp, co
        for_each_rawref(warn_if_dangling_symref, &data);
  }
  
 +int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 +{
 +      return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
 +}
 +
  int for_each_tag_ref(each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in("refs/tags/", fn, cb_data);
 +      return refs_for_each_tag_ref(get_main_ref_store(), fn, cb_data);
  }
  
  int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data);
 +      return refs_for_each_tag_ref(get_submodule_ref_store(submodule),
 +                                   fn, cb_data);
 +}
 +
 +int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 +{
 +      return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
  }
  
  int for_each_branch_ref(each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in("refs/heads/", fn, cb_data);
 +      return refs_for_each_branch_ref(get_main_ref_store(), fn, cb_data);
  }
  
  int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data);
 +      return refs_for_each_branch_ref(get_submodule_ref_store(submodule),
 +                                      fn, cb_data);
 +}
 +
 +int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 +{
 +      return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
  }
  
  int for_each_remote_ref(each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in("refs/remotes/", fn, cb_data);
 +      return refs_for_each_remote_ref(get_main_ref_store(), fn, cb_data);
  }
  
  int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
 +      return refs_for_each_remote_ref(get_submodule_ref_store(submodule),
 +                                      fn, cb_data);
  }
  
  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@@ -404,11 -365,11 +404,11 @@@ int for_each_glob_ref(each_ref_fn fn, c
  
  const char *prettify_refname(const char *name)
  {
 -      return name + (
 -              starts_with(name, "refs/heads/") ? 11 :
 -              starts_with(name, "refs/tags/") ? 10 :
 -              starts_with(name, "refs/remotes/") ? 13 :
 -              0);
 +      if (skip_prefix(name, "refs/heads/", &name) ||
 +          skip_prefix(name, "refs/tags/", &name) ||
 +          skip_prefix(name, "refs/remotes/", &name))
 +              ; /* nothing */
 +      return name;
  }
  
  static const char *ref_rev_parse_rules[] = {
@@@ -443,7 -404,7 +443,7 @@@ int refname_match(const char *abbrev_na
  static char *substitute_branch_name(const char **string, int *len)
  {
        struct strbuf buf = STRBUF_INIT;
 -      int ret = interpret_branch_name(*string, *len, &buf);
 +      int ret = interpret_branch_name(*string, *len, &buf, 0);
  
        if (ret == *len) {
                size_t size;
  int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
  {
        char *last_branch = substitute_branch_name(&str, &len);
 +      int   refs_found  = expand_ref(str, len, sha1, ref);
 +      free(last_branch);
 +      return refs_found;
 +}
 +
 +int expand_ref(const char *str, int len, unsigned char *sha1, char **ref)
 +{
        const char **p, *r;
        int refs_found = 0;
 +      struct strbuf fullref = STRBUF_INIT;
  
        *ref = NULL;
        for (p = ref_rev_parse_rules; *p; p++) {
 -              char fullref[PATH_MAX];
                unsigned char sha1_from_ref[20];
                unsigned char *this_result;
                int flag;
  
                this_result = refs_found ? sha1_from_ref : sha1;
 -              mksnpath(fullref, sizeof(fullref), *p, len, str);
 -              r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,
 +              strbuf_reset(&fullref);
 +              strbuf_addf(&fullref, *p, len, str);
 +              r = resolve_ref_unsafe(fullref.buf, RESOLVE_REF_READING,
                                       this_result, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
                        if (!warn_ambiguous_refs)
                                break;
 -              } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
 -                      warning("ignoring dangling symref %s.", fullref);
 -              } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
 -                      warning("ignoring broken ref %s.", fullref);
 +              } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
 +                      warning("ignoring dangling symref %s.", fullref.buf);
 +              } else if ((flag & REF_ISBROKEN) && strchr(fullref.buf, '/')) {
 +                      warning("ignoring broken ref %s.", fullref.buf);
                }
        }
 -      free(last_branch);
 +      strbuf_release(&fullref);
        return refs_found;
  }
  
@@@ -500,22 -453,21 +500,22 @@@ int dwim_log(const char *str, int len, 
        char *last_branch = substitute_branch_name(&str, &len);
        const char **p;
        int logs_found = 0;
 +      struct strbuf path = STRBUF_INIT;
  
        *log = NULL;
        for (p = ref_rev_parse_rules; *p; p++) {
                unsigned char hash[20];
 -              char path[PATH_MAX];
                const char *ref, *it;
  
 -              mksnpath(path, sizeof(path), *p, len, str);
 -              ref = resolve_ref_unsafe(path, RESOLVE_REF_READING,
 +              strbuf_reset(&path);
 +              strbuf_addf(&path, *p, len, str);
 +              ref = resolve_ref_unsafe(path.buf, RESOLVE_REF_READING,
                                         hash, NULL);
                if (!ref)
                        continue;
 -              if (reflog_exists(path))
 -                      it = path;
 -              else if (strcmp(ref, path) && reflog_exists(ref))
 +              if (reflog_exists(path.buf))
 +                      it = path.buf;
 +              else if (strcmp(ref, path.buf) && reflog_exists(ref))
                        it = ref;
                else
                        continue;
                if (!warn_ambiguous_refs)
                        break;
        }
 +      strbuf_release(&path);
        free(last_branch);
        return logs_found;
  }
@@@ -634,23 -585,19 +634,23 @@@ static int delete_pseudoref(const char 
        return 0;
  }
  
 -int delete_ref(const char *refname, const unsigned char *old_sha1,
 -             unsigned int flags)
 +int refs_delete_ref(struct ref_store *refs, const char *msg,
 +                  const char *refname,
 +                  const unsigned char *old_sha1,
 +                  unsigned int flags)
  {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
  
 -      if (ref_type(refname) == REF_TYPE_PSEUDOREF)
 +      if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 +              assert(refs == get_main_ref_store());
                return delete_pseudoref(refname, old_sha1);
 +      }
  
 -      transaction = ref_transaction_begin(&err);
 +      transaction = ref_store_transaction_begin(refs, &err);
        if (!transaction ||
            ref_transaction_delete(transaction, refname, old_sha1,
 -                                 flags, NULL, &err) ||
 +                                 flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
                ref_transaction_free(transaction);
        return 0;
  }
  
 +int delete_ref(const char *msg, const char *refname,
 +             const unsigned char *old_sha1, unsigned int flags)
 +{
 +      return refs_delete_ref(get_main_ref_store(), msg, refname,
 +                             old_sha1, flags);
 +}
 +
  int copy_reflog_msg(char *buf, const char *msg)
  {
        char *cp = buf;
  
  int should_autocreate_reflog(const char *refname)
  {
 -      if (!log_all_ref_updates)
 +      switch (log_all_ref_updates) {
 +      case LOG_REFS_ALWAYS:
 +              return 1;
 +      case LOG_REFS_NORMAL:
 +              return starts_with(refname, "refs/heads/") ||
 +                      starts_with(refname, "refs/remotes/") ||
 +                      starts_with(refname, "refs/notes/") ||
 +                      !strcmp(refname, "HEAD");
 +      default:
                return 0;
 -      return starts_with(refname, "refs/heads/") ||
 -              starts_with(refname, "refs/remotes/") ||
 -              starts_with(refname, "refs/notes/") ||
 -              !strcmp(refname, "HEAD");
 +      }
  }
  
  int is_branch(const char *refname)
@@@ -728,7 -663,7 +728,7 @@@ struct read_ref_at_cb 
        int *cutoff_cnt;
  };
  
 -static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
 +static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
  {
                 * hold the values for the previous record.
                 */
                if (!is_null_sha1(cb->osha1)) {
 -                      hashcpy(cb->sha1, nsha1);
 -                      if (hashcmp(cb->osha1, nsha1))
 +                      hashcpy(cb->sha1, noid->hash);
 +                      if (hashcmp(cb->osha1, noid->hash))
                                warning("Log for ref %s has gap after %s.",
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
 -                      hashcpy(cb->sha1, nsha1);
 -              else if (hashcmp(nsha1, cb->sha1))
 +                      hashcpy(cb->sha1, noid->hash);
 +              else if (hashcmp(noid->hash, cb->sha1))
                        warning("Log for ref %s unexpectedly ended on %s.",
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
 -              hashcpy(cb->osha1, osha1);
 -              hashcpy(cb->nsha1, nsha1);
 +              hashcpy(cb->osha1, ooid->hash);
 +              hashcpy(cb->nsha1, noid->hash);
                cb->found_it = 1;
                return 1;
        }
 -      hashcpy(cb->osha1, osha1);
 -      hashcpy(cb->nsha1, nsha1);
 +      hashcpy(cb->osha1, ooid->hash);
 +      hashcpy(cb->nsha1, noid->hash);
        if (cb->cnt > 0)
                cb->cnt--;
        return 0;
  }
  
 -static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
 +static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid,
                                  const char *email, unsigned long timestamp,
                                  int tz, const char *message, void *cb_data)
  {
                *cb->cutoff_tz = tz;
        if (cb->cutoff_cnt)
                *cb->cutoff_cnt = cb->reccnt;
 -      hashcpy(cb->sha1, osha1);
 +      hashcpy(cb->sha1, ooid->hash);
        if (is_null_sha1(cb->sha1))
 -              hashcpy(cb->sha1, nsha1);
 +              hashcpy(cb->sha1, noid->hash);
        /* We just want the first entry */
        return 1;
  }
@@@ -828,20 -763,11 +828,20 @@@ int read_ref_at(const char *refname, un
        return 1;
  }
  
 -struct ref_transaction *ref_transaction_begin(struct strbuf *err)
 +struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
 +                                                  struct strbuf *err)
  {
 +      struct ref_transaction *tr;
        assert(err);
  
 -      return xcalloc(1, sizeof(struct ref_transaction));
 +      tr = xcalloc(1, sizeof(struct ref_transaction));
 +      tr->ref_store = refs;
 +      return tr;
 +}
 +
 +struct ref_transaction *ref_transaction_begin(struct strbuf *err)
 +{
 +      return ref_store_transaction_begin(get_main_ref_store(), err);
  }
  
  void ref_transaction_free(struct ref_transaction *transaction)
@@@ -884,7 -810,8 +884,7 @@@ struct ref_update *ref_transaction_add_
                hashcpy(update->new_sha1, new_sha1);
        if (flags & REF_HAVE_OLD)
                hashcpy(update->old_sha1, old_sha1);
 -      if (msg)
 -              update->msg = xstrdup(msg);
 +      update->msg = xstrdup_or_null(msg);
        return update;
  }
  
@@@ -958,20 -885,18 +958,20 @@@ int update_ref_oid(const char *msg, con
                old_oid ? old_oid->hash : NULL, flags, onerr);
  }
  
 -int update_ref(const char *msg, const char *refname,
 -             const unsigned char *new_sha1, const unsigned char *old_sha1,
 -             unsigned int flags, enum action_on_err onerr)
 +int refs_update_ref(struct ref_store *refs, const char *msg,
 +                  const char *refname, const unsigned char *new_sha1,
 +                  const unsigned char *old_sha1, unsigned int flags,
 +                  enum action_on_err onerr)
  {
        struct ref_transaction *t = NULL;
        struct strbuf err = STRBUF_INIT;
        int ret = 0;
  
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 +              assert(refs == get_main_ref_store());
                ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
        } else {
 -              t = ref_transaction_begin(&err);
 +              t = ref_store_transaction_begin(refs, &err);
                if (!t ||
                    ref_transaction_update(t, refname, new_sha1, old_sha1,
                                           flags, msg, &err) ||
        return 0;
  }
  
 +int update_ref(const char *msg, const char *refname,
 +             const unsigned char *new_sha1,
 +             const unsigned char *old_sha1,
 +             unsigned int flags, enum action_on_err onerr)
 +{
 +      return refs_update_ref(get_main_ref_store(), msg, refname, new_sha1,
 +                             old_sha1, flags, onerr);
 +}
 +
  char *shorten_unambiguous_ref(const char *refname, int strict)
  {
        int i;
        static char **scanf_fmts;
        static int nr_rules;
        char *short_name;
 +      struct strbuf resolved_buf = STRBUF_INIT;
  
        if (!nr_rules) {
                /*
                 */
                for (j = 0; j < rules_to_fail; j++) {
                        const char *rule = ref_rev_parse_rules[j];
 -                      char refname[PATH_MAX];
  
                        /* skip matched rule */
                        if (i == j)
                         * (with this previous rule) to a valid ref
                         * read_ref() returns 0 on success
                         */
 -                      mksnpath(refname, sizeof(refname),
 -                               rule, short_name_len, short_name);
 -                      if (ref_exists(refname))
 +                      strbuf_reset(&resolved_buf);
 +                      strbuf_addf(&resolved_buf, rule,
 +                                  short_name_len, short_name);
 +                      if (ref_exists(resolved_buf.buf))
                                break;
                }
  
                 * short name is non-ambiguous if all previous rules
                 * haven't resolved to a valid ref
                 */
 -              if (j == rules_to_fail)
 +              if (j == rules_to_fail) {
 +                      strbuf_release(&resolved_buf);
                        return short_name;
 +              }
        }
  
 +      strbuf_release(&resolved_buf);
        free(short_name);
        return xstrdup(refname);
  }
@@@ -1112,10 -1024,10 +1112,10 @@@ static struct string_list *hide_refs
  
  int parse_hide_refs_config(const char *var, const char *value, const char *section)
  {
 +      const char *key;
        if (!strcmp("transfer.hiderefs", var) ||
 -          /* NEEDSWORK: use parse_config_key() once both are merged */
 -          (starts_with(var, section) && var[strlen(section)] == '.' &&
 -           !strcmp(var + strlen(section), ".hiderefs"))) {
 +          (!parse_config_key(var, section, NULL, NULL, &key) &&
 +           !strcmp(key, "hiderefs"))) {
                char *ref;
                int len;
  
@@@ -1196,17 -1108,14 +1196,17 @@@ const char *find_descendant_ref(const c
        return NULL;
  }
  
 -int rename_ref_available(const char *old_refname, const char *new_refname)
 +int refs_rename_ref_available(struct ref_store *refs,
 +                            const char *old_refname,
 +                            const char *new_refname)
  {
        struct string_list skip = STRING_LIST_INIT_NODUP;
        struct strbuf err = STRBUF_INIT;
        int ok;
  
        string_list_insert(&skip, old_refname);
 -      ok = !verify_refname_available(new_refname, NULL, &skip, &err);
 +      ok = !refs_verify_refname_available(refs, new_refname,
 +                                          NULL, &skip, &err);
        if (!ok)
                error("%s", err.buf);
  
@@@ -1247,9 -1156,10 +1247,9 @@@ int head_ref(each_ref_fn fn, void *cb_d
   * non-zero value, stop the iteration and return that value;
   * otherwise, return 0.
   */
 -static int do_for_each_ref(const char *submodule, const char *prefix,
 +static int do_for_each_ref(struct ref_store *refs, const char *prefix,
                           each_ref_fn fn, int trim, int flags, void *cb_data)
  {
 -      struct ref_store *refs = get_ref_store(submodule);
        struct ref_iterator *iter;
  
        if (!refs)
        return do_for_each_ref_iterator(iter, fn, cb_data);
  }
  
 +int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(refs, "", fn, 0, 0, cb_data);
 +}
 +
  int for_each_ref(each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
 +      return refs_for_each_ref(get_main_ref_store(), fn, cb_data);
  }
  
  int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
 +      return refs_for_each_ref(get_submodule_ref_store(submodule), fn, cb_data);
 +}
 +
 +int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
 +                       each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(refs, prefix, fn, strlen(prefix), 0, cb_data);
  }
  
  int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
 +      return refs_for_each_ref_in(get_main_ref_store(), prefix, fn, cb_data);
  }
  
  int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
  
        if (broken)
                flag = DO_FOR_EACH_INCLUDE_BROKEN;
 -      return do_for_each_ref(NULL, prefix, fn, 0, flag, cb_data);
 +      return do_for_each_ref(get_main_ref_store(),
 +                             prefix, fn, 0, flag, cb_data);
  }
  
  int for_each_ref_in_submodule(const char *submodule, const char *prefix,
 -              each_ref_fn fn, void *cb_data)
 +                            each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
 +      return refs_for_each_ref_in(get_submodule_ref_store(submodule),
 +                                  prefix, fn, cb_data);
  }
  
  int for_each_replace_ref(each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, git_replace_ref_base, fn,
 -                             strlen(git_replace_ref_base), 0, cb_data);
 +      return do_for_each_ref(get_main_ref_store(),
 +                             git_replace_ref_base, fn,
 +                             strlen(git_replace_ref_base),
 +                             0, cb_data);
  }
  
  int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
        struct strbuf buf = STRBUF_INIT;
        int ret;
        strbuf_addf(&buf, "%srefs/", get_git_namespace());
 -      ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
 +      ret = do_for_each_ref(get_main_ref_store(),
 +                            buf.buf, fn, 0, 0, cb_data);
        strbuf_release(&buf);
        return ret;
  }
  
 -int for_each_rawref(each_ref_fn fn, void *cb_data)
 +int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, "", fn, 0,
 +      return do_for_each_ref(refs, "", fn, 0,
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
  }
  
 +int for_each_rawref(each_ref_fn fn, void *cb_data)
 +{
 +      return refs_for_each_rawref(get_main_ref_store(), fn, cb_data);
 +}
 +
  /* This function needs to return a meaningful errno on failure */
 -static const char *resolve_ref_recursively(struct ref_store *refs,
 -                                         const char *refname,
 -                                         int resolve_flags,
 -                                         unsigned char *sha1, int *flags)
 +const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 +                                  const char *refname,
 +                                  int resolve_flags,
 +                                  unsigned char *sha1, int *flags)
  {
        static struct strbuf sb_refname = STRBUF_INIT;
        int unused_flags;
  /* backend functions */
  int refs_init_db(struct strbuf *err)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      struct ref_store *refs = get_main_ref_store();
  
        return refs->be->init_db(refs, err);
  }
  const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               unsigned char *sha1, int *flags)
  {
 -      return resolve_ref_recursively(get_ref_store(NULL), refname,
 +      return refs_resolve_ref_unsafe(get_main_ref_store(), refname,
                                       resolve_flags, sha1, flags);
  }
  
@@@ -1443,220 -1332,161 +1443,226 @@@ int resolve_gitlink_ref(const char *sub
                /* We need to strip off one or more trailing slashes */
                char *stripped = xmemdupz(submodule, len);
  
 -              refs = get_ref_store(stripped);
 +              refs = get_submodule_ref_store(stripped);
                free(stripped);
        } else {
 -              refs = get_ref_store(submodule);
 +              refs = get_submodule_ref_store(submodule);
        }
  
        if (!refs)
                return -1;
  
 -      if (!resolve_ref_recursively(refs, refname, 0, sha1, &flags) ||
 +      if (!refs_resolve_ref_unsafe(refs, refname, 0, sha1, &flags) ||
            is_null_sha1(sha1))
                return -1;
        return 0;
  }
  
 +struct submodule_hash_entry
 +{
 +      struct hashmap_entry ent; /* must be the first member! */
 +
 +      struct ref_store *refs;
 +
 +      /* NUL-terminated name of submodule: */
 +      char submodule[FLEX_ARRAY];
 +};
 +
 +static int submodule_hash_cmp(const void *entry, const void *entry_or_key,
 +                            const void *keydata)
 +{
 +      const struct submodule_hash_entry *e1 = entry, *e2 = entry_or_key;
 +      const char *submodule = keydata ? keydata : e2->submodule;
 +
 +      return strcmp(e1->submodule, submodule);
 +}
 +
 +static struct submodule_hash_entry *alloc_submodule_hash_entry(
 +              const char *submodule, struct ref_store *refs)
 +{
 +      struct submodule_hash_entry *entry;
 +
 +      FLEX_ALLOC_STR(entry, submodule, submodule);
 +      hashmap_entry_init(entry, strhash(submodule));
 +      entry->refs = refs;
 +      return entry;
 +}
 +
  /* A pointer to the ref_store for the main repository: */
  static struct ref_store *main_ref_store;
  
 -/* A linked list of ref_stores for submodules: */
 -static struct ref_store *submodule_ref_stores;
 +/* A hashmap of ref_stores, stored by submodule name: */
 +static struct hashmap submodule_ref_stores;
  
 -void base_ref_store_init(struct ref_store *refs,
 -                       const struct ref_storage_be *be,
 -                       const char *submodule)
 +/*
 + * Return the ref_store instance for the specified submodule. If that
 + * ref_store hasn't been initialized yet, return NULL.
 + */
 +static struct ref_store *lookup_submodule_ref_store(const char *submodule)
  {
 -      refs->be = be;
 -      if (!submodule) {
 -              if (main_ref_store)
 -                      die("BUG: main_ref_store initialized twice");
 +      struct submodule_hash_entry *entry;
  
 -              refs->submodule = "";
 -              refs->next = NULL;
 -              main_ref_store = refs;
 -      } else {
 -              if (lookup_ref_store(submodule))
 -                      die("BUG: ref_store for submodule '%s' initialized twice",
 -                          submodule);
 +      if (!submodule_ref_stores.tablesize)
 +              /* It's initialized on demand in register_ref_store(). */
 +              return NULL;
  
 -              refs->submodule = xstrdup(submodule);
 -              refs->next = submodule_ref_stores;
 -              submodule_ref_stores = refs;
 -      }
 +      entry = hashmap_get_from_hash(&submodule_ref_stores,
 +                                    strhash(submodule), submodule);
 +      return entry ? entry->refs : NULL;
  }
  
 -struct ref_store *ref_store_init(const char *submodule)
 +/*
 + * Create, record, and return a ref_store instance for the specified
 + * gitdir.
 + */
 +static struct ref_store *ref_store_init(const char *gitdir,
 +                                      unsigned int flags)
  {
        const char *be_name = "files";
        struct ref_storage_be *be = find_ref_storage_backend(be_name);
 +      struct ref_store *refs;
  
        if (!be)
                die("BUG: reference backend %s is unknown", be_name);
  
 -      if (!submodule || !*submodule)
 -              return be->init(NULL);
 -      else
 -              return be->init(submodule);
 +      refs = be->init(gitdir, flags);
 +      return refs;
  }
  
 -struct ref_store *lookup_ref_store(const char *submodule)
 +struct ref_store *get_main_ref_store(void)
  {
 -      struct ref_store *refs;
 -
 -      if (!submodule || !*submodule)
 +      if (main_ref_store)
                return main_ref_store;
  
 -      for (refs = submodule_ref_stores; refs; refs = refs->next) {
 -              if (!strcmp(submodule, refs->submodule))
 -                      return refs;
 -      }
 +      main_ref_store = ref_store_init(get_git_dir(),
 +                                      (REF_STORE_READ |
 +                                       REF_STORE_WRITE |
 +                                       REF_STORE_ODB |
 +                                       REF_STORE_MAIN));
 +      return main_ref_store;
 +}
  
 -      return NULL;
 +/*
 + * Register the specified ref_store to be the one that should be used
 + * for submodule. It is a fatal error to call this function twice for
 + * the same submodule.
 + */
 +static void register_submodule_ref_store(struct ref_store *refs,
 +                                       const char *submodule)
 +{
 +      if (!submodule_ref_stores.tablesize)
 +              hashmap_init(&submodule_ref_stores, submodule_hash_cmp, 0);
 +
 +      if (hashmap_put(&submodule_ref_stores,
 +                      alloc_submodule_hash_entry(submodule, refs)))
 +              die("BUG: ref_store for submodule '%s' initialized twice",
 +                  submodule);
  }
  
 -struct ref_store *get_ref_store(const char *submodule)
 +struct ref_store *get_submodule_ref_store(const char *submodule)
  {
 +      struct strbuf submodule_sb = STRBUF_INIT;
        struct ref_store *refs;
 +      int ret;
  
        if (!submodule || !*submodule) {
 -              refs = lookup_ref_store(NULL);
 +              /*
 +               * FIXME: This case is ideally not allowed. But that
 +               * can't happen until we clean up all the callers.
 +               */
 +              return get_main_ref_store();
 +      }
  
 -              if (!refs)
 -                      refs = ref_store_init(NULL);
 -      } else {
 -              refs = lookup_ref_store(submodule);
 +      refs = lookup_submodule_ref_store(submodule);
 +      if (refs)
 +              return refs;
  
 -              if (!refs) {
 -                      struct strbuf submodule_sb = STRBUF_INIT;
 +      strbuf_addstr(&submodule_sb, submodule);
 +      ret = is_nonbare_repository_dir(&submodule_sb);
 +      strbuf_release(&submodule_sb);
 +      if (!ret)
 +              return NULL;
  
 -                      strbuf_addstr(&submodule_sb, submodule);
 -                      if (is_nonbare_repository_dir(&submodule_sb))
 -                              refs = ref_store_init(submodule);
 -                      strbuf_release(&submodule_sb);
 -              }
 +      ret = submodule_to_gitdir(&submodule_sb, submodule);
 +      if (ret) {
 +              strbuf_release(&submodule_sb);
 +              return NULL;
        }
  
 +      /* assume that add_submodule_odb() has been called */
 +      refs = ref_store_init(submodule_sb.buf,
 +                            REF_STORE_READ | REF_STORE_ODB);
 +      register_submodule_ref_store(refs, submodule);
 +
 +      strbuf_release(&submodule_sb);
        return refs;
  }
  
 -void assert_main_repository(struct ref_store *refs, const char *caller)
 +void base_ref_store_init(struct ref_store *refs,
 +                       const struct ref_storage_be *be)
  {
 -      if (*refs->submodule)
 -              die("BUG: %s called for a submodule", caller);
 +      refs->be = be;
  }
  
  /* backend functions */
 -int pack_refs(unsigned int flags)
 +int refs_pack_refs(struct ref_store *refs, unsigned int flags)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 -
        return refs->be->pack_refs(refs, flags);
  }
  
 +int refs_peel_ref(struct ref_store *refs, const char *refname,
 +                unsigned char *sha1)
 +{
 +      return refs->be->peel_ref(refs, refname, sha1);
 +}
 +
  int peel_ref(const char *refname, unsigned char *sha1)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_peel_ref(get_main_ref_store(), refname, sha1);
 +}
  
 -      return refs->be->peel_ref(refs, refname, sha1);
 +int refs_create_symref(struct ref_store *refs,
 +                     const char *ref_target,
 +                     const char *refs_heads_master,
 +                     const char *logmsg)
 +{
 +      return refs->be->create_symref(refs, ref_target,
 +                                     refs_heads_master,
 +                                     logmsg);
  }
  
  int create_symref(const char *ref_target, const char *refs_heads_master,
                  const char *logmsg)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 -
 -      return refs->be->create_symref(refs, ref_target, refs_heads_master,
 -                                     logmsg);
 +      return refs_create_symref(get_main_ref_store(), ref_target,
 +                                refs_heads_master, logmsg);
  }
  
  int ref_transaction_commit(struct ref_transaction *transaction,
                           struct strbuf *err)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      struct ref_store *refs = transaction->ref_store;
  
+       if (getenv(GIT_QUARANTINE_ENVIRONMENT)) {
+               strbuf_addstr(err,
+                             _("ref updates forbidden inside quarantine environment"));
+               return -1;
+       }
        return refs->be->transaction_commit(refs, transaction, err);
  }
  
 -int verify_refname_available(const char *refname,
 -                           const struct string_list *extra,
 -                           const struct string_list *skip,
 -                           struct strbuf *err)
 +int refs_verify_refname_available(struct ref_store *refs,
 +                                const char *refname,
 +                                const struct string_list *extra,
 +                                const struct string_list *skip,
 +                                struct strbuf *err)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 -
        return refs->be->verify_refname_available(refs, refname, extra, skip, err);
  }
  
 -int for_each_reflog(each_ref_fn fn, void *cb_data)
 +int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
        struct ref_iterator *iter;
  
        iter = refs->be->reflog_iterator_begin(refs);
        return do_for_each_ref_iterator(iter, fn, cb_data);
  }
  
 -int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
 -                              void *cb_data)
 +int for_each_reflog(each_ref_fn fn, void *cb_data)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_for_each_reflog(get_main_ref_store(), fn, cb_data);
 +}
  
 +int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
 +                                   const char *refname,
 +                                   each_reflog_ent_fn fn,
 +                                   void *cb_data)
 +{
        return refs->be->for_each_reflog_ent_reverse(refs, refname,
                                                     fn, cb_data);
  }
  
 +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
 +                              void *cb_data)
 +{
 +      return refs_for_each_reflog_ent_reverse(get_main_ref_store(),
 +                                              refname, fn, cb_data);
 +}
 +
 +int refs_for_each_reflog_ent(struct ref_store *refs, const char *refname,
 +                           each_reflog_ent_fn fn, void *cb_data)
 +{
 +      return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data);
 +}
 +
  int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
                        void *cb_data)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_for_each_reflog_ent(get_main_ref_store(), refname,
 +                                      fn, cb_data);
 +}
  
 -      return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data);
 +int refs_reflog_exists(struct ref_store *refs, const char *refname)
 +{
 +      return refs->be->reflog_exists(refs, refname);
  }
  
  int reflog_exists(const char *refname)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_reflog_exists(get_main_ref_store(), refname);
 +}
  
 -      return refs->be->reflog_exists(refs, refname);
 +int refs_create_reflog(struct ref_store *refs, const char *refname,
 +                     int force_create, struct strbuf *err)
 +{
 +      return refs->be->create_reflog(refs, refname, force_create, err);
  }
  
  int safe_create_reflog(const char *refname, int force_create,
                       struct strbuf *err)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_create_reflog(get_main_ref_store(), refname,
 +                                force_create, err);
 +}
  
 -      return refs->be->create_reflog(refs, refname, force_create, err);
 +int refs_delete_reflog(struct ref_store *refs, const char *refname)
 +{
 +      return refs->be->delete_reflog(refs, refname);
  }
  
  int delete_reflog(const char *refname)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_delete_reflog(get_main_ref_store(), refname);
 +}
  
 -      return refs->be->delete_reflog(refs, refname);
 +int refs_reflog_expire(struct ref_store *refs,
 +                     const char *refname, const unsigned char *sha1,
 +                     unsigned int flags,
 +                     reflog_expiry_prepare_fn prepare_fn,
 +                     reflog_expiry_should_prune_fn should_prune_fn,
 +                     reflog_expiry_cleanup_fn cleanup_fn,
 +                     void *policy_cb_data)
 +{
 +      return refs->be->reflog_expire(refs, refname, sha1, flags,
 +                                     prepare_fn, should_prune_fn,
 +                                     cleanup_fn, policy_cb_data);
  }
  
  int reflog_expire(const char *refname, const unsigned char *sha1,
                  reflog_expiry_cleanup_fn cleanup_fn,
                  void *policy_cb_data)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 -
 -      return refs->be->reflog_expire(refs, refname, sha1, flags,
 -                                     prepare_fn, should_prune_fn,
 -                                     cleanup_fn, policy_cb_data);
 +      return refs_reflog_expire(get_main_ref_store(),
 +                                refname, sha1, flags,
 +                                prepare_fn, should_prune_fn,
 +                                cleanup_fn, policy_cb_data);
  }
  
  int initial_ref_transaction_commit(struct ref_transaction *transaction,
                                   struct strbuf *err)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      struct ref_store *refs = transaction->ref_store;
  
        return refs->be->initial_transaction_commit(refs, transaction, err);
  }
  
 -int delete_refs(struct string_list *refnames, unsigned int flags)
 +int refs_delete_refs(struct ref_store *refs, struct string_list *refnames,
 +                   unsigned int flags)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 -
        return refs->be->delete_refs(refs, refnames, flags);
  }
  
 -int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 +int delete_refs(struct string_list *refnames, unsigned int flags)
  {
 -      struct ref_store *refs = get_ref_store(NULL);
 +      return refs_delete_refs(get_main_ref_store(), refnames, flags);
 +}
  
 +int refs_rename_ref(struct ref_store *refs, const char *oldref,
 +                  const char *newref, const char *logmsg)
 +{
        return refs->be->rename_ref(refs, oldref, newref, logmsg);
  }
 +
 +int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 +{
 +      return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
 +}
index af9fcd833a5e9e4997dd5cb917ac13f4b07f10ab,462bfc9cba81e0db9a8a97b94d32dcb96de6eec7..113c87007f31abced0d93b3702f0b54f50ff4679
@@@ -33,29 -33,15 +33,40 @@@ test_expect_success 'rejected objects a
        test_cmp expect actual
  '
  
 +test_expect_success 'push to repo path with path separator (colon)' '
 +      # The interesting failure case here is when the
 +      # receiving end cannot access its original object directory,
 +      # so make it likely for us to generate a delta by having
 +      # a non-trivial file with multiple versions.
 +
 +      test-genrandom foo 4096 >file.bin &&
 +      git add file.bin &&
 +      git commit -m bin &&
 +
 +      if test_have_prereq MINGW
 +      then
 +              pathsep=";"
 +      else
 +              pathsep=":"
 +      fi &&
 +      git clone --bare . "xxx${pathsep}yyy.git" &&
 +
 +      echo change >>file.bin &&
 +      git commit -am change &&
 +      # Note that we have to use the full path here, or it gets confused
 +      # with the ssh host:path syntax.
 +      git push "$(pwd)/xxx${pathsep}yyy.git" HEAD
 +'
 +
+ test_expect_success 'updating a ref from quarantine is forbidden' '
+       git init --bare update.git &&
+       write_script update.git/hooks/pre-receive <<-\EOF &&
+       read old new refname
+       git update-ref refs/heads/unrelated $new
+       exit 1
+       EOF
+       test_must_fail git push update.git HEAD &&
+       git -C update.git fsck
+ '
  test_done