Commit aaa48d06d9c0ac08e09422f601f722fb7533186a

Edward Thomson 2019-08-27T11:26:50

Merge pull request #5196 from pks-t/pks/config-include-onbranch config: implement "onbranch" conditional

diff --git a/src/config_file.c b/src/config_file.c
index 3e8e30c..849096d 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -651,12 +651,64 @@ static int conditional_match_gitdir_i(
 	return do_match_gitdir(matches, repo, cfg_file, value, true);
 }
 
+static int conditional_match_onbranch(
+	int *matches,
+	const git_repository *repo,
+	const char *cfg_file,
+	const char *condition)
+{
+	git_buf reference = GIT_BUF_INIT, buf = GIT_BUF_INIT;
+	int error;
+
+	GIT_UNUSED(cfg_file);
+
+	/*
+	 * NOTE: you cannot use `git_repository_head` here. Looking up the
+	 * HEAD reference will create the ODB, which causes us to read the
+	 * repo's config for keys like core.precomposeUnicode. As we're
+	 * just parsing the config right now, though, this would result in
+	 * an endless recursion.
+	 */
+
+	if ((error = git_buf_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 ||
+	    (error = git_futils_readbuffer(&reference, buf.ptr)) < 0)
+		goto out;
+	git_buf_rtrim(&reference);
+
+	if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)))
+		goto out;
+	git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF));
+
+	if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR)))
+		goto out;
+	git_buf_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR));
+
+	/*
+	 * If the condition ends with a '/', then we should treat it as if
+	 * it had '**' appended.
+	 */
+	if ((error = git_buf_sets(&buf, condition)) < 0)
+		goto out;
+	if (git_path_is_dirsep(condition[strlen(condition) - 1]) &&
+	    (error = git_buf_puts(&buf, "**")) < 0)
+		goto out;
+
+	*matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH;
+out:
+	git_buf_dispose(&reference);
+	git_buf_dispose(&buf);
+
+	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 },
-	{ "gitdir/i:", conditional_match_gitdir_i }
+	{ "gitdir/i:", conditional_match_gitdir_i },
+	{ "onbranch:", conditional_match_onbranch }
 };
 
 static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file)
diff --git a/tests/config/conditionals.c b/tests/config/conditionals.c
index 0e629e4..5858782 100644
--- a/tests/config/conditionals.c
+++ b/tests/config/conditionals.c
@@ -1,6 +1,7 @@
 #include "clar_libgit2.h"
 #include "buffer.h"
 #include "futils.h"
+#include "repository.h"
 
 #ifdef GIT_WIN32
 # define ROOT_PREFIX "C:"
@@ -22,11 +23,11 @@ void test_config_conditionals__cleanup(void)
 
 static void assert_condition_includes(const char *keyword, const char *path, bool expected)
 {
-	git_config *cfg;
 	git_buf buf = GIT_BUF_INIT;
+	git_config *cfg;
 
-	git_buf_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path);
-	git_buf_puts(&buf, "path = other\n");
+	cl_git_pass(git_buf_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path));
+	cl_git_pass(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");
@@ -106,3 +107,42 @@ void test_config_conditionals__invalid_conditional_fails(void)
 {
 	assert_condition_includes("foobar", ".git", false);
 }
+
+static void set_head(git_repository *repo, const char *name)
+{
+	cl_git_pass(git_repository_create_head(git_repository_path(repo), name));
+}
+
+void test_config_conditionals__onbranch(void)
+{
+	assert_condition_includes("onbranch", "master", true);
+	assert_condition_includes("onbranch", "m*", true);
+	assert_condition_includes("onbranch", "*", true);
+	assert_condition_includes("onbranch", "master/", false);
+	assert_condition_includes("onbranch", "foo", false);
+
+	set_head(_repo, "foo");
+	assert_condition_includes("onbranch", "master", false);
+	assert_condition_includes("onbranch", "foo", true);
+	assert_condition_includes("onbranch", "f*o", true);
+
+	set_head(_repo, "dir/ref");
+	assert_condition_includes("onbranch", "dir/ref", true);
+	assert_condition_includes("onbranch", "dir/", true);
+	assert_condition_includes("onbranch", "dir/*", true);
+	assert_condition_includes("onbranch", "dir/**", true);
+	assert_condition_includes("onbranch", "**", true);
+	assert_condition_includes("onbranch", "dir", false);
+	assert_condition_includes("onbranch", "dir*", false);
+
+	set_head(_repo, "dir/subdir/ref");
+	assert_condition_includes("onbranch", "dir/subdir/", true);
+	assert_condition_includes("onbranch", "dir/subdir/*", true);
+	assert_condition_includes("onbranch", "dir/subdir/ref", true);
+	assert_condition_includes("onbranch", "dir/", true);
+	assert_condition_includes("onbranch", "dir/**", true);
+	assert_condition_includes("onbranch", "**", true);
+	assert_condition_includes("onbranch", "dir", false);
+	assert_condition_includes("onbranch", "dir*", false);
+	assert_condition_includes("onbranch", "dir/*", false);
+}
diff --git a/tests/config/include.c b/tests/config/include.c
index 48261dd..e2b0fc9 100644
--- a/tests/config/include.c
+++ b/tests/config/include.c
@@ -202,3 +202,33 @@ void test_config_include__included_variables_cannot_be_modified(void)
 	cl_git_pass(p_unlink("top-level"));
 	cl_git_pass(p_unlink("included"));
 }
+
+void test_config_include__variables_in_included_override_including(void)
+{
+	int i;
+
+	cl_git_mkfile("top-level", "[foo]\nbar = 1\n[include]\npath = included");
+	cl_git_mkfile("included", "[foo]\nbar = 2");
+
+	cl_git_pass(git_config_open_ondisk(&cfg, "top-level"));
+	cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar"));
+	cl_assert_equal_i(i, 2);
+
+	cl_git_pass(p_unlink("top-level"));
+	cl_git_pass(p_unlink("included"));
+}
+
+void test_config_include__variables_in_including_override_included(void)
+{
+	int i;
+
+	cl_git_mkfile("top-level", "[include]\npath = included\n[foo]\nbar = 1");
+	cl_git_mkfile("included", "[foo]\nbar = 2");
+
+	cl_git_pass(git_config_open_ondisk(&cfg, "top-level"));
+	cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar"));
+	cl_assert_equal_i(i, 1);
+
+	cl_git_pass(p_unlink("top-level"));
+	cl_git_pass(p_unlink("included"));
+}
diff --git a/tests/config/snapshot.c b/tests/config/snapshot.c
index 3ea07c1..61562d2 100644
--- a/tests/config/snapshot.c
+++ b/tests/config/snapshot.c
@@ -1,45 +1,49 @@
 #include "clar_libgit2.h"
 
-void test_config_snapshot__create_snapshot(void)
-{
-	int32_t tmp;
-	git_config *cfg, *snapshot, *new_snapshot;
-	const char *filename = "config-ext-change";
+static git_config *cfg;
+static git_config *snapshot;
 
-	cl_git_mkfile(filename, "[old]\nvalue = 5\n");
+void test_config_snapshot__cleanup(void)
+{
+	git_config_free(cfg);
+	cfg = NULL;
+	git_config_free(snapshot);
+	snapshot = NULL;
+}
 
-	cl_git_pass(git_config_open_ondisk(&cfg, filename));
+void test_config_snapshot__create_snapshot(void)
+{
+	int32_t i;
 
-	cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
-	cl_assert_equal_i(5, tmp);
+	cl_git_mkfile("config", "[old]\nvalue = 5\n");
+	cl_git_pass(git_config_open_ondisk(&cfg, "config"));
+	cl_git_pass(git_config_get_int32(&i, cfg, "old.value"));
+	cl_assert_equal_i(5, i);
 
 	cl_git_pass(git_config_snapshot(&snapshot, cfg));
 
 	/* Change the value on the file itself (simulate external process) */
-	cl_git_mkfile(filename, "[old]\nvalue = 56\n");
+	cl_git_mkfile("config", "[old]\nvalue = 56\n");
 
-	cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
-	cl_assert_equal_i(56, tmp);
-
-	cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value"));
-	cl_assert_equal_i(5, tmp);
+	cl_git_pass(git_config_get_int32(&i, cfg, "old.value"));
+	cl_assert_equal_i(56, i);
+	cl_git_pass(git_config_get_int32(&i, snapshot, "old.value"));
+	cl_assert_equal_i(5, i);
 
 	/* Change the value on the file itself (simulate external process) */
-	cl_git_mkfile(filename, "[old]\nvalue = 999\n");
+	cl_git_mkfile("config", "[old]\nvalue = 999\n");
 
-	cl_git_pass(git_config_snapshot(&new_snapshot, cfg));
+	/* Old snapshot should still have the old value */
+	cl_git_pass(git_config_get_int32(&i, snapshot, "old.value"));
+	cl_assert_equal_i(5, i);
 
 	/* New snapshot should see new value */
-	cl_git_pass(git_config_get_int32(&tmp, new_snapshot, "old.value"));
-	cl_assert_equal_i(999, tmp);
-
-	/* Old snapshot should still have the old value */
-	cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value"));
-	cl_assert_equal_i(5, tmp);
-	
-	git_config_free(new_snapshot);
 	git_config_free(snapshot);
-	git_config_free(cfg);
+	cl_git_pass(git_config_snapshot(&snapshot, cfg));
+	cl_git_pass(git_config_get_int32(&i, snapshot, "old.value"));
+	cl_assert_equal_i(999, i);
+
+	cl_git_pass(p_unlink("config"));
 }
 
 static int count_me(const git_config_entry *entry, void *payload)
@@ -55,24 +59,44 @@ static int count_me(const git_config_entry *entry, void *payload)
 
 void test_config_snapshot__multivar(void)
 {
-	int count = 0;
-	git_config *cfg, *snapshot;
-	const char *filename = "config-file";
-
-	cl_git_mkfile(filename, "[old]\nvalue = 5\nvalue = 6\n");
+	int count;
 
-	cl_git_pass(git_config_open_ondisk(&cfg, filename));
+	count = 0;
+	cl_git_mkfile("config", "[old]\nvalue = 5\nvalue = 6\n");
+	cl_git_pass(git_config_open_ondisk(&cfg, "config"));
 	cl_git_pass(git_config_get_multivar_foreach(cfg, "old.value", NULL, count_me, &count));
+	cl_assert_equal_i(2, count);
 
+	count = 0;
+	cl_git_pass(git_config_snapshot(&snapshot, cfg));
+	cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count));
 	cl_assert_equal_i(2, count);
 
+	cl_git_pass(p_unlink("config"));
+}
+
+void test_config_snapshot__includes(void)
+{
+	int i;
+
+	cl_git_mkfile("including", "[include]\npath = included");
+	cl_git_mkfile("included", "[section]\nkey = 1\n");
+
+	cl_git_pass(git_config_open_ondisk(&cfg, "including"));
 	cl_git_pass(git_config_snapshot(&snapshot, cfg));
-	git_config_free(cfg);
 
-	count = 0;
-	cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count));
+	cl_git_pass(git_config_get_int32(&i, snapshot, "section.key"));
+	cl_assert_equal_i(i, 1);
 
-	cl_assert_equal_i(2, count);
+	/* Rewrite "included" config */
+	cl_git_mkfile("included", "[section]\nkey = 11\n");
 
-	git_config_free(snapshot);
+	/* Assert that the live config changed, but snapshot remained the same */
+	cl_git_pass(git_config_get_int32(&i, cfg, "section.key"));
+	cl_assert_equal_i(i, 11);
+	cl_git_pass(git_config_get_int32(&i, snapshot, "section.key"));
+	cl_assert_equal_i(i, 1);
+
+	cl_git_pass(p_unlink("including"));
+	cl_git_pass(p_unlink("included"));
 }