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>
This commit is contained in:
Ben Peart 2016-05-24 00:32:38 +00:00 коммит произвёл Johannes Schindelin
Родитель 24bcae8eab
Коммит 51870be45e
4 изменённых файлов: 201 добавлений и 4 удалений

84
git.c
Просмотреть файл

@ -15,6 +15,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)
@ -427,6 +429,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;
@ -462,16 +525,21 @@ 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);
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;
@ -749,13 +817,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
@ -766,6 +837,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)
@ -873,6 +946,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:
@ -898,10 +972,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]))

55
hook.c
Просмотреть файл

@ -1,5 +1,6 @@
#include "git-compat-util.h"
#include "abspath.h"
#include "environment.h"
#include "advice.h"
#include "gettext.h"
#include "hook.h"
@ -35,6 +36,54 @@ static int identical_to_template_hook(const char *name, const char *path)
return ret;
}
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;
@ -42,7 +91,11 @@ const char *find_hook(const char *name)
int found_hook;
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;
found_hook = access(path.buf, X_OK) >= 0;
#ifdef STRIP_EXTENSION
if (!found_hook) {

34
t/t0400-pre-command-hook.sh Executable file
Просмотреть файл

@ -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
t/t0401-post-command-hook.sh Executable file
Просмотреть файл

@ -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