read-cache: add post-index-change hook

Add a post-index-change hook that is invoked after the index is written in do_write_locked_index(). This hook is meant primarily for notification, and cannot affect the outcome of git commands that trigger the index write. The hook is passed a flag to indicate whether the working directory was updated or not and a flag indicating if a skip-worktree bit could have changed. These flags enable the hook to optimize its response to the index change notification. Signed-off-by: Ben Peart <benpeart@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ben Peart committed Feb 15, 2019 at 12:59 UTC 1956ecd0ab26dea9c3ed6b9afe334101d9d12f60
7 files changed +182 -3
Documentation/githooks.txt
+18
@@ -492,6 +492,24 @@ This hook is invoked by `git-p4 submit`. It takes no parameters and nothing
492 from standard input. Exiting with non-zero status from this script prevent
493 `git-p4 submit` from launching. Run `git-p4 submit --help` for details.
494
495 +post-index-change
496 +~~~~~~~~~~~~~~~~~
497 +
498 +This hook is invoked when the index is written in read-cache.c
499 +do_write_locked_index.
500 +
501 +The first parameter passed to the hook is the indicator for the
502 +working directory being updated. "1" meaning working directory
503 +was updated or "0" when the working directory was not updated.
504 +
505 +The second parameter passed to the hook is the indicator for whether
506 +or not the index was updated and the skip-worktree bit could have
507 +changed. "1" meaning skip-worktree bits could have been updated
508 +and "0" meaning they were not.
509 +
510 +Only one parameter should be set to "1" when the hook runs. The hook
511 +running passing "1", "1" should not be possible.
512 +
513 GIT
514 ---
515 Part of the linkgit:git[1] suite
builtin/reset.c
+1
@@ -380,6 +380,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
380 int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
381 if (read_from_tree(&pathspec, &oid, intent_to_add))
382 return 1;
383 + the_index.updated_skipworktree = 1;
384 if (!quiet && get_git_work_tree()) {
385 uint64_t t_begin, t_delta_in_ms;
386
builtin/update-index.c
+2
@@ -1071,6 +1071,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
1071 if (entries < 0)
1072 die("cache corrupted");
1073
1074 + the_index.updated_skipworktree = 1;
1075 +
1076 /*
1077 * Custom copy of parse_options() because we want to handle
1078 * filename arguments as they come.
cache.h
+3 -1
@@ -338,7 +338,9 @@ struct index_state {
338 struct cache_time timestamp;
339 unsigned name_hash_initialized : 1,
340 initialized : 1,
341 - drop_cache_tree : 1;
341 + drop_cache_tree : 1,
342 + updated_workdir : 1,
343 + updated_skipworktree : 1;
344 struct hashmap name_hash;
345 struct hashmap dir_hash;
346 struct object_id oid;
read-cache.c
+12 -2
@@ -17,6 +17,7 @@
17 #include "commit.h"
18 #include "blob.h"
19 #include "resolve-undo.h"
20 +#include "run-command.h"
21 #include "strbuf.h"
22 #include "varint.h"
23 #include "split-index.h"
@@ -2999,8 +3000,17 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
3000 if (ret)
3001 return ret;
3002 if (flags & COMMIT_LOCK)
3002 - return commit_locked_index(lock);
3003 - return close_lock_file_gently(lock);
3003 + ret = commit_locked_index(lock);
3004 + else
3005 + ret = close_lock_file_gently(lock);
3006 +
3007 + run_hook_le(NULL, "post-index-change",
3008 + istate->updated_workdir ? "1" : "0",
3009 + istate->updated_skipworktree ? "1" : "0", NULL);
3010 + istate->updated_workdir = 0;
3011 + istate->updated_skipworktree = 0;
3012 +
3013 + return ret;
3014 }
3015
3016 static int write_split_index(struct index_state *istate,
t/t7113-post-index-change-hook.sh new
+144
@@ -0,0 +1,144 @@
1 +#!/bin/sh
2 +
3 +test_description='post index change hook'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup' '
8 + mkdir -p dir1 &&
9 + touch dir1/file1.txt &&
10 + echo testing >dir1/file2.txt &&
11 + git add . &&
12 + git commit -m "initial"
13 +'
14 +
15 +test_expect_success 'test status, add, commit, others trigger hook without flags set' '
16 + mkdir -p .git/hooks &&
17 + write_script .git/hooks/post-index-change <<-\EOF &&
18 + if test "$1" -eq 1; then
19 + echo "Invalid combination of flags passed to hook; updated_workdir is set." >testfailure
20 + exit 1
21 + fi
22 + if test "$2" -eq 1; then
23 + echo "Invalid combination of flags passed to hook; updated_skipworktree is set." >testfailure
24 + exit 1
25 + fi
26 + if test -f ".git/index.lock"; then
27 + echo ".git/index.lock exists" >testfailure
28 + exit 3
29 + fi
30 + if ! test -f ".git/index"; then
31 + echo ".git/index does not exist" >testfailure
32 + exit 3
33 + fi
34 + echo "success" >testsuccess
35 + EOF
36 + mkdir -p dir2 &&
37 + touch dir2/file1.txt &&
38 + touch dir2/file2.txt &&
39 + : force index to be dirty &&
40 + test-tool chmtime +60 dir1/file1.txt &&
41 + git status &&
42 + test_path_is_file testsuccess && rm -f testsuccess &&
43 + test_path_is_missing testfailure &&
44 + git add . &&
45 + test_path_is_file testsuccess && rm -f testsuccess &&
46 + test_path_is_missing testfailure &&
47 + git commit -m "second" &&
48 + test_path_is_file testsuccess && rm -f testsuccess &&
49 + test_path_is_missing testfailure &&
50 + git checkout -- dir1/file1.txt &&
51 + test_path_is_file testsuccess && rm -f testsuccess &&
52 + test_path_is_missing testfailure &&
53 + git update-index &&
54 + test_path_is_missing testsuccess &&
55 + test_path_is_missing testfailure &&
56 + git reset --soft &&
57 + test_path_is_missing testsuccess &&
58 + test_path_is_missing testfailure
59 +'
60 +
61 +test_expect_success 'test checkout and reset trigger the hook' '
62 + write_script .git/hooks/post-index-change <<-\EOF &&
63 + if test "$1" -eq 1 && test "$2" -eq 1; then
64 + echo "Invalid combination of flags passed to hook; updated_workdir and updated_skipworktree are both set." >testfailure
65 + exit 1
66 + fi
67 + if test "$1" -eq 0 && test "$2" -eq 0; then
68 + echo "Invalid combination of flags passed to hook; neither updated_workdir or updated_skipworktree are set." >testfailure
69 + exit 2
70 + fi
71 + if test "$1" -eq 1; then
72 + if test -f ".git/index.lock"; then
73 + echo "updated_workdir set but .git/index.lock exists" >testfailure
74 + exit 3
75 + fi
76 + if ! test -f ".git/index"; then
77 + echo "updated_workdir set but .git/index does not exist" >testfailure
78 + exit 3
79 + fi
80 + else
81 + echo "update_workdir should be set for checkout" >testfailure
82 + exit 4
83 + fi
84 + echo "success" >testsuccess
85 + EOF
86 + : force index to be dirty &&
87 + test-tool chmtime +60 dir1/file1.txt &&
88 + git checkout master &&
89 + test_path_is_file testsuccess && rm -f testsuccess &&
90 + test_path_is_missing testfailure &&
91 + test-tool chmtime +60 dir1/file1.txt &&
92 + git checkout HEAD &&
93 + test_path_is_file testsuccess && rm -f testsuccess &&
94 + test_path_is_missing testfailure &&
95 + test-tool chmtime +60 dir1/file1.txt &&
96 + git reset --hard &&
97 + test_path_is_file testsuccess && rm -f testsuccess &&
98 + test_path_is_missing testfailure &&
99 + git checkout -B test &&
100 + test_path_is_file testsuccess && rm -f testsuccess &&
101 + test_path_is_missing testfailure
102 +'
103 +
104 +test_expect_success 'test reset --mixed and update-index triggers the hook' '
105 + write_script .git/hooks/post-index-change <<-\EOF &&
106 + if test "$1" -eq 1 && test "$2" -eq 1; then
107 + echo "Invalid combination of flags passed to hook; updated_workdir and updated_skipworktree are both set." >testfailure
108 + exit 1
109 + fi
110 + if test "$1" -eq 0 && test "$2" -eq 0; then
111 + echo "Invalid combination of flags passed to hook; neither updated_workdir or updated_skipworktree are set." >testfailure
112 + exit 2
113 + fi
114 + if test "$2" -eq 1; then
115 + if test -f ".git/index.lock"; then
116 + echo "updated_skipworktree set but .git/index.lock exists" >testfailure
117 + exit 3
118 + fi
119 + if ! test -f ".git/index"; then
120 + echo "updated_skipworktree set but .git/index does not exist" >testfailure
121 + exit 3
122 + fi
123 + else
124 + echo "updated_skipworktree should be set for reset --mixed and update-index" >testfailure
125 + exit 4
126 + fi
127 + echo "success" >testsuccess
128 + EOF
129 + : force index to be dirty &&
130 + test-tool chmtime +60 dir1/file1.txt &&
131 + git reset --mixed --quiet HEAD~1 &&
132 + test_path_is_file testsuccess && rm -f testsuccess &&
133 + test_path_is_missing testfailure &&
134 + git hash-object -w --stdin <dir1/file2.txt >expect &&
135 + git update-index --cacheinfo 100644 "$(cat expect)" dir1/file1.txt &&
136 + test_path_is_file testsuccess && rm -f testsuccess &&
137 + test_path_is_missing testfailure &&
138 + git update-index --skip-worktree dir1/file2.txt &&
139 + git update-index --remove dir1/file2.txt &&
140 + test_path_is_file testsuccess && rm -f testsuccess &&
141 + test_path_is_missing testfailure
142 +'
143 +
144 +test_done
unpack-trees.c
+2
@@ -1637,6 +1637,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
1637 WRITE_TREE_SILENT |
1638 WRITE_TREE_REPAIR);
1639 }
1640 +
1641 + o->result.updated_workdir = 1;
1642 discard_index(o->dst_index);
1643 *o->dst_index = o->result;
1644 } else {