receive-pack: switch global variable 'commands' to a parameter
[gitweb.git] / builtin-receive-pack.c
index db67c3162c6b8ffaba793fd6082324aeae158f86..fffb6eac881a536ea7fa4a94a0790a117d8b4cf2 100644 (file)
@@ -2,6 +2,7 @@
 #include "pack.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "sideband.h"
 #include "run-command.h"
 #include "exec_cmd.h"
 #include "commit.h"
 #include "remote.h"
 #include "transport.h"
 
-static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
 enum deny_action {
+       DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
        DENY_REFUSE,
 };
 
-static int deny_deletes = 0;
-static int deny_non_fast_forwards = 0;
-static enum deny_action deny_current_branch = DENY_WARN;
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
 static int receive_fsck_objects;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
-
-static char capabilities[] = " report-status delete-refs ";
-static int capabilities_sent;
+static int use_sideband;
+static int prefer_ofs_delta = 1;
+static int auto_update_server_info;
+static int auto_gc = 1;
+static const char *head_name;
+static int sent_capabilities;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
 {
@@ -76,24 +82,46 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "receive.denydeletecurrent") == 0) {
+               deny_delete_current = parse_deny_action(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+               prefer_ofs_delta = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.updateserverinfo") == 0) {
+               auto_update_server_info = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.autogc") == 0) {
+               auto_gc = git_config_bool(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
 static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       if (capabilities_sent)
+       if (sent_capabilities)
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
        else
-               packet_write(1, "%s %s%c%s\n",
-                            sha1_to_hex(sha1), path, 0, capabilities);
-       capabilities_sent = 1;
+               packet_write(1, "%s %s%c%s%s\n",
+                            sha1_to_hex(sha1), path, 0,
+                            " report-status delete-refs side-band-64k",
+                            prefer_ofs_delta ? " ofs-delta" : "");
+       sent_capabilities = 1;
        return 0;
 }
 
 static void write_head_info(void)
 {
        for_each_ref(show_ref, NULL);
-       if (!capabilities_sent)
+       if (!sent_capabilities)
                show_ref("capabilities^{}", null_sha1, 0, NULL);
 
 }
@@ -106,41 +134,64 @@ struct command {
        char ref_name[FLEX_ARRAY]; /* more */
 };
 
-static struct command *commands;
-
 static const char pre_receive_hook[] = "hooks/pre-receive";
 static const char post_receive_hook[] = "hooks/post-receive";
 
-static int hook_status(int code, const char *hook_name)
+static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+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)
 {
-       switch (code) {
-       case 0:
-               return 0;
-       case -ERR_RUN_COMMAND_FORK:
-               return error("hook fork failed");
-       case -ERR_RUN_COMMAND_EXEC:
-               return error("hook execute failed");
-       case -ERR_RUN_COMMAND_PIPE:
-               return error("hook pipe failed");
-       case -ERR_RUN_COMMAND_WAITPID:
-               return error("waitpid failed");
-       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-               return error("waitpid is confused");
-       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-               return error("%s died of signal", hook_name);
-       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-               return error("%s died strangely", hook_name);
-       default:
-               error("%s exited with error code %d", hook_name, -code);
-               return -code;
+       int sz = strlen(prefix);
+       char msg[4096];
+
+       strncpy(msg, prefix, sz);
+       sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
+       if (sz > (sizeof(msg) - 1))
+               sz = sizeof(msg) - 1;
+       msg[sz++] = '\n';
+
+       if (use_sideband)
+               send_sideband(1, 2, msg, sz, use_sideband);
+       else
+               xwrite(2, msg, sz);
+}
+
+static void rp_warning(const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       report_message("warning: ", err, params);
+       va_end(params);
+}
+
+static void rp_error(const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       report_message("error: ", err, params);
+       va_end(params);
+}
+
+static int copy_to_sideband(int in, int out, void *arg)
+{
+       char data[128];
+       while (1) {
+               ssize_t sz = xread(in, data, sizeof(data));
+               if (sz <= 0)
+                       break;
+               send_sideband(1, 2, data, sz, use_sideband);
        }
+       close(in);
+       return 0;
 }
 
-static int run_hook(const char *hook_name)
+static int run_receive_hook(struct command *commands, const char *hook_name)
 {
        static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
        struct command *cmd;
        struct child_process proc;
+       struct async muxer;
        const char *argv[2];
        int have_input = 0, code;
 
@@ -160,9 +211,23 @@ static int run_hook(const char *hook_name)
        proc.in = -1;
        proc.stdout_to_stderr = 1;
 
+       if (use_sideband) {
+               memset(&muxer, 0, sizeof(muxer));
+               muxer.proc = copy_to_sideband;
+               muxer.in = -1;
+               code = start_async(&muxer);
+               if (code)
+                       return code;
+               proc.err = muxer.in;
+       }
+
        code = start_command(&proc);
-       if (code)
-               return hook_status(code, hook_name);
+       if (code) {
+               if (use_sideband)
+                       finish_async(&muxer);
+               return code;
+       }
+
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string) {
                        size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
@@ -174,14 +239,17 @@ static int run_hook(const char *hook_name)
                }
        }
        close(proc.in);
-       return hook_status(finish_command(&proc), hook_name);
+       if (use_sideband)
+               finish_async(&muxer);
+       return finish_command(&proc);
 }
 
 static int run_update_hook(struct command *cmd)
 {
        static const char update_hook[] = "hooks/update";
-       struct child_process proc;
        const char *argv[5];
+       struct child_process proc;
+       int code;
 
        if (access(update_hook, X_OK) < 0)
                return 0;
@@ -193,25 +261,70 @@ static int run_update_hook(struct command *cmd)
        argv[4] = NULL;
 
        memset(&proc, 0, sizeof(proc));
-       proc.argv = argv;
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
+       proc.err = use_sideband ? -1 : 0;
+       proc.argv = argv;
 
-       return hook_status(run_command(&proc), update_hook);
+       code = start_command(&proc);
+       if (code)
+               return code;
+       if (use_sideband)
+               copy_to_sideband(proc.err, -1, NULL);
+       return finish_command(&proc);
 }
 
 static int is_ref_checked_out(const char *ref)
 {
-       unsigned char sha1[20];
-       const char *head;
-
        if (is_bare_repository())
                return 0;
 
-       head = resolve_ref("HEAD", sha1, 0, NULL);
-       if (!head)
+       if (!head_name)
                return 0;
-       return !strcmp(head, 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 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]);
+}
+
+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 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]);
 }
 
 static const char *update(struct command *cmd)
@@ -223,26 +336,24 @@ static const char *update(struct command *cmd)
 
        /* only refs/... are allowed */
        if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
-               error("refusing to create funny ref '%s' remotely", name);
+               rp_error("refusing to create funny ref '%s' remotely", name);
                return "funny refname";
        }
 
-       switch (deny_current_branch) {
-       case DENY_IGNORE:
-               break;
-       case DENY_WARN:
-               if (!is_ref_checked_out(name))
+       if (is_ref_checked_out(name)) {
+               switch (deny_current_branch) {
+               case DENY_IGNORE:
                        break;
-               warning("updating the currently checked out branch; this may"
-                       " cause confusion,\n"
-                       "as the index and working tree do not reflect changes"
-                       " that are now in HEAD.");
-               break;
-       case DENY_REFUSE:
-               if (!is_ref_checked_out(name))
+               case DENY_WARN:
+                       rp_warning("updating the current branch");
                        break;
-               error("refusing to update checked out branch: %s", name);
-               return "branch is currently checked out";
+               case DENY_REFUSE:
+               case DENY_UNCONFIGURED:
+                       rp_error("refusing to update checked out branch: %s", name);
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               refuse_unconfigured_deny();
+                       return "branch is currently checked out";
+               }
        }
 
        if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
@@ -250,12 +361,30 @@ static const char *update(struct command *cmd)
                      "but I can't find it!", sha1_to_hex(new_sha1));
                return "bad pack";
        }
-       if (deny_deletes && is_null_sha1(new_sha1) &&
-           !is_null_sha1(old_sha1) &&
-           !prefixcmp(name, "refs/heads/")) {
-               error("denying ref deletion for %s", name);
-               return "deletion prohibited";
+
+       if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+               if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+                       rp_error("denying ref deletion for %s", name);
+                       return "deletion prohibited";
+               }
+
+               if (!strcmp(name, head_name)) {
+                       switch (deny_delete_current) {
+                       case DENY_IGNORE:
+                               break;
+                       case DENY_WARN:
+                               rp_warning("deleting the current branch");
+                               break;
+                       case DENY_REFUSE:
+                       case DENY_UNCONFIGURED:
+                               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";
+                       }
+               }
        }
+
        if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
            !is_null_sha1(old_sha1) &&
            !prefixcmp(name, "refs/heads/")) {
@@ -280,23 +409,23 @@ static const char *update(struct command *cmd)
                                break;
                free_commit_list(bases);
                if (!ent) {
-                       error("denying non-fast forward %s"
-                             " (you should pull first)", name);
-                       return "non-fast forward";
+                       rp_error("denying non-fast-forward %s"
+                                " (you should pull first)", name);
+                       return "non-fast-forward";
                }
        }
        if (run_update_hook(cmd)) {
-               error("hook declined to update %s", name);
+               rp_error("hook declined to update %s", name);
                return "hook declined";
        }
 
        if (is_null_sha1(new_sha1)) {
                if (!parse_object(old_sha1)) {
-                       warning ("Allowing deletion of corrupt ref.");
+                       rp_warning("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
                }
                if (delete_ref(name, old_sha1, 0)) {
-                       error("failed to delete %s", name);
+                       rp_error("failed to delete %s", name);
                        return "failed to delete";
                }
                return NULL; /* good */
@@ -304,7 +433,7 @@ static const char *update(struct command *cmd)
        else {
                lock = lock_any_ref_for_update(name, old_sha1, 0);
                if (!lock) {
-                       error("failed to lock %s", name);
+                       rp_error("failed to lock %s", name);
                        return "failed to lock";
                }
                if (write_ref_sha1(lock, new_sha1, "push")) {
@@ -316,14 +445,15 @@ static const char *update(struct command *cmd)
 
 static char update_post_hook[] = "hooks/post-update";
 
-static void run_update_post_hook(struct command *cmd)
+static void run_update_post_hook(struct command *commands)
 {
-       struct command *cmd_p;
+       struct command *cmd;
        int argc;
        const char **argv;
+       struct child_process proc;
 
-       for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
-               if (cmd_p->error_string)
+       for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
+               if (cmd->error_string)
                        continue;
                argc++;
        }
@@ -332,48 +462,56 @@ static void run_update_post_hook(struct command *cmd)
        argv = xmalloc(sizeof(*argv) * (2 + argc));
        argv[0] = update_post_hook;
 
-       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+       for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
                char *p;
-               if (cmd_p->error_string)
+               if (cmd->error_string)
                        continue;
-               p = xmalloc(strlen(cmd_p->ref_name) + 1);
-               strcpy(p, cmd_p->ref_name);
+               p = xmalloc(strlen(cmd->ref_name) + 1);
+               strcpy(p, cmd->ref_name);
                argv[argc] = p;
                argc++;
        }
        argv[argc] = NULL;
-       run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
-               | RUN_COMMAND_STDOUT_TO_STDERR);
+
+       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)
+                       copy_to_sideband(proc.err, -1, NULL);
+               finish_command(&proc);
+       }
 }
 
-static void execute_commands(const char *unpacker_error)
+static void execute_commands(struct command *commands, const char *unpacker_error)
 {
-       struct command *cmd = commands;
+       struct command *cmd;
+       unsigned char sha1[20];
 
        if (unpacker_error) {
-               while (cmd) {
+               for (cmd = commands; cmd; cmd = cmd->next)
                        cmd->error_string = "n/a (unpacker error)";
-                       cmd = cmd->next;
-               }
                return;
        }
 
-       if (run_hook(pre_receive_hook)) {
-               while (cmd) {
+       if (run_receive_hook(commands, pre_receive_hook)) {
+               for (cmd = commands; cmd; cmd = cmd->next)
                        cmd->error_string = "pre-receive hook declined";
-                       cmd = cmd->next;
-               }
                return;
        }
 
-       while (cmd) {
+       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
+       for (cmd = commands; cmd; cmd = cmd->next)
                cmd->error_string = update(cmd);
-               cmd = cmd->next;
-       }
 }
 
-static void read_head_info(void)
+static struct command *read_head_info(void)
 {
+       struct command *commands = NULL;
        struct command **p = &commands;
        for (;;) {
                static char line[1000];
@@ -400,6 +538,8 @@ static void read_head_info(void)
                if (reflen + 82 < len) {
                        if (strstr(refname + reflen + 1, "report-status"))
                                report_status = 1;
+                       if (strstr(refname + reflen + 1, "side-band-64k"))
+                               use_sideband = LARGE_PACKET_MAX;
                }
                cmd = xmalloc(sizeof(struct command) + len - 80);
                hashcpy(cmd->old_sha1, old_sha1);
@@ -410,6 +550,7 @@ static void read_head_info(void)
                *p = cmd;
                p = &cmd->next;
        }
+       return commands;
 }
 
 static const char *parse_pack_header(struct pack_header *hdr)
@@ -456,24 +597,9 @@ static const char *unpack(void)
                unpacker[i++] = hdr_arg;
                unpacker[i++] = NULL;
                code = run_command_v_opt(unpacker, RUN_GIT_CMD);
-               switch (code) {
-               case 0:
+               if (!code)
                        return NULL;
-               case -ERR_RUN_COMMAND_FORK:
-                       return "unpack fork failed";
-               case -ERR_RUN_COMMAND_EXEC:
-                       return "unpack execute failed";
-               case -ERR_RUN_COMMAND_WAITPID:
-                       return "waitpid failed";
-               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-                       return "waitpid is confused";
-               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-                       return "unpacker died of signal";
-               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-                       return "unpacker died strangely";
-               default:
-                       return "unpacker exited with error code";
-               }
+               return "unpack-objects abnormal exit";
        } else {
                const char *keeper[7];
                int s, status, i = 0;
@@ -496,8 +622,10 @@ static const char *unpack(void)
                ip.argv = keeper;
                ip.out = -1;
                ip.git_cmd = 1;
-               if (start_command(&ip))
+               status = start_command(&ip);
+               if (status) {
                        return "index-pack fork failed";
+               }
                pack_lockfile = index_pack_lockfile(ip.out);
                close(ip.out);
                status = finish_command(&ip);
@@ -509,28 +637,36 @@ static const char *unpack(void)
        }
 }
 
-static void report(const char *unpack_status)
+static void report(struct command *commands, const char *unpack_status)
 {
        struct command *cmd;
-       packet_write(1, "unpack %s\n",
-                    unpack_status ? unpack_status : "ok");
+       struct strbuf buf = STRBUF_INIT;
+
+       packet_buf_write(&buf, "unpack %s\n",
+                        unpack_status ? unpack_status : "ok");
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string)
-                       packet_write(1, "ok %s\n",
-                                    cmd->ref_name);
+                       packet_buf_write(&buf, "ok %s\n",
+                                        cmd->ref_name);
                else
-                       packet_write(1, "ng %s %s\n",
-                                    cmd->ref_name, cmd->error_string);
+                       packet_buf_write(&buf, "ng %s %s\n",
+                                        cmd->ref_name, cmd->error_string);
        }
-       packet_flush(1);
+       packet_buf_flush(&buf);
+
+       if (use_sideband)
+               send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+       else
+               safe_write(1, buf.buf, buf.len);
+       strbuf_release(&buf);
 }
 
-static int delete_only(struct command *cmd)
+static int delete_only(struct command *commands)
 {
-       while (cmd) {
+       struct command *cmd;
+       for (cmd = commands; cmd; cmd = cmd->next) {
                if (!is_null_sha1(cmd->new_sha1))
                        return 0;
-               cmd = cmd->next;
        }
        return 1;
 }
@@ -576,15 +712,26 @@ static void add_alternate_refs(void)
 
 int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 {
+       int advertise_refs = 0;
+       int stateless_rpc = 0;
        int i;
        char *dir = NULL;
+       struct command *commands;
 
        argv++;
        for (i = 1; i < argc; i++) {
                const char *arg = *argv++;
 
                if (*arg == '-') {
-                       /* Do flag handling here */
+                       if (!strcmp(arg, "--advertise-refs")) {
+                               advertise_refs = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--stateless-rpc")) {
+                               stateless_rpc = 1;
+                               continue;
+                       }
+
                        usage(receive_pack_usage);
                }
                if (dir)
@@ -597,7 +744,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        setup_path();
 
        if (!enter_repo(dir, 0))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
 
        if (is_repository_shallow())
                die("attempt to push into a shallow repository");
@@ -609,26 +756,39 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
 
-       add_alternate_refs();
-       write_head_info();
-       clear_extra_refs();
+       if (advertise_refs || !stateless_rpc) {
+               add_alternate_refs();
+               write_head_info();
+               clear_extra_refs();
 
-       /* EOF */
-       packet_flush(1);
+               /* EOF */
+               packet_flush(1);
+       }
+       if (advertise_refs)
+               return 0;
 
-       read_head_info();
-       if (commands) {
+       if ((commands = read_head_info()) != NULL) {
                const char *unpack_status = NULL;
 
                if (!delete_only(commands))
                        unpack_status = unpack();
-               execute_commands(unpack_status);
+               execute_commands(commands, unpack_status);
                if (pack_lockfile)
-                       unlink(pack_lockfile);
+                       unlink_or_warn(pack_lockfile);
                if (report_status)
-                       report(unpack_status);
-               run_hook(post_receive_hook);
+                       report(commands, unpack_status);
+               run_receive_hook(commands, post_receive_hook);
                run_update_post_hook(commands);
+               if (auto_gc) {
+                       const char *argv_gc_auto[] = {
+                               "gc", "--auto", "--quiet", NULL,
+                       };
+                       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+               }
+               if (auto_update_server_info)
+                       update_server_info(0);
        }
+       if (use_sideband)
+               packet_flush(1);
        return 0;
 }