From 49cda4cad01d6b8fd0795a9c09d98ed228eaeca9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Feb 2009 21:52:51 +0100 Subject: [PATCH 1/2] 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 --- Documentation/config.txt | 5 ++++ builtin/receive-pack.c | 58 ++++++++++++++++++++++++++++++++++++++-- t/t5516-fetch-push.sh | 36 +++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index c26a7c8469..b367df0216 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2010,6 +2010,11 @@ receive.denyCurrentBranch:: 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 "refuse". ++ +There are two more options that are meant for Git experts: "updateInstead" +which will run `read-tree -u -m HEAD` and "detachInstead" which will detach +the HEAD so it does not need to change. Both options come with their own +set of possible *complications*, but can be appropriate in rare workflows. receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c3230817db..d64409321c 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -22,7 +22,9 @@ enum deny_action { DENY_UNCONFIGURED, DENY_IGNORE, DENY_WARN, - DENY_REFUSE + DENY_REFUSE, + DENY_UPDATE_INSTEAD, + DENY_DETACH_INSTEAD, }; static int deny_deletes; @@ -100,7 +102,12 @@ static int receive_pack_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "receive.denycurrentbranch")) { - deny_current_branch = parse_deny_action(var, value); + if (value && !strcasecmp(value, "updateinstead")) + deny_current_branch = DENY_UPDATE_INSTEAD; + else if (value && !strcasecmp(value, "detachinstead")) + deny_current_branch = DENY_DETACH_INSTEAD; + else + deny_current_branch = parse_deny_action(var, value); return 0; } @@ -468,6 +475,44 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) return 0; } +static void merge_worktree(unsigned char *sha1) +{ + const char *update_refresh[] = { + "update-index", "--refresh", NULL + }; + const char *read_tree[] = { + "read-tree", "-u", "-m", sha1_to_hex(sha1), NULL + }; + struct child_process child; + struct strbuf git_env = STRBUF_INIT; + const char *env[2]; + + if (is_bare_repository()) + die ("denyCurrentBranch = updateInstead needs a worktree"); + + strbuf_addf(&git_env, "GIT_DIR=%s", absolute_path(get_git_dir())); + env[0] = git_env.buf; + env[1] = NULL; + + memset(&child, 0, sizeof(child)); + child.argv = update_refresh; + child.env = env; + child.dir = git_work_tree_cfg ? git_work_tree_cfg : ".."; + child.stdout_to_stderr = 1; + child.git_cmd = 1; + if (run_command(&child)) + die ("Could not refresh the index"); + + child.argv = read_tree; + child.no_stdin = 1; + child.no_stdout = 1; + child.stdout_to_stderr = 0; + if (run_command(&child)) + die ("Could not merge working tree with new HEAD. Good luck."); + + strbuf_release(&git_env); +} + static const char *update(struct command *cmd, struct shallow_info *si) { const char *name = cmd->ref_name; @@ -499,6 +544,13 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (deny_current_branch == DENY_UNCONFIGURED) refuse_unconfigured_deny(); return "branch is currently checked out"; + case DENY_UPDATE_INSTEAD: + merge_worktree(new_sha1); + break; + case DENY_DETACH_INSTEAD: + update_ref("push into current branch (detach)", "HEAD", + old_sha1, NULL, REF_NODEREF, DIE_ON_ERR); + break; } } @@ -527,6 +579,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) refuse_unconfigured_deny_delete_current(); rp_error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; + default: + die ("Invalid denyDeleteCurrent setting"); } } } diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 67e0ab3462..0df3210693 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1277,4 +1277,40 @@ EOF git push --no-thin --receive-pack="$rcvpck" no-thin/.git refs/heads/master:refs/heads/foo ' +test_expect_success 'receive.denyCurrentBranch = updateInstead' ' + git push testrepo master && + (cd testrepo && + git reset --hard && + git config receive.denyCurrentBranch updateInstead + ) && + test_commit third path2 && + git push testrepo master && + test $(git rev-parse HEAD) = $(cd testrepo && git rev-parse HEAD) && + test third = "$(cat testrepo/path2)" && + (cd testrepo && + git update-index --refresh && + git diff-files --quiet && + git diff-index --cached HEAD -- + ) +' + +test_expect_success 'receive.denyCurrentBranch = detachInstead' ' + (cd testrepo && + git reset --hard && + git config receive.denyCurrentBranch detachInstead + ) && + OLDHEAD=$(cd testrepo && git rev-parse HEAD) && + test_commit fourth path2 && + test fourth = "$(cat path2)" && + git push testrepo master && + test $OLDHEAD = $(cd testrepo && git rev-parse HEAD) && + test fourth != "$(cat testrepo/path2)" && + (cd testrepo && + test_must_fail git symbolic-ref HEAD && + git update-index --refresh && + git diff-files --quiet && + git diff-index --cached HEAD -- + ) +' + test_done From 298a107b1c3661304550f25b2560363083db9005 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 23 Oct 2010 08:06:23 -0700 Subject: [PATCH 2/2] Let deny.currentBranch=updateInstead ignore submodules They are not affected by the update anyway. Signed-off-by: Johannes Schindelin --- builtin/receive-pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d64409321c..83fbececf5 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -478,7 +478,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) static void merge_worktree(unsigned char *sha1) { const char *update_refresh[] = { - "update-index", "--refresh", NULL + "update-index", "--ignore-submodules", "--refresh", NULL }; const char *read_tree[] = { "read-tree", "-u", "-m", sha1_to_hex(sha1), NULL