Skip to content

Commit a89e24f

Browse files
committed
fsmonitor: invalidate entire cone on directory move/rename
Invalidate the CE_FSMONITOR_VALID bit for all of the files in the cone under a directory when it is moved or renamed. Previously, we only invalidated the files immediately contained within the directory, but that ignored the fact that the pathnames of nested items also changed. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 711baf1 commit a89e24f

File tree

2 files changed

+77
-12
lines changed

2 files changed

+77
-12
lines changed

fsmonitor.c

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,37 +198,75 @@ int fsmonitor_is_trivial_response(const struct strbuf *query_result)
198198
static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
199199
{
200200
int i, len = strlen(name);
201+
int pos = index_name_pos(istate, name, len);
202+
203+
trace_printf_key(&trace_fsmonitor,
204+
"fsmonitor_refresh_callback '%s' (pos %d)",
205+
name, pos);
206+
201207
if (name[len - 1] == '/') {
202-
const char *rel;
203-
int pos = index_name_pos(istate, name, len);
208+
/*
209+
* The daemon can decorate directory events, such as
210+
* moves or renames, with a trailing slash if the OS
211+
* FS Event contains sufficient information, such as
212+
* MacOS.
213+
*
214+
* Use this to invalidate the entire cone under that
215+
* directory.
216+
*
217+
* We do not expect an exact match because the index
218+
* does not normally contain directory entries, so we
219+
* start at the insertion point and scan.
220+
*/
204221
if (pos < 0)
205222
pos = -pos - 1;
206223

207224
/* Mark all entries for the folder invalid */
208225
for (i = pos; i < istate->cache_nr; i++) {
209226
if (!starts_with(istate->cache[i]->name, name))
210227
break;
211-
/* Only mark the immediate children in the folder */
212-
rel = istate->cache[i]->name + len;
213-
if (!strchr(rel, '/'))
214-
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
228+
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
215229
}
216-
/* Need to remove the / from the path for the untracked cache */
230+
231+
/*
232+
* We need to remove the traling "/" from the path
233+
* for the untracked cache.
234+
*/
217235
name[len - 1] = '\0';
236+
} else if (pos >= 0) {
237+
/*
238+
* We have an exact match for this path and can just
239+
* invalidate it.
240+
*/
241+
istate->cache[pos]->ce_flags &= ~CE_FSMONITOR_VALID;
218242
} else {
219-
int pos = index_name_pos(istate, name, len);
243+
/*
244+
* The path is not a tracked file -or- it is a
245+
* directory event on a platform that cannot
246+
* distinguish between file and directory events in
247+
* the event handler, such as Windows.
248+
*
249+
* Scan as if it is a directory and invalidate the
250+
* cone under it. (But remember to ignore items
251+
* between "name" and "name/", such as "name-" and
252+
* "name.".
253+
*/
254+
pos = -pos - 1;
220255

221-
if (pos >= 0) {
222-
struct cache_entry *ce = istate->cache[pos];
223-
ce->ce_flags &= ~CE_FSMONITOR_VALID;
256+
for (i = pos; i < istate->cache_nr; i++) {
257+
if (!starts_with(istate->cache[i]->name, name))
258+
break;
259+
if ((unsigned char)istate->cache[i]->name[len] > '/')
260+
break;
261+
if (istate->cache[i]->name[len] == '/')
262+
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
224263
}
225264
}
226265

227266
/*
228267
* Mark the untracked cache dirty even if it wasn't found in the index
229268
* as it could be a new untracked file.
230269
*/
231-
trace_printf_key(&trace_fsmonitor, "fsmonitor_refresh_callback '%s'", name);
232270
untracked_cache_invalidate_path(istate, name, 0);
233271
}
234272

t/t7527-builtin-fsmonitor.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ test_expect_success 'setup' '
261261
trace*
262262
EOF
263263
264+
mkdir -p T1/T2/T3/T4 &&
265+
echo 1 >T1/F1 &&
266+
echo 1 >T1/T2/F1 &&
267+
echo 1 >T1/T2/T3/F1 &&
268+
echo 1 >T1/T2/T3/T4/F1 &&
269+
echo 2 >T1/F2 &&
270+
echo 2 >T1/T2/F2 &&
271+
echo 2 >T1/T2/T3/F2 &&
272+
echo 2 >T1/T2/T3/T4/F2 &&
273+
264274
git -c core.useBuiltinFSMonitor= add . &&
265275
test_tick &&
266276
git -c core.useBuiltinFSMonitor= commit -m initial &&
@@ -354,6 +364,19 @@ verify_status() {
354364
echo HELLO AFTER
355365
}
356366

367+
move_directory_contents_deeper() {
368+
mkdir T1/_new_
369+
mv T1/[A-Z]* T1/_new_
370+
}
371+
372+
move_directory_up() {
373+
mv T1/T2/T3 T1
374+
}
375+
376+
move_directory() {
377+
mv T1/T2/T3 T1/T2/NewT3
378+
}
379+
357380
# The next few test cases confirm that our fsmonitor daemon sees each type
358381
# of OS filesystem notification that we care about. At this layer we just
359382
# ensure we are getting the OS notifications and do not try to confirm what
@@ -622,6 +645,7 @@ test_expect_success 'Matrix: setup for untracked-cache,fsmonitor matrix' '
622645
'
623646

624647
matrix_clean_up_repo () {
648+
git clean -fd
625649
git reset --hard HEAD
626650
git clean -fd
627651
}
@@ -687,6 +711,9 @@ do
687711
matrix_try $uc_val $fsm_val rename_files
688712
matrix_try $uc_val $fsm_val file_to_directory
689713
matrix_try $uc_val $fsm_val directory_to_file
714+
matrix_try $uc_val $fsm_val move_directory_contents_deeper
715+
matrix_try $uc_val $fsm_val move_directory_up
716+
matrix_try $uc_val $fsm_val move_directory
690717

691718
if test $fsm_val = true
692719
then

0 commit comments

Comments
 (0)