git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] add a 'pre-push' hook

From
Scott Chacon <schacon@gmail.com>
Date
Aug 19, 2008, 18:55 UTC
Message-ID
<d411cc4a0808191155g188b0f10je5fd79afb92f36ef@mail.gmail.com>
In-Reply-To
<1219170876-46893-1-git-send-email-schacon@gmail.com>

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@gmail.com>
---
 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
Next: Jeff King
Message 1 of 15 in “add a 'pre-push' hook”
  1. add a 'pre-push' hookScott Chacon, Aug 19, 2008
  2. Jeff KingAug 19, 2008
  3. Scott ChaconAug 19, 2008
  4. Jeff KingAug 19, 2008
  5. Shawn O. PearceAug 19, 2008
  6. Scott ChaconAug 19, 2008
  7. しらいしななこAug 19, 2008
  8. Scott ChaconAug 19, 2008
  9. Junio C HamanoAug 19, 2008
  10. Sam VilainAug 19, 2008
  11. Jeff KingAug 20, 2008
  12. Junio C HamanoAug 19, 2008
  13. Scott ChaconAug 19, 2008
  14. Junio C HamanoAug 19, 2008
  15. Shawn O. PearceAug 19, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.