diff --git a/Documentation/config.txt b/Documentation/config.txt index bbe38ccaa2..da18a5458e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -117,6 +117,15 @@ core.fileMode:: the working copy are ignored; useful on broken filesystems like FAT. See linkgit:git-update-index[1]. True by default. +core.ignoreCygwinFSTricks:: + This option is only used by Cygwin implementation of Git. If false, + the Cygwin stat() and lstat() functions are used. This may be useful + if your repository consists of a few separate directories joined in + one hierarchy using Cygwin mount. If true, Git uses native Win32 API + whenever it is possible and falls back to Cygwin functions only to + handle symbol links. The native mode is more than twice faster than + normal Cygwin l/stat() functions. True by default. + core.trustctime:: If false, the ctime differences between the index and the working copy are ignored; useful when the inode change time diff --git a/Makefile b/Makefile index e0c03c3eec..7db2ccc296 100644 --- a/Makefile +++ b/Makefile @@ -346,6 +346,7 @@ LIB_H += cache.h LIB_H += cache-tree.h LIB_H += commit.h LIB_H += compat/mingw.h +LIB_H += compat/cygwin.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -748,6 +749,9 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif +ifneq (,$(findstring CYGWIN,$(uname_S))) + COMPAT_OBJS += compat/cygwin.o +endif ifneq (,$(findstring MINGW,$(uname_S))) NO_MMAP = YesPlease NO_PREAD = YesPlease diff --git a/compat/cygwin.c b/compat/cygwin.c new file mode 100644 index 0000000000..423ff20b0e --- /dev/null +++ b/compat/cygwin.c @@ -0,0 +1,127 @@ +#define WIN32_LEAN_AND_MEAN +#include "../git-compat-util.h" +#include "win32.h" +#include "../cache.h" /* to read configuration */ + +static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts) +{ + long long winTime = ((long long)ft->dwHighDateTime << 32) + + ft->dwLowDateTime; + winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ + /* convert 100-nsecond interval to seconds and nanoseconds */ + ts->tv_sec = (time_t)(winTime/10000000); + ts->tv_nsec = (long)(winTime - ts->tv_sec*10000000LL) * 100; +} + +#define size_to_blocks(s) (((s)+511)/512) + +/* do_stat is a common implementation for cygwin_lstat and cygwin_stat. + * + * To simplify its logic, in the case of cygwin symlinks, this implementation + * falls back to the cygwin version of stat/lstat, which is provided as the + * last argument. + */ +static int do_stat(const char *file_name, struct stat *buf, stat_fn_t cygstat) +{ + WIN32_FILE_ATTRIBUTE_DATA fdata; + + if (file_name[0] == '/') + return cygstat (file_name, buf); + + if (!(errno = get_file_attr(file_name, &fdata))) { + /* + * If the system attribute is set and it is not a directory then + * it could be a symbol link created in the nowinsymlinks mode. + * Normally, Cygwin works in the winsymlinks mode, so this situation + * is very unlikely. For the sake of simplicity of our code, let's + * Cygwin to handle it. + */ + if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) && + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + return cygstat(file_name, buf); + + /* fill out the stat structure */ + buf->st_dev = buf->st_rdev = 0; /* not used by Git */ + buf->st_ino = 0; + buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes); + buf->st_nlink = 1; + buf->st_uid = buf->st_gid = 0; +#ifdef __CYGWIN_USE_BIG_TYPES__ + buf->st_size = ((_off64_t)fdata.nFileSizeHigh << 32) + + fdata.nFileSizeLow; +#else + buf->st_size = (off_t)fdata.nFileSizeLow; +#endif + buf->st_blocks = size_to_blocks(buf->st_size); + filetime_to_timespec(&fdata.ftLastAccessTime, &buf->st_atim); + filetime_to_timespec(&fdata.ftLastWriteTime, &buf->st_mtim); + filetime_to_timespec(&fdata.ftCreationTime, &buf->st_ctim); + return 0; + } else if (errno == ENOENT) { + /* + * In the winsymlinks mode (which is the default), Cygwin + * emulates symbol links using Windows shortcut files. These + * files are formed by adding .lnk extension. So, if we have + * not found the specified file name, it could be that it is + * a symbol link. Let's Cygwin to deal with that. + */ + return cygstat(file_name, buf); + } + return -1; +} + +/* We provide our own lstat/stat functions, since the provided Cygwin versions + * of these functions are too slow. These stat functions are tailored for Git's + * usage, and therefore they are not meant to be complete and correct emulation + * of lstat/stat functionality. + */ +static int cygwin_lstat(const char *path, struct stat *buf) +{ + return do_stat(path, buf, lstat); +} + +static int cygwin_stat(const char *path, struct stat *buf) +{ + return do_stat(path, buf, stat); +} + + +/* + * At start up, we are trying to determine whether Win32 API or cygwin stat + * functions should be used. The choice is determined by core.ignorecygwinfstricks. + * Reading this option is not always possible immediately as git_dir may be + * not be set yet. So until it is set, use cygwin lstat/stat functions. + */ +static int native_stat = 1; + +static int git_cygwin_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "core.ignorecygwinfstricks")) + native_stat = git_config_bool(var, value); + return 0; +} + +static int init_stat(void) +{ + if (have_git_dir()) { + git_config(git_cygwin_config, NULL); + cygwin_stat_fn = native_stat ? cygwin_stat : stat; + cygwin_lstat_fn = native_stat ? cygwin_lstat : lstat; + return 0; + } + return 1; +} + +static int cygwin_stat_stub(const char *file_name, struct stat *buf) +{ + return (init_stat() ? stat : *cygwin_stat_fn)(file_name, buf); +} + +static int cygwin_lstat_stub(const char *file_name, struct stat *buf) +{ + return (init_stat() ? lstat : *cygwin_lstat_fn)(file_name, buf); +} + +stat_fn_t cygwin_stat_fn = cygwin_stat_stub; +stat_fn_t cygwin_lstat_fn = cygwin_lstat_stub; + diff --git a/compat/cygwin.h b/compat/cygwin.h new file mode 100644 index 0000000000..a3229f5b4f --- /dev/null +++ b/compat/cygwin.h @@ -0,0 +1,9 @@ +#include +#include + +typedef int (*stat_fn_t)(const char*, struct stat*); +extern stat_fn_t cygwin_stat_fn; +extern stat_fn_t cygwin_lstat_fn; + +#define stat(path, buf) (*cygwin_stat_fn)(path, buf) +#define lstat(path, buf) (*cygwin_lstat_fn)(path, buf) diff --git a/git-compat-util.h b/git-compat-util.h index 2ac832f3b4..e20b1e858c 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -85,6 +85,7 @@ #undef _XOPEN_SOURCE #include #define _XOPEN_SOURCE 600 +#include "compat/cygwin.h" #else #undef _ALL_SOURCE /* AIX 5.3L defines a struct list with _ALL_SOURCE. */ #include