| /* |
| * 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 "path.h" |
| #include "posix.h" |
| #ifdef GIT_WIN32 |
| #include "win32/dir.h" |
| #include "win32/posix.h" |
| #else |
| #include <dirent.h> |
| #endif |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| |
| #define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':') |
| |
| #ifdef GIT_WIN32 |
| static bool looks_like_network_computer_name(const char *path, int pos) |
| { |
| if (pos < 3) |
| return false; |
| |
| if (path[0] != '/' || path[1] != '/') |
| return false; |
| |
| while (pos-- > 2) { |
| if (path[pos] == '/') |
| return false; |
| } |
| |
| return true; |
| } |
| #endif |
| |
| /* |
| * Based on the Android implementation, BSD licensed. |
| * http://android.git.kernel.org/ |
| * |
| * Copyright (C) 2008 The Android Open Source Project |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| int git_path_basename_r(git_buf *buffer, const char *path) |
| { |
| const char *endp, *startp; |
| int len, result; |
| |
| /* Empty or NULL string gets treated as "." */ |
| if (path == NULL || *path == '\0') { |
| startp = "."; |
| len = 1; |
| goto Exit; |
| } |
| |
| /* Strip trailing slashes */ |
| endp = path + strlen(path) - 1; |
| while (endp > path && *endp == '/') |
| endp--; |
| |
| /* All slashes becomes "/" */ |
| if (endp == path && *endp == '/') { |
| startp = "/"; |
| len = 1; |
| goto Exit; |
| } |
| |
| /* Find the start of the base */ |
| startp = endp; |
| while (startp > path && *(startp - 1) != '/') |
| startp--; |
| |
| /* Cast is safe because max path < max int */ |
| len = (int)(endp - startp + 1); |
| |
| Exit: |
| result = len; |
| |
| if (buffer != NULL && git_buf_set(buffer, startp, len) < 0) |
| return -1; |
| |
| return result; |
| } |
| |
| /* |
| * Based on the Android implementation, BSD licensed. |
| * Check http://android.git.kernel.org/ |
| */ |
| int git_path_dirname_r(git_buf *buffer, const char *path) |
| { |
| const char *endp; |
| int result, len; |
| |
| /* Empty or NULL string gets treated as "." */ |
| if (path == NULL || *path == '\0') { |
| path = "."; |
| len = 1; |
| goto Exit; |
| } |
| |
| /* Strip trailing slashes */ |
| endp = path + strlen(path) - 1; |
| while (endp > path && *endp == '/') |
| endp--; |
| |
| /* Find the start of the dir */ |
| while (endp > path && *endp != '/') |
| endp--; |
| |
| /* Either the dir is "/" or there are no slashes */ |
| if (endp == path) { |
| path = (*endp == '/') ? "/" : "."; |
| len = 1; |
| goto Exit; |
| } |
| |
| do { |
| endp--; |
| } while (endp > path && *endp == '/'); |
| |
| /* Cast is safe because max path < max int */ |
| len = (int)(endp - path + 1); |
| |
| #ifdef GIT_WIN32 |
| /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return |
| 'C:/' here */ |
| |
| if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) { |
| len = 3; |
| goto Exit; |
| } |
| |
| /* Similarly checks if we're dealing with a network computer name |
| '//computername/.git' will return '//computername/' */ |
| |
| if (looks_like_network_computer_name(path, len)) { |
| len++; |
| goto Exit; |
| } |
| |
| #endif |
| |
| Exit: |
| result = len; |
| |
| if (buffer != NULL && git_buf_set(buffer, path, len) < 0) |
| return -1; |
| |
| return result; |
| } |
| |
| |
| char *git_path_dirname(const char *path) |
| { |
| git_buf buf = GIT_BUF_INIT; |
| char *dirname; |
| |
| git_path_dirname_r(&buf, path); |
| dirname = git_buf_detach(&buf); |
| git_buf_free(&buf); /* avoid memleak if error occurs */ |
| |
| return dirname; |
| } |
| |
| char *git_path_basename(const char *path) |
| { |
| git_buf buf = GIT_BUF_INIT; |
| char *basename; |
| |
| git_path_basename_r(&buf, path); |
| basename = git_buf_detach(&buf); |
| git_buf_free(&buf); /* avoid memleak if error occurs */ |
| |
| return basename; |
| } |
| |
| size_t git_path_basename_offset(git_buf *buffer) |
| { |
| ssize_t slash; |
| |
| if (!buffer || buffer->size <= 0) |
| return 0; |
| |
| slash = git_buf_rfind_next(buffer, '/'); |
| |
| if (slash >= 0 && buffer->ptr[slash] == '/') |
| return (size_t)(slash + 1); |
| |
| return 0; |
| } |
| |
| const char *git_path_topdir(const char *path) |
| { |
| size_t len; |
| ssize_t i; |
| |
| assert(path); |
| len = strlen(path); |
| |
| if (!len || path[len - 1] != '/') |
| return NULL; |
| |
| for (i = (ssize_t)len - 2; i >= 0; --i) |
| if (path[i] == '/') |
| break; |
| |
| return &path[i + 1]; |
| } |
| |
| int git_path_root(const char *path) |
| { |
| int offset = 0; |
| |
| /* Does the root of the path look like a windows drive ? */ |
| if (LOOKS_LIKE_DRIVE_PREFIX(path)) |
| offset += 2; |
| |
| #ifdef GIT_WIN32 |
| /* Are we dealing with a windows network path? */ |
| else if ((path[0] == '/' && path[1] == '/') || |
| (path[0] == '\\' && path[1] == '\\')) |
| { |
| offset += 2; |
| |
| /* Skip the computer name segment */ |
| while (path[offset] && path[offset] != '/' && path[offset] != '\\') |
| offset++; |
| } |
| #endif |
| |
| if (path[offset] == '/' || path[offset] == '\\') |
| return offset; |
| |
| return -1; /* Not a real error - signals that path is not rooted */ |
| } |
| |
| int git_path_join_unrooted( |
| git_buf *path_out, const char *path, const char *base, ssize_t *root_at) |
| { |
| int error, root; |
| |
| assert(path && path_out); |
| |
| root = git_path_root(path); |
| |
| if (base != NULL && root < 0) { |
| error = git_buf_joinpath(path_out, base, path); |
| |
| if (root_at) |
| *root_at = (ssize_t)strlen(base); |
| } |
| else { |
| error = git_buf_sets(path_out, path); |
| |
| if (root_at) |
| *root_at = (root < 0) ? 0 : (ssize_t)root; |
| } |
| |
| return error; |
| } |
| |
| int git_path_prettify(git_buf *path_out, const char *path, const char *base) |
| { |
| char buf[GIT_PATH_MAX]; |
| |
| assert(path && path_out); |
| |
| /* construct path if needed */ |
| if (base != NULL && git_path_root(path) < 0) { |
| if (git_buf_joinpath(path_out, base, path) < 0) |
| return -1; |
| path = path_out->ptr; |
| } |
| |
| if (p_realpath(path, buf) == NULL) { |
| /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */ |
| int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; |
| giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); |
| |
| git_buf_clear(path_out); |
| |
| return error; |
| } |
| |
| return git_buf_sets(path_out, buf); |
| } |
| |
| int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base) |
| { |
| int error = git_path_prettify(path_out, path, base); |
| return (error < 0) ? error : git_path_to_dir(path_out); |
| } |
| |
| int git_path_to_dir(git_buf *path) |
| { |
| if (path->asize > 0 && |
| git_buf_len(path) > 0 && |
| path->ptr[git_buf_len(path) - 1] != '/') |
| git_buf_putc(path, '/'); |
| |
| return git_buf_oom(path) ? -1 : 0; |
| } |
| |
| void git_path_string_to_dir(char* path, size_t size) |
| { |
| size_t end = strlen(path); |
| |
| if (end && path[end - 1] != '/' && end < size) { |
| path[end] = '/'; |
| path[end + 1] = '\0'; |
| } |
| } |
| |
| int git__percent_decode(git_buf *decoded_out, const char *input) |
| { |
| int len, hi, lo, i; |
| assert(decoded_out && input); |
| |
| len = (int)strlen(input); |
| git_buf_clear(decoded_out); |
| |
| for(i = 0; i < len; i++) |
| { |
| char c = input[i]; |
| |
| if (c != '%') |
| goto append; |
| |
| if (i >= len - 2) |
| goto append; |
| |
| hi = git__fromhex(input[i + 1]); |
| lo = git__fromhex(input[i + 2]); |
| |
| if (hi < 0 || lo < 0) |
| goto append; |
| |
| c = (char)(hi << 4 | lo); |
| i += 2; |
| |
| append: |
| if (git_buf_putc(decoded_out, c) < 0) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int error_invalid_local_file_uri(const char *uri) |
| { |
| giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri); |
| return -1; |
| } |
| |
| int git_path_fromurl(git_buf *local_path_out, const char *file_url) |
| { |
| int offset = 0, len; |
| |
| assert(local_path_out && file_url); |
| |
| if (git__prefixcmp(file_url, "file://") != 0) |
| return error_invalid_local_file_uri(file_url); |
| |
| offset += 7; |
| len = (int)strlen(file_url); |
| |
| if (offset < len && file_url[offset] == '/') |
| offset++; |
| else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0) |
| offset += 10; |
| else |
| return error_invalid_local_file_uri(file_url); |
| |
| if (offset >= len || file_url[offset] == '/') |
| return error_invalid_local_file_uri(file_url); |
| |
| #ifndef GIT_WIN32 |
| offset--; /* A *nix absolute path starts with a forward slash */ |
| #endif |
| |
| git_buf_clear(local_path_out); |
| |
| return git__percent_decode(local_path_out, file_url + offset); |
| } |
| |
| int git_path_walk_up( |
| git_buf *path, |
| const char *ceiling, |
| int (*cb)(void *data, git_buf *), |
| void *data) |
| { |
| int error = 0; |
| git_buf iter; |
| ssize_t stop = 0, scan; |
| char oldc = '\0'; |
| |
| assert(path && cb); |
| |
| if (ceiling != NULL) { |
| if (git__prefixcmp(path->ptr, ceiling) == 0) |
| stop = (ssize_t)strlen(ceiling); |
| else |
| stop = git_buf_len(path); |
| } |
| scan = git_buf_len(path); |
| |
| iter.ptr = path->ptr; |
| iter.size = git_buf_len(path); |
| iter.asize = path->asize; |
| |
| while (scan >= stop) { |
| error = cb(data, &iter); |
| iter.ptr[scan] = oldc; |
| if (error < 0) |
| break; |
| scan = git_buf_rfind_next(&iter, '/'); |
| if (scan >= 0) { |
| scan++; |
| oldc = iter.ptr[scan]; |
| iter.size = scan; |
| iter.ptr[scan] = '\0'; |
| } |
| } |
| |
| if (scan >= 0) |
| iter.ptr[scan] = oldc; |
| |
| return error; |
| } |
| |
| bool git_path_exists(const char *path) |
| { |
| assert(path); |
| return p_access(path, F_OK) == 0; |
| } |
| |
| bool git_path_isdir(const char *path) |
| { |
| struct stat st; |
| if (p_stat(path, &st) < 0) |
| return false; |
| |
| return S_ISDIR(st.st_mode) != 0; |
| } |
| |
| bool git_path_isfile(const char *path) |
| { |
| struct stat st; |
| |
| assert(path); |
| if (p_stat(path, &st) < 0) |
| return false; |
| |
| return S_ISREG(st.st_mode) != 0; |
| } |
| |
| #ifdef GIT_WIN32 |
| |
| bool git_path_is_empty_dir(const char *path) |
| { |
| git_buf pathbuf = GIT_BUF_INIT; |
| HANDLE hFind = INVALID_HANDLE_VALUE; |
| wchar_t wbuf[GIT_WIN_PATH]; |
| WIN32_FIND_DATAW ffd; |
| bool retval = true; |
| |
| if (!git_path_isdir(path)) return false; |
| |
| git_buf_printf(&pathbuf, "%s\\*", path); |
| git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf)); |
| |
| hFind = FindFirstFileW(wbuf, &ffd); |
| if (INVALID_HANDLE_VALUE == hFind) { |
| giterr_set(GITERR_OS, "Couldn't open '%s'", path); |
| return false; |
| } |
| |
| do { |
| if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { |
| retval = false; |
| } |
| } while (FindNextFileW(hFind, &ffd) != 0); |
| |
| FindClose(hFind); |
| git_buf_free(&pathbuf); |
| return retval; |
| } |
| |
| #else |
| |
| bool git_path_is_empty_dir(const char *path) |
| { |
| DIR *dir = NULL; |
| struct dirent *e; |
| bool retval = true; |
| |
| if (!git_path_isdir(path)) return false; |
| |
| dir = opendir(path); |
| if (!dir) { |
| giterr_set(GITERR_OS, "Couldn't open '%s'", 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); |
| |
| return retval; |
| } |
| #endif |
| |
| int git_path_lstat(const char *path, struct stat *st) |
| { |
| 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); |
| } |
| |
| return err; |
| } |
| |
| static bool _check_dir_contents( |
| git_buf *dir, |
| const char *sub, |
| bool (*predicate)(const char *)) |
| { |
| bool result; |
| size_t dir_size = git_buf_len(dir); |
| size_t sub_size = strlen(sub); |
| |
| /* leave base valid even if we could not make space for subdir */ |
| if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0) |
| return false; |
| |
| /* save excursion */ |
| git_buf_joinpath(dir, dir->ptr, sub); |
| |
| result = predicate(dir->ptr); |
| |
| /* restore path */ |
| git_buf_truncate(dir, dir_size); |
| return result; |
| } |
| |
| bool git_path_contains(git_buf *dir, const char *item) |
| { |
| return _check_dir_contents(dir, item, &git_path_exists); |
| } |
| |
| bool git_path_contains_dir(git_buf *base, const char *subdir) |
| { |
| return _check_dir_contents(base, subdir, &git_path_isdir); |
| } |
| |
| bool git_path_contains_file(git_buf *base, const char *file) |
| { |
| return _check_dir_contents(base, file, &git_path_isfile); |
| } |
| |
| int git_path_find_dir(git_buf *dir, const char *path, const char *base) |
| { |
| int error = git_path_join_unrooted(dir, path, base, NULL); |
| |
| if (!error) { |
| char buf[GIT_PATH_MAX]; |
| if (p_realpath(dir->ptr, buf) != NULL) |
| error = git_buf_sets(dir, buf); |
| } |
| |
| /* call dirname if this is not a directory */ |
| if (!error && git_path_isdir(dir->ptr) == false) |
| error = git_path_dirname_r(dir, dir->ptr); |
| |
| if (!error) |
| error = git_path_to_dir(dir); |
| |
| return error; |
| } |
| |
| int git_path_resolve_relative(git_buf *path, size_t ceiling) |
| { |
| char *base, *to, *from, *next; |
| size_t len; |
| |
| if (!path || git_buf_oom(path)) |
| return -1; |
| |
| if (ceiling > path->size) |
| ceiling = path->size; |
| |
| /* recognize drive prefixes, etc. that should not be backed over */ |
| if (ceiling == 0) |
| ceiling = git_path_root(path->ptr) + 1; |
| |
| /* recognize URL prefixes that should not be backed over */ |
| if (ceiling == 0) { |
| for (next = path->ptr; *next && git__isalpha(*next); ++next); |
| if (next[0] == ':' && next[1] == '/' && next[2] == '/') |
| ceiling = (next + 3) - path->ptr; |
| } |
| |
| base = to = from = path->ptr + ceiling; |
| |
| while (*from) { |
| for (next = from; *next && *next != '/'; ++next); |
| |
| len = next - from; |
| |
| if (len == 1 && from[0] == '.') |
| /* do nothing with singleton dot */; |
| |
| else if (len == 2 && from[0] == '.' && from[1] == '.') { |
| while (to > base && to[-1] == '/') to--; |
| while (to > base && to[-1] != '/') to--; |
| } |
| |
| else { |
| if (*next == '/') |
| len++; |
| |
| if (to != from) |
| memmove(to, from, len); |
| |
| to += len; |
| } |
| |
| from += len; |
| |
| while (*from == '/') from++; |
| } |
| |
| *to = '\0'; |
| |
| path->size = to - path->ptr; |
| |
| return 0; |
| } |
| |
| int git_path_apply_relative(git_buf *target, const char *relpath) |
| { |
| git_buf_joinpath(target, git_buf_cstr(target), relpath); |
| return git_path_resolve_relative(target, 0); |
| } |
| |
| int git_path_cmp( |
| const char *name1, size_t len1, int isdir1, |
| const char *name2, size_t len2, int isdir2, |
| int (*compare)(const char *, const char *, size_t)) |
| { |
| unsigned char c1, c2; |
| size_t len = len1 < len2 ? len1 : len2; |
| int cmp; |
| |
| cmp = compare(name1, name2, len); |
| if (cmp) |
| return cmp; |
| |
| c1 = name1[len]; |
| c2 = name2[len]; |
| |
| if (c1 == '\0' && isdir1) |
| c1 = '/'; |
| |
| if (c2 == '\0' && isdir2) |
| c2 = '/'; |
| |
| return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; |
| } |
| |
| int git_path_direach( |
| git_buf *path, |
| int (*fn)(void *, git_buf *), |
| void *arg) |
| { |
| ssize_t wd_len; |
| DIR *dir; |
| struct dirent *de, *de_buf; |
| |
| if (git_path_to_dir(path) < 0) |
| return -1; |
| |
| wd_len = git_buf_len(path); |
| |
| if ((dir = opendir(path->ptr)) == NULL) { |
| giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr); |
| 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 |
| |
| while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { |
| int result; |
| |
| if (git_path_is_dot_or_dotdot(de->d_name)) |
| continue; |
| |
| if (git_buf_puts(path, de->d_name) < 0) { |
| closedir(dir); |
| git__free(de_buf); |
| return -1; |
| } |
| |
| result = fn(arg, path); |
| |
| git_buf_truncate(path, wd_len); /* restore path */ |
| |
| if (result < 0) { |
| closedir(dir); |
| git__free(de_buf); |
| return -1; |
| } |
| } |
| |
| closedir(dir); |
| git__free(de_buf); |
| return 0; |
| } |
| |
| int git_path_dirload( |
| const char *path, |
| size_t prefix_len, |
| size_t alloc_extra, |
| git_vector *contents) |
| { |
| int error, need_slash; |
| DIR *dir; |
| struct dirent *de, *de_buf; |
| size_t path_len; |
| |
| assert(path != NULL && contents != NULL); |
| path_len = strlen(path); |
| assert(path_len > 0 && path_len >= prefix_len); |
| |
| 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 |
| |
| 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; |
| |
| if (git_path_is_dot_or_dotdot(de->d_name)) |
| continue; |
| |
| entry_len = strlen(de->d_name); |
| |
| entry_path = git__malloc( |
| path_len + need_slash + entry_len + 1 + alloc_extra); |
| GITERR_CHECK_ALLOC(entry_path); |
| |
| 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'; |
| |
| if (git_vector_insert(contents, entry_path) < 0) { |
| closedir(dir); |
| git__free(de_buf); |
| return -1; |
| } |
| } |
| |
| closedir(dir); |
| git__free(de_buf); |
| |
| if (error != 0) |
| giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); |
| |
| return error; |
| } |
| |
| 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); |
| } |
| |
| 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, |
| bool ignore_case, |
| 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) |
| return -1; |
| |
| error = git_path_dirload( |
| path, prefix_len, sizeof(git_path_with_stat) + 1, contents); |
| if (error < 0) { |
| git_buf_free(&full); |
| return error; |
| } |
| |
| strncomp = ignore_case ? git__strncasecmp : git__strncmp; |
| |
| /* stat struct at start of git_path_with_stat, so shift path text */ |
| git_vector_foreach(contents, i, ps) { |
| size_t path_len = strlen((char *)ps); |
| memmove(ps->path, ps, path_len + 1); |
| ps->path_len = path_len; |
| } |
| |
| git_vector_foreach(contents, i, ps) { |
| /* skip if before start_stat or after end_stat */ |
| cmp_len = min(start_len, ps->path_len); |
| if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0) |
| continue; |
| cmp_len = min(end_len, ps->path_len); |
| if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0) |
| continue; |
| |
| git_buf_truncate(&full, prefix_len); |
| |
| if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || |
| (error = git_path_lstat(full.ptr, &ps->st)) < 0) |
| break; |
| |
| if (S_ISDIR(ps->st.st_mode)) { |
| if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0) |
| break; |
| |
| if (p_access(full.ptr, F_OK) == 0) { |
| ps->st.st_mode = GIT_FILEMODE_COMMIT; |
| } else { |
| ps->path[ps->path_len++] = '/'; |
| ps->path[ps->path_len] = '\0'; |
| } |
| } |
| } |
| |
| /* sort now that directory suffix is added */ |
| git_vector_sort(contents); |
| |
| git_buf_free(&full); |
| |
| return error; |
| } |