git_path_diriter: use FindFirstFile in win32

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.
This commit is contained in:
Edward Thomson 2015-04-29 17:23:02 -04:00
parent 5c387b6c5a
commit f63a1b729b
4 changed files with 213 additions and 8 deletions

View 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;

View 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;

View 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.
*

View File

@ -30,11 +30,6 @@
#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;