summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2017-05-24 11:13:36 +0200
committerPatrick Steinhardt <ps@pks.im>2017-10-09 12:48:01 +0200
commit071b6c0652ff0a0d6ce8c8c35d538d6edd67a52e (patch)
tree057196d3563c8cc28d4794e5b3fe19b6cfa167b6
parent9d7a75be7c66cd5c7931db620b97cbd6525b626f (diff)
downloadlibgit2-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.c85
-rw-r--r--tests/config/conditionals.c82
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);
+}