Merge branch 'maint'
[gitweb.git] / builtin-receive-pack.c
index 45e3cd90fd476cdb0a32e5de27739b18e060e031..db67c3162c6b8ffaba793fd6082324aeae158f86 100644 (file)
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
+enum deny_action {
+       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 receive_fsck_objects;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
@@ -21,8 +29,28 @@ 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) {
+               deny_deletes = git_config_bool(var, value);
+               return 0;
+       }
+
        if (strcmp(var, "receive.denynonfastforwards") == 0) {
                deny_non_fast_forwards = git_config_bool(var, value);
                return 0;
@@ -43,6 +71,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);
 }
 
@@ -167,6 +200,20 @@ 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 const char *update(struct command *cmd)
 {
        const char *name = cmd->ref_name;
@@ -180,11 +227,35 @@ static const char *update(struct command *cmd)
                return "funny refname";
        }
 
+       switch (deny_current_branch) {
+       case DENY_IGNORE:
+               break;
+       case DENY_WARN:
+               if (!is_ref_checked_out(name))
+                       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))
+                       break;
+               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));
                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 (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
            !is_null_sha1(old_sha1) &&
            !prefixcmp(name, "refs/heads/")) {
@@ -224,7 +295,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";
                }
@@ -466,12 +537,17 @@ static int delete_only(struct command *cmd)
 
 static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
 {
-       char *other = xstrdup(make_absolute_path(e->base));
-       size_t len = strlen(other);
+       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))