Merge branch 'master' of https://github.com/Softcatala/git-po
[gitweb.git] / builtin / receive-pack.c
index c8e32b297c4be68bb0d33a6a88c15470821ae4ea..6b97cbdbe9444d72d575c149678e4b609a154842 100644 (file)
 #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,
@@ -41,15 +45,18 @@ 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;
@@ -73,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) {
@@ -190,13 +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 (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;
 
@@ -208,8 +239,10 @@ 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;
@@ -238,9 +271,10 @@ static int show_ref_cb(const char *path_full, const struct object_id *oid,
        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)
@@ -316,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);
@@ -547,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;
@@ -564,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));
@@ -603,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_;
@@ -631,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;
@@ -643,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;
@@ -667,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)
@@ -686,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]);
@@ -734,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;
 
@@ -746,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;
@@ -1068,10 +1163,6 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        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];
        int flag;
 
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
@@ -1081,13 +1172,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;
@@ -1102,14 +1193,14 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
 
        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_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";
@@ -1154,12 +1245,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";
        }
 }
@@ -1313,11 +1409,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)
@@ -1325,14 +1425,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";
@@ -1340,6 +1455,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);
@@ -1372,11 +1500,9 @@ static struct command **queue_command(struct command **tail,
 
        refname = line + 82;
        reflen = linelen - 82;
-       cmd = xcalloc(1, st_add3(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;
 }
@@ -1436,6 +1562,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")) {
@@ -1468,6 +1597,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)) {
@@ -1519,6 +1663,18 @@ 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)
+               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)
@@ -1526,6 +1682,9 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                if (fsck_objects)
                        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;
@@ -1545,11 +1704,18 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                                 (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_pushf(&child.args, "--strict%s",
                                fsck_msg_types.buf);
-               if (fix_thin)
+               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;
@@ -1574,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;
@@ -1707,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();
 
@@ -1769,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)
@@ -1777,20 +1932,36 @@ 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);
+               if (push_options.nr)
+                       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;
+                       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();
-                       run_command_v_opt(argv_gc_auto, opt);
+                       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);