summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2015-05-04 11:16:52 +0200
committerCarlos Martín Nieto <cmn@dwim.me>2015-05-04 11:16:52 +0200
commitcfc2e56d59db3d0d2b58aeffc4ef4640b7f846b5 (patch)
treec701b88b8d94a83b9961040d68517ab348aa009b
parent72f8da9175c823036077445915309e250d802b3b (diff)
parentbe3f104967ad21e949f72ef10a6b5ec00795ffaa (diff)
downloadlibgit2-cfc2e56d59db3d0d2b58aeffc4ef4640b7f846b5.tar.gz
Merge pull request #3087 from ethomson/pr/3054
Performance Improvements to Status on Windows
-rw-r--r--src/attr.c6
-rw-r--r--src/attr_file.c18
-rw-r--r--src/attr_file.h4
-rw-r--r--src/ignore.c6
-rw-r--r--src/ignore.h2
-rw-r--r--src/iterator.c145
-rw-r--r--src/path.c413
-rw-r--r--src/path.h192
-rw-r--r--src/posix.h1
-rw-r--r--src/unix/posix.h1
-rw-r--r--src/win32/buffer.c55
-rw-r--r--src/win32/buffer.h18
-rw-r--r--src/win32/path_w32.c75
-rw-r--r--src/win32/path_w32.h3
-rw-r--r--src/win32/posix.h4
-rw-r--r--src/win32/posix_w32.c120
-rw-r--r--src/win32/utf-conv.c4
-rw-r--r--src/win32/utf-conv.h4
-rw-r--r--src/win32/w32_util.h72
-rw-r--r--tests/attr/lookup.c6
-rw-r--r--tests/core/dirent.c59
-rw-r--r--tests/diff/drivers.c2
22 files changed, 852 insertions, 358 deletions
diff --git a/src/attr.c b/src/attr.c
index 38420807a..102d0248c 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -55,7 +55,7 @@ int git_attr_get(
*value = NULL;
- if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
return -1;
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
@@ -114,7 +114,7 @@ int git_attr_get_many_with_session(
assert(values && repo && names);
- if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
return -1;
if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
@@ -193,7 +193,7 @@ int git_attr_foreach(
assert(repo && callback);
- if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
return -1;
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
diff --git a/src/attr_file.c b/src/attr_file.c
index eed39661f..ef98aacc2 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -457,7 +457,7 @@ git_attr_assignment *git_attr_rule__lookup_assignment(
}
int git_attr_path__init(
- git_attr_path *info, const char *path, const char *base)
+ git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
{
ssize_t root;
@@ -488,7 +488,21 @@ int git_attr_path__init(
if (!info->basename || !*info->basename)
info->basename = info->path;
- info->is_dir = (int)git_path_isdir(info->full.ptr);
+ switch (dir_flag)
+ {
+ case GIT_DIR_FLAG_FALSE:
+ info->is_dir = 0;
+ break;
+
+ case GIT_DIR_FLAG_TRUE:
+ info->is_dir = 1;
+ break;
+
+ case GIT_DIR_FLAG_UNKNOWN:
+ default:
+ info->is_dir = (int)git_path_isdir(info->full.ptr);
+ break;
+ }
return 0;
}
diff --git a/src/attr_file.h b/src/attr_file.h
index aa9a16de0..388ecf4c0 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -202,8 +202,10 @@ extern bool git_attr_rule__match(
extern git_attr_assignment *git_attr_rule__lookup_assignment(
git_attr_rule *rule, const char *name);
+typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
+
extern int git_attr_path__init(
- git_attr_path *info, const char *path, const char *base);
+ git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
extern void git_attr_path__free(git_attr_path *info);
diff --git a/src/ignore.c b/src/ignore.c
index 3a5efedce..7ad8500e8 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -388,7 +388,7 @@ static bool ignore_lookup_in_rules(
}
int git_ignore__lookup(
- int *out, git_ignores *ignores, const char *pathname)
+ int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
{
unsigned int i;
git_attr_file *file;
@@ -397,7 +397,7 @@ int git_ignore__lookup(
*out = GIT_IGNORE_NOTFOUND;
if (git_attr_path__init(
- &path, pathname, git_repository_workdir(ignores->repo)) < 0)
+ &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
return -1;
/* first process builtins - success means path was found */
@@ -470,7 +470,7 @@ int git_ignore_path_is_ignored(
memset(&path, 0, sizeof(path));
memset(&ignores, 0, sizeof(ignores));
- if ((error = git_attr_path__init(&path, pathname, workdir)) < 0 ||
+ if ((error = git_attr_path__init(&path, pathname, workdir, GIT_DIR_FLAG_UNKNOWN)) < 0 ||
(error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
goto cleanup;
diff --git a/src/ignore.h b/src/ignore.h
index 77668c661..d40bd60f9 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -49,7 +49,7 @@ enum {
GIT_IGNORE_TRUE = 1,
};
-extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path);
+extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag);
/* command line Git sometimes generates an error message if given a
* pathspec that contains an exact match to an ignored file (provided
diff --git a/src/iterator.c b/src/iterator.c
index 9ddacebd1..c5c5fd7ce 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -920,12 +920,31 @@ struct fs_iterator {
#define FS_MAX_DEPTH 100
+typedef struct {
+ struct stat st;
+ size_t path_len;
+ char path[GIT_FLEX_ARRAY];
+} fs_iterator_path_with_stat;
+
+static int fs_iterator_path_with_stat_cmp(const void *a, const void *b)
+{
+ const fs_iterator_path_with_stat *psa = a, *psb = b;
+ return strcmp(psa->path, psb->path);
+}
+
+static int fs_iterator_path_with_stat_cmp_icase(const void *a, const void *b)
+{
+ const fs_iterator_path_with_stat *psa = a, *psb = b;
+ return strcasecmp(psa->path, psb->path);
+}
+
static fs_iterator_frame *fs_iterator__alloc_frame(fs_iterator *fi)
{
fs_iterator_frame *ff = git__calloc(1, sizeof(fs_iterator_frame));
git_vector_cmp entry_compare = CASESELECT(
iterator__ignore_case(fi),
- git_path_with_stat_cmp_icase, git_path_with_stat_cmp);
+ fs_iterator_path_with_stat_cmp_icase,
+ fs_iterator_path_with_stat_cmp);
if (ff && git_vector_init(&ff->entries, 0, entry_compare) < 0) {
git__free(ff);
@@ -967,7 +986,7 @@ static int fs_iterator__advance_over(
static int fs_iterator__entry_cmp(const void *i, const void *item)
{
const fs_iterator *fi = (const fs_iterator *)i;
- const git_path_with_stat *ps = item;
+ const fs_iterator_path_with_stat *ps = item;
return fi->base.prefixcomp(fi->base.start, ps->path);
}
@@ -984,6 +1003,96 @@ static void fs_iterator__seek_frame_start(
ff->index = 0;
}
+static int dirload_with_stat(
+ const char *dirpath,
+ size_t prefix_len,
+ unsigned int flags,
+ const char *start_stat,
+ const char *end_stat,
+ git_vector *contents)
+{
+ 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;
+ size_t end_len = end_stat ? strlen(end_stat) : 0;
+ fs_iterator_path_with_stat *ps;
+ size_t path_len, cmp_len, ps_size;
+ int error;
+
+ strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
+ git__strncasecmp : git__strncmp;
+
+ if ((error = git_path_diriter_init(&diriter, dirpath, flags)) < 0)
+ goto done;
+
+ while ((error = git_path_diriter_next(&diriter)) == 0) {
+ if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0)
+ goto done;
+
+ assert(path_len > prefix_len);
+
+ /* remove the prefix if requested */
+ path += prefix_len;
+ path_len -= prefix_len;
+
+ /* skip if before start_stat or after end_stat */
+ cmp_len = min(start_len, path_len);
+ if (cmp_len && strncomp(path, start_stat, cmp_len) < 0)
+ continue;
+ cmp_len = min(end_len, path_len);
+ if (cmp_len && strncomp(path, end_stat, cmp_len) > 0)
+ continue;
+
+ /* Make sure to append two bytes, one for the path's null
+ * termination, one for a possible trailing '/' for folders.
+ */
+ GITERR_CHECK_ALLOC_ADD(&ps_size, sizeof(fs_iterator_path_with_stat), path_len);
+ GITERR_CHECK_ALLOC_ADD(&ps_size, ps_size, 2);
+
+ ps = git__calloc(1, ps_size);
+ ps->path_len = path_len;
+
+ memcpy(ps->path, path, path_len);
+
+ if ((error = git_path_diriter_stat(&ps->st, &diriter)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ /* file was removed between readdir and lstat */
+ git__free(ps);
+ continue;
+ }
+
+ /* Treat the file as unreadable if we get any other error */
+ memset(&ps->st, 0, sizeof(ps->st));
+ ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
+
+ giterr_clear();
+ error = 0;
+ } else if (S_ISDIR(ps->st.st_mode)) {
+ /* Suffix directory paths with a '/' */
+ ps->path[ps->path_len++] = '/';
+ ps->path[ps->path_len] = '\0';
+ } else if(!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) {
+ /* Ignore wacky things in the filesystem */
+ git__free(ps);
+ continue;
+ }
+
+ git_vector_insert(contents, ps);
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ /* sort now that directory suffix is added */
+ git_vector_sort(contents);
+
+done:
+ git_path_diriter_free(&diriter);
+ return error;
+}
+
+
static int fs_iterator__expand_dir(fs_iterator *fi)
{
int error;
@@ -998,7 +1107,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
ff = fs_iterator__alloc_frame(fi);
GITERR_CHECK_ALLOC(ff);
- error = git_path_dirload_with_stat(
+ error = dirload_with_stat(
fi->path.ptr, fi->root_len, fi->dirload_flags,
fi->base.start, fi->base.end, &ff->entries);
@@ -1086,7 +1195,7 @@ static int fs_iterator__advance_over(
int error = 0;
fs_iterator *fi = (fs_iterator *)self;
fs_iterator_frame *ff;
- git_path_with_stat *next;
+ fs_iterator_path_with_stat *next;
if (entry != NULL)
*entry = NULL;
@@ -1176,7 +1285,7 @@ static void fs_iterator__free(git_iterator *self)
static int fs_iterator__update_entry(fs_iterator *fi)
{
- git_path_with_stat *ps;
+ fs_iterator_path_with_stat *ps;
memset(&fi->entry, 0, sizeof(fi->entry));
@@ -1307,7 +1416,7 @@ GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
* We consider it a submodule if the path is listed as a submodule in
* either the tree or the index.
*/
-static int is_submodule(workdir_iterator *wi, git_path_with_stat *ie)
+static int is_submodule(workdir_iterator *wi, fs_iterator_path_with_stat *ie)
{
int error, is_submodule = 0;
@@ -1344,17 +1453,29 @@ static int is_submodule(workdir_iterator *wi, git_path_with_stat *ie)
return is_submodule;
}
+GIT_INLINE(git_dir_flag) git_entry__dir_flag(git_index_entry *entry) {
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+ return (entry && entry->mode)
+ ? S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE
+ : GIT_DIR_FLAG_UNKNOWN;
+#else
+ GIT_UNUSED(entry);
+ return GIT_DIR_FLAG_UNKNOWN;
+#endif
+}
+
static int workdir_iterator__enter_dir(fs_iterator *fi)
{
workdir_iterator *wi = (workdir_iterator *)fi;
fs_iterator_frame *ff = fi->stack;
size_t pos;
- git_path_with_stat *entry;
+ fs_iterator_path_with_stat *entry;
bool found_submodules = false;
+ git_dir_flag dir_flag = git_entry__dir_flag(&fi->entry);
+
/* check if this directory is ignored */
- if (git_ignore__lookup(
- &ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len) < 0) {
+ if (git_ignore__lookup(&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len, dir_flag) < 0) {
giterr_clear();
ff->is_ignored = GIT_IGNORE_NOTFOUND;
}
@@ -1483,7 +1604,6 @@ int git_iterator_for_workdir_ext(
return fs_iterator__initialize(out, &wi->fi, repo_workdir);
}
-
void git_iterator_free(git_iterator *iter)
{
if (iter == NULL)
@@ -1574,8 +1694,9 @@ int git_iterator_current_parent_tree(
static void workdir_iterator_update_is_ignored(workdir_iterator *wi)
{
- if (git_ignore__lookup(
- &wi->is_ignored, &wi->ignores, wi->fi.entry.path) < 0) {
+ git_dir_flag dir_flag = git_entry__dir_flag(&wi->fi.entry);
+
+ if (git_ignore__lookup(&wi->is_ignored, &wi->ignores, wi->fi.entry.path, dir_flag) < 0) {
giterr_clear();
wi->is_ignored = GIT_IGNORE_NOTFOUND;
}
diff --git a/src/path.c b/src/path.c
index 6a636bbd2..df6762c3a 100644
--- a/src/path.c
+++ b/src/path.c
@@ -10,6 +10,7 @@
#include "repository.h"
#ifdef GIT_WIN32
#include "win32/posix.h"
+#include "win32/buffer.h"
#include "win32/w32_util.h"
#else
#include <dirent.h>
@@ -260,6 +261,20 @@ int git_path_root(const char *path)
return -1; /* Not a real error - signals that path is not rooted */
}
+void git_path_trim_slashes(git_buf *path)
+{
+ int ceiling = git_path_root(path->ptr) + 1;
+ assert(ceiling >= 0);
+
+ while (path->size > (size_t)ceiling) {
+ if (path->ptr[path->size-1] != '/')
+ break;
+
+ path->ptr[path->size-1] = '\0';
+ path->size--;
+ }
+}
+
int git_path_join_unrooted(
git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
{
@@ -1064,205 +1079,327 @@ int git_path_direach(
return error;
}
-static int entry_path_alloc(
- char **out,
+#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,
- size_t path_len,
- const char *de_path,
- size_t de_len,
- size_t alloc_extra)
+ unsigned int flags)
{
- int need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;
- size_t alloc_size;
- char *entry_path;
+ git_win32_path path_filter;
+ git_buf hack = {0};
- GITERR_CHECK_ALLOC_ADD(&alloc_size, path_len, de_len);
- GITERR_CHECK_ALLOC_ADD(&alloc_size, alloc_size, need_slash);
- GITERR_CHECK_ALLOC_ADD(&alloc_size, alloc_size, 1);
- GITERR_CHECK_ALLOC_ADD(&alloc_size, alloc_size, alloc_extra);
- entry_path = git__calloc(1, alloc_size);
- GITERR_CHECK_ALLOC(entry_path);
+ assert(diriter && path);
- if (path_len)
- memcpy(entry_path, path, path_len);
+ memset(diriter, 0, sizeof(git_path_diriter));
+ diriter->handle = INVALID_HANDLE_VALUE;
- if (need_slash)
- entry_path[path_len] = '/';
+ if (git_buf_puts(&diriter->path_utf8, path) < 0)
+ return -1;
- memcpy(&entry_path[path_len + need_slash], de_path, de_len);
+ git_path_trim_slashes(&diriter->path_utf8);
- *out = entry_path;
+ 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;
}
-int git_path_dirload(
- const char *path,
- size_t prefix_len,
- size_t alloc_extra,
- unsigned int flags,
- git_vector *contents)
+static int diriter_update_paths(git_path_diriter *diriter)
{
- int error;
- DIR *dir;
- size_t path_len;
- path_dirent_data de_data;
- struct dirent *de, *de_buf = (struct dirent *)&de_data;
+ size_t filename_len, path_len;
-#ifdef GIT_USE_ICONV
- git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
-#endif
+ filename_len = wcslen(diriter->current.cFileName);
- GIT_UNUSED(flags);
+ 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';
+
+ git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
+ git_buf_putc(&diriter->path_utf8, '/');
+ git_buf_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_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_paths(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,
+ unsigned int flags)
+{
+ assert(diriter && path);
- assert(path && contents);
+ memset(diriter, 0, sizeof(git_path_diriter));
+
+ if (git_buf_puts(&diriter->path, path) < 0)
+ return -1;
- path_len = strlen(path);
+ git_path_trim_slashes(&diriter->path);
- if (!path_len || path_len < prefix_len) {
- giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path);
+ if (diriter->path.size == 0) {
+ giterr_set(GITERR_FILESYSTEM, "Could not open directory '%s'", path);
return -1;
}
- if ((dir = opendir(path)) == NULL) {
+
+ if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
+ git_buf_free(&diriter->path);
+
giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
return -1;
}
#ifdef GIT_USE_ICONV
if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
- (void)git_path_iconv_init_precompose(&ic);
+ (void)git_path_iconv_init_precompose(&diriter->ic);
#endif
- path += prefix_len;
- path_len -= prefix_len;
+ diriter->parent_len = diriter->path.size;
+ diriter->flags = flags;
- while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
- char *entry_path, *de_path = de->d_name;
- size_t de_len = strlen(de_path);
+ return 0;
+}
- if (git_path_is_dot_or_dotdot(de_path))
- continue;
+int git_path_diriter_next(git_path_diriter *diriter)
+{
+ struct dirent *de;
+ const char *filename;
+ size_t filename_len;
+ bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+ int error = 0;
-#ifdef GIT_USE_ICONV
- if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
- break;
-#endif
+ assert(diriter);
- if ((error = entry_path_alloc(&entry_path,
- path, path_len, de_path, de_len, alloc_extra)) < 0)
- break;
+ errno = 0;
- if ((error = git_vector_insert(contents, entry_path)) < 0) {
- git__free(entry_path);
- break;
+ do {
+ if ((de = readdir(diriter->dir)) == NULL) {
+ if (!errno)
+ return GIT_ITEROVER;
+
+ giterr_set(GITERR_OS,
+ "Could not read directory '%s'", diriter->path);
+ return -1;
}
- }
+ } while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
- closedir(dir);
+ filename = de->d_name;
+ filename_len = strlen(filename);
#ifdef GIT_USE_ICONV
- git_path_iconv_clear(&ic);
+ if ((diriter->flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
+ (error = git_path_iconv(&diriter->ic, (char **)&filename, &filename_len)) < 0)
+ return error;
#endif
- if (error != 0)
- giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
+ git_buf_truncate(&diriter->path, diriter->parent_len);
+ git_buf_putc(&diriter->path, '/');
+ git_buf_put(&diriter->path, filename, filename_len);
+
+ if (git_buf_oom(&diriter->path))
+ return -1;
return error;
}
-int git_path_with_stat_cmp(const void *a, const void *b)
+int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
+{
+ 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;
+}
+
+int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter)
{
- const git_path_with_stat *psa = a, *psb = b;
- return strcmp(psa->path, psb->path);
+ assert(out && out_len && diriter);
+
+ *out = diriter->path.ptr;
+ *out_len = diriter->path.size;
+ return 0;
}
-int git_path_with_stat_cmp_icase(const void *a, const void *b)
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
{
- const git_path_with_stat *psa = a, *psb = b;
- return strcasecmp(psa->path, psb->path);
+ assert(out && diriter);
+
+ return git_path_lstat(diriter->path.ptr, out);
}
-int git_path_dirload_with_stat(
- const char *path,
- size_t prefix_len,
- unsigned int flags,
- const char *start_stat,
- const char *end_stat,
- git_vector *contents)
+void git_path_diriter_free(git_path_diriter *diriter)
{
- int error;
- unsigned int i;
- git_path_with_stat *ps;
- git_buf full = GIT_BUF_INIT;
- int (*strncomp)(const char *a, const char *b, size_t sz);
- size_t start_len = start_stat ? strlen(start_stat) : 0;
- size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len;
-
- if (git_buf_set(&full, path, prefix_len) < 0)
- return -1;
+ if (diriter == NULL)
+ return;
- error = git_path_dirload(
- path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents);
- if (error < 0) {
- git_buf_free(&full);
- return error;
+ if (diriter->dir) {
+ closedir(diriter->dir);
+ diriter->dir = NULL;
}
- strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
- git__strncasecmp : git__strncmp;
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&diriter->ic);
+#endif
- /* stat struct at start of git_path_with_stat, so shift path text */
- git_vector_foreach(contents, i, ps) {
- size_t path_len = strlen((char *)ps);
- memmove(ps->path, ps, path_len + 1);
- ps->path_len = path_len;
- }
+ git_buf_free(&diriter->path);
+}
- git_vector_foreach(contents, i, ps) {
- /* skip if before start_stat or after end_stat */
- cmp_len = min(start_len, ps->path_len);
- if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)
- continue;
- cmp_len = min(end_len, ps->path_len);
- if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
- continue;
+#endif
- git_buf_truncate(&full, prefix_len);
+int git_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ unsigned int flags)
+{
+ git_path_diriter iter = GIT_PATH_DIRITER_INIT;
+ const char *name;
+ size_t name_len;
+ char *dup;
+ int error;
- if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
- (error = git_path_lstat(full.ptr, &ps->st)) < 0) {
+ assert(contents && path);
- if (error == GIT_ENOTFOUND) {
- /* file was removed between readdir and lstat */
- char *entry_path = git_vector_get(contents, i);
- git_vector_remove(contents, i--);
- git__free(entry_path);
- } else {
- /* Treat the file as unreadable if we get any other error */
- memset(&ps->st, 0, sizeof(ps->st));
- ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
- }
+ if ((error = git_path_diriter_init(&iter, path, flags)) < 0)
+ return error;
- giterr_clear();
- error = 0;
- continue;
- }
+ while ((error = git_path_diriter_next(&iter)) == 0) {
+ if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
+ break;
- if (S_ISDIR(ps->st.st_mode)) {
- ps->path[ps->path_len++] = '/';
- ps->path[ps->path_len] = '\0';
- }
- else if (!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) {
- char *entry_path = git_vector_get(contents, i);
- git_vector_remove(contents, i--);
- git__free(entry_path);
- }
- }
+ assert(name_len > prefix_len);
+
+ dup = git__strndup(name + prefix_len, name_len - prefix_len);
+ GITERR_CHECK_ALLOC(dup);
- /* sort now that directory suffix is added */
- git_vector_sort(contents);
+ if ((error = git_vector_insert(contents, dup)) < 0)
+ break;
+ }
- git_buf_free(&full);
+ if (error == GIT_ITEROVER)
+ error = 0;
+ git_path_diriter_free(&iter);
return error;
}
diff --git a/src/path.h b/src/path.h
index 440b5420c..14237cb46 100644
--- a/src/path.h
+++ b/src/path.h
@@ -273,6 +273,7 @@ extern int git_path_apply_relative(git_buf *target, const char *relpath);
enum {
GIT_PATH_DIR_IGNORE_CASE = (1u << 0),
GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1),
+ GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2),
};
/**
@@ -326,66 +327,6 @@ extern int git_path_walk_up(
int (*callback)(void *payload, const char *path),
void *payload);
-/**
- * Load all directory entries (except '.' and '..') into a vector.
- *
- * For cases where `git_path_direach()` is not appropriate, this
- * allows you to load the filenames in a directory into a vector
- * of strings. That vector can then be sorted, iterated, or whatever.
- * Remember to free alloc of the allocated strings when you are done.
- *
- * @param path The directory to read from.
- * @param prefix_len When inserting entries, the trailing part of path
- * will be prefixed after this length. I.e. given path "/a/b" and
- * prefix_len 3, the entries will look like "b/e1", "b/e2", etc.
- * @param alloc_extra Extra bytes to add to each string allocation in
- * case you want to append anything funny.
- * @param flags Combination of GIT_PATH_DIR flags.
- * @param contents Vector to fill with directory entry names.
- */
-extern int git_path_dirload(
- const char *path,
- size_t prefix_len,
- size_t alloc_extra,
- uint32_t flags,
- git_vector *contents);
-
-
-typedef struct {
- struct stat st;
- size_t path_len;
- char path[GIT_FLEX_ARRAY];
-} git_path_with_stat;
-
-extern int git_path_with_stat_cmp(const void *a, const void *b);
-extern int git_path_with_stat_cmp_icase(const void *a, const void *b);
-
-/**
- * Load all directory entries along with stat info into a vector.
- *
- * This adds four things on top of plain `git_path_dirload`:
- *
- * 1. Each entry in the vector is a `git_path_with_stat` struct that
- * contains both the path and the stat info
- * 2. The entries will be sorted alphabetically
- * 3. Entries that are directories will be suffixed with a '/'
- * 4. Optionally, you can be a start and end prefix and only elements
- * after the start and before the end (inclusively) will be stat'ed.
- *
- * @param path The directory to read from
- * @param prefix_len The trailing part of path to prefix to entry paths
- * @param flags GIT_PATH_DIR flags from above
- * @param start_stat As optimization, only stat values after this prefix
- * @param end_stat As optimization, only stat values before this prefix
- * @param contents Vector to fill with git_path_with_stat structures
- */
-extern int git_path_dirload_with_stat(
- const char *path,
- size_t prefix_len,
- uint32_t flags,
- const char *start_stat,
- const char *end_stat,
- git_vector *contents);
enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 };
@@ -472,6 +413,137 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen);
extern bool git_path_does_fs_decompose_unicode(const char *root);
+
+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;
+ size_t parent_len;
+
+ unsigned int flags;
+
+ DIR *dir;
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic;
+#endif
+};
+
+#define GIT_PATH_DIRITER_INIT { GIT_BUF_INIT }
+
+#endif
+
+/**
+ * Initialize a directory iterator.
+ *
+ * @param diriter Pointer to a diriter structure that will be setup.
+ * @param path The path that will be iterated over
+ * @param flags Directory reader flags
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_init(
+ git_path_diriter *diriter,
+ const char *path,
+ unsigned int flags);
+
+/**
+ * Advance the directory iterator. Will return GIT_ITEROVER when
+ * the iteration has completed successfully.
+ *
+ * @param diriter The directory iterator
+ * @return 0, GIT_ITEROVER, or an error code
+ */
+extern int git_path_diriter_next(git_path_diriter *diriter);
+
+/**
+ * Returns the file name of the current item in the iterator.
+ *
+ * @param out Pointer to store the path in
+ * @param out_len Pointer to store the length of the path in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter);
+
+/**
+ * Returns the full path of the current item in the iterator; that
+ * is the current filename plus the path of the directory that the
+ * iterator was constructed with.
+ *
+ * @param out Pointer to store the path in
+ * @param out_len Pointer to store the length of the path in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_path_diriter *diriter);
+
+/**
+ * Performs an `lstat` on the current item in the iterator.
+ *
+ * @param out Pointer to store the stat data in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter);
+
+/**
+ * Closes the directory iterator.
+ *
+ * @param diriter The directory iterator
+ */
+extern void git_path_diriter_free(git_path_diriter *diriter);
+
+/**
+ * Load all directory entries (except '.' and '..') into a vector.
+ *
+ * For cases where `git_path_direach()` is not appropriate, this
+ * allows you to load the filenames in a directory into a vector
+ * of strings. That vector can then be sorted, iterated, or whatever.
+ * Remember to free alloc of the allocated strings when you are done.
+ *
+ * @param contents Vector to fill with directory entry names.
+ * @param path The directory to read from.
+ * @param prefix_len When inserting entries, the trailing part of path
+ * will be prefixed after this length. I.e. given path "/a/b" and
+ * prefix_len 3, the entries will look like "b/e1", "b/e2", etc.
+ * @param flags Combination of GIT_PATH_DIR flags.
+ */
+extern int git_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ uint32_t flags);
+
+
/* Used for paths to repositories on the filesystem */
extern bool git_path_is_local_file_url(const char *file_url);
extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path);
diff --git a/src/posix.h b/src/posix.h
index 22f472c90..8785a4c99 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -122,7 +122,6 @@ extern int git__page_size(size_t *page_size);
#include "strnlen.h"
#ifdef NO_READDIR_R
-# include <dirent.h>
GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
{
GIT_UNUSED(entry);
diff --git a/src/unix/posix.h b/src/unix/posix.h
index e4f3ac67a..8b4f427f7 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -8,6 +8,7 @@
#define INCLUDE_posix__unix_h__
#include <stdio.h>
+#include <dirent.h>
#include <sys/param.h>
typedef int GIT_SOCKET;
diff --git a/src/win32/buffer.c b/src/win32/buffer.c
new file mode 100644
index 000000000..74950189e
--- /dev/null
+++ b/src/win32/buffer.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "buffer.h"
+#include "../buffer.h"
+#include "utf-conv.h"
+
+GIT_INLINE(int) handle_wc_error(void)
+{
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+
+ return -1;
+}
+
+int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w)
+{
+ int utf8_len, utf8_write_len;
+ size_t new_size;
+
+ if (!len_w)
+ return 0;
+
+ assert(string_w);
+
+ /* Measure the string necessary for conversion */
+ if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, len_w, NULL, 0, NULL, NULL)) == 0)
+ return 0;
+
+ assert(utf8_len > 0);
+
+ GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+
+ if (git_buf_grow(buf, new_size) < 0)
+ return -1;
+
+ if ((utf8_write_len = WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, string_w, len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0)
+ return handle_wc_error();
+
+ assert(utf8_write_len == utf8_len);
+
+ buf->size += utf8_write_len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
diff --git a/src/win32/buffer.h b/src/win32/buffer.h
new file mode 100644
index 000000000..62243986f
--- /dev/null
+++ b/src/win32/buffer.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_win32_buffer_h__
+#define INCLUDE_git_win32_buffer_h__
+
+#include "../buffer.h"
+
+/**
+ * Convert a wide character string to UTF-8 and append the results to the
+ * buffer.
+ */
+int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w);
+
+#endif
diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c
index d66969c4d..118e8bcc5 100644
--- a/src/win32/path_w32.c
+++ b/src/win32/path_w32.c
@@ -9,6 +9,9 @@
#include "path.h"
#include "path_w32.h"
#include "utf-conv.h"
+#include "posix.h"
+#include "reparse.h"
+#include "dir.h"
#define PATH__NT_NAMESPACE L"\\\\?\\"
#define PATH__NT_NAMESPACE_LEN 4
@@ -303,3 +306,75 @@ char *git_win32_path_8dot3_name(const char *path)
return shortname;
}
+
+static bool path_is_volume(wchar_t *target, size_t target_len)
+{
+ return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0);
+}
+
+/* On success, returns the length, in characters, of the path stored in dest.
+* On failure, returns a negative value. */
+int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
+{
+ BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
+ HANDLE handle = NULL;
+ DWORD ioctl_ret;
+ wchar_t *target;
+ size_t target_len;
+
+ int error = -1;
+
+ handle = CreateFileW(path, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ switch (reparse_buf->ReparseTag) {
+ case IO_REPARSE_TAG_SYMLINK:
+ target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
+ (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ target = reparse_buf->MountPointReparseBuffer.PathBuffer +
+ (reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ default:
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ if (path_is_volume(target, target_len)) {
+ /* This path is a reparse point that represents another volume mounted
+ * at this location, it is not a symbolic link our input was canonical.
+ */
+ errno = EINVAL;
+ error = -1;
+ } else if (target_len) {
+ /* The path may need to have a prefix removed. */
+ target_len = git_win32__canonicalize_path(target, target_len);
+
+ /* Need one additional character in the target buffer
+ * for the terminating NULL. */
+ if (GIT_WIN_PATH_UTF16 > target_len) {
+ wcscpy(dest, target);
+ error = (int)target_len;
+ }
+ }
+
+on_error:
+ CloseHandle(handle);
+ return error;
+}
diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h
index 033afbb0f..3d9f82860 100644
--- a/src/win32/path_w32.h
+++ b/src/win32/path_w32.h
@@ -8,6 +8,7 @@
#define INCLUDE_git_path_w32_h__
#include "common.h"
+#include "vector.h"
/*
* Provides a large enough buffer to support Windows paths: MAX_PATH is
@@ -79,4 +80,6 @@ extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src);
*/
extern char *git_win32_path_8dot3_name(const char *path);
+extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path);
+
#endif
diff --git a/src/win32/posix.h b/src/win32/posix.h
index 4bc6bfe2e..bf35c8125 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -49,7 +49,7 @@ extern int p_ftruncate(int fd, git_off_t size);
*/
extern int p_lstat_posixly(const char *filename, struct stat *buf);
-extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);
-extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);
+extern struct tm * p_localtime_r(const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result);
#endif
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 544b1ebd5..332ea233c 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -130,88 +130,6 @@ int p_fsync(int fd)
return 0;
}
-GIT_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;
-}
-
-static bool path_is_volume(wchar_t *target, size_t target_len)
-{
- return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0);
-}
-
-/* On success, returns the length, in characters, of the path stored in dest.
- * On failure, returns a negative value. */
-static int readlink_w(
- git_win32_path dest,
- const git_win32_path path)
-{
- BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
- GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
- HANDLE handle = NULL;
- DWORD ioctl_ret;
- wchar_t *target;
- size_t target_len;
-
- int error = -1;
-
- handle = CreateFileW(path, GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
- FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
-
- if (handle == INVALID_HANDLE_VALUE) {
- errno = ENOENT;
- return -1;
- }
-
- if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
- reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
- errno = EINVAL;
- goto on_error;
- }
-
- switch (reparse_buf->ReparseTag) {
- case IO_REPARSE_TAG_SYMLINK:
- target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
- (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
- target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
- break;
- case IO_REPARSE_TAG_MOUNT_POINT:
- target = reparse_buf->MountPointReparseBuffer.PathBuffer +
- (reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
- target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
- break;
- default:
- errno = EINVAL;
- goto on_error;
- }
-
- if (path_is_volume(target, target_len)) {
- /* This path is a reparse point that represents another volume mounted
- * at this location, it is not a symbolic link our input was canonical.
- */
- errno = EINVAL;
- error = -1;
- } else if (target_len) {
- /* The path may need to have a prefix removed. */
- target_len = git_win32__canonicalize_path(target, target_len);
-
- /* Need one additional character in the target buffer
- * for the terminating NULL. */
- if (GIT_WIN_PATH_UTF16 > target_len) {
- wcscpy(dest, target);
- error = (int)target_len;
- }
- }
-
-on_error:
- CloseHandle(handle);
- return error;
-}
-
#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
static int lstat_w(
@@ -222,44 +140,10 @@ static int lstat_w(
WIN32_FILE_ATTRIBUTE_DATA fdata;
if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
- int fMode = S_IREAD;
-
if (!buf)
return 0;
- 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 = (mode_t)fMode;
- buf->st_size = ((git_off_t)fdata.nFileSizeHigh << 32) + fdata.nFileSizeLow;
- 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));
-
- if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
- git_win32_path target;
-
- if (readlink_w(target, path) >= 0) {
- buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFLNK;
-
- /* st_size gets the UTF-8 length of the target name, in bytes,
- * not counting the NULL terminator */
- if ((buf->st_size = git__utf16_to_8(NULL, 0, target)) < 0)
- return -1;
- }
- }
-
- return 0;
+ return git_win32__file_attribute_to_stat(buf, &fdata, path);
}
errno = ENOENT;
@@ -331,7 +215,7 @@ int p_readlink(const char *path, char *buf, size_t bufsiz)
* we need to buffer the result on the stack. */
if (git_win32_path_from_utf8(path_w, path) < 0 ||
- readlink_w(target_w, path_w) < 0 ||
+ git_win32_path_readlink_w(target_w, path_w) < 0 ||
(len = git_win32_path_to_utf8(target, target_w)) < 0)
return -1;
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index 0dad4eab0..f1b674ea0 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -8,10 +8,6 @@
#include "common.h"
#include "utf-conv.h"
-#ifndef WC_ERR_INVALID_CHARS
-# define WC_ERR_INVALID_CHARS 0x80
-#endif
-
GIT_INLINE(DWORD) get_wc_flags(void)
{
static char inited = 0;
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
index 89cdb96da..33b95f59f 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -10,6 +10,10 @@
#include <wchar.h>
#include "common.h"
+#ifndef WC_ERR_INVALID_CHARS
+# define WC_ERR_INVALID_CHARS 0x80
+#endif
+
/**
* Converts a UTF-8 string to wide characters.
*
diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h
index 9c1b94359..8cb0f5b94 100644
--- a/src/win32/w32_util.h
+++ b/src/win32/w32_util.h
@@ -9,8 +9,21 @@
#define INCLUDE_w32_util_h__
#include "utf-conv.h"
+#include "posix.h"
#include "path_w32.h"
+/*
+
+#include "common.h"
+#include "path.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "posix.h"
+#include "reparse.h"
+#include "dir.h"
+*/
+
+
GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
{
return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
@@ -52,4 +65,63 @@ size_t git_win32__path_trim_end(wchar_t *str, size_t len);
*/
size_t git_win32__canonicalize_path(wchar_t *str, size_t len);
+/**
+ * Converts a FILETIME structure to a time_t.
+ *
+ * @param FILETIME A pointer to a FILETIME
+ * @return A time_t containing the same time
+ */
+GIT_INLINE(time_t) git_win32__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;
+}
+
+GIT_INLINE(int) git_win32__file_attribute_to_stat(
+ struct stat *st,
+ const WIN32_FILE_ATTRIBUTE_DATA *attrdata,
+ const wchar_t *path)
+{
+ mode_t mode = S_IREAD;
+
+ if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ mode |= S_IFDIR;
+ else
+ mode |= S_IFREG;
+
+ if ((attrdata->dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0)
+ mode |= S_IWRITE;
+
+ st->st_ino = 0;
+ st->st_gid = 0;
+ st->st_uid = 0;
+ st->st_nlink = 1;
+ st->st_mode = mode;
+ st->st_size = ((git_off_t)attrdata->nFileSizeHigh << 32) + attrdata->nFileSizeLow;
+ st->st_dev = _getdrive() - 1;
+ st->st_rdev = st->st_dev;
+ st->st_atime = git_win32__filetime_to_time_t(&(attrdata->ftLastAccessTime));
+ st->st_mtime = git_win32__filetime_to_time_t(&(attrdata->ftLastWriteTime));
+ st->st_ctime = git_win32__filetime_to_time_t(&(attrdata->ftCreationTime));
+
+ if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) {
+ git_win32_path target;
+
+ if (git_win32_path_readlink_w(target, path) >= 0) {
+ st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK;
+
+ /* st_size gets the UTF-8 length of the target name, in bytes,
+ * not counting the NULL terminator */
+ if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) {
+ giterr_set(GITERR_OS, "Could not convert reparse point name for '%s'", path);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
#endif
diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c
index 030ea075d..71e87cbae 100644
--- a/tests/attr/lookup.c
+++ b/tests/attr/lookup.c
@@ -13,7 +13,7 @@ void test_attr_lookup__simple(void)
cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path);
cl_assert(file->rules.length == 1);
- cl_git_pass(git_attr_path__init(&path, "test", NULL));
+ cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN));
cl_assert_equal_s("test", path.path);
cl_assert_equal_s("test", path.basename);
cl_assert(!path.is_dir);
@@ -36,7 +36,7 @@ static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int
int error;
for (c = cases; c->path != NULL; c++) {
- cl_git_pass(git_attr_path__init(&path, c->path, NULL));
+ cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN));
if (force_dir)
path.is_dir = 1;
@@ -133,7 +133,7 @@ void test_attr_lookup__match_variants(void)
cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path);
cl_assert(file->rules.length == 10);
- cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL));
+ cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN));
cl_assert_equal_s("pat0", path.basename);
run_test_cases(file, cases, 0);
diff --git a/tests/core/dirent.c b/tests/core/dirent.c
index f17260362..d95e44196 100644
--- a/tests/core/dirent.c
+++ b/tests/core/dirent.c
@@ -67,10 +67,23 @@ static void check_counts(walk_data *d)
}
}
+static int update_count(name_data *data, const char *name)
+{
+ name_data *n;
+
+ for (n = data; n->name; n++) {
+ if (!strcmp(n->name, name)) {
+ n->count++;
+ return 0;
+ }
+ }
+
+ return GIT_ERROR;
+}
+
static int one_entry(void *state, git_buf *path)
{
walk_data *d = (walk_data *) state;
- name_data *n;
if (state != state_loc)
return GIT_ERROR;
@@ -78,14 +91,7 @@ static int one_entry(void *state, git_buf *path)
if (path != &d->path)
return GIT_ERROR;
- for (n = d->names; n->name; n++) {
- if (!strcmp(n->name, path->ptr)) {
- n->count++;
- return 0;
- }
- }
-
- return GIT_ERROR;
+ return update_count(d->names, path->ptr);
}
@@ -234,3 +240,38 @@ void test_core_dirent__empty_dir(void)
cl_must_pass(p_rmdir("empty_dir"));
}
+
+static void handle_next(git_path_diriter *diriter, walk_data *walk)
+{
+ const char *fullpath, *filename;
+ size_t fullpath_len, filename_len;
+
+ cl_git_pass(git_path_diriter_fullpath(&fullpath, &fullpath_len, diriter));
+ cl_git_pass(git_path_diriter_filename(&filename, &filename_len, diriter));
+
+ cl_assert_equal_strn(fullpath, "sub/", 4);
+ cl_assert_equal_s(fullpath+4, filename);
+
+ update_count(walk->names, fullpath);
+}
+
+/* test directory iterator */
+void test_core_dirent__diriter_with_fullname(void)
+{
+ git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
+ int error;
+
+ cl_set_cleanup(&dirent_cleanup__cb, &sub);
+ setup(&sub);
+
+ cl_git_pass(git_path_diriter_init(&diriter, sub.path.ptr, 0));
+
+ while ((error = git_path_diriter_next(&diriter)) == 0)
+ handle_next(&diriter, &sub);
+
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_path_diriter_free(&diriter);
+
+ check_counts(&sub);
+}
diff --git a/tests/diff/drivers.c b/tests/diff/drivers.c
index 8b12368ea..e3a0014db 100644
--- a/tests/diff/drivers.c
+++ b/tests/diff/drivers.c
@@ -186,7 +186,7 @@ void test_diff_drivers__builtins(void)
g_repo = cl_git_sandbox_init("userdiff");
- cl_git_pass(git_path_dirload("userdiff/files", 9, 0, 0, &files));
+ cl_git_pass(git_path_dirload(&files, "userdiff/files", 9, 0));
opts.interhunk_lines = 1;
opts.context_lines = 1;