even if that push is forced. This configuration variable is
set when initializing a shared repository.
+receive.denyCurrentBranch::
+ If set to true or "refuse", receive-pack will deny a ref update
+ to the currently checked out branch of a non-bare repository.
+ Such a push is potentially dangerous because it brings the HEAD
+ out of sync with the index and working tree. If set to "warn",
+ print a warning of such a push to stderr, but allow the push to
+ proceed. If set to false or "ignore", allow such pushes with no
+ message. Defaults to "warn".
+
transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are
not set, the value of this variable is used instead.
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;
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) {
return 0;
}
+ if (!strcmp(var, "receive.denycurrentbranch")) {
+ deny_current_branch = parse_deny_action(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
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;
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));
'
+test_expect_success 'warn on push to HEAD of non-bare repository' '
+ mk_test heads/master
+ (cd testrepo &&
+ git checkout master &&
+ git config receive.denyCurrentBranch warn) &&
+ git push testrepo master 2>stderr &&
+ grep "warning.*this may cause confusion" stderr
+'
+
+test_expect_success 'deny push to HEAD of non-bare repository' '
+ mk_test heads/master
+ (cd testrepo &&
+ git checkout master &&
+ git config receive.denyCurrentBranch true) &&
+ test_must_fail git push testrepo master
+'
+
+test_expect_success 'allow push to HEAD of bare repository (bare)' '
+ mk_test heads/master
+ (cd testrepo &&
+ git checkout master &&
+ git config receive.denyCurrentBranch true &&
+ git config core.bare true) &&
+ git push testrepo master 2>stderr &&
+ ! grep "warning.*this may cause confusion" stderr
+'
+
+test_expect_success 'allow push to HEAD of non-bare repository (config)' '
+ mk_test heads/master
+ (cd testrepo &&
+ git checkout master &&
+ git config receive.denyCurrentBranch false
+ ) &&
+ git push testrepo master 2>stderr &&
+ ! grep "warning.*this may cause confusion" stderr
+'
+
test_done