Skip to content

Commit 596ea81

Browse files
jeffhostetlerdscho
authored andcommitted
fsmonitor: optimize processing of directory events
Teach Git to perform binary search over the cache-entries for a directory notification and then linearly scan forward to find the immediate children. Previously, when the FSMonitor reported a modified directory Git would perform a linear search on the entire cache-entry array for all entries matching that directory prefix and invalidate them. Since the cache-entry array is already sorted, we can use a binary search to find the first matching entry and then only linearly walk forward and invalidate entries until the prefix changes. Also, the original code would invalidate anything having the same directory prefix. Since a directory event should only be received for items that are immediately within the directory (and not within sub-directories of it), only invalidate those entries and not the whole subtree. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 016c8b8 commit 596ea81

File tree

1 file changed

+54
-17
lines changed

1 file changed

+54
-17
lines changed

fsmonitor.c

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -184,38 +184,75 @@ static int query_fsmonitor_hook(struct repository *r,
184184
static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
185185
{
186186
int i, len = strlen(name);
187-
if (name[len - 1] == '/') {
187+
int pos = index_name_pos(istate, name, len);
188+
189+
trace_printf_key(&trace_fsmonitor,
190+
"fsmonitor_refresh_callback '%s' (pos %d)",
191+
name, pos);
188192

193+
if (name[len - 1] == '/') {
189194
/*
190-
* TODO We should binary search to find the first path with
191-
* TODO this directory prefix. Then linearly update entries
192-
* TODO while the prefix matches. Taking care to search without
193-
* TODO the trailing slash -- because '/' sorts after a few
194-
* TODO interesting special chars, like '.' and ' '.
195+
* The daemon can decorate directory events, such as
196+
* moves or renames, with a trailing slash if the OS
197+
* FS Event contains sufficient information, such as
198+
* MacOS.
199+
*
200+
* Use this to invalidate the entire cone under that
201+
* directory.
202+
*
203+
* We do not expect an exact match because the index
204+
* does not normally contain directory entries, so we
205+
* start at the insertion point and scan.
195206
*/
207+
if (pos < 0)
208+
pos = -pos - 1;
196209

197210
/* Mark all entries for the folder invalid */
198-
for (i = 0; i < istate->cache_nr; i++) {
199-
if (istate->cache[i]->ce_flags & CE_FSMONITOR_VALID &&
200-
starts_with(istate->cache[i]->name, name))
201-
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
211+
for (i = pos; i < istate->cache_nr; i++) {
212+
if (!starts_with(istate->cache[i]->name, name))
213+
break;
214+
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
202215
}
203-
/* Need to remove the / from the path for the untracked cache */
216+
217+
/*
218+
* We need to remove the traling "/" from the path
219+
* for the untracked cache.
220+
*/
204221
name[len - 1] = '\0';
222+
} else if (pos >= 0) {
223+
/*
224+
* We have an exact match for this path and can just
225+
* invalidate it.
226+
*/
227+
istate->cache[pos]->ce_flags &= ~CE_FSMONITOR_VALID;
205228
} else {
206-
int pos = index_name_pos(istate, name, strlen(name));
207-
208-
if (pos >= 0) {
209-
struct cache_entry *ce = istate->cache[pos];
210-
ce->ce_flags &= ~CE_FSMONITOR_VALID;
229+
/*
230+
* The path is not a tracked file -or- it is a
231+
* directory event on a platform that cannot
232+
* distinguish between file and directory events in
233+
* the event handler, such as Windows.
234+
*
235+
* Scan as if it is a directory and invalidate the
236+
* cone under it. (But remember to ignore items
237+
* between "name" and "name/", such as "name-" and
238+
* "name.".
239+
*/
240+
pos = -pos - 1;
241+
242+
for (i = pos; i < istate->cache_nr; i++) {
243+
if (!starts_with(istate->cache[i]->name, name))
244+
break;
245+
if ((unsigned char)istate->cache[i]->name[len] > '/')
246+
break;
247+
if (istate->cache[i]->name[len] == '/')
248+
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
211249
}
212250
}
213251

214252
/*
215253
* Mark the untracked cache dirty even if it wasn't found in the index
216254
* as it could be a new untracked file.
217255
*/
218-
trace_printf_key(&trace_fsmonitor, "fsmonitor_refresh_callback '%s'", name);
219256
untracked_cache_invalidate_path(istate, name, 0);
220257
}
221258

0 commit comments

Comments
 (0)