Skip to content

Commit 8b202aa

Browse files
committed
hooks: add custom post-command hook config
The microsoft/git fork includes pre- and post-command hooks, with the initial intention of using these for VFS for Git. In that environment, these are important hooks to avoid concurrent issues when the virtualization is incomplete. However, in the Office monorepo the post-command hook is used in a different way. A custom hook is used to update the sparse-checkout, if necessary. To avoid this hook from being incredibly slow on every Git command, this hook checks for the existence of a "sentinel file" that is written by a custom post-index-change hook and no-ops if that file does not exist. However, even this "no-op" is 200ms due to the use of two scripts (one simple script in .git/hooks/ does some environment checking and then calls a script from the working directory which actually contains the logic). Add a new config option, 'postCommand.strategy', that will allow for multiple possible strategies in the future. For now, the one we are adding is 'post-index-change' which states that we should write a sentinel file instead of running the 'post-index-change' hook and then skip the 'post-command' hook if the proper sentinel file doesn't exist. (If it does exist, then delete it and run the hook.) I originally planned to put this into the repo-settings, but this caused the repo settings to load in more cases than they did previously. When there is an invalid boolean config option, this causes failure in new places. This was caught by t3007. This behavior is tested in t0401-post-command-hook.sh. Signed-off-by: Derrick Stolee <[email protected]>
1 parent 4829785 commit 8b202aa

File tree

4 files changed

+126
-1
lines changed

4 files changed

+126
-1
lines changed

Documentation/config.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,8 @@ include::config/pack.adoc[]
490490

491491
include::config/pager.adoc[]
492492

493+
include::config/postcommand.adoc[]
494+
493495
include::config/pretty.adoc[]
494496

495497
include::config/promisor.adoc[]

Documentation/config/postcommand.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
postCommand.strategy::
2+
The `post-command` hook is run on every Git process by default. This
3+
config option allows running the hook only conditionally, according
4+
to these values:
5+
+
6+
----
7+
`always`;;
8+
run the `post-command` hook on every process (default).
9+
10+
`post-index-change`;;
11+
run the `post-command` hook only if the current process wrote to
12+
the index.
13+
----

hook.c

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#define USE_THE_REPOSITORY_VARIABLE
22

33
#include "git-compat-util.h"
4+
#include "trace2/tr2_sid.h"
45
#include "abspath.h"
56
#include "environment.h"
67
#include "advice.h"
@@ -176,6 +177,58 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
176177
strvec_clear(&options->args);
177178
}
178179

180+
static char *get_post_index_change_sentinel_name(struct repository *r)
181+
{
182+
struct strbuf path = STRBUF_INIT;
183+
const char *sid = tr2_sid_get();
184+
char *slash = strchr(sid, '/');
185+
186+
/*
187+
* Name is based on top-level SID, so children can indicate that
188+
* the top-level process should run the post-command hook.
189+
*/
190+
if (slash)
191+
*slash = 0;
192+
193+
repo_git_path_replace(r, &path, "hooks/index-change-%s.snt", sid);
194+
195+
return strbuf_detach(&path, NULL);
196+
}
197+
198+
static int write_post_index_change_sentinel(struct repository *r)
199+
{
200+
char *path = get_post_index_change_sentinel_name(r);
201+
FILE *fp = xfopen(path, "w");
202+
203+
if (fp) {
204+
fprintf(fp, "run post-command hook");
205+
fclose(fp);
206+
}
207+
208+
free(path);
209+
return fp ? 0 : -1;
210+
}
211+
212+
/**
213+
* Try to delete the sentinel file for this repository. If that succeeds, then
214+
* return 1.
215+
*/
216+
static int post_index_change_sentinel_exists(struct repository *r)
217+
{
218+
char *path = get_post_index_change_sentinel_name(r);
219+
int res = 1;
220+
221+
if (unlink(path)) {
222+
if (is_missing_file_error(errno))
223+
res = 0;
224+
else
225+
warning_errno("failed to remove index-change sentinel file '%s'", path);
226+
}
227+
228+
free(path);
229+
return res;
230+
}
231+
179232
int run_hooks_opt(struct repository *r, const char *hook_name,
180233
struct run_hooks_opt *options)
181234
{
@@ -185,7 +238,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
185238
.hook_name = hook_name,
186239
.options = options,
187240
};
188-
const char *hook_path = find_hook(r, hook_name);
241+
const char *hook_path;
189242
int ret = 0;
190243
const struct run_process_parallel_opts opts = {
191244
.tr2_category = "hook",
@@ -201,6 +254,21 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
201254
.data = &cb_data,
202255
};
203256

257+
/* Interject hook behavior depending on strategy. */
258+
if (r && r->gitdir) {
259+
const char *strval;
260+
if (!repo_config_get_string_tmp(r, "postcommand.strategy", &strval) &&
261+
!strcasecmp(strval, "post-index-change")) {
262+
if (!strcmp(hook_name, "post-index-change"))
263+
return write_post_index_change_sentinel(r);
264+
if (!strcmp(hook_name, "post-command") &&
265+
!post_index_change_sentinel_exists(r))
266+
return 0;
267+
}
268+
}
269+
270+
hook_path = find_hook(r, hook_name);
271+
204272
/*
205273
* Backwards compatibility hack in VFS for Git: when originally
206274
* introduced (and used!), it was called `post-indexchanged`, but this

t/t0401-post-command-hook.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ test_expect_success 'with succeeding hook' '
2222
'
2323

2424
test_expect_success 'with failing pre-command hook' '
25+
test_when_finished rm -f .git/hooks/pre-command &&
2526
write_script .git/hooks/pre-command <<-EOF &&
2627
exit 1
2728
EOF
@@ -30,4 +31,45 @@ test_expect_success 'with failing pre-command hook' '
3031
test_path_is_missing "$(cat .git/post-command.out)"
3132
'
3233

34+
test_expect_success 'with post-index-change config' '
35+
mkdir -p .git/hooks &&
36+
write_script .git/hooks/post-command <<-EOF &&
37+
echo ran >post-command.out
38+
EOF
39+
write_script .git/hooks/post-index-change <<-EOF &&
40+
echo ran >post-index-change.out
41+
EOF
42+
43+
# First, show expected behavior.
44+
echo ran >expect &&
45+
rm -f post-command.out post-index-change.out &&
46+
47+
# rev-parse leaves index intact, but runs post-command.
48+
git rev-parse HEAD &&
49+
test_path_is_missing post-index-change.out &&
50+
test_cmp expect post-command.out &&
51+
rm -f post-command.out &&
52+
53+
echo stuff >>file &&
54+
# add updates the index and runs post-command.
55+
git add file &&
56+
test_cmp expect post-index-change.out &&
57+
test_cmp expect post-command.out &&
58+
59+
# Now, show configured behavior
60+
git config postCommand.strategy post-index-change &&
61+
rm -f post-command.out post-index-change.out &&
62+
63+
# rev-parse leaves index intact and thus skips post-command.
64+
git rev-parse HEAD &&
65+
test_path_is_missing post-index-change.out &&
66+
test_path_is_missing post-command.out &&
67+
68+
echo stuff >>file &&
69+
# add updates the index and runs post-command.
70+
git add file &&
71+
test_path_is_missing post-index-change.out &&
72+
test_cmp expect post-command.out
73+
'
74+
3375
test_done

0 commit comments

Comments
 (0)