Skip to content

Commit 77a9745

Browse files
stefanbellergitster
authored andcommitted
push options: {pre,post}-receive hook learns about push options
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]>
1 parent 5c589a7 commit 77a9745

File tree

3 files changed

+76
-13
lines changed

3 files changed

+76
-13
lines changed

Documentation/githooks.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,15 @@ Both standard output and standard error output are forwarded to
247247
'git send-pack' on the other end, so you can simply `echo` messages
248248
for the user.
249249

250+
The number of push options given on the command line of
251+
`git push --push-option=...` can be read from the environment
252+
variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
253+
found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
254+
If it is negotiated to not use the push options phase, the
255+
environment variables will not be set. If the client selects
256+
to use push options, but doesn't transmit any, the count variable
257+
will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
258+
250259
[[update]]
251260
update
252261
~~~~~~
@@ -322,6 +331,15 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
322331
directory in Git distribution, which implements sending commit
323332
emails.
324333

334+
The number of push options given on the command line of
335+
`git push --push-option=...` can be read from the environment
336+
variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
337+
found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
338+
If it is negotiated to not use the push options phase, the
339+
environment variables will not be set. If the client selects
340+
to use push options, but doesn't transmit any, the count variable
341+
will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
342+
325343
[[post-update]]
326344
post-update
327345
~~~~~~~~~~~

builtin/receive-pack.c

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,16 @@ static void prepare_push_cert_sha1(struct child_process *proc)
550550
}
551551
}
552552

553+
struct receive_hook_feed_state {
554+
struct command *cmd;
555+
int skip_broken;
556+
struct strbuf buf;
557+
const struct string_list *push_options;
558+
};
559+
553560
typedef int (*feed_fn)(void *, const char **, size_t *);
554-
static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
561+
static int run_and_feed_hook(const char *hook_name, feed_fn feed,
562+
struct receive_hook_feed_state *feed_state)
555563
{
556564
struct child_process proc = CHILD_PROCESS_INIT;
557565
struct async muxer;
@@ -567,6 +575,16 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
567575
proc.argv = argv;
568576
proc.in = -1;
569577
proc.stdout_to_stderr = 1;
578+
if (feed_state->push_options) {
579+
int i;
580+
for (i = 0; i < feed_state->push_options->nr; i++)
581+
argv_array_pushf(&proc.env_array,
582+
"GIT_PUSH_OPTION_%d=%s", i,
583+
feed_state->push_options->items[i].string);
584+
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
585+
feed_state->push_options->nr);
586+
} else
587+
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
570588

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

609-
struct receive_hook_feed_state {
610-
struct command *cmd;
611-
int skip_broken;
612-
struct strbuf buf;
613-
};
614-
615627
static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
616628
{
617629
struct receive_hook_feed_state *state = state_;
@@ -634,8 +646,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
634646
return 0;
635647
}
636648

637-
static int run_receive_hook(struct command *commands, const char *hook_name,
638-
int skip_broken)
649+
static int run_receive_hook(struct command *commands,
650+
const char *hook_name,
651+
int skip_broken,
652+
const struct string_list *push_options)
639653
{
640654
struct receive_hook_feed_state state;
641655
int status;
@@ -646,6 +660,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
646660
if (feed_receive_hook(&state, NULL, NULL))
647661
return 0;
648662
state.cmd = commands;
663+
state.push_options = push_options;
649664
status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
650665
strbuf_release(&state.buf);
651666
return status;
@@ -1316,7 +1331,8 @@ static void execute_commands_atomic(struct command *commands,
13161331

13171332
static void execute_commands(struct command *commands,
13181333
const char *unpacker_error,
1319-
struct shallow_info *si)
1334+
struct shallow_info *si,
1335+
const struct string_list *push_options)
13201336
{
13211337
struct command *cmd;
13221338
unsigned char sha1[20];
@@ -1335,7 +1351,7 @@ static void execute_commands(struct command *commands,
13351351

13361352
reject_updates_to_hidden(commands);
13371353

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

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

17601777
prepare_shallow_info(&si, &shallow);
17611778
if (!si.nr_ours && !si.nr_theirs)
@@ -1764,13 +1781,17 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
17641781
unpack_status = unpack_with_sideband(&si);
17651782
update_shallow_info(commands, &si, &ref);
17661783
}
1767-
execute_commands(commands, unpack_status, &si);
1784+
execute_commands(commands, unpack_status, &si,
1785+
&push_options);
17681786
if (pack_lockfile)
17691787
unlink_or_warn(pack_lockfile);
17701788
if (report_status)
17711789
report(commands, unpack_status);
1772-
run_receive_hook(commands, "post-receive", 1);
1790+
run_receive_hook(commands, "post-receive", 1,
1791+
&push_options);
17731792
run_update_post_hook(commands);
1793+
if (push_options.nr)
1794+
string_list_clear(&push_options, 0);
17741795
if (auto_gc) {
17751796
const char *argv_gc_auto[] = {
17761797
"gc", "--auto", "--quiet", NULL,

templates/hooks--pre-receive.sample

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to make use of push options.
4+
# The example simply echoes all push options that start with 'echoback='
5+
# and rejects all pushes when the "reject" push option is used.
6+
#
7+
# To enable this hook, rename this file to "pre-receive".
8+
9+
if test -n "$GIT_PUSH_OPTION_COUNT"
10+
then
11+
i=0
12+
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
13+
do
14+
eval "value=\$GIT_PUSH_OPTION_$i"
15+
case "$value" in
16+
echoback=*)
17+
echo "echo from the pre-receive-hook: ${value#*=}" >&2
18+
;;
19+
reject)
20+
exit 1
21+
esac
22+
i=$((i + 1))
23+
done
24+
fi

0 commit comments

Comments
 (0)