lstat_cache(): print a warning if doing ping-pong between cache types
[gitweb.git] / builtin-receive-pack.c
index 2c0225c89a44a28150806608ea3956c44e27ef6f..6de186c397fcd5eaab2331db56546acab5f545f4 100644 (file)
@@ -9,10 +9,18 @@
 #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_UNCONFIGURED;
 static int receive_fsck_objects;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
@@ -22,6 +30,21 @@ static int report_status;
 static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
 
+static enum deny_action parse_deny_action(const char *var, const char *value)
+{
+       if (value) {
+               if (!strcasecmp(value, "ignore"))
+                       return DENY_IGNORE;
+               if (!strcasecmp(value, "warn"))
+                       return DENY_WARN;
+               if (!strcasecmp(value, "refuse"))
+                       return DENY_REFUSE;
+       }
+       if (git_config_bool(var, value))
+               return DENY_REFUSE;
+       return DENY_IGNORE;
+}
+
 static int receive_pack_config(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "receive.denydeletes") == 0) {
@@ -49,6 +72,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!strcmp(var, "receive.denycurrentbranch")) {
+               deny_current_branch = parse_deny_action(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -109,7 +137,7 @@ static int hook_status(int code, const char *hook_name)
        }
 }
 
-static int run_hook(const char *hook_name)
+static int run_receive_hook(const char *hook_name)
 {
        static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
        struct command *cmd;
@@ -173,6 +201,49 @@ static int run_update_hook(struct command *cmd)
        return hook_status(run_command(&proc), update_hook);
 }
 
+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)
+               return 0;
+       return !strcmp(head, ref);
+}
+
+static char *warn_unconfigured_deny_msg[] = {
+       "Updating the currently checked out branch may cause confusion,",
+       "as the index and work tree do not reflect changes that are in HEAD.",
+       "As a result, you may see the changes you just pushed into it",
+       "reverted when you run 'git diff' over there, and you may want",
+       "to run 'git reset --hard' before starting to work to recover.",
+       "",
+       "You can set 'receive.denyCurrentBranch' configuration variable to",
+       "'refuse' in the remote repository to forbid pushing into its",
+       "current branch."
+       "",
+       "To allow pushing into the current branch, you can set it to 'ignore';",
+       "but 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, you can set it to 'warn'.",
+       "",
+       "Note that the default will change in a future version of git",
+       "to refuse updating the current branch unless you have the",
+       "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
+               warning(warn_unconfigured_deny_msg[i]);
+}
+
 static const char *update(struct command *cmd)
 {
        const char *name = cmd->ref_name;
@@ -186,6 +257,22 @@ static const char *update(struct command *cmd)
                return "funny refname";
        }
 
+       if (is_ref_checked_out(name)) {
+               switch (deny_current_branch) {
+               case DENY_IGNORE:
+                       break;
+               case DENY_UNCONFIGURED:
+               case DENY_WARN:
+                       warning("updating the current branch");
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               warn_unconfigured_deny();
+                       break;
+               case DENY_REFUSE:
+                       error("refusing to update checked out branch: %s", name);
+                       return "branch is currently checked out";
+               }
+       }
+
        if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
                error("unpack should have generated %s, "
                      "but I can't find it!", sha1_to_hex(new_sha1));
@@ -236,7 +323,7 @@ static const char *update(struct command *cmd)
                        warning ("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
                }
-               if (delete_ref(name, old_sha1)) {
+               if (delete_ref(name, old_sha1, 0)) {
                        error("failed to delete %s", name);
                        return "failed to delete";
                }
@@ -299,7 +386,7 @@ static void execute_commands(const char *unpacker_error)
                return;
        }
 
-       if (run_hook(pre_receive_hook)) {
+       if (run_receive_hook(pre_receive_hook)) {
                while (cmd) {
                        cmd->error_string = "pre-receive hook declined";
                        cmd = cmd->next;
@@ -568,7 +655,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        unlink(pack_lockfile);
                if (report_status)
                        report(unpack_status);
-               run_hook(post_receive_hook);
+               run_receive_hook(post_receive_hook);
                run_update_post_hook(commands);
        }
        return 0;