Commit cfc2e56d by Carlos Martín Nieto

Merge pull request #3087 from ethomson/pr/3054

Performance Improvements to Status on Windows
parents 72f8da91 be3f1049
......@@ -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;
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;
}
......
......@@ -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
......
......@@ -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;
}
......
......@@ -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;
......
/*
* 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;
}
/*
* 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
......@@ -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
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment