diff options
author | Patrick Steinhardt <ps@pks.im> | 2017-05-24 11:13:36 +0200 |
---|---|---|
committer | Patrick Steinhardt <ps@pks.im> | 2017-10-09 12:48:01 +0200 |
commit | 071b6c0652ff0a0d6ce8c8c35d538d6edd67a52e (patch) | |
tree | 057196d3563c8cc28d4794e5b3fe19b6cfa167b6 | |
parent | 9d7a75be7c66cd5c7931db620b97cbd6525b626f (diff) | |
download | libgit2-071b6c0652ff0a0d6ce8c8c35d538d6edd67a52e.tar.gz |
config_file: implement conditional "gitdir" includes
Upstream git.git has implemented the ability to include other
configuration files based on conditions. Right now, this only includes
the ability to include a file based on the gitdir-location of the
repository the currently parsed configuration file belongs to. This
commit implements handling these conditional includes for the
case-sensitive "gitdir" condition.
-rw-r--r-- | src/config_file.c | 85 | ||||
-rw-r--r-- | tests/config/conditionals.c | 82 |
2 files changed, 167 insertions, 0 deletions
diff --git a/src/config_file.c b/src/config_file.c index 800e584d6..ba520fc29 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1612,6 +1612,86 @@ static int parse_include(struct reader *reader, return result; } +static int conditional_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + git_buf path = GIT_BUF_INIT; + int error, fnmatch_flags; + + if (value[0] == '.' && git_path_is_dirsep(value[1])) { + git_path_dirname_r(&path, cfg_file); + git_buf_joinpath(&path, path.ptr, value + 2); + } else if (value[0] == '~' && git_path_is_dirsep(value[1])) + git_sysdir_expand_global_file(&path, value + 1); + else if (!git_path_is_absolute(value)) + git_buf_joinpath(&path, "**", value); + else + git_buf_sets(&path, value); + + if (git_buf_oom(&path)) { + error = -1; + goto out; + } + + if (git_path_is_dirsep(value[strlen(value) - 1])) + git_buf_puts(&path, "**"); + + fnmatch_flags = FNM_PATHNAME|FNM_LEADING_DIR; + + if ((error = p_fnmatch(path.ptr, git_repository_path(repo), fnmatch_flags)) < 0) + + goto out; + + *matches = (error == 0); + +out: + git_buf_free(&path); + return error; +} + +static const struct { + const char *prefix; + int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value); +} conditions[] = { + { "gitdir:", conditional_match_gitdir } +}; + +static int parse_conditional_include(struct reader *reader, + struct parse_data *parse_data, const char *section, const char *file) +{ + char *condition; + size_t i; + int error = 0, matches; + + if (!parse_data->repo) + return 0; + + condition = git__substrdup(section + strlen("includeIf."), + strlen(section) - strlen("includeIf.") - strlen(".path")); + + for (i = 0; i < ARRAY_SIZE(conditions); i++) { + if (git__prefixcmp(condition, conditions[i].prefix)) + continue; + + if ((error = conditions[i].matches(&matches, + parse_data->repo, + parse_data->file_path, + condition + strlen(conditions[i].prefix))) < 0) + break; + + if (matches) + error = parse_include(reader, parse_data, file); + + break; + } + + git__free(condition); + return error; +} + static int read_on_variable( struct reader *reader, const char *current_section, @@ -1656,6 +1736,11 @@ static int read_on_variable( /* Add or append the new config option */ if (!git__strcmp(var->entry->name, "include.path")) result = parse_include(reader, parse_data, var->entry->value); + else if (!git__prefixcmp(var->entry->name, "includeif.") && + !git__suffixcmp(var->entry->name, ".path")) + result = parse_conditional_include(reader, parse_data, + var->entry->name, var->entry->value); + return result; } diff --git a/tests/config/conditionals.c b/tests/config/conditionals.c new file mode 100644 index 000000000..323bbaef4 --- /dev/null +++ b/tests/config/conditionals.c @@ -0,0 +1,82 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "fileops.h" + +#ifdef GIT_WIN32 +# define ROOT_PREFIX "C:" +#else +# define ROOT_PREFIX +#endif + +static git_repository *_repo; + +void test_config_conditionals__initialize(void) +{ + _repo = cl_git_sandbox_init("empty_standard_repo"); +} + +void test_config_conditionals__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_condition_includes(const char *keyword, const char *path, bool expected) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + git_buf_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path); + git_buf_puts(&buf, "path = other\n"); + + cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); + cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); + _repo = cl_git_sandbox_reopen(); + + cl_git_pass(git_repository_config(&cfg, _repo)); + + if (expected) { + git_buf_clear(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_assert_equal_s("baz", git_buf_cstr(&buf)); + } else { + cl_git_fail_with(GIT_ENOTFOUND, + git_config_get_string_buf(&buf, cfg, "foo.bar")); + } + + git_buf_free(&buf); + git_config_free(cfg); +} + +void test_config_conditionals__gitdir(void) +{ + git_buf path = GIT_BUF_INIT; + + assert_condition_includes("gitdir", ROOT_PREFIX "/", true); + assert_condition_includes("gitdir", "empty_standard_repo", true); + assert_condition_includes("gitdir", "empty_standard_repo/", true); + assert_condition_includes("gitdir", "./", true); + + assert_condition_includes("gitdir", ROOT_PREFIX "/nonexistent", false); + assert_condition_includes("gitdir", ROOT_PREFIX "/empty_standard_repo", false); + assert_condition_includes("gitdir", "empty_stand", false); + assert_condition_includes("gitdir", "~/empty_standard_repo", false); + + git_buf_joinpath(&path, clar_sandbox_path(), "/"); + assert_condition_includes("gitdir", path.ptr, true); + + git_buf_joinpath(&path, clar_sandbox_path(), "/*"); + assert_condition_includes("gitdir", path.ptr, true); + + git_buf_joinpath(&path, clar_sandbox_path(), "empty_standard_repo"); + assert_condition_includes("gitdir", path.ptr, true); + + git_buf_joinpath(&path, clar_sandbox_path(), "Empty_Standard_Repo"); + assert_condition_includes("gitdir", path.ptr, false); + + git_buf_free(&path); +} + +void test_config_conditionals__invalid_conditional_fails(void) +{ + assert_condition_includes("foobar", ".git", false); +} |