This commit adds support for a 'pre-push' hook that can be called before a `git push` command. It takes no arguments currently, but if the .git/hooks/pre-push script exists and is executable, it will be called before the 'git push' command and will stop the push process if it does not exit with a 0 status. This hook can be overridden by passing in the --no-verify or -n option to git push. Documentation and tests have been updated to reflect the change. Signed-off-by: Scott Chacon <schacon@xxxxxxxxx> --- Documentation/git-push.txt | 11 +++- builtin-push.c | 27 +++++++++- t/t5550-pre-push-hook.sh | 132 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 t/t5550-pre-push-hook.sh diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 45c9643..2b504b3 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git push' [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] - [--repo=all] [-f | --force] [-v | --verbose] + [--repo=all] [-f | --force] [-v | --verbose] [-n | --no-verify] [<repository> <refspec>...] DESCRIPTION @@ -111,6 +111,10 @@ nor in any Push line of the corresponding remotes file---see below). transfer spends extra cycles to minimize the number of objects to be sent and meant to be used on slower connection. +--no-verify:: + This option bypasses the pre-push hook. + See also linkgit:githooks[5]. + -v:: --verbose:: Run verbosely. @@ -193,6 +197,11 @@ git push origin master:refs/heads/experimental:: needed to create a new branch or tag in the remote repository when the local name and the remote name are different; otherwise, the ref name on its own will work. + +HOOKS +----- +This command can run the `pre-push` hook. +See linkgit:githooks[5] for more information. Author ------ diff --git a/builtin-push.c b/builtin-push.c index c1ed68d..f63de9f 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,11 +10,12 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]", + "git push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-n | --no-verify] [-v] [<repository> <refspec>...]", NULL, }; static int thin; +static int skiphook; static const char *receivepack; static const char **refspec; @@ -98,6 +99,24 @@ static int do_push(const char *repo, int flags) return !!errs; } +static int run_hook(const char *name) +{ + struct child_process hook; + const char *argv[1]; + + argv[0] = git_path("hooks/%s", name); + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.no_stdin = 1; + hook.stdout_to_stderr = 1; + + return run_command(&hook); +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -115,6 +134,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), + OPT_BOOLEAN('n', "no-verify", &skiphook, "skip pre-push hook"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), OPT_END() @@ -130,6 +150,11 @@ int cmd_push(int argc, const char **argv, const char *prefix) set_refspecs(argv + 1, argc - 1); } + if (!skiphook && run_hook("pre-push")) { + fprintf(stderr, "pre-push script failed: exiting\n"); + return 128; + } + rc = do_push(repo, flags); if (rc == -1) usage_with_options(push_usage, options); diff --git a/t/t5550-pre-push-hook.sh b/t/t5550-pre-push-hook.sh new file mode 100644 index 0000000..f3c9cce --- /dev/null +++ b/t/t5550-pre-push-hook.sh @@ -0,0 +1,132 @@ +#!/bin/sh + +test_description='pre-push hook' + +. ./test-lib.sh + +D=`pwd` + +mk_repo_pair () { + rm -rf master mirror && + mkdir mirror && + ( + cd mirror && + git init + ) && + mkdir master && + ( + cd master && + git init && + git remote add $1 up ../mirror + ) +} + +test_expect_success 'with no hook' ' + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up + ) +' + +test_expect_success '--no-verify with no hook' ' + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --no-verify --mirror up + ) +' + +# now install hook that always succeeds +HOOKDIR="master/.git/hooks" +HOOK="$HOOKDIR/pre-push" +mk_hook_exec () { + mkdir -p "$HOOKDIR" +cat > "$HOOK" <<EOF +#!/bin/sh +exit 0 +EOF + chmod +x "$HOOK" +} + +test_expect_success 'with succeeding hook' ' + mk_repo_pair && + ( + mk_hook_exec && + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up + ) +' + +test_expect_success '--no-verify with succeeding hook' ' + mk_repo_pair && + ( + mk_hook_exec && + cd master && + echo one >foo && git add foo && git commit -m one && + git push --no-verify --mirror up + ) +' + +# now a hook that fails +mk_hook_fail () { +cat > "$HOOK" <<EOF +#!/bin/sh +echo 'test run' +exit 1 +EOF + chmod +x "$HOOK" +} + +test_expect_success 'with failing hook' ' + mk_repo_pair && + ( + mk_hook_fail && + cd master && + echo one >foo && git add foo && git commit -m one && + test_must_fail git push --mirror up + ) +' + +test_expect_success '--no-verify with failing hook' ' + mk_repo_pair && + ( + mk_hook_fail && + cd master && + echo one >foo && git add foo && git commit -m one && + git push --no-verify --mirror up + ) +' + +chmod -x "$HOOK" +mk_hook_no_exec () { +cat > "$HOOK" <<EOF +#!/bin/sh +echo 'test run' +exit 0 +EOF +} + +test_expect_success 'with non-executable hook' ' + mk_repo_pair && + ( + mk_hook_no_exec && + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up + ) +' + +test_expect_success '--no-verify with non-executable hook' ' + mk_repo_pair && + ( + mk_hook_no_exec && + cd master && + echo one >foo && git add foo && git commit -m one && + git push --no-verify --mirror up + ) +' +test_done -- 1.6.0.GIT -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html