system_path: move RUNTIME_PREFIX to a sub-function
[gitweb.git] / builtin / receive-pack.c
index 04cb5a1a060ae0f26d9d49eb02b57408b9c71929..b618d52139beccafdb77cc4eed4d3fc121b4124a 100644 (file)
 #include "tag.h"
 #include "gpg-interface.h"
 #include "sigchain.h"
+#include "fsck.h"
+#include "tmp-objdir.h"
 
-static const char receive_pack_usage[] = "git receive-pack <git-dir>";
+static const char * const receive_pack_usage[] = {
+       N_("git receive-pack <git-dir>"),
+       NULL
+};
 
 enum deny_action {
        DENY_UNCONFIGURED,
@@ -36,18 +41,22 @@ static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
 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;
@@ -71,6 +80,15 @@ 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)
 {
        if (value) {
@@ -115,6 +133,26 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                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;
@@ -168,16 +206,28 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                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;
 
@@ -189,17 +239,24 @@ static void show_ref(const char *path, const unsigned char *sha1)
                        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 struct object_id *oid, int flag, void *unused)
+static int show_ref_cb(const char *path_full, const struct object_id *oid,
+                      int flag, void *unused)
 {
-       path = strip_namespace(path);
+       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
@@ -214,15 +271,16 @@ static int show_ref_cb(const char *path, const struct object_id *oid, int flag,
        return 0;
 }
 
-static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
 {
        show_ref(".have", sha1);
+       return 0;
 }
 
 static void collect_one_alternate_ref(const struct ref *ref, void *data)
 {
        struct sha1_array *sa = data;
-       sha1_array_append(sa, ref->old_sha1);
+       sha1_array_append(sa, ref->old_oid.hash);
 }
 
 static void write_head_info(void)
@@ -258,10 +316,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2
 
 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;
@@ -292,10 +350,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);
@@ -523,8 +631,16 @@ static void prepare_push_cert_sha1(struct child_process *proc)
        }
 }
 
+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 = CHILD_PROCESS_INIT;
        struct async muxer;
@@ -540,6 +656,19 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
+       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));
@@ -579,12 +708,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
        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_;
@@ -607,8 +730,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
        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;
@@ -619,6 +744,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
        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;
@@ -643,6 +769,7 @@ static int run_update_hook(struct command *cmd)
        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)
@@ -662,47 +789,39 @@ static int is_ref_checked_out(const char *ref)
        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]);
@@ -710,7 +829,7 @@ 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;
-       const char *alt_file;
+       struct check_connected_options opt = CHECK_CONNECTED_INIT;
        uint32_t mask = 1 << (cmd->index % 32);
        int i;
 
@@ -722,9 +841,9 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
                    !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;
@@ -1006,35 +1125,26 @@ static const char *update(struct command *cmd, struct shallow_info *si)
 static void run_update_post_hook(struct command *commands)
 {
        struct command *cmd;
-       int argc;
-       const char **argv;
        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;
 
        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)
@@ -1049,8 +1159,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        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);
@@ -1060,13 +1169,13 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        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;
@@ -1081,14 +1190,14 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
 
        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";
@@ -1133,12 +1242,17 @@ static void set_connectivity_errors(struct command *commands,
 
        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";
        }
 }
@@ -1170,16 +1284,29 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
 
 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)
@@ -1279,11 +1406,15 @@ static void execute_commands_atomic(struct command *commands,
 
 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)
 {
+       struct check_connected_options opt = CHECK_CONNECTED_INIT;
        struct command *cmd;
        unsigned char sha1[20];
        struct iterate_data data;
+       struct async muxer;
+       int err_fd = 0;
 
        if (unpacker_error) {
                for (cmd = commands; cmd; cmd = cmd->next)
@@ -1291,14 +1422,29 @@ static void execute_commands(struct command *commands,
                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";
@@ -1306,6 +1452,19 @@ static void execute_commands(struct command *commands,
                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);
@@ -1338,11 +1497,9 @@ 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;
 }
@@ -1402,6 +1559,9 @@ static struct command *read_head_info(struct sha1_array *shallow)
                        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")) {
@@ -1434,6 +1594,21 @@ static struct command *read_head_info(struct sha1_array *shallow)
        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)) {
@@ -1485,12 +1660,31 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                argv_array_push(&child.args, alt_shallow_file);
        }
 
+       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(&child.args, "unpack-objects", hdr_arg, NULL);
                if (quiet)
                        argv_array_push(&child.args, "-q");
                if (fsck_objects)
-                       argv_array_push(&child.args, "--strict");
+                       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;
@@ -1498,19 +1692,30 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                if (status)
                        return "unpack-objects abnormal exit";
        } else {
-               int s;
-               char keep_arg[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");
+               char hostname[256];
 
                argv_array_pushl(&child.args, "index-pack",
-                                "--stdin", hdr_arg, keep_arg, NULL);
+                                "--stdin", hdr_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(&child.args, "--strict");
-               if (fix_thin)
+                       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;
@@ -1535,6 +1740,7 @@ static const char *unpack_with_sideband(struct shallow_info *si)
        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;
@@ -1552,8 +1758,7 @@ static void prepare_shallow_update(struct command *commands,
 {
        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 =
@@ -1573,7 +1778,7 @@ static void prepare_shallow_update(struct command *commands,
                                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]++;
                }
 
@@ -1619,7 +1824,7 @@ static void update_shallow_info(struct command *commands,
                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))
@@ -1669,45 +1874,29 @@ static int delete_only(struct command *commands)
 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;
 
-       packet_trace_identity("receive-pack");
+       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()
+       };
 
-       argv++;
-       for (i = 1; i < argc; i++) {
-               const char *arg = *argv++;
+       packet_trace_identity("receive-pack");
 
-               if (*arg == '-') {
-                       if (!strcmp(arg, "--quiet")) {
-                               quiet = 1;
-                               continue;
-                       }
+       argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);
 
-                       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;
-                       }
+       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);
 
-                       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();
 
@@ -1731,6 +1920,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 
        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)
@@ -1739,19 +1932,35 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        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);