Skip to content

hooks: add custom post-command hook config #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Documentation/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ include::config/pack.adoc[]

include::config/pager.adoc[]

include::config/postcommand.adoc[]

include::config/pretty.adoc[]

include::config/promisor.adoc[]
Expand Down
13 changes: 13 additions & 0 deletions Documentation/config/postcommand.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
postCommand.strategy::
The `post-command` hook is run on every Git process by default. This
config option allows running the hook only conditionally, according
to these values:
+
----
`always`;;
run the `post-command` hook on every process (default).

`post-index-change`;;
run the `post-command` hook only if the current process wrote to
the index.
----
70 changes: 69 additions & 1 deletion hook.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#define USE_THE_REPOSITORY_VARIABLE

#include "git-compat-util.h"
#include "trace2/tr2_sid.h"
#include "abspath.h"
#include "environment.h"
#include "advice.h"
Expand Down Expand Up @@ -176,6 +177,58 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
strvec_clear(&options->args);
}

static char *get_post_index_change_sentinel_name(struct repository *r)
{
struct strbuf path = STRBUF_INIT;
const char *sid = tr2_sid_get();
char *slash = strchr(sid, '/');

/*
* Name is based on top-level SID, so children can indicate that
* the top-level process should run the post-command hook.
*/
if (slash)
*slash = 0;

repo_git_path_replace(r, &path, "hooks/index-change-%s.snt", sid);

return strbuf_detach(&path, NULL);
}

static int write_post_index_change_sentinel(struct repository *r)
{
char *path = get_post_index_change_sentinel_name(r);
FILE *fp = xfopen(path, "w");

if (fp) {
fprintf(fp, "run post-command hook");
fclose(fp);
}

free(path);
return fp ? 0 : -1;
}

/**
* Try to delete the sentinel file for this repository. If that succeeds, then
* return 1.
*/
static int post_index_change_sentinel_exists(struct repository *r)
{
char *path = get_post_index_change_sentinel_name(r);
int res = 1;

if (unlink(path)) {
if (is_missing_file_error(errno))
res = 0;
else
warning_errno("failed to remove index-change sentinel file '%s'", path);
}

free(path);
return res;
}

int run_hooks_opt(struct repository *r, const char *hook_name,
struct run_hooks_opt *options)
{
Expand All @@ -185,7 +238,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
.hook_name = hook_name,
.options = options,
};
const char *hook_path = find_hook(r, hook_name);
const char *hook_path;
int ret = 0;
const struct run_process_parallel_opts opts = {
.tr2_category = "hook",
Expand All @@ -201,6 +254,21 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
.data = &cb_data,
};

/* Interject hook behavior depending on strategy. */
if (r && r->gitdir) {
const char *strval;
if (!repo_config_get_string_tmp(r, "postcommand.strategy", &strval) &&
!strcasecmp(strval, "post-index-change")) {
if (!strcmp(hook_name, "post-index-change"))
return write_post_index_change_sentinel(r);
if (!strcmp(hook_name, "post-command") &&
!post_index_change_sentinel_exists(r))
return 0;
}
}

hook_path = find_hook(r, hook_name);

/*
* Backwards compatibility hack in VFS for Git: when originally
* introduced (and used!), it was called `post-indexchanged`, but this
Expand Down
42 changes: 42 additions & 0 deletions t/t0401-post-command-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ test_expect_success 'with succeeding hook' '
'

test_expect_success 'with failing pre-command hook' '
test_when_finished rm -f .git/hooks/pre-command &&
write_script .git/hooks/pre-command <<-EOF &&
exit 1
EOF
Expand All @@ -30,4 +31,45 @@ test_expect_success 'with failing pre-command hook' '
test_path_is_missing "$(cat .git/post-command.out)"
'

test_expect_success 'with post-index-change config' '
mkdir -p .git/hooks &&
write_script .git/hooks/post-command <<-EOF &&
echo ran >post-command.out
EOF
write_script .git/hooks/post-index-change <<-EOF &&
echo ran >post-index-change.out
EOF

# First, show expected behavior.
echo ran >expect &&
rm -f post-command.out post-index-change.out &&

# rev-parse leaves index intact, but runs post-command.
git rev-parse HEAD &&
test_path_is_missing post-index-change.out &&
test_cmp expect post-command.out &&
rm -f post-command.out &&

echo stuff >>file &&
# add updates the index and runs post-command.
git add file &&
test_cmp expect post-index-change.out &&
test_cmp expect post-command.out &&

# Now, show configured behavior
git config postCommand.strategy post-index-change &&
rm -f post-command.out post-index-change.out &&

# rev-parse leaves index intact and thus skips post-command.
git rev-parse HEAD &&
test_path_is_missing post-index-change.out &&
test_path_is_missing post-command.out &&

echo stuff >>file &&
# add updates the index and runs post-command.
git add file &&
test_path_is_missing post-index-change.out &&
test_cmp expect post-command.out
'

test_done