summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/checkout.c3
-rw-r--r--src/clone.c42
-rw-r--r--src/config.h2
-rw-r--r--src/config_cache.c1
-rw-r--r--src/errors.c1
-rw-r--r--src/fileops.c130
-rw-r--r--src/fileops.h16
-rw-r--r--src/index.c8
-rw-r--r--src/index.h2
-rw-r--r--src/indexer.c10
-rw-r--r--src/iterator.c18
-rw-r--r--src/iterator.h10
-rw-r--r--src/odb_loose.c6
-rw-r--r--src/odb_pack.c2
-rw-r--r--src/path.c265
-rw-r--r--src/path.h94
-rw-r--r--src/refdb_fs.c20
-rw-r--r--src/refs.c67
-rw-r--r--src/refs.h2
-rw-r--r--src/repository.c272
-rw-r--r--src/repository.h3
-rw-r--r--src/submodule.c3
-rw-r--r--src/win32/posix_w32.c4
23 files changed, 643 insertions, 338 deletions
diff --git a/src/checkout.c b/src/checkout.c
index 5d741d393..d3f673d40 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -783,7 +783,8 @@ static int checkout_update_index(
memset(&entry, 0, sizeof(entry));
entry.path = (char *)file->path; /* cast to prevent warning */
- git_index_entry__init_from_stat(&entry, st);
+ git_index_entry__init_from_stat(
+ &entry, st, !(git_index_caps(data->index) & GIT_INDEXCAP_NO_FILEMODE));
git_oid_cpy(&entry.oid, &file->oid);
return git_index_add(data->index, &entry);
diff --git a/src/clone.c b/src/clone.c
index f3e365c07..657243945 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -391,11 +391,11 @@ int git_clone(
const char *local_path,
const git_clone_options *_options)
{
- int retcode = GIT_ERROR;
+ int error = 0;
git_repository *repo = NULL;
git_remote *origin;
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
- int remove_directory_on_failure = 0;
+ uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
assert(out && url && local_path);
@@ -408,33 +408,29 @@ int git_clone(
if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) {
giterr_set(GITERR_INVALID,
"'%s' exists and is not an empty directory", local_path);
- return GIT_ERROR;
+ return GIT_EEXISTS;
}
- /* Only remove the directory on failure if we create it */
- remove_directory_on_failure = !git_path_exists(local_path);
+ /* Only remove the root directory on failure if we create it */
+ if (git_path_exists(local_path))
+ rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
- if ((retcode = git_repository_init(&repo, local_path, options.bare)) < 0)
- return retcode;
+ if ((error = git_repository_init(&repo, local_path, options.bare)) < 0)
+ return error;
- if ((retcode = create_and_configure_origin(&origin, repo, url, &options)) < 0)
- goto cleanup;
+ if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
+ error = git_clone_into(
+ repo, origin, &options.checkout_opts, options.checkout_branch);
- retcode = git_clone_into(repo, origin, &options.checkout_opts, options.checkout_branch);
- git_remote_free(origin);
+ git_remote_free(origin);
+ }
- if (retcode < 0)
- goto cleanup;
+ if (error < 0) {
+ git_repository_free(repo);
+ repo = NULL;
+ (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
+ }
*out = repo;
- return 0;
-
-cleanup:
- git_repository_free(repo);
- if (remove_directory_on_failure)
- git_futils_rmdir_r(local_path, NULL, GIT_RMDIR_REMOVE_FILES);
- else
- git_futils_cleanupdir_r(local_path);
-
- return retcode;
+ return error;
}
diff --git a/src/config.h b/src/config.h
index 85db5e3e1..01e8465cc 100644
--- a/src/config.h
+++ b/src/config.h
@@ -47,7 +47,7 @@ extern int git_config_rename_section(
* @param out the new backend
* @param path where the config file is located
*/
-extern int git_config_file__ondisk(struct git_config_backend **out, const char *path);
+extern int git_config_file__ondisk(git_config_backend **out, const char *path);
extern int git_config__normalize_name(const char *in, char **out);
diff --git a/src/config_cache.c b/src/config_cache.c
index 84de3a5ed..6808521a3 100644
--- a/src/config_cache.c
+++ b/src/config_cache.c
@@ -67,6 +67,7 @@ static struct map_data _cvar_maps[] = {
{"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT },
{"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT },
{"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT },
+ {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT },
};
int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
diff --git a/src/errors.c b/src/errors.c
index e2629f69e..c9d9e4e37 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -116,4 +116,3 @@ const git_error *giterr_last(void)
{
return GIT_GLOBAL->last_error;
}
-
diff --git a/src/fileops.c b/src/fileops.c
index 5a5041cc3..63aedc6d0 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -78,11 +78,8 @@ int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, con
int git_futils_open_ro(const char *path)
{
int fd = p_open(path, O_RDONLY);
- if (fd < 0) {
- if (errno == ENOENT || errno == ENOTDIR)
- fd = GIT_ENOTFOUND;
- giterr_set(GITERR_OS, "Failed to open '%s'", path);
- }
+ if (fd < 0)
+ return git_path_set_error(errno, path, "open");
return fd;
}
@@ -138,7 +135,6 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
int git_futils_readbuffer_updated(
git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
{
- int error = 0;
git_file fd;
struct stat st;
bool changed = false;
@@ -148,13 +144,8 @@ int git_futils_readbuffer_updated(
if (updated != NULL)
*updated = 0;
- if (p_stat(path, &st) < 0) {
- error = errno;
- giterr_set(GITERR_OS, "Failed to stat '%s'", path);
- if (error == ENOENT || error == ENOTDIR)
- return GIT_ENOTFOUND;
- return -1;
- }
+ if (p_stat(path, &st) < 0)
+ return git_path_set_error(errno, path, "stat");
if (S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
@@ -398,8 +389,11 @@ typedef struct {
size_t baselen;
uint32_t flags;
int error;
+ int depth;
} futils__rmdir_data;
+#define FUTILS_MAX_DEPTH 100
+
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
{
if (filemsg)
@@ -438,51 +432,60 @@ static int futils__rm_first_parent(git_buf *path, const char *ceiling)
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
- struct stat st;
futils__rmdir_data *data = opaque;
+ int error = data->error;
+ struct stat st;
+
+ if (data->depth > FUTILS_MAX_DEPTH)
+ error = futils__error_cannot_rmdir(
+ path->ptr, "directory nesting too deep");
- if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) {
+ else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
if (errno == ENOENT)
- data->error = 0;
+ error = 0;
else if (errno == ENOTDIR) {
/* asked to remove a/b/c/d/e and a/b is a normal file */
if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
- data->error = futils__rm_first_parent(path, data->base);
+ error = futils__rm_first_parent(path, data->base);
else
futils__error_cannot_rmdir(
path->ptr, "parent is not directory");
}
else
- futils__error_cannot_rmdir(path->ptr, "cannot access");
+ error = git_path_set_error(errno, path->ptr, "rmdir");
}
else if (S_ISDIR(st.st_mode)) {
- int error = git_path_direach(path, futils__rmdir_recurs_foreach, data);
+ data->depth++;
+
+ error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
if (error < 0)
return (error == GIT_EUSER) ? data->error : error;
- data->error = p_rmdir(path->ptr);
+ data->depth--;
+
+ if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
+ return data->error;
- if (data->error < 0) {
+ if ((error = p_rmdir(path->ptr)) < 0) {
if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
- data->error = 0;
+ error = 0;
else
- futils__error_cannot_rmdir(path->ptr, NULL);
+ error = git_path_set_error(errno, path->ptr, "rmdir");
}
}
else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
- data->error = p_unlink(path->ptr);
-
- if (data->error < 0)
- futils__error_cannot_rmdir(path->ptr, "cannot be removed");
+ if (p_unlink(path->ptr) < 0)
+ error = git_path_set_error(errno, path->ptr, "remove");
}
else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
- data->error = futils__error_cannot_rmdir(path->ptr, "still present");
+ error = futils__error_cannot_rmdir(path->ptr, "still present");
- return data->error;
+ data->error = error;
+ return error;
}
static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
@@ -505,7 +508,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
giterr_clear();
error = GIT_ITEROVER;
} else {
- futils__error_cannot_rmdir(git_buf_cstr(path), NULL);
+ error = git_path_set_error(errno, git_buf_cstr(path), "rmdir");
}
}
@@ -517,7 +520,7 @@ int git_futils_rmdir_r(
{
int error;
git_buf fullpath = GIT_BUF_INIT;
- futils__rmdir_data data;
+ futils__rmdir_data data = { 0 };
/* build path and find "root" where we should start calling mkdir */
if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
@@ -526,7 +529,6 @@ int git_futils_rmdir_r(
data.base = base ? base : "";
data.baselen = base ? strlen(base) : 0;
data.flags = flags;
- data.error = 0;
error = futils__rmdir_recurs_foreach(&data, &fullpath);
@@ -544,41 +546,6 @@ int git_futils_rmdir_r(
return error;
}
-int git_futils_cleanupdir_r(const char *path)
-{
- int error;
- git_buf fullpath = GIT_BUF_INIT;
- futils__rmdir_data data;
-
- if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0)
- goto clean_up;
-
- data.base = "";
- data.baselen = 0;
- data.flags = GIT_RMDIR_REMOVE_FILES;
- data.error = 0;
-
- if (!git_path_exists(path)) {
- giterr_set(GITERR_OS, "Path does not exist: %s" , path);
- error = GIT_ERROR;
- goto clean_up;
- }
-
- if (!git_path_isdir(path)) {
- giterr_set(GITERR_OS, "Path is not a directory: %s" , path);
- error = GIT_ERROR;
- goto clean_up;
- }
-
- error = git_path_direach(&fullpath, futils__rmdir_recurs_foreach, &data);
- if (error == GIT_EUSER)
- error = data.error;
-
-clean_up:
- git_buf_free(&fullpath);
- return error;
-}
-
static int git_futils_guess_system_dirs(git_buf *out)
{
@@ -836,11 +803,8 @@ int git_futils_cp(const char *from, const char *to, mode_t filemode)
return ifd;
if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
- if (errno == ENOENT || errno == ENOTDIR)
- ofd = GIT_ENOTFOUND;
- giterr_set(GITERR_OS, "Failed to open '%s' for writing", to);
p_close(ifd);
- return ofd;
+ return git_path_set_error(errno, to, "open for writing");
}
return cp_by_fd(ifd, ofd, true);
@@ -923,15 +887,14 @@ static int _cp_r_callback(void *ref, git_buf *from)
goto exit;
}
- if (p_lstat(info->to.ptr, &to_st) < 0) {
- if (errno != ENOENT && errno != ENOTDIR) {
- giterr_set(GITERR_OS,
- "Could not access %s while copying files", info->to.ptr);
- error = -1;
- goto exit;
- }
- } else
+ if (!(error = git_path_lstat(info->to.ptr, &to_st)))
exists = true;
+ else if (error != GIT_ENOTFOUND)
+ goto exit;
+ else {
+ giterr_clear();
+ error = 0;
+ }
if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
goto exit;
@@ -948,9 +911,12 @@ static int _cp_r_callback(void *ref, git_buf *from)
error = _cp_r_mkdir(info, from);
/* recurse onto target directory */
- if (!error && (!exists || S_ISDIR(to_st.st_mode)) &&
- ((error = git_path_direach(from, _cp_r_callback, info)) == GIT_EUSER))
- error = info->error;
+ if (!error && (!exists || S_ISDIR(to_st.st_mode))) {
+ error = git_path_direach(from, 0, _cp_r_callback, info);
+
+ if (error == GIT_EUSER)
+ error = info->error;
+ }
if (oldmode != 0)
info->dirmode = oldmode;
diff --git a/src/fileops.h b/src/fileops.h
index 6bd693b99..1b2728e58 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -115,12 +115,7 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode);
* * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
* if removing this item leaves them empty
* * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
- *
- * The old values translate into the new as follows:
- *
- * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY
- * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES
- * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY
+ * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself
*/
typedef enum {
GIT_RMDIR_EMPTY_HIERARCHY = 0,
@@ -128,6 +123,7 @@ typedef enum {
GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
+ GIT_RMDIR_SKIP_ROOT = (1 << 4),
} git_futils_rmdir_flags;
/**
@@ -141,14 +137,6 @@ typedef enum {
extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
/**
- * Remove all files and directories beneath the specified path.
- *
- * @param path Path to the top level directory to process.
- * @return 0 on success; -1 on error.
- */
-extern int git_futils_cleanupdir_r(const char *path);
-
-/**
* Create and open a temporary file with a `_git2_` suffix.
* Writes the filename into path_out.
* @return On success, an open file descriptor, else an error code < 0.
diff --git a/src/index.c b/src/index.c
index c923f1675..5cdd40aaa 100644
--- a/src/index.c
+++ b/src/index.c
@@ -580,7 +580,8 @@ const git_index_entry *git_index_get_bypath(
return git_index_get_byindex(index, pos);
}
-void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st)
+void git_index_entry__init_from_stat(
+ git_index_entry *entry, struct stat *st, bool trust_mode)
{
entry->ctime.seconds = (git_time_t)st->st_ctime;
entry->mtime.seconds = (git_time_t)st->st_mtime;
@@ -588,7 +589,8 @@ void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st)
/* entry->ctime.nanoseconds = st->st_ctimensec; */
entry->dev = st->st_rdev;
entry->ino = st->st_ino;
- entry->mode = index_create_mode(st->st_mode);
+ entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ?
+ index_create_mode(0666) : index_create_mode(st->st_mode);
entry->uid = st->st_uid;
entry->gid = st->st_gid;
entry->file_size = st->st_size;
@@ -632,7 +634,7 @@ static int index_entry_init(
entry = git__calloc(1, sizeof(git_index_entry));
GITERR_CHECK_ALLOC(entry);
- git_index_entry__init_from_stat(entry, &st);
+ git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
entry->oid = oid;
entry->path = git__strdup(rel_path);
diff --git a/src/index.h b/src/index.h
index 40577e105..4c448fabf 100644
--- a/src/index.h
+++ b/src/index.h
@@ -48,7 +48,7 @@ struct git_index_conflict_iterator {
};
extern void git_index_entry__init_from_stat(
- git_index_entry *entry, struct stat *st);
+ git_index_entry *entry, struct stat *st, bool trust_mode);
extern size_t git_index__prefix_position(git_index *index, const char *path);
diff --git a/src/indexer.c b/src/indexer.c
index 4ce69fc8d..1270488f0 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -50,7 +50,7 @@ struct git_indexer_stream {
/* Fields for calculating the packfile trailer (hash of everything before it) */
char inbuf[GIT_OID_RAWSZ];
- int inbuf_len;
+ size_t inbuf_len;
git_hash_ctx trailer;
};
@@ -378,13 +378,13 @@ static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress *
/* Hash everything but the last 20B of input */
static void hash_partially(git_indexer_stream *idx, const uint8_t *data, size_t size)
{
- int to_expell, to_keep;
+ size_t to_expell, to_keep;
if (size == 0)
return;
/* Easy case, dump the buffer and the data minus the last 20 bytes */
- if (size >= 20) {
+ if (size >= GIT_OID_RAWSZ) {
git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len);
git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ);
@@ -402,8 +402,8 @@ static void hash_partially(git_indexer_stream *idx, const uint8_t *data, size_t
}
/* We need to partially drain the buffer and then append */
- to_expell = abs(size - (GIT_OID_RAWSZ - idx->inbuf_len));
- to_keep = abs(idx->inbuf_len - to_expell);
+ to_keep = GIT_OID_RAWSZ - size;
+ to_expell = idx->inbuf_len - to_keep;
git_hash_update(&idx->trailer, idx->inbuf, to_expell);
diff --git a/src/iterator.c b/src/iterator.c
index bdc98d22b..c0d7862ff 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -893,6 +893,7 @@ struct fs_iterator {
git_index_entry entry;
git_buf path;
size_t root_len;
+ uint32_t dirload_flags;
int depth;
int (*enter_dir_cb)(fs_iterator *self);
@@ -986,7 +987,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
GITERR_CHECK_ALLOC(ff);
error = git_path_dirload_with_stat(
- fi->path.ptr, fi->root_len, iterator__ignore_case(fi),
+ fi->path.ptr, fi->root_len, fi->dirload_flags,
fi->base.start, fi->base.end, &ff->entries);
if (error < 0) {
@@ -1174,7 +1175,7 @@ static int fs_iterator__update_entry(fs_iterator *fi)
return GIT_ITEROVER;
fi->entry.path = ps->path;
- git_index_entry__init_from_stat(&fi->entry, &ps->st);
+ git_index_entry__init_from_stat(&fi->entry, &ps->st, true);
/* need different mode here to keep directories during iteration */
fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
@@ -1207,6 +1208,11 @@ static int fs_iterator__initialize(
}
fi->root_len = fi->path.size;
+ fi->dirload_flags =
+ (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
+ (iterator__flag(fi, PRECOMPOSE_UNICODE) ?
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
+
if ((error = fs_iterator__expand_dir(fi)) < 0) {
if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) {
giterr_clear();
@@ -1329,7 +1335,7 @@ int git_iterator_for_workdir_ext(
const char *start,
const char *end)
{
- int error;
+ int error, precompose = 0;
workdir_iterator *wi;
if (!repo_workdir) {
@@ -1356,6 +1362,12 @@ int git_iterator_for_workdir_ext(
return error;
}
+ /* try to look up precompose and set flag if appropriate */
+ if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
+ giterr_clear();
+ else if (precompose)
+ wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+
return fs_iterator__initialize(out, &wi->fi, repo_workdir);
}
diff --git a/src/iterator.h b/src/iterator.h
index ea88fa6a2..751e139d0 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -24,13 +24,15 @@ typedef enum {
typedef enum {
/** ignore case for entry sort order */
- GIT_ITERATOR_IGNORE_CASE = (1 << 0),
+ GIT_ITERATOR_IGNORE_CASE = (1u << 0),
/** force case sensitivity for entry sort order */
- GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1),
+ GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1),
/** return tree items in addition to blob items */
- GIT_ITERATOR_INCLUDE_TREES = (1 << 2),
+ GIT_ITERATOR_INCLUDE_TREES = (1u << 2),
/** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */
- GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3),
+ GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3),
+ /** convert precomposed unicode to decomposed unicode */
+ GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4),
} git_iterator_flag_t;
typedef struct {
diff --git a/src/odb_loose.c b/src/odb_loose.c
index 4ff57158d..07dfae539 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -544,7 +544,7 @@ static int locate_object_short_oid(
/* Explore directory to find a unique object matching short_oid */
error = git_path_direach(
- object_location, fn_locate_object_short_oid, &state);
+ object_location, 0, fn_locate_object_short_oid, &state);
if (error && error != GIT_EUSER)
return error;
@@ -745,7 +745,7 @@ static int foreach_cb(void *_state, git_buf *path)
{
struct foreach_state *state = (struct foreach_state *) _state;
- return git_path_direach(path, foreach_object_dir_cb, state);
+ return git_path_direach(path, 0, foreach_object_dir_cb, state);
}
static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
@@ -768,7 +768,7 @@ static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb
state.data = data;
state.dir_len = git_buf_len(&buf);
- error = git_path_direach(&buf, foreach_cb, &state);
+ error = git_path_direach(&buf, 0, foreach_cb, &state);
git_buf_free(&buf);
diff --git a/src/odb_pack.c b/src/odb_pack.c
index cadc93a65..897bddf32 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -331,7 +331,7 @@ static int pack_backend__refresh(git_odb_backend *_backend)
git_buf_sets(&path, backend->pack_folder);
/* reload all packs */
- error = git_path_direach(&path, packfile_load__cb, (void *)backend);
+ error = git_path_direach(&path, 0, packfile_load__cb, backend);
git_buf_free(&path);
diff --git a/src/path.c b/src/path.c
index 9d8e90361..d45751cd1 100644
--- a/src/path.c
+++ b/src/path.c
@@ -483,23 +483,23 @@ bool git_path_isfile(const char *path)
bool git_path_is_empty_dir(const char *path)
{
- git_buf pathbuf = GIT_BUF_INIT;
HANDLE hFind = INVALID_HANDLE_VALUE;
git_win32_path wbuf;
+ int wbufsz;
WIN32_FIND_DATAW ffd;
bool retval = true;
- if (!git_path_isdir(path)) return false;
+ if (!git_path_isdir(path))
+ return false;
- git_buf_printf(&pathbuf, "%s\\*", path);
- git_win32_path_from_c(wbuf, git_buf_cstr(&pathbuf));
+ wbufsz = git_win32_path_from_c(wbuf, path);
+ if (!wbufsz || wbufsz + 2 > GIT_WIN_PATH_UTF16)
+ return false;
+ memcpy(&wbuf[wbufsz - 1], L"\\*", 3 * sizeof(wchar_t));
hFind = FindFirstFileW(wbuf, &ffd);
- if (INVALID_HANDLE_VALUE == hFind) {
- giterr_set(GITERR_OS, "Couldn't open '%s'", path);
- git_buf_free(&pathbuf);
+ if (INVALID_HANDLE_VALUE == hFind)
return false;
- }
do {
if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {
@@ -509,50 +509,64 @@ bool git_path_is_empty_dir(const char *path)
} while (FindNextFileW(hFind, &ffd) != 0);
FindClose(hFind);
- git_buf_free(&pathbuf);
return retval;
}
#else
-bool git_path_is_empty_dir(const char *path)
+static int path_found_entry(void *payload, git_buf *path)
{
- DIR *dir = NULL;
- struct dirent *e;
- bool retval = true;
+ GIT_UNUSED(payload);
+ return !git_path_is_dot_or_dotdot(path->ptr);
+}
- if (!git_path_isdir(path)) return false;
+bool git_path_is_empty_dir(const char *path)
+{
+ int error;
+ git_buf dir = GIT_BUF_INIT;
- dir = opendir(path);
- if (!dir) {
- giterr_set(GITERR_OS, "Couldn't open '%s'", path);
+ if (!git_path_isdir(path))
return false;
- }
- while ((e = readdir(dir)) != NULL) {
- if (!git_path_is_dot_or_dotdot(e->d_name)) {
- giterr_set(GITERR_INVALID,
- "'%s' exists and is not an empty directory", path);
- retval = false;
- break;
- }
- }
- closedir(dir);
+ if (!(error = git_buf_sets(&dir, path)))
+ error = git_path_direach(&dir, 0, path_found_entry, NULL);
- return retval;
+ git_buf_free(&dir);
+
+ return !error;
}
+
#endif
-int git_path_lstat(const char *path, struct stat *st)
+int git_path_set_error(int errno_value, const char *path, const char *action)
{
- int err = 0;
-
- if (p_lstat(path, st) < 0) {
- err = (errno == ENOENT) ? GIT_ENOTFOUND : -1;
- giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
+ switch (errno_value) {
+ case ENOENT:
+ case ENOTDIR:
+ giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action);
+ return GIT_ENOTFOUND;
+
+ case EINVAL:
+ case ENAMETOOLONG:
+ giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path);
+ return GIT_EINVALIDSPEC;
+
+ case EEXIST:
+ giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
+ return GIT_EEXISTS;
+
+ default:
+ giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
+ return -1;
}
+}
+
+int git_path_lstat(const char *path, struct stat *st)
+{
+ if (p_lstat(path, st) == 0)
+ return 0;
- return err;
+ return git_path_set_error(errno, path, "stat");
}
static bool _check_dir_contents(
@@ -724,14 +738,103 @@ int git_path_cmp(
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
+bool git_path_has_non_ascii(const char *path, size_t pathlen)
+{
+ const uint8_t *scan = (const uint8_t *)path, *end;
+
+ for (end = scan + pathlen; scan < end; ++scan)
+ if (*scan & 0x80)
+ return true;
+
+ return false;
+}
+
+#ifdef GIT_USE_ICONV
+
+int git_path_iconv_init_precompose(git_path_iconv_t *ic)
+{
+ git_buf_init(&ic->buf, 0);
+ ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
+ return 0;
+}
+
+void git_path_iconv_clear(git_path_iconv_t *ic)
+{
+ if (ic) {
+ if (ic->map != (iconv_t)-1)
+ iconv_close(ic->map);
+ git_buf_free(&ic->buf);
+ }
+}
+
+int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen)
+{
+ char *nfd = *in, *nfc;
+ size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, rv;
+ int retry = 1;
+
+ if (!ic || ic->map == (iconv_t)-1 ||
+ !git_path_has_non_ascii(*in, *inlen))
+ return 0;
+
+ while (1) {
+ if (git_buf_grow(&ic->buf, wantlen) < 0)
+ return -1;
+
+ nfc = ic->buf.ptr + ic->buf.size;
+ nfclen = ic->buf.asize - ic->buf.size;
+
+ rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
+
+ ic->buf.size = (nfc - ic->buf.ptr);
+
+ if (rv != (size_t)-1)
+ break;
+
+ if (errno != E2BIG)
+ goto fail;
+
+ /* make space for 2x the remaining data to be converted
+ * (with per retry overhead to avoid infinite loops)
+ */
+ wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
+
+ if (retry++ > 4)
+ goto fail;
+ }
+
+ ic->buf.ptr[ic->buf.size] = '\0';
+
+ *in = ic->buf.ptr;
+ *inlen = ic->buf.size;
+
+ return 0;
+
+fail:
+ giterr_set(GITERR_OS, "Unable to convert unicode path data");
+ return -1;
+}
+
+#endif
+
+#if defined(__sun) || defined(__GNU__)
+typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
+#else
+typedef struct dirent path_dirent_data;
+#endif
+
int git_path_direach(
git_buf *path,
+ uint32_t flags,
int (*fn)(void *, git_buf *),
void *arg)
{
+ int error = 0;
ssize_t wd_len;
DIR *dir;
- struct dirent *de, *de_buf;
+ path_dirent_data de_data;
+ struct dirent *de, *de_buf = (struct dirent *)&de_data;
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
if (git_path_to_dir(path) < 0)
return -1;
@@ -743,99 +846,98 @@ int git_path_direach(
return -1;
}
-#if defined(__sun) || defined(__GNU__)
- de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
-#else
- de_buf = git__malloc(sizeof(struct dirent));
-#endif
+ if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_path_iconv_init_precompose(&ic);
while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
- int result;
+ char *de_path = de->d_name;
+ size_t de_len = strlen(de_path);
- if (git_path_is_dot_or_dotdot(de->d_name))
+ if (git_path_is_dot_or_dotdot(de_path))
continue;
- if (git_buf_puts(path, de->d_name) < 0) {
- closedir(dir);
- git__free(de_buf);
- return -1;
- }
+ if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0 ||
+ (error = git_buf_put(path, de_path, de_len)) < 0)
+ break;
- result = fn(arg, path);
+ error = fn(arg, path);
git_buf_truncate(path, wd_len); /* restore path */
- if (result) {
- closedir(dir);
- git__free(de_buf);
- return GIT_EUSER;
+ if (error) {
+ error = GIT_EUSER;
+ break;
}
}
closedir(dir);
- git__free(de_buf);
- return 0;
+ git_path_iconv_clear(&ic);
+
+ return error;
}
int git_path_dirload(
const char *path,
size_t prefix_len,
size_t alloc_extra,
+ unsigned int flags,
git_vector *contents)
{
int error, need_slash;
DIR *dir;
- struct dirent *de, *de_buf;
size_t path_len;
+ path_dirent_data de_data;
+ struct dirent *de, *de_buf = (struct dirent *)&de_data;
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+
+ assert(path && contents);
- assert(path != NULL && contents != NULL);
path_len = strlen(path);
- assert(path_len > 0 && path_len >= prefix_len);
+ if (!path_len || path_len < prefix_len) {
+ giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path);
+ return -1;
+ }
if ((dir = opendir(path)) == NULL) {
giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
return -1;
}
-#if defined(__sun) || defined(__GNU__)
- de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
-#else
- de_buf = git__malloc(sizeof(struct dirent));
-#endif
+ if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_path_iconv_init_precompose(&ic);
path += prefix_len;
path_len -= prefix_len;
need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;
while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
- char *entry_path;
- size_t entry_len;
+ char *entry_path, *de_path = de->d_name;
+ size_t alloc_size, de_len = strlen(de_path);
- if (git_path_is_dot_or_dotdot(de->d_name))
+ if (git_path_is_dot_or_dotdot(de_path))
continue;
- entry_len = strlen(de->d_name);
+ if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
+ break;
- entry_path = git__malloc(
- path_len + need_slash + entry_len + 1 + alloc_extra);
- GITERR_CHECK_ALLOC(entry_path);
+ alloc_size = path_len + need_slash + de_len + 1 + alloc_extra;
+ if ((entry_path = git__calloc(alloc_size, 1)) == NULL) {
+ error = -1;
+ break;
+ }
if (path_len)
memcpy(entry_path, path, path_len);
if (need_slash)
entry_path[path_len] = '/';
- memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len);
- entry_path[path_len + need_slash + entry_len] = '\0';
+ memcpy(&entry_path[path_len + need_slash], de_path, de_len);
- if (git_vector_insert(contents, entry_path) < 0) {
- closedir(dir);
- git__free(de_buf);
- return -1;
- }
+ if ((error = git_vector_insert(contents, entry_path)) < 0)
+ break;
}
closedir(dir);
- git__free(de_buf);
+ git_path_iconv_clear(&ic);
if (error != 0)
giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
@@ -858,7 +960,7 @@ int git_path_with_stat_cmp_icase(const void *a, const void *b)
int git_path_dirload_with_stat(
const char *path,
size_t prefix_len,
- bool ignore_case,
+ unsigned int flags,
const char *start_stat,
const char *end_stat,
git_vector *contents)
@@ -875,13 +977,14 @@ int git_path_dirload_with_stat(
return -1;
error = git_path_dirload(
- path, prefix_len, sizeof(git_path_with_stat) + 1, contents);
+ path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents);
if (error < 0) {
git_buf_free(&full);
return error;
}
- strncomp = ignore_case ? git__strncasecmp : git__strncmp;
+ 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) {
diff --git a/src/path.h b/src/path.h
index b2899e97f..eaf94d486 100644
--- a/src/path.h
+++ b/src/path.h
@@ -242,21 +242,28 @@ extern int git_path_resolve_relative(git_buf *path, size_t ceiling);
*/
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),
+};
+
/**
* Walk each directory entry, except '.' and '..', calling fn(state).
*
- * @param pathbuf buffer the function reads the initial directory
+ * @param pathbuf Buffer the function reads the initial directory
* path from, and updates with each successive entry's name.
- * @param fn function to invoke with each entry. The first arg is
- * the input state and the second arg is pathbuf. The function
- * may modify the pathbuf, but only by appending new text.
- * @param state to pass to fn as the first arg.
+ * @param flags Combination of GIT_PATH_DIR flags.
+ * @param callback Callback for each entry. Passed the `payload` and each
+ * successive path inside the directory as a full path. This may
+ * safely append text to the pathbuf if needed.
+ * @param payload Passed to callback as first argument.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
extern int git_path_direach(
git_buf *pathbuf,
- int (*fn)(void *, git_buf *),
- void *state);
+ uint32_t flags,
+ int (*callback)(void *payload, git_buf *path),
+ void *payload);
/**
* Sort function to order two paths
@@ -276,19 +283,19 @@ extern int git_path_cmp(
* @param pathbuf Buffer the function reads the directory from and
* and updates with each successive name.
* @param ceiling Prefix of path at which to stop walking up. If NULL,
- * this will walk all the way up to the root. If not a prefix of
- * pathbuf, the callback will be invoked a single time on the
- * original input path.
- * @param fn Function to invoke on each path. The first arg is the
- * input satte and the second arg is the pathbuf. The function
- * should not modify the pathbuf.
+ * this will walk all the way up to the root. If not a prefix of
+ * pathbuf, the callback will be invoked a single time on the
+ * original input path.
+ * @param callback Function to invoke on each path. Passed the `payload`
+ * and the buffer containing the current path. The path should not
+ * be modified in any way.
* @param state Passed to fn as the first ath.
*/
extern int git_path_walk_up(
git_buf *pathbuf,
const char *ceiling,
- int (*fn)(void *state, git_buf *),
- void *state);
+ int (*callback)(void *payload, git_buf *path),
+ void *payload);
/**
* Load all directory entries (except '.' and '..') into a vector.
@@ -304,12 +311,14 @@ extern int git_path_walk_up(
* 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);
@@ -336,7 +345,7 @@ extern int git_path_with_stat_cmp_icase(const void *a, const void *b);
*
* @param path The directory to read from
* @param prefix_len The trailing part of path to prefix to entry paths
- * @param ignore_case How to sort and compare paths with start/end limits
+ * @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
@@ -344,9 +353,60 @@ extern int git_path_with_stat_cmp_icase(const void *a, const void *b);
extern int git_path_dirload_with_stat(
const char *path,
size_t prefix_len,
- bool ignore_case,
+ uint32_t flags,
const char *start_stat,
const char *end_stat,
git_vector *contents);
+/* translate errno to libgit2 error code and set error message */
+extern int git_path_set_error(
+ int errno_value, const char *path, const char *action);
+
+/* check if non-ascii characters are present in filename */
+extern bool git_path_has_non_ascii(const char *path, size_t pathlen);
+
+#define GIT_PATH_REPO_ENCODING "UTF-8"
+
+#ifdef __APPLE__
+#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC"
+#else
+#define GIT_PATH_NATIVE_ENCODING "UTF-8"
+#endif
+
+#ifdef GIT_USE_ICONV
+
+#include <iconv.h>
+
+typedef struct {
+ iconv_t map;
+ git_buf buf;
+} git_path_iconv_t;
+
+#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_BUF_INIT }
+
+/* Init iconv data for converting decomposed UTF-8 to precomposed */
+extern int git_path_iconv_init_precompose(git_path_iconv_t *ic);
+
+/* Clear allocated iconv data */
+extern void git_path_iconv_clear(git_path_iconv_t *ic);
+
+/*
+ * Rewrite `in` buffer using iconv map if necessary, replacing `in`
+ * pointer internal iconv buffer if rewrite happened. The `in` pointer
+ * will be left unchanged if no rewrite was needed.
+ */
+extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen);
+
+#else
+
+typedef struct {
+ int unused;
+} git_path_iconv_t;
+#define GIT_PATH_ICONV_INIT { 0 }
+#define git_path_iconv_init_precompose(X) 0
+#define git_path_iconv_clear(X) (void)(X)
+#define git_path_iconv(X,Y,Z) 0
+
+#endif /* GIT_USE_ICONV */
+
#endif
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index 0ba711eb7..afe1a2a42 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -56,6 +56,8 @@ typedef struct refdb_fs_backend {
git_sortedcache *refcache;
int peeling_mode;
+ git_iterator_flag_t iterator_flags;
+ uint32_t direach_flags;
} refdb_fs_backend;
static int packref_cmp(const void *a_, const void *b_)
@@ -269,7 +271,8 @@ static int _dirent_loose_load(void *data, git_buf *full_path)
return 0;
if (git_path_isdir(full_path->ptr))
- return git_path_direach(full_path, _dirent_loose_load, backend);
+ return git_path_direach(
+ full_path, backend->direach_flags, _dirent_loose_load, backend);
file_path = full_path->ptr + strlen(backend->path);
@@ -295,7 +298,8 @@ static int packed_loadloose(refdb_fs_backend *backend)
* This will overwrite any old packed entries with their
* updated loose versions
*/
- error = git_path_direach(&refs_path, _dirent_loose_load, backend);
+ error = git_path_direach(
+ &refs_path, backend->direach_flags, _dirent_loose_load, backend);
git_buf_free(&refs_path);
@@ -468,7 +472,7 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 ||
(error = git_iterator_for_filesystem(
- &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0) {
+ &fsit, path.ptr, backend->iterator_flags, NULL, NULL)) < 0) {
git_buf_free(&path);
return error;
}
@@ -1071,6 +1075,7 @@ int git_refdb_backend_fs(
git_refdb_backend **backend_out,
git_repository *repository)
{
+ int t = 0;
git_buf path = GIT_BUF_INIT;
refdb_fs_backend *backend;
@@ -1092,6 +1097,15 @@ int git_refdb_backend_fs(
git_buf_free(&path);
+ if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
+ backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
+ backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE;
+ }
+ if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) {
+ backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+ backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
+ }
+
backend->parent.exists = &refdb_fs_backend__exists;
backend->parent.lookup = &refdb_fs_backend__lookup;
backend->parent.iterator = &refdb_fs_backend__iterator;
diff --git a/src/refs.c b/src/refs.c
index c045ab9dc..0da02a666 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -138,6 +138,22 @@ int git_reference_name_to_id(
return 0;
}
+static int reference_normalize_for_repo(
+ char *out,
+ size_t out_size,
+ git_repository *repo,
+ const char *name)
+{
+ int precompose;
+ unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL;
+
+ if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) &&
+ precompose)
+ flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE;
+
+ return git_reference_normalize_name(out, out_size, name, flags);
+}
+
int git_reference_lookup_resolved(
git_reference **ref_out,
git_repository *repo,
@@ -159,13 +175,13 @@ int git_reference_lookup_resolved(
else if (max_nesting < 0)
max_nesting = DEFAULT_NESTING_LEVEL;
- strncpy(scan_name, name, GIT_REFNAME_MAX);
scan_type = GIT_REF_SYMBOLIC;
- if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
- return -1;
+ if ((error = reference_normalize_for_repo(
+ scan_name, sizeof(scan_name), repo, name)) < 0)
+ return error;
- if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0)
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
return error;
for (nesting = max_nesting;
@@ -173,7 +189,7 @@ int git_reference_lookup_resolved(
nesting--)
{
if (nesting != max_nesting) {
- strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX);
+ strncpy(scan_name, ref->target.symbolic, sizeof(scan_name));
git_reference_free(ref);
}
@@ -711,17 +727,18 @@ static bool is_all_caps_and_underscore(const char *name, size_t len)
return true;
}
+/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
int git_reference__normalize_name(
git_buf *buf,
const char *name,
unsigned int flags)
{
- // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100
-
char *current;
int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
unsigned int process_flags;
bool normalize = (buf != NULL);
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+
assert(name);
process_flags = flags;
@@ -733,6 +750,13 @@ int git_reference__normalize_name(
if (normalize)
git_buf_clear(buf);
+ if ((flags & GIT_REF_FORMAT__PRECOMPOSE_UNICODE) != 0) {
+ size_t namelen = strlen(current);
+ if ((error = git_path_iconv_init_precompose(&ic)) < 0 ||
+ (error = git_path_iconv(&ic, &current, &namelen)) < 0)
+ goto cleanup;
+ }
+
while (true) {
segment_len = ensure_segment_validity(current);
if (segment_len < 0) {
@@ -809,6 +833,8 @@ cleanup:
if (error && normalize)
git_buf_free(buf);
+ git_path_iconv_clear(&ic);
+
return error;
}
@@ -983,9 +1009,9 @@ static int peel_error(int error, git_reference *ref, const char* msg)
}
int git_reference_peel(
- git_object **peeled,
- git_reference *ref,
- git_otype target_type)
+ git_object **peeled,
+ git_reference *ref,
+ git_otype target_type)
{
git_reference *resolved = NULL;
git_object *target = NULL;
@@ -1027,24 +1053,19 @@ cleanup:
return error;
}
-int git_reference__is_valid_name(
- const char *refname,
- unsigned int flags)
+int git_reference__is_valid_name(const char *refname, unsigned int flags)
{
- int error;
-
- error = git_reference__normalize_name(NULL, refname, flags) == 0;
- giterr_clear();
+ if (git_reference__normalize_name(NULL, refname, flags) < 0) {
+ giterr_clear();
+ return false;
+ }
- return error;
+ return true;
}
-int git_reference_is_valid_name(
- const char *refname)
+int git_reference_is_valid_name(const char *refname)
{
- return git_reference__is_valid_name(
- refname,
- GIT_REF_FORMAT_ALLOW_ONELEVEL);
+ return git_reference__is_valid_name(refname, GIT_REF_FORMAT_ALLOW_ONELEVEL);
}
const char *git_reference_shorthand(git_reference *ref)
diff --git a/src/refs.h b/src/refs.h
index 4b91c25e8..80c7703fc 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -46,6 +46,8 @@
#define GIT_STASH_FILE "stash"
#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE
+#define GIT_REF_FORMAT__PRECOMPOSE_UNICODE (1u << 16)
+
#define GIT_REFNAME_MAX 1024
struct git_reference {
diff --git a/src/repository.c b/src/repository.c
index 0d7c09484..c5ce8425f 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -843,10 +843,6 @@ fail:
static bool is_chmod_supported(const char *file_path)
{
struct stat st1, st2;
- static int _is_supported = -1;
-
- if (_is_supported > -1)
- return _is_supported;
if (p_stat(file_path, &st1) < 0)
return false;
@@ -857,27 +853,19 @@ static bool is_chmod_supported(const char *file_path)
if (p_stat(file_path, &st2) < 0)
return false;
- _is_supported = (st1.st_mode != st2.st_mode);
-
- return _is_supported;
+ return (st1.st_mode != st2.st_mode);
}
static bool is_filesystem_case_insensitive(const char *gitdir_path)
{
git_buf path = GIT_BUF_INIT;
- static int _is_insensitive = -1;
+ int is_insensitive = -1;
- if (_is_insensitive > -1)
- return _is_insensitive;
-
- if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0)
- goto cleanup;
+ if (!git_buf_joinpath(&path, gitdir_path, "CoNfIg"))
+ is_insensitive = git_path_exists(git_buf_cstr(&path));
- _is_insensitive = git_path_exists(git_buf_cstr(&path));
-
-cleanup:
git_buf_free(&path);
- return _is_insensitive;
+ return is_insensitive;
}
static bool are_symlinks_supported(const char *wd_path)
@@ -885,26 +873,77 @@ static bool are_symlinks_supported(const char *wd_path)
git_buf path = GIT_BUF_INIT;
int fd;
struct stat st;
- static int _symlinks_supported = -1;
-
- if (_symlinks_supported > -1)
- return _symlinks_supported;
+ int symlinks_supported = -1;
if ((fd = git_futils_mktmp(&path, wd_path)) < 0 ||
p_close(fd) < 0 ||
p_unlink(path.ptr) < 0 ||
p_symlink("testing", path.ptr) < 0 ||
p_lstat(path.ptr, &st) < 0)
- _symlinks_supported = false;
+ symlinks_supported = false;
else
- _symlinks_supported = (S_ISLNK(st.st_mode) != 0);
+ symlinks_supported = (S_ISLNK(st.st_mode) != 0);
(void)p_unlink(path.ptr);
git_buf_free(&path);
- return _symlinks_supported;
+ return symlinks_supported;
+}
+
+#ifdef GIT_USE_ICONV
+
+static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
+static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
+
+/* Check if the platform is decomposing unicode data for us. We will
+ * emulate core Git and prefer to use precomposed unicode data internally
+ * on these platforms, composing the decomposed unicode on the fly.
+ *
+ * This mainly happens on the Mac where HDFS stores filenames as
+ * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
+ * return decomposed unicode from readdir() even when the actual
+ * filesystem is storing precomposed unicode.
+ */
+static bool does_fs_decompose_unicode_paths(const char *wd_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ bool found_decomposed = false;
+ char tmp[6];
+
+ /* Create a file using a precomposed path and then try to find it
+ * using the decomposed name. If the lookup fails, then we will mark
+ * that we should precompose unicode for this repository.
+ */
+ if (git_buf_joinpath(&path, wd_path, nfc_file) < 0 ||
+ (fd = p_mkstemp(path.ptr)) < 0)
+ goto done;
+ p_close(fd);
+
+ /* record trailing digits generated by mkstemp */
+ memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
+
+ /* try to look up as NFD path */
+ if (git_buf_joinpath(&path, wd_path, nfd_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ found_decomposed = git_path_exists(path.ptr);
+
+ /* remove temporary file (using original precomposed path) */
+ if (git_buf_joinpath(&path, wd_path, nfc_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ (void)p_unlink(path.ptr);
+
+done:
+ git_buf_free(&path);
+ return found_decomposed;
}
+#endif
+
static int create_empty_file(const char *path, mode_t mode)
{
int fd;
@@ -922,71 +961,131 @@ static int create_empty_file(const char *path, mode_t mode)
return 0;
}
+static int repo_local_config(
+ git_config **out,
+ git_buf *config_dir,
+ git_repository *repo,
+ const char *repo_dir)
+{
+ int error = 0;
+ git_config *parent;
+ const char *cfg_path;
+
+ if (git_buf_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
+ return -1;
+ cfg_path = git_buf_cstr(config_dir);
+
+ /* make LOCAL config if missing */
+ if (!git_path_isfile(cfg_path) &&
+ (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0)
+ return error;
+
+ /* if no repo, just open that file directly */
+ if (!repo)
+ return git_config_open_ondisk(out, cfg_path);
+
+ /* otherwise, open parent config and get that level */
+ if ((error = git_repository_config__weakptr(&parent, repo)) < 0)
+ return error;
+
+ if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) {
+ giterr_clear();
+
+ if (!(error = git_config_add_file_ondisk(
+ parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, false)))
+ error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL);
+ }
+
+ git_config_free(parent);
+
+ return error;
+}
+
+static int repo_init_fs_configs(
+ git_config *cfg,
+ const char *cfg_path,
+ const char *repo_dir,
+ const char *work_dir,
+ bool update_ignorecase)
+{
+ int error = 0;
+
+ if (!work_dir)
+ work_dir = repo_dir;
+
+ if ((error = git_config_set_bool(
+ cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0)
+ return error;
+
+ if (!are_symlinks_supported(work_dir)) {
+ if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0)
+ return error;
+ } else if (git_config_delete_entry(cfg, "core.symlinks") < 0)
+ giterr_clear();
+
+ if (update_ignorecase) {
+ if (is_filesystem_case_insensitive(repo_dir)) {
+ if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0)
+ return error;
+ } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0)
+ giterr_clear();
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((error = git_config_set_bool(
+ cfg, "core.precomposeunicode",
+ does_fs_decompose_unicode_paths(work_dir))) < 0)
+ return error;
+#endif
+
+ return 0;
+}
+
static int repo_init_config(
const char *repo_dir,
const char *work_dir,
- git_repository_init_options *opts)
+ uint32_t flags,
+ uint32_t mode)
{
int error = 0;
git_buf cfg_path = GIT_BUF_INIT;
git_config *config = NULL;
+ bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0);
+ bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0);
-#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\
- if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
- goto cleanup; } while (0)
+ if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0)
+ goto cleanup;
- if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
- return -1;
+ if (is_reinit && (error = check_repositoryformatversion(config)) < 0)
+ goto cleanup;
- if (!git_path_isfile(git_buf_cstr(&cfg_path)) &&
- create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) {
- git_buf_free(&cfg_path);
- return -1;
- }
+#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \
+ if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
+ goto cleanup; } while (0)
- if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) {
- git_buf_free(&cfg_path);
- return -1;
- }
+ SET_REPO_CONFIG(bool, "core.bare", is_bare);
+ SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION);
- if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 &&
- (error = check_repositoryformatversion(config)) < 0)
+ if ((error = repo_init_fs_configs(
+ config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0)
goto cleanup;
- SET_REPO_CONFIG(
- bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
- SET_REPO_CONFIG(
- int32, "core.repositoryformatversion", GIT_REPO_VERSION);
- SET_REPO_CONFIG(
- bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path)));
-
- if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) {
+ if (!is_bare) {
SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
- if (!are_symlinks_supported(work_dir))
- SET_REPO_CONFIG(bool, "core.symlinks", false);
-
- if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {
+ if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD))
SET_REPO_CONFIG(string, "core.worktree", work_dir);
- }
- else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) {
+ else if (is_reinit) {
if (git_config_delete_entry(config, "core.worktree") < 0)
giterr_clear();
}
- } else {
- if (!are_symlinks_supported(repo_dir))
- SET_REPO_CONFIG(bool, "core.symlinks", false);
}
- if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) &&
- is_filesystem_case_insensitive(repo_dir))
- SET_REPO_CONFIG(bool, "core.ignorecase", true);
-
- if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {
+ if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {
SET_REPO_CONFIG(int32, "core.sharedrepository", 1);
SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
}
- else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) {
+ else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) {
SET_REPO_CONFIG(int32, "core.sharedrepository", 2);
SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
}
@@ -998,6 +1097,41 @@ cleanup:
return error;
}
+static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p)
+{
+ git_repository *smrepo = NULL;
+ GIT_UNUSED(n); GIT_UNUSED(p);
+
+ if (git_submodule_open(&smrepo, sm) < 0 ||
+ git_repository_reinit_filesystem(smrepo, true) < 0)
+ giterr_clear();
+ git_repository_free(smrepo);
+
+ return 0;
+}
+
+int git_repository_reinit_filesystem(git_repository *repo, int recurse)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ git_config *config = NULL;
+ const char *repo_dir = git_repository_path(repo);
+
+ if (!(error = repo_local_config(&config, &path, repo, repo_dir)))
+ error = repo_init_fs_configs(
+ config, path.ptr, repo_dir, git_repository_workdir(repo), true);
+
+ git_config_free(config);
+ git_buf_free(&path);
+
+ git_repository__cvar_cache_clear(repo);
+
+ if (!repo->is_bare && recurse)
+ (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL);
+
+ return error;
+}
+
static int repo_write_template(
const char *git_dir,
bool allow_overwrite,
@@ -1386,22 +1520,22 @@ int git_repository_init_ext(
opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;
error = repo_init_config(
- git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts);
+ repo_path.ptr, wd_path.ptr, opts->flags, opts->mode);
/* TODO: reinitialize the templates */
}
else {
if (!(error = repo_init_structure(
- git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) &&
+ repo_path.ptr, wd_path.ptr, opts)) &&
!(error = repo_init_config(
- git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)))
+ repo_path.ptr, wd_path.ptr, opts->flags, opts->mode)))
error = repo_init_create_head(
- git_buf_cstr(&repo_path), opts->initial_head);
+ repo_path.ptr, opts->initial_head);
}
if (error < 0)
goto cleanup;
- error = git_repository_open(out, git_buf_cstr(&repo_path));
+ error = git_repository_open(out, repo_path.ptr);
if (!error && opts->origin_url)
error = repo_init_create_origin(*out, opts->origin_url);
diff --git a/src/repository.h b/src/repository.h
index 12dc50d51..832df3bd2 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -37,6 +37,7 @@ typedef enum {
GIT_CVAR_IGNORESTAT, /* core.ignorestat */
GIT_CVAR_TRUSTCTIME, /* core.trustctime */
GIT_CVAR_ABBREV, /* core.abbrev */
+ GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */
GIT_CVAR_CACHE_MAX
} git_cvar_cached;
@@ -86,6 +87,8 @@ typedef enum {
GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE,
/* core.abbrev */
GIT_ABBREV_DEFAULT = 7,
+ /* core.precomposeunicode */
+ GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE,
} git_cvar_value;
diff --git a/src/submodule.c b/src/submodule.c
index 121383b9c..12ade83fe 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -372,7 +372,8 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
memset(&entry, 0, sizeof(entry));
entry.path = sm->path;
- git_index_entry__init_from_stat(&entry, &st);
+ git_index_entry__init_from_stat(
+ &entry, &st, !(git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE));
/* calling git_submodule_open will have set sm->wd_oid if possible */
if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 2f490529c..18f717b0f 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -125,8 +125,8 @@ static int do_lstat(
errno = ENOENT;
- /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the
- * file path is a regular file,otherwise ENOENT must be set.
+ /* To match POSIX behavior, set ENOTDIR when any of the folders in the
+ * file path is a regular file, otherwise set ENOENT.
*/
if (posix_enotdir) {
/* scan up path until we find an existing item */