Merge branch 'bc/push-cert-receive-fix'
authorJunio C Hamano <gitster@pobox.com>
Thu, 30 Mar 2017 21:07:18 +0000 (14:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 30 Mar 2017 21:07:18 +0000 (14:07 -0700)
"git receive-pack" could have been forced to die by attempting
allocate an unreasonably large amount of memory with a crafted push
certificate; this has been fixed.

* bc/push-cert-receive-fix:
  builtin/receive-pack: fix incorrect pointer arithmetic

1  2 
builtin/receive-pack.c

diff --combined builtin/receive-pack.c
@@@ -1,5 -1,4 +1,5 @@@
  #include "builtin.h"
 +#include "lockfile.h"
  #include "pack.h"
  #include "refs.h"
  #include "pkt-line.h"
  #include "version.h"
  #include "tag.h"
  #include "gpg-interface.h"
 -
 -static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 +#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>"),
 +      NULL
 +};
  
  enum deny_action {
        DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
 -      DENY_REFUSE
 +      DENY_REFUSE,
 +      DENY_UPDATE_INSTEAD
  };
  
  static int deny_deletes;
@@@ -42,22 -33,16 +42,22 @@@ static enum deny_action deny_current_br
  static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
  static int receive_fsck_objects = -1;
  static int transfer_fsck_objects = -1;
 +static struct strbuf fsck_msg_types = STRBUF_INIT;
  static int receive_unpack_limit = -1;
  static int transfer_unpack_limit = -1;
 +static int advertise_atomic_push = 1;
 +static int advertise_push_options;
  static int unpack_limit = 100;
 +static off_t max_input_size;
  static int report_status;
  static int use_sideband;
 +static int use_atomic;
 +static int use_push_options;
  static int quiet;
  static int prefer_ofs_delta = 1;
  static int auto_update_server_info;
  static int auto_gc = 1;
 -static int fix_thin = 1;
 +static int reject_thin;
  static int stateless_rpc;
  static const char *service_dir;
  static const char *head_name;
@@@ -79,16 -64,6 +79,16 @@@ static const char *NONCE_SLOP = "SLOP"
  static const char *nonce_status;
  static long nonce_stamp_slop;
  static unsigned long nonce_stamp_slop_limit;
 +static struct ref_transaction *transaction;
 +
 +static enum {
 +      KEEPALIVE_NEVER = 0,
 +      KEEPALIVE_AFTER_NUL,
 +      KEEPALIVE_ALWAYS
 +} use_keepalive;
 +static int keepalive_in_sec = 5;
 +
 +static struct tmp_objdir *tmp_objdir;
  
  static enum deny_action parse_deny_action(const char *var, const char *value)
  {
@@@ -99,8 -74,6 +99,8 @@@
                        return DENY_WARN;
                if (!strcasecmp(value, "refuse"))
                        return DENY_REFUSE;
 +              if (!strcasecmp(value, "updateinstead"))
 +                      return DENY_UPDATE_INSTEAD;
        }
        if (git_config_bool(var, value))
                return DENY_REFUSE;
@@@ -134,26 -107,6 +134,26 @@@ static int receive_pack_config(const ch
                return 0;
        }
  
 +      if (strcmp(var, "receive.fsck.skiplist") == 0) {
 +              const char *path;
 +
 +              if (git_config_pathname(&path, var, value))
 +                      return 1;
 +              strbuf_addf(&fsck_msg_types, "%cskiplist=%s",
 +                      fsck_msg_types.len ? ',' : '=', path);
 +              free((char *)path);
 +              return 0;
 +      }
 +
 +      if (skip_prefix(var, "receive.fsck.", &var)) {
 +              if (is_valid_msg_type(var, value))
 +                      strbuf_addf(&fsck_msg_types, "%c%s=%s",
 +                              fsck_msg_types.len ? ',' : '=', var, value);
 +              else
 +                      warning("Skipping unknown msg id '%s'", var);
 +              return 0;
 +      }
 +
        if (strcmp(var, "receive.fsckobjects") == 0) {
                receive_fsck_objects = git_config_bool(var, value);
                return 0;
                return 0;
        }
  
 +      if (strcmp(var, "receive.advertiseatomic") == 0) {
 +              advertise_atomic_push = git_config_bool(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "receive.advertisepushoptions") == 0) {
 +              advertise_push_options = git_config_bool(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "receive.keepalive") == 0) {
 +              keepalive_in_sec = git_config_int(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "receive.maxinputsize") == 0) {
 +              max_input_size = git_config_int64(var, value);
 +              return 0;
 +      }
 +
        return git_default_config(var, value, cb);
  }
  
  static void show_ref(const char *path, const unsigned char *sha1)
  {
 -      if (ref_is_hidden(path))
 -              return;
 -
        if (sent_capabilities) {
 -              packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
 +              packet_write_fmt(1, "%s %s\n", sha1_to_hex(sha1), path);
        } else {
                struct strbuf cap = STRBUF_INIT;
  
                strbuf_addstr(&cap,
                              "report-status delete-refs side-band-64k quiet");
 +              if (advertise_atomic_push)
 +                      strbuf_addstr(&cap, " atomic");
                if (prefer_ofs_delta)
                        strbuf_addstr(&cap, " ofs-delta");
                if (push_cert_nonce)
                        strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
 +              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",
 +              packet_write_fmt(1, "%s %s%c%s\n",
                             sha1_to_hex(sha1), path, 0, cap.buf);
                strbuf_release(&cap);
                sent_capabilities = 1;
        }
  }
  
 -static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
 +static int show_ref_cb(const char *path_full, const struct object_id *oid,
 +                     int flag, void *data)
  {
 -      path = strip_namespace(path);
 +      struct oidset *seen = data;
 +      const char *path = strip_namespace(path_full);
 +
 +      if (ref_is_hidden(path, path_full))
 +              return 0;
 +
        /*
         * 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, sha1);
 +      } else {
 +              oidset_insert(seen, oid);
 +      }
 +      show_ref(path, oid->hash);
        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_sha1);
 +      if (oidset_insert(seen, oid))
 +              return;
 +
 +      show_ref(".have", oid->hash);
  }
  
  static void write_head_info(void)
  {
 -      struct sha1_array sa = SHA1_ARRAY_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);
 +      static struct oidset seen = OIDSET_INIT;
 +
 +      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);
  
@@@ -319,10 -242,10 +319,10 @@@ static void rp_warning(const char *err
  
  static void report_message(const char *prefix, const char *err, va_list params)
  {
 -      int sz = strlen(prefix);
 +      int sz;
        char msg[4096];
  
 -      strncpy(msg, prefix, sz);
 +      sz = xsnprintf(msg, sizeof(msg), "%s", prefix);
        sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
        if (sz > (sizeof(msg) - 1))
                sz = sizeof(msg) - 1;
@@@ -353,60 -276,10 +353,60 @@@ static void rp_error(const char *err, .
  static int copy_to_sideband(int in, int out, void *arg)
  {
        char data[128];
 +      int keepalive_active = 0;
 +
 +      if (keepalive_in_sec <= 0)
 +              use_keepalive = KEEPALIVE_NEVER;
 +      if (use_keepalive == KEEPALIVE_ALWAYS)
 +              keepalive_active = 1;
 +
        while (1) {
 -              ssize_t sz = xread(in, data, sizeof(data));
 +              ssize_t sz;
 +
 +              if (keepalive_active) {
 +                      struct pollfd pfd;
 +                      int ret;
 +
 +                      pfd.fd = in;
 +                      pfd.events = POLLIN;
 +                      ret = poll(&pfd, 1, 1000 * keepalive_in_sec);
 +
 +                      if (ret < 0) {
 +                              if (errno == EINTR)
 +                                      continue;
 +                              else
 +                                      break;
 +                      } else if (ret == 0) {
 +                              /* no data; send a keepalive packet */
 +                              static const char buf[] = "0005\1";
 +                              write_or_die(1, buf, sizeof(buf) - 1);
 +                              continue;
 +                      } /* else there is actual data to read */
 +              }
 +
 +              sz = xread(in, data, sizeof(data));
                if (sz <= 0)
                        break;
 +
 +              if (use_keepalive == KEEPALIVE_AFTER_NUL && !keepalive_active) {
 +                      const char *p = memchr(data, '\0', sz);
 +                      if (p) {
 +                              /*
 +                               * The NUL tells us to start sending keepalives. Make
 +                               * sure we send any other data we read along
 +                               * with it.
 +                               */
 +                              keepalive_active = 1;
 +                              send_sideband(1, 2, data, p - data, use_sideband);
 +                              send_sideband(1, 2, p + 1, sz - (p - data + 1), use_sideband);
 +                              continue;
 +                      }
 +              }
 +
 +              /*
 +               * Either we're not looking for a NUL signal, or we didn't see
 +               * it yet; just pass along the data.
 +               */
                send_sideband(1, 2, data, sz, use_sideband);
        }
        close(in);
@@@ -556,7 -429,7 +556,7 @@@ static const char *check_nonce(const ch
        nonce_stamp_slop = (long)ostamp - (long)stamp;
  
        if (nonce_stamp_slop_limit &&
 -          abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
 +          labs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
                /*
                 * Pretend as if the received nonce (which passes the
                 * HMAC check, so it is not a forged by third-party)
@@@ -578,6 -451,7 +578,6 @@@ leave
  static void prepare_push_cert_sha1(struct child_process *proc)
  {
        static int already_done;
 -      struct argv_array env = ARGV_ARRAY_INIT;
  
        if (!push_cert.len)
                return;
                nonce_status = check_nonce(push_cert.buf, bogs);
        }
        if (!is_null_sha1(push_cert_sha1)) {
 -              argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1));
 -              argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s",
 +              argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
 +                               sha1_to_hex(push_cert_sha1));
 +              argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
                                 sigcheck.signer ? sigcheck.signer : "");
 -              argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s",
 +              argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
                                 sigcheck.key ? sigcheck.key : "");
 -              argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result);
 +              argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c",
 +                               sigcheck.result);
                if (push_cert_nonce) {
 -                      argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce);
 -                      argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status);
 +                      argv_array_pushf(&proc->env_array,
 +                                       "GIT_PUSH_CERT_NONCE=%s",
 +                                       push_cert_nonce);
 +                      argv_array_pushf(&proc->env_array,
 +                                       "GIT_PUSH_CERT_NONCE_STATUS=%s",
 +                                       nonce_status);
                        if (nonce_status == NONCE_SLOP)
 -                              argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld",
 +                              argv_array_pushf(&proc->env_array,
 +                                               "GIT_PUSH_CERT_NONCE_SLOP=%ld",
                                                 nonce_stamp_slop);
                }
 -              proc->env = env.argv;
        }
  }
  
 +struct receive_hook_feed_state {
 +      struct command *cmd;
 +      int skip_broken;
 +      struct strbuf buf;
 +      const struct string_list *push_options;
 +};
 +
  typedef int (*feed_fn)(void *, const char **, size_t *);
 -static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
 +static int run_and_feed_hook(const char *hook_name, feed_fn feed,
 +                           struct receive_hook_feed_state *feed_state)
  {
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        struct async muxer;
        const char *argv[2];
        int code;
  
        argv[1] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
 -
 -      prepare_push_cert_sha1(&proc);
 +      if (feed_state->push_options) {
 +              int i;
 +              for (i = 0; i < feed_state->push_options->nr; i++)
 +                      argv_array_pushf(&proc.env_array,
 +                              "GIT_PUSH_OPTION_%d=%s", i,
 +                              feed_state->push_options->items[i].string);
 +              argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
 +                               feed_state->push_options->nr);
 +      } else
 +              argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
 +
 +      if (tmp_objdir)
 +              argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
  
        if (use_sideband) {
                memset(&muxer, 0, sizeof(muxer));
                proc.err = muxer.in;
        }
  
 +      prepare_push_cert_sha1(&proc);
 +
        code = start_command(&proc);
        if (code) {
                if (use_sideband)
                return code;
        }
  
 +      sigchain_push(SIGPIPE, SIG_IGN);
 +
        while (1) {
                const char *buf;
                size_t n;
        close(proc.in);
        if (use_sideband)
                finish_async(&muxer);
 +
 +      sigchain_pop(SIGPIPE);
 +
        return finish_command(&proc);
  }
  
 -struct receive_hook_feed_state {
 -      struct command *cmd;
 -      int skip_broken;
 -      struct strbuf buf;
 -};
 -
  static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
  {
        struct receive_hook_feed_state *state = state_;
        return 0;
  }
  
 -static int run_receive_hook(struct command *commands, const char *hook_name,
 -                          int skip_broken)
 +static int run_receive_hook(struct command *commands,
 +                          const char *hook_name,
 +                          int skip_broken,
 +                          const struct string_list *push_options)
  {
        struct receive_hook_feed_state state;
        int status;
        if (feed_receive_hook(&state, NULL, NULL))
                return 0;
        state.cmd = commands;
 +      state.push_options = push_options;
        status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
        strbuf_release(&state.buf);
        return status;
  static int run_update_hook(struct command *cmd)
  {
        const char *argv[5];
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        int code;
  
        argv[0] = find_hook("update");
        argv[3] = sha1_to_hex(cmd->new_sha1);
        argv[4] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        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)
@@@ -792,39 -638,47 +792,39 @@@ static int is_ref_checked_out(const cha
        return !strcmp(head_name, ref);
  }
  
 -static char *refuse_unconfigured_deny_msg[] = {
 -      "By default, updating the current branch in a non-bare repository",
 -      "is denied, because it will make the index and work tree inconsistent",
 -      "with what you pushed, and will require 'git reset --hard' to match",
 -      "the work tree to HEAD.",
 -      "",
 -      "You can set 'receive.denyCurrentBranch' configuration variable to",
 -      "'ignore' or 'warn' in the remote repository to allow pushing into",
 -      "its current branch; however, this is not recommended unless you",
 -      "arranged to update its work tree to match what you pushed in some",
 -      "other way.",
 -      "",
 -      "To squelch this message and still keep the default behaviour, set",
 -      "'receive.denyCurrentBranch' configuration variable to 'refuse'."
 -};
 +static char *refuse_unconfigured_deny_msg =
 +      N_("By default, updating the current branch in a non-bare repository\n"
 +         "is denied, because it will make the index and work tree inconsistent\n"
 +         "with what you pushed, and will require 'git reset --hard' to match\n"
 +         "the work tree to HEAD.\n"
 +         "\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"
 +         "\n"
 +         "To squelch this message and still keep the default behaviour, set\n"
 +         "'receive.denyCurrentBranch' configuration variable to 'refuse'.");
  
  static void refuse_unconfigured_deny(void)
  {
 -      int i;
 -      for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
 -              rp_error("%s", refuse_unconfigured_deny_msg[i]);
 +      rp_error("%s", _(refuse_unconfigured_deny_msg));
  }
  
 -static char *refuse_unconfigured_deny_delete_current_msg[] = {
 -      "By default, deleting the current branch is denied, because the next",
 -      "'git clone' won't result in any file checked out, causing confusion.",
 -      "",
 -      "You can set 'receive.denyDeleteCurrent' configuration variable to",
 -      "'warn' or 'ignore' in the remote repository to allow deleting the",
 -      "current branch, with or without a warning message.",
 -      "",
 -      "To squelch this message, you can set it to 'refuse'."
 -};
 +static char *refuse_unconfigured_deny_delete_current_msg =
 +      N_("By default, deleting the current branch is denied, because the next\n"
 +         "'git clone' won't result in any file checked out, causing confusion.\n"
 +         "\n"
 +         "You can set 'receive.denyDeleteCurrent' configuration variable to\n"
 +         "'warn' or 'ignore' in the remote repository to allow deleting the\n"
 +         "current branch, with or without a warning message.\n"
 +         "\n"
 +         "To squelch this message, you can set it to 'refuse'.");
  
  static void refuse_unconfigured_deny_delete_current(void)
  {
 -      int i;
 -      for (i = 0;
 -           i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
 -           i++)
 -              rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
 +      rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg));
  }
  
  static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]);
@@@ -832,7 -686,7 +832,7 @@@ static int update_shallow_ref(struct co
  {
        static struct lock_file shallow_lock;
        struct sha1_array extra = SHA1_ARRAY_INIT;
 -      const char *alt_file;
 +      struct check_connected_options opt = CHECK_CONNECTED_INIT;
        uint32_t mask = 1 << (cmd->index % 32);
        int i;
  
                    !delayed_reachability_test(si, i))
                        sha1_array_append(&extra, si->shallow->sha1[i]);
  
 -      setup_alternate_shallow(&shallow_lock, &alt_file, &extra);
 -      if (check_shallow_connected(command_singleton_iterator,
 -                                  0, cmd, alt_file)) {
 +      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);
                return -1;
        return 0;
  }
  
 +/*
 + * NEEDSWORK: we should consolidate various implementions of "are we
 + * on an unborn branch?" test into one, and make the unified one more
 + * robust. !get_sha1() based check used here and elsewhere would not
 + * allow us to tell an unborn branch from corrupt ref, for example.
 + * For the purpose of fixing "deploy-to-update does not work when
 + * pushing into an empty repository" issue, this should suffice for
 + * now.
 + */
 +static int head_has_history(void)
 +{
 +      unsigned char sha1[20];
 +
 +      return !get_sha1("HEAD", sha1);
 +}
 +
 +static const char *push_to_deploy(unsigned char *sha1,
 +                                struct argv_array *env,
 +                                const char *work_tree)
 +{
 +      const char *update_refresh[] = {
 +              "update-index", "-q", "--ignore-submodules", "--refresh", NULL
 +      };
 +      const char *diff_files[] = {
 +              "diff-files", "--quiet", "--ignore-submodules", "--", NULL
 +      };
 +      const char *diff_index[] = {
 +              "diff-index", "--quiet", "--cached", "--ignore-submodules",
 +              NULL, "--", NULL
 +      };
 +      const char *read_tree[] = {
 +              "read-tree", "-u", "-m", NULL, NULL
 +      };
 +      struct child_process child = CHILD_PROCESS_INIT;
 +
 +      child.argv = update_refresh;
 +      child.env = env->argv;
 +      child.dir = work_tree;
 +      child.no_stdin = 1;
 +      child.stdout_to_stderr = 1;
 +      child.git_cmd = 1;
 +      if (run_command(&child))
 +              return "Up-to-date check failed";
 +
 +      /* run_command() does not clean up completely; reinitialize */
 +      child_process_init(&child);
 +      child.argv = diff_files;
 +      child.env = env->argv;
 +      child.dir = work_tree;
 +      child.no_stdin = 1;
 +      child.stdout_to_stderr = 1;
 +      child.git_cmd = 1;
 +      if (run_command(&child))
 +              return "Working directory has unstaged changes";
 +
 +      /* diff-index with either HEAD or an empty tree */
 +      diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX;
 +
 +      child_process_init(&child);
 +      child.argv = diff_index;
 +      child.env = env->argv;
 +      child.no_stdin = 1;
 +      child.no_stdout = 1;
 +      child.stdout_to_stderr = 0;
 +      child.git_cmd = 1;
 +      if (run_command(&child))
 +              return "Working directory has staged changes";
 +
 +      read_tree[3] = sha1_to_hex(sha1);
 +      child_process_init(&child);
 +      child.argv = read_tree;
 +      child.env = env->argv;
 +      child.dir = work_tree;
 +      child.no_stdin = 1;
 +      child.no_stdout = 1;
 +      child.stdout_to_stderr = 0;
 +      child.git_cmd = 1;
 +      if (run_command(&child))
 +              return "Could not update working tree to new HEAD";
 +
 +      return NULL;
 +}
 +
 +static const char *push_to_checkout_hook = "push-to-checkout";
 +
 +static const char *push_to_checkout(unsigned char *sha1,
 +                                  struct argv_array *env,
 +                                  const char *work_tree)
 +{
 +      argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
 +      if (run_hook_le(env->argv, push_to_checkout_hook,
 +                      sha1_to_hex(sha1), NULL))
 +              return "push-to-checkout hook declined";
 +      else
 +              return NULL;
 +}
 +
 +static const char *update_worktree(unsigned char *sha1)
 +{
 +      const char *retval;
 +      const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
 +      struct argv_array env = ARGV_ARRAY_INIT;
 +
 +      if (is_bare_repository())
 +              return "denyCurrentBranch = updateInstead needs a worktree";
 +
 +      argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
 +
 +      if (!find_hook(push_to_checkout_hook))
 +              retval = push_to_deploy(sha1, &env, work_tree);
 +      else
 +              retval = push_to_checkout(sha1, &env, work_tree);
 +
 +      argv_array_clear(&env);
 +      return retval;
 +}
 +
  static const char *update(struct command *cmd, struct shallow_info *si)
  {
        const char *name = cmd->ref_name;
        struct strbuf namespaced_name_buf = STRBUF_INIT;
 -      const char *namespaced_name;
 +      const char *namespaced_name, *ret;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
 -      struct ref_lock *lock;
  
        /* only refs/... are allowed */
        if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
                        if (deny_current_branch == DENY_UNCONFIGURED)
                                refuse_unconfigured_deny();
                        return "branch is currently checked out";
 +              case DENY_UPDATE_INSTEAD:
 +                      ret = update_worktree(new_sha1);
 +                      if (ret)
 +                              return ret;
 +                      break;
                }
        }
  
                        return "deletion prohibited";
                }
  
 -              if (!strcmp(namespaced_name, head_name)) {
 +              if (head_name && !strcmp(namespaced_name, head_name)) {
                        switch (deny_delete_current) {
                        case DENY_IGNORE:
                                break;
                                break;
                        case DENY_REFUSE:
                        case DENY_UNCONFIGURED:
 +                      case DENY_UPDATE_INSTEAD:
                                if (deny_delete_current == DENY_UNCONFIGURED)
                                        refuse_unconfigured_deny_delete_current();
                                rp_error("refusing to delete the current branch: %s", name);
                                return "deletion of the current branch prohibited";
 +                      default:
 +                              return "Invalid denyDeleteCurrent setting";
                        }
                }
        }
        }
  
        if (is_null_sha1(new_sha1)) {
 +              struct strbuf err = STRBUF_INIT;
                if (!parse_object(old_sha1)) {
                        old_sha1 = NULL;
                        if (ref_exists(name)) {
                                cmd->did_not_exist = 1;
                        }
                }
 -              if (delete_ref(namespaced_name, old_sha1, 0)) {
 -                      rp_error("failed to delete %s", name);
 +              if (ref_transaction_delete(transaction,
 +                                         namespaced_name,
 +                                         old_sha1,
 +                                         0, "push", &err)) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_release(&err);
                        return "failed to delete";
                }
 +              strbuf_release(&err);
                return NULL; /* good */
        }
        else {
 +              struct strbuf err = STRBUF_INIT;
                if (shallow_update && si->shallow_ref[cmd->index] &&
                    update_shallow_ref(cmd, si))
                        return "shallow error";
  
 -              lock = lock_any_ref_for_update(namespaced_name, old_sha1,
 -                                             0, NULL);
 -              if (!lock) {
 -                      rp_error("failed to lock %s", name);
 -                      return "failed to lock";
 -              }
 -              if (write_ref_sha1(lock, new_sha1, "push")) {
 -                      return "failed to write"; /* error() already called */
 +              if (ref_transaction_update(transaction,
 +                                         namespaced_name,
 +                                         new_sha1, old_sha1,
 +                                         0, "push",
 +                                         &err)) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_release(&err);
 +
 +                      return "failed to update ref";
                }
 +              strbuf_release(&err);
 +
                return NULL; /* good */
        }
  }
  static void run_update_post_hook(struct command *commands)
  {
        struct command *cmd;
 -      int argc;
 -      const char **argv;
 -      struct child_process proc;
 -      char *hook;
 +      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 = xmalloc(sizeof(*argv) * (2 + argc));
 -      argv[0] = hook;
 -
 -      for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
 +      for (cmd = commands; cmd; cmd = cmd->next) {
                if (cmd->error_string || cmd->did_not_exist)
                        continue;
 -              argv[argc] = xstrdup(cmd->ref_name);
 -              argc++;
 +              if (!proc.args.argc)
 +                      argv_array_push(&proc.args, hook);
 +              argv_array_push(&proc.args, cmd->ref_name);
        }
 -      argv[argc] = NULL;
 +      if (!proc.args.argc)
 +              return;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
 -      proc.argv = argv;
  
        if (!start_command(&proc)) {
                if (use_sideband)
@@@ -1162,23 -892,24 +1162,23 @@@ static void check_aliased_update(struc
        const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
 -      unsigned char sha1[20];
 -      char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
 +      unsigned char sha1[GIT_SHA1_RAWSZ];
        int flag;
  
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
 -      dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
 +      dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);
        strbuf_release(&buf);
  
        if (!(flag & REF_ISSYMREF))
                return;
  
 -      dst_name = strip_namespace(dst_name);
        if (!dst_name) {
                rp_error("refusing update to broken symref '%s'", cmd->ref_name);
                cmd->skip_update = 1;
                cmd->error_string = "broken symref";
                return;
        }
 +      dst_name = strip_namespace(dst_name);
  
        if ((item = string_list_lookup(list, dst_name)) == NULL)
                return;
  
        dst_cmd->skip_update = 1;
  
 -      strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV));
 -      strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV));
 -      strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV));
 -      strcpy(dst_newh, find_unique_abbrev(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_sha1, DEFAULT_ABBREV),
 +               find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV),
 +               dst_cmd->ref_name,
 +               find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV),
 +               find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
  
        cmd->error_string = dst_cmd->error_string =
                "inconsistent aliased update";
@@@ -1216,7 -947,7 +1216,7 @@@ static void check_aliased_updates(struc
                        string_list_append(&ref_list, cmd->ref_name);
                item->util = (void *)cmd;
        }
 -      sort_string_list(&ref_list);
 +      string_list_sort(&ref_list);
  
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string)
@@@ -1245,17 -976,12 +1245,17 @@@ static void set_connectivity_errors(str
  
        for (cmd = commands; cmd; cmd = cmd->next) {
                struct command *singleton = cmd;
 +              struct check_connected_options opt = CHECK_CONNECTED_INIT;
 +
                if (shallow_update && si->shallow_ref[cmd->index])
                        /* to be checked in update_shallow_ref() */
                        continue;
 -              if (!check_everything_connected(command_singleton_iterator,
 -                                              0, &singleton))
 +
 +              opt.env = tmp_objdir_env(tmp_objdir);
 +              if (!check_connected(command_singleton_iterator, &singleton,
 +                                   &opt))
                        continue;
 +
                cmd->error_string = "missing necessary objects";
        }
  }
@@@ -1287,137 -1013,26 +1287,137 @@@ static int iterate_receive_command_list
  
  static void reject_updates_to_hidden(struct command *commands)
  {
 +      struct strbuf refname_full = STRBUF_INIT;
 +      size_t prefix_len;
        struct command *cmd;
  
 +      strbuf_addstr(&refname_full, get_git_namespace());
 +      prefix_len = refname_full.len;
 +
        for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (cmd->error_string || !ref_is_hidden(cmd->ref_name))
 +              if (cmd->error_string)
 +                      continue;
 +
 +              strbuf_setlen(&refname_full, prefix_len);
 +              strbuf_addstr(&refname_full, cmd->ref_name);
 +
 +              if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
                        continue;
                if (is_null_sha1(cmd->new_sha1))
                        cmd->error_string = "deny deleting a hidden ref";
                else
                        cmd->error_string = "deny updating a hidden ref";
        }
 +
 +      strbuf_release(&refname_full);
 +}
 +
 +static int should_process_cmd(struct command *cmd)
 +{
 +      return !cmd->error_string && !cmd->skip_update;
 +}
 +
 +static void warn_if_skipped_connectivity_check(struct command *commands,
 +                                             struct shallow_info *si)
 +{
 +      struct command *cmd;
 +      int checked_connectivity = 1;
 +
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
 +                      error("BUG: connectivity check has not been run on ref %s",
 +                            cmd->ref_name);
 +                      checked_connectivity = 0;
 +              }
 +      }
 +      if (!checked_connectivity)
 +              die("BUG: connectivity check skipped???");
 +}
 +
 +static void execute_commands_non_atomic(struct command *commands,
 +                                      struct shallow_info *si)
 +{
 +      struct command *cmd;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (!should_process_cmd(cmd))
 +                      continue;
 +
 +              transaction = ref_transaction_begin(&err);
 +              if (!transaction) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_reset(&err);
 +                      cmd->error_string = "transaction failed to start";
 +                      continue;
 +              }
 +
 +              cmd->error_string = update(cmd, si);
 +
 +              if (!cmd->error_string
 +                  && ref_transaction_commit(transaction, &err)) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_reset(&err);
 +                      cmd->error_string = "failed to update ref";
 +              }
 +              ref_transaction_free(transaction);
 +      }
 +      strbuf_release(&err);
 +}
 +
 +static void execute_commands_atomic(struct command *commands,
 +                                      struct shallow_info *si)
 +{
 +      struct command *cmd;
 +      struct strbuf err = STRBUF_INIT;
 +      const char *reported_error = "atomic push failure";
 +
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction) {
 +              rp_error("%s", err.buf);
 +              strbuf_reset(&err);
 +              reported_error = "transaction failed to start";
 +              goto failure;
 +      }
 +
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (!should_process_cmd(cmd))
 +                      continue;
 +
 +              cmd->error_string = update(cmd, si);
 +
 +              if (cmd->error_string)
 +                      goto failure;
 +      }
 +
 +      if (ref_transaction_commit(transaction, &err)) {
 +              rp_error("%s", err.buf);
 +              reported_error = "atomic transaction failed";
 +              goto failure;
 +      }
 +      goto cleanup;
 +
 +failure:
 +      for (cmd = commands; cmd; cmd = cmd->next)
 +              if (!cmd->error_string)
 +                      cmd->error_string = reported_error;
 +
 +cleanup:
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
  }
  
  static void execute_commands(struct command *commands,
                             const char *unpacker_error,
 -                           struct shallow_info *si)
 +                           struct shallow_info *si,
 +                           const struct string_list *push_options)
  {
 -      int checked_connectivity;
 +      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;
  
        if (unpacker_error) {
                for (cmd = commands; cmd; cmd = cmd->next)
                return;
        }
  
 +      if (use_sideband) {
 +              memset(&muxer, 0, sizeof(muxer));
 +              muxer.proc = copy_to_sideband;
 +              muxer.in = -1;
 +              if (!start_async(&muxer))
 +                      err_fd = muxer.in;
 +              /* ...else, continue without relaying sideband */
 +      }
 +
        data.cmds = commands;
        data.si = si;
 -      if (check_everything_connected(iterate_receive_command_list, 0, &data))
 +      opt.err_fd = err_fd;
 +      opt.progress = err_fd && !quiet;
 +      opt.env = tmp_objdir_env(tmp_objdir);
 +      if (check_connected(iterate_receive_command_list, &data, &opt))
                set_connectivity_errors(commands, si);
  
 +      if (use_sideband)
 +              finish_async(&muxer);
 +
        reject_updates_to_hidden(commands);
  
 -      if (run_receive_hook(commands, "pre-receive", 0)) {
 +      if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
                for (cmd = commands; cmd; cmd = cmd->next) {
                        if (!cmd->error_string)
                                cmd->error_string = "pre-receive hook declined";
                return;
        }
  
 +      /*
 +       * Now we'll start writing out refs, which means the objects need
 +       * to be in their final positions so that other processes can see them.
 +       */
 +      if (tmp_objdir_migrate(tmp_objdir) < 0) {
 +              for (cmd = commands; cmd; cmd = cmd->next) {
 +                      if (!cmd->error_string)
 +                              cmd->error_string = "unable to migrate objects to permanent storage";
 +              }
 +              return;
 +      }
 +      tmp_objdir = NULL;
 +
        check_aliased_updates(commands);
  
        free(head_name_to_free);
 -      head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
 -
 -      checked_connectivity = 1;
 -      for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (cmd->error_string)
 -                      continue;
 +      head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL);
  
 -              if (cmd->skip_update)
 -                      continue;
 -
 -              cmd->error_string = update(cmd, si);
 -              if (shallow_update && !cmd->error_string &&
 -                  si->shallow_ref[cmd->index]) {
 -                      error("BUG: connectivity check has not been run on ref %s",
 -                            cmd->ref_name);
 -                      checked_connectivity = 0;
 -              }
 -      }
 +      if (use_atomic)
 +              execute_commands_atomic(commands, si);
 +      else
 +              execute_commands_non_atomic(commands, si);
  
 -      if (shallow_update && !checked_connectivity)
 -              error("BUG: run 'git fsck' for safety.\n"
 -                    "If there are errors, try to remove "
 -                    "the reported refs above");
 +      if (shallow_update)
 +              warn_if_skipped_connectivity_check(commands, si);
  }
  
  static struct command **queue_command(struct command **tail,
  
        refname = line + 82;
        reflen = linelen - 82;
 -      cmd = xcalloc(1, sizeof(struct command) + reflen + 1);
 +      FLEX_ALLOC_MEM(cmd, ref_name, refname, reflen);
        hashcpy(cmd->old_sha1, old_sha1);
        hashcpy(cmd->new_sha1, new_sha1);
 -      memcpy(cmd->ref_name, refname, reflen);
 -      cmd->ref_name[reflen] = '\0';
        *tail = cmd;
        return &cmd->next;
  }
@@@ -1524,7 -1127,7 +1524,7 @@@ 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;
        }
  }
@@@ -1559,12 -1162,6 +1559,12 @@@ static struct command *read_head_info(s
                                use_sideband = LARGE_PACKET_MAX;
                        if (parse_feature_request(feature_list, "quiet"))
                                quiet = 1;
 +                      if (advertise_atomic_push
 +                          && parse_feature_request(feature_list, "atomic"))
 +                              use_atomic = 1;
 +                      if (advertise_push_options
 +                          && parse_feature_request(feature_list, "push-options"))
 +                              use_push_options = 1;
                }
  
                if (!strcmp(line, "push-cert")) {
        return commands;
  }
  
 +static void read_push_options(struct string_list *options)
 +{
 +      while (1) {
 +              char *line;
 +              int len;
 +
 +              line = packet_read_line(0, &len);
 +
 +              if (!line)
 +                      break;
 +
 +              string_list_append(options, line);
 +      }
 +}
 +
  static const char *parse_pack_header(struct pack_header *hdr)
  {
        switch (read_pack_header(0, hdr)) {
@@@ -1637,10 -1219,11 +1637,10 @@@ static const char *pack_lockfile
  static const char *unpack(int err_fd, struct shallow_info *si)
  {
        struct pack_header hdr;
 -      struct argv_array av = ARGV_ARRAY_INIT;
        const char *hdr_err;
        int status;
        char hdr_arg[38];
 -      struct child_process child;
 +      struct child_process child = CHILD_PROCESS_INIT;
        int fsck_objects = (receive_fsck_objects >= 0
                            ? receive_fsck_objects
                            : transfer_fsck_objects >= 0
  
        if (si->nr_ours || si->nr_theirs) {
                alt_shallow_file = setup_temporary_shallow(si->shallow);
 -              argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL);
 +              argv_array_push(&child.args, "--shallow-file");
 +              argv_array_push(&child.args, alt_shallow_file);
        }
  
 -      memset(&child, 0, sizeof(child));
 +      tmp_objdir = tmp_objdir_create();
 +      if (!tmp_objdir) {
 +              if (err_fd > 0)
 +                      close(err_fd);
 +              return "unable to create temporary object directory";
 +      }
 +      child.env = tmp_objdir_env(tmp_objdir);
 +
 +      /*
 +       * Normally we just pass the tmp_objdir environment to the child
 +       * processes that do the heavy lifting, but we may need to see these
 +       * objects ourselves to set up shallow information.
 +       */
 +      tmp_objdir_add_as_alternate(tmp_objdir);
 +
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
 -              argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL);
 +              argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
                if (quiet)
 -                      argv_array_push(&av, "-q");
 +                      argv_array_push(&child.args, "-q");
                if (fsck_objects)
 -                      argv_array_push(&av, "--strict");
 -              child.argv = av.argv;
 +                      argv_array_pushf(&child.args, "--strict%s",
 +                              fsck_msg_types.buf);
 +              if (max_input_size)
 +                      argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
 +                              (uintmax_t)max_input_size);
                child.no_stdout = 1;
                child.err = err_fd;
                child.git_cmd = 1;
                if (status)
                        return "unpack-objects abnormal exit";
        } else {
 -              int s;
 -              char keep_arg[256];
 +              char hostname[256];
  
 -              s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
 -              if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
 -                      strcpy(keep_arg + s, "localhost");
 +              argv_array_pushl(&child.args, "index-pack",
 +                               "--stdin", hdr_arg, NULL);
  
 -              argv_array_pushl(&av, "index-pack",
 -                               "--stdin", hdr_arg, keep_arg, NULL);
 +              if (gethostname(hostname, sizeof(hostname)))
 +                      xsnprintf(hostname, sizeof(hostname), "localhost");
 +              argv_array_pushf(&child.args,
 +                               "--keep=receive-pack %"PRIuMAX" on %s",
 +                               (uintmax_t)getpid(),
 +                               hostname);
 +
 +              if (!quiet && err_fd)
 +                      argv_array_push(&child.args, "--show-resolving-progress");
 +              if (use_sideband)
 +                      argv_array_push(&child.args, "--report-end-of-input");
                if (fsck_objects)
 -                      argv_array_push(&av, "--strict");
 -              if (fix_thin)
 -                      argv_array_push(&av, "--fix-thin");
 -              child.argv = av.argv;
 +                      argv_array_pushf(&child.args, "--strict%s",
 +                              fsck_msg_types.buf);
 +              if (!reject_thin)
 +                      argv_array_push(&child.args, "--fix-thin");
 +              if (max_input_size)
 +                      argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
 +                              (uintmax_t)max_input_size);
                child.out = -1;
                child.err = err_fd;
                child.git_cmd = 1;
@@@ -1743,7 -1298,6 +1743,7 @@@ static const char *unpack_with_sideband
        if (!use_sideband)
                return unpack(0, si);
  
 +      use_keepalive = KEEPALIVE_AFTER_NUL;
        memset(&muxer, 0, sizeof(muxer));
        muxer.proc = copy_to_sideband;
        muxer.in = -1;
@@@ -1761,7 -1315,8 +1761,7 @@@ static void prepare_shallow_update(stru
  {
        int i, j, k, bitmap_size = (si->ref->nr + 31) / 32;
  
 -      si->used_shallow = xmalloc(sizeof(*si->used_shallow) *
 -                                 si->shallow->nr);
 +      ALLOC_ARRAY(si->used_shallow, si->shallow->nr);
        assign_shallow_commits_to_refs(si, si->used_shallow, NULL);
  
        si->need_reachability_test =
                                continue;
                        si->need_reachability_test[i]++;
                        for (k = 0; k < 32; k++)
 -                              if (si->used_shallow[i][j] & (1 << k))
 +                              if (si->used_shallow[i][j] & (1U << k))
                                        si->shallow_ref[j * 32 + k]++;
                }
  
@@@ -1827,7 -1382,7 +1827,7 @@@ static void update_shallow_info(struct 
                return;
        }
  
 -      ref_status = xmalloc(sizeof(*ref_status) * ref->nr);
 +      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))
@@@ -1877,29 -1432,45 +1877,29 @@@ static int delete_only(struct command *
  int cmd_receive_pack(int argc, const char **argv, const char *prefix)
  {
        int advertise_refs = 0;
 -      int i;
        struct command *commands;
        struct sha1_array shallow = SHA1_ARRAY_INIT;
        struct sha1_array ref = SHA1_ARRAY_INIT;
        struct shallow_info si;
  
 +      struct option options[] = {
 +              OPT__QUIET(&quiet, N_("quiet")),
 +              OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL),
 +              OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL),
 +              OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL),
 +              OPT_END()
 +      };
 +
        packet_trace_identity("receive-pack");
  
 -      argv++;
 -      for (i = 1; i < argc; i++) {
 -              const char *arg = *argv++;
 +      argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);
  
 -              if (*arg == '-') {
 -                      if (!strcmp(arg, "--quiet")) {
 -                              quiet = 1;
 -                              continue;
 -                      }
 +      if (argc > 1)
 +              usage_msg_opt(_("Too many arguments."), receive_pack_usage, options);
 +      if (argc == 0)
 +              usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options);
  
 -                      if (!strcmp(arg, "--advertise-refs")) {
 -                              advertise_refs = 1;
 -                              continue;
 -                      }
 -                      if (!strcmp(arg, "--stateless-rpc")) {
 -                              stateless_rpc = 1;
 -                              continue;
 -                      }
 -                      if (!strcmp(arg, "--reject-thin-pack-for-testing")) {
 -                              fix_thin = 0;
 -                              continue;
 -                      }
 -
 -                      usage(receive_pack_usage);
 -              }
 -              if (service_dir)
 -                      usage(receive_pack_usage);
 -              service_dir = arg;
 -      }
 -      if (!service_dir)
 -              usage(receive_pack_usage);
 +      service_dir = argv[0];
  
        setup_path();
  
  
        if ((commands = read_head_info(&shallow)) != NULL) {
                const char *unpack_status = NULL;
 +              struct string_list push_options = STRING_LIST_INIT_DUP;
 +
 +              if (use_push_options)
 +                      read_push_options(&push_options);
  
                prepare_shallow_info(&si, &shallow);
                if (!si.nr_ours && !si.nr_theirs)
                        unpack_status = unpack_with_sideband(&si);
                        update_shallow_info(commands, &si, &ref);
                }
 -              execute_commands(commands, unpack_status, &si);
 +              use_keepalive = KEEPALIVE_ALWAYS;
 +              execute_commands(commands, unpack_status, &si,
 +                               &push_options);
                if (pack_lockfile)
                        unlink_or_warn(pack_lockfile);
                if (report_status)
                        report(commands, unpack_status);
 -              run_receive_hook(commands, "post-receive", 1);
 +              run_receive_hook(commands, "post-receive", 1,
 +                               &push_options);
                run_update_post_hook(commands);
 +              string_list_clear(&push_options, 0);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {
                                "gc", "--auto", "--quiet", NULL,
                        };
 -                      int opt = RUN_GIT_CMD | RUN_COMMAND_STDOUT_TO_STDERR;
 -                      run_command_v_opt(argv_gc_auto, opt);
 +                      struct child_process proc = CHILD_PROCESS_INIT;
 +
 +                      proc.no_stdin = 1;
 +                      proc.stdout_to_stderr = 1;
 +                      proc.err = use_sideband ? -1 : 0;
 +                      proc.git_cmd = 1;
 +                      proc.argv = argv_gc_auto;
 +
 +                      close_all_packs();
 +                      if (!start_command(&proc)) {
 +                              if (use_sideband)
 +                                      copy_to_sideband(proc.err, -1, NULL);
 +                              finish_command(&proc);
 +                      }
                }
                if (auto_update_server_info)
                        update_server_info(0);