Git fork

fsmonitor: handle version 2 of the hooks that will use opaque token

Some file monitors like watchman will use something other than a timestamp
to keep better track of what changes happen in between calls to query
the fsmonitor. The clockid in watchman is a string. Now that the index
is storing an opaque token for the last update the code needs to be
updated to pass that opaque token to a verion 2 of the fsmonitor hook.

Because there are repos that already have version 1 of the hook and we
want them to continue to work when git is updated, we need to handle
both version 1 and version 2 of the hook. In order to do that a
config value is being added core.fsmonitorHookVersion to force what
version of the hook should be used. When this is not set it will default
to -1 and then the code will attempt to call version 2 of the hook first.
If that fails it will fallback to trying version 1.

Signed-off-by: Kevin Willford <Kevin.Willford@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Kevin Willford and committed by
Junio C Hamano
8da2c576 56c69100

+71 -15
+64 -11
fsmonitor.c
··· 8 8 9 9 #define INDEX_EXTENSION_VERSION1 (1) 10 10 #define INDEX_EXTENSION_VERSION2 (2) 11 - #define HOOK_INTERFACE_VERSION (1) 11 + #define HOOK_INTERFACE_VERSION1 (1) 12 + #define HOOK_INTERFACE_VERSION2 (2) 12 13 13 14 struct trace_key trace_fsmonitor = TRACE_KEY_INIT(FSMONITOR); 14 15 ··· 23 24 24 25 ce = istate->cache[pos]; 25 26 ce->ce_flags &= ~CE_FSMONITOR_VALID; 27 + } 28 + 29 + static int fsmonitor_hook_version(void) 30 + { 31 + int hook_version; 32 + 33 + if (git_config_get_int("core.fsmonitorhookversion", &hook_version)) 34 + return -1; 35 + 36 + if (hook_version == HOOK_INTERFACE_VERSION1 || 37 + hook_version == HOOK_INTERFACE_VERSION2) 38 + return hook_version; 39 + 40 + warning("Invalid hook version '%i' in core.fsmonitorhookversion. " 41 + "Must be 1 or 2.", hook_version); 42 + return -1; 26 43 } 27 44 28 45 int read_fsmonitor_extension(struct index_state *istate, const void *data, ··· 158 175 void refresh_fsmonitor(struct index_state *istate) 159 176 { 160 177 struct strbuf query_result = STRBUF_INIT; 161 - int query_success = 0; 162 - size_t bol; /* beginning of line */ 178 + int query_success = 0, hook_version = -1; 179 + size_t bol = 0; /* beginning of line */ 163 180 uint64_t last_update; 164 181 struct strbuf last_update_token = STRBUF_INIT; 165 182 char *buf; ··· 167 184 168 185 if (!core_fsmonitor || istate->fsmonitor_has_run_once) 169 186 return; 187 + 188 + hook_version = fsmonitor_hook_version(); 189 + 170 190 istate->fsmonitor_has_run_once = 1; 171 191 172 192 trace_printf_key(&trace_fsmonitor, "refresh fsmonitor"); ··· 175 195 * should be inclusive to ensure we don't miss potential changes. 176 196 */ 177 197 last_update = getnanotime(); 178 - strbuf_addf(&last_update_token, "%"PRIu64"", last_update); 198 + if (hook_version == HOOK_INTERFACE_VERSION1) 199 + strbuf_addf(&last_update_token, "%"PRIu64"", last_update); 179 200 180 201 /* 181 - * If we have a last update time, call query_fsmonitor for the set of 182 - * changes since that time, else assume everything is possibly dirty 202 + * If we have a last update token, call query_fsmonitor for the set of 203 + * changes since that token, else assume everything is possibly dirty 183 204 * and check it all. 184 205 */ 185 206 if (istate->fsmonitor_last_update) { 186 - query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION, 187 - istate->fsmonitor_last_update, &query_result); 207 + if (hook_version == -1 || hook_version == HOOK_INTERFACE_VERSION2) { 208 + query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION2, 209 + istate->fsmonitor_last_update, &query_result); 210 + 211 + if (query_success) { 212 + if (hook_version < 0) 213 + hook_version = HOOK_INTERFACE_VERSION2; 214 + 215 + /* 216 + * First entry will be the last update token 217 + * Need to use a char * variable because static 218 + * analysis was suggesting to use strbuf_addbuf 219 + * but we don't want to copy the entire strbuf 220 + * only the the chars up to the first NUL 221 + */ 222 + buf = query_result.buf; 223 + strbuf_addstr(&last_update_token, buf); 224 + if (!last_update_token.len) { 225 + warning("Empty last update token."); 226 + query_success = 0; 227 + } else { 228 + bol = last_update_token.len + 1; 229 + } 230 + } else if (hook_version < 0) { 231 + hook_version = HOOK_INTERFACE_VERSION1; 232 + if (!last_update_token.len) 233 + strbuf_addf(&last_update_token, "%"PRIu64"", last_update); 234 + } 235 + } 236 + 237 + if (hook_version == HOOK_INTERFACE_VERSION1) { 238 + query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION1, 239 + istate->fsmonitor_last_update, &query_result); 240 + } 241 + 188 242 trace_performance_since(last_update, "fsmonitor process '%s'", core_fsmonitor); 189 243 trace_printf_key(&trace_fsmonitor, "fsmonitor process '%s' returned %s", 190 244 core_fsmonitor, query_success ? "success" : "failure"); 191 245 } 192 246 193 247 /* a fsmonitor process can return '/' to indicate all entries are invalid */ 194 - if (query_success && query_result.buf[0] != '/') { 248 + if (query_success && query_result.buf[bol] != '/') { 195 249 /* Mark all entries returned by the monitor as dirty */ 196 250 buf = query_result.buf; 197 - bol = 0; 198 - for (i = 0; i < query_result.len; i++) { 251 + for (i = bol; i < query_result.len; i++) { 199 252 if (buf[i] != '\0') 200 253 continue; 201 254 fsmonitor_refresh_callback(istate, buf + bol);
+6 -1
t/t7519-status-fsmonitor.sh
··· 32 32 echo "$0: exactly 2 arguments expected" 33 33 exit 2 34 34 fi 35 - if test "$1" != 1 35 + if test "$1" != 2 36 36 then 37 37 echo "Unsupported core.fsmonitor hook version." >&2 38 38 exit 1 39 39 fi 40 + printf "last_update_token\0" 40 41 printf "untracked\0" 41 42 printf "dir1/untracked\0" 42 43 printf "dir2/untracked\0" ··· 107 108 # test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit 108 109 test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' ' 109 110 write_script .git/hooks/fsmonitor-test<<-\EOF && 111 + printf "last_update_token\0" 110 112 EOF 111 113 git update-index --fsmonitor && 112 114 git update-index --fsmonitor-valid dir1/modified && ··· 167 169 # test that newly added files are marked valid 168 170 test_expect_success 'newly added files are marked valid' ' 169 171 write_script .git/hooks/fsmonitor-test<<-\EOF && 172 + printf "last_update_token\0" 170 173 EOF 171 174 git add new && 172 175 git add dir1/new && ··· 207 210 # test that *only* files returned by the integration script get flagged as invalid 208 211 test_expect_success '*only* files returned by the integration script get flagged as invalid' ' 209 212 write_script .git/hooks/fsmonitor-test<<-\EOF && 213 + printf "last_update_token\0" 210 214 printf "dir1/modified\0" 211 215 EOF 212 216 clean_repo && ··· 276 280 # (if enabled) files unless it is told about them. 277 281 test_expect_success "status doesn't detect unreported modifications" ' 278 282 write_script .git/hooks/fsmonitor-test<<-\EOF && 283 + printf "last_update_token\0" 279 284 :>marker 280 285 EOF 281 286 clean_repo &&
-1
t/t7519/fsmonitor-all
··· 17 17 18 18 if test "$1" != 1 19 19 then 20 - echo "Unsupported core.fsmonitor hook version." >&2 21 20 exit 1 22 21 fi 23 22
+1 -2
t/t7519/fsmonitor-watchman
··· 26 26 # subtract one second to make sure watchman will return all changes 27 27 $time = int ($time / 1000000000) - 1; 28 28 } else { 29 - die "Unsupported query-fsmonitor hook version '$version'.\n" . 30 - "Falling back to scanning...\n"; 29 + exit 1; 31 30 } 32 31 33 32 my $git_work_tree;