Skip to content

Commit bf15ebc

Browse files
Ben Peartdscho
Ben Peart
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 <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent d5b9f91 commit bf15ebc

File tree

5 files changed

+205
-4
lines changed

5 files changed

+205
-4
lines changed

git.c

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "shallow.h"
1818
#include "trace.h"
1919
#include "trace2.h"
20+
#include "dir.h"
21+
#include "hook.h"
2022

2123
#define RUN_SETUP (1<<0)
2224
#define RUN_SETUP_GENTLY (1<<1)
@@ -437,6 +439,67 @@ static int handle_alias(struct strvec *args)
437439
return ret;
438440
}
439441

442+
/* Runs pre/post-command hook */
443+
static struct strvec sargv = STRVEC_INIT;
444+
static int run_post_hook = 0;
445+
static int exit_code = -1;
446+
447+
static int run_pre_command_hook(struct repository *r, const char **argv)
448+
{
449+
char *lock;
450+
int ret = 0;
451+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
452+
453+
/*
454+
* Ensure the global pre/post command hook is only called for
455+
* the outer command and not when git is called recursively
456+
* or spawns multiple commands (like with the alias command)
457+
*/
458+
lock = getenv("COMMAND_HOOK_LOCK");
459+
if (lock && !strcmp(lock, "true"))
460+
return 0;
461+
setenv("COMMAND_HOOK_LOCK", "true", 1);
462+
463+
/* call the hook proc */
464+
strvec_pushv(&sargv, argv);
465+
strvec_pushv(&opt.args, sargv.v);
466+
ret = run_hooks_opt(r, "pre-command", &opt);
467+
468+
if (!ret)
469+
run_post_hook = 1;
470+
return ret;
471+
}
472+
473+
static int run_post_command_hook(struct repository *r)
474+
{
475+
char *lock;
476+
int ret = 0;
477+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
478+
479+
/*
480+
* Only run post_command if pre_command succeeded in this process
481+
*/
482+
if (!run_post_hook)
483+
return 0;
484+
lock = getenv("COMMAND_HOOK_LOCK");
485+
if (!lock || strcmp(lock, "true"))
486+
return 0;
487+
488+
strvec_pushv(&opt.args, sargv.v);
489+
strvec_pushf(&opt.args, "--exit_code=%u", exit_code);
490+
ret = run_hooks_opt(r, "post-command", &opt);
491+
492+
run_post_hook = 0;
493+
strvec_clear(&sargv);
494+
setenv("COMMAND_HOOK_LOCK", "false", 1);
495+
return ret;
496+
}
497+
498+
static void post_command_hook_atexit(void)
499+
{
500+
run_post_command_hook(the_repository);
501+
}
502+
440503
static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct repository *repo)
441504
{
442505
int status, help;
@@ -473,16 +536,21 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct
473536
if (!help && p->option & NEED_WORK_TREE)
474537
setup_work_tree();
475538

539+
if (run_pre_command_hook(the_repository, argv))
540+
die("pre-command hook aborted command");
541+
476542
trace_argv_printf(argv, "trace: built-in: git");
477543
trace2_cmd_name(p->cmd);
478544

479545
validate_cache_entries(repo->index);
480-
status = p->fn(argc, argv, prefix, no_repo ? NULL : repo);
546+
exit_code = status = p->fn(argc, argv, prefix, no_repo ? NULL : repo);
481547
validate_cache_entries(repo->index);
482548

483549
if (status)
484550
return status;
485551

552+
run_post_command_hook(the_repository);
553+
486554
/* Somebody closed stdout? */
487555
if (fstat(fileno(stdout), &st))
488556
return 0;
@@ -770,13 +838,16 @@ static void execv_dashed_external(const char **argv)
770838
*/
771839
trace_argv_printf(cmd.args.v, "trace: exec:");
772840

841+
if (run_pre_command_hook(the_repository, cmd.args.v))
842+
die("pre-command hook aborted command");
843+
773844
/*
774845
* If we fail because the command is not found, it is
775846
* OK to return. Otherwise, we just pass along the status code,
776847
* or our usual generic code if we were not even able to exec
777848
* the program.
778849
*/
779-
status = run_command(&cmd);
850+
exit_code = status = run_command(&cmd);
780851

781852
/*
782853
* If the child process ran and we are now going to exit, emit a
@@ -787,6 +858,8 @@ static void execv_dashed_external(const char **argv)
787858
exit(status);
788859
else if (errno != ENOENT)
789860
exit(128);
861+
862+
run_post_command_hook(the_repository);
790863
}
791864

792865
static int run_argv(struct strvec *args)
@@ -894,6 +967,7 @@ int cmd_main(int argc, const char **argv)
894967
}
895968

896969
trace_command_performance(argv);
970+
atexit(post_command_hook_atexit);
897971

898972
/*
899973
* "git-xxxx" is the same as "git xxxx", but we obviously:
@@ -921,10 +995,14 @@ int cmd_main(int argc, const char **argv)
921995
if (!argc) {
922996
/* The user didn't specify a command; give them help */
923997
commit_pager_choice();
998+
if (run_pre_command_hook(the_repository, argv))
999+
die("pre-command hook aborted command");
9241000
printf(_("usage: %s\n\n"), git_usage_string);
9251001
list_common_cmds_help();
9261002
printf("\n%s\n", _(git_more_info_string));
927-
exit(1);
1003+
exit_code = 1;
1004+
run_post_command_hook(the_repository);
1005+
exit(exit_code);
9281006
}
9291007

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

hook.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
#define USE_THE_REPOSITORY_VARIABLE
2+
13
#include "git-compat-util.h"
24
#include "abspath.h"
5+
#include "environment.h"
36
#include "advice.h"
47
#include "gettext.h"
58
#include "hook.h"
@@ -10,14 +13,66 @@
1013
#include "environment.h"
1114
#include "setup.h"
1215

16+
static int early_hooks_path_config(const char *var, const char *value,
17+
const struct config_context *ctx UNUSED, void *cb)
18+
{
19+
if (!strcmp(var, "core.hookspath"))
20+
return git_config_pathname((char **)cb, var, value);
21+
22+
return 0;
23+
}
24+
25+
/* Discover the hook before setup_git_directory() was called */
26+
static const char *hook_path_early(const char *name, struct strbuf *result)
27+
{
28+
static struct strbuf hooks_dir = STRBUF_INIT;
29+
static int initialized;
30+
31+
if (initialized < 0)
32+
return NULL;
33+
34+
if (!initialized) {
35+
struct strbuf gitdir = STRBUF_INIT, commondir = STRBUF_INIT;
36+
char *early_hooks_dir = NULL;
37+
38+
if (discover_git_directory(&commondir, &gitdir) < 0) {
39+
strbuf_release(&gitdir);
40+
strbuf_release(&commondir);
41+
initialized = -1;
42+
return NULL;
43+
}
44+
45+
read_early_config(the_repository, early_hooks_path_config, &early_hooks_dir);
46+
if (!early_hooks_dir)
47+
strbuf_addf(&hooks_dir, "%s/hooks/", commondir.buf);
48+
else {
49+
strbuf_add_absolute_path(&hooks_dir, early_hooks_dir);
50+
free(early_hooks_dir);
51+
strbuf_addch(&hooks_dir, '/');
52+
}
53+
54+
strbuf_release(&gitdir);
55+
strbuf_release(&commondir);
56+
57+
initialized = 1;
58+
}
59+
60+
strbuf_addf(result, "%s%s", hooks_dir.buf, name);
61+
return result->buf;
62+
}
63+
1364
const char *find_hook(struct repository *r, const char *name)
1465
{
1566
static struct strbuf path = STRBUF_INIT;
1667

1768
int found_hook;
1869

1970
strbuf_reset(&path);
20-
strbuf_repo_git_path(&path, r, "hooks/%s", name);
71+
if (have_git_dir())
72+
strbuf_repo_git_path(&path, r, "hooks/%s", name);
73+
else if (!hook_path_early(name, &path))
74+
return NULL;
75+
2176
found_hook = access(path.buf, X_OK) >= 0;
2277
#ifdef STRIP_EXTENSION
2378
if (!found_hook) {

t/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ integration_tests = [
150150
't0301-credential-cache.sh',
151151
't0302-credential-store.sh',
152152
't0303-credential-external.sh',
153+
't0400-pre-command-hook.sh',
154+
't0401-post-command-hook.sh',
153155
't0410-partial-clone.sh',
154156
't0411-clone-from-partial.sh',
155157
't0450-txt-doc-vs-help.sh',

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)