mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-07 18:11:43 +00:00
Merge pull request #3087 from ethomson/pr/3054
Performance Improvements to Status on Windows
This commit is contained in:
commit
cfc2e56d59
@ -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 ||
|
||||
|
@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
145
src/iterator.c
145
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;
|
||||
}
|
||||
|
445
src/path.c
445
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;
|
||||
|
||||
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);
|
||||
|
||||
if (git_path_is_dot_or_dotdot(de_path))
|
||||
continue;
|
||||
|
||||
#ifdef GIT_USE_ICONV
|
||||
if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
|
||||
break;
|
||||
#endif
|
||||
|
||||
if ((error = entry_path_alloc(&entry_path,
|
||||
path, path_len, de_path, de_len, alloc_extra)) < 0)
|
||||
break;
|
||||
|
||||
if ((error = git_vector_insert(contents, entry_path)) < 0) {
|
||||
git__free(entry_path);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
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;
|
||||
|
||||
assert(diriter);
|
||||
|
||||
errno = 0;
|
||||
|
||||
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));
|
||||
|
||||
filename = de->d_name;
|
||||
filename_len = strlen(filename);
|
||||
|
||||
#ifdef GIT_USE_ICONV
|
||||
git_path_iconv_clear(&ic);
|
||||
#endif
|
||||
|
||||
if (error != 0)
|
||||
giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
|
||||
|
||||
if ((diriter->flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
|
||||
(error = git_path_iconv(&diriter->ic, (char **)&filename, &filename_len)) < 0)
|
||||
return error;
|
||||
}
|
||||
#endif
|
||||
|
||||
int git_path_with_stat_cmp(const void *a, const void *b)
|
||||
{
|
||||
const git_path_with_stat *psa = a, *psb = b;
|
||||
return strcmp(psa->path, psb->path);
|
||||
}
|
||||
git_buf_truncate(&diriter->path, diriter->parent_len);
|
||||
git_buf_putc(&diriter->path, '/');
|
||||
git_buf_put(&diriter->path, filename, filename_len);
|
||||
|
||||
int git_path_with_stat_cmp_icase(const void *a, const void *b)
|
||||
{
|
||||
const git_path_with_stat *psa = a, *psb = b;
|
||||
return strcasecmp(psa->path, psb->path);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
if (git_buf_oom(&diriter->path))
|
||||
return -1;
|
||||
|
||||
error = git_path_dirload(
|
||||
path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents);
|
||||
if (error < 0) {
|
||||
git_buf_free(&full);
|
||||
return error;
|
||||
}
|
||||
|
||||
strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
|
||||
git__strncasecmp : git__strncmp;
|
||||
int git_path_diriter_filename(
|
||||
const char **out,
|
||||
size_t *out_len,
|
||||
git_path_diriter *diriter)
|
||||
{
|
||||
assert(out && out_len && diriter);
|
||||
|
||||
/* 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
int git_path_diriter_fullpath(
|
||||
const char **out,
|
||||
size_t *out_len,
|
||||
git_path_diriter *diriter)
|
||||
{
|
||||
assert(out && out_len && diriter);
|
||||
|
||||
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;
|
||||
*out = diriter->path.ptr;
|
||||
*out_len = diriter->path.size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
giterr_clear();
|
||||
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)
|
||||
{
|
||||
git_path_diriter iter = GIT_PATH_DIRITER_INIT;
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
char *dup;
|
||||
int error;
|
||||
|
||||
assert(contents && path);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (error == GIT_ITEROVER)
|
||||
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);
|
||||
|
||||
git_path_diriter_free(&iter);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
192
src/path.h
192
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);
|
||||
|
@ -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);
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define INCLUDE_posix__unix_h__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
typedef int GIT_SOCKET;
|
||||
|
55
src/win32/buffer.c
Normal file
55
src/win32/buffer.c
Normal file
@ -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;
|
||||
}
|
||||
|
18
src/win32/buffer.h
Normal file
18
src/win32/buffer.h
Normal file
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user