Commit af795759a7c1e753d440e3c3ca4dd5d8e391018c

Vicent Martí 2013-05-07T15:09:11

Merge pull request #1552 from carlosmn/config-helpers Config helpers for global/xdg config files

diff --git a/include/git2/config.h b/include/git2/config.h
index 5a2f956..724788a 100644
--- a/include/git2/config.h
+++ b/include/git2/config.h
@@ -195,6 +195,21 @@ GIT_EXTERN(int) git_config_open_level(
     unsigned int level);
 
 /**
+ * Open the global/XDG configuration file according to git's rules
+ *
+ * Git allows you to store your global configuration at
+ * `$HOME/.config` or `$XDG_CONFIG_HOME/git/config`. For backwards
+ * compatability, the XDG file shouldn't be used unless the use has
+ * created it explicitly. With this function you'll open the correct
+ * one to write to.
+ *
+ * @param out pointer in which to store the config object
+ * @param config the config object in which to look
+ */
+GIT_EXTERN(int) git_config_open_global(git_config **out, git_config *config);
+
+
+/**
  * Reload changed config files
  *
  * A config file may be changed on disk out from under the in-memory
diff --git a/src/config.c b/src/config.c
index 3f475ea..bd629f7 100644
--- a/src/config.c
+++ b/src/config.c
@@ -91,13 +91,15 @@ int git_config_add_file_ondisk(
 	int force)
 {
 	git_config_backend *file = NULL;
+	struct stat st;
 	int res;
 
 	assert(cfg && path);
 
-	if (!git_path_isfile(path)) {
-		giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path);
-		return GIT_ENOTFOUND;
+	res = p_stat(path, &st);
+	if (res < 0 && errno != ENOENT) {
+		giterr_set(GITERR_CONFIG, "Error stat'ing config file '%s'", path);
+		return -1;
 	}
 
 	if (git_config_file__ondisk(&file, path) < 0)
@@ -225,6 +227,14 @@ static int git_config__add_internal(
 	return 0;
 }
 
+int git_config_open_global(git_config **cfg_out, git_config *cfg)
+{
+	if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG))
+		return 0;
+
+	return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL);
+}
+
 int git_config_open_level(
     git_config **cfg_out,
     const git_config *cfg_parent,
@@ -381,7 +391,6 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
 
 	internal = git_vector_get(&cfg->files, 0);
 	if (!internal)
-		/* Should we auto-vivify .git/config? Tricky from this location */
 		return config_error_nofiles(name);
 	file = internal->file;
 
@@ -598,6 +607,33 @@ int git_config_find_system(char *system_config_path, size_t length)
 		system_config_path, length, git_config_find_system_r);
 }
 
+int git_config__global_location(git_buf *buf)
+{
+	const git_buf *paths;
+	const char *sep, *start;
+	size_t len;
+
+	if (git_futils_dirs_get(&paths, GIT_FUTILS_DIR_GLOBAL) < 0)
+		return -1;
+
+	/* no paths, so give up */
+	if (git_buf_len(paths) == 0)
+		return -1;
+
+	start = git_buf_cstr(paths);
+	sep = strchr(start, GIT_PATH_LIST_SEPARATOR);
+
+	if (sep)
+		len = sep - start;
+	else
+		len = paths->size;
+
+	if (git_buf_set(buf, start, len) < 0)
+		return -1;
+
+	return git_buf_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL);
+}
+
 int git_config_open_default(git_config **out)
 {
 	int error;
@@ -606,9 +642,12 @@ int git_config_open_default(git_config **out)
 
 	error = git_config_new(&cfg);
 
-	if (!error && !git_config_find_global_r(&buf))
+	if (!error && (!git_config_find_global_r(&buf) ||
+		       !git_config__global_location(&buf))) {
 		error = git_config_add_file_ondisk(cfg, buf.ptr,
 			GIT_CONFIG_LEVEL_GLOBAL, 0);
+	} else {
+	}
 
 	if (!error && !git_config_find_xdg_r(&buf))
 		error = git_config_add_file_ondisk(cfg, buf.ptr,
diff --git a/src/config.h b/src/config.h
index c43e47e..c5c11ae 100644
--- a/src/config.h
+++ b/src/config.h
@@ -28,6 +28,9 @@ extern int git_config_find_global_r(git_buf *global_config_path);
 extern int git_config_find_xdg_r(git_buf *system_config_path);
 extern int git_config_find_system_r(git_buf *system_config_path);
 
+
+extern int git_config__global_location(git_buf *buf);
+
 extern int git_config_rename_section(
 	git_repository *repo,
 	const char *old_section_name,	/* eg "branch.dummy" */
diff --git a/src/repository.c b/src/repository.c
index 44e7ca3..e2cedc0 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -594,6 +594,10 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo)
 		git_config_find_xdg_r(&xdg_buf);
 		git_config_find_system_r(&system_buf);
 
+		/* If there is no global file, open a backend for it anyway */
+		if (git_buf_len(&global_buf) == 0)
+			git_config__global_location(&global_buf);
+
 		error = load_config(
 			&config, repo,
 			path_unless_empty(&global_buf),
diff --git a/tests-clar/config/global.c b/tests-clar/config/global.c
new file mode 100644
index 0000000..2ecdf97
--- /dev/null
+++ b/tests-clar/config/global.c
@@ -0,0 +1,67 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "fileops.h"
+
+void test_config_global__initialize(void)
+{
+	git_buf path = GIT_BUF_INIT;
+
+	cl_must_pass(p_mkdir("home", 0777));
+	cl_git_pass(git_path_prettify(&path, "home", NULL));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+
+	cl_must_pass(p_mkdir("xdg", 0777));
+	cl_git_pass(git_path_prettify(&path, "xdg", NULL));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, NULL));
+
+	git_buf_free(&path);
+}
+
+void test_config_global__cleanup(void)
+{
+	cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES));
+	cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_config_global__open_global(void)
+{
+	git_config *cfg, *global, *selected, *dummy;
+
+	cl_git_pass(git_config_open_default(&cfg));
+	cl_git_pass(git_config_open_level(&global, cfg, GIT_CONFIG_LEVEL_GLOBAL));
+	cl_git_fail(git_config_open_level(&dummy, cfg, GIT_CONFIG_LEVEL_XDG));
+	cl_git_pass(git_config_open_global(&selected, cfg));
+
+	git_config_free(selected);
+	git_config_free(global);
+	git_config_free(cfg);
+}
+
+void test_config_global__open_xdg(void)
+{
+	git_config *cfg, *xdg, *selected;
+	const char *val, *str = "teststring";
+	const char *key = "this.variable";
+
+	p_setenv("XDG_CONFIG_HOME", "xdg", 1);
+
+	cl_must_pass(p_mkdir("xdg/git/", 0777));
+	cl_git_mkfile("xdg/git/config", "");
+
+	cl_git_pass(git_config_open_default(&cfg));
+	cl_git_pass(git_config_open_level(&xdg, cfg, GIT_CONFIG_LEVEL_XDG));
+	cl_git_pass(git_config_open_global(&selected, cfg));
+
+	cl_git_pass(git_config_set_string(xdg, key, str));
+	cl_git_pass(git_config_get_string(&val, selected, key));
+	cl_assert_equal_s(str, val);
+
+	git_config_free(selected);
+	git_config_free(xdg);
+	git_config_free(cfg);
+}
diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c
index c858268..9f943d0 100644
--- a/tests-clar/config/read.c
+++ b/tests-clar/config/read.c
@@ -449,10 +449,3 @@ void test_config_read__can_load_and_parse_an_empty_config_file(void)
 
 	git_config_free(cfg);
 }
-
-void test_config_read__cannot_load_a_non_existing_config_file(void)
-{
-	git_config *cfg;
-
-	cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_ondisk(&cfg, "./no.config"));
-}
diff --git a/tests-clar/repo/config.c b/tests-clar/repo/config.c
new file mode 100644
index 0000000..086fb5e
--- /dev/null
+++ b/tests-clar/repo/config.c
@@ -0,0 +1,75 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include <ctype.h>
+
+git_buf path = GIT_BUF_INIT;
+
+void test_repo_config__initialize(void)
+{
+	cl_fixture_sandbox("empty_standard_repo");
+	cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
+
+	git_buf_clear(&path);
+
+	cl_must_pass(p_mkdir("alternate", 0777));
+	cl_git_pass(git_path_prettify(&path, "alternate", NULL));
+
+}
+
+void test_repo_config__cleanup(void)
+{
+	cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES));
+
+	git_buf_free(&path);
+	cl_fixture_cleanup("empty_standard_repo");
+}
+
+void test_repo_config__open_missing_global(void)
+{
+	git_repository *repo;
+	git_config *config, *global;
+
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+	cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+	cl_git_pass(git_repository_config(&config, repo));
+	cl_git_pass(git_config_open_level(&global, config, GIT_CONFIG_LEVEL_GLOBAL));
+
+	cl_git_pass(git_config_set_string(global, "test.set", "42"));
+
+	git_config_free(global);
+	git_config_free(config);
+	git_repository_free(repo);
+}
+
+void test_repo_config__open_missing_global_with_separators(void)
+{
+	git_repository *repo;
+	git_config *config, *global;
+
+	cl_git_pass(git_buf_printf(&path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy"));
+
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+	git_buf_free(&path);
+
+	cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+	cl_git_pass(git_repository_config(&config, repo));
+	cl_git_pass(git_config_open_level(&global, config, GIT_CONFIG_LEVEL_GLOBAL));
+
+	cl_git_pass(git_config_set_string(global, "test.set", "42"));
+
+	git_config_free(global);
+	git_config_free(config);
+	git_repository_free(repo);
+}
diff --git a/tests-clar/repo/open.c b/tests-clar/repo/open.c
index 6b52537..8408585 100644
--- a/tests-clar/repo/open.c
+++ b/tests-clar/repo/open.c
@@ -309,7 +309,7 @@ void test_repo_open__no_config(void)
 	cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
 	cl_git_pass(git_repository_config(&config, repo));
 
-	cl_git_fail(git_config_set_string(config, "test.set", "42"));
+	cl_git_pass(git_config_set_string(config, "test.set", "42"));
 
 	git_config_free(config);
 	git_repository_free(repo);