Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 528a03f

Browse files
dschokasal
authored andcommitted
Add a few more values for receive.denyCurrentBranch
For a long time, this developer thought that Git's insistence that pushing into the current branch is evil was completely merited. Just for fun, the original patch tried to show people that Git is correct to forbid that, and that it causes more trouble than it does good when Git allows you to try to update the working tree for fast-forwards, or to detach the HEAD, depending on some config settings. To the developer's surprise, the opposite was shown. So here is the support for two new options you can give the config variable receive.denyCurrentBranch: 'updateInstead': Try to merge the working tree with the new tip of the branch (which can lead to really horrible merge conflicts). 'detachInstead': Detach the HEAD, thereby avoiding a disagreement between the HEAD and the index (as well as the working tree), possibly leaving the local user wondering how on earth her HEAD became so detached. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent ec765f6 commit 528a03f

File tree

3 files changed

+97
-2
lines changed

3 files changed

+97
-2
lines changed

Documentation/config.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,11 @@ receive.denyCurrentBranch::
20692069
print a warning of such a push to stderr, but allow the push to
20702070
proceed. If set to false or "ignore", allow such pushes with no
20712071
message. Defaults to "refuse".
2072+
+
2073+
There are two more options that are meant for Git experts: "updateInstead"
2074+
which will run `read-tree -u -m HEAD` and "detachInstead" which will detach
2075+
the HEAD so it does not need to change. Both options come with their own
2076+
set of possible *complications*, but can be appropriate in rare workflows.
20722077

20732078
receive.denyNonFastForwards::
20742079
If set to true, git-receive-pack will deny a ref update which is

builtin/receive-pack.c

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ enum deny_action {
2222
DENY_UNCONFIGURED,
2323
DENY_IGNORE,
2424
DENY_WARN,
25-
DENY_REFUSE
25+
DENY_REFUSE,
26+
DENY_UPDATE_INSTEAD,
27+
DENY_DETACH_INSTEAD,
2628
};
2729

2830
static int deny_deletes;
@@ -100,7 +102,12 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
100102
}
101103

102104
if (!strcmp(var, "receive.denycurrentbranch")) {
103-
deny_current_branch = parse_deny_action(var, value);
105+
if (value && !strcasecmp(value, "updateinstead"))
106+
deny_current_branch = DENY_UPDATE_INSTEAD;
107+
else if (value && !strcasecmp(value, "detachinstead"))
108+
deny_current_branch = DENY_DETACH_INSTEAD;
109+
else
110+
deny_current_branch = parse_deny_action(var, value);
104111
return 0;
105112
}
106113

@@ -468,6 +475,44 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
468475
return 0;
469476
}
470477

478+
static void merge_worktree(unsigned char *sha1)
479+
{
480+
const char *update_refresh[] = {
481+
"update-index", "--refresh", NULL
482+
};
483+
const char *read_tree[] = {
484+
"read-tree", "-u", "-m", sha1_to_hex(sha1), NULL
485+
};
486+
struct child_process child;
487+
struct strbuf git_env = STRBUF_INIT;
488+
const char *env[2];
489+
490+
if (is_bare_repository())
491+
die ("denyCurrentBranch = updateInstead needs a worktree");
492+
493+
strbuf_addf(&git_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
494+
env[0] = git_env.buf;
495+
env[1] = NULL;
496+
497+
memset(&child, 0, sizeof(child));
498+
child.argv = update_refresh;
499+
child.env = env;
500+
child.dir = git_work_tree_cfg ? git_work_tree_cfg : "..";
501+
child.stdout_to_stderr = 1;
502+
child.git_cmd = 1;
503+
if (run_command(&child))
504+
die ("Could not refresh the index");
505+
506+
child.argv = read_tree;
507+
child.no_stdin = 1;
508+
child.no_stdout = 1;
509+
child.stdout_to_stderr = 0;
510+
if (run_command(&child))
511+
die ("Could not merge working tree with new HEAD. Good luck.");
512+
513+
strbuf_release(&git_env);
514+
}
515+
471516
static const char *update(struct command *cmd, struct shallow_info *si)
472517
{
473518
const char *name = cmd->ref_name;
@@ -499,6 +544,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)
499544
if (deny_current_branch == DENY_UNCONFIGURED)
500545
refuse_unconfigured_deny();
501546
return "branch is currently checked out";
547+
case DENY_UPDATE_INSTEAD:
548+
merge_worktree(new_sha1);
549+
break;
550+
case DENY_DETACH_INSTEAD:
551+
update_ref("push into current branch (detach)", "HEAD",
552+
old_sha1, NULL, REF_NODEREF, DIE_ON_ERR);
553+
break;
502554
}
503555
}
504556

@@ -527,6 +579,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
527579
refuse_unconfigured_deny_delete_current();
528580
rp_error("refusing to delete the current branch: %s", name);
529581
return "deletion of the current branch prohibited";
582+
default:
583+
die ("Invalid denyDeleteCurrent setting");
530584
}
531585
}
532586
}

t/t5516-fetch-push.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,4 +1277,40 @@ EOF
12771277
git push --no-thin --receive-pack="$rcvpck" no-thin/.git refs/heads/master:refs/heads/foo
12781278
'
12791279

1280+
test_expect_success 'receive.denyCurrentBranch = updateInstead' '
1281+
git push testrepo master &&
1282+
(cd testrepo &&
1283+
git reset --hard &&
1284+
git config receive.denyCurrentBranch updateInstead
1285+
) &&
1286+
test_commit third path2 &&
1287+
git push testrepo master &&
1288+
test $(git rev-parse HEAD) = $(cd testrepo && git rev-parse HEAD) &&
1289+
test third = "$(cat testrepo/path2)" &&
1290+
(cd testrepo &&
1291+
git update-index --refresh &&
1292+
git diff-files --quiet &&
1293+
git diff-index --cached HEAD --
1294+
)
1295+
'
1296+
1297+
test_expect_success 'receive.denyCurrentBranch = detachInstead' '
1298+
(cd testrepo &&
1299+
git reset --hard &&
1300+
git config receive.denyCurrentBranch detachInstead
1301+
) &&
1302+
OLDHEAD=$(cd testrepo && git rev-parse HEAD) &&
1303+
test_commit fourth path2 &&
1304+
test fourth = "$(cat path2)" &&
1305+
git push testrepo master &&
1306+
test $OLDHEAD = $(cd testrepo && git rev-parse HEAD) &&
1307+
test fourth != "$(cat testrepo/path2)" &&
1308+
(cd testrepo &&
1309+
test_must_fail git symbolic-ref HEAD &&
1310+
git update-index --refresh &&
1311+
git diff-files --quiet &&
1312+
git diff-index --cached HEAD --
1313+
)
1314+
'
1315+
12801316
test_done

0 commit comments

Comments
 (0)