Merge branch 'maint'
[gitweb.git] / builtin / receive-pack.c
index 0559fcc871894548050e99c8c258561a0dcad5c9..8c9e91e78c95a4a3d581aae59d0f00bc95b2c942 100644 (file)
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
 #include "pack.h"
 #include "refs.h"
 #include "pkt-line.h"
@@ -9,6 +9,9 @@
 #include "object.h"
 #include "remote.h"
 #include "transport.h"
+#include "string-list.h"
+#include "sha1-array.h"
+#include "connected.h"
 
 static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
@@ -16,14 +19,15 @@ enum deny_action {
        DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
-       DENY_REFUSE,
+       DENY_REFUSE
 };
 
 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_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
@@ -33,6 +37,7 @@ static int prefer_ofs_delta = 1;
 static int auto_update_server_info;
 static int auto_gc = 1;
 static const char *head_name;
+static void *head_name_to_free;
 static int sent_capabilities;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
@@ -77,6 +82,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "transfer.fsckobjects") == 0) {
+               transfer_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "receive.denycurrentbranch")) {
                deny_current_branch = parse_deny_action(var, value);
                return 0;
@@ -105,7 +115,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+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);
@@ -115,27 +125,60 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void
                             " report-status delete-refs side-band-64k",
                             prefer_ofs_delta ? " ofs-delta" : "");
        sent_capabilities = 1;
+}
+
+static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
+{
+       path = strip_namespace(path);
+       /*
+        * 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.
+        */
+       if (!path)
+               path = ".have";
+       show_ref(path, sha1);
        return 0;
 }
 
+static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+{
+       show_ref(".have", sha1);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+       struct sha1_array *sa = data;
+       sha1_array_append(sa, ref->old_sha1);
+}
+
 static void write_head_info(void)
 {
-       for_each_ref(show_ref, NULL);
+       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);
        if (!sent_capabilities)
-               show_ref("capabilities^{}", null_sha1, 0, NULL);
+               show_ref("capabilities^{}", null_sha1);
 
+       /* EOF */
+       packet_flush(1);
 }
 
 struct command {
        struct command *next;
        const char *error_string;
+       unsigned int skip_update:1,
+                    did_not_exist:1;
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        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";
 
@@ -188,21 +231,15 @@ static int copy_to_sideband(int in, int out, void *arg)
        return 0;
 }
 
-static int run_receive_hook(const char *hook_name)
+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 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;
-
-       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       have_input = 1;
-       }
+       int code;
 
-       if (!have_input || access(hook_name, X_OK) < 0)
+       if (access(hook_name, X_OK) < 0)
                return 0;
 
        argv[0] = hook_name;
@@ -230,15 +267,13 @@ static int run_receive_hook(const char *hook_name)
                return code;
        }
 
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string) {
-                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
-                               sha1_to_hex(cmd->old_sha1),
-                               sha1_to_hex(cmd->new_sha1),
-                               cmd->ref_name);
-                       if (write_in_full(proc.in, buf, n) != n)
-                               break;
-               }
+       while (1) {
+               const char *buf;
+               size_t n;
+               if (feed(feed_state, &buf, &n))
+                       break;
+               if (write_in_full(proc.in, buf, n) != n)
+                       break;
        }
        close(proc.in);
        if (use_sideband)
@@ -246,6 +281,51 @@ static int run_receive_hook(const char *hook_name)
        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_;
+       struct command *cmd = state->cmd;
+
+       while (cmd &&
+              state->skip_broken && (cmd->error_string || cmd->did_not_exist))
+               cmd = cmd->next;
+       if (!cmd)
+               return -1; /* EOF */
+       strbuf_reset(&state->buf);
+       strbuf_addf(&state->buf, "%s %s %s\n",
+                   sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
+                   cmd->ref_name);
+       state->cmd = cmd->next;
+       if (bufp) {
+               *bufp = state->buf.buf;
+               *sizep = state->buf.len;
+       }
+       return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name,
+                           int skip_broken)
+{
+       struct receive_hook_feed_state state;
+       int status;
+
+       strbuf_init(&state.buf, 0);
+       state.cmd = commands;
+       state.skip_broken = skip_broken;
+       if (feed_receive_hook(&state, NULL, NULL))
+               return 0;
+       state.cmd = commands;
+       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)
 {
        static const char update_hook[] = "hooks/update";
@@ -332,17 +412,22 @@ static void refuse_unconfigured_deny_delete_current(void)
 static const char *update(struct command *cmd)
 {
        const char *name = cmd->ref_name;
+       struct strbuf namespaced_name_buf = STRBUF_INIT;
+       const char *namespaced_name;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
        struct ref_lock *lock;
 
        /* only refs/... are allowed */
-       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+       if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) {
                rp_error("refusing to create funny ref '%s' remotely", name);
                return "funny refname";
        }
 
-       if (is_ref_checked_out(name)) {
+       strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
+       namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
+
+       if (is_ref_checked_out(namespaced_name)) {
                switch (deny_current_branch) {
                case DENY_IGNORE:
                        break;
@@ -370,7 +455,7 @@ static const char *update(struct command *cmd)
                        return "deletion prohibited";
                }
 
-               if (!strcmp(name, head_name)) {
+               if (!strcmp(namespaced_name, head_name)) {
                        switch (deny_delete_current) {
                        case DENY_IGNORE:
                                break;
@@ -423,17 +508,22 @@ static const char *update(struct command *cmd)
 
        if (is_null_sha1(new_sha1)) {
                if (!parse_object(old_sha1)) {
-                       rp_warning("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
+                       if (ref_exists(name)) {
+                               rp_warning("Allowing deletion of corrupt ref.");
+                       } else {
+                               rp_warning("Deleting a non-existent ref.");
+                               cmd->did_not_exist = 1;
+                       }
                }
-               if (delete_ref(name, old_sha1, 0)) {
+               if (delete_ref(namespaced_name, old_sha1, 0)) {
                        rp_error("failed to delete %s", name);
                        return "failed to delete";
                }
                return NULL; /* good */
        }
        else {
-               lock = lock_any_ref_for_update(name, old_sha1, 0);
+               lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
                if (!lock) {
                        rp_error("failed to lock %s", name);
                        return "failed to lock";
@@ -447,15 +537,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 || cmd->did_not_exist)
                        continue;
                argc++;
        }
@@ -464,12 +554,12 @@ 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 || cmd->did_not_exist)
                        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++;
        }
@@ -488,37 +578,152 @@ static void run_update_post_hook(struct command *cmd)
        }
 }
 
-static void execute_commands(const char *unpacker_error)
+static void check_aliased_update(struct command *cmd, struct string_list *list)
 {
-       struct command *cmd = commands;
+       struct strbuf buf = STRBUF_INIT;
+       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];
+       int flag;
 
-       if (unpacker_error) {
-               while (cmd) {
-                       cmd->error_string = "n/a (unpacker error)";
-                       cmd = cmd->next;
-               }
+       strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
+       dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &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;
        }
 
-       if (run_receive_hook(pre_receive_hook)) {
-               while (cmd) {
-                       cmd->error_string = "pre-receive hook declined";
-                       cmd = cmd->next;
-               }
+       if ((item = string_list_lookup(list, dst_name)) == NULL)
                return;
+
+       cmd->skip_update = 1;
+
+       dst_cmd = (struct command *) item->util;
+
+       if (!hashcmp(cmd->old_sha1, dst_cmd->old_sha1) &&
+           !hashcmp(cmd->new_sha1, dst_cmd->new_sha1))
+               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->error_string = dst_cmd->error_string =
+               "inconsistent aliased update";
+}
+
+static void check_aliased_updates(struct command *commands)
+{
+       struct command *cmd;
+       struct string_list ref_list = STRING_LIST_INIT_NODUP;
+
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               struct string_list_item *item =
+                       string_list_append(&ref_list, cmd->ref_name);
+               item->util = (void *)cmd;
+       }
+       sort_string_list(&ref_list);
+
+       for (cmd = commands; cmd; cmd = cmd->next)
+               check_aliased_update(cmd, &ref_list);
+
+       string_list_clear(&ref_list, 0);
+}
+
+static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
+{
+       struct command **cmd_list = cb_data;
+       struct command *cmd = *cmd_list;
+
+       if (!cmd || is_null_sha1(cmd->new_sha1))
+               return -1; /* end of list */
+       *cmd_list = NULL; /* this returns only one */
+       hashcpy(sha1, cmd->new_sha1);
+       return 0;
+}
+
+static void set_connectivity_errors(struct command *commands)
+{
+       struct command *cmd;
+
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               struct command *singleton = cmd;
+               if (!check_everything_connected(command_singleton_iterator,
+                                               0, &singleton))
+                       continue;
+               cmd->error_string = "missing necessary objects";
        }
+}
 
-       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
+{
+       struct command **cmd_list = cb_data;
+       struct command *cmd = *cmd_list;
 
        while (cmd) {
-               cmd->error_string = update(cmd);
+               if (!is_null_sha1(cmd->new_sha1)) {
+                       hashcpy(sha1, cmd->new_sha1);
+                       *cmd_list = cmd->next;
+                       return 0;
+               }
                cmd = cmd->next;
        }
+       *cmd_list = NULL;
+       return -1; /* end of list */
+}
+
+static void execute_commands(struct command *commands, const char *unpacker_error)
+{
+       struct command *cmd;
+       unsigned char sha1[20];
+
+       if (unpacker_error) {
+               for (cmd = commands; cmd; cmd = cmd->next)
+                       cmd->error_string = "n/a (unpacker error)";
+               return;
+       }
+
+       cmd = commands;
+       if (check_everything_connected(iterate_receive_command_list,
+                                      0, &cmd))
+               set_connectivity_errors(commands);
+
+       if (run_receive_hook(commands, pre_receive_hook, 0)) {
+               for (cmd = commands; cmd; cmd = cmd->next)
+                       cmd->error_string = "pre-receive hook declined";
+               return;
+       }
+
+       check_aliased_updates(commands);
+
+       free(head_name_to_free);
+       head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
+
+       for (cmd = commands; cmd; cmd = cmd->next)
+               if (!cmd->skip_update)
+                       cmd->error_string = update(cmd);
 }
 
-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];
@@ -548,15 +753,14 @@ static void read_head_info(void)
                        if (strstr(refname + reflen + 1, "side-band-64k"))
                                use_sideband = LARGE_PACKET_MAX;
                }
-               cmd = xmalloc(sizeof(struct command) + len - 80);
+               cmd = xcalloc(1, sizeof(struct command) + len - 80);
                hashcpy(cmd->old_sha1, old_sha1);
                hashcpy(cmd->new_sha1, new_sha1);
                memcpy(cmd->ref_name, line + 82, len - 81);
-               cmd->error_string = NULL;
-               cmd->next = NULL;
                *p = cmd;
                p = &cmd->next;
        }
+       return commands;
 }
 
 static const char *parse_pack_header(struct pack_header *hdr)
@@ -586,6 +790,11 @@ static const char *unpack(void)
        struct pack_header hdr;
        const char *hdr_err;
        char hdr_arg[38];
+       int fsck_objects = (receive_fsck_objects >= 0
+                           ? receive_fsck_objects
+                           : transfer_fsck_objects >= 0
+                           ? transfer_fsck_objects
+                           : 0);
 
        hdr_err = parse_pack_header(&hdr);
        if (hdr_err)
@@ -598,7 +807,7 @@ static const char *unpack(void)
                int code, i = 0;
                const char *unpacker[4];
                unpacker[i++] = "unpack-objects";
-               if (receive_fsck_objects)
+               if (fsck_objects)
                        unpacker[i++] = "--strict";
                unpacker[i++] = hdr_arg;
                unpacker[i++] = NULL;
@@ -618,7 +827,7 @@ static const char *unpack(void)
 
                keeper[i++] = "index-pack";
                keeper[i++] = "--stdin";
-               if (receive_fsck_objects)
+               if (fsck_objects)
                        keeper[i++] = "--strict";
                keeper[i++] = "--fix-thin";
                keeper[i++] = hdr_arg;
@@ -643,7 +852,7 @@ 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;
        struct strbuf buf = STRBUF_INIT;
@@ -667,61 +876,25 @@ static void report(const char *unpack_status)
        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;
 }
 
-static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
-{
-       char *other;
-       size_t len;
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
-
-       e->name[-1] = '\0';
-       other = xstrdup(make_absolute_path(e->base));
-       e->name[-1] = '/';
-       len = strlen(other);
-
-       while (other[len-1] == '/')
-               other[--len] = '\0';
-       if (len < 8 || memcmp(other + len - 8, "/objects", 8))
-               return 0;
-       /* Is this a git repository with refs? */
-       memcpy(other + len - 8, "/refs", 6);
-       if (!is_directory(other))
-               return 0;
-       other[len - 8] = '\0';
-       remote = remote_get(other);
-       transport = transport_get(remote, other);
-       for (extra = transport_get_remote_refs(transport);
-            extra;
-            extra = extra->next) {
-               add_extra_ref(".have", extra->old_sha1, 0);
-       }
-       transport_disconnect(transport);
-       free(other);
-       return 0;
-}
-
-static void add_alternate_refs(void)
-{
-       foreach_alt_odb(add_refs_from_alternate, NULL);
-}
-
 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;
+
+       packet_trace_identity("receive-pack");
 
        argv++;
        for (i = 1; i < argc; i++) {
@@ -762,28 +935,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                unpack_limit = receive_unpack_limit;
 
        if (advertise_refs || !stateless_rpc) {
-               add_alternate_refs();
                write_head_info();
-               clear_extra_refs();
-
-               /* 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_or_warn(pack_lockfile);
                if (report_status)
-                       report(unpack_status);
-               run_receive_hook(post_receive_hook);
+                       report(commands, unpack_status);
+               run_receive_hook(commands, post_receive_hook, 1);
                run_update_post_hook(commands);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {