|
5 | 5 | #include "ewah/ewok.h"
|
6 | 6 | #include "fsmonitor.h"
|
7 | 7 | #include "fsmonitor-ipc.h"
|
| 8 | +#include "name-hash.h" |
8 | 9 | #include "run-command.h"
|
9 | 10 | #include "strbuf.h"
|
10 | 11 | #include "trace2.h"
|
@@ -183,79 +184,282 @@ static int query_fsmonitor_hook(struct repository *r,
|
183 | 184 | return result;
|
184 | 185 | }
|
185 | 186 |
|
186 |
| -static void fsmonitor_refresh_callback(struct index_state *istate, char *name) |
| 187 | +/* |
| 188 | + * Invalidate the FSM bit on this CE. This is like mark_fsmonitor_invalid() |
| 189 | + * but we've already handled the untracked-cache, so let's not repeat that |
| 190 | + * work. This also lets us have a different trace message so that we can |
| 191 | + * see everything that was done as part of the refresh-callback. |
| 192 | + */ |
| 193 | +static void invalidate_ce_fsm(struct cache_entry *ce) |
187 | 194 | {
|
188 |
| - int i, len = strlen(name); |
189 |
| - int pos = index_name_pos(istate, name, len); |
| 195 | + if (ce->ce_flags & CE_FSMONITOR_VALID) { |
| 196 | + trace_printf_key(&trace_fsmonitor, |
| 197 | + "fsmonitor_refresh_callback INV: '%s'", |
| 198 | + ce->name); |
| 199 | + ce->ce_flags &= ~CE_FSMONITOR_VALID; |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +static size_t handle_path_with_trailing_slash( |
| 204 | + struct index_state *istate, const char *name, int pos); |
| 205 | + |
| 206 | +/* |
| 207 | + * Use the name-hash to do a case-insensitive cache-entry lookup with |
| 208 | + * the pathname and invalidate the cache-entry. |
| 209 | + * |
| 210 | + * Returns the number of cache-entries that we invalidated. |
| 211 | + */ |
| 212 | +static size_t handle_using_name_hash_icase( |
| 213 | + struct index_state *istate, const char *name) |
| 214 | +{ |
| 215 | + struct cache_entry *ce = NULL; |
| 216 | + |
| 217 | + ce = index_file_exists(istate, name, strlen(name), 1); |
| 218 | + if (!ce) |
| 219 | + return 0; |
190 | 220 |
|
| 221 | + /* |
| 222 | + * A case-insensitive search in the name-hash using the |
| 223 | + * observed pathname found a cache-entry, so the observed path |
| 224 | + * is case-incorrect. Invalidate the cache-entry and use the |
| 225 | + * correct spelling from the cache-entry to invalidate the |
| 226 | + * untracked-cache. Since we now have sparse-directories in |
| 227 | + * the index, the observed pathname may represent a regular |
| 228 | + * file or a sparse-index directory. |
| 229 | + * |
| 230 | + * Note that we should not have seen FSEvents for a |
| 231 | + * sparse-index directory, but we handle it just in case. |
| 232 | + * |
| 233 | + * Either way, we know that there are not any cache-entries for |
| 234 | + * children inside the cone of the directory, so we don't need to |
| 235 | + * do the usual scan. |
| 236 | + */ |
191 | 237 | trace_printf_key(&trace_fsmonitor,
|
192 |
| - "fsmonitor_refresh_callback '%s' (pos %d)", |
193 |
| - name, pos); |
| 238 | + "fsmonitor_refresh_callback MAP: '%s' '%s'", |
| 239 | + name, ce->name); |
194 | 240 |
|
195 |
| - if (name[len - 1] == '/') { |
196 |
| - /* |
197 |
| - * The daemon can decorate directory events, such as |
198 |
| - * moves or renames, with a trailing slash if the OS |
199 |
| - * FS Event contains sufficient information, such as |
200 |
| - * MacOS. |
201 |
| - * |
202 |
| - * Use this to invalidate the entire cone under that |
203 |
| - * directory. |
204 |
| - * |
205 |
| - * We do not expect an exact match because the index |
206 |
| - * does not normally contain directory entries, so we |
207 |
| - * start at the insertion point and scan. |
208 |
| - */ |
209 |
| - if (pos < 0) |
210 |
| - pos = -pos - 1; |
| 241 | + /* |
| 242 | + * NEEDSWORK: We used the name-hash to find the correct |
| 243 | + * case-spelling of the pathname in the cache-entry[], so |
| 244 | + * technically this is a tracked file or a sparse-directory. |
| 245 | + * It should not have any entries in the untracked-cache, so |
| 246 | + * we should not need to use the case-corrected spelling to |
| 247 | + * invalidate the the untracked-cache. So we may not need to |
| 248 | + * do this. For now, I'm going to be conservative and always |
| 249 | + * do it; we can revisit this later. |
| 250 | + */ |
| 251 | + untracked_cache_invalidate_trimmed_path(istate, ce->name, 0); |
211 | 252 |
|
212 |
| - /* Mark all entries for the folder invalid */ |
213 |
| - for (i = pos; i < istate->cache_nr; i++) { |
214 |
| - if (!starts_with(istate->cache[i]->name, name)) |
215 |
| - break; |
216 |
| - istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID; |
217 |
| - } |
| 253 | + invalidate_ce_fsm(ce); |
| 254 | + return 1; |
| 255 | +} |
| 256 | + |
| 257 | +/* |
| 258 | + * Use the dir-name-hash to find the correct-case spelling of the |
| 259 | + * directory. Use the canonical spelling to invalidate all of the |
| 260 | + * cache-entries within the matching cone. |
| 261 | + * |
| 262 | + * Returns the number of cache-entries that we invalidated. |
| 263 | + */ |
| 264 | +static size_t handle_using_dir_name_hash_icase( |
| 265 | + struct index_state *istate, const char *name) |
| 266 | +{ |
| 267 | + struct strbuf canonical_path = STRBUF_INIT; |
| 268 | + int pos; |
| 269 | + size_t len = strlen(name); |
| 270 | + size_t nr_in_cone; |
| 271 | + |
| 272 | + if (name[len - 1] == '/') |
| 273 | + len--; |
| 274 | + |
| 275 | + if (!index_dir_find(istate, name, len, &canonical_path)) |
| 276 | + return 0; /* name is untracked */ |
218 | 277 |
|
| 278 | + if (!memcmp(name, canonical_path.buf, canonical_path.len)) { |
| 279 | + strbuf_release(&canonical_path); |
219 | 280 | /*
|
220 |
| - * We need to remove the traling "/" from the path |
221 |
| - * for the untracked cache. |
| 281 | + * NEEDSWORK: Our caller already tried an exact match |
| 282 | + * and failed to find one. They called us to do an |
| 283 | + * ICASE match, so we should never get an exact match, |
| 284 | + * so we could promote this to a BUG() here if we |
| 285 | + * wanted to. It doesn't hurt anything to just return |
| 286 | + * 0 and go on because we should never get here. Or we |
| 287 | + * could just get rid of the memcmp() and this "if" |
| 288 | + * clause completely. |
222 | 289 | */
|
223 |
| - name[len - 1] = '\0'; |
224 |
| - } else if (pos >= 0) { |
| 290 | + BUG("handle_using_dir_name_hash_icase(%s) did not exact match", |
| 291 | + name); |
| 292 | + } |
| 293 | + |
| 294 | + trace_printf_key(&trace_fsmonitor, |
| 295 | + "fsmonitor_refresh_callback MAP: '%s' '%s'", |
| 296 | + name, canonical_path.buf); |
| 297 | + |
| 298 | + /* |
| 299 | + * The dir-name-hash only tells us the corrected spelling of |
| 300 | + * the prefix. We have to use this canonical path to do a |
| 301 | + * lookup in the cache-entry array so that we repeat the |
| 302 | + * original search using the case-corrected spelling. |
| 303 | + */ |
| 304 | + strbuf_addch(&canonical_path, '/'); |
| 305 | + pos = index_name_pos(istate, canonical_path.buf, |
| 306 | + canonical_path.len); |
| 307 | + nr_in_cone = handle_path_with_trailing_slash( |
| 308 | + istate, canonical_path.buf, pos); |
| 309 | + strbuf_release(&canonical_path); |
| 310 | + return nr_in_cone; |
| 311 | +} |
| 312 | + |
| 313 | +/* |
| 314 | + * The daemon sent an observed pathname without a trailing slash. |
| 315 | + * (This is the normal case.) We do not know if it is a tracked or |
| 316 | + * untracked file, a sparse-directory, or a populated directory (on a |
| 317 | + * platform such as Windows where FSEvents are not qualified). |
| 318 | + * |
| 319 | + * The pathname contains the observed case reported by the FS. We |
| 320 | + * do not know it is case-correct or -incorrect. |
| 321 | + * |
| 322 | + * Assume it is case-correct and try an exact match. |
| 323 | + * |
| 324 | + * Return the number of cache-entries that we invalidated. |
| 325 | + */ |
| 326 | +static size_t handle_path_without_trailing_slash( |
| 327 | + struct index_state *istate, const char *name, int pos) |
| 328 | +{ |
| 329 | + /* |
| 330 | + * Mark the untracked cache dirty for this path (regardless of |
| 331 | + * whether or not we find an exact match for it in the index). |
| 332 | + * Since the path is unqualified (no trailing slash hint in the |
| 333 | + * FSEvent), it may refer to a file or directory. So we should |
| 334 | + * not assume one or the other and should always let the untracked |
| 335 | + * cache decide what needs to invalidated. |
| 336 | + */ |
| 337 | + untracked_cache_invalidate_trimmed_path(istate, name, 0); |
| 338 | + |
| 339 | + if (pos >= 0) { |
225 | 340 | /*
|
226 |
| - * We have an exact match for this path and can just |
227 |
| - * invalidate it. |
| 341 | + * An exact match on a tracked file. We assume that we |
| 342 | + * do not need to scan forward for a sparse-directory |
| 343 | + * cache-entry with the same pathname, nor for a cone |
| 344 | + * at that directory. (That is, assume no D/F conflicts.) |
228 | 345 | */
|
229 |
| - istate->cache[pos]->ce_flags &= ~CE_FSMONITOR_VALID; |
| 346 | + invalidate_ce_fsm(istate->cache[pos]); |
| 347 | + return 1; |
230 | 348 | } else {
|
| 349 | + size_t nr_in_cone; |
| 350 | + struct strbuf work_path = STRBUF_INIT; |
| 351 | + |
231 | 352 | /*
|
232 |
| - * The path is not a tracked file -or- it is a |
233 |
| - * directory event on a platform that cannot |
234 |
| - * distinguish between file and directory events in |
235 |
| - * the event handler, such as Windows. |
236 |
| - * |
237 |
| - * Scan as if it is a directory and invalidate the |
238 |
| - * cone under it. (But remember to ignore items |
239 |
| - * between "name" and "name/", such as "name-" and |
240 |
| - * "name.". |
| 353 | + * The negative "pos" gives us the suggested insertion |
| 354 | + * point for the pathname (without the trailing slash). |
| 355 | + * We need to see if there is a directory with that |
| 356 | + * prefix, but there can be lots of pathnames between |
| 357 | + * "foo" and "foo/" like "foo-" or "foo-bar", so we |
| 358 | + * don't want to do our own scan. |
241 | 359 | */
|
| 360 | + strbuf_add(&work_path, name, strlen(name)); |
| 361 | + strbuf_addch(&work_path, '/'); |
| 362 | + pos = index_name_pos(istate, work_path.buf, work_path.len); |
| 363 | + nr_in_cone = handle_path_with_trailing_slash( |
| 364 | + istate, work_path.buf, pos); |
| 365 | + strbuf_release(&work_path); |
| 366 | + return nr_in_cone; |
| 367 | + } |
| 368 | +} |
| 369 | + |
| 370 | +/* |
| 371 | + * The daemon can decorate directory events, such as a move or rename, |
| 372 | + * by adding a trailing slash to the observed name. Use this to |
| 373 | + * explicitly invalidate the entire cone under that directory. |
| 374 | + * |
| 375 | + * The daemon can only reliably do that if the OS FSEvent contains |
| 376 | + * sufficient information in the event. |
| 377 | + * |
| 378 | + * macOS FSEvents have enough information. |
| 379 | + * |
| 380 | + * Other platforms may or may not be able to do it (and it might |
| 381 | + * depend on the type of event (for example, a daemon could lstat() an |
| 382 | + * observed pathname after a rename, but not after a delete)). |
| 383 | + * |
| 384 | + * If we find an exact match in the index for a path with a trailing |
| 385 | + * slash, it means that we matched a sparse-index directory in a |
| 386 | + * cone-mode sparse-checkout (since that's the only time we have |
| 387 | + * directories in the index). We should never see this in practice |
| 388 | + * (because sparse directories should not be present and therefore |
| 389 | + * not generating FS events). Either way, we can treat them in the |
| 390 | + * same way and just invalidate the cache-entry and the untracked |
| 391 | + * cache (and in this case, the forward cache-entry scan won't find |
| 392 | + * anything and it doesn't hurt to let it run). |
| 393 | + * |
| 394 | + * Return the number of cache-entries that we invalidated. We will |
| 395 | + * use this later to determine if we need to attempt a second |
| 396 | + * case-insensitive search on case-insensitive file systems. That is, |
| 397 | + * if the search using the observed-case in the FSEvent yields any |
| 398 | + * results, we assume the prefix is case-correct. If there are no |
| 399 | + * matches, we still don't know if the observed path is simply |
| 400 | + * untracked or case-incorrect. |
| 401 | + */ |
| 402 | +static size_t handle_path_with_trailing_slash( |
| 403 | + struct index_state *istate, const char *name, int pos) |
| 404 | +{ |
| 405 | + int i; |
| 406 | + size_t nr_in_cone = 0; |
| 407 | + |
| 408 | + /* |
| 409 | + * Mark the untracked cache dirty for this directory path |
| 410 | + * (regardless of whether or not we find an exact match for it |
| 411 | + * in the index or find it to be proper prefix of one or more |
| 412 | + * files in the index), since the FSEvent is hinting that |
| 413 | + * there may be changes on or within the directory. |
| 414 | + */ |
| 415 | + untracked_cache_invalidate_trimmed_path(istate, name, 0); |
| 416 | + |
| 417 | + if (pos < 0) |
242 | 418 | pos = -pos - 1;
|
243 | 419 |
|
244 |
| - for (i = pos; i < istate->cache_nr; i++) { |
245 |
| - if (!starts_with(istate->cache[i]->name, name)) |
246 |
| - break; |
247 |
| - if ((unsigned char)istate->cache[i]->name[len] > '/') |
248 |
| - break; |
249 |
| - if (istate->cache[i]->name[len] == '/') |
250 |
| - istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID; |
251 |
| - } |
| 420 | + /* Mark all entries for the folder invalid */ |
| 421 | + for (i = pos; i < istate->cache_nr; i++) { |
| 422 | + if (!starts_with(istate->cache[i]->name, name)) |
| 423 | + break; |
| 424 | + invalidate_ce_fsm(istate->cache[i]); |
| 425 | + nr_in_cone++; |
252 | 426 | }
|
253 | 427 |
|
| 428 | + return nr_in_cone; |
| 429 | +} |
| 430 | + |
| 431 | +static void fsmonitor_refresh_callback(struct index_state *istate, char *name) |
| 432 | +{ |
| 433 | + int len = strlen(name); |
| 434 | + int pos = index_name_pos(istate, name, len); |
| 435 | + size_t nr_in_cone; |
| 436 | + |
| 437 | + trace_printf_key(&trace_fsmonitor, |
| 438 | + "fsmonitor_refresh_callback '%s' (pos %d)", |
| 439 | + name, pos); |
| 440 | + |
| 441 | + if (name[len - 1] == '/') |
| 442 | + nr_in_cone = handle_path_with_trailing_slash(istate, name, pos); |
| 443 | + else |
| 444 | + nr_in_cone = handle_path_without_trailing_slash(istate, name, pos); |
| 445 | + |
254 | 446 | /*
|
255 |
| - * Mark the untracked cache dirty even if it wasn't found in the index |
256 |
| - * as it could be a new untracked file. |
| 447 | + * If we did not find an exact match for this pathname or any |
| 448 | + * cache-entries with this directory prefix and we're on a |
| 449 | + * case-insensitive file system, try again using the name-hash |
| 450 | + * and dir-name-hash. |
257 | 451 | */
|
258 |
| - untracked_cache_invalidate_path(istate, name, 0); |
| 452 | + if (!nr_in_cone && ignore_case) { |
| 453 | + nr_in_cone = handle_using_name_hash_icase(istate, name); |
| 454 | + if (!nr_in_cone) |
| 455 | + nr_in_cone = handle_using_dir_name_hash_icase( |
| 456 | + istate, name); |
| 457 | + } |
| 458 | + |
| 459 | + if (nr_in_cone) |
| 460 | + trace_printf_key(&trace_fsmonitor, |
| 461 | + "fsmonitor_refresh_callback CNT: %d", |
| 462 | + (int)nr_in_cone); |
259 | 463 | }
|
260 | 464 |
|
261 | 465 | /*
|
|
0 commit comments