Skip to content

Commit bba3a1d

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.) This behavior is tested in t0401-post-command-hook.sh. Signed-off-by: Derrick Stolee <[email protected]>
1 parent 4829785 commit bba3a1d

File tree

6 files changed

+157
-1
lines changed

6 files changed

+157
-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[]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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.

hook.c

Lines changed: 68 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,20 @@ 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+
prepare_repo_settings(r);
260+
if (r->settings.post_command_strategy == POST_INDEX_CHANGE_SENTINEL) {
261+
if (!strcmp(hook_name, "post-index-change"))
262+
return write_post_index_change_sentinel(r);
263+
if (!strcmp(hook_name, "post-command") &&
264+
!post_index_change_sentinel_exists(r))
265+
return 0;
266+
}
267+
}
268+
269+
hook_path = find_hook(r, hook_name);
270+
204271
/*
205272
* Backwards compatibility hack in VFS for Git: when originally
206273
* introduced (and used!), it was called `post-indexchanged`, but this

repo-settings.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ void prepare_repo_settings(struct repository *r)
3939
repo_settings_clear(r);
4040
r->settings.initialized++;
4141

42+
/* Place microsoft/git customizations here to avoid conflicts. */
43+
44+
if (!repo_config_get_string_tmp(r, "postcommand.strategy", &strval) &&
45+
!strcasecmp(strval, "post-index-change"))
46+
r->settings.post_command_strategy = POST_INDEX_CHANGE_SENTINEL;
47+
48+
/* Resume upstream settings. */
49+
4250
/* Booleans config or default, cascades to other settings */
4351
repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0);
4452
repo_cfg_bool(r, "feature.experimental", &experimental, 0);

repo-settings.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,32 @@ enum log_refs_config {
2323
LOG_REFS_ALWAYS
2424
};
2525

26+
enum post_command_strategy_option {
27+
DEFAULT = 0,
28+
29+
/*
30+
* If given as "post-index-change", then the post-index-change
31+
* hook will be replaced with writing a sentinel file into the
32+
* hooks directory and the presence will signal that the
33+
* post-command hook should run. Otherwise, the hook does not
34+
* execute. Useful for post-command hooks that interact with the
35+
* sparse-checkout or other pre-build steps.
36+
*/
37+
POST_INDEX_CHANGE_SENTINEL = 1,
38+
};
39+
2640
struct repo_settings {
41+
/*
42+
* microsoft/git settings are up top to avoid conflict with
43+
* new upstream settings that will likely land at bottom of
44+
* struct.
45+
*/
46+
47+
enum post_command_strategy_option post_command_strategy;
48+
49+
50+
/* Resume upstream settings. */
51+
2752
int initialized;
2853

2954
int core_commit_graph;

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)