diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2023-04-22 23:12:00 +0100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2023-04-22 23:17:32 +0100 |
commit | 190a4c55df72b32adf4d60f77cbc47276b74f84b (patch) | |
tree | 896d6d41c2ea6305992bd646bbbe79a3fdb5f6fe /src/libgit2/repository.c | |
parent | e288f874a3a73ef31f88bb524f6d25d5ff3c5a3a (diff) | |
parent | 8a62616f43fe5ea37d41296f40293ff97aa88cfa (diff) | |
download | libgit2-190a4c55df72b32adf4d60f77cbc47276b74f84b.tar.gz |
Merge remote-tracking branch 'origin/main' into shallow-clone-network
Diffstat (limited to 'src/libgit2/repository.c')
-rw-r--r-- | src/libgit2/repository.c | 731 |
1 files changed, 436 insertions, 295 deletions
diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index bb6ae1b0f..1e356c84e 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -67,7 +67,7 @@ static const struct { static int check_repositoryformatversion(int *version, git_config *config); static int check_extensions(git_config *config, int version); -static int load_global_config(git_config **config); +static int load_global_config(git_config **config, bool use_env); static int load_objectformat(git_repository *repo, git_config *config); #define GIT_COMMONDIR_FILE "commondir" @@ -194,11 +194,23 @@ void git_repository_free(git_repository *repo) } /* Check if we have a separate commondir (e.g. we have a worktree) */ -static int lookup_commondir(bool *separate, git_str *commondir, git_str *repository_path) +static int lookup_commondir( + bool *separate, + git_str *commondir, + git_str *repository_path, + uint32_t flags) { - git_str common_link = GIT_STR_INIT; + git_str common_link = GIT_STR_INIT; int error; + /* Environment variable overrides configuration */ + if ((flags & GIT_REPOSITORY_OPEN_FROM_ENV)) { + error = git__getenv(commondir, "GIT_COMMON_DIR"); + + if (!error || error != GIT_ENOTFOUND) + goto done; + } + /* * If there's no commondir file, the repository path is the * common path, but it needs a trailing slash. @@ -225,12 +237,11 @@ static int lookup_commondir(bool *separate, git_str *commondir, git_str *reposit git_str_swap(commondir, &common_link); } - git_str_dispose(&common_link); - /* Make sure the commondir path always has a trailing slash */ error = git_fs_path_prettify_dir(commondir, commondir->ptr, NULL); done: + git_str_dispose(&common_link); return error; } @@ -255,14 +266,19 @@ GIT_INLINE(int) validate_repo_path(git_str *path) * * Open a repository object from its path */ -static int is_valid_repository_path(bool *out, git_str *repository_path, git_str *common_path) +static int is_valid_repository_path( + bool *out, + git_str *repository_path, + git_str *common_path, + uint32_t flags) { bool separate_commondir = false; int error; *out = false; - if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0) + if ((error = lookup_commondir(&separate_commondir, + common_path, repository_path, flags)) < 0) return error; /* Ensure HEAD file exists */ @@ -340,19 +356,42 @@ static int load_config_data(git_repository *repo, const git_config *config) return 0; } -static int load_workdir(git_repository *repo, git_config *config, git_str *parent_path) +static int load_workdir( + git_repository *repo, + git_config *config, + git_str *parent_path) { - int error; - git_config_entry *ce; + git_config_entry *ce = NULL; git_str worktree = GIT_STR_INIT; git_str path = GIT_STR_INIT; + git_str workdir_env = GIT_STR_INIT; + const char *value = NULL; + int error; if (repo->is_bare) return 0; - if ((error = git_config__lookup_entry( - &ce, config, "core.worktree", false)) < 0) - return error; + /* Environment variables are preferred */ + if (repo->use_env) { + error = git__getenv(&workdir_env, "GIT_WORK_TREE"); + + if (error == 0) + value = workdir_env.ptr; + else if (error == GIT_ENOTFOUND) + error = 0; + else + goto cleanup; + } + + /* Examine configuration values if necessary */ + if (!value) { + if ((error = git_config__lookup_entry(&ce, config, + "core.worktree", false)) < 0) + return error; + + if (ce && ce->value) + value = ce->value; + } if (repo->is_worktree) { char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); @@ -370,17 +409,21 @@ static int load_workdir(git_repository *repo, git_config *config, git_str *paren } repo->workdir = git_str_detach(&worktree); - } - else if (ce && ce->value) { - if ((error = git_fs_path_prettify_dir( - &worktree, ce->value, repo->gitdir)) < 0) + } else if (value) { + if (!*value) { + git_error_set(GIT_ERROR_NET, "working directory cannot be set to empty path"); + error = -1; + goto cleanup; + } + + if ((error = git_fs_path_prettify_dir(&worktree, + value, repo->gitdir)) < 0) goto cleanup; repo->workdir = git_str_detach(&worktree); - } - else if (parent_path && git_fs_path_isdir(parent_path->ptr)) + } else if (parent_path && git_fs_path_isdir(parent_path->ptr)) { repo->workdir = git_str_detach(parent_path); - else { + } else { if (git_fs_path_dirname_r(&worktree, repo->gitdir) < 0 || git_fs_path_to_dir(&worktree) < 0) { error = -1; @@ -391,8 +434,10 @@ static int load_workdir(git_repository *repo, git_config *config, git_str *paren } GIT_ERROR_CHECK_ALLOC(repo->workdir); + cleanup: git_str_dispose(&path); + git_str_dispose(&workdir_env); git_config_entry_free(ce); return error; } @@ -544,7 +589,10 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) return 0; } -static int validate_ownership_config(bool *is_safe, const char *path) +static int validate_ownership_config( + bool *is_safe, + const char *path, + bool use_env) { validate_ownership_data ownership_data = { path, GIT_STR_INIT, is_safe @@ -552,7 +600,7 @@ static int validate_ownership_config(bool *is_safe, const char *path) git_config *config; int error; - if (load_global_config(&config) != 0) + if (load_global_config(&config, use_env) != 0) return 0; error = git_config_get_multivar_foreach(config, @@ -626,7 +674,8 @@ static int validate_ownership(git_repository *repo) } if (is_safe || - (error = validate_ownership_config(&is_safe, validation_paths[0])) < 0) + (error = validate_ownership_config( + &is_safe, validation_paths[0], repo->use_env)) < 0) goto done; if (!is_safe) { @@ -640,14 +689,28 @@ done: return error; } -static int find_repo( - git_str *gitdir_path, - git_str *workdir_path, - git_str *gitlink_path, - git_str *commondir_path, +struct repo_paths { + git_str gitdir; + git_str workdir; + git_str gitlink; + git_str commondir; +}; + +#define REPO_PATHS_INIT { GIT_STR_INIT } + +GIT_INLINE(void) repo_paths_dispose(struct repo_paths *paths) +{ + git_str_dispose(&paths->gitdir); + git_str_dispose(&paths->workdir); + git_str_dispose(&paths->gitlink); + git_str_dispose(&paths->commondir); +} + +static int find_repo_traverse( + struct repo_paths *out, const char *start_path, - uint32_t flags, - const char *ceiling_dirs) + const char *ceiling_dirs, + uint32_t flags) { git_str path = GIT_STR_INIT; git_str repo_link = GIT_STR_INIT; @@ -659,19 +722,23 @@ static int find_repo( size_t ceiling_offset = 0; int error; - git_str_clear(gitdir_path); + git_str_clear(&out->gitdir); - error = git_fs_path_prettify(&path, start_path, NULL); - if (error < 0) + if ((error = git_fs_path_prettify(&path, start_path, NULL)) < 0) return error; - /* in_dot_git toggles each loop: + /* + * In each loop we look first for a `.git` dir within the + * directory, then to see if the directory itself is a repo. + * + * In other words: if we start in /a/b/c, then we look at: * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a - * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we - * assume we started with /a/b/c.git and don't append .git the first - * time through. - * min_iterations indicates the number of iterations left before going - * further counts as a search. */ + * + * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, + * we assume we started with /a/b/c.git and don't append .git the + * first time through. min_iterations indicates the number of + * iterations left before going further counts as a search. + */ if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { in_dot_git = true; min_iterations = 1; @@ -698,48 +765,51 @@ static int find_repo( break; if (S_ISDIR(st.st_mode)) { - if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) + if ((error = is_valid_repository_path(&is_valid, &path, &common_link, flags)) < 0) goto out; if (is_valid) { if ((error = git_fs_path_to_dir(&path)) < 0 || - (error = git_str_set(gitdir_path, path.ptr, path.size)) < 0) + (error = git_str_set(&out->gitdir, path.ptr, path.size)) < 0) + goto out; + + if ((error = git_str_attach(&out->gitlink, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) goto out; - if (gitlink_path) - if ((error = git_str_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); + git_str_swap(&common_link, &out->commondir); break; } } else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { if ((error = read_gitfile(&repo_link, path.ptr)) < 0 || - (error = is_valid_repository_path(&is_valid, &repo_link, &common_link)) < 0) + (error = is_valid_repository_path(&is_valid, &repo_link, &common_link, flags)) < 0) goto out; if (is_valid) { - git_str_swap(gitdir_path, &repo_link); + git_str_swap(&out->gitdir, &repo_link); - if (gitlink_path) - if ((error = git_str_put(gitlink_path, path.ptr, path.size)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); + if ((error = git_str_put(&out->gitlink, path.ptr, path.size)) < 0) + goto out; + + git_str_swap(&common_link, &out->commondir); } break; } } - /* Move up one directory. If we're in_dot_git, we'll search the - * parent itself next. If we're !in_dot_git, we'll search .git - * in the parent directory next (added at the top of the loop). */ + /* + * Move up one directory. If we're in_dot_git, we'll + * search the parent itself next. If we're !in_dot_git, + * we'll search .git in the parent directory next (added + * at the top of the loop). + */ if ((error = git_fs_path_dirname_r(&path, path.ptr)) < 0) goto out; - /* Once we've checked the directory (and .git if applicable), - * find the ceiling for a search. */ + /* + * Once we've checked the directory (and .git if + * applicable), find the ceiling for a search. + */ if (min_iterations && (--min_iterations == 0)) ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); @@ -749,23 +819,26 @@ static int find_repo( break; } - if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { - if (!git_str_len(gitdir_path)) - git_str_clear(workdir_path); - else if ((error = git_fs_path_dirname_r(workdir_path, path.ptr)) < 0 || - (error = git_fs_path_to_dir(workdir_path)) < 0) + if (!(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (!git_str_len(&out->gitdir)) + git_str_clear(&out->workdir); + else if ((error = git_fs_path_dirname_r(&out->workdir, path.ptr)) < 0 || + (error = git_fs_path_to_dir(&out->workdir)) < 0) goto out; } - /* If we didn't find the repository, and we don't have any other error - * to report, report that. */ - if (!git_str_len(gitdir_path)) { - git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); + /* If we didn't find the repository, and we don't have any other + * error to report, report that. */ + if (!git_str_len(&out->gitdir)) { + git_error_set(GIT_ERROR_REPOSITORY, "could not find repository at '%s'", start_path); error = GIT_ENOTFOUND; goto out; } out: + if (error) + repo_paths_dispose(out); + git_str_dispose(&path); git_str_dispose(&repo_link); git_str_dispose(&common_link); @@ -793,6 +866,70 @@ error: return error; } +static int find_repo( + struct repo_paths *out, + const char *start_path, + const char *ceiling_dirs, + uint32_t flags) +{ + bool use_env = !!(flags & GIT_REPOSITORY_OPEN_FROM_ENV); + git_str gitdir_buf = GIT_STR_INIT, + ceiling_dirs_buf = GIT_STR_INIT, + across_fs_buf = GIT_STR_INIT; + int error; + + if (use_env && !start_path) { + error = git__getenv(&gitdir_buf, "GIT_DIR"); + + if (!error) { + start_path = gitdir_buf.ptr; + flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; + flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; + } else if (error == GIT_ENOTFOUND) { + start_path = "."; + } else { + goto done; + } + } + + if (use_env && !ceiling_dirs) { + error = git__getenv(&ceiling_dirs_buf, + "GIT_CEILING_DIRECTORIES"); + + if (!error) + ceiling_dirs = ceiling_dirs_buf.ptr; + else if (error != GIT_ENOTFOUND) + goto done; + } + + if (use_env) { + error = git__getenv(&across_fs_buf, + "GIT_DISCOVERY_ACROSS_FILESYSTEM"); + + if (!error) { + int across_fs = 0; + + if ((error = git_config_parse_bool(&across_fs, + git_str_cstr(&across_fs_buf))) < 0) + goto done; + + if (across_fs) + flags |= GIT_REPOSITORY_OPEN_CROSS_FS; + } else if (error != GIT_ENOTFOUND) { + goto done; + } + } + + error = find_repo_traverse(out, start_path, ceiling_dirs, flags); + +done: + git_str_dispose(&gitdir_buf); + git_str_dispose(&ceiling_dirs_buf); + git_str_dispose(&across_fs_buf); + + return error; +} + static int obtain_config_and_set_oid_type( git_config **config_ptr, git_repository *repo) @@ -811,7 +948,7 @@ static int obtain_config_and_set_oid_type( goto out; if (config && - (error = check_repositoryformatversion(&version, config)) < 0) + (error = check_repositoryformatversion(&version, config)) < 0) goto out; if ((error = check_extensions(config, version)) < 0) @@ -821,7 +958,7 @@ static int obtain_config_and_set_oid_type( if ((error = load_objectformat(repo, config)) < 0) goto out; } else { - repo->oid_type = GIT_OID_SHA1; + repo->oid_type = GIT_OID_DEFAULT; } out: @@ -841,7 +978,7 @@ int git_repository_open_bare( git_config *config; if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 || - (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) + (error = is_valid_repository_path(&is_valid, &path, &common_path, 0)) < 0) return error; if (!is_valid) { @@ -875,173 +1012,22 @@ cleanup: return error; } -static int _git_repository_open_ext_from_env( - git_repository **out, - const char *start_path) +static int repo_load_namespace(git_repository *repo) { - git_repository *repo = NULL; - git_index *index = NULL; - git_odb *odb = NULL; - git_str dir_buf = GIT_STR_INIT; - git_str ceiling_dirs_buf = GIT_STR_INIT; - git_str across_fs_buf = GIT_STR_INIT; - git_str index_file_buf = GIT_STR_INIT; git_str namespace_buf = GIT_STR_INIT; - git_str object_dir_buf = GIT_STR_INIT; - git_str alts_buf = GIT_STR_INIT; - git_str work_tree_buf = GIT_STR_INIT; - git_str common_dir_buf = GIT_STR_INIT; - const char *ceiling_dirs = NULL; - unsigned flags = 0; int error; - if (!start_path) { - error = git__getenv(&dir_buf, "GIT_DIR"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - start_path = "."; - } else if (error < 0) - goto error; - else { - start_path = git_str_cstr(&dir_buf); - flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; - flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; - } - } - - error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else - ceiling_dirs = git_str_cstr(&ceiling_dirs_buf); - - error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - int across_fs = 0; - error = git_config_parse_bool(&across_fs, git_str_cstr(&across_fs_buf)); - if (error < 0) - goto error; - if (across_fs) - flags |= GIT_REPOSITORY_OPEN_CROSS_FS; - } - - error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_index_open(&index, git_str_cstr(&index_file_buf)); - if (error < 0) - goto error; - } + if (!repo->use_env) + return 0; error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - - error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_odb__open(&odb, git_str_cstr(&object_dir_buf), NULL); - if (error < 0) - goto error; - } - error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_WORK_TREE unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_COMMON_DIR unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); - if (error < 0) - goto error; - - if (odb) - git_repository_set_odb(repo, odb); - - error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } else if (error < 0) - goto error; - else { - const char *end; - char *alt, *sep; - if (!odb) { - error = git_repository_odb(&odb, repo); - if (error < 0) - goto error; - } - - end = git_str_cstr(&alts_buf) + git_str_len(&alts_buf); - for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { - for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) - ; - if (*sep) - *sep = '\0'; - error = git_odb_add_disk_alternate(odb, alt); - if (error < 0) - goto error; - } - } - - if (git_str_len(&namespace_buf)) { - error = git_repository_set_namespace(repo, git_str_cstr(&namespace_buf)); - if (error < 0) - goto error; - } - - git_repository_set_index(repo, index); + if (error == 0) + repo->namespace = git_str_detach(&namespace_buf); + else if (error != GIT_ENOTFOUND) + return error; - if (out) { - *out = repo; - goto success; - } -error: - git_repository_free(repo); -success: - git_odb_free(odb); - git_index_free(index); - git_str_dispose(&common_dir_buf); - git_str_dispose(&work_tree_buf); - git_str_dispose(&alts_buf); - git_str_dispose(&object_dir_buf); - git_str_dispose(&namespace_buf); - git_str_dispose(&index_file_buf); - git_str_dispose(&across_fs_buf); - git_str_dispose(&ceiling_dirs_buf); - git_str_dispose(&dir_buf); - return error; + return 0; } static int repo_is_worktree(unsigned *out, const git_repository *repo) @@ -1073,21 +1059,16 @@ int git_repository_open_ext( unsigned int flags, const char *ceiling_dirs) { - int error; - unsigned is_worktree; - git_str gitdir = GIT_STR_INIT, workdir = GIT_STR_INIT, - gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT; + struct repo_paths paths = { GIT_STR_INIT }; git_repository *repo = NULL; git_config *config = NULL; - - if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) - return _git_repository_open_ext_from_env(repo_ptr, start_path); + unsigned is_worktree; + int error; if (repo_ptr) *repo_ptr = NULL; - error = find_repo( - &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); + error = find_repo(&paths, start_path, ceiling_dirs, flags); if (error < 0 || !repo_ptr) goto cleanup; @@ -1095,20 +1076,23 @@ int git_repository_open_ext( repo = repository_alloc(); GIT_ERROR_CHECK_ALLOC(repo); - repo->gitdir = git_str_detach(&gitdir); + repo->use_env = !!(flags & GIT_REPOSITORY_OPEN_FROM_ENV); + + repo->gitdir = git_str_detach(&paths.gitdir); GIT_ERROR_CHECK_ALLOC(repo->gitdir); - if (gitlink.size) { - repo->gitlink = git_str_detach(&gitlink); + if (paths.gitlink.size) { + repo->gitlink = git_str_detach(&paths.gitlink); GIT_ERROR_CHECK_ALLOC(repo->gitlink); } - if (commondir.size) { - repo->commondir = git_str_detach(&commondir); + if (paths.commondir.size) { + repo->commondir = git_str_detach(&paths.commondir); GIT_ERROR_CHECK_ALLOC(repo->commondir); } if ((error = repo_is_worktree(&is_worktree, repo)) < 0) goto cleanup; + repo->is_worktree = is_worktree; error = obtain_config_and_set_oid_type(&config, repo); @@ -1123,10 +1107,13 @@ int git_repository_open_ext( } else { if (config && ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &workdir)) < 0)) + (error = load_workdir(repo, config, &paths.workdir)) < 0)) goto cleanup; } + if ((error = repo_load_namespace(repo)) < 0) + goto cleanup; + /* * Ensure that the git directory and worktree are * owned by the current user. @@ -1136,10 +1123,7 @@ int git_repository_open_ext( goto cleanup; cleanup: - git_str_dispose(&gitdir); - git_str_dispose(&workdir); - git_str_dispose(&gitlink); - git_str_dispose(&commondir); + repo_paths_dispose(&paths); git_config_free(config); if (error < 0) @@ -1207,11 +1191,17 @@ int git_repository_discover( int across_fs, const char *ceiling_dirs) { + struct repo_paths paths = { GIT_STR_INIT }; uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + int error; GIT_ASSERT_ARG(start_path); - GIT_BUF_WRAP_PRIVATE(out, find_repo, NULL, NULL, NULL, start_path, flags, ceiling_dirs); + if ((error = find_repo(&paths, start_path, ceiling_dirs, flags)) == 0) + error = git_buf_fromstr(out, &paths.gitdir); + + repo_paths_dispose(&paths); + return error; } static int load_config( @@ -1282,32 +1272,81 @@ static const char *path_unless_empty(git_str *buf) return git_str_len(buf) > 0 ? git_str_cstr(buf) : NULL; } +GIT_INLINE(int) config_path_system(git_str *out, bool use_env) +{ + if (use_env) { + git_str no_system_buf = GIT_STR_INIT; + int no_system = 0; + int error; + + error = git__getenv(&no_system_buf, "GIT_CONFIG_NOSYSTEM"); + + if (error && error != GIT_ENOTFOUND) + return error; + + error = git_config_parse_bool(&no_system, no_system_buf.ptr); + git_str_dispose(&no_system_buf); + + if (no_system) + return 0; + + error = git__getenv(out, "GIT_CONFIG_SYSTEM"); + + if (error == 0 || error != GIT_ENOTFOUND) + return 0; + } + + git_config__find_system(out); + return 0; +} + +GIT_INLINE(int) config_path_global(git_str *out, bool use_env) +{ + if (use_env) { + int error = git__getenv(out, "GIT_CONFIG_GLOBAL"); + + if (error == 0 || error != GIT_ENOTFOUND) + return 0; + } + + git_config__find_global(out); + return 0; +} + int git_repository_config__weakptr(git_config **out, git_repository *repo) { int error = 0; if (repo->_config == NULL) { + git_str system_buf = GIT_STR_INIT; git_str global_buf = GIT_STR_INIT; git_str xdg_buf = GIT_STR_INIT; - git_str system_buf = GIT_STR_INIT; git_str programdata_buf = GIT_STR_INIT; + bool use_env = repo->use_env; git_config *config; - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); + if (!(error = config_path_system(&system_buf, use_env)) && + !(error = config_path_global(&global_buf, use_env))) { + git_config__find_xdg(&xdg_buf); + git_config__find_programdata(&programdata_buf); + } - /* If there is no global file, open a backend for it anyway */ - if (git_str_len(&global_buf) == 0) - git_config__global_location(&global_buf); + if (!error) { + /* + * If there is no global file, open a backend + * for it anyway. + */ + if (git_str_len(&global_buf) == 0) + git_config__global_location(&global_buf); + + error = load_config( + &config, repo, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + } - error = load_config( - &config, repo, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); if (!error) { GIT_REFCOUNT_OWN(config, repo); @@ -1356,6 +1395,56 @@ int git_repository_set_config(git_repository *repo, git_config *config) return 0; } +static int repository_odb_path(git_str *out, git_repository *repo) +{ + int error = GIT_ENOTFOUND; + + if (repo->use_env) + error = git__getenv(out, "GIT_OBJECT_DIRECTORY"); + + if (error == GIT_ENOTFOUND) + error = git_repository__item_path(out, repo, + GIT_REPOSITORY_ITEM_OBJECTS); + + return error; +} + +static int repository_odb_alternates( + git_odb *odb, + git_repository *repo) +{ + git_str alternates = GIT_STR_INIT; + char *sep, *alt; + int error; + + if (!repo->use_env) + return 0; + + error = git__getenv(&alternates, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); + + if (error != 0) + return (error == GIT_ENOTFOUND) ? 0 : error; + + alt = alternates.ptr; + + while (*alt) { + sep = strchr(alt, GIT_PATH_LIST_SEPARATOR); + + if (sep) + *sep = '\0'; + + error = git_odb_add_disk_alternate(odb, alt); + + if (sep) + alt = sep + 1; + else + break; + } + + git_str_dispose(&alternates); + return 0; +} + int git_repository_odb__weakptr(git_odb **out, git_repository *repo) { int error = 0; @@ -1371,9 +1460,9 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) odb_opts.oid_type = repo->oid_type; - if ((error = git_repository__item_path(&odb_path, repo, - GIT_REPOSITORY_ITEM_OBJECTS)) < 0 || - (error = git_odb__new(&odb, &odb_opts)) < 0) + if ((error = repository_odb_path(&odb_path, repo)) < 0 || + (error = git_odb__new(&odb, &odb_opts)) < 0 || + (error = repository_odb_alternates(odb, repo)) < 0) return error; GIT_REFCOUNT_OWN(odb, repo); @@ -1457,6 +1546,20 @@ int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) return 0; } +static int repository_index_path(git_str *out, git_repository *repo) +{ + int error = GIT_ENOTFOUND; + + if (repo->use_env) + error = git__getenv(out, "GIT_INDEX_FILE"); + + if (error == GIT_ENOTFOUND) + error = git_repository__item_path(out, repo, + GIT_REPOSITORY_ITEM_INDEX); + + return error; +} + int git_repository_index__weakptr(git_index **out, git_repository *repo) { int error = 0; @@ -1468,10 +1571,11 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) git_str index_path = GIT_STR_INIT; git_index *index; - if ((error = git_str_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) + if ((error = repository_index_path(&index_path, repo)) < 0) return error; - error = git_index_open(&index, index_path.ptr); + error = git_index__open(&index, index_path.ptr, repo->oid_type); + if (!error) { GIT_REFCOUNT_OWN(index, repo); @@ -1673,7 +1777,7 @@ static const char *builtin_extensions[] = { "objectformat" }; -static git_vector user_extensions = GIT_VECTOR_INIT; +static git_vector user_extensions = { 0, git__strcmp_cb }; static int check_valid_extension(const git_config_entry *entry, void *payload) { @@ -1741,7 +1845,7 @@ static int load_objectformat(git_repository *repo, git_config *config) if ((error = git_config_get_entry(&entry, config, "extensions.objectformat")) < 0) { if (error == GIT_ENOTFOUND) { - repo->oid_type = GIT_OID_SHA1; + repo->oid_type = GIT_OID_DEFAULT; git_error_clear(); error = 0; } @@ -1814,7 +1918,7 @@ int git_repository__extensions(char ***out, size_t *out_len) char *extension; size_t i, j; - if (git_vector_init(&extensions, 8, NULL) < 0) + if (git_vector_init(&extensions, 8, git__strcmp_cb) < 0) return -1; for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { @@ -1846,21 +1950,49 @@ int git_repository__extensions(char ***out, size_t *out_len) return -1; } + git_vector_sort(&extensions); + *out = (char **)git_vector_detach(out_len, NULL, &extensions); return 0; } +static int dup_ext_err(void **old, void *extension) +{ + GIT_UNUSED(old); + GIT_UNUSED(extension); + return GIT_EEXISTS; +} + int git_repository__set_extensions(const char **extensions, size_t len) { char *extension; - size_t i; + size_t i, j; + int error; git_repository__free_extensions(); for (i = 0; i < len; i++) { - if ((extension = git__strdup(extensions[i])) == NULL || - git_vector_insert(&user_extensions, extension) < 0) + bool is_builtin = false; + + for (j = 0; j < ARRAY_SIZE(builtin_extensions); j++) { + if (strcmp(builtin_extensions[j], extensions[i]) == 0) { + is_builtin = true; + break; + } + } + + if (is_builtin) + continue; + + if ((extension = git__strdup(extensions[i])) == NULL) return -1; + + if ((error = git_vector_insert_sorted(&user_extensions, extension, dup_ext_err)) < 0) { + git__free(extension); + + if (error != GIT_EEXISTS) + return -1; + } } return 0; @@ -1929,7 +2061,7 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path) * Return a configuration object with only the global and system * configurations; no repository-level configuration. */ -static int load_global_config(git_config **config) +static int load_global_config(git_config **config, bool use_env) { git_str global_buf = GIT_STR_INIT; git_str xdg_buf = GIT_STR_INIT; @@ -1937,16 +2069,17 @@ static int load_global_config(git_config **config) git_str programdata_buf = GIT_STR_INIT; int error; - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); + if (!(error = config_path_system(&system_buf, use_env)) && + !(error = config_path_global(&global_buf, use_env))) { + git_config__find_xdg(&xdg_buf); + git_config__find_programdata(&programdata_buf); - error = load_config(config, NULL, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); + error = load_config(config, NULL, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + } git_str_dispose(&global_buf); git_str_dispose(&xdg_buf); @@ -1956,7 +2089,7 @@ static int load_global_config(git_config **config) return error; } -static bool are_symlinks_supported(const char *wd_path) +static bool are_symlinks_supported(const char *wd_path, bool use_env) { git_config *config = NULL; int symlinks = 0; @@ -1969,10 +2102,12 @@ static bool are_symlinks_supported(const char *wd_path) * _not_ set, then we do not test or enable symlink support. */ #ifdef GIT_WIN32 - if (load_global_config(&config) < 0 || + if (load_global_config(&config, use_env) < 0 || git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks) goto done; +#else + GIT_UNUSED(use_env); #endif if (!(symlinks = git_fs_path_supports_symlinks(wd_path))) @@ -2045,7 +2180,8 @@ static int repo_init_fs_configs( const char *cfg_path, const char *repo_dir, const char *work_dir, - bool update_ignorecase) + bool update_ignorecase, + bool use_env) { int error = 0; @@ -2056,7 +2192,7 @@ static int repo_init_fs_configs( cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) return error; - if (!are_symlinks_supported(work_dir)) { + if (!are_symlinks_supported(work_dir, use_env)) { if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) return error; } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) @@ -2093,6 +2229,7 @@ static int repo_init_config( git_config *config = NULL; bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); + bool use_env = ((flags & GIT_REPOSITORY_OPEN_FROM_ENV) != 0); int version = GIT_REPO_VERSION_DEFAULT; if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) @@ -2113,7 +2250,8 @@ static int repo_init_config( SET_REPO_CONFIG(int32, "core.repositoryformatversion", version); if ((error = repo_init_fs_configs( - config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) + config, cfg_path.ptr, repo_dir, work_dir, + !is_reinit, use_env)) < 0) goto cleanup; if (!is_bare) { @@ -2143,7 +2281,7 @@ static int repo_init_config( SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); } - if (oid_type != GIT_OID_SHA1) { + if (oid_type != GIT_OID_DEFAULT) { SET_REPO_CONFIG(int32, "core.repositoryformatversion", 1); SET_REPO_CONFIG(string, "extensions.objectformat", git_oid_type_name(oid_type)); } @@ -2177,8 +2315,8 @@ int git_repository_reinit_filesystem(git_repository *repo, int recurse) 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); + error = repo_init_fs_configs(config, path.ptr, repo_dir, + git_repository_workdir(repo), true, repo->use_env); git_config_free(config); git_str_dispose(&path); @@ -2647,7 +2785,7 @@ int git_repository_init_ext( wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_str_cstr(&wd_path); - if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path)) < 0) + if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path, opts->flags)) < 0) goto out; if (is_valid) { @@ -3530,16 +3668,19 @@ int git_repository__shallow_roots_write(git_repository *repo, git_array_oid_t ro { git_filebuf file = GIT_FILEBUF_INIT; git_str path = GIT_STR_INIT; - int error = 0; size_t idx; git_oid *oid; + int filebuf_hash, error = 0; assert(repo); + filebuf_hash = git_filebuf_hash_flags(git_oid_algorithm(GIT_OID_SHA1)); + GIT_ASSERT(filebuf_hash); + if ((error = git_str_joinpath(&path, repo->gitdir, "shallow")) < 0) goto on_error; - if ((error = git_filebuf_open(&file, git_str_cstr(&path), GIT_FILEBUF_HASH_CONTENTS, 0666)) < 0) + if ((error = git_filebuf_open(&file, git_str_cstr(&path), filebuf_hash, 0666)) < 0) goto on_error; git_array_foreach(roots, idx, oid) { |