]> git.proxmox.com Git - libgit2.git/commitdiff
git_path_diriter: use FindFirstFile in win32
authorEdward Thomson <ethomson@microsoft.com>
Wed, 29 Apr 2015 21:23:02 +0000 (17:23 -0400)
committerEdward Thomson <ethomson@microsoft.com>
Fri, 1 May 2015 16:31:40 +0000 (12:31 -0400)
Using FindFirstFile and FindNextFile in win32 allows us to
use the directory information that is returned, instead of
us having to get the file attributes all over again, which
is a distinct cost savings on win32.

src/iterator.c
src/path.c
src/path.h
src/win32/path_w32.c

index 52814bae78bd468fa30bc5c04ffd495ae65e5bbf..c5c5fd7ceff7d5daba065e82c7efc826f555ec2a 100644 (file)
@@ -1011,7 +1011,7 @@ static int dirload_with_stat(
        const char *end_stat,
        git_vector *contents)
 {
-       git_path_diriter diriter = {0};
+       git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
        const char *path;
        int (*strncomp)(const char *a, const char *b, size_t sz);
        size_t start_len = start_stat ? strlen(start_stat) : 0;
index ee566985ad34304e624923d569eae7456ea85faa..6c9852b79ac27710a5e5de15a7501ddaeb4bcced 100644 (file)
@@ -1078,6 +1078,182 @@ int git_path_direach(
        return error;
 }
 
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
+ * and better.  Prior versions will ignore this.
+ */
+#ifndef FIND_FIRST_EX_LARGE_FETCH
+# define FIND_FIRST_EX_LARGE_FETCH 2
+#endif
+
+int git_path_diriter_init(
+       git_path_diriter *diriter,
+       const char *path,
+       unsigned int flags)
+{
+       git_win32_path path_filter;
+       git_buf hack = {0};
+
+       assert(diriter && path);
+
+       memset(diriter, 0, sizeof(git_path_diriter));
+       diriter->handle = INVALID_HANDLE_VALUE;
+
+       if (git_buf_puts(&diriter->path_utf8, path) < 0)
+               return -1;
+
+       git_path_trim_slashes(&diriter->path_utf8);
+
+       if (diriter->path_utf8.size == 0) {
+               giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+               return -1;
+       }
+
+       if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 ||
+                       !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) {
+               giterr_set(GITERR_OS, "Could not parse the directory path '%s'", path);
+               return -1;
+       }
+
+       diriter->handle = FindFirstFileExW(
+               path_filter,
+               FindExInfoBasic,
+               &diriter->current,
+               FindExSearchNameMatch,
+               NULL,
+               FIND_FIRST_EX_LARGE_FETCH);
+
+       if (diriter->handle == INVALID_HANDLE_VALUE) {
+               giterr_set(GITERR_OS, "Could not open directory '%s'", path);
+               return -1;
+       }
+
+       diriter->parent_utf8_len = diriter->path_utf8.size;
+       diriter->flags = flags;
+       return 0;
+}
+
+static int diriter_update_utf16(git_path_diriter *diriter)
+{
+       size_t filename_len, path_len;
+
+       filename_len = wcslen(diriter->current.cFileName);
+
+       if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
+               GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
+               return -1;
+
+       if (path_len > GIT_WIN_PATH_UTF16) {
+               giterr_set(GITERR_FILESYSTEM,
+                       "invalid path '%.*ls\\%ls' (path too long)",
+                       diriter->parent_len, diriter->path, diriter->current.cFileName);
+               return -1;
+       }
+
+       diriter->path[diriter->parent_len] = L'\\';
+       memcpy(&diriter->path[diriter->parent_len+1],
+               diriter->current.cFileName, filename_len * sizeof(wchar_t));
+       diriter->path[path_len-1] = L'\0';
+
+       return 0;
+}
+
+static int diriter_update_utf8(git_path_diriter *diriter)
+{
+       git_win32_utf8_path filename_utf8;
+       wchar_t *filename_utf16;
+       int filename_utf8_len;
+
+       /* Don't copy the full UTF-16 path into the UTF-8 path, only do the
+        * UTF16 -> UTF8 conversion of the filename portion. This prevents us
+        * from trying to encode the parent path differently, which would be
+        * bad since we do arithmetic based on the already computed parent len.
+        */
+
+       filename_utf16 = &diriter->path[diriter->parent_len + 1];
+
+       if ((filename_utf8_len = git_win32_path_to_utf8(filename_utf8, filename_utf16)) < 0)
+               return filename_utf8_len;
+
+       git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
+       git_buf_putc(&diriter->path_utf8, '/');
+       git_buf_put(&diriter->path_utf8, filename_utf8, (size_t)filename_utf8_len);
+
+       if (git_buf_oom(&diriter->path_utf8))
+               return -1;
+
+       return 0;
+}
+
+int git_path_diriter_next(git_path_diriter *diriter)
+{
+       bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+
+       do {
+               /* Our first time through, we already have the data from
+                * FindFirstFileW.  Use it, otherwise get the next file.
+                */
+               if (!diriter->needs_next)
+                       diriter->needs_next = 1;
+               else if (!FindNextFileW(diriter->handle, &diriter->current))
+                       return GIT_ITEROVER;
+       } while (skip_dot && git_path_is_dot_or_dotdotW(diriter->current.cFileName));
+
+       if (diriter_update_utf16(diriter) < 0 || diriter_update_utf8(diriter) < 0)
+               return -1;
+
+       return 0;
+}
+
+int git_path_diriter_filename(
+       const char **out,
+       size_t *out_len,
+       git_path_diriter *diriter)
+{
+       assert(out && out_len && diriter);
+
+       assert(diriter->path_utf8.size > diriter->parent_utf8_len);
+
+       *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
+       *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
+       return 0;
+}
+
+int git_path_diriter_fullpath(
+       const char **out,
+       size_t *out_len,
+       git_path_diriter *diriter)
+{
+       assert(out && out_len && diriter);
+
+       *out = diriter->path_utf8.ptr;
+       *out_len = diriter->path_utf8.size;
+       return 0;
+}
+
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
+{
+       assert(out && diriter);
+
+       return git_win32__file_attribute_to_stat(out,
+               (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
+               diriter->path);
+}
+
+void git_path_diriter_free(git_path_diriter *diriter)
+{
+       if (diriter == NULL)
+               return;
+
+       if (diriter->handle != INVALID_HANDLE_VALUE) {
+               FindClose(diriter->handle);
+               diriter->handle = INVALID_HANDLE_VALUE;
+       }
+}
+
+#else
+
 int git_path_diriter_init(
        git_path_diriter *diriter,
        const char *path,
@@ -1090,9 +1266,13 @@ int git_path_diriter_init(
        if (git_buf_puts(&diriter->path, path) < 0)
                return -1;
 
-       git_path_mkposix(diriter->path.ptr);
        git_path_trim_slashes(&diriter->path);
 
+       if (diriter->path.size == 0) {
+               giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
+               return -1;
+       }
+
        if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
                git_buf_free(&diriter->path);
 
@@ -1159,6 +1339,8 @@ int git_path_diriter_filename(
 {
        assert(out && out_len && diriter);
 
+       assert(diriter->path.size > diriter->parent_len);
+
        *out = &diriter->path.ptr[diriter->parent_len+1];
        *out_len = diriter->path.size - diriter->parent_len - 1;
        return 0;
@@ -1200,13 +1382,15 @@ void git_path_diriter_free(git_path_diriter *diriter)
        git_buf_free(&diriter->path);
 }
 
+#endif
+
 int git_path_dirload(
        git_vector *contents,
        const char *path,
        size_t prefix_len,
        unsigned int flags)
 {
-       git_path_diriter iter = {0};
+       git_path_diriter iter = GIT_PATH_DIRITER_INIT;
        const char *name;
        size_t name_len;
        char *dup;
index 927d2fc6ea2f3b05ecf9c05e2059c8ca7fd6e70d..9c2b85a877655ec65d052909a0afa6914878c521 100644 (file)
@@ -329,6 +329,28 @@ extern int git_path_walk_up(
 
 typedef struct git_path_diriter git_path_diriter;
 
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+struct git_path_diriter
+{
+       git_win32_path path;
+       size_t parent_len;
+
+       git_buf path_utf8;
+       size_t parent_utf8_len;
+
+       HANDLE handle;
+
+       unsigned int flags;
+
+       WIN32_FIND_DATAW current;
+       unsigned int needs_next;
+};
+
+#define GIT_PATH_DIRITER_INIT { {0}, 0, GIT_BUF_INIT, 0, INVALID_HANDLE_VALUE }
+
+#else
+
 struct git_path_diriter
 {
        git_buf path;
@@ -339,6 +361,10 @@ struct git_path_diriter
        DIR *dir;
 };
 
+#define GIT_PATH_DIRITER_INIT { GIT_BUF_INIT }
+
+#endif
+
 /**
  * Initialize a directory iterator.
  *
index c145379f7cb8f7097469b9ea337a4d2a9cbc9417..118e8bcc568b529759eba8e88e138871bb7d4ed9 100644 (file)
 #define path__is_unc(p) \
        (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/'))
 
-/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
- * and better.  Prior versions will ignore this.
- */
-#define _FIND_FIRST_EX_LARGE_FETCH 2
-
 GIT_INLINE(int) path__cwd(wchar_t *path, int size)
 {
        int len;