From 0cce83445cb03b9ae93c2f7f3427e0ca2063dc73 Mon Sep 17 00:00:00 2001 From: Marius Storm-Olsen Date: Mon, 3 Sep 2007 20:40:26 +0200 Subject: [PATCH] Add a new lstat and fstat implementation based on Win32 API This gives us a significant speedup when adding, committing and stat'ing files. Also, since Windows doesn't really handle symlinks, we let stat just uses lstat. We also need to replace fstat, since our implementation and the standard stat() functions report slightly different timestamps, possibly due to timezones. We simply report UTC in our implementation, and do our FILETIME to time_t conversion based on the document at http://support.microsoft.com/kb/167296. With Moe's repo structure (100K files in 100 dirs, containing 2-4 bytes) mkdir bummer && cd bummer; for ((i=0;i<100;i++)); do mkdir $i && pushd $i; for ((j=0;j<1000;j++)); do echo "$j" >$j; done; popd; done We get the following performance boost: With normal lstat & stat Custom lstat/fstat ------------------------ ------------------------ Command: git init Command: git init ------------------------ ------------------------ real 0m 0.047s real 0m 0.063s user 0m 0.031s user 0m 0.015s sys 0m 0.000s sys 0m 0.015s ------------------------ ------------------------ Command: git add . Command: git add . ------------------------ ------------------------ real 0m19.390s real 0m12.031s 1.6x user 0m 0.015s user 0m 0.031s sys 0m 0.030s sys 0m 0.000s ------------------------ ------------------------ Command: git commit -a.. Command: git commit -a.. ------------------------ ------------------------ real 0m30.812s real 0m16.875s 1.8x user 0m 0.015s user 0m 0.015s sys 0m 0.000s sys 0m 0.015s ------------------------ ------------------------ 3x Command: git-status 3x Command: git-status ------------------------ ------------------------ real 0m11.860s real 0m 5.266s 2.2x user 0m 0.015s user 0m 0.015s sys 0m 0.015s sys 0m 0.015s real 0m11.703s real 0m 5.234s user 0m 0.015s user 0m 0.015s sys 0m 0.000s sys 0m 0.000s real 0m11.672s real 0m 5.250s user 0m 0.031s user 0m 0.015s sys 0m 0.000s sys 0m 0.000s ------------------------ ------------------------ Command: git commit... Command: git commit... (single file) (single file) ------------------------ ------------------------ real 0m14.234s real 0m 7.735s 1.8x user 0m 0.015s user 0m 0.031s sys 0m 0.000s sys 0m 0.000s Signed-off-by: Marius Storm-Olsen --- compat/mingw.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- git-compat-util.h | 8 ++++ 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 7711a3fb39..20eadd2334 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -23,12 +23,81 @@ int fchmod(int fildes, mode_t mode) return -1; } -int lstat(const char *file_name, struct stat *buf) +static inline time_t filetime_to_time_t(const FILETIME *ft) +{ + long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ + winTime /= 10000000; /* Nano to seconds resolution */ + return (time_t)winTime; +} + +extern int _getdrive( void ); +/* We keep the do_lstat code in a separate function to avoid recursion. + * When a path ends with a slash, the stat will fail with ENOENT. In + * this case, we strip the trailing slashes and stat again. + */ +static int do_lstat(const char *file_name, struct stat *buf) +{ + WIN32_FILE_ATTRIBUTE_DATA fdata; + + if (GetFileAttributesExA(file_name, GetFileExInfoStandard, &fdata)) { + int fMode = S_IREAD; + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + fMode |= S_IFDIR; + else + fMode |= S_IFREG; + if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + fMode |= S_IWRITE; + + buf->st_ino = 0; + buf->st_gid = 0; + buf->st_uid = 0; + buf->st_nlink = 1; + buf->st_mode = fMode; + buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ + buf->st_dev = buf->st_rdev = (_getdrive() - 1); + buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); + buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); + buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + errno = 0; + return 0; + } + + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_SHARING_BUFFER_EXCEEDED: + errno = EACCES; + break; + case ERROR_BUFFER_OVERFLOW: + errno = ENAMETOOLONG; + break; + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + break; + case ERROR_INVALID_NAME: + errno = EFAULT; + break; + default: + errno = ENOENT; + break; + } + return -1; +} + +/* We provide our own lstat/fstat functions, since the provided + * lstat/fstat functions are so slow. These stat functions are + * tailored for Git's usage (read: fast), and are not meant to be + * complete. Note that Git stat()s are redirected to git_lstat() + * too, since Windows doesn't really handle symlinks that well. + */ +int git_lstat(const char *file_name, struct stat *buf) { int namelen; static char alt_name[PATH_MAX]; - if (!stat(file_name, buf)) + if (!do_lstat(file_name, buf)) return 0; /* if file_name ended in a '/', Windows returned ENOENT; @@ -36,6 +105,7 @@ int lstat(const char *file_name, struct stat *buf) */ if (errno != ENOENT) return -1; + namelen = strlen(file_name); if (namelen && file_name[namelen-1] != '/') return -1; @@ -43,9 +113,40 @@ int lstat(const char *file_name, struct stat *buf) --namelen; if (!namelen || namelen >= PATH_MAX) return -1; + memcpy(alt_name, file_name, namelen); alt_name[namelen] = 0; - return stat(alt_name, buf); + return do_lstat(alt_name, buf); +} + +int git_fstat(int fd, struct stat *buf) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + BY_HANDLE_FILE_INFORMATION fdata; + + if (fh != INVALID_HANDLE_VALUE && GetFileInformationByHandle(fh, &fdata)) { + int fMode = S_IREAD; + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + fMode |= S_IFDIR; + else + fMode |= S_IFREG; + if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + fMode |= S_IWRITE; + + buf->st_ino = 0; + buf->st_gid = 0; + buf->st_uid = 0; + buf->st_nlink = 1; + buf->st_mode = fMode; + buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ + buf->st_dev = buf->st_rdev = (_getdrive() - 1); + buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); + buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); + buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + return 0; + } + errno = EBADF; + return -1; } /* missing: link, mkstemp, fchmod, getuid (?), gettimeofday */ diff --git a/git-compat-util.h b/git-compat-util.h index 1ba499f750..bdc2e3f5fc 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -488,6 +488,14 @@ int mingw_rename(const char*, const char*); extern void quote_argv(const char **dst, const char **src); extern const char *parse_interpreter(const char *cmd); +/* Use git_lstat() instead of lstat()/stat() and + * git_fstat() instead of fstat() on Windows + */ +int git_lstat(const char *file_name, struct stat *buf); +int git_fstat(int fd, struct stat *buf); +#define lstat(x,y) git_lstat(x,y) +#define stat(x,y) git_lstat(x,y) +#define fstat(x,y) git_fstat(x,y) #endif /* __MINGW32__ */ #endif -- 2.11.4.GIT