Skip to content

Commit 43c3ae9

Browse files
jeffhostetlerGit for Windows Build Agent
authored and
Git for Windows Build Agent
committed
dir.c: regression fix for add_excludes with fscache
Fix regression described in: #1392 which was introduced in: b235337 Problem Symptoms ================ When the user has a .gitignore file that is a symlink, the fscache optimization introduced above caused the stat-data from the symlink, rather that of the target file, to be returned. Later when the ignore file was read, the buffer length did not match the stat.st_size field and we called die("cannot use <path> as an exclude file") Optimization Rationale ====================== The above optimization calls lstat() before open() primarily to ask fscache if the file exists. It gets the current stat-data as a side effect essentially for free (since we already have it in memory). If the file does not exist, it does not need to call open(). And since very few directories have .gitignore files, we can greatly reduce time spent in the filesystem. Discussion of Fix ================= The above optimization calls lstat() rather than stat() because the fscache only intercepts lstat() calls. Calls to stat() stay directed to the mingw_stat() completly bypassing fscache. Furthermore, calls to mingw_stat() always call {open, fstat, close} so that symlinks are properly dereferenced, which adds *additional* open/close calls on top of what the original code in dir.c is doing. Since the problem only manifests for symlinks, we add code to overwrite the stat-data when the path is a symlink. This preserves the effect of the performance gains provided by the fscache in the normal case. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 32bf2bc commit 43c3ae9

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

dir.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,13 +1114,41 @@ static int add_patterns(const char *fname, const char *base, int baselen,
11141114
size_t size = 0;
11151115
char *buf;
11161116

1117+
/*
1118+
* A performance optimization for status.
1119+
*
1120+
* During a status scan, git looks in each directory for a .gitignore
1121+
* file before scanning the directory. Since .gitignore files are not
1122+
* that common, we can waste a lot of time looking for files that are
1123+
* not there. Fortunately, the fscache already knows if the directory
1124+
* contains a .gitignore file, since it has already read the directory
1125+
* and it already has the stat-data.
1126+
*
1127+
* If the fscache is enabled, use the fscache-lstat() interlude to see
1128+
* if the file exists (in the fscache hash maps) before trying to open()
1129+
* it.
1130+
*
1131+
* This causes problem when the .gitignore file is a symlink, because
1132+
* we call lstat() rather than stat() on the symlnk and the resulting
1133+
* stat-data is for the symlink itself rather than the target file.
1134+
* We CANNOT use stat() here because the fscache DOES NOT install an
1135+
* interlude for stat() and mingw_stat() always calls "open-fstat-close"
1136+
* on the file and defeats the purpose of the optimization here. Since
1137+
* symlinks are even more rare than .gitignore files, we force a fstat()
1138+
* after our open() to get stat-data for the target file.
1139+
*/
11171140
if (is_fscache_enabled(fname)) {
11181141
if (lstat(fname, &st) < 0) {
11191142
fd = -1;
11201143
} else {
11211144
fd = open(fname, O_RDONLY);
11221145
if (fd < 0)
11231146
warn_on_fopen_errors(fname);
1147+
else if (S_ISLNK(st.st_mode) && fstat(fd, &st) < 0) {
1148+
warn_on_fopen_errors(fname);
1149+
close(fd);
1150+
fd = -1;
1151+
}
11241152
}
11251153
} else {
11261154
if (flags & PATTERN_NOFOLLOW)

0 commit comments

Comments
 (0)