diff --git a/include/git2/repository.h b/include/git2/repository.h index 493e82ad5..4a7303e68 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -132,6 +132,34 @@ GIT_EXTERN(int) git_repository_open3(git_repository **repository, const char *git_index_file, const char *git_work_tree); +/** + * Look for a git repository and copy its path in the given buffer. The lookup start + * from base_path and walk across parent directories if nothing has been found. The + * lookup ends when the first repository is found, or when reaching a directory + * referenced in ceiling_dirs or when the filesystem changes (in case across_fs + * is true). + * + * The method will automatically detect if the repository is bare (if there is + * a repository). + * + * @param repository_path The user allocated buffer which will contain the found path. + * + * @param size repository_path size + * + * @param start_path The base path where the lookup starts. + * + * @param across_fs If true, then the lookup will not stop when a filesystem device change + * is detected while exploring parent directories. + * + * @param ceiling_dirs A colon separated of absolute symbolic link free paths. The lookup will + * stop when any of this paths is reached. Note that the lookup always performs on start_path + * no matter start_path appears in ceiling_dirs + * ceiling_dirs might be NULL (which is equivalent to an empty string) + * + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_repository_discover(char *repository_path, size_t size, const char *start_path, int across_fs, const char *ceiling_dirs); + /** * Get the object database behind a Git repository * diff --git a/src/filebuf.c b/src/filebuf.c index 63f2897cb..97dec83f3 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -41,16 +41,14 @@ static int lock_file(git_filebuf *file, int flags) /* create path to the file buffer is required */ if (flags & GIT_FILEBUF_FORCE) { - file->fd = gitfo_creat_force(file->path_lock, 0644); + file->fd = gitfo_creat_locked_force(file->path_lock, 0644); } else { - file->fd = gitfo_creat(file->path_lock, 0644); + file->fd = gitfo_creat_locked(file->path_lock, 0644); } if (file->fd < 0) return git__throw(GIT_EOSERR, "Failed to create lock"); - /* TODO: do a flock() in the descriptor file_lock */ - if ((flags & GIT_FILEBUF_APPEND) && gitfo_exists(file->path_original) == 0) { git_file source; char buffer[2048]; diff --git a/src/fileops.c b/src/fileops.c index 73939349d..56bf2927f 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -66,6 +66,20 @@ int gitfo_creat_force(const char *path, int mode) return gitfo_creat(path, mode); } +int gitfo_creat_locked(const char *path, int mode) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); + return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create locked file. Could not open %s", path); +} + +int gitfo_creat_locked_force(const char *path, int mode) +{ + if (gitfo_mkdir_2file(path) < GIT_SUCCESS) + return git__throw(GIT_EOSERR, "Failed to create locked file %s", path); + + return gitfo_creat_locked(path, mode); +} + int gitfo_read(git_file fd, void *buf, size_t cnt) { char *b = buf; @@ -131,7 +145,26 @@ int gitfo_isdir(const char *path) return git__throw(GIT_ENOTFOUND, "%s does not exist", path); if (!S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, "%s is a file", path); + return git__throw(GIT_ENOTFOUND, "%s is not a directory", path); + + return GIT_SUCCESS; +} + +int gitfo_isfile(const char *path) +{ + struct stat st; + int stat_error; + + if (!path) + return git__throw(GIT_ENOTFOUND, "No path given to gitfo_isfile"); + + stat_error = gitfo_stat(path, &st); + + if (stat_error < GIT_SUCCESS) + return git__throw(GIT_ENOTFOUND, "%s does not exist", path); + + if (!S_ISREG(st.st_mode)) + return git__throw(GIT_ENOTFOUND, "%s is not a file", path); return GIT_SUCCESS; } @@ -301,8 +334,19 @@ int gitfo_dirent( return GIT_SUCCESS; } +void gitfo_posixify_path(char *path) +{ + #if GIT_PLATFORM_PATH_SEP != '/' + while (*path) { + if (*path == GIT_PLATFORM_PATH_SEP) + *path = '/'; -int retrieve_path_root_offset(const char *path) + path++; + } + #endif +} + +int gitfo_retrieve_path_root_offset(const char *path) { int offset = 0; @@ -320,7 +364,6 @@ int retrieve_path_root_offset(const char *path) return -1; /* Not a real error. Rather a signal than the path is not rooted */ } - int gitfo_mkdir_recurs(const char *path, int mode) { int error, root_path_offset; @@ -333,7 +376,7 @@ int gitfo_mkdir_recurs(const char *path, int mode) error = GIT_SUCCESS; pp = path_copy; - root_path_offset = retrieve_path_root_offset(pp); + root_path_offset = gitfo_retrieve_path_root_offset(pp); if (root_path_offset > 0) pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */ @@ -367,7 +410,7 @@ static int retrieve_previous_path_component_start(const char *path) { int offset, len, root_offset, start = 0; - root_offset = retrieve_path_root_offset(path); + root_offset = gitfo_retrieve_path_root_offset(path); if (root_offset > -1) start += root_offset; @@ -392,7 +435,7 @@ static int retrieve_previous_path_component_start(const char *path) return offset; } -int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) +int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path, const char *base_path) { int len = 0, segment_len, only_dots, root_path_offset, error = GIT_SUCCESS; char *current; @@ -402,11 +445,20 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) buffer_end = path + strlen(path); buffer_out_start = buffer_out; - root_path_offset = retrieve_path_root_offset(path); + root_path_offset = gitfo_retrieve_path_root_offset(path); if (root_path_offset < 0) { - error = gitfo_getcwd(buffer_out, size); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ + if (base_path == NULL) { + error = gitfo_getcwd(buffer_out, size); + if (error < GIT_SUCCESS) + return error; /* The callee already takes care of setting the correct error message. */ + } else { + if (size < (strlen(base_path) + 1) * sizeof(char)) + return git__throw(GIT_EOVERFLOW, "Failed to prettify dir path: the base path is too long for the buffer."); + + strcpy(buffer_out, base_path); + gitfo_posixify_path(buffer_out); + git__joinpath(buffer_out, buffer_out, ""); + } len = strlen(buffer_out); buffer_out += len; @@ -470,9 +522,9 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) return GIT_SUCCESS; } -int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path) +int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path, const char *base_path) { - int error, path_len, i; + int error, path_len, i, root_offset; const char* pattern = "/.."; path_len = strlen(path); @@ -487,12 +539,13 @@ int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path) return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); } - error = gitfo_prettify_dir_path(buffer_out, size, path); + error = gitfo_prettify_dir_path(buffer_out, size, path, base_path); if (error < GIT_SUCCESS) return error; /* The callee already takes care of setting the correct error message. */ path_len = strlen(buffer_out); - if (path_len < 2) /* TODO: Fixme. We should also take of detecting Windows rooted path (probably through usage of retrieve_path_root_offset) */ + root_offset = gitfo_retrieve_path_root_offset(buffer_out) + 1; + if (path_len == root_offset) return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); /* Remove the trailing slash */ @@ -519,16 +572,6 @@ int gitfo_cmp_path(const char *name1, int len1, int isdir1, return 0; } -static void posixify_path(char *path) -{ - while (*path) { - if (*path == '\\') - *path = '/'; - - path++; - } -} - int gitfo_getcwd(char *buffer_out, size_t size) { char *cwd_buffer; @@ -544,7 +587,7 @@ int gitfo_getcwd(char *buffer_out, size_t size) if (cwd_buffer == NULL) return git__throw(GIT_EOSERR, "Failed to retrieve current working directory"); - posixify_path(buffer_out); + gitfo_posixify_path(buffer_out); git__joinpath(buffer_out, buffer_out, ""); //Ensure the path ends with a trailing slash diff --git a/src/fileops.h b/src/fileops.h index d0381b675..4a86e1c63 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -12,6 +12,14 @@ #include #include +#define GIT_PATH_LIST_SEPARATOR ':' + +#ifdef GIT_WIN32 +#define GIT_PLATFORM_PATH_SEP '\\' +#else +#define GIT_PLATFORM_PATH_SEP '/' +#endif + #ifdef GIT_WIN32 GIT_INLINE(int) link(const char *GIT_UNUSED(old), const char *GIT_UNUSED(new)) { @@ -57,8 +65,11 @@ extern int gitfo_exists(const char *path); extern int gitfo_open(const char *path, int flags); extern int gitfo_creat(const char *path, int mode); extern int gitfo_creat_force(const char *path, int mode); +extern int gitfo_creat_locked(const char *path, int mode); +extern int gitfo_creat_locked_force(const char *path, int mode); extern int gitfo_mktemp(char *path_out, const char *filename); extern int gitfo_isdir(const char *path); +extern int gitfo_isfile(const char *path); extern int gitfo_mkdir_recurs(const char *path, int mode); extern int gitfo_mkdir_2file(const char *path); #define gitfo_close(fd) close(fd) @@ -162,7 +173,7 @@ extern int gitfo_getcwd(char *buffer_out, size_t size); * - GIT_SUCCESS on success; * - GIT_ERROR when the input path is invalid or escapes the current directory. */ -int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path); +int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path, const char *base_path); /** * Clean up a provided absolute or relative file path. @@ -185,7 +196,10 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path); * - GIT_SUCCESS on success; * - GIT_ERROR when the input path is invalid or escapes the current directory. */ -int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path); +int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path, const char *base_path); + +void gitfo_posixify_path(char *path); + +int gitfo_retrieve_path_root_offset(const char *path); -int retrieve_path_root_offset(const char *path); #endif /* INCLUDE_fileops_h__ */ diff --git a/src/repository.c b/src/repository.c index 32ca8dd79..0b67d144c 100644 --- a/src/repository.c +++ b/src/repository.c @@ -38,6 +38,9 @@ #define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" #define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" +#define GIT_FILE_CONTENT_PREFIX "gitdir: " +#define GIT_FILE_CONTENT_PREFIX_LENGTH 8 + #define GIT_BRANCH_MASTER "master" typedef struct { @@ -65,7 +68,7 @@ static int assign_repository_dirs( if (git_dir == NULL) return git__throw(GIT_ENOTFOUND, "Failed to open repository. Git dir not found"); - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_dir); + error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_dir, NULL); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to open repository"); @@ -78,7 +81,7 @@ static int assign_repository_dirs( if (git_object_directory == NULL) git__joinpath(path_aux, repo->path_repository, GIT_OBJECTS_DIR); else { - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_object_directory); + error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_object_directory, NULL); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to open repository"); } @@ -92,7 +95,7 @@ static int assign_repository_dirs( if (git_work_tree == NULL) repo->is_bare = 1; else { - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_work_tree); + error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_work_tree, NULL); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to open repository"); @@ -105,7 +108,7 @@ static int assign_repository_dirs( if (git_index_file == NULL) git__joinpath(path_aux, repo->path_repository, GIT_INDEX_FILE); else { - error = gitfo_prettify_file_path(path_aux, sizeof(path_aux), git_index_file); + error = gitfo_prettify_file_path(path_aux, sizeof(path_aux), git_index_file, NULL); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to open repository"); } @@ -268,6 +271,21 @@ cleanup: return git__rethrow(error, "Failed to open repository"); } +static int discover_repository_dirs(git_repository *repo, const char *path) +{ + int error; + + error = guess_repository_dirs(repo, path); + if (error < GIT_SUCCESS) + return error; + + error = check_repository_dirs(repo); + if (error < GIT_SUCCESS) + return error; + + return GIT_SUCCESS; +} + int git_repository_open(git_repository **repo_out, const char *path) { git_repository *repo; @@ -279,11 +297,7 @@ int git_repository_open(git_repository **repo_out, const char *path) if (repo == NULL) return GIT_ENOMEM; - error = guess_repository_dirs(repo, path); - if (error < GIT_SUCCESS) - goto cleanup; - - error = check_repository_dirs(repo); + error = discover_repository_dirs(repo, path); if (error < GIT_SUCCESS) goto cleanup; @@ -299,6 +313,129 @@ cleanup: return git__rethrow(error, "Failed to open repository"); } +static int abspath(char *buffer_out, size_t size, const char *path) +{ + assert(buffer_out && size >= GIT_PATH_MAX); + + #ifdef GIT_WIN32 + if (_fullpath(buffer_out, path, size) == NULL) + return git__throw(GIT_EOSERR, "Failed to retrieve real path: %s causing errors", buffer_out); + #else + if (realpath(path, buffer_out) == NULL) + return git__throw(GIT_EOSERR, "Failed to retrieve real path: %s causing errors", buffer_out); + #endif + + gitfo_posixify_path(buffer_out); + + return GIT_SUCCESS; +} + +static dev_t retrieve_device(dev_t *device_out, const char *path) +{ + struct stat path_info; + + assert(device_out); + + if (gitfo_stat(path, &path_info)) + return git__throw(GIT_EOSERR, "Failed to get file informations: %s", path); + + *device_out = path_info.st_dev; + + return GIT_SUCCESS; +} + +static int retrieve_ceiling_directories_offset(const char *path, const char *ceiling_directories) +{ + char buf[GIT_PATH_MAX + 1]; + char buf2[GIT_PATH_MAX + 1]; + const char *ceil, *sep; + int len, max_len = -1; + int min_len; + + assert(path); + + min_len = gitfo_retrieve_path_root_offset(path) + 1; + + if (ceiling_directories == NULL || min_len == 0) + return min_len; + + for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { + for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); + len = sep - ceil; + + if (len == 0 || len > GIT_PATH_MAX || gitfo_retrieve_path_root_offset(ceil) == -1) + continue; + + strncpy(buf, ceil, len); + buf[len] = '\0'; + + gitfo_posixify_path(buf); + if (gitfo_prettify_dir_path(buf2, sizeof(buf2), buf, NULL) < GIT_SUCCESS) + continue; + + len = strlen(buf2); + if (len > 0 && buf2[len-1] == '/') + buf[--len] = '\0'; + + if (!strncmp(path, buf2, len) && + path[len] == '/' && + len > max_len) + { + max_len = len; + } + } + + return max_len <= min_len ? min_len : max_len; +} + +static int read_gitfile(char *path_out, size_t size, const char *file_path, const char *base_path) +{ + gitfo_buf file; + int error, end_offset; + char *data; + + assert(file_path && path_out && size > 0); + + error = gitfo_read_file(&file, file_path); + + if (error < GIT_SUCCESS) + return error; + + data = (char*)(file.data); + + if (git__prefixcmp(data, GIT_FILE_CONTENT_PREFIX)) { + gitfo_free_buf(&file); + return git__throw(GIT_ENOTFOUND, "Invalid gitfile format `%s`", file_path); + } + + end_offset = strlen(data) - 1; + + for (;data[end_offset] != '\r' && data[end_offset] != '\n'; --end_offset); + data[end_offset] = '\0'; + + if (GIT_FILE_CONTENT_PREFIX_LENGTH == end_offset) { + gitfo_free_buf(&file); + return git__throw(GIT_ENOTFOUND, "No path in git file `%s`", file_path); + } + + error = gitfo_prettify_dir_path(path_out, size, data + GIT_FILE_CONTENT_PREFIX_LENGTH, base_path); + gitfo_free_buf(&file); + + return error; +} + +static void git_repository__free_dirs(git_repository *repo) +{ + free(repo->path_workdir); + repo->path_workdir = NULL; + free(repo->path_index); + repo->path_index = NULL; + free(repo->path_repository); + repo->path_repository = NULL; + free(repo->path_odb); + repo->path_odb = NULL; +} + void git_repository_free(git_repository *repo) { if (repo == NULL) @@ -306,11 +443,7 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); - - free(repo->path_workdir); - free(repo->path_index); - free(repo->path_repository); - free(repo->path_odb); + git_repository__free_dirs(repo); if (repo->db != NULL) git_odb_close(repo->db); @@ -318,6 +451,119 @@ void git_repository_free(git_repository *repo) free(repo); } +int git_repository_discover(char *repository_path, size_t size, const char *start_path, int across_fs, const char *ceiling_dirs) +{ + git_repository repo; + int error, ceiling_offset; + char bare_path[GIT_PATH_MAX]; + char normal_path[GIT_PATH_MAX]; + char *found_path; + dev_t current_device; + + assert(start_path && repository_path); + memset(&repo, 0x0, sizeof(git_repository)); + + error = abspath(bare_path, sizeof(bare_path), start_path); + + if (error < GIT_SUCCESS) + goto cleanup; + + if (!across_fs) { + error = retrieve_device(¤t_device, bare_path); + + if (error < GIT_SUCCESS) + goto cleanup; + } + + ceiling_offset = retrieve_ceiling_directories_offset(bare_path, ceiling_dirs); + git__joinpath(normal_path, bare_path, DOT_GIT); + + while(1){ + //look for .git file + if (gitfo_isfile(normal_path) == GIT_SUCCESS) { + error = read_gitfile(repository_path, size, normal_path, bare_path); + + if (error < GIT_SUCCESS) { + git__rethrow(error, "Unable to read git file `%s`", normal_path); + goto cleanup; + } + + error = discover_repository_dirs(&repo, repository_path); + if (error < GIT_SUCCESS) + goto cleanup; + + git_repository__free_dirs(&repo); + + return GIT_SUCCESS; + } + + //look for .git repository + error = discover_repository_dirs(&repo, normal_path); + if (error < GIT_SUCCESS && error != GIT_ENOTAREPO) + goto cleanup; + + if (error == GIT_SUCCESS) { + found_path = normal_path; + break; + } + + git_repository__free_dirs(&repo); + + //look for bare repository in current directory + error = discover_repository_dirs(&repo, bare_path); + if (error < GIT_SUCCESS && error != GIT_ENOTAREPO) + goto cleanup; + + if (error == GIT_SUCCESS) { + found_path = bare_path; + break; + } + + git_repository__free_dirs(&repo); + + //nothing has been found, lets try the parent directory + if (bare_path[ceiling_offset] == '\0') { + error = git__throw(GIT_ENOTAREPO,"Not a git repository (or any of the parent directories): %s", start_path); + goto cleanup; + } + + if (git__dirname_r(normal_path, sizeof(normal_path), bare_path) < GIT_SUCCESS) + goto cleanup; + + if (!across_fs) { + dev_t new_device; + error = retrieve_device(&new_device, normal_path); + + if (error < GIT_SUCCESS) + goto cleanup; + + if (current_device != new_device) { + error = git__throw(GIT_ENOTAREPO,"Not a git repository (or any parent up to mount parent %s)\n" + "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM_ENVIRONMENT not set).", bare_path); + goto cleanup; + } + current_device = new_device; + } + + strcpy(bare_path, normal_path); + git__joinpath(normal_path, bare_path, DOT_GIT); + } + + if (size < (strlen(found_path) + 1) * sizeof(char)) { + error = git__throw(GIT_EOVERFLOW, "The repository buffer is not long enough to handle the repository path `%s`", found_path); + goto cleanup; + } + + strcpy(repository_path, found_path); + git_repository__free_dirs(&repo); + + return GIT_SUCCESS; + + cleanup: + git_repository__free_dirs(&repo); + return git__rethrow(error, "Failed to discover repository"); +} + git_odb *git_repository_database(git_repository *repo) { assert(repo); @@ -390,7 +636,7 @@ static int repo_init_find_dir(repo_init *results, const char* path) char temp_path[GIT_PATH_MAX]; int error = GIT_SUCCESS; - error = gitfo_prettify_dir_path(temp_path, sizeof(temp_path), path); + error = gitfo_prettify_dir_path(temp_path, sizeof(temp_path), path, NULL); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to find directory to initialize repository"); diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index f4a3c42cc..6ac00c5aa 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -97,7 +97,7 @@ int sha1_entry_pos(const void *table, unsigned lo, unsigned hi, unsigned nr, const unsigned char *key) { - const unsigned char *base = table; + const unsigned char *base = (const unsigned char*)table; const unsigned char *hi_key, *lo_key; unsigned ofs_0; @@ -192,5 +192,5 @@ int sha1_entry_pos(const void *table, lo_key = mi_key + elem_size; } } while (lo < hi); - return -lo-1; + return -((int)lo)-1; } diff --git a/tests/t00-core.c b/tests/t00-core.c index ab9a683a7..9498178cf 100644 --- a/tests/t00-core.c +++ b/tests/t00-core.c @@ -151,7 +151,7 @@ BEGIN_TEST(path2, "get the latest component in a path") #undef TOPDIR_TEST END_TEST -typedef int (normalize_path)(char *, size_t, const char *); +typedef int (normalize_path)(char *, size_t, const char *, const char *); /* Assert flags */ #define CWD_AS_PREFIX 1 @@ -168,7 +168,7 @@ static int ensure_normalized(const char *input_path, const char *expected_path, if (error < GIT_SUCCESS) return error; - error = normalizer(buffer_out, sizeof(buffer_out), input_path); + error = normalizer(buffer_out, sizeof(buffer_out), input_path, NULL); if (error < GIT_SUCCESS) return error; @@ -417,7 +417,7 @@ BEGIN_TEST(path7, "prevent a path which escapes the root directory from being pr for (i = 0; i < number_to_escape + 1; i++) git__joinpath(current_workdir, current_workdir, "../"); - must_fail(gitfo_prettify_dir_path(prettified, sizeof(prettified), current_workdir)); + must_fail(gitfo_prettify_dir_path(prettified, sizeof(prettified), current_workdir, NULL)); END_TEST typedef struct name_data {