Merge branch 'ab/config-based-hooks-2'
More "config-based hooks". * ab/config-based-hooks-2: run-command: remove old run_hook_{le,ve}() hook API receive-pack: convert push-to-checkout hook to hook.h read-cache: convert post-index-change to use hook.h commit: convert {pre-commit,prepare-commit-msg} hook to hook.h git-p4: use 'git hook' to run hooks send-email: use 'git hook run' for 'sendemail-validate' git hook run: add an --ignore-missing flag hooks: convert worktree 'post-checkout' hook to hook library hooks: convert non-worktree 'post-checkout' hook to hook library merge: convert post-merge to use hook.h am: convert applypatch-msg to use hook.h rebase: convert pre-rebase to use hook.h hook API: add a run_hooks_l() wrapper am: convert {pre,post}-applypatch to use hook.h gc: use hook library for pre-auto-gc hook hook API: add a run_hooks() wrapper hook: add 'run' subcommand
This commit is contained in:
commit
c70bc338e9
1
.gitignore
vendored
1
.gitignore
vendored
@ -77,6 +77,7 @@
|
||||
/git-grep
|
||||
/git-hash-object
|
||||
/git-help
|
||||
/git-hook
|
||||
/git-http-backend
|
||||
/git-http-fetch
|
||||
/git-http-push
|
||||
|
45
Documentation/git-hook.txt
Normal file
45
Documentation/git-hook.txt
Normal file
@ -0,0 +1,45 @@
|
||||
git-hook(1)
|
||||
===========
|
||||
|
||||
NAME
|
||||
----
|
||||
git-hook - Run git hooks
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
A command interface to running git hooks (see linkgit:githooks[5]),
|
||||
for use by other scripted git commands.
|
||||
|
||||
SUBCOMMANDS
|
||||
-----------
|
||||
|
||||
run::
|
||||
Run the `<hook-name>` hook. See linkgit:githooks[5] for
|
||||
supported hook names.
|
||||
+
|
||||
|
||||
Any positional arguments to the hook should be passed after a
|
||||
mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
|
||||
linkgit:githooks[5] for arguments hooks might expect (if any).
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
--ignore-missing::
|
||||
Ignore any missing hook by quietly returning zero. Used for
|
||||
tools that want to do a blind one-shot run of a hook that may
|
||||
or may not be present.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:githooks[5]
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
@ -698,6 +698,10 @@ and "0" meaning they were not.
|
||||
Only one parameter should be set to "1" when the hook runs. The hook
|
||||
running passing "1", "1" should not be possible.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-hook[1]
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
||||
|
1
Makefile
1
Makefile
@ -1109,6 +1109,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
|
||||
BUILTIN_OBJS += builtin/grep.o
|
||||
BUILTIN_OBJS += builtin/hash-object.o
|
||||
BUILTIN_OBJS += builtin/help.o
|
||||
BUILTIN_OBJS += builtin/hook.o
|
||||
BUILTIN_OBJS += builtin/index-pack.o
|
||||
BUILTIN_OBJS += builtin/init-db.o
|
||||
BUILTIN_OBJS += builtin/interpret-trailers.o
|
||||
|
@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
|
||||
int cmd_grep(int argc, const char **argv, const char *prefix);
|
||||
int cmd_hash_object(int argc, const char **argv, const char *prefix);
|
||||
int cmd_help(int argc, const char **argv, const char *prefix);
|
||||
int cmd_hook(int argc, const char **argv, const char *prefix);
|
||||
int cmd_index_pack(int argc, const char **argv, const char *prefix);
|
||||
int cmd_init_db(int argc, const char **argv, const char *prefix);
|
||||
int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
|
||||
|
@ -474,7 +474,7 @@ static int run_applypatch_msg_hook(struct am_state *state)
|
||||
int ret;
|
||||
|
||||
assert(state->msg);
|
||||
ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
|
||||
ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL);
|
||||
|
||||
if (!ret) {
|
||||
FREE_AND_NULL(state->msg);
|
||||
@ -1636,7 +1636,7 @@ static void do_commit(const struct am_state *state)
|
||||
const char *reflog_msg, *author, *committer = NULL;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
if (run_hook_le(NULL, "pre-applypatch", NULL))
|
||||
if (run_hooks("pre-applypatch"))
|
||||
exit(1);
|
||||
|
||||
if (write_cache_as_tree(&tree, 0, NULL))
|
||||
@ -1688,7 +1688,7 @@ static void do_commit(const struct am_state *state)
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
run_hook_le(NULL, "post-applypatch", NULL);
|
||||
run_hooks("post-applypatch");
|
||||
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "config.h"
|
||||
#include "diff.h"
|
||||
#include "dir.h"
|
||||
#include "hook.h"
|
||||
#include "ll-merge.h"
|
||||
#include "lockfile.h"
|
||||
#include "merge-recursive.h"
|
||||
@ -114,7 +115,7 @@ static void branch_info_release(struct branch_info *info)
|
||||
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
|
||||
int changed)
|
||||
{
|
||||
return run_hook_le(NULL, "post-checkout",
|
||||
return run_hooks_l("post-checkout",
|
||||
oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
|
||||
oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
|
||||
changed ? "1" : "0", NULL);
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "connected.h"
|
||||
#include "packfile.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "hook.h"
|
||||
|
||||
/*
|
||||
* Overall FIXMEs:
|
||||
@ -705,7 +706,7 @@ static int checkout(int submodule_progress)
|
||||
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
|
||||
die(_("unable to write new index file"));
|
||||
|
||||
err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
|
||||
err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()),
|
||||
oid_to_hex(&oid), "1", NULL);
|
||||
|
||||
if (!err && (option_recurse_submodules.nr > 0)) {
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "remote.h"
|
||||
#include "object-store.h"
|
||||
#include "exec-cmd.h"
|
||||
#include "hook.h"
|
||||
|
||||
#define FAILED_RUN "failed to run %s"
|
||||
|
||||
@ -394,7 +395,7 @@ static int need_to_gc(void)
|
||||
else
|
||||
return 0;
|
||||
|
||||
if (run_hook_le(NULL, "pre-auto-gc", NULL))
|
||||
if (run_hooks("pre-auto-gc"))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
84
builtin/hook.c
Normal file
84
builtin/hook.c
Normal file
@ -0,0 +1,84 @@
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "config.h"
|
||||
#include "hook.h"
|
||||
#include "parse-options.h"
|
||||
#include "strbuf.h"
|
||||
#include "strvec.h"
|
||||
|
||||
#define BUILTIN_HOOK_RUN_USAGE \
|
||||
N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
|
||||
|
||||
static const char * const builtin_hook_usage[] = {
|
||||
BUILTIN_HOOK_RUN_USAGE,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char * const builtin_hook_run_usage[] = {
|
||||
BUILTIN_HOOK_RUN_USAGE,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int run(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int i;
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
int ignore_missing = 0;
|
||||
const char *hook_name;
|
||||
struct option run_options[] = {
|
||||
OPT_BOOL(0, "ignore-missing", &ignore_missing,
|
||||
N_("silently ignore missing requested <hook-name>")),
|
||||
OPT_END(),
|
||||
};
|
||||
int ret;
|
||||
|
||||
argc = parse_options(argc, argv, prefix, run_options,
|
||||
builtin_hook_run_usage,
|
||||
PARSE_OPT_KEEP_DASHDASH);
|
||||
|
||||
if (!argc)
|
||||
goto usage;
|
||||
|
||||
/*
|
||||
* Having a -- for "run" when providing <hook-args> is
|
||||
* mandatory.
|
||||
*/
|
||||
if (argc > 1 && strcmp(argv[1], "--") &&
|
||||
strcmp(argv[1], "--end-of-options"))
|
||||
goto usage;
|
||||
|
||||
/* Add our arguments, start after -- */
|
||||
for (i = 2 ; i < argc; i++)
|
||||
strvec_push(&opt.args, argv[i]);
|
||||
|
||||
/* Need to take into account core.hooksPath */
|
||||
git_config(git_default_config, NULL);
|
||||
|
||||
hook_name = argv[0];
|
||||
if (!ignore_missing)
|
||||
opt.error_if_missing = 1;
|
||||
ret = run_hooks_opt(hook_name, &opt);
|
||||
if (ret < 0) /* error() return */
|
||||
ret = 1;
|
||||
return ret;
|
||||
usage:
|
||||
usage_with_options(builtin_hook_run_usage, run_options);
|
||||
}
|
||||
|
||||
int cmd_hook(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct option builtin_hook_options[] = {
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
argc = parse_options(argc, argv, NULL, builtin_hook_options,
|
||||
builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
|
||||
if (!argc)
|
||||
goto usage;
|
||||
|
||||
if (!strcmp(argv[0], "run"))
|
||||
return run(argc, argv, prefix);
|
||||
|
||||
usage:
|
||||
usage_with_options(builtin_hook_usage, builtin_hook_options);
|
||||
}
|
@ -490,7 +490,7 @@ static void finish(struct commit *head_commit,
|
||||
}
|
||||
|
||||
/* Run a post-merge hook */
|
||||
run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
|
||||
run_hooks_l("post-merge", squash ? "1" : "0", NULL);
|
||||
|
||||
apply_autostash(git_path_merge_autostash(the_repository));
|
||||
strbuf_release(&reflog_message);
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "sequencer.h"
|
||||
#include "rebase-interactive.h"
|
||||
#include "reset.h"
|
||||
#include "hook.h"
|
||||
|
||||
#define DEFAULT_REFLOG_ACTION "rebase"
|
||||
|
||||
@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||
|
||||
/* If a hook exists, give it a chance to interrupt*/
|
||||
if (!ok_to_skip_pre_rebase &&
|
||||
run_hook_le(NULL, "pre-rebase", options.upstream_arg,
|
||||
run_hooks_l("pre-rebase", options.upstream_arg,
|
||||
argc ? argv[0] : NULL, NULL))
|
||||
die(_("The pre-rebase hook refused to rebase."));
|
||||
|
||||
|
@ -1411,9 +1411,12 @@ static const char *push_to_checkout(unsigned char *hash,
|
||||
struct strvec *env,
|
||||
const char *work_tree)
|
||||
{
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
|
||||
strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
|
||||
if (run_hook_le(env->v, push_to_checkout_hook,
|
||||
hash_to_hex(hash), NULL))
|
||||
strvec_pushv(&opt.env, env->v);
|
||||
strvec_push(&opt.args, hash_to_hex(hash));
|
||||
if (run_hooks_opt(push_to_checkout_hook, &opt))
|
||||
return "push-to-checkout hook declined";
|
||||
else
|
||||
return NULL;
|
||||
|
@ -382,21 +382,17 @@ done:
|
||||
* is_junk is cleared, but do return appropriate code when hook fails.
|
||||
*/
|
||||
if (!ret && opts->checkout) {
|
||||
const char *hook = find_hook("post-checkout");
|
||||
if (hook) {
|
||||
const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
cp.no_stdin = 1;
|
||||
cp.stdout_to_stderr = 1;
|
||||
cp.dir = path;
|
||||
strvec_pushv(&cp.env_array, env);
|
||||
cp.trace2_hook_name = "post-checkout";
|
||||
strvec_pushl(&cp.args, absolute_path(hook),
|
||||
oid_to_hex(null_oid()),
|
||||
oid_to_hex(&commit->object.oid),
|
||||
"1", NULL);
|
||||
ret = run_command(&cp);
|
||||
}
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
|
||||
strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
|
||||
strvec_pushl(&opt.args,
|
||||
oid_to_hex(null_oid()),
|
||||
oid_to_hex(&commit->object.oid),
|
||||
"1",
|
||||
NULL);
|
||||
opt.dir = path;
|
||||
|
||||
ret = run_hooks_opt("post-checkout", &opt);
|
||||
}
|
||||
|
||||
strvec_clear(&child_env);
|
||||
|
@ -103,6 +103,7 @@ git-grep mainporcelain info
|
||||
git-gui mainporcelain
|
||||
git-hash-object plumbingmanipulators
|
||||
git-help ancillaryinterrogators complete
|
||||
git-hook purehelpers
|
||||
git-http-backend synchingrepositories
|
||||
git-http-fetch synchelpers
|
||||
git-http-push synchelpers
|
||||
|
15
commit.c
15
commit.c
@ -21,6 +21,7 @@
|
||||
#include "commit-reach.h"
|
||||
#include "run-command.h"
|
||||
#include "shallow.h"
|
||||
#include "hook.h"
|
||||
|
||||
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
|
||||
|
||||
@ -1714,22 +1715,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
|
||||
int run_commit_hook(int editor_is_used, const char *index_file,
|
||||
const char *name, ...)
|
||||
{
|
||||
struct strvec hook_env = STRVEC_INIT;
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
va_list args;
|
||||
int ret;
|
||||
const char *arg;
|
||||
|
||||
strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
|
||||
strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
|
||||
|
||||
/*
|
||||
* Let the hook know that no editor will be launched.
|
||||
*/
|
||||
if (!editor_is_used)
|
||||
strvec_push(&hook_env, "GIT_EDITOR=:");
|
||||
strvec_push(&opt.env, "GIT_EDITOR=:");
|
||||
|
||||
va_start(args, name);
|
||||
ret = run_hook_ve(hook_env.v, name, args);
|
||||
while ((arg = va_arg(args, const char *)))
|
||||
strvec_push(&opt.args, arg);
|
||||
va_end(args);
|
||||
strvec_clear(&hook_env);
|
||||
|
||||
return ret;
|
||||
return run_hooks_opt(name, &opt);
|
||||
}
|
||||
|
70
git-p4.py
70
git-p4.py
@ -220,70 +220,12 @@ def decode_path(path):
|
||||
|
||||
def run_git_hook(cmd, param=[]):
|
||||
"""Execute a hook if the hook exists."""
|
||||
if verbose:
|
||||
sys.stderr.write("Looking for hook: %s\n" % cmd)
|
||||
sys.stderr.flush()
|
||||
|
||||
hooks_path = gitConfig("core.hooksPath")
|
||||
if len(hooks_path) <= 0:
|
||||
hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
|
||||
|
||||
if not isinstance(param, list):
|
||||
param=[param]
|
||||
|
||||
# resolve hook file name, OS depdenent
|
||||
hook_file = os.path.join(hooks_path, cmd)
|
||||
if platform.system() == 'Windows':
|
||||
if not os.path.isfile(hook_file):
|
||||
# look for the file with an extension
|
||||
files = glob.glob(hook_file + ".*")
|
||||
if not files:
|
||||
return True
|
||||
files.sort()
|
||||
hook_file = files.pop()
|
||||
while hook_file.upper().endswith(".SAMPLE"):
|
||||
# The file is a sample hook. We don't want it
|
||||
if len(files) > 0:
|
||||
hook_file = files.pop()
|
||||
else:
|
||||
return True
|
||||
|
||||
if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
|
||||
return True
|
||||
|
||||
return run_hook_command(hook_file, param) == 0
|
||||
|
||||
def run_hook_command(cmd, param):
|
||||
"""Executes a git hook command
|
||||
cmd = the command line file to be executed. This can be
|
||||
a file that is run by OS association.
|
||||
|
||||
param = a list of parameters to pass to the cmd command
|
||||
|
||||
On windows, the extension is checked to see if it should
|
||||
be run with the Git for Windows Bash shell. If there
|
||||
is no file extension, the file is deemed a bash shell
|
||||
and will be handed off to sh.exe. Otherwise, Windows
|
||||
will be called with the shell to handle the file assocation.
|
||||
|
||||
For non Windows operating systems, the file is called
|
||||
as an executable.
|
||||
"""
|
||||
cli = [cmd] + param
|
||||
use_shell = False
|
||||
if platform.system() == 'Windows':
|
||||
(root,ext) = os.path.splitext(cmd)
|
||||
if ext == "":
|
||||
exe_path = os.environ.get("EXEPATH")
|
||||
if exe_path is None:
|
||||
exe_path = ""
|
||||
else:
|
||||
exe_path = os.path.join(exe_path, "bin")
|
||||
cli = [os.path.join(exe_path, "SH.EXE")] + cli
|
||||
else:
|
||||
use_shell = True
|
||||
return subprocess.call(cli, shell=use_shell)
|
||||
|
||||
args = ['git', 'hook', 'run', '--ignore-missing', cmd]
|
||||
if param:
|
||||
args.append("--")
|
||||
for p in param:
|
||||
args.append(p)
|
||||
return subprocess.call(args) == 0
|
||||
|
||||
def write_pipe(c, stdin, *k, **kw):
|
||||
if verbose:
|
||||
|
@ -225,13 +225,13 @@ my $multiedit;
|
||||
my $editor;
|
||||
|
||||
sub system_or_msg {
|
||||
my ($args, $msg) = @_;
|
||||
my ($args, $msg, $cmd_name) = @_;
|
||||
system(@$args);
|
||||
my $signalled = $? & 127;
|
||||
my $exit_code = $? >> 8;
|
||||
return unless $signalled or $exit_code;
|
||||
|
||||
my @sprintf_args = ($args->[0], $exit_code);
|
||||
my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
|
||||
if (defined $msg) {
|
||||
# Quiet the 'redundant' warning category, except we
|
||||
# need to support down to Perl 5.8, so we can't do a
|
||||
@ -2075,10 +2075,10 @@ sub validate_patch {
|
||||
my ($fn, $xfer_encoding) = @_;
|
||||
|
||||
if ($repo) {
|
||||
my $hook_name = 'sendemail-validate';
|
||||
my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
|
||||
require File::Spec;
|
||||
my $validate_hook = File::Spec->catfile($hooks_path,
|
||||
'sendemail-validate');
|
||||
my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
|
||||
my $hook_error;
|
||||
if (-x $validate_hook) {
|
||||
require Cwd;
|
||||
@ -2088,13 +2088,19 @@ sub validate_patch {
|
||||
chdir($repo->wc_path() or $repo->repo_path())
|
||||
or die("chdir: $!");
|
||||
local $ENV{"GIT_DIR"} = $repo->repo_path();
|
||||
$hook_error = system_or_msg([$validate_hook, $target]);
|
||||
my @cmd = ("git", "hook", "run", "--ignore-missing",
|
||||
$hook_name, "--");
|
||||
my @cmd_msg = (@cmd, "<patch>");
|
||||
my @cmd_run = (@cmd, $target);
|
||||
$hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
|
||||
chdir($cwd_save) or die("chdir: $!");
|
||||
}
|
||||
if ($hook_error) {
|
||||
die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
|
||||
"%s\n" .
|
||||
"warning: no patches were sent\n"), $fn, $hook_error);
|
||||
$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
|
||||
$hook_error . "\n" .
|
||||
"warning: no patches were sent\n"),
|
||||
$fn, $hook_name);
|
||||
die $hook_error;
|
||||
}
|
||||
}
|
||||
|
||||
|
1
git.c
1
git.c
@ -541,6 +541,7 @@ static struct cmd_struct commands[] = {
|
||||
{ "grep", cmd_grep, RUN_SETUP_GENTLY },
|
||||
{ "hash-object", cmd_hash_object },
|
||||
{ "help", cmd_help },
|
||||
{ "hook", cmd_hook, RUN_SETUP },
|
||||
{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
|
||||
{ "init", cmd_init_db },
|
||||
{ "init-db", cmd_init_db },
|
||||
|
131
hook.c
131
hook.c
@ -1,6 +1,7 @@
|
||||
#include "cache.h"
|
||||
#include "hook.h"
|
||||
#include "run-command.h"
|
||||
#include "config.h"
|
||||
|
||||
const char *find_hook(const char *name)
|
||||
{
|
||||
@ -40,3 +41,133 @@ int hook_exists(const char *name)
|
||||
{
|
||||
return !!find_hook(name);
|
||||
}
|
||||
|
||||
static int pick_next_hook(struct child_process *cp,
|
||||
struct strbuf *out,
|
||||
void *pp_cb,
|
||||
void **pp_task_cb)
|
||||
{
|
||||
struct hook_cb_data *hook_cb = pp_cb;
|
||||
const char *hook_path = hook_cb->hook_path;
|
||||
|
||||
if (!hook_path)
|
||||
return 0;
|
||||
|
||||
cp->no_stdin = 1;
|
||||
strvec_pushv(&cp->env_array, hook_cb->options->env.v);
|
||||
cp->stdout_to_stderr = 1;
|
||||
cp->trace2_hook_name = hook_cb->hook_name;
|
||||
cp->dir = hook_cb->options->dir;
|
||||
|
||||
strvec_push(&cp->args, hook_path);
|
||||
strvec_pushv(&cp->args, hook_cb->options->args.v);
|
||||
|
||||
/* Provide context for errors if necessary */
|
||||
*pp_task_cb = (char *)hook_path;
|
||||
|
||||
/*
|
||||
* This pick_next_hook() will be called again, we're only
|
||||
* running one hook, so indicate that no more work will be
|
||||
* done.
|
||||
*/
|
||||
hook_cb->hook_path = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int notify_start_failure(struct strbuf *out,
|
||||
void *pp_cb,
|
||||
void *pp_task_cp)
|
||||
{
|
||||
struct hook_cb_data *hook_cb = pp_cb;
|
||||
const char *hook_path = pp_task_cp;
|
||||
|
||||
hook_cb->rc |= 1;
|
||||
|
||||
strbuf_addf(out, _("Couldn't start hook '%s'\n"),
|
||||
hook_path);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int notify_hook_finished(int result,
|
||||
struct strbuf *out,
|
||||
void *pp_cb,
|
||||
void *pp_task_cb)
|
||||
{
|
||||
struct hook_cb_data *hook_cb = pp_cb;
|
||||
|
||||
hook_cb->rc |= result;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_hooks_opt_clear(struct run_hooks_opt *options)
|
||||
{
|
||||
strvec_clear(&options->env);
|
||||
strvec_clear(&options->args);
|
||||
}
|
||||
|
||||
int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
|
||||
{
|
||||
struct strbuf abs_path = STRBUF_INIT;
|
||||
struct hook_cb_data cb_data = {
|
||||
.rc = 0,
|
||||
.hook_name = hook_name,
|
||||
.options = options,
|
||||
};
|
||||
const char *const hook_path = find_hook(hook_name);
|
||||
int jobs = 1;
|
||||
int ret = 0;
|
||||
|
||||
if (!options)
|
||||
BUG("a struct run_hooks_opt must be provided to run_hooks");
|
||||
|
||||
if (!hook_path && !options->error_if_missing)
|
||||
goto cleanup;
|
||||
|
||||
if (!hook_path) {
|
||||
ret = error("cannot find a hook named %s", hook_name);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cb_data.hook_path = hook_path;
|
||||
if (options->dir) {
|
||||
strbuf_add_absolute_path(&abs_path, hook_path);
|
||||
cb_data.hook_path = abs_path.buf;
|
||||
}
|
||||
|
||||
run_processes_parallel_tr2(jobs,
|
||||
pick_next_hook,
|
||||
notify_start_failure,
|
||||
notify_hook_finished,
|
||||
&cb_data,
|
||||
"hook",
|
||||
hook_name);
|
||||
ret = cb_data.rc;
|
||||
cleanup:
|
||||
strbuf_release(&abs_path);
|
||||
run_hooks_opt_clear(options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int run_hooks(const char *hook_name)
|
||||
{
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
|
||||
return run_hooks_opt(hook_name, &opt);
|
||||
}
|
||||
|
||||
int run_hooks_l(const char *hook_name, ...)
|
||||
{
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
va_list ap;
|
||||
const char *arg;
|
||||
|
||||
va_start(ap, hook_name);
|
||||
while ((arg = va_arg(ap, const char *)))
|
||||
strvec_push(&opt.args, arg);
|
||||
va_end(ap);
|
||||
|
||||
return run_hooks_opt(hook_name, &opt);
|
||||
}
|
||||
|
57
hook.h
57
hook.h
@ -1,5 +1,37 @@
|
||||
#ifndef HOOK_H
|
||||
#define HOOK_H
|
||||
#include "strvec.h"
|
||||
|
||||
struct run_hooks_opt
|
||||
{
|
||||
/* Environment vars to be set for each hook */
|
||||
struct strvec env;
|
||||
|
||||
/* Args to be passed to each hook */
|
||||
struct strvec args;
|
||||
|
||||
/* Emit an error if the hook is missing */
|
||||
unsigned int error_if_missing:1;
|
||||
|
||||
/**
|
||||
* An optional initial working directory for the hook,
|
||||
* translates to "struct child_process"'s "dir" member.
|
||||
*/
|
||||
const char *dir;
|
||||
};
|
||||
|
||||
#define RUN_HOOKS_OPT_INIT { \
|
||||
.env = STRVEC_INIT, \
|
||||
.args = STRVEC_INIT, \
|
||||
}
|
||||
|
||||
struct hook_cb_data {
|
||||
/* rc reflects the cumulative failure state */
|
||||
int rc;
|
||||
const char *hook_name;
|
||||
const char *hook_path;
|
||||
struct run_hooks_opt *options;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns the path to the hook file, or NULL if the hook is missing
|
||||
@ -13,4 +45,29 @@ const char *find_hook(const char *name);
|
||||
*/
|
||||
int hook_exists(const char *hookname);
|
||||
|
||||
/**
|
||||
* Takes a `hook_name`, resolves it to a path with find_hook(), and
|
||||
* runs the hook for you with the options specified in "struct
|
||||
* run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
|
||||
*
|
||||
* Returns the status code of the run hook, or a negative value on
|
||||
* error().
|
||||
*/
|
||||
int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
|
||||
|
||||
/**
|
||||
* A wrapper for run_hooks_opt() which provides a dummy "struct
|
||||
* run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
|
||||
*/
|
||||
int run_hooks(const char *hook_name);
|
||||
|
||||
/**
|
||||
* Like run_hooks(), a wrapper for run_hooks_opt().
|
||||
*
|
||||
* In addition to the wrapping behavior provided by run_hooks(), this
|
||||
* wrapper takes a list of strings terminated by a NULL
|
||||
* argument. These things will be used as positional arguments to the
|
||||
* hook. This function behaves like the old run_hook_le() API.
|
||||
*/
|
||||
int run_hooks_l(const char *hook_name, ...);
|
||||
#endif
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "sparse-index.h"
|
||||
#include "csum-file.h"
|
||||
#include "promisor-remote.h"
|
||||
#include "hook.h"
|
||||
|
||||
/* Mask for the name length in ce_flags in the on-disk index */
|
||||
|
||||
@ -3150,7 +3151,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
|
||||
else
|
||||
ret = close_lock_file_gently(lock);
|
||||
|
||||
run_hook_le(NULL, "post-index-change",
|
||||
run_hooks_l("post-index-change",
|
||||
istate->updated_workdir ? "1" : "0",
|
||||
istate->updated_skipworktree ? "1" : "0", NULL);
|
||||
istate->updated_workdir = 0;
|
||||
|
3
reset.c
3
reset.c
@ -7,6 +7,7 @@
|
||||
#include "tree-walk.h"
|
||||
#include "tree.h"
|
||||
#include "unpack-trees.h"
|
||||
#include "hook.h"
|
||||
|
||||
int reset_head(struct repository *r, struct object_id *oid, const char *action,
|
||||
const char *switch_to_branch, unsigned flags,
|
||||
@ -127,7 +128,7 @@ reset_head_refs:
|
||||
reflog_head);
|
||||
}
|
||||
if (run_hook)
|
||||
run_hook_le(NULL, "post-checkout",
|
||||
run_hooks_l("post-checkout",
|
||||
oid_to_hex(orig ? orig : null_oid()),
|
||||
oid_to_hex(oid), "1", NULL);
|
||||
|
||||
|
@ -1307,39 +1307,6 @@ int async_with_fork(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
int run_hook_ve(const char *const *env, const char *name, va_list args)
|
||||
{
|
||||
struct child_process hook = CHILD_PROCESS_INIT;
|
||||
const char *p;
|
||||
|
||||
p = find_hook(name);
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
strvec_push(&hook.args, p);
|
||||
while ((p = va_arg(args, const char *)))
|
||||
strvec_push(&hook.args, p);
|
||||
if (env)
|
||||
strvec_pushv(&hook.env_array, (const char **)env);
|
||||
hook.no_stdin = 1;
|
||||
hook.stdout_to_stderr = 1;
|
||||
hook.trace2_hook_name = name;
|
||||
|
||||
return run_command(&hook);
|
||||
}
|
||||
|
||||
int run_hook_le(const char *const *env, const char *name, ...)
|
||||
{
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
va_start(args, name);
|
||||
ret = run_hook_ve(env, name, args);
|
||||
va_end(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct io_pump {
|
||||
/* initialized by caller */
|
||||
int fd;
|
||||
|
@ -220,23 +220,6 @@ int finish_command_in_signal(struct child_process *);
|
||||
*/
|
||||
int run_command(struct child_process *);
|
||||
|
||||
/**
|
||||
* Run a hook.
|
||||
* The first argument is a pathname to an index file, or NULL
|
||||
* if the hook uses the default index file or no index is needed.
|
||||
* The second argument is the name of the hook.
|
||||
* The further arguments correspond to the hook arguments.
|
||||
* The last argument has to be NULL to terminate the arguments list.
|
||||
* If the hook does not exist or is not executable, the return
|
||||
* value will be zero.
|
||||
* If it is executable, the hook will be executed and the exit
|
||||
* status of the hook is returned.
|
||||
* On execution, .stdout_to_stderr and .no_stdin will be set.
|
||||
*/
|
||||
LAST_ARG_MUST_BE_NULL
|
||||
int run_hook_le(const char *const *env, const char *name, ...);
|
||||
int run_hook_ve(const char *const *env, const char *name, va_list args);
|
||||
|
||||
/*
|
||||
* Trigger an auto-gc
|
||||
*/
|
||||
|
134
t/t1800-hook.sh
Executable file
134
t/t1800-hook.sh
Executable file
@ -0,0 +1,134 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git-hook command'
|
||||
|
||||
TEST_PASSES_SANITIZE_LEAK=true
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'git hook usage' '
|
||||
test_expect_code 129 git hook &&
|
||||
test_expect_code 129 git hook run &&
|
||||
test_expect_code 129 git hook run -h &&
|
||||
test_expect_code 129 git hook run --unknown 2>err &&
|
||||
grep "unknown option" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: nonexistent hook' '
|
||||
cat >stderr.expect <<-\EOF &&
|
||||
error: cannot find a hook named test-hook
|
||||
EOF
|
||||
test_expect_code 1 git hook run test-hook 2>stderr.actual &&
|
||||
test_cmp stderr.expect stderr.actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
|
||||
git hook run --ignore-missing does-not-exist 2>stderr.actual &&
|
||||
test_must_be_empty stderr.actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: basic' '
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
echo Test hook
|
||||
EOF
|
||||
|
||||
cat >expect <<-\EOF &&
|
||||
Test hook
|
||||
EOF
|
||||
git hook run test-hook 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
echo >&1 Will end up on stderr
|
||||
echo >&2 Will end up on stderr
|
||||
EOF
|
||||
|
||||
cat >stderr.expect <<-\EOF &&
|
||||
Will end up on stderr
|
||||
Will end up on stderr
|
||||
EOF
|
||||
git hook run test-hook >stdout.actual 2>stderr.actual &&
|
||||
test_cmp stderr.expect stderr.actual &&
|
||||
test_must_be_empty stdout.actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: exit codes are passed along' '
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
test_expect_code 1 git hook run test-hook &&
|
||||
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
exit 2
|
||||
EOF
|
||||
|
||||
test_expect_code 2 git hook run test-hook &&
|
||||
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
exit 128
|
||||
EOF
|
||||
|
||||
test_expect_code 128 git hook run test-hook &&
|
||||
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
exit 129
|
||||
EOF
|
||||
|
||||
test_expect_code 129 git hook run test-hook
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run arg u ments without -- is not allowed' '
|
||||
test_expect_code 129 git hook run test-hook arg u ments
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run -- pass arguments' '
|
||||
write_script .git/hooks/test-hook <<-\EOF &&
|
||||
echo $1
|
||||
echo $2
|
||||
EOF
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
arg
|
||||
u ments
|
||||
EOF
|
||||
|
||||
git hook run test-hook -- arg "u ments" 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run -- out-of-repo runs excluded' '
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
echo Test hook
|
||||
EOF
|
||||
|
||||
nongit test_must_fail git hook run test-hook
|
||||
'
|
||||
|
||||
test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
|
||||
mkdir my-hooks &&
|
||||
write_script my-hooks/test-hook <<-\EOF &&
|
||||
echo Hook ran $1 >>actual
|
||||
EOF
|
||||
|
||||
cat >expect <<-\EOF &&
|
||||
Test hook
|
||||
Hook ran one
|
||||
Hook ran two
|
||||
Hook ran three
|
||||
Hook ran four
|
||||
EOF
|
||||
|
||||
# Test various ways of specifying the path. See also
|
||||
# t1350-config-hooks-path.sh
|
||||
>actual &&
|
||||
git hook run test-hook -- ignored 2>>actual &&
|
||||
git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
|
||||
git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
|
||||
git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
|
||||
git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
|
||||
test_path_is_file my-hooks.ran &&
|
||||
cat >expect <<-EOF &&
|
||||
fatal: longline.patch: rejected by sendemail-validate hook
|
||||
fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
|
||||
fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
|
||||
warning: no patches were sent
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
|
||||
test_path_is_file my-hooks.ran &&
|
||||
cat >expect <<-EOF &&
|
||||
fatal: longline.patch: rejected by sendemail-validate hook
|
||||
fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
|
||||
fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
|
||||
warning: no patches were sent
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
|
Loading…
Reference in New Issue
Block a user