Skip to content

Commit

Permalink
gvfs: add global command pre and post hook procs
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Ben Peart authored and dscho committed Nov 20, 2023
1 parent b7c8f6e commit 841fbcf
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 4 deletions.
84 changes: 81 additions & 3 deletions git.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "shallow.h"
#include "trace.h"
#include "trace2.h"
#include "dir.h"
#include "hook.h"

#define RUN_SETUP (1<<0)
#define RUN_SETUP_GENTLY (1<<1)
Expand Down Expand Up @@ -425,6 +427,67 @@ static int handle_alias(int *argcp, const char ***argv)
return ret;
}

/* Runs pre/post-command hook */
static struct strvec sargv = STRVEC_INIT;
static int run_post_hook = 0;
static int exit_code = -1;

static int run_pre_command_hook(const char **argv)
{
char *lock;
int ret = 0;
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;

/*
* Ensure the global pre/post command hook is only called for
* the outer command and not when git is called recursively
* or spawns multiple commands (like with the alias command)
*/
lock = getenv("COMMAND_HOOK_LOCK");
if (lock && !strcmp(lock, "true"))
return 0;
setenv("COMMAND_HOOK_LOCK", "true", 1);

/* call the hook proc */
strvec_pushv(&sargv, argv);
strvec_pushv(&opt.args, sargv.v);
ret = run_hooks_opt("pre-command", &opt);

if (!ret)
run_post_hook = 1;
return ret;
}

static int run_post_command_hook(void)
{
char *lock;
int ret = 0;
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;

/*
* Only run post_command if pre_command succeeded in this process
*/
if (!run_post_hook)
return 0;
lock = getenv("COMMAND_HOOK_LOCK");
if (!lock || strcmp(lock, "true"))
return 0;

strvec_pushv(&opt.args, sargv.v);
strvec_pushf(&opt.args, "--exit_code=%u", exit_code);
ret = run_hooks_opt("post-command", &opt);

run_post_hook = 0;
strvec_clear(&sargv);
setenv("COMMAND_HOOK_LOCK", "false", 1);
return ret;
}

static void post_command_hook_atexit(void)
{
run_post_command_hook();
}

static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
{
int status, help;
Expand Down Expand Up @@ -460,18 +523,23 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
if (!help && p->option & NEED_WORK_TREE)
setup_work_tree();

if (run_pre_command_hook(argv))
die("pre-command hook aborted command");

trace_argv_printf(argv, "trace: built-in: git");
trace2_cmd_name(p->cmd);
trace2_cmd_list_config();
trace2_cmd_list_env_vars();

validate_cache_entries(the_repository->index);
status = p->fn(argc, argv, prefix);
exit_code = status = p->fn(argc, argv, prefix);
validate_cache_entries(the_repository->index);

if (status)
return status;

run_post_command_hook();

/* Somebody closed stdout? */
if (fstat(fileno(stdout), &st))
return 0;
Expand Down Expand Up @@ -748,13 +816,16 @@ static void execv_dashed_external(const char **argv)
*/
trace_argv_printf(cmd.args.v, "trace: exec:");

if (run_pre_command_hook(cmd.args.v))
die("pre-command hook aborted command");

/*
* If we fail because the command is not found, it is
* OK to return. Otherwise, we just pass along the status code,
* or our usual generic code if we were not even able to exec
* the program.
*/
status = run_command(&cmd);
exit_code = status = run_command(&cmd);

/*
* If the child process ran and we are now going to exit, emit a
Expand All @@ -765,6 +836,8 @@ static void execv_dashed_external(const char **argv)
exit(status);
else if (errno != ENOENT)
exit(128);

run_post_command_hook();
}

static int run_argv(int *argcp, const char ***argv)
Expand Down Expand Up @@ -872,6 +945,7 @@ int cmd_main(int argc, const char **argv)
}

trace_command_performance(argv);
atexit(post_command_hook_atexit);

/*
* "git-xxxx" is the same as "git xxxx", but we obviously:
Expand All @@ -897,10 +971,14 @@ int cmd_main(int argc, const char **argv)
if (!argc) {
/* The user didn't specify a command; give them help */
commit_pager_choice();
if (run_pre_command_hook(argv))
die("pre-command hook aborted command");
printf(_("usage: %s\n\n"), git_usage_string);
list_common_cmds_help();
printf("\n%s\n", _(git_more_info_string));
exit(1);
exit_code = 1;
run_post_command_hook();
exit(exit_code);
}

if (!strcmp("--version", argv[0]) || !strcmp("-v", argv[0]))
Expand Down
56 changes: 55 additions & 1 deletion hook.c
Original file line number Diff line number Diff line change
@@ -1,19 +1,73 @@
#include "git-compat-util.h"
#include "abspath.h"
#include "environment.h"
#include "advice.h"
#include "gettext.h"
#include "hook.h"
#include "path.h"
#include "run-command.h"
#include "config.h"
#include "strbuf.h"
#include "setup.h"

static int early_hooks_path_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
if (!strcmp(var, "core.hookspath"))
return git_config_pathname((const char **)cb, var, value);

return 0;
}

/* Discover the hook before setup_git_directory() was called */
static const char *hook_path_early(const char *name, struct strbuf *result)
{
static struct strbuf hooks_dir = STRBUF_INIT;
static int initialized;

if (initialized < 0)
return NULL;

if (!initialized) {
struct strbuf gitdir = STRBUF_INIT, commondir = STRBUF_INIT;
const char *early_hooks_dir = NULL;

if (discover_git_directory(&commondir, &gitdir) < 0) {
strbuf_release(&gitdir);
strbuf_release(&commondir);
initialized = -1;
return NULL;
}

read_early_config(early_hooks_path_config, &early_hooks_dir);
if (!early_hooks_dir)
strbuf_addf(&hooks_dir, "%s/hooks/", commondir.buf);
else {
strbuf_add_absolute_path(&hooks_dir, early_hooks_dir);
free((void *)early_hooks_dir);
strbuf_addch(&hooks_dir, '/');
}

strbuf_release(&gitdir);
strbuf_release(&commondir);

initialized = 1;
}

strbuf_addf(result, "%s%s", hooks_dir.buf, name);
return result->buf;
}

const char *find_hook(const char *name)
{
static struct strbuf path = STRBUF_INIT;

strbuf_reset(&path);
strbuf_git_path(&path, "hooks/%s", name);
if (have_git_dir())
strbuf_git_path(&path, "hooks/%s", name);
else if (!hook_path_early(name, &path))
return NULL;

if (access(path.buf, X_OK) < 0) {
int err = errno;

Expand Down
34 changes: 34 additions & 0 deletions t/t0400-pre-command-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh

test_description='pre-command hook'

. ./test-lib.sh

test_expect_success 'with no hook' '
echo "first" > file &&
git add file &&
git commit -m "first"
'

test_expect_success 'with succeeding hook' '
mkdir -p .git/hooks &&
write_script .git/hooks/pre-command <<-EOF &&
echo "\$*" >\$(git rev-parse --git-dir)/pre-command.out
EOF
echo "second" >> file &&
git add file &&
test "add file" = "$(cat .git/pre-command.out)" &&
echo Hello | git hash-object --stdin &&
test "hash-object --stdin" = "$(cat .git/pre-command.out)"
'

test_expect_success 'with failing hook' '
write_script .git/hooks/pre-command <<-EOF &&
exit 1
EOF
echo "third" >> file &&
test_must_fail git add file &&
test_path_is_missing "$(cat .git/pre-command.out)"
'

test_done
32 changes: 32 additions & 0 deletions t/t0401-post-command-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh

test_description='post-command hook'

. ./test-lib.sh

test_expect_success 'with no hook' '
echo "first" > file &&
git add file &&
git commit -m "first"
'

test_expect_success 'with succeeding hook' '
mkdir -p .git/hooks &&
write_script .git/hooks/post-command <<-EOF &&
echo "\$*" >\$(git rev-parse --git-dir)/post-command.out
EOF
echo "second" >> file &&
git add file &&
test "add file --exit_code=0" = "$(cat .git/post-command.out)"
'

test_expect_success 'with failing pre-command hook' '
write_script .git/hooks/pre-command <<-EOF &&
exit 1
EOF
echo "third" >> file &&
test_must_fail git add file &&
test_path_is_missing "$(cat .git/post-command.out)"
'

test_done

0 comments on commit 841fbcf

Please sign in to comment.