diff options
Diffstat (limited to 'src/repository.c')
-rw-r--r-- | src/repository.c | 1033 |
1 files changed, 529 insertions, 504 deletions
diff --git a/src/repository.c b/src/repository.c index 536522a9b..6ce3a560f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1,10 +1,11 @@ /* - * Copyright (C) 2009-2011 the libgit2 contributors + * Copyright (C) 2009-2012 the libgit2 contributors * * 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 <stdarg.h> +#include <ctype.h> #include "git2/object.h" @@ -20,12 +21,11 @@ #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 "gitdir:" #define GIT_BRANCH_MASTER "master" -#define GIT_CONFIG_CORE_REPOSITORYFORMATVERSION "core.repositoryformatversion" -#define GIT_REPOSITORYFORMATVERSION 0 +#define GIT_REPO_VERSION 0 static void drop_odb(git_repository *repo) { @@ -43,6 +43,8 @@ static void drop_config(git_repository *repo) git_config_free(repo->_config); repo->_config = NULL; } + + git_repository__cvar_cache_clear(repo); } static void drop_index(git_repository *repo) @@ -62,6 +64,7 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); git_attr_cache_flush(repo); + git_submodule_config_free(repo); git__free(repo->path_repository); git__free(repo->workdir); @@ -78,218 +81,423 @@ void git_repository_free(git_repository *repo) * * Open a repository object from its path */ -static int quickcheck_repository_dir(git_buf *repository_path) +static bool valid_repository_path(git_buf *repository_path) { /* Check OBJECTS_DIR first, since it will generate the longest path name */ - if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR, 0) < 0) - return GIT_ERROR; + if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) + return false; /* Ensure HEAD file exists */ - if (git_path_contains_file(repository_path, GIT_HEAD_FILE, 0) < 0) - return GIT_ERROR; + if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) + return false; - if (git_path_contains_dir(repository_path, GIT_REFS_DIR, 0) < 0) - return GIT_ERROR; + if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) + return false; - return GIT_SUCCESS; + return true; } - static git_repository *repository_alloc(void) { - int error; - git_repository *repo = git__malloc(sizeof(git_repository)); if (!repo) return NULL; memset(repo, 0x0, sizeof(git_repository)); - error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); - if (error < GIT_SUCCESS) { + if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) { git__free(repo); return NULL; } + /* set all the entries in the cvar cache to `unset` */ + git_repository__cvar_cache_clear(repo); + return repo; } static int load_config_data(git_repository *repo) { - int error, is_bare; + int is_bare; + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + if (git_config_get_bool(&is_bare, config, "core.bare") < 0) + return -1; /* FIXME: We assume that a missing core.bare + variable is an error. Is this right? */ + + repo->is_bare = is_bare; + return 0; +} + +static int load_workdir(git_repository *repo, git_buf *parent_path) +{ + int error; git_config *config; + const char *worktree; + git_buf worktree_buf = GIT_BUF_INIT; + + if (repo->is_bare) + return 0; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; - error = git_repository_config__weakptr(&config, repo); - if (error < GIT_SUCCESS) + error = git_config_get_string(&worktree, config, "core.worktree"); + if (!error && worktree != NULL) + repo->workdir = git__strdup(worktree); + else if (error != GIT_ENOTFOUND) return error; + else { + giterr_clear(); + + if (parent_path && git_path_isdir(parent_path->ptr)) + repo->workdir = git_buf_detach(parent_path); + else { + git_path_dirname_r(&worktree_buf, repo->path_repository); + git_path_to_dir(&worktree_buf); + repo->workdir = git_buf_detach(&worktree_buf); + } + } + + GITERR_CHECK_ALLOC(repo->workdir); + + return 0; +} + +/* + * This function returns furthest offset into path where a ceiling dir + * is found, so we can stop processing the path at that point. + * + * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on + * the stack could remove directories name limits, but at the cost of doing + * repeated malloc/frees inside the loop below, so let's not do it now. + */ +static int find_ceiling_dir_offset( + const char *path, + const char *ceiling_directories) +{ + char buf[GIT_PATH_MAX + 1]; + char buf2[GIT_PATH_MAX + 1]; + const char *ceil, *sep; + size_t len, max_len = 0, min_len; + + assert(path); + + min_len = (size_t)(git_path_root(path) + 1); + + if (ceiling_directories == NULL || min_len == 0) + return (int)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 >= sizeof(buf) || git_path_root(ceil) == -1) + continue; + + strncpy(buf, ceil, len); + buf[len] = '\0'; - error = git_config_get_bool(config, "core.bare", &is_bare); - if (error == GIT_SUCCESS) - repo->is_bare = is_bare; + if (p_realpath(buf, buf2) == NULL) + continue; + + len = strlen(buf2); + if (len > 0 && buf2[len-1] == '/') + buf[--len] = '\0'; - /* TODO: what else can we load/cache here? */ + if (!strncmp(path, buf2, len) && + path[len] == '/' && + len > max_len) + { + max_len = len; + } + } - return GIT_SUCCESS; + return (int)(max_len <= min_len ? min_len : max_len); } -static int load_workdir(git_repository *repo) +/* + * Read the contents of `file_path` and set `path_out` to the repo dir that + * it points to. Before calling, set `path_out` to the base directory that + * should be used if the contents of `file_path` are a relative path. + */ +static int read_gitfile(git_buf *path_out, const char *file_path) { - int error; - git_buf workdir_buf = GIT_BUF_INIT; + int error = 0; + git_buf file = GIT_BUF_INIT; + size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); - if (repo->is_bare) - return GIT_SUCCESS; + assert(path_out && file_path); - git_path_dirname_r(&workdir_buf, repo->path_repository); - git_path_to_dir(&workdir_buf); + if (git_futils_readbuffer(&file, file_path) < 0) + return -1; - if ((error = git_buf_lasterror(&workdir_buf)) == GIT_SUCCESS) - repo->workdir = git_buf_detach(&workdir_buf); + git_buf_rtrim(&file); - git_buf_free(&workdir_buf); + if (file.size <= prefix_len || + memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) + { + giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path); + error = -1; + } + else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) { + const char *gitlink = ((const char *)file.ptr) + prefix_len; + while (*gitlink && git__isspace(*gitlink)) gitlink++; + error = git_path_prettify_dir(path_out, gitlink, path_out->ptr); + } + git_buf_free(&file); return error; } -int git_repository_open(git_repository **repo_out, const char *path) +static int find_repo( + git_buf *repo_path, + git_buf *parent_path, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) { - int error = GIT_SUCCESS; - git_buf path_buf = GIT_BUF_INIT; - git_repository *repo = NULL; + int error; + git_buf path = GIT_BUF_INIT; + struct stat st; + dev_t initial_device = 0; + bool try_with_dot_git = false; + int ceiling_offset; + + git_buf_free(repo_path); + + if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0) + return error; + + ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); + + if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) + return error; + + while (!error && !git_buf_len(repo_path)) { + if (p_stat(path.ptr, &st) == 0) { + /* check that we have not crossed device boundaries */ + if (initial_device == 0) + initial_device = st.st_dev; + else if (st.st_dev != initial_device && + (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0) + break; + + if (S_ISDIR(st.st_mode)) { + if (valid_repository_path(&path)) { + git_path_to_dir(&path); + git_buf_set(repo_path, path.ptr, path.size); + break; + } + } + else if (S_ISREG(st.st_mode)) { + git_buf repo_link = GIT_BUF_INIT; - error = git_path_prettify_dir(&path_buf, path, NULL); - if (error < GIT_SUCCESS) - goto cleanup; - - /** - * Check if the path we've been given is actually the path - * of the working dir, by testing if it contains a `.git` - * folder inside of it. - */ - git_path_contains_dir(&path_buf, GIT_DIR, 1); /* append on success */ - /* ignore error, since it just means `path/.git` doesn't exist */ - - if (quickcheck_repository_dir(&path_buf) < GIT_SUCCESS) { - error = git__throw(GIT_ENOTAREPO, - "The given path (%s) is not a valid Git repository", git_buf_cstr(&path_buf)); - goto cleanup; + if (!(error = read_gitfile(&repo_link, path.ptr))) { + if (valid_repository_path(&repo_link)) + git_buf_swap(repo_path, &repo_link); + + git_buf_free(&repo_link); + break; + } + git_buf_free(&repo_link); + } + } + + /* move up one directory level */ + if (git_path_dirname_r(&path, path.ptr) < 0) { + error = -1; + break; + } + + if (try_with_dot_git) { + /* if we tried original dir with and without .git AND either hit + * directory ceiling or NO_SEARCH was requested, then be done. + */ + if (path.ptr[ceiling_offset] == '\0' || + (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0) + break; + /* otherwise look first for .git item */ + error = git_buf_joinpath(&path, path.ptr, DOT_GIT); + } + try_with_dot_git = !try_with_dot_git; } - repo = repository_alloc(); - if (repo == NULL) { - error = GIT_ENOMEM; - goto cleanup; + if (!error && parent_path != NULL) { + if (!git_buf_len(repo_path)) + git_buf_clear(parent_path); + else { + git_path_dirname_r(parent_path, path.ptr); + git_path_to_dir(parent_path); + } + if (git_buf_oom(parent_path)) + return -1; } - repo->path_repository = git_buf_detach(&path_buf); - if (repo->path_repository == NULL) { - error = GIT_ENOMEM; - goto cleanup; + git_buf_free(&path); + + if (!git_buf_len(repo_path) && !error) { + giterr_set(GITERR_REPOSITORY, + "Could not find repository from '%s'", start_path); + error = GIT_ENOTFOUND; } - error = load_config_data(repo); - if (error < GIT_SUCCESS) - goto cleanup; + return error; +} - error = load_workdir(repo); - if (error < GIT_SUCCESS) - goto cleanup; +int git_repository_open_ext( + git_repository **repo_ptr, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) +{ + int error; + git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; + git_repository *repo; - *repo_out = repo; - return GIT_SUCCESS; + *repo_ptr = NULL; - cleanup: - git_repository_free(repo); - git_buf_free(&path_buf); - return error; + if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0) + return error; + + repo = repository_alloc(); + GITERR_CHECK_ALLOC(repo); + + repo->path_repository = git_buf_detach(&path); + GITERR_CHECK_ALLOC(repo->path_repository); + + if ((error = load_config_data(repo)) < 0 || + (error = load_workdir(repo, &parent)) < 0) + { + git_repository_free(repo); + return error; + } + + git_buf_free(&parent); + *repo_ptr = repo; + return 0; +} + +int git_repository_open(git_repository **repo_out, const char *path) +{ + return git_repository_open_ext( + repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); +} + +int git_repository_discover( + char *repository_path, + size_t size, + const char *start_path, + int across_fs, + const char *ceiling_dirs) +{ + git_buf path = GIT_BUF_INIT; + uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + int error; + + assert(start_path && repository_path && size > 0); + + *repository_path = '\0'; + + if ((error = find_repo(&path, NULL, start_path, flags, ceiling_dirs)) < 0) + return error != GIT_ENOTFOUND ? -1 : error; + + if (size < (size_t)(path.size + 1)) { + giterr_set(GITERR_REPOSITORY, + "The given buffer is too long to store the discovered path"); + git_buf_free(&path); + return -1; + } + + /* success: we discovered a repository */ + git_buf_copy_cstr(repository_path, size, &path); + git_buf_free(&path); + return 0; } static int load_config( - git_config **out, - git_repository *repo, - const char *global_config_path, - const char *system_config_path) + git_config **out, + git_repository *repo, + const char *global_config_path, + const char *system_config_path) { git_buf config_path = GIT_BUF_INIT; - int error; git_config *cfg = NULL; assert(repo && out); - error = git_config_new(&cfg); - if (error < GIT_SUCCESS) - return error; + if (git_config_new(&cfg) < 0) + return -1; + + if (git_buf_joinpath( + &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) + goto on_error; - error = git_buf_joinpath(&config_path, repo->path_repository, - GIT_CONFIG_FILENAME_INREPO); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0) + goto on_error; - error = git_config_add_file_ondisk(cfg, config_path.ptr, 3); - git_buf_free(&config_path); /* done with config_path now */ - if (error < GIT_SUCCESS) - goto cleanup; + git_buf_free(&config_path); if (global_config_path != NULL) { - error = git_config_add_file_ondisk(cfg, global_config_path, 2); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0) + goto on_error; } if (system_config_path != NULL) { - error = git_config_add_file_ondisk(cfg, system_config_path, 1); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0) + goto on_error; } *out = cfg; - return GIT_SUCCESS; + return 0; -cleanup: +on_error: + git_buf_free(&config_path); git_config_free(cfg); *out = NULL; - return error; + return -1; } int git_repository_config__weakptr(git_config **out, git_repository *repo) { if (repo->_config == NULL) { - int error; git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; + int res; const char *global_config_path = NULL; const char *system_config_path = NULL; - if (git_config_find_global_r(&global_buf) == GIT_SUCCESS) + if (git_config_find_global_r(&global_buf) == 0) global_config_path = global_buf.ptr; - if (git_config_find_system_r(&system_buf) == GIT_SUCCESS) + if (git_config_find_system_r(&system_buf) == 0) system_config_path = system_buf.ptr; - error = load_config(&repo->_config, repo, global_config_path, system_config_path); + res = load_config(&repo->_config, repo, global_config_path, system_config_path); git_buf_free(&global_buf); git_buf_free(&system_buf); - if (error < GIT_SUCCESS) - return error; + if (res < 0) + return -1; GIT_REFCOUNT_OWN(repo->_config, repo); } *out = repo->_config; - return GIT_SUCCESS; + return 0; } int git_repository_config(git_config **out, git_repository *repo) { - int error = git_repository_config__weakptr(out, repo); - - if (error == GIT_SUCCESS) { - GIT_REFCOUNT_INC(*out); - } + if (git_repository_config__weakptr(out, repo) < 0) + return -1; - return error; + GIT_REFCOUNT_INC(*out); + return 0; } void git_repository_set_config(git_repository *repo, git_config *config) @@ -307,34 +515,32 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) assert(repo && out); if (repo->_odb == NULL) { - int error; git_buf odb_path = GIT_BUF_INIT; + int res; - error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0) + return -1; - error = git_odb_open(&repo->_odb, odb_path.ptr); + res = git_odb_open(&repo->_odb, odb_path.ptr); git_buf_free(&odb_path); /* done with path */ - if (error < GIT_SUCCESS) - return error; + + if (res < 0) + return -1; GIT_REFCOUNT_OWN(repo->_odb, repo); } *out = repo->_odb; - return GIT_SUCCESS; + return 0; } int git_repository_odb(git_odb **out, git_repository *repo) { - int error = git_repository_odb__weakptr(out, repo); + if (git_repository_odb__weakptr(out, repo) < 0) + return -1; - if (error == GIT_SUCCESS) { - GIT_REFCOUNT_INC(*out); - } - - return error; + GIT_REFCOUNT_INC(*out); + return 0; } void git_repository_set_odb(git_repository *repo, git_odb *odb) @@ -345,6 +551,7 @@ void git_repository_set_odb(git_repository *repo, git_odb *odb) repo->_odb = odb; GIT_REFCOUNT_OWN(repo->_odb, repo); + GIT_REFCOUNT_INC(odb); } int git_repository_index__weakptr(git_index **out, git_repository *repo) @@ -352,34 +559,32 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) assert(out && repo); if (repo->_index == NULL) { - int error; + int res; git_buf index_path = GIT_BUF_INIT; - error = git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0) + return -1; - error = git_index_open(&repo->_index, index_path.ptr); + res = git_index_open(&repo->_index, index_path.ptr); git_buf_free(&index_path); /* done with path */ - if (error < GIT_SUCCESS) - return error; + + if (res < 0) + return -1; GIT_REFCOUNT_OWN(repo->_index, repo); } *out = repo->_index; - return GIT_SUCCESS; + return 0; } int git_repository_index(git_index **out, git_repository *repo) { - int error = git_repository_index__weakptr(out, repo); - - if (error == GIT_SUCCESS) { - GIT_REFCOUNT_INC(*out); - } + if (git_repository_index__weakptr(out, repo) < 0) + return -1; - return error; + GIT_REFCOUNT_INC(*out); + return 0; } void git_repository_set_index(git_repository *repo, git_index *index) @@ -390,476 +595,269 @@ void git_repository_set_index(git_repository *repo, git_index *index) repo->_index = index; GIT_REFCOUNT_OWN(repo->_index, repo); -} - - -static int retrieve_device(dev_t *device_out, const char *path) -{ - struct stat path_info; - - assert(device_out); - - if (p_lstat(path, &path_info)) - return git__throw(GIT_EOSERR, "Failed to get file informations: %s", path); - - *device_out = path_info.st_dev; - - return GIT_SUCCESS; -} - -/* - * This function returns furthest offset into path where a ceiling dir - * is found, so we can stop processing the path at that point. - * - * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on - * the stack could remove directories name limits, but at the cost of doing - * repeated malloc/frees inside the loop below, so let's not do it now. - */ -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 = git_path_root(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 >= (int)sizeof(buf) || git_path_root(ceil) == -1) - continue; - - strncpy(buf, ceil, len); - buf[len] = '\0'; - - if (p_realpath(buf, buf2) == NULL) - 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; -} - -/* - * Read the contents of `file_path` and set `path_out` to the repo dir that - * it points to. Before calling, set `path_out` to the base directory that - * should be used if the contents of `file_path` are a relative path. - */ -static int read_gitfile(git_buf *path_out, const char *file_path, const char *base_path) -{ - git_fbuffer file; - int error; - - assert(path_out && file_path); - - error = git_futils_readbuffer(&file, file_path); - if (error < GIT_SUCCESS) - return error; - - if (git__prefixcmp((char *)file.data, GIT_FILE_CONTENT_PREFIX)) { - git_futils_freebuffer(&file); - return git__throw(GIT_ENOTFOUND, "Invalid gitfile format `%s`", file_path); - } - - git_futils_fbuffer_rtrim(&file); - - if (strlen(GIT_FILE_CONTENT_PREFIX) == file.len) { - git_futils_freebuffer(&file); - return git__throw(GIT_ENOTFOUND, "No path in git file `%s`", file_path); - } - - error = git_path_prettify_dir(path_out, - ((char *)file.data) + strlen(GIT_FILE_CONTENT_PREFIX), base_path); - - git_futils_freebuffer(&file); - - if (error == GIT_SUCCESS && git_path_exists(path_out->ptr) == 0) - return GIT_SUCCESS; - - return git__throw(GIT_EOBJCORRUPTED, "The `.git` file points to a nonexistent path"); -} - -int git_repository_discover( - char *repository_path, - size_t size, - const char *start_path, - int across_fs, - const char *ceiling_dirs) -{ - int error, ceiling_offset; - git_buf bare_path = GIT_BUF_INIT; - git_buf normal_path = GIT_BUF_INIT; - git_buf *found_path = NULL; - dev_t current_device = 0; - - assert(start_path && repository_path); - - *repository_path = '\0'; - - error = git_path_prettify_dir(&bare_path, start_path, NULL); - if (error < GIT_SUCCESS) - goto cleanup; - - if (!across_fs) { - error = retrieve_device(¤t_device, bare_path.ptr); - if (error < GIT_SUCCESS) - goto cleanup; - } - - ceiling_offset = retrieve_ceiling_directories_offset(bare_path.ptr, ceiling_dirs); - - while(1) { - error = git_buf_joinpath(&normal_path, bare_path.ptr, DOT_GIT); - if (error < GIT_SUCCESS) - break; - - /** - * If the `.git` file is regular instead of - * a directory, it should contain the path of the actual git repository - */ - if (git_path_isfile(normal_path.ptr) == GIT_SUCCESS) { - git_buf gitfile_path = GIT_BUF_INIT; - - error = read_gitfile(&gitfile_path, normal_path.ptr, bare_path.ptr); - if (error < GIT_SUCCESS) - git__rethrow(error, "Unable to read git file `%s`", normal_path.ptr); - else if ((error = quickcheck_repository_dir(&gitfile_path)) < GIT_SUCCESS) - git__throw(GIT_ENOTFOUND, - "The `.git` file found at '%s' points " - "to a nonexistent git folder", normal_path.ptr); - else { - git_buf_swap(&normal_path, &gitfile_path); - found_path = &normal_path; - } - - git_buf_free(&gitfile_path); - break; - } - - /** - * If the `.git` file is a folder, we check inside of it - */ - if (git_path_isdir(normal_path.ptr) == GIT_SUCCESS) { - error = quickcheck_repository_dir(&normal_path); - if (error == GIT_SUCCESS) { - found_path = &normal_path; - break; - } - } - - /** - * Otherwise, the repository may be bare, let's check - * the root anyway - */ - error = quickcheck_repository_dir(&bare_path); - if (error == GIT_SUCCESS) { - found_path = &bare_path; - break; - } - - /** - * If we didn't find it, walk up the tree - */ - error = git_path_dirname_r(&normal_path, bare_path.ptr); - if (error < GIT_SUCCESS) { - git__rethrow(GIT_EOSERR, "Failed to dirname '%s'", bare_path.ptr); - break; - } - - git_buf_swap(&bare_path, &normal_path); - - if (!across_fs) { - dev_t new_device; - error = retrieve_device(&new_device, bare_path.ptr); - - if (error < GIT_SUCCESS || 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.", normal_path.ptr); - break; - } - current_device = new_device; - } - - /* nothing has been found, lets try the parent directory - * but stop if we hit one of the ceiling directories - */ - if (bare_path.ptr[ceiling_offset] == '\0') { - error = git__throw(GIT_ENOTAREPO, - "Not a git repository (or any of the parent directories): %s", start_path); - break; - } - } - - assert(found_path || error != GIT_SUCCESS); - - if (found_path) { - if ((error = git_path_to_dir(found_path)) < GIT_SUCCESS) - git__rethrow(error, "Could not convert git repository to directory"); - else if (size < (size_t)(found_path->size + 1)) - error = git__throw(GIT_ESHORTBUFFER, - "The repository buffer is not long enough to " - "handle the repository path `%s`", found_path->ptr); - else - git_buf_copy_cstr(repository_path, size, found_path); - } - -cleanup: - git_buf_free(&bare_path); - git_buf_free(&normal_path); - return error; + GIT_REFCOUNT_INC(index); } static int check_repositoryformatversion(git_repository *repo) { git_config *config; - int version, error = GIT_SUCCESS; + int version; - if ((error = git_repository_config(&config, repo)) < GIT_SUCCESS) - return git__throw(error, "Failed to open config file."); + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; - error = git_config_get_int32(config, GIT_CONFIG_CORE_REPOSITORYFORMATVERSION, &version); + if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0) + return -1; - if (GIT_REPOSITORYFORMATVERSION < version) - error = git__throw(GIT_ERROR, "Unsupported git repository version (Expected version <= %d, found %d).", GIT_REPOSITORYFORMATVERSION, version); - - git_config_free(config); + if (GIT_REPO_VERSION < version) { + giterr_set(GITERR_REPOSITORY, + "Unsupported repository version %d. Only versions up to %d are supported.", + version, GIT_REPO_VERSION); + return -1; + } - return error; + return 0; } static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare) { - int error; git_repository *repo = NULL; - if ((error = git_repository_open(&repo, repository_path)) < GIT_SUCCESS) - goto error; + GIT_UNUSED(is_bare); + + if (git_repository_open(&repo, repository_path) < 0) + return -1; - if ((error = check_repositoryformatversion(repo)) < GIT_SUCCESS) - goto error; + if (check_repositoryformatversion(repo) < 0) { + git_repository_free(repo); + return -1; + } /* TODO: reinitialize the templates */ *repo_out = repo; - - return GIT_SUCCESS; - -error: - git_repository_free(repo); - - return git__rethrow(error, - "Failed to reinitialize the %srepository at '%s'. ", - is_bare ? "bare " : "", repository_path); + return 0; } static int repo_init_createhead(const char *git_dir) { - int error; git_buf ref_path = GIT_BUF_INIT; git_filebuf ref = GIT_FILEBUF_INIT; - if (!(error = git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) && - !(error = git_filebuf_open(&ref, ref_path.ptr, 0)) && - !(error = git_filebuf_printf(&ref, "ref: refs/heads/master\n"))) - error = git_filebuf_commit(&ref, GIT_REFS_FILE_MODE); + if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || + git_filebuf_open(&ref, ref_path.ptr, 0) < 0 || + git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 || + git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) + return -1; git_buf_free(&ref_path); - return error; + return 0; } static int repo_init_config(const char *git_dir, int is_bare) { git_buf cfg_path = GIT_BUF_INIT; git_config *config = NULL; - int error = GIT_SUCCESS; #define SET_REPO_CONFIG(type, name, val) {\ - error = git_config_set_##type(config, name, val);\ - if (error < GIT_SUCCESS)\ - goto cleanup;\ + if (git_config_set_##type(config, name, val) < 0) { \ + git_buf_free(&cfg_path); \ + git_config_free(config); \ + return -1; } \ } - error = git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; - error = git_config_open_ondisk(&config, cfg_path.ptr); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_open_ondisk(&config, cfg_path.ptr) < 0) { + git_buf_free(&cfg_path); + return -1; + } SET_REPO_CONFIG(bool, "core.bare", is_bare); - SET_REPO_CONFIG(int32, GIT_CONFIG_CORE_REPOSITORYFORMATVERSION, GIT_REPOSITORYFORMATVERSION); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); /* TODO: what other defaults? */ -cleanup: git_buf_free(&cfg_path); git_config_free(config); + return 0; +} + +#define GIT_HOOKS_DIR "hooks/" +#define GIT_HOOKS_DIR_MODE 0755 + +#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" +#define GIT_HOOKS_README_MODE 0755 +#define GIT_HOOKS_README_CONTENT \ +"#!/bin/sh\n"\ +"#\n"\ +"# Place appropriately named executable hook scripts into this directory\n"\ +"# to intercept various actions that git takes. See `git help hooks` for\n"\ +"# more information.\n" + +#define GIT_INFO_DIR "info/" +#define GIT_INFO_DIR_MODE 0755 + +#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" +#define GIT_INFO_EXCLUDE_MODE 0644 +#define GIT_INFO_EXCLUDE_CONTENT \ +"# File patterns to ignore; see `git help ignore` for more information.\n"\ +"# Lines that start with '#' are comments.\n" + +#define GIT_DESC_FILE "description" +#define GIT_DESC_MODE 0644 +#define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n" + +static int repo_write_template( + const char *git_dir, const char *file, mode_t mode, const char *content) +{ + git_buf path = GIT_BUF_INIT; + int fd, error = 0; + + if (git_buf_joinpath(&path, git_dir, file) < 0) + return -1; + + fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode); + + if (fd >= 0) { + error = p_write(fd, content, strlen(content)); + + p_close(fd); + } + else if (errno != EEXIST) + error = fd; + + git_buf_free(&path); + + if (error) + giterr_set(GITERR_OS, + "Failed to initialize repository with template '%s'", file); + return error; } static int repo_init_structure(const char *git_dir, int is_bare) { - int error, i; + int i; struct { const char *dir; mode_t mode; } dirs[] = { { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */ { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */ { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */ { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */ + { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */ + { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */ { NULL, 0 } }; + struct { const char *file; mode_t mode; const char *content; } tmpl[] = { + { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, + { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, + { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, + { NULL, 0, NULL } + }; /* Make the base directory */ - error = git_futils_mkdir_r(git_dir, NULL, is_bare ? - GIT_BARE_DIR_MODE : GIT_DIR_MODE); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure. Could not mkdir"); + if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0) + return -1; /* Hides the ".git" directory */ if (!is_bare) { #ifdef GIT_WIN32 - error = p_hide_directory__w32(git_dir); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure"); + if (p_hide_directory__w32(git_dir) < 0) { + giterr_set(GITERR_REPOSITORY, + "Failed to mark Git repository folder as hidden"); + return -1; + } #endif } /* Make subdirectories as needed */ for (i = 0; dirs[i].dir != NULL; ++i) { - error = git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode); - if (error < GIT_SUCCESS) - return git__rethrow(error, - "Failed to create repository folder `%s`", dirs[i].dir); + if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0) + return -1; } - /* TODO: what's left? templates? */ + /* Make template files as needed */ + for (i = 0; tmpl[i].file != NULL; ++i) { + if (repo_write_template( + git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) + return -1; + } - return error; + return 0; } int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) { - int error = GIT_SUCCESS; - git_repository *repo = NULL; git_buf repository_path = GIT_BUF_INIT; assert(repo_out && path); - error = git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) + return -1; - if (git_path_isdir(repository_path.ptr) == GIT_SUCCESS) { - if (quickcheck_repository_dir(&repository_path) == GIT_SUCCESS) { - error = repo_init_reinit(repo_out, repository_path.ptr, is_bare); + if (git_path_isdir(repository_path.ptr) == true) { + if (valid_repository_path(&repository_path) == true) { + int res = repo_init_reinit(repo_out, repository_path.ptr, is_bare); git_buf_free(&repository_path); - return error; + return res; } } - if (!(error = repo_init_structure(repository_path.ptr, is_bare)) && - !(error = repo_init_config(repository_path.ptr, is_bare)) && - !(error = repo_init_createhead(repository_path.ptr))) - error = git_repository_open(repo_out, repository_path.ptr); - else - git_repository_free(repo); + if (repo_init_structure(repository_path.ptr, is_bare) < 0 || + repo_init_config(repository_path.ptr, is_bare) < 0 || + repo_init_createhead(repository_path.ptr) < 0 || + git_repository_open(repo_out, repository_path.ptr) < 0) { + git_buf_free(&repository_path); + return -1; + } git_buf_free(&repository_path); - - if (error != GIT_SUCCESS) - git__rethrow(error, "Failed to (re)init the repository `%s`", path); - - return error; + return 0; } int git_repository_head_detached(git_repository *repo) { git_reference *ref; - int error; - size_t _size; - git_otype type; git_odb *odb = NULL; + int exists; - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; - error = git_reference_lookup(&ref, repo, GIT_HEAD_FILE); - if (error < GIT_SUCCESS) - return error; + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + return -1; if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { git_reference_free(ref); return 0; } - error = git_odb_read_header(&_size, &type, odb, git_reference_oid(ref)); + exists = git_odb_exists(odb, git_reference_oid(ref)); git_reference_free(ref); - - if (error < GIT_SUCCESS) - return error; - - if (type != GIT_OBJ_COMMIT) - return git__throw(GIT_EOBJCORRUPTED, "HEAD is not a commit"); - - return 1; + return exists; } int git_repository_head(git_reference **head_out, git_repository *repo) { - git_reference *ref, *resolved_ref; - int error; - - *head_out = NULL; - - error = git_reference_lookup(&ref, repo, GIT_HEAD_FILE); - if (error < GIT_SUCCESS) - return git__rethrow(GIT_ENOTAREPO, "Failed to locate the HEAD"); - - error = git_reference_resolve(&resolved_ref, ref); - if (error < GIT_SUCCESS) { - git_reference_free(ref); - return git__rethrow(error, "Failed to resolve the HEAD"); - } - - git_reference_free(ref); - - *head_out = resolved_ref; - return GIT_SUCCESS; + return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1); } int git_repository_head_orphan(git_repository *repo) { - git_reference *ref; + git_reference *ref = NULL; int error; error = git_repository_head(&ref, repo); + git_reference_free(ref); - if (error == GIT_SUCCESS) - git_reference_free(ref); + if (error == GIT_ENOTFOUND) + return 1; + + if (error < 0) + return -1; - return error == GIT_ENOTFOUND ? 1 : error; + return 0; } int git_repository_is_empty(git_repository *repo) @@ -867,9 +865,8 @@ int git_repository_is_empty(git_repository *repo) git_reference *head = NULL, *branch = NULL; int error; - error = git_reference_lookup(&head, repo, "HEAD"); - if (error < GIT_SUCCESS) - return git__throw(error, "Corrupted repository. HEAD does not exist"); + if (git_reference_lookup(&head, repo, "HEAD") < 0) + return -1; if (git_reference_type(head) != GIT_REF_SYMBOLIC) { git_reference_free(head); @@ -886,7 +883,13 @@ int git_repository_is_empty(git_repository *repo) git_reference_free(head); git_reference_free(branch); - return error == GIT_ENOTFOUND ? 1 : error; + if (error == GIT_ENOTFOUND) + return 1; + + if (error < 0) + return -1; + + return 0; } const char *git_repository_path(git_repository *repo) @@ -907,16 +910,18 @@ const char *git_repository_workdir(git_repository *repo) int git_repository_set_workdir(git_repository *repo, const char *workdir) { + git_buf path = GIT_BUF_INIT; + assert(repo && workdir); - free(repo->workdir); + if (git_path_prettify_dir(&path, workdir, NULL) < 0) + return -1; - repo->workdir = git__strdup(workdir); - if (repo->workdir == NULL) - return GIT_ENOMEM; + git__free(repo->workdir); + repo->workdir = git_buf_detach(&path); repo->is_bare = 0; - return GIT_SUCCESS; + return 0; } int git_repository_is_bare(git_repository *repo) @@ -924,3 +929,23 @@ int git_repository_is_bare(git_repository *repo) assert(repo); return repo->is_bare; } + +int git_repository_head_tree(git_tree **tree, git_repository *repo) +{ + git_oid head_oid; + git_object *obj = NULL; + + if (git_reference_name_to_oid(&head_oid, repo, GIT_HEAD_FILE) < 0) { + /* cannot resolve HEAD - probably brand new repo */ + giterr_clear(); + *tree = NULL; + return 0; + } + + if (git_object_lookup(&obj, repo, &head_oid, GIT_OBJ_ANY) < 0 || + git_object__resolve_to_type(&obj, GIT_OBJ_TREE) < 0) + return -1; + + *tree = (git_tree *)obj; + return 0; +} |