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 @@ -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; - assert(path && contents); - - path_len = strlen(path); - - if (!path_len || path_len < prefix_len) { - giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path); + 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; } - if ((dir = opendir(path)) == NULL) { + + 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); + + memset(diriter, 0, sizeof(git_path_diriter)); + + if (git_buf_puts(&diriter->path, path) < 0) + return -1; + + 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); + 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) { - const git_path_with_stat *psa = a, *psb = b; - return strcmp(psa->path, psb->path); + 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_with_stat_cmp_icase(const void *a, const void *b) +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 strcasecmp(psa->path, psb->path); + assert(out && out_len && diriter); + + *out = diriter->path.ptr; + *out_len = diriter->path.size; + return 0; } -int git_path_dirload_with_stat( +int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter) +{ + assert(out && diriter); + + return git_path_lstat(diriter->path.ptr, out); +} + +void git_path_diriter_free(git_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + if (diriter->dir) { + closedir(diriter->dir); + diriter->dir = NULL; + } + +#ifdef GIT_USE_ICONV + git_path_iconv_clear(&diriter->ic); +#endif + + git_buf_free(&diriter->path); +} + +#endif + +int git_path_dirload( + git_vector *contents, const char *path, size_t prefix_len, - unsigned int flags, - const char *start_stat, - const char *end_stat, - git_vector *contents) + unsigned int flags) { + git_path_diriter iter = GIT_PATH_DIRITER_INIT; + const char *name; + size_t name_len; + char *dup; 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; + assert(contents && path); - error = git_path_dirload( - path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents); - if (error < 0) { - git_buf_free(&full); + if ((error = git_path_diriter_init(&iter, path, flags)) < 0) return error; + + while ((error = git_path_diriter_next(&iter)) == 0) { + if ((error = git_path_diriter_fullpath(&name, &name_len, &iter)) < 0) + break; + + assert(name_len > prefix_len); + + dup = git__strndup(name + prefix_len, name_len - prefix_len); + GITERR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(contents, dup)) < 0) + break; } - strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? - git__strncasecmp : git__strncmp; - - /* 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_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; - - git_buf_truncate(&full, prefix_len); - - if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || - (error = git_path_lstat(full.ptr, &ps->st)) < 0) { - - 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; - } - - giterr_clear(); - error = 0; - continue; - } - - 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); - } - } - - /* sort now that directory suffix is added */ - git_vector_sort(contents); - - 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 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 +#include #include 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 #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;