Skip to content

Commit 1404bcb

Browse files
dschogitster
authored andcommitted
receive-pack: add another option for receive.denyCurrentBranch
When synchronizing between working directories, it can be handy to update the current branch via 'push' rather than 'pull', e.g. when pushing a fix from inside a VM, or when pushing a fix made on a user's machine (where the developer is not at liberty to install an ssh daemon let alone know the user's password). The common workaround – pushing into a temporary branch and then merging on the other machine – is no longer necessary with this patch. The new option is: 'updateInstead': Update the working tree accordingly, but refuse to do so if there are any uncommitted changes. Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 66edfe9 commit 1404bcb

File tree

3 files changed

+124
-2
lines changed

3 files changed

+124
-2
lines changed

Documentation/config.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,13 @@ receive.denyCurrentBranch::
21292129
print a warning of such a push to stderr, but allow the push to
21302130
proceed. If set to false or "ignore", allow such pushes with no
21312131
message. Defaults to "refuse".
2132+
+
2133+
Another option is "updateInstead" which will update the working
2134+
directory (must be clean) if pushing into the current branch. This option is
2135+
intended for synchronizing working directories when one side is not easily
2136+
accessible via interactive ssh (e.g. a live web site, hence the requirement
2137+
that the working directory be clean). This mode also comes in handy when
2138+
developing inside a VM to test and fix code on different Operating Systems.
21322139

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

builtin/receive-pack.c

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ enum deny_action {
2626
DENY_UNCONFIGURED,
2727
DENY_IGNORE,
2828
DENY_WARN,
29-
DENY_REFUSE
29+
DENY_REFUSE,
30+
DENY_UPDATE_INSTEAD
3031
};
3132

3233
static int deny_deletes;
@@ -76,6 +77,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
7677
return DENY_WARN;
7778
if (!strcasecmp(value, "refuse"))
7879
return DENY_REFUSE;
80+
if (!strcasecmp(value, "updateinstead"))
81+
return DENY_UPDATE_INSTEAD;
7982
}
8083
if (git_config_bool(var, value))
8184
return DENY_REFUSE;
@@ -730,11 +733,89 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
730733
return 0;
731734
}
732735

736+
static const char *update_worktree(unsigned char *sha1)
737+
{
738+
const char *update_refresh[] = {
739+
"update-index", "-q", "--ignore-submodules", "--refresh", NULL
740+
};
741+
const char *diff_files[] = {
742+
"diff-files", "--quiet", "--ignore-submodules", "--", NULL
743+
};
744+
const char *diff_index[] = {
745+
"diff-index", "--quiet", "--cached", "--ignore-submodules",
746+
"HEAD", "--", NULL
747+
};
748+
const char *read_tree[] = {
749+
"read-tree", "-u", "-m", NULL, NULL
750+
};
751+
const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
752+
struct argv_array env = ARGV_ARRAY_INIT;
753+
struct child_process child = CHILD_PROCESS_INIT;
754+
755+
if (is_bare_repository())
756+
return "denyCurrentBranch = updateInstead needs a worktree";
757+
758+
argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
759+
760+
child.argv = update_refresh;
761+
child.env = env.argv;
762+
child.dir = work_tree;
763+
child.no_stdin = 1;
764+
child.stdout_to_stderr = 1;
765+
child.git_cmd = 1;
766+
if (run_command(&child)) {
767+
argv_array_clear(&env);
768+
return "Up-to-date check failed";
769+
}
770+
771+
/* run_command() does not clean up completely; reinitialize */
772+
child_process_init(&child);
773+
child.argv = diff_files;
774+
child.env = env.argv;
775+
child.dir = work_tree;
776+
child.no_stdin = 1;
777+
child.stdout_to_stderr = 1;
778+
child.git_cmd = 1;
779+
if (run_command(&child)) {
780+
argv_array_clear(&env);
781+
return "Working directory has unstaged changes";
782+
}
783+
784+
child_process_init(&child);
785+
child.argv = diff_index;
786+
child.env = env.argv;
787+
child.no_stdin = 1;
788+
child.no_stdout = 1;
789+
child.stdout_to_stderr = 0;
790+
child.git_cmd = 1;
791+
if (run_command(&child)) {
792+
argv_array_clear(&env);
793+
return "Working directory has staged changes";
794+
}
795+
796+
read_tree[3] = sha1_to_hex(sha1);
797+
child_process_init(&child);
798+
child.argv = read_tree;
799+
child.env = env.argv;
800+
child.dir = work_tree;
801+
child.no_stdin = 1;
802+
child.no_stdout = 1;
803+
child.stdout_to_stderr = 0;
804+
child.git_cmd = 1;
805+
if (run_command(&child)) {
806+
argv_array_clear(&env);
807+
return "Could not update working tree to new HEAD";
808+
}
809+
810+
argv_array_clear(&env);
811+
return NULL;
812+
}
813+
733814
static const char *update(struct command *cmd, struct shallow_info *si)
734815
{
735816
const char *name = cmd->ref_name;
736817
struct strbuf namespaced_name_buf = STRBUF_INIT;
737-
const char *namespaced_name;
818+
const char *namespaced_name, *ret;
738819
unsigned char *old_sha1 = cmd->old_sha1;
739820
unsigned char *new_sha1 = cmd->new_sha1;
740821

@@ -760,6 +841,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
760841
if (deny_current_branch == DENY_UNCONFIGURED)
761842
refuse_unconfigured_deny();
762843
return "branch is currently checked out";
844+
case DENY_UPDATE_INSTEAD:
845+
ret = update_worktree(new_sha1);
846+
if (ret)
847+
return ret;
848+
break;
763849
}
764850
}
765851

@@ -784,10 +870,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)
784870
break;
785871
case DENY_REFUSE:
786872
case DENY_UNCONFIGURED:
873+
case DENY_UPDATE_INSTEAD:
787874
if (deny_delete_current == DENY_UNCONFIGURED)
788875
refuse_unconfigured_deny_delete_current();
789876
rp_error("refusing to delete the current branch: %s", name);
790877
return "deletion of the current branch prohibited";
878+
default:
879+
return "Invalid denyDeleteCurrent setting";
791880
}
792881
}
793882
}

t/t5516-fetch-push.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,4 +1330,30 @@ test_expect_success 'fetch into bare respects core.logallrefupdates' '
13301330
)
13311331
'
13321332

1333+
test_expect_success 'receive.denyCurrentBranch = updateInstead' '
1334+
git push testrepo master &&
1335+
(cd testrepo &&
1336+
git reset --hard &&
1337+
git config receive.denyCurrentBranch updateInstead
1338+
) &&
1339+
test_commit third path2 &&
1340+
git push testrepo master &&
1341+
test $(git rev-parse HEAD) = $(cd testrepo && git rev-parse HEAD) &&
1342+
test third = "$(cat testrepo/path2)" &&
1343+
(cd testrepo &&
1344+
git update-index -q --refresh &&
1345+
git diff-files --quiet -- &&
1346+
git diff-index --quiet --cached HEAD -- &&
1347+
echo changed >path2 &&
1348+
git add path2
1349+
) &&
1350+
test_commit fourth path2 &&
1351+
test_must_fail git push testrepo master &&
1352+
test $(git rev-parse HEAD^) = $(git -C testrepo rev-parse HEAD) &&
1353+
(cd testrepo &&
1354+
git diff --quiet &&
1355+
test changed = "$(cat path2)"
1356+
)
1357+
'
1358+
13331359
test_done

0 commit comments

Comments
 (0)