Merge branch 'js/push-to-deploy'
"git push" into a repository with a working tree normally refuses to modify the branch that is checked out. The command learned to optionally do an equivalent of "git reset --hard" only when there is no change to the working tree and the index instead, which would be useful to "deploy" by pushing into a repository. * js/push-to-deploy: t5516: more tests for receive.denyCurrentBranch=updateInstead receive-pack: add another option for receive.denyCurrentBranch
This commit is contained in:
@ -2144,6 +2144,13 @@ receive.denyCurrentBranch::
|
|||||||
print a warning of such a push to stderr, but allow the push to
|
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
|
proceed. If set to false or "ignore", allow such pushes with no
|
||||||
message. Defaults to "refuse".
|
message. Defaults to "refuse".
|
||||||
|
+
|
||||||
|
Another option is "updateInstead" which will update the working
|
||||||
|
directory (must be clean) if pushing into the current branch. This option is
|
||||||
|
intended for synchronizing working directories when one side is not easily
|
||||||
|
accessible via interactive ssh (e.g. a live web site, hence the requirement
|
||||||
|
that the working directory be clean). This mode also comes in handy when
|
||||||
|
developing inside a VM to test and fix code on different Operating Systems.
|
||||||
|
|
||||||
receive.denyNonFastForwards::
|
receive.denyNonFastForwards::
|
||||||
If set to true, git-receive-pack will deny a ref update which is
|
If set to true, git-receive-pack will deny a ref update which is
|
||||||
|
@ -26,7 +26,8 @@ enum deny_action {
|
|||||||
DENY_UNCONFIGURED,
|
DENY_UNCONFIGURED,
|
||||||
DENY_IGNORE,
|
DENY_IGNORE,
|
||||||
DENY_WARN,
|
DENY_WARN,
|
||||||
DENY_REFUSE
|
DENY_REFUSE,
|
||||||
|
DENY_UPDATE_INSTEAD
|
||||||
};
|
};
|
||||||
|
|
||||||
static int deny_deletes;
|
static int deny_deletes;
|
||||||
@ -76,6 +77,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
|
|||||||
return DENY_WARN;
|
return DENY_WARN;
|
||||||
if (!strcasecmp(value, "refuse"))
|
if (!strcasecmp(value, "refuse"))
|
||||||
return DENY_REFUSE;
|
return DENY_REFUSE;
|
||||||
|
if (!strcasecmp(value, "updateinstead"))
|
||||||
|
return DENY_UPDATE_INSTEAD;
|
||||||
}
|
}
|
||||||
if (git_config_bool(var, value))
|
if (git_config_bool(var, value))
|
||||||
return DENY_REFUSE;
|
return DENY_REFUSE;
|
||||||
@ -730,11 +733,89 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *update_worktree(unsigned char *sha1)
|
||||||
|
{
|
||||||
|
const char *update_refresh[] = {
|
||||||
|
"update-index", "-q", "--ignore-submodules", "--refresh", NULL
|
||||||
|
};
|
||||||
|
const char *diff_files[] = {
|
||||||
|
"diff-files", "--quiet", "--ignore-submodules", "--", NULL
|
||||||
|
};
|
||||||
|
const char *diff_index[] = {
|
||||||
|
"diff-index", "--quiet", "--cached", "--ignore-submodules",
|
||||||
|
"HEAD", "--", NULL
|
||||||
|
};
|
||||||
|
const char *read_tree[] = {
|
||||||
|
"read-tree", "-u", "-m", NULL, NULL
|
||||||
|
};
|
||||||
|
const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
|
||||||
|
struct argv_array env = ARGV_ARRAY_INIT;
|
||||||
|
struct child_process child = CHILD_PROCESS_INIT;
|
||||||
|
|
||||||
|
if (is_bare_repository())
|
||||||
|
return "denyCurrentBranch = updateInstead needs a worktree";
|
||||||
|
|
||||||
|
argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
|
||||||
|
|
||||||
|
child.argv = update_refresh;
|
||||||
|
child.env = env.argv;
|
||||||
|
child.dir = work_tree;
|
||||||
|
child.no_stdin = 1;
|
||||||
|
child.stdout_to_stderr = 1;
|
||||||
|
child.git_cmd = 1;
|
||||||
|
if (run_command(&child)) {
|
||||||
|
argv_array_clear(&env);
|
||||||
|
return "Up-to-date check failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* run_command() does not clean up completely; reinitialize */
|
||||||
|
child_process_init(&child);
|
||||||
|
child.argv = diff_files;
|
||||||
|
child.env = env.argv;
|
||||||
|
child.dir = work_tree;
|
||||||
|
child.no_stdin = 1;
|
||||||
|
child.stdout_to_stderr = 1;
|
||||||
|
child.git_cmd = 1;
|
||||||
|
if (run_command(&child)) {
|
||||||
|
argv_array_clear(&env);
|
||||||
|
return "Working directory has unstaged changes";
|
||||||
|
}
|
||||||
|
|
||||||
|
child_process_init(&child);
|
||||||
|
child.argv = diff_index;
|
||||||
|
child.env = env.argv;
|
||||||
|
child.no_stdin = 1;
|
||||||
|
child.no_stdout = 1;
|
||||||
|
child.stdout_to_stderr = 0;
|
||||||
|
child.git_cmd = 1;
|
||||||
|
if (run_command(&child)) {
|
||||||
|
argv_array_clear(&env);
|
||||||
|
return "Working directory has staged changes";
|
||||||
|
}
|
||||||
|
|
||||||
|
read_tree[3] = sha1_to_hex(sha1);
|
||||||
|
child_process_init(&child);
|
||||||
|
child.argv = read_tree;
|
||||||
|
child.env = env.argv;
|
||||||
|
child.dir = work_tree;
|
||||||
|
child.no_stdin = 1;
|
||||||
|
child.no_stdout = 1;
|
||||||
|
child.stdout_to_stderr = 0;
|
||||||
|
child.git_cmd = 1;
|
||||||
|
if (run_command(&child)) {
|
||||||
|
argv_array_clear(&env);
|
||||||
|
return "Could not update working tree to new HEAD";
|
||||||
|
}
|
||||||
|
|
||||||
|
argv_array_clear(&env);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *update(struct command *cmd, struct shallow_info *si)
|
static const char *update(struct command *cmd, struct shallow_info *si)
|
||||||
{
|
{
|
||||||
const char *name = cmd->ref_name;
|
const char *name = cmd->ref_name;
|
||||||
struct strbuf namespaced_name_buf = STRBUF_INIT;
|
struct strbuf namespaced_name_buf = STRBUF_INIT;
|
||||||
const char *namespaced_name;
|
const char *namespaced_name, *ret;
|
||||||
unsigned char *old_sha1 = cmd->old_sha1;
|
unsigned char *old_sha1 = cmd->old_sha1;
|
||||||
unsigned char *new_sha1 = cmd->new_sha1;
|
unsigned char *new_sha1 = cmd->new_sha1;
|
||||||
|
|
||||||
@ -760,6 +841,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
|
|||||||
if (deny_current_branch == DENY_UNCONFIGURED)
|
if (deny_current_branch == DENY_UNCONFIGURED)
|
||||||
refuse_unconfigured_deny();
|
refuse_unconfigured_deny();
|
||||||
return "branch is currently checked out";
|
return "branch is currently checked out";
|
||||||
|
case DENY_UPDATE_INSTEAD:
|
||||||
|
ret = update_worktree(new_sha1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,10 +870,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)
|
|||||||
break;
|
break;
|
||||||
case DENY_REFUSE:
|
case DENY_REFUSE:
|
||||||
case DENY_UNCONFIGURED:
|
case DENY_UNCONFIGURED:
|
||||||
|
case DENY_UPDATE_INSTEAD:
|
||||||
if (deny_delete_current == DENY_UNCONFIGURED)
|
if (deny_delete_current == DENY_UNCONFIGURED)
|
||||||
refuse_unconfigured_deny_delete_current();
|
refuse_unconfigured_deny_delete_current();
|
||||||
rp_error("refusing to delete the current branch: %s", name);
|
rp_error("refusing to delete the current branch: %s", name);
|
||||||
return "deletion of the current branch prohibited";
|
return "deletion of the current branch prohibited";
|
||||||
|
default:
|
||||||
|
return "Invalid denyDeleteCurrent setting";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1330,4 +1330,108 @@ test_expect_success 'fetch into bare respects core.logallrefupdates' '
|
|||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'receive.denyCurrentBranch = updateInstead' '
|
||||||
|
git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git reset --hard &&
|
||||||
|
git config receive.denyCurrentBranch updateInstead
|
||||||
|
) &&
|
||||||
|
test_commit third path2 &&
|
||||||
|
|
||||||
|
# Try pushing into a repository with pristine working tree
|
||||||
|
git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git update-index -q --refresh &&
|
||||||
|
git diff-files --quiet -- &&
|
||||||
|
git diff-index --quiet --cached HEAD -- &&
|
||||||
|
test third = "$(cat path2)" &&
|
||||||
|
test $(git -C .. rev-parse HEAD) = $(git rev-parse HEAD)
|
||||||
|
) &&
|
||||||
|
|
||||||
|
# Try pushing into a repository with working tree needing a refresh
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git reset --hard HEAD^ &&
|
||||||
|
test $(git -C .. rev-parse HEAD^) = $(git rev-parse HEAD) &&
|
||||||
|
test-chmtime +100 path1
|
||||||
|
) &&
|
||||||
|
git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git update-index -q --refresh &&
|
||||||
|
git diff-files --quiet -- &&
|
||||||
|
git diff-index --quiet --cached HEAD -- &&
|
||||||
|
test_cmp ../path1 path1 &&
|
||||||
|
test third = "$(cat path2)" &&
|
||||||
|
test $(git -C .. rev-parse HEAD) = $(git rev-parse HEAD)
|
||||||
|
) &&
|
||||||
|
|
||||||
|
# Update what is to be pushed
|
||||||
|
test_commit fourth path2 &&
|
||||||
|
|
||||||
|
# Try pushing into a repository with a dirty working tree
|
||||||
|
# (1) the working tree updated
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
echo changed >path1
|
||||||
|
) &&
|
||||||
|
test_must_fail git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
test $(git -C .. rev-parse HEAD^) = $(git rev-parse HEAD) &&
|
||||||
|
git diff --quiet --cached &&
|
||||||
|
test changed = "$(cat path1)"
|
||||||
|
) &&
|
||||||
|
|
||||||
|
# (2) the index updated
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
echo changed >path1 &&
|
||||||
|
git add path1
|
||||||
|
) &&
|
||||||
|
test_must_fail git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
test $(git -C .. rev-parse HEAD^) = $(git rev-parse HEAD) &&
|
||||||
|
git diff --quiet &&
|
||||||
|
test changed = "$(cat path1)"
|
||||||
|
) &&
|
||||||
|
|
||||||
|
# Introduce a new file in the update
|
||||||
|
test_commit fifth path3 &&
|
||||||
|
|
||||||
|
# (3) the working tree has an untracked file that would interfere
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git reset --hard &&
|
||||||
|
echo changed >path3
|
||||||
|
) &&
|
||||||
|
test_must_fail git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
test $(git -C .. rev-parse HEAD^^) = $(git rev-parse HEAD) &&
|
||||||
|
git diff --quiet &&
|
||||||
|
git diff --quiet --cached &&
|
||||||
|
test changed = "$(cat path3)"
|
||||||
|
) &&
|
||||||
|
|
||||||
|
# (4) the target changes to what gets pushed but it still is a change
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
git reset --hard &&
|
||||||
|
echo fifth >path3 &&
|
||||||
|
git add path3
|
||||||
|
) &&
|
||||||
|
test_must_fail git push testrepo master &&
|
||||||
|
(
|
||||||
|
cd testrepo &&
|
||||||
|
test $(git -C .. rev-parse HEAD^^) = $(git rev-parse HEAD) &&
|
||||||
|
git diff --quiet &&
|
||||||
|
test fifth = "$(cat path3)"
|
||||||
|
)
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Reference in New Issue
Block a user