At times, we may already know that a path represented by a cache_entry ce has no changes via some out-of-line means, like fsmonitor, and yet need the control to go through a codepath that requires us to have "struct stat" obtained by lstat() on the path, for various purposes (e.g. "ie_match_stat()" wants cached stat-info is still current wrt "struct stat", "diff" wants to know st_mode). The callers of lstat() on a tracked file, when its cache_entry knows it is up-to-date, can instead call this helper to pretend that it called lstat() by faking the "struct stat" information. Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> --- * Just setting the .st_mode member in check_removed() relies on the assumption that other members of "struct stat" are never looked at a bit too much. We could use something like this to bypass calling lstat() and instead populate from the data we have in cache_entry. builtin/ls-files.c | 5 ++++- read-cache-ll.h | 8 ++++++++ read-cache.c | 27 +++++++++++++++++++++++++++ statinfo.c | 27 +++++++++++++++++++++++++++ statinfo.h | 8 ++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git c/builtin/ls-files.c w/builtin/ls-files.c index a0229c3277..7031ffcb0a 100644 --- c/builtin/ls-files.c +++ w/builtin/ls-files.c @@ -450,7 +450,10 @@ static void show_files(struct repository *repo, struct dir_struct *dir) continue; if (ce_skip_worktree(ce)) continue; - stat_err = lstat(fullname.buf, &st); + if (!(ce->ce_flags & CE_FSMONITOR_VALID)) + stat_err = lstat(fullname.buf, &st); + else + stat_err = fake_lstat(ce, &st); if (stat_err && (errno != ENOENT && errno != ENOTDIR)) error_errno("cannot lstat '%s'", fullname.buf); if (stat_err && show_deleted) { diff --git c/read-cache-ll.h w/read-cache-ll.h index 9a1a7edc5a..2a50a784f0 100644 --- c/read-cache-ll.h +++ w/read-cache-ll.h @@ -436,6 +436,14 @@ int match_stat_data_racy(const struct index_state *istate, void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, struct stat *st); +/* + * Fill members of st by members of sd enough to convince match_stat() + * to consider that they match. It should be usable as a replacement + * for lstat() for a tracked path that is known to be up-to-date via + * some out-of-line means (like fsmonitor). + */ +int fake_lstat(const struct cache_entry *ce, struct stat *st); + #define REFRESH_REALLY (1 << 0) /* ignore_valid */ #define REFRESH_UNMERGED (1 << 1) /* allow unmerged */ #define REFRESH_QUIET (1 << 2) /* be quiet about it */ diff --git c/read-cache.c w/read-cache.c index 080bd39713..eb750e2e49 100644 --- c/read-cache.c +++ w/read-cache.c @@ -197,6 +197,33 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st } } +static unsigned int st_mode_from_ce(const struct cache_entry *ce) +{ + extern int trust_executable_bit, has_symlinks; + + switch (ce->ce_mode & S_IFMT) { + case S_IFLNK: + return has_symlinks ? S_IFLNK : (S_IFREG | 0644); + case S_IFREG: + return (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG; + case S_IFGITLINK: + return S_IFDIR | 0755; + case S_IFDIR: + return ce->ce_mode; + default: + BUG("unsupported ce_mode: %o", ce->ce_mode); + } +} + +int fake_lstat(const struct cache_entry *ce, struct stat *st) +{ + fake_lstat_data(&ce->ce_stat_data, st); + st->st_mode = st_mode_from_ce(ce); + + /* always succeed as lstat() replacement */ + return 0; +} + static int ce_compare_data(struct index_state *istate, const struct cache_entry *ce, struct stat *st) diff --git c/statinfo.c w/statinfo.c index 17bb8966c3..45156109de 100644 --- c/statinfo.c +++ w/statinfo.c @@ -15,6 +15,33 @@ void fill_stat_data(struct stat_data *sd, struct stat *st) sd->sd_size = st->st_size; } +static void set_times(struct stat *st, const struct stat_data *sd) +{ + st->st_ctime = sd->sd_ctime.sec; + st->st_mtime = sd->sd_mtime.sec; +#ifdef NO_NSEC + ; /* nothing */ +#else +#ifdef USE_ST_TIMESPEC + st->st_ctimespec.tv_nsec = sd->sd_ctime.nsec; + st->st_mtimespec.tv_nsec = sd->sd_mtime.nsec; +#else + st->st_ctim.tv_nsec = sd->sd_ctime.nsec; + st->st_mtim.tv_nsec = sd->sd_mtime.nsec; +#endif +#endif +} + +void fake_lstat_data(const struct stat_data *sd, struct stat *st) +{ + set_times(st, sd); + st->st_dev = sd->sd_dev; + st->st_ino = sd->sd_ino; + st->st_uid = sd->sd_uid; + st->st_gid = sd->sd_gid; + st->st_size = sd->sd_size; +} + int match_stat_data(const struct stat_data *sd, struct stat *st) { int changed = 0; diff --git c/statinfo.h w/statinfo.h index 700f502ac0..5b21a30f90 100644 --- c/statinfo.h +++ w/statinfo.h @@ -46,6 +46,14 @@ struct stat_validity { */ void fill_stat_data(struct stat_data *sd, struct stat *st); +/* + * The inverse of the above. When we know the cache_entry that + * contains sd is up-to-date, but still need to pretend we called + * lstat() to learn that fact, this function fills "st" enough to + * fool ie_match_stat(). + */ +void fake_lstat_data(const struct stat_data *sd, struct stat *st); + /* * Return 0 if st is consistent with a file not having been changed * since sd was filled. If there are differences, return a