git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/4] pre-push hook support
@ 2012-12-28 22:57 Aaron Schrab
  2012-12-28 22:57 ` [PATCH 1/4] hooks: Add function to check if a hook exists Aaron Schrab
                   ` (8 more replies)
  0 siblings, 9 replies; 22+ messages in thread
From: Aaron Schrab @ 2012-12-28 22:57 UTC (permalink / raw)
  To: git

There have been at least a couple of submissions to add support for a
pre-push hook, which were rejected at least partially because they didn't
provide enough information to a hook script for it to determine what was
to be pushed any better than a separate wrapper around the 'git push'
command would be able to do.  In this series I attempt to address that
problem.

The first two patches in this series do a little bit of refactoring in
order to make it easier to call hooks with a variable number of arguments.

The third patch actually adds support for calling a pre-push hook.  If it
exists, it will be called with the name and URL of the destination remote
(if a named remote isn't being used, the URL will be supplied for both)
followed by another argument for each ref being pushed; these arguments
take the form:

  <local ref>:<local sha1>:<remote ref>:<remote sha1>

This should provide enough information for a script to easily determine
the set of commits that is being pushed, and thus make a decision if that
should be allowed.

The final patch adds a sample pre-push hook script which will deny
attempts to push commits that are marked as a work in progress.

Aaron Schrab (4):
  hooks: Add function to check if a hook exists
  hooks: support variable number of parameters
  push: Add support for pre-push hooks
  Add sample pre-push hook script

 Documentation/githooks.txt       |   28 ++++++++
 builtin/push.c                   |    1 +
 run-command.c                    |   35 ++++++---
 run-command.h                    |    3 +
 t/t5571-pre-push-hook.sh         |  145 ++++++++++++++++++++++++++++++++++++++
 templates/hooks--pre-push.sample |   63 +++++++++++++++++
 transport.c                      |   25 +++++++
 transport.h                      |    1 +
 8 files changed, 292 insertions(+), 9 deletions(-)
 create mode 100755 t/t5571-pre-push-hook.sh
 create mode 100644 templates/hooks--pre-push.sample

-- 
1.7.10.4

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 1/4] hooks: Add function to check if a hook exists
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
@ 2012-12-28 22:57 ` Aaron Schrab
  2012-12-29  2:08   ` Junio C Hamano
  2012-12-28 22:57 ` [PATCH 2/4] hooks: support variable number of parameters Aaron Schrab
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 22+ messages in thread
From: Aaron Schrab @ 2012-12-28 22:57 UTC (permalink / raw)
  To: git

Create find_hook() function to determine if a given hook exists and is
executable.  If it is the path to the script will be returned, otherwise
NULL is returned.

This is in support for an upcoming run_hook_argv() function which will
expect the full path to the hook script as the first element in the
argv_array.  This also makes it simple for places that can use a hook to
check if a hook exists before doing, possibly lengthy, setup work which
would be pointless if no such hook is present.

The returned value is left as a static value from get_pathname() rather
than a duplicate because it is anticipated that the return value will
either be used as a boolean, or immediately added to an argv_array list
which would result in it being duplicated at that point.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 run-command.c |   15 +++++++++++++--
 run-command.h |    1 +
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/run-command.c b/run-command.c
index 3b982e4..49c8fa0 100644
--- a/run-command.c
+++ b/run-command.c
@@ -735,6 +735,15 @@ int finish_async(struct async *async)
 #endif
 }
 
+char *find_hook(const char *name)
+{
+	char *path = git_path("hooks/%s", name);
+	if (access(path, X_OK) < 0)
+		path = NULL;
+
+	return path;
+}
+
 int run_hook(const char *index_file, const char *name, ...)
 {
 	struct child_process hook;
@@ -744,11 +753,13 @@ int run_hook(const char *index_file, const char *name, ...)
 	va_list args;
 	int ret;
 
-	if (access(git_path("hooks/%s", name), X_OK) < 0)
+	p = find_hook(name);
+	if (!p)
 		return 0;
 
+	argv_array_push(&argv, p);
+
 	va_start(args, name);
-	argv_array_push(&argv, git_path("hooks/%s", name));
 	while ((p = va_arg(args, const char *)))
 		argv_array_push(&argv, p);
 	va_end(args);
diff --git a/run-command.h b/run-command.h
index 850c638..221ce33 100644
--- a/run-command.h
+++ b/run-command.h
@@ -45,6 +45,7 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
+extern char *find_hook(const char *name);
 extern int run_hook(const char *index_file, const char *name, ...);
 
 #define RUN_COMMAND_NO_STDIN 1
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH 2/4] hooks: support variable number of parameters
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
  2012-12-28 22:57 ` [PATCH 1/4] hooks: Add function to check if a hook exists Aaron Schrab
@ 2012-12-28 22:57 ` Aaron Schrab
  2012-12-28 22:57 ` [PATCH 3/4] push: Add support for pre-push hooks Aaron Schrab
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Aaron Schrab @ 2012-12-28 22:57 UTC (permalink / raw)
  To: git

Define the run_hook_argv() function to allow hooks to be created where
the number of parameters to be passed is variable.  The existing
run_hook() function uses stdarg to allow it to receive a variable number
of arguments, but the number of arguments that a given caller is passing
is fixed at compile time.  This function will allow the caller of a hook
to determine the number of arguments to pass when preparing to call the
hook.

The first use of this function will be for a pre-push hook which will
add an argument for every reference which is to be pushed.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 run-command.c |   20 +++++++++++++-------
 run-command.h |    2 ++
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/run-command.c b/run-command.c
index 49c8fa0..e07202b 100644
--- a/run-command.c
+++ b/run-command.c
@@ -2,7 +2,6 @@
 #include "run-command.h"
 #include "exec_cmd.h"
 #include "sigchain.h"
-#include "argv-array.h"
 
 #ifndef SHELL_PATH
 # define SHELL_PATH "/bin/sh"
@@ -746,10 +745,8 @@ char *find_hook(const char *name)
 
 int run_hook(const char *index_file, const char *name, ...)
 {
-	struct child_process hook;
 	struct argv_array argv = ARGV_ARRAY_INIT;
-	const char *p, *env[2];
-	char index[PATH_MAX];
+	const char *p;
 	va_list args;
 	int ret;
 
@@ -764,6 +761,17 @@ int run_hook(const char *index_file, const char *name, ...)
 		argv_array_push(&argv, p);
 	va_end(args);
 
+	ret = run_hook_argv(index_file, argv);
+	argv_array_clear(&argv);
+	return ret;
+}
+
+int run_hook_argv(const char *index_file, struct argv_array argv)
+{
+	struct child_process hook;
+	char index[PATH_MAX];
+	const char *env[2];
+
 	memset(&hook, 0, sizeof(hook));
 	hook.argv = argv.argv;
 	hook.no_stdin = 1;
@@ -775,7 +783,5 @@ int run_hook(const char *index_file, const char *name, ...)
 		hook.env = env;
 	}
 
-	ret = run_command(&hook);
-	argv_array_clear(&argv);
-	return ret;
+	return run_command(&hook);
 }
diff --git a/run-command.h b/run-command.h
index 221ce33..12faa5b 100644
--- a/run-command.h
+++ b/run-command.h
@@ -1,6 +1,7 @@
 #ifndef RUN_COMMAND_H
 #define RUN_COMMAND_H
 
+#include "argv-array.h"
 #ifndef NO_PTHREADS
 #include <pthread.h>
 #endif
@@ -47,6 +48,7 @@ int run_command(struct child_process *);
 
 extern char *find_hook(const char *name);
 extern int run_hook(const char *index_file, const char *name, ...);
+extern int run_hook_argv(const char *index_file, struct argv_array);
 
 #define RUN_COMMAND_NO_STDIN 1
 #define RUN_GIT_CMD	     2	/*If this is to be git sub-command */
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH 3/4] push: Add support for pre-push hooks
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
  2012-12-28 22:57 ` [PATCH 1/4] hooks: Add function to check if a hook exists Aaron Schrab
  2012-12-28 22:57 ` [PATCH 2/4] hooks: support variable number of parameters Aaron Schrab
@ 2012-12-28 22:57 ` Aaron Schrab
  2012-12-28 22:57 ` [PATCH 4/4] Add sample pre-push hook script Aaron Schrab
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Aaron Schrab @ 2012-12-28 22:57 UTC (permalink / raw)
  To: git

Add support for a pre-push hook which can be used to determine if the
set of refs to be pushed is suitable for the target repository.  The
hook should be supplied with:

 1. name of the remote being used, or the URL if not using a named
    remote
 2. the URL to which we're pushing
 3. descriptions of what references are to be pushed

Each reference to be pushed should be described in a separate parameter
to the hook script in the form:

  <local ref>:<local sha1>:<remote ref>:<remote sha1>

This will allow the script to determine if the push is acceptable based
on the target repository and branch(es), the commits which are to be
pushed, and even the source branches in some cases.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 Documentation/githooks.txt |   28 +++++++++
 builtin/push.c             |    1 +
 t/t5571-pre-push-hook.sh   |  145 ++++++++++++++++++++++++++++++++++++++++++++
 transport.c                |   25 ++++++++
 transport.h                |    1 +
 5 files changed, 200 insertions(+)
 create mode 100755 t/t5571-pre-push-hook.sh

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index b9003fe..e9539bb 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -176,6 +176,34 @@ save and restore any form of metadata associated with the working tree
 (eg: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl
 for an example of how to do this.
 
+pre-push
+~~~~~~~~
+
+This hook is called by 'git push' and can be used to prevent a push from
+taking place.  The hook is called with a variable number of parameters.
+
+The first parameters provide the name and location of the destination
+remote, if a named remote is not being used both values will be the same.
+
+Remaining parameters provide information about the commits which are to be
+pushed and the ref names being used.  These arguments take the form:
+
+  <local ref>:<local sha1>:<remote ref>:<remote sha1>
+
+For instance, if the command +git push origin master:foreign+ were run the
+hook would be called with a third arugment similar to:
+
+  refs/heads/master:67890:refs/heads/foreign:12345
+
+although the full, 40-character SHA1s would be supplied.  If the foreign ref
+does not yet exist the `<remote SHA1>` will be 40 `0`.  If a ref is to be
+deleted, the `<local ref>` will be supplied as `(delete)` and the `<local
+SHA1>` will be 40 `0`.  If the local commit was specified by something other
+than a name which could be expanded (such as `HEAD~`, or a SHA1) it will be
+supplied as it was originally given.
+
+If this hook exits with a non-zero status, 'git push' will abort.
+
 [[pre-receive]]
 pre-receive
 ~~~~~~~~~~~
diff --git a/builtin/push.c b/builtin/push.c
index db9ba30..c33fb9b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -399,6 +399,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
 		OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
 			TRANSPORT_PUSH_PRUNE),
+		OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
 		OPT_END()
 	};
 
diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
new file mode 100755
index 0000000..5444c9b
--- /dev/null
+++ b/t/t5571-pre-push-hook.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='check pre-push hooks'
+. ./test-lib.sh
+
+# Setup hook that always succeeds
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/pre-push"
+mkdir -p "$HOOKDIR"
+cat >"$HOOK" <<EOF
+#!/bin/sh
+exit 0
+EOF
+chmod +x "$HOOK"
+
+test_expect_success 'setup' '
+	git config push.default upstream &&
+	git init --bare repo1 &&
+	git remote add parent1 repo1 &&
+	test_commit one &&
+	git push parent1 HEAD:foreign
+'
+cat >"$HOOK" <<EOF
+#!/bin/sh
+exit 1
+EOF
+
+COMMIT1="$(git rev-parse HEAD)"
+export COMMIT1
+
+test_expect_success 'push with failing hook' '
+	test_commit two &&
+	test_must_fail git push parent1 HEAD
+'
+
+test_expect_success '--no-verify bypasses hook' '
+	git push --no-verify parent1 HEAD
+'
+
+COMMIT2="$(git rev-parse HEAD)"
+export COMMIT2
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 3
+test "$1" = parent1
+test "$2" = repo1
+test "$3" = "refs/heads/master:$COMMIT2:refs/heads/foreign:$COMMIT1"
+EOF
+
+test_expect_success 'push with hook' '
+	git push parent1 master:foreign
+'
+
+test_expect_success 'add a branch' '
+	git checkout -b other &&
+	test_commit three
+'
+
+COMMIT3="$(git rev-parse HEAD)"
+export COMMIT3
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 4
+test "$1" = parent1
+test "$2" = repo1
+test "$3" = "refs/heads/other:$COMMIT3:refs/heads/foreign:$COMMIT2"
+test "$4" = "refs/heads/master:$COMMIT2:refs/heads/new:$_z40"
+EOF
+
+test_expect_success 'push multiple refs' '
+	git push parent1 other:foreign master:new
+'
+
+test_expect_success 'add a branch with an upstream' '
+	git checkout -t -b tracking parent1/foreign &&
+	test_commit four
+'
+COMMIT4="$(git rev-parse HEAD)"
+export COMMIT4
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 3
+test "$1" = parent1
+test "$2" = repo1
+test "$3" = "refs/heads/tracking:$COMMIT4:refs/heads/foreign:$COMMIT3"
+EOF
+
+test_expect_success 'push to upstream branch' '
+	git push &&
+	git checkout other
+'
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 3
+test "$1" = parent1
+test "$2" = repo1
+test "$3" = "(delete):$_z40:refs/heads/new:$COMMIT2"
+EOF
+
+test_expect_success 'push deletion' '
+	git push parent1 :new
+'
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 3
+test "$1" = repo2
+test "$2" = repo2
+test "$3" = "refs/heads/other:$COMMIT3:refs/heads/new:$_z40"
+EOF
+
+test_expect_success 'push to URL' '
+	git init --bare repo2 &&
+	git push repo2 other:new
+'
+
+ABBR=$(expr substr $COMMIT3 1 8)
+export ABBR
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 4
+test "$3" = "HEAD~:$COMMIT2:refs/heads/commitish:$_z40"
+test "$4" = "$ABBR:$COMMIT3:refs/heads/sha:$_z40"
+EOF
+
+test_expect_success 'push commit' '
+	git push parent1 HEAD~:refs/heads/commitish $ABBR:refs/heads/sha
+'
+
+cat >"$HOOK" <<'EOF'
+#!/bin/sh -ex
+test "$#" = 3
+test "$3" = "refs/tags/one:$COMMIT1:refs/tags/tagpush:$_z40"
+EOF
+
+test_expect_success 'push tag' '
+	git push parent1 one:tagpush
+'
+
+test_done
diff --git a/transport.c b/transport.c
index 9932f40..b0c9a15 100644
--- a/transport.c
+++ b/transport.c
@@ -1052,6 +1052,7 @@ int transport_push(struct transport *transport,
 		int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
 		int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
 		int push_ret, ret, err;
+		char *hook;
 
 		if (flags & TRANSPORT_PUSH_ALL)
 			match_flags |= MATCH_REFS_ALL;
@@ -1069,6 +1070,30 @@ int transport_push(struct transport *transport,
 			flags & TRANSPORT_PUSH_MIRROR,
 			flags & TRANSPORT_PUSH_FORCE);
 
+		if (!(flags & TRANSPORT_PUSH_NO_HOOK) && (hook = find_hook("pre-push"))) {
+			struct ref *r;
+			struct argv_array argv = ARGV_ARRAY_INIT;
+
+			argv_array_push(&argv, hook);
+			argv_array_push(&argv, transport->remote->name);
+			argv_array_push(&argv, transport->url);
+
+			for (r = remote_refs; r; r = r->next) {
+				if (!r->peer_ref) continue;
+				if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
+				if (r->status == REF_STATUS_UPTODATE) continue;
+
+				argv_array_pushf(&argv, "%s:%s:%s:%s",
+					r->peer_ref->name, sha1_to_hex(r->new_sha1),
+					r->name, sha1_to_hex(r->old_sha1));
+			}
+
+			ret = run_hook_argv(NULL, argv);
+			argv_array_clear(&argv);
+			if (ret)
+				return -1;
+		}
+
 		if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
 			struct ref *ref = remote_refs;
 			for (; ref; ref = ref->next)
diff --git a/transport.h b/transport.h
index 4a61c0c..3bc9863 100644
--- a/transport.h
+++ b/transport.h
@@ -104,6 +104,7 @@ struct transport {
 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 #define TRANSPORT_PUSH_PRUNE 128
 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
+#define TRANSPORT_PUSH_NO_HOOK 512
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH 4/4] Add sample pre-push hook script
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
                   ` (2 preceding siblings ...)
  2012-12-28 22:57 ` [PATCH 3/4] push: Add support for pre-push hooks Aaron Schrab
@ 2012-12-28 22:57 ` Aaron Schrab
  2012-12-29  2:01 ` [PATCH 0/4] pre-push hook support Junio C Hamano
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Aaron Schrab @ 2012-12-28 22:57 UTC (permalink / raw)
  To: git

Create a sample of a script for a pre-push hook.  The main purpose is to
illustrate how a script may parse the parameters which are supplied to
such a hook.  The script may also be useful to some people as-is for
avoiding to push commits which are marked as a work in progress.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 templates/hooks--pre-push.sample |   63 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)
 create mode 100644 templates/hooks--pre-push.sample

diff --git a/templates/hooks--pre-push.sample b/templates/hooks--pre-push.sample
new file mode 100644
index 0000000..1d3b4a3
--- /dev/null
+++ b/templates/hooks--pre-push.sample
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+# An example hook script to verify what is about to be pushed.
+# Called by "git push" after it has checked the remote status, but before
+# anything has been pushed.  If this script exits with a non-zero status
+# nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+#   If pushing without using a named remote those arguments will be equal.
+#
+# Further arguments provide information about the commits which are being
+# pushed in the form:
+#
+#   <local ref>:<local sha1>:<remote ref>:<remote sha1>
+#
+# This sample shows how to prevent push of commits where the log
+# message starts with "WIP" (work in progress).
+
+remote="$1"
+url="$2"
+shift 2
+
+z40=0000000000000000000000000000000000000000
+
+old_ifs="$IFS"
+for to_push in "$@"
+do
+	# Split the value into its parts
+	IFS=:
+	set -- $to_push
+	IFS="$old_ifs"
+
+	local_ref="$1"
+	local_sha="$2"
+	remote_ref="$3"
+	remote_sha="$4"
+
+	if [ "$local_sha" = $z40 ]
+	then
+		range=''
+		# Handle deletes
+	else
+		if [ "$remote_sha" = $z40 ]
+		then
+			range="$local_sha"
+		else
+			range="$remote_sha..$local_sha"
+		fi
+
+		commit=`git rev-list -n 1 --grep '^WIP' "$range"`
+		if [ -n "$commit" ]
+		then
+			echo "Found WIP commit in $local_ref, not pushing"
+			exit 1
+		fi
+	fi
+done
+
+exit 0
-- 
1.7.10.4

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH 0/4] pre-push hook support
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
                   ` (3 preceding siblings ...)
  2012-12-28 22:57 ` [PATCH 4/4] Add sample pre-push hook script Aaron Schrab
@ 2012-12-29  2:01 ` Junio C Hamano
  2012-12-29 14:50   ` Aaron Schrab
  2013-01-13  5:17 ` [PATCH v2 0/3] " Aaron Schrab
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2012-12-29  2:01 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> There have been at least a couple of submissions to add support for a
> pre-push hook, which were rejected at least partially because they didn't
> provide enough information to a hook script for it to determine what was
> to be pushed any better than a separate wrapper around the 'git push'
> command would be able to do.  In this series I attempt to address that
> problem.
>
> The first two patches in this series do a little bit of refactoring in
> order to make it easier to call hooks with a variable number of arguments.
>
> The third patch actually adds support for calling a pre-push hook.  If it
> exists, it will be called with the name and URL of the destination remote
> (if a named remote isn't being used, the URL will be supplied for both)
> followed by another argument for each ref being pushed; these arguments
> take the form:
>
>   <local ref>:<local sha1>:<remote ref>:<remote sha1>

One lesson we learned long time ago while doing hooks is to avoid
unbound number of command line arguments and instead feed them from
the standard input.  I think this should do the same.

> This should provide enough information for a script to easily determine
> the set of commits that is being pushed, and thus make a decision if that
> should be allowed.

How does the hook communicate its decision to the calling Git?

Will it be "all-or-none", or "I'll allow these but not those"?

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 1/4] hooks: Add function to check if a hook exists
  2012-12-28 22:57 ` [PATCH 1/4] hooks: Add function to check if a hook exists Aaron Schrab
@ 2012-12-29  2:08   ` Junio C Hamano
  2012-12-29 14:50     ` Aaron Schrab
  0 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2012-12-29  2:08 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> Create find_hook() function to determine if a given hook exists and is
> executable.  If it is the path to the script will be returned, otherwise
> NULL is returned.

Sounds like a sensible thing to do.  To make sure the API is also
sensible, all the existing hooks should be updated to use this API,
no?

> This is in support for an upcoming run_hook_argv() function which will
> expect the full path to the hook script as the first element in the
> argv_array.  

There is currently a public function called run_hook() that squats
on the good name with a kludgy API that is too specific to using
separate index file.  Back when it was a private helper in the
implementation of "git commit", it was perfectly fine, but it was
exported without giving much thought on the API.

If you are introducing a new run_hook_* function, give it a generic
enough API that lets all the existing hook callers to use it.  I
would imagine that the API requirement may be modelled after
run_command() API so that we can pass argv[] and tweak the hook's
environ[], as well as feeding its stdin and possibly reading from
its stdout.  That would be very useful.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 0/4] pre-push hook support
  2012-12-29  2:01 ` [PATCH 0/4] pre-push hook support Junio C Hamano
@ 2012-12-29 14:50   ` Aaron Schrab
  2012-12-29 16:48     ` Junio C Hamano
  0 siblings, 1 reply; 22+ messages in thread
From: Aaron Schrab @ 2012-12-29 14:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

At 18:01 -0800 28 Dec 2012, Junio C Hamano <gitster@pobox.com> wrote:
>One lesson we learned long time ago while doing hooks is to avoid
>unbound number of command line arguments and instead feed them from
>the standard input.  I think this should do the same.

Good point.  I had been trying to keep the interface for this hook as 
close as possible to the ones for other client-side hooks on the theory 
that less development effort may go into those than for server-side 
hooks.  But thinking on that more I certainly see that this could easily 
run into limits on argument length on some systems; especially when it's 
likely that each of those arguments is likely to be over 100 bytes long.

I'll work on an updated version which sends the variable length 
information over a pipe, using the command-line arguments only to pass 
the remote name and URL.

>How does the hook communicate its decision to the calling Git?
>
>Will it be "all-or-none", or "I'll allow these but not those"?

Currently it just uses the exit code to communicate that back, so it's 
all-or-none.  I think I'll keep that in the updated version as well.

A future enhancement could modify the protocol to support reading from 
the hook's stdout the names of remote refs which are to be rejected, I 
think that just having the option for all-or-nothing is a good starting 
point.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 1/4] hooks: Add function to check if a hook exists
  2012-12-29  2:08   ` Junio C Hamano
@ 2012-12-29 14:50     ` Aaron Schrab
  2012-12-29 16:54       ` Junio C Hamano
  0 siblings, 1 reply; 22+ messages in thread
From: Aaron Schrab @ 2012-12-29 14:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

At 18:08 -0800 28 Dec 2012, Junio C Hamano <gitster@pobox.com> wrote:
>Aaron Schrab <aaron@schrab.com> writes:
>
>> Create find_hook() function to determine if a given hook exists and is
>> executable.  If it is the path to the script will be returned, otherwise
>> NULL is returned.
>
>Sounds like a sensible thing to do.  To make sure the API is also
>sensible, all the existing hooks should be updated to use this API,
>no?

I'd been trying to keep the changes limited.  I'll see about modifying 
the existing places that run hooks in v2 of the series.

>> This is in support for an upcoming run_hook_argv() function which will
>> expect the full path to the hook script as the first element in the
>> argv_array.
>
>There is currently a public function called run_hook() that squats
>on the good name with a kludgy API that is too specific to using
>separate index file.  Back when it was a private helper in the
>implementation of "git commit", it was perfectly fine, but it was
>exported without giving much thought on the API.
>
>If you are introducing a new run_hook_* function, give it a generic
>enough API that lets all the existing hook callers to use it.  I
>would imagine that the API requirement may be modelled after
>run_command() API so that we can pass argv[] and tweak the hook's
>environ[], as well as feeding its stdin and possibly reading from
>its stdout.  That would be very useful.

I think the attraction of the run_hook() API is its simplicity.  It's 
currently a fairly thin wrapper around the run_command() API.  I suspect 
that if the run_hook() API were made generic enough to support all of 
the existing hook callers it would greatly complicate the existing calls 
to run_hook() while not providing much benefit to hook callers which 
can't currently use it beyond what run_command() offers.

Since I'm going to be changing the interface for this hook in v2 of the 
series so that it will be more complicated than can be readily addressed 
with the run_hook() API (and will have use a fixed number of arguments 
anyway) I'll be dropping the run_hook_argv() function.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 0/4] pre-push hook support
  2012-12-29 14:50   ` Aaron Schrab
@ 2012-12-29 16:48     ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2012-12-29 16:48 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> At 18:01 -0800 28 Dec 2012, Junio C Hamano <gitster@pobox.com> wrote:
>>Will it be "all-or-none", or "I'll allow these but not those"?
>
> Currently it just uses the exit code to communicate that back, so it's
> all-or-none.  I think I'll keep that in the updated version as well.

Thanks; that sounds sensible.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 1/4] hooks: Add function to check if a hook exists
  2012-12-29 14:50     ` Aaron Schrab
@ 2012-12-29 16:54       ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2012-12-29 16:54 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> Since I'm going to be changing the interface for this hook in v2 of
> the series so that it will be more complicated than can be readily
> addressed with the run_hook() API (and will have use a fixed number of
> arguments anyway) I'll be dropping the run_hook_argv() function.

Just to make sure there is no misunderstanding (sorry for sending
the message without finishing it with this clarification at the end
in the first place).  I didn't mean that converting all of the
existing callers must come earlier than introducing a new hook
invoker.

I just wanted to make sure that we are aware that we are adding to
our technical debt, if we are adding another that is also
specialized; as the proposed interface looked sufficiently generic,
it would be the ideal one to make _other_ ones thin wrappers around
it to unify the various codepaths.

Thanks.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v2 0/3] pre-push hook support
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
                   ` (4 preceding siblings ...)
  2012-12-29  2:01 ` [PATCH 0/4] pre-push hook support Junio C Hamano
@ 2013-01-13  5:17 ` Aaron Schrab
  2013-01-14 17:42   ` Junio C Hamano
  2013-01-13  5:17 ` [PATCH v2 1/3] hooks: Add function to check if a hook exists Aaron Schrab
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 22+ messages in thread
From: Aaron Schrab @ 2013-01-13  5:17 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Main changes since the initial version:

 * The first patch converts the existing hook callers to use the new
   find_hook() function.
 * Information about what is to be pushed is now sent over a pipe rather
   than passed as command-line parameters.

Aaron Schrab (3):
  hooks: Add function to check if a hook exists
  push: Add support for pre-push hooks
  Add sample pre-push hook script

 Documentation/githooks.txt       |  29 +++++++++
 builtin/commit.c                 |   6 +-
 builtin/push.c                   |   1 +
 builtin/receive-pack.c           |  25 ++++----
 run-command.c                    |  15 ++++-
 run-command.h                    |   1 +
 t/t5571-pre-push-hook.sh         | 129 +++++++++++++++++++++++++++++++++++++++
 templates/hooks--pre-push.sample |  53 ++++++++++++++++
 transport.c                      |  60 ++++++++++++++++++
 transport.h                      |   1 +
 10 files changed, 300 insertions(+), 20 deletions(-)
 create mode 100755 t/t5571-pre-push-hook.sh
 create mode 100644 templates/hooks--pre-push.sample

-- 
1.8.1.340.g425b78d

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v2 1/3] hooks: Add function to check if a hook exists
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
                   ` (5 preceding siblings ...)
  2013-01-13  5:17 ` [PATCH v2 0/3] " Aaron Schrab
@ 2013-01-13  5:17 ` Aaron Schrab
  2013-01-13  5:17 ` [PATCH v2 2/3] push: Add support for pre-push hooks Aaron Schrab
  2013-01-13  5:17 ` [PATCH v2 3/3] Add sample pre-push hook script Aaron Schrab
  8 siblings, 0 replies; 22+ messages in thread
From: Aaron Schrab @ 2013-01-13  5:17 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Create find_hook() function to determine if a given hook exists and is
executable.  If it is, the path to the script will be returned,
otherwise NULL is returned.

This encapsulates the tests that are used to check for the existence of
a hook in one place, making it easier to modify those checks if that is
found to be necessary.  This also makes it simple for places that can
use a hook to check if a hook exists before doing, possibly lengthy,
setup work which would be pointless if no such hook is present.

The returned value is left as a static value from get_pathname() rather
than a duplicate because it is anticipated that the return value will
either be used as a boolean, immediately added to an argv_array list
which would result in it being duplicated at that point, or used to
actually run the command without much intervening work.  Callers which
need to hold onto the returned value for a longer time are expected to
duplicate the return value themselves.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 builtin/commit.c       |  6 ++----
 builtin/receive-pack.c | 25 +++++++++++--------------
 run-command.c          | 15 +++++++++++++--
 run-command.h          |  1 +
 4 files changed, 27 insertions(+), 20 deletions(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index d6dd3df..65d08d2 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1327,8 +1327,6 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
-static const char post_rewrite_hook[] = "hooks/post-rewrite";
-
 static int run_rewrite_hook(const unsigned char *oldsha1,
 			    const unsigned char *newsha1)
 {
@@ -1339,10 +1337,10 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
 	int code;
 	size_t n;
 
-	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+	argv[0] = find_hook("post-rewrite");
+	if (!argv[0])
 		return 0;
 
-	argv[0] = git_path(post_rewrite_hook);
 	argv[1] = "amend";
 	argv[2] = NULL;
 
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index ff781fe..e8878de 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -182,9 +182,6 @@ struct command {
 	char ref_name[FLEX_ARRAY]; /* more */
 };
 
-static const char pre_receive_hook[] = "hooks/pre-receive";
-static const char post_receive_hook[] = "hooks/post-receive";
-
 static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
@@ -242,10 +239,10 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
 	const char *argv[2];
 	int code;
 
-	if (access(hook_name, X_OK) < 0)
+	argv[0] = find_hook(hook_name);
+	if (!argv[0])
 		return 0;
 
-	argv[0] = hook_name;
 	argv[1] = NULL;
 
 	memset(&proc, 0, sizeof(proc));
@@ -331,15 +328,14 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
 
 static int run_update_hook(struct command *cmd)
 {
-	static const char update_hook[] = "hooks/update";
 	const char *argv[5];
 	struct child_process proc;
 	int code;
 
-	if (access(update_hook, X_OK) < 0)
+	argv[0] = find_hook("update");
+	if (!argv[0])
 		return 0;
 
-	argv[0] = update_hook;
 	argv[1] = cmd->ref_name;
 	argv[2] = sha1_to_hex(cmd->old_sha1);
 	argv[3] = sha1_to_hex(cmd->new_sha1);
@@ -532,24 +528,25 @@ static const char *update(struct command *cmd)
 	}
 }
 
-static char update_post_hook[] = "hooks/post-update";
-
 static void run_update_post_hook(struct command *commands)
 {
 	struct command *cmd;
 	int argc;
 	const char **argv;
 	struct child_process proc;
+	char *hook;
 
+	hook = find_hook("post-update");
 	for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
 		if (cmd->error_string || cmd->did_not_exist)
 			continue;
 		argc++;
 	}
-	if (!argc || access(update_post_hook, X_OK) < 0)
+	if (!argc || !hook)
 		return;
+
 	argv = xmalloc(sizeof(*argv) * (2 + argc));
-	argv[0] = update_post_hook;
+	argv[0] = hook;
 
 	for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
 		char *p;
@@ -704,7 +701,7 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
 				       0, &cmd))
 		set_connectivity_errors(commands);
 
-	if (run_receive_hook(commands, pre_receive_hook, 0)) {
+	if (run_receive_hook(commands, "pre-receive", 0)) {
 		for (cmd = commands; cmd; cmd = cmd->next) {
 			if (!cmd->error_string)
 				cmd->error_string = "pre-receive hook declined";
@@ -994,7 +991,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 			unlink_or_warn(pack_lockfile);
 		if (report_status)
 			report(commands, unpack_status);
-		run_receive_hook(commands, post_receive_hook, 1);
+		run_receive_hook(commands, "post-receive", 1);
 		run_update_post_hook(commands);
 		if (auto_gc) {
 			const char *argv_gc_auto[] = {
diff --git a/run-command.c b/run-command.c
index 0471219..12d4ddb 100644
--- a/run-command.c
+++ b/run-command.c
@@ -735,6 +735,15 @@ int finish_async(struct async *async)
 #endif
 }
 
+char *find_hook(const char *name)
+{
+	char *path = git_path("hooks/%s", name);
+	if (access(path, X_OK) < 0)
+		path = NULL;
+
+	return path;
+}
+
 int run_hook(const char *index_file, const char *name, ...)
 {
 	struct child_process hook;
@@ -744,11 +753,13 @@ int run_hook(const char *index_file, const char *name, ...)
 	va_list args;
 	int ret;
 
-	if (access(git_path("hooks/%s", name), X_OK) < 0)
+	p = find_hook(name);
+	if (!p)
 		return 0;
 
+	argv_array_push(&argv, p);
+
 	va_start(args, name);
-	argv_array_push(&argv, git_path("hooks/%s", name));
 	while ((p = va_arg(args, const char *)))
 		argv_array_push(&argv, p);
 	va_end(args);
diff --git a/run-command.h b/run-command.h
index 850c638..221ce33 100644
--- a/run-command.h
+++ b/run-command.h
@@ -45,6 +45,7 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
+extern char *find_hook(const char *name);
 extern int run_hook(const char *index_file, const char *name, ...);
 
 #define RUN_COMMAND_NO_STDIN 1
-- 
1.8.1.340.g425b78d

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 2/3] push: Add support for pre-push hooks
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
                   ` (6 preceding siblings ...)
  2013-01-13  5:17 ` [PATCH v2 1/3] hooks: Add function to check if a hook exists Aaron Schrab
@ 2013-01-13  5:17 ` Aaron Schrab
  2013-01-14 17:39   ` Junio C Hamano
  2013-01-15  0:36   ` Junio C Hamano
  2013-01-13  5:17 ` [PATCH v2 3/3] Add sample pre-push hook script Aaron Schrab
  8 siblings, 2 replies; 22+ messages in thread
From: Aaron Schrab @ 2013-01-13  5:17 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Add support for a pre-push hook which can be used to determine if the
set of refs to be pushed is suitable for the target repository.  The
hook is run with two arguments specifying the name and location of the
destination repository.

Information about what is to be pushed is provided by sending lines of
the following form to the hook's standard input:

  <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF

If the hook exits with a non-zero status, the push will be aborted.

This will allow the script to determine if the push is acceptable based
on the target repository and branch(es), the commits which are to be
pushed, and even the source branches in some cases.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 Documentation/githooks.txt |  29 ++++++++++
 builtin/push.c             |   1 +
 t/t5571-pre-push-hook.sh   | 129 +++++++++++++++++++++++++++++++++++++++++++++
 transport.c                |  60 +++++++++++++++++++++
 transport.h                |   1 +
 5 files changed, 220 insertions(+)
 create mode 100755 t/t5571-pre-push-hook.sh

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index b9003fe..d839233 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -176,6 +176,35 @@ save and restore any form of metadata associated with the working tree
 (eg: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl
 for an example of how to do this.
 
+pre-push
+~~~~~~~~
+
+This hook is called by 'git push' and can be used to prevent a push from taking
+place.  The hook is called with two parameters which provide the name and
+location of the destination remote, if a named remote is not being used both
+values will be the same.
+
+Information about what is to be pushed is provided on the hook's standard
+input with lines of the form:
+
+  <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
+
+For instance, if the command +git push origin master:foreign+ were run the
+hook would receive a line like the following:
+
+  refs/heads/master 67890 refs/heads/foreign 12345
+
+although the full, 40-character SHA1s would be supplied.  If the foreign ref
+does not yet exist the `<remote SHA1>` will be 40 `0`.  If a ref is to be
+deleted, the `<local ref>` will be supplied as `(delete)` and the `<local
+SHA1>` will be 40 `0`.  If the local commit was specified by something other
+than a name which could be expanded (such as `HEAD~`, or a SHA1) it will be
+supplied as it was originally given.
+
+If this hook exits with a non-zero status, 'git push' will abort without
+pushing anything.  Information about why the push is rejected may be sent
+to the user by writing to standard error.
+
 [[pre-receive]]
 pre-receive
 ~~~~~~~~~~~
diff --git a/builtin/push.c b/builtin/push.c
index 8491e43..b158028 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -407,6 +407,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
 		OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
 			TRANSPORT_PUSH_PRUNE),
+		OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
 		OPT_END()
 	};
 
diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
new file mode 100755
index 0000000..d68fed7
--- /dev/null
+++ b/t/t5571-pre-push-hook.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='check pre-push hooks'
+. ./test-lib.sh
+
+# Setup hook that always succeeds
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/pre-push"
+mkdir -p "$HOOKDIR"
+write_script "$HOOK" <<EOF
+exit 0
+EOF
+
+test_expect_success 'setup' '
+	git config push.default upstream &&
+	git init --bare repo1 &&
+	git remote add parent1 repo1 &&
+	test_commit one &&
+	git push parent1 HEAD:foreign
+'
+write_script "$HOOK" <<EOF
+exit 1
+EOF
+
+COMMIT1="$(git rev-parse HEAD)"
+export COMMIT1
+
+test_expect_success 'push with failing hook' '
+	test_commit two &&
+	test_must_fail git push parent1 HEAD
+'
+
+test_expect_success '--no-verify bypasses hook' '
+	git push --no-verify parent1 HEAD
+'
+
+COMMIT2="$(git rev-parse HEAD)"
+export COMMIT2
+
+write_script "$HOOK" <<'EOF'
+echo "$1" >actual
+echo "$2" >>actual
+cat >>actual
+EOF
+
+cat >expected <<EOF
+parent1
+repo1
+refs/heads/master $COMMIT2 refs/heads/foreign $COMMIT1
+EOF
+
+test_expect_success 'push with hook' '
+	git push parent1 master:foreign &&
+	diff expected actual
+'
+
+test_expect_success 'add a branch' '
+	git checkout -b other parent1/foreign &&
+	test_commit three
+'
+
+COMMIT3="$(git rev-parse HEAD)"
+export COMMIT3
+
+cat >expected <<EOF
+parent1
+repo1
+refs/heads/other $COMMIT3 refs/heads/foreign $COMMIT2
+EOF
+
+test_expect_success 'push to default' '
+	git push &&
+	diff expected actual
+'
+
+cat >expected <<EOF
+parent1
+repo1
+refs/tags/one $COMMIT1 refs/tags/tag1 $_z40
+HEAD~ $COMMIT2 refs/heads/prev $_z40
+EOF
+
+test_expect_success 'push non-branches' '
+	git push parent1 one:tag1 HEAD~:refs/heads/prev &&
+	diff expected actual
+'
+
+cat >expected <<EOF
+parent1
+repo1
+(delete) $_z40 refs/heads/prev $COMMIT2
+EOF
+
+test_expect_success 'push delete' '
+	git push parent1 :prev &&
+	diff expected actual
+'
+
+cat >expected <<EOF
+repo1
+repo1
+HEAD $COMMIT3 refs/heads/other $_z40
+EOF
+
+test_expect_success 'push to URL' '
+	git push repo1 HEAD &&
+	diff expected actual
+'
+
+# Test that filling pipe buffers doesn't cause failure
+# Too slow to leave enabled for general use
+if false
+then
+	printf 'parent1\nrepo1\n' >expected
+	nr=1000
+	while test $nr -lt 2000
+	do
+		nr=$(( $nr + 1 ))
+		git branch b/$nr $COMMIT3
+		echo "refs/heads/b/$nr $COMMIT3 refs/heads/b/$nr $_z40" >>expected
+	done
+
+	test_expect_success 'push many refs' '
+		git push parent1 "refs/heads/b/*:refs/heads/b/*" &&
+		diff expected actual
+	'
+fi
+
+test_done
diff --git a/transport.c b/transport.c
index 2673d27..0750a5f 100644
--- a/transport.c
+++ b/transport.c
@@ -1034,6 +1034,62 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)
 	die("Aborting.");
 }
 
+static int run_pre_push_hook(struct transport *transport,
+			     struct ref *remote_refs)
+{
+	int ret = 0, x;
+	struct ref *r;
+	struct child_process proc;
+	struct strbuf buf;
+	const char *argv[4];
+
+	if (!(argv[0] = find_hook("pre-push")))
+		return 0;
+
+	argv[1] = transport->remote->name;
+	argv[2] = transport->url;
+	argv[3] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+
+	if (start_command(&proc)) {
+		finish_command(&proc);
+		return -1;
+	}
+
+	strbuf_init(&buf, 256);
+
+	for (r = remote_refs; r; r = r->next) {
+		if (!r->peer_ref) continue;
+		if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
+		if (r->status == REF_STATUS_UPTODATE) continue;
+
+		strbuf_reset(&buf);
+		strbuf_addf( &buf, "%s %s %s %s\n",
+			 r->peer_ref->name, sha1_to_hex(r->new_sha1),
+			 r->name, sha1_to_hex(r->old_sha1));
+
+		if (write_in_full(proc.in, buf.buf, buf.len) != buf.len) {
+			ret = -1;
+			break;
+		}
+	}
+
+	strbuf_release(&buf);
+
+	x = close(proc.in);
+	if (!ret)
+		ret = x;
+
+	x = finish_command(&proc);
+	if (!ret)
+		ret = x;
+
+	return ret;
+}
+
 int transport_push(struct transport *transport,
 		   int refspec_nr, const char **refspec, int flags,
 		   unsigned int *reject_reasons)
@@ -1074,6 +1130,10 @@ int transport_push(struct transport *transport,
 			flags & TRANSPORT_PUSH_MIRROR,
 			flags & TRANSPORT_PUSH_FORCE);
 
+		if (!(flags & TRANSPORT_PUSH_NO_HOOK))
+			if (run_pre_push_hook(transport, remote_refs))
+				return -1;
+
 		if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
 			struct ref *ref = remote_refs;
 			for (; ref; ref = ref->next)
diff --git a/transport.h b/transport.h
index bfd2df5..ac5a9f5 100644
--- a/transport.h
+++ b/transport.h
@@ -104,6 +104,7 @@ struct transport {
 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 #define TRANSPORT_PUSH_PRUNE 128
 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
+#define TRANSPORT_PUSH_NO_HOOK 512
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
-- 
1.8.1.340.g425b78d

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 3/3] Add sample pre-push hook script
  2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
                   ` (7 preceding siblings ...)
  2013-01-13  5:17 ` [PATCH v2 2/3] push: Add support for pre-push hooks Aaron Schrab
@ 2013-01-13  5:17 ` Aaron Schrab
  2013-01-14 17:42   ` Junio C Hamano
  8 siblings, 1 reply; 22+ messages in thread
From: Aaron Schrab @ 2013-01-13  5:17 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Create a sample of a script for a pre-push hook.  The main purpose is to
illustrate how a script may parse the information which is supplied to
such a hook.  The script may also be useful to some people as-is for
avoiding to push commits which are marked as a work in progress.

Signed-off-by: Aaron Schrab <aaron@schrab.com>
---
 templates/hooks--pre-push.sample | 53 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 templates/hooks--pre-push.sample

diff --git a/templates/hooks--pre-push.sample b/templates/hooks--pre-push.sample
new file mode 100644
index 0000000..15ab6d8
--- /dev/null
+++ b/templates/hooks--pre-push.sample
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+# An example hook script to verify what is about to be pushed.  Called by "git
+# push" after it has checked the remote status, but before anything has been
+# pushed.  If this script exits with a non-zero status nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+# If pushing without using a named remote those arguments will be equal.
+#
+# Information about the commits which are being pushed is supplied as lines to
+# the standard input in the form:
+#
+#   <local ref> <local sha1> <remote ref> <remote sha1>
+#
+# This sample shows how to prevent push of commits where the log message starts
+# with "WIP" (work in progress).
+
+remote="$1"
+url="$2"
+
+z40=0000000000000000000000000000000000000000
+
+IFS=' '
+while read local_ref local_sha remote_ref remote_sha
+do
+	if [ "$local_sha" = $z40 ]
+	then
+		# Handle delete
+	else
+		if [ "$remote_sha" = $z40 ]
+		then
+			# New branch, examine all commits
+			range="$local_sha"
+		else
+			# Update to existing branch, examine new commits
+			range="$remote_sha..$local_sha"
+		fi
+
+		# Check for WIP commit
+		commit=`git rev-list -n 1 --grep '^WIP' "$range"`
+		if [ -n "$commit" ]
+		then
+			echo "Found WIP commit in $local_ref, not pushing"
+			exit 1
+		fi
+	fi
+done
+
+exit 0
-- 
1.8.1.340.g425b78d

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 2/3] push: Add support for pre-push hooks
  2013-01-13  5:17 ` [PATCH v2 2/3] push: Add support for pre-push hooks Aaron Schrab
@ 2013-01-14 17:39   ` Junio C Hamano
  2013-01-15  0:36   ` Junio C Hamano
  1 sibling, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-01-14 17:39 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> Add support for a pre-push hook which can be used to determine if the
> set of refs to be pushed is suitable for the target repository.  The
> hook is run with two arguments specifying the name and location of the
> destination repository.
>
> Information about what is to be pushed is provided by sending lines of
> the following form to the hook's standard input:
>
>   <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
>
> If the hook exits with a non-zero status, the push will be aborted.
>
> This will allow the script to determine if the push is acceptable based
> on the target repository and branch(es), the commits which are to be
> pushed, and even the source branches in some cases.
>
> Signed-off-by: Aaron Schrab <aaron@schrab.com>
> ---
>  Documentation/githooks.txt |  29 ++++++++++
>  builtin/push.c             |   1 +
>  t/t5571-pre-push-hook.sh   | 129 +++++++++++++++++++++++++++++++++++++++++++++
>  transport.c                |  60 +++++++++++++++++++++
>  transport.h                |   1 +
>  5 files changed, 220 insertions(+)
>  create mode 100755 t/t5571-pre-push-hook.sh
>
> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
> index b9003fe..d839233 100644
> --- a/Documentation/githooks.txt
> +++ b/Documentation/githooks.txt
> @@ -176,6 +176,35 @@ save and restore any form of metadata associated with the working tree
>  (eg: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl
>  for an example of how to do this.
>  
> +pre-push
> +~~~~~~~~
> +
> +This hook is called by 'git push' and can be used to prevent a push from taking
> +place.  The hook is called with two parameters which provide the name and
> +location of the destination remote, if a named remote is not being used both
> +values will be the same.
> +
> +Information about what is to be pushed is provided on the hook's standard
> +input with lines of the form:
> +
> +  <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
> +
> +For instance, if the command +git push origin master:foreign+ were run the

Just being curious, but why use +monospace text+ here?  Most of the
new text use `monospace text literally` instead in this patch.

> +hook would receive a line like the following:
> +
> +  refs/heads/master 67890 refs/heads/foreign 12345
> +
> +although the full, 40-character SHA1s would be supplied.

Perhaps ellipses are called for here?

    refs/heads/master 67890... refs/heads/foreign 12345...

 (the above abbreviates full 40-hexdigits for illustration purposes only)

> +If the foreign ref
> +does not yet exist the `<remote SHA1>` will be 40 `0`.  If a ref is to be
> +deleted, the `<local ref>` will be supplied as `(delete)` and the `<local
> +SHA1>` will be 40 `0`.  If the local commit was specified by something other
> +than a name which could be expanded (such as `HEAD~`, or a SHA1) it will be
> +supplied as it was originally given.
> +
> +If this hook exits with a non-zero status, 'git push' will abort without
> +pushing anything.  Information about why the push is rejected may be sent
> +to the user by writing to standard error.

s/standard error/& of the hook/; perhaps?  It is unclear who does
the writing and it can be misunderstood that git-push will write to
standard error upon seeing your hook that silently exits.

> diff --git a/builtin/push.c b/builtin/push.c
> index 8491e43..b158028 100644
> --- a/builtin/push.c
> +++ b/builtin/push.c
> @@ -407,6 +407,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
>  		OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
>  		OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
>  			TRANSPORT_PUSH_PRUNE),
> +		OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
>  		OPT_END()
>  	};

So to countermand this, you have to say --no-no-verify?  Wouldn't it
be more natural to introduce a --verify option that turns the bit
on, which automatically gives you --no-verify to turn it off?  A
bit in a flag word can be initialized to true before the flag word
is given to the parse_options() machinery to make the field default
to true, no?

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 3/3] Add sample pre-push hook script
  2013-01-13  5:17 ` [PATCH v2 3/3] Add sample pre-push hook script Aaron Schrab
@ 2013-01-14 17:42   ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-01-14 17:42 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> Create a sample of a script for a pre-push hook.  The main purpose is to
> illustrate how a script may parse the information which is supplied to
> such a hook.  The script may also be useful to some people as-is for
> avoiding to push commits which are marked as a work in progress.
>
> Signed-off-by: Aaron Schrab <aaron@schrab.com>
> ---
>  templates/hooks--pre-push.sample | 53 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 53 insertions(+)
>  create mode 100644 templates/hooks--pre-push.sample
>
> diff --git a/templates/hooks--pre-push.sample b/templates/hooks--pre-push.sample
> new file mode 100644
> index 0000000..15ab6d8
> --- /dev/null
> +++ b/templates/hooks--pre-push.sample
> @@ -0,0 +1,53 @@
> +#!/bin/sh
> +
> +# An example hook script to verify what is about to be pushed.  Called by "git
> +# push" after it has checked the remote status, but before anything has been
> +# pushed.  If this script exits with a non-zero status nothing will be pushed.
> +#
> +# This hook is called with the following parameters:
> +#
> +# $1 -- Name of the remote to which the push is being done
> +# $2 -- URL to which the push is being done
> +#
> +# If pushing without using a named remote those arguments will be equal.
> +#
> +# Information about the commits which are being pushed is supplied as lines to
> +# the standard input in the form:
> +#
> +#   <local ref> <local sha1> <remote ref> <remote sha1>
> +#
> +# This sample shows how to prevent push of commits where the log message starts
> +# with "WIP" (work in progress).

An example for a plausible use case is nice to have.  I would prefer
to see any new shell script to follow the Git style, though.

> +remote="$1"
> +url="$2"
> +
> +z40=0000000000000000000000000000000000000000
> +
> +IFS=' '
> +while read local_ref local_sha remote_ref remote_sha
> +do
> +	if [ "$local_sha" = $z40 ]
> +	then
> +		# Handle delete

    ... by doing what?

> +	else
> +		if [ "$remote_sha" = $z40 ]
> +		then
> +			# New branch, examine all commits
> +			range="$local_sha"
> +		else
> +			# Update to existing branch, examine new commits
> +			range="$remote_sha..$local_sha"
> +		fi
> +
> +		# Check for WIP commit
> +		commit=`git rev-list -n 1 --grep '^WIP' "$range"`
> +		if [ -n "$commit" ]
> +		then
> +			echo "Found WIP commit in $local_ref, not pushing"
> +			exit 1
> +		fi
> +	fi
> +done
> +
> +exit 0

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 0/3] pre-push hook support
  2013-01-13  5:17 ` [PATCH v2 0/3] " Aaron Schrab
@ 2013-01-14 17:42   ` Junio C Hamano
  2013-01-14 22:54     ` Junio C Hamano
  0 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2013-01-14 17:42 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Aaron Schrab <aaron@schrab.com> writes:

> Main changes since the initial version:
>
>  * The first patch converts the existing hook callers to use the new
>    find_hook() function.
>  * Information about what is to be pushed is now sent over a pipe rather
>    than passed as command-line parameters.
>
> Aaron Schrab (3):
>   hooks: Add function to check if a hook exists
>   push: Add support for pre-push hooks
>   Add sample pre-push hook script

Getting much nicer.  Thanks.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 0/3] pre-push hook support
  2013-01-14 17:42   ` Junio C Hamano
@ 2013-01-14 22:54     ` Junio C Hamano
  2013-01-15  0:24       ` Junio C Hamano
  0 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2013-01-14 22:54 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Junio C Hamano <gitster@pobox.com> writes:

> Aaron Schrab <aaron@schrab.com> writes:
>
>> Main changes since the initial version:
>>
>>  * The first patch converts the existing hook callers to use the new
>>    find_hook() function.
>>  * Information about what is to be pushed is now sent over a pipe rather
>>    than passed as command-line parameters.
>>
>> Aaron Schrab (3):
>>   hooks: Add function to check if a hook exists
>>   push: Add support for pre-push hooks
>>   Add sample pre-push hook script
>
> Getting much nicer.  Thanks.

Hmph, t5571 seems to be flaky in that it sometimes fails but passes
when run again.  Something timing dependent is going on???

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 0/3] pre-push hook support
  2013-01-14 22:54     ` Junio C Hamano
@ 2013-01-15  0:24       ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-01-15  0:24 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git

Junio C Hamano <gitster@pobox.com> writes:

> Junio C Hamano <gitster@pobox.com> writes:
>
>> Aaron Schrab <aaron@schrab.com> writes:
>>
>>> Main changes since the initial version:
>>>
>>>  * The first patch converts the existing hook callers to use the new
>>>    find_hook() function.
>>>  * Information about what is to be pushed is now sent over a pipe rather
>>>    than passed as command-line parameters.
>>>
>>> Aaron Schrab (3):
>>>   hooks: Add function to check if a hook exists
>>>   push: Add support for pre-push hooks
>>>   Add sample pre-push hook script
>>
>> Getting much nicer.  Thanks.
>
> Hmph, t5571 seems to be flaky in that it sometimes fails but passes
> when run again.  Something timing dependent is going on???

With this patch applied, repeatedly try to

 - make sure "foreign" ref does not exist; and
 - attempt pushing the HEAD:foreign to create the "foreign" ref

until it fails, I can get it stop before the output scrolls off of
my 114 line terminal.  Then when I revert the changes to transport.[ch]
and builtin/push.c in this series, the test will keep going.

Wait.  The sample hook used in the test _is_ fed some input but it
exits without reading any.  What happens when we fork it, and it
completes execution before we even have a chance to feed a single
byte?  Wont' we get a sigpipe and die?

Yup, I think that is what is missing from run_pre_push_hook()
implementation.

diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
index d68fed7..050318b 100755
--- a/t/t5571-pre-push-hook.sh
+++ b/t/t5571-pre-push-hook.sh
@@ -16,8 +16,15 @@ test_expect_success 'setup' '
 	git init --bare repo1 &&
 	git remote add parent1 repo1 &&
 	test_commit one &&
-	git push parent1 HEAD:foreign
+	while :
+	do
+		git push parent1 :refs/heads/foreign &&
+		git push parent1 HEAD:foreign || break
+	done
 '
+
+exit
+
 write_script "$HOOK" <<EOF
 exit 1
 EOF

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 2/3] push: Add support for pre-push hooks
  2013-01-13  5:17 ` [PATCH v2 2/3] push: Add support for pre-push hooks Aaron Schrab
  2013-01-14 17:39   ` Junio C Hamano
@ 2013-01-15  0:36   ` Junio C Hamano
  2013-01-15  3:12     ` Junio C Hamano
  1 sibling, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2013-01-15  0:36 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git, peff

Aaron Schrab <aaron@schrab.com> writes:

>  t/t5571-pre-push-hook.sh   | 129 +++++++++++++++++++++++++++++++++++++++++++++
> diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
> new file mode 100755
> index 0000000..d68fed7
> --- /dev/null
> +++ b/t/t5571-pre-push-hook.sh
> @@ -0,0 +1,129 @@
> +#!/bin/sh
> +
> +test_description='check pre-push hooks'
> +. ./test-lib.sh
> +
> +# Setup hook that always succeeds
> +HOOKDIR="$(git rev-parse --git-dir)/hooks"
> +HOOK="$HOOKDIR/pre-push"
> +mkdir -p "$HOOKDIR"
> +write_script "$HOOK" <<EOF
> +exit 0
> +EOF

As this script is expected to read from the pipe, if this exits
before the parent has a chance to write to the pipe, the parent can
be killed with sigpipe.

At least the attached patch is necessary.

In the longer term, we may want to discuss what should happen when
the hook exited without even reading what we fed.  My gut feeling is
that we can still trust its exit status (a hook that was badly coded
so it wanted to read from us and use that information to decide but
somehow died before fully reading from us is not likely to exit with
zero status, so we wouldn't diagnosing breakage as a success), but
there may be downsides for being that lax.

If we decide we want to be lax, then the call site of this hook and
the pre-receive hook (is there any other "take info from the
standard input" hook?) need to be modified so that they ignore
sigpipe, I think.

There was a related discussion around this issue about a year ago.

http://thread.gmane.org/gmane.comp.version-control.git/180346/focus=186291


 t/t5571-pre-push-hook.sh | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
index d68fed7..577d252 100755
--- a/t/t5571-pre-push-hook.sh
+++ b/t/t5571-pre-push-hook.sh
@@ -8,6 +8,7 @@ HOOKDIR="$(git rev-parse --git-dir)/hooks"
 HOOK="$HOOKDIR/pre-push"
 mkdir -p "$HOOKDIR"
 write_script "$HOOK" <<EOF
+cat >/dev/null
 exit 0
 EOF
 
@@ -19,6 +20,7 @@ test_expect_success 'setup' '
 	git push parent1 HEAD:foreign
 '
 write_script "$HOOK" <<EOF
+cat >/dev/null
 exit 1
 EOF
 
@@ -38,6 +40,7 @@ COMMIT2="$(git rev-parse HEAD)"
 export COMMIT2
 
 write_script "$HOOK" <<'EOF'
+cat >/dev/null
 echo "$1" >actual
 echo "$2" >>actual
 cat >>actual

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 2/3] push: Add support for pre-push hooks
  2013-01-15  0:36   ` Junio C Hamano
@ 2013-01-15  3:12     ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-01-15  3:12 UTC (permalink / raw)
  To: Aaron Schrab; +Cc: git, peff

Junio C Hamano <gitster@pobox.com> writes:

> At least the attached patch is necessary.

Sorry, but the last hunk (see below) is not.  It breaks the hook.

> In the longer term, we may want to discuss what should happen when
> the hook exited without even reading what we fed.  My gut feeling is
> that we can still trust its exit status (a hook that was badly coded
> so it wanted to read from us and use that information to decide but
> somehow died before fully reading from us is not likely to exit with
> zero status, so we wouldn't diagnosing breakage as a success), but
> there may be downsides for being that lax.
>
> If we decide we want to be lax, then the call site of this hook and
> the pre-receive hook (is there any other "take info from the
> standard input" hook?) need to be modified so that they ignore
> sigpipe, I think.
>
> There was a related discussion around this issue about a year ago.
>
> http://thread.gmane.org/gmane.comp.version-control.git/180346/focus=186291
> ...
>

> @@ -38,6 +40,7 @@ COMMIT2="$(git rev-parse HEAD)"
>  export COMMIT2
>  
>  write_script "$HOOK" <<'EOF'
> +cat >/dev/null
>  echo "$1" >actual
>  echo "$2" >>actual
>  cat >>actual

As this one wants to keep the incoming data to "actual", we do not
want the extra "cat" to slurp everything in.  Sorry for not being
careful.

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2013-01-15  3:12 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-12-28 22:57 [PATCH 0/4] pre-push hook support Aaron Schrab
2012-12-28 22:57 ` [PATCH 1/4] hooks: Add function to check if a hook exists Aaron Schrab
2012-12-29  2:08   ` Junio C Hamano
2012-12-29 14:50     ` Aaron Schrab
2012-12-29 16:54       ` Junio C Hamano
2012-12-28 22:57 ` [PATCH 2/4] hooks: support variable number of parameters Aaron Schrab
2012-12-28 22:57 ` [PATCH 3/4] push: Add support for pre-push hooks Aaron Schrab
2012-12-28 22:57 ` [PATCH 4/4] Add sample pre-push hook script Aaron Schrab
2012-12-29  2:01 ` [PATCH 0/4] pre-push hook support Junio C Hamano
2012-12-29 14:50   ` Aaron Schrab
2012-12-29 16:48     ` Junio C Hamano
2013-01-13  5:17 ` [PATCH v2 0/3] " Aaron Schrab
2013-01-14 17:42   ` Junio C Hamano
2013-01-14 22:54     ` Junio C Hamano
2013-01-15  0:24       ` Junio C Hamano
2013-01-13  5:17 ` [PATCH v2 1/3] hooks: Add function to check if a hook exists Aaron Schrab
2013-01-13  5:17 ` [PATCH v2 2/3] push: Add support for pre-push hooks Aaron Schrab
2013-01-14 17:39   ` Junio C Hamano
2013-01-15  0:36   ` Junio C Hamano
2013-01-15  3:12     ` Junio C Hamano
2013-01-13  5:17 ` [PATCH v2 3/3] Add sample pre-push hook script Aaron Schrab
2013-01-14 17:42   ` Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).