Code

receive-pack: receive.denyDeleteCurrent
authorJunio C Hamano <gitster@pobox.com>
Mon, 9 Feb 2009 06:31:21 +0000 (22:31 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 11 Feb 2009 06:26:49 +0000 (22:26 -0800)
This is a companion patch to the recent 3d95d92 (receive-pack: explain
what to do when push updates the current branch, 2009-01-31).

Deleting the current branch from a remote will result in the next clone
from it not check out anything, among other things.  It also is one of the
cause that makes remotes/origin/HEAD a dangling symbolic ref.  This patch
still allows the traditional behaviour but with a big warning, and promises
that the default will change to 'refuse' in a future release.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin-receive-pack.c
t/t5400-send-pack.sh

index 6de186c397fcd5eaab2331db56546acab5f545f4..1dc373e31036eaf34842c97b63f728ad5cfdb0e9 100644 (file)
@@ -21,11 +21,13 @@ enum deny_action {
 static int deny_deletes = 0;
 static int deny_non_fast_forwards = 0;
 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 const char *head_name;
 
 static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
@@ -77,6 +79,11 @@ 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;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -203,16 +210,12 @@ static int run_update_hook(struct command *cmd)
 
 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 *warn_unconfigured_deny_msg[] = {
@@ -244,6 +247,32 @@ static void warn_unconfigured_deny(void)
                warning(warn_unconfigured_deny_msg[i]);
 }
 
+static char *warn_unconfigured_deny_delete_current_msg[] = {
+       "Deleting the current branch can cause confusion by making the next",
+       "'git clone' not check out any file.",
+       "",
+       "You can set 'receive.denyDeleteCurrent' configuration variable to",
+       "'refuse' in the remote repository to disallow deleting the current",
+       "branch.",
+       "",
+       "You can set it to 'ignore' to allow such a delete without a warning.",
+       "",
+       "To make this warning message less loud, you can set it to 'warn'.",
+       "",
+       "Note that the default will change in a future version of git",
+       "to refuse deleting the current branch unless you have the",
+       "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny_delete_current(void)
+{
+       int i;
+       for (i = 0;
+            i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
+            i++)
+               warning(warn_unconfigured_deny_delete_current_msg[i]);
+}
+
 static const char *update(struct command *cmd)
 {
        const char *name = cmd->ref_name;
@@ -278,12 +307,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/")) {
+                       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:
+                       case DENY_UNCONFIGURED:
+                               if (deny_delete_current == DENY_UNCONFIGURED)
+                                       warn_unconfigured_deny_delete_current();
+                               warning("deleting the current branch");
+                               break;
+                       case DENY_REFUSE:
+                               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/")) {
@@ -377,6 +424,7 @@ static void run_update_post_hook(struct command *cmd)
 static void execute_commands(const char *unpacker_error)
 {
        struct command *cmd = commands;
+       unsigned char sha1[20];
 
        if (unpacker_error) {
                while (cmd) {
@@ -394,6 +442,8 @@ static void execute_commands(const char *unpacker_error)
                return;
        }
 
+       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
        while (cmd) {
                cmd->error_string = update(cmd);
                cmd = cmd->next;
index d6151f885476e9c4f0f90c3c102f9fa41576e74c..f2d5581b12f7d70c9f346da75dced81e12bd4c7f 100755 (executable)
@@ -190,4 +190,17 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
        test "$parent_head" = "$child_head"
 '
 
+test_expect_success 'warn pushing to delete current branch' '
+       rewound_push_setup &&
+       (
+           cd child &&
+           git send-pack ../parent :refs/heads/master 2>errs
+       ) &&
+       grep "warning: to refuse deleting" child/errs &&
+       (
+               cd parent &&
+               test_must_fail git rev-parse --verify master
+       )
+'
+
 test_done