Skip to content

Commit

Permalink
push options: {pre,post}-receive hook learns about push options
Browse files Browse the repository at this point in the history
The environment variable GIT_PUSH_OPTION_COUNT is set to the number of
push options sent, and GIT_PUSH_OPTION_{0,1,..} is set to the transmitted
option.

The code is not executed as the push options are set to NULL, nor is the
new capability advertised.

There was some discussion back and forth how to present these push options
to the user as there are some ways to do it:

Keep all options in one environment variable
============================================
+ easiest way to implement in Git
- This would make things hard to parse correctly in the hook.

Put the options in files instead,
filenames are in GIT_PUSH_OPTION_FILES
======================================
+ After a discussion about environment variables and shells, we may not
  want to put user data into an environment variable (see [1] for example).
+ We could transmit binaries, i.e. we're not bound to C strings as
  we are when using environment variables to the user.
+ Maybe easier to parse than constructing environment variable names
  GIT_PUSH_OPTION_{0,1,..} yourself
- cleanup of the temporary files is hard to do reliably
- we have race conditions with multiple clients pushing, hence we'd need
  to use mkstemp. That's not too bad, but still.

Use environment variables, but restrict to key/value pairs
==========================================================
(When the user pushes a push option `foo=bar`, we'd
GIT_PUSH_OPTION_foo=bar)
+ very easy to parse for a simple model of push options
- it's not sufficient for more elaborate models, e.g.
  it doesn't allow doubles (e.g. cc=reviewer@email)

Present the options in different environment variables
======================================================
(This is implemented)
* harder to parse as a user, but we have a sample hook for that.
- doesn't allow binary files
+ allows the same option twice, i.e. is not restrictive about
  options, except for binary files.
+ doesn't clutter a remote directory with (possibly stale)
  temporary files

As we first want to focus on getting simple strings to work
reliably, we go with the last option for now. If we want to
do transmission of binaries later, we can just attach a
'side-channel', e.g. "any push option that contains a '\0' is
put into a file instead of the environment variable and we'd
have new GIT_PUSH_OPTION_FILES, GIT_PUSH_OPTION_FILENAME_{0,1,..}
environment variables".

[1] 'Shellshock' https://lwn.net/Articles/614218/

Signed-off-by: Stefan Beller <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
stefanbeller authored and gitster committed Jul 14, 2016
1 parent 5c589a7 commit 77a9745
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 13 deletions.
18 changes: 18 additions & 0 deletions Documentation/githooks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,15 @@ Both standard output and standard error output are forwarded to
'git send-pack' on the other end, so you can simply `echo` messages
for the user.

The number of push options given on the command line of
`git push --push-option=...` can be read from the environment
variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
If it is negotiated to not use the push options phase, the
environment variables will not be set. If the client selects
to use push options, but doesn't transmit any, the count variable
will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.

[[update]]
update
~~~~~~
Expand Down Expand Up @@ -322,6 +331,15 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
directory in Git distribution, which implements sending commit
emails.

The number of push options given on the command line of
`git push --push-option=...` can be read from the environment
variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
If it is negotiated to not use the push options phase, the
environment variables will not be set. If the client selects
to use push options, but doesn't transmit any, the count variable
will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.

[[post-update]]
post-update
~~~~~~~~~~~
Expand Down
47 changes: 34 additions & 13 deletions builtin/receive-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,16 @@ static void prepare_push_cert_sha1(struct child_process *proc)
}
}

struct receive_hook_feed_state {
struct command *cmd;
int skip_broken;
struct strbuf buf;
const struct string_list *push_options;
};

typedef int (*feed_fn)(void *, const char **, size_t *);
static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
static int run_and_feed_hook(const char *hook_name, feed_fn feed,
struct receive_hook_feed_state *feed_state)
{
struct child_process proc = CHILD_PROCESS_INIT;
struct async muxer;
Expand All @@ -567,6 +575,16 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
if (feed_state->push_options) {
int i;
for (i = 0; i < feed_state->push_options->nr; i++)
argv_array_pushf(&proc.env_array,
"GIT_PUSH_OPTION_%d=%s", i,
feed_state->push_options->items[i].string);
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
feed_state->push_options->nr);
} else
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");

if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
Expand Down Expand Up @@ -606,12 +624,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
return finish_command(&proc);
}

struct receive_hook_feed_state {
struct command *cmd;
int skip_broken;
struct strbuf buf;
};

static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
{
struct receive_hook_feed_state *state = state_;
Expand All @@ -634,8 +646,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
return 0;
}

static int run_receive_hook(struct command *commands, const char *hook_name,
int skip_broken)
static int run_receive_hook(struct command *commands,
const char *hook_name,
int skip_broken,
const struct string_list *push_options)
{
struct receive_hook_feed_state state;
int status;
Expand All @@ -646,6 +660,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
state.push_options = push_options;
status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
strbuf_release(&state.buf);
return status;
Expand Down Expand Up @@ -1316,7 +1331,8 @@ static void execute_commands_atomic(struct command *commands,

static void execute_commands(struct command *commands,
const char *unpacker_error,
struct shallow_info *si)
struct shallow_info *si,
const struct string_list *push_options)
{
struct command *cmd;
unsigned char sha1[20];
Expand All @@ -1335,7 +1351,7 @@ static void execute_commands(struct command *commands,

reject_updates_to_hidden(commands);

if (run_receive_hook(commands, "pre-receive", 0)) {
if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "pre-receive hook declined";
Expand Down Expand Up @@ -1756,6 +1772,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)

if ((commands = read_head_info(&shallow)) != NULL) {
const char *unpack_status = NULL;
struct string_list push_options = STRING_LIST_INIT_DUP;

prepare_shallow_info(&si, &shallow);
if (!si.nr_ours && !si.nr_theirs)
Expand All @@ -1764,13 +1781,17 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unpack_status = unpack_with_sideband(&si);
update_shallow_info(commands, &si, &ref);
}
execute_commands(commands, unpack_status, &si);
execute_commands(commands, unpack_status, &si,
&push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
report(commands, unpack_status);
run_receive_hook(commands, "post-receive", 1);
run_receive_hook(commands, "post-receive", 1,
&push_options);
run_update_post_hook(commands);
if (push_options.nr)
string_list_clear(&push_options, 0);
if (auto_gc) {
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
Expand Down
24 changes: 24 additions & 0 deletions templates/hooks--pre-receive.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".

if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

0 comments on commit 77a9745

Please sign in to comment.