зеркало из https://github.com/microsoft/git.git
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:
Родитель
24bcae8eab
Коммит
51870be45e
84
git.c
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
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) {
|
||||
|
|
|
@ -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
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче