Skip to content

Commit 306a691

Browse files
jeffhostetlerGit for Windows Build Agent
authored and
Git for Windows Build Agent
committed
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 a13e664 commit 306a691

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
@@ -198,38 +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-
if (name[len - 1] == '/') {
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);
202206

207+
if (name[len - 1] == '/') {
203208
/*
204-
* TODO We should binary search to find the first path with
205-
* TODO this directory prefix. Then linearly update entries
206-
* TODO while the prefix matches. Taking care to search without
207-
* TODO the trailing slash -- because '/' sorts after a few
208-
* TODO interesting special chars, like '.' and ' '.
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.
209220
*/
221+
if (pos < 0)
222+
pos = -pos - 1;
210223

211224
/* Mark all entries for the folder invalid */
212-
for (i = 0; i < istate->cache_nr; i++) {
213-
if (istate->cache[i]->ce_flags & CE_FSMONITOR_VALID &&
214-
starts_with(istate->cache[i]->name, name))
215-
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
225+
for (i = pos; i < istate->cache_nr; i++) {
226+
if (!starts_with(istate->cache[i]->name, name))
227+
break;
228+
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
216229
}
217-
/* 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+
*/
218235
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;
219242
} else {
220-
int pos = index_name_pos(istate, name, strlen(name));
221-
222-
if (pos >= 0) {
223-
struct cache_entry *ce = istate->cache[pos];
224-
ce->ce_flags &= ~CE_FSMONITOR_VALID;
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;
255+
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;
225263
}
226264
}
227265

228266
/*
229267
* Mark the untracked cache dirty even if it wasn't found in the index
230268
* as it could be a new untracked file.
231269
*/
232-
trace_printf_key(&trace_fsmonitor, "fsmonitor_refresh_callback '%s'", name);
233270
untracked_cache_invalidate_path(istate, name, 0);
234271
}
235272

0 commit comments

Comments
 (0)