Skip to content

Commit 6f2a13e

Browse files
Ben Peartdscho
authored andcommitted
gvfs: add global command pre and post hook procs
This adds hard-coded call to GVFS.hooks.exe before and after each Git command runs. To make sure that this is only called on repositories cloned with GVFS, we test for the tell-tale .gvfs. 2021-10-30: Recent movement of find_hook() to hook.c required moving these changes out of run-command.c to hook.c. Signed-off-by: Ben Peart <Ben.Peart@microsoft.com>
1 parent 327dd3a commit 6f2a13e

File tree

4 files changed

+197
-4
lines changed

4 files changed

+197
-4
lines changed

git.c

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "run-command.h"
66
#include "alias.h"
77
#include "shallow.h"
8+
#include "dir.h"
9+
#include "hook.h"
810

911
#define RUN_SETUP (1<<0)
1012
#define RUN_SETUP_GENTLY (1<<1)
@@ -417,6 +419,67 @@ static int handle_alias(int *argcp, const char ***argv)
417419
return ret;
418420
}
419421

422+
/* Runs pre/post-command hook */
423+
static struct strvec sargv = STRVEC_INIT;
424+
static int run_post_hook = 0;
425+
static int exit_code = -1;
426+
427+
static int run_pre_command_hook(const char **argv)
428+
{
429+
char *lock;
430+
int ret = 0;
431+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
432+
433+
/*
434+
* Ensure the global pre/post command hook is only called for
435+
* the outer command and not when git is called recursively
436+
* or spawns multiple commands (like with the alias command)
437+
*/
438+
lock = getenv("COMMAND_HOOK_LOCK");
439+
if (lock && !strcmp(lock, "true"))
440+
return 0;
441+
setenv("COMMAND_HOOK_LOCK", "true", 1);
442+
443+
/* call the hook proc */
444+
strvec_pushv(&sargv, argv);
445+
strvec_pushv(&opt.args, sargv.v);
446+
ret = run_hooks_opt("pre-command", &opt);
447+
448+
if (!ret)
449+
run_post_hook = 1;
450+
return ret;
451+
}
452+
453+
static int run_post_command_hook(void)
454+
{
455+
char *lock;
456+
int ret = 0;
457+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
458+
459+
/*
460+
* Only run post_command if pre_command succeeded in this process
461+
*/
462+
if (!run_post_hook)
463+
return 0;
464+
lock = getenv("COMMAND_HOOK_LOCK");
465+
if (!lock || strcmp(lock, "true"))
466+
return 0;
467+
468+
strvec_pushv(&opt.args, sargv.v);
469+
strvec_pushf(&opt.args, "--exit_code=%u", exit_code);
470+
ret = run_hooks_opt("post-command", &opt);
471+
472+
run_post_hook = 0;
473+
strvec_clear(&sargv);
474+
setenv("COMMAND_HOOK_LOCK", "false", 1);
475+
return ret;
476+
}
477+
478+
static void post_command_hook_atexit(void)
479+
{
480+
run_post_command_hook();
481+
}
482+
420483
static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
421484
{
422485
int status, help;
@@ -457,18 +520,23 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
457520
if (!help && p->option & NEED_WORK_TREE)
458521
setup_work_tree();
459522

523+
if (run_pre_command_hook(argv))
524+
die("pre-command hook aborted command");
525+
460526
trace_argv_printf(argv, "trace: built-in: git");
461527
trace2_cmd_name(p->cmd);
462528
trace2_cmd_list_config();
463529
trace2_cmd_list_env_vars();
464530

465531
validate_cache_entries(the_repository->index);
466-
status = p->fn(argc, argv, prefix);
532+
exit_code = status = p->fn(argc, argv, prefix);
467533
validate_cache_entries(the_repository->index);
468534

469535
if (status)
470536
return status;
471537

538+
run_post_command_hook();
539+
472540
/* Somebody closed stdout? */
473541
if (fstat(fileno(stdout), &st))
474542
return 0;
@@ -749,13 +817,16 @@ static void execv_dashed_external(const char **argv)
749817
*/
750818
trace_argv_printf(cmd.args.v, "trace: exec:");
751819

820+
if (run_pre_command_hook(cmd.args.v))
821+
die("pre-command hook aborted command");
822+
752823
/*
753824
* If we fail because the command is not found, it is
754825
* OK to return. Otherwise, we just pass along the status code,
755826
* or our usual generic code if we were not even able to exec
756827
* the program.
757828
*/
758-
status = run_command(&cmd);
829+
exit_code = status = run_command(&cmd);
759830

760831
/*
761832
* If the child process ran and we are now going to exit, emit a
@@ -766,6 +837,8 @@ static void execv_dashed_external(const char **argv)
766837
exit(status);
767838
else if (errno != ENOENT)
768839
exit(128);
840+
841+
run_post_command_hook();
769842
}
770843

771844
static int run_argv(int *argcp, const char ***argv)
@@ -873,6 +946,7 @@ int cmd_main(int argc, const char **argv)
873946
}
874947

875948
trace_command_performance(argv);
949+
atexit(post_command_hook_atexit);
876950

877951
/*
878952
* "git-xxxx" is the same as "git xxxx", but we obviously:
@@ -902,10 +976,14 @@ int cmd_main(int argc, const char **argv)
902976
} else {
903977
/* The user didn't specify a command; give them help */
904978
commit_pager_choice();
979+
if (run_pre_command_hook(argv))
980+
die("pre-command hook aborted command");
905981
printf(_("usage: %s\n\n"), git_usage_string);
906982
list_common_cmds_help();
907983
printf("\n%s\n", _(git_more_info_string));
908-
exit(1);
984+
exit_code = 1;
985+
run_post_command_hook();
986+
exit(exit_code);
909987
}
910988
cmd = argv[0];
911989

hook.c

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,61 @@
33
#include "run-command.h"
44
#include "config.h"
55

6+
static int early_hooks_path_config(const char *var, const char *value, void *data)
7+
{
8+
if (!strcmp(var, "core.hookspath"))
9+
return git_config_pathname((const char **)data, var, value);
10+
11+
return 0;
12+
}
13+
14+
/* Discover the hook before setup_git_directory() was called */
15+
static const char *hook_path_early(const char *name, struct strbuf *result)
16+
{
17+
static struct strbuf hooks_dir = STRBUF_INIT;
18+
static int initialized;
19+
20+
if (initialized < 0)
21+
return NULL;
22+
23+
if (!initialized) {
24+
struct strbuf gitdir = STRBUF_INIT, commondir = STRBUF_INIT;
25+
const char *early_hooks_dir = NULL;
26+
27+
if (discover_git_directory(&commondir, &gitdir) < 0) {
28+
initialized = -1;
29+
return NULL;
30+
}
31+
32+
read_early_config(early_hooks_path_config, &early_hooks_dir);
33+
if (!early_hooks_dir)
34+
strbuf_addf(&hooks_dir, "%s/hooks/", commondir.buf);
35+
else {
36+
strbuf_add_absolute_path(&hooks_dir, early_hooks_dir);
37+
free((void *)early_hooks_dir);
38+
strbuf_addch(&hooks_dir, '/');
39+
}
40+
41+
strbuf_release(&gitdir);
42+
strbuf_release(&commondir);
43+
44+
initialized = 1;
45+
}
46+
47+
strbuf_addf(result, "%s%s", hooks_dir.buf, name);
48+
return result->buf;
49+
}
50+
651
const char *find_hook(const char *name)
752
{
853
static struct strbuf path = STRBUF_INIT;
954

1055
strbuf_reset(&path);
11-
strbuf_git_path(&path, "hooks/%s", name);
56+
if (have_git_dir())
57+
strbuf_git_path(&path, "hooks/%s", name);
58+
else if (!hook_path_early(name, &path))
59+
return NULL;
60+
1261
if (access(path.buf, X_OK) < 0) {
1362
int err = errno;
1463

t/t0400-pre-command-hook.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/sh
2+
3+
test_description='pre-command hook'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'with no hook' '
8+
echo "first" > file &&
9+
git add file &&
10+
git commit -m "first"
11+
'
12+
13+
test_expect_success 'with succeeding hook' '
14+
mkdir -p .git/hooks &&
15+
write_script .git/hooks/pre-command <<-EOF &&
16+
echo "\$*" >\$(git rev-parse --git-dir)/pre-command.out
17+
EOF
18+
echo "second" >> file &&
19+
git add file &&
20+
test "add file" = "$(cat .git/pre-command.out)" &&
21+
echo Hello | git hash-object --stdin &&
22+
test "hash-object --stdin" = "$(cat .git/pre-command.out)"
23+
'
24+
25+
test_expect_success 'with failing hook' '
26+
write_script .git/hooks/pre-command <<-EOF &&
27+
exit 1
28+
EOF
29+
echo "third" >> file &&
30+
test_must_fail git add file &&
31+
test_path_is_missing "$(cat .git/pre-command.out)"
32+
'
33+
34+
test_done

t/t0401-post-command-hook.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/sh
2+
3+
test_description='post-command hook'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'with no hook' '
8+
echo "first" > file &&
9+
git add file &&
10+
git commit -m "first"
11+
'
12+
13+
test_expect_success 'with succeeding hook' '
14+
mkdir -p .git/hooks &&
15+
write_script .git/hooks/post-command <<-EOF &&
16+
echo "\$*" >\$(git rev-parse --git-dir)/post-command.out
17+
EOF
18+
echo "second" >> file &&
19+
git add file &&
20+
test "add file --exit_code=0" = "$(cat .git/post-command.out)"
21+
'
22+
23+
test_expect_success 'with failing pre-command hook' '
24+
write_script .git/hooks/pre-command <<-EOF &&
25+
exit 1
26+
EOF
27+
echo "third" >> file &&
28+
test_must_fail git add file &&
29+
test_path_is_missing "$(cat .git/post-command.out)"
30+
'
31+
32+
test_done

0 commit comments

Comments
 (0)