Skip to content

Commit 4ee053c

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 564c2c4 commit 4ee053c

File tree

4 files changed

+200
-4
lines changed

4 files changed

+200
-4
lines changed

git.c

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "shallow.h"
1515
#include "trace.h"
1616
#include "trace2.h"
17+
#include "dir.h"
18+
#include "hook.h"
1719

1820
#define RUN_SETUP (1<<0)
1921
#define RUN_SETUP_GENTLY (1<<1)
@@ -425,6 +427,67 @@ static int handle_alias(int *argcp, const char ***argv)
425427
return ret;
426428
}
427429

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

526+
if (run_pre_command_hook(argv))
527+
die("pre-command hook aborted command");
528+
463529
trace_argv_printf(argv, "trace: built-in: git");
464530
trace2_cmd_name(p->cmd);
465531
trace2_cmd_list_config();
466532
trace2_cmd_list_env_vars();
467533

468534
validate_cache_entries(the_repository->index);
469-
status = p->fn(argc, argv, prefix);
535+
exit_code = status = p->fn(argc, argv, prefix);
470536
validate_cache_entries(the_repository->index);
471537

472538
if (status)
473539
return status;
474540

541+
run_post_command_hook();
542+
475543
/* Somebody closed stdout? */
476544
if (fstat(fileno(stdout), &st))
477545
return 0;
@@ -748,13 +816,16 @@ static void execv_dashed_external(const char **argv)
748816
*/
749817
trace_argv_printf(cmd.args.v, "trace: exec:");
750818

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

759830
/*
760831
* If the child process ran and we are now going to exit, emit a
@@ -765,6 +836,8 @@ static void execv_dashed_external(const char **argv)
765836
exit(status);
766837
else if (errno != ENOENT)
767838
exit(128);
839+
840+
run_post_command_hook();
768841
}
769842

770843
static int run_argv(int *argcp, const char ***argv)
@@ -872,6 +945,7 @@ int cmd_main(int argc, const char **argv)
872945
}
873946

874947
trace_command_performance(argv);
948+
atexit(post_command_hook_atexit);
875949

876950
/*
877951
* "git-xxxx" is the same as "git xxxx", but we obviously:
@@ -897,10 +971,14 @@ int cmd_main(int argc, const char **argv)
897971
if (!argc) {
898972
/* The user didn't specify a command; give them help */
899973
commit_pager_choice();
974+
if (run_pre_command_hook(argv))
975+
die("pre-command hook aborted command");
900976
printf(_("usage: %s\n\n"), git_usage_string);
901977
list_common_cmds_help();
902978
printf("\n%s\n", _(git_more_info_string));
903-
exit(1);
979+
exit_code = 1;
980+
run_post_command_hook();
981+
exit(exit_code);
904982
}
905983

906984
if (!strcmp("--version", argv[0]) || !strcmp("-v", argv[0]))

hook.c

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,71 @@
11
#include "git-compat-util.h"
22
#include "abspath.h"
3+
#include "environment.h"
34
#include "advice.h"
45
#include "gettext.h"
56
#include "hook.h"
67
#include "path.h"
78
#include "run-command.h"
89
#include "config.h"
910
#include "strbuf.h"
11+
#include "setup.h"
12+
13+
static int early_hooks_path_config(const char *var, const char *value,
14+
const struct config_context *ctx, void *cb)
15+
{
16+
if (!strcmp(var, "core.hookspath"))
17+
return git_config_pathname((const char **)cb, var, value);
18+
19+
return 0;
20+
}
21+
22+
/* Discover the hook before setup_git_directory() was called */
23+
static const char *hook_path_early(const char *name, struct strbuf *result)
24+
{
25+
static struct strbuf hooks_dir = STRBUF_INIT;
26+
static int initialized;
27+
28+
if (initialized < 0)
29+
return NULL;
30+
31+
if (!initialized) {
32+
struct strbuf gitdir = STRBUF_INIT, commondir = STRBUF_INIT;
33+
const char *early_hooks_dir = NULL;
34+
35+
if (discover_git_directory(&commondir, &gitdir) < 0) {
36+
initialized = -1;
37+
return NULL;
38+
}
39+
40+
read_early_config(early_hooks_path_config, &early_hooks_dir);
41+
if (!early_hooks_dir)
42+
strbuf_addf(&hooks_dir, "%s/hooks/", commondir.buf);
43+
else {
44+
strbuf_add_absolute_path(&hooks_dir, early_hooks_dir);
45+
free((void *)early_hooks_dir);
46+
strbuf_addch(&hooks_dir, '/');
47+
}
48+
49+
strbuf_release(&gitdir);
50+
strbuf_release(&commondir);
51+
52+
initialized = 1;
53+
}
54+
55+
strbuf_addf(result, "%s%s", hooks_dir.buf, name);
56+
return result->buf;
57+
}
1058

1159
const char *find_hook(const char *name)
1260
{
1361
static struct strbuf path = STRBUF_INIT;
1462

1563
strbuf_reset(&path);
16-
strbuf_git_path(&path, "hooks/%s", name);
64+
if (have_git_dir())
65+
strbuf_git_path(&path, "hooks/%s", name);
66+
else if (!hook_path_early(name, &path))
67+
return NULL;
68+
1769
if (access(path.buf, X_OK) < 0) {
1870
int err = errno;
1971

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)