mirror of
https://github.com/git/git.git
synced 2024-11-29 04:54:56 +08:00
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:
commit
72ecc6ef53
@ -2144,6 +2144,13 @@ 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".
|
||||
+
|
||||
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::
|
||||
If set to true, git-receive-pack will deny a ref update which is
|
||||
|
@ -26,7 +26,8 @@ enum deny_action {
|
||||
DENY_UNCONFIGURED,
|
||||
DENY_IGNORE,
|
||||
DENY_WARN,
|
||||
DENY_REFUSE
|
||||
DENY_REFUSE,
|
||||
DENY_UPDATE_INSTEAD
|
||||
};
|
||||
|
||||
static int deny_deletes;
|
||||
@ -76,6 +77,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
|
||||
return DENY_WARN;
|
||||
if (!strcasecmp(value, "refuse"))
|
||||
return DENY_REFUSE;
|
||||
if (!strcasecmp(value, "updateinstead"))
|
||||
return DENY_UPDATE_INSTEAD;
|
||||
}
|
||||
if (git_config_bool(var, value))
|
||||
return DENY_REFUSE;
|
||||
@ -730,11 +733,89 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
|
||||
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)
|
||||
{
|
||||
const char *name = cmd->ref_name;
|
||||
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 *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)
|
||||
refuse_unconfigured_deny();
|
||||
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;
|
||||
case DENY_REFUSE:
|
||||
case DENY_UNCONFIGURED:
|
||||
case DENY_UPDATE_INSTEAD:
|
||||
if (deny_delete_current == DENY_UNCONFIGURED)
|
||||
refuse_unconfigured_deny_delete_current();
|
||||
rp_error("refusing to delete the current branch: %s", name);
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user