From a4b75dcf56bc8e6d295cae89e2f5871c4707af21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 6 May 2013 21:51:25 +0200 Subject: [PATCH 1/2] repo: unconditionally create a global config backend When a repository is initialised, we need to probe to see if there is a global config to load. If this is not the case, the user isn't able to write to the global config without creating the backend and adding it themselves, which is inconvenient and overly complex. Unconditionally create and add a backend for the global config file regardless of whether it exists as a convenience for users. To enable this, we allow creating backends to files that do not exist yet, changing the semantics somewhat, and making some tests invalid. --- src/config.c | 41 +++++++++++++++++++--- src/config.h | 3 ++ src/repository.c | 4 +++ tests-clar/config/read.c | 7 ---- tests-clar/repo/config.c | 75 ++++++++++++++++++++++++++++++++++++++++ tests-clar/repo/open.c | 2 +- 6 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 tests-clar/repo/config.c diff --git a/src/config.c b/src/config.c index 3f475ea63..99aa00f50 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) @@ -381,7 +383,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 +599,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 +634,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 c43e47e82..c5c11ae14 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 44e7ca3c4..e2cedc0f7 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/read.c b/tests-clar/config/read.c index c85826886..9f943d0f6 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 000000000..086fb5e4f --- /dev/null +++ b/tests-clar/repo/config.c @@ -0,0 +1,75 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include + +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 6b5253797..840858586 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); From 5d8318875fc7234d00140ce1f18b67bc4703e5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 7 May 2013 00:10:02 +0200 Subject: [PATCH 2/2] config: convenience function to open global/xdg The rules for which one to open is a bit silly, so let's make it easier for our users. --- include/git2/config.h | 15 +++++++++ src/config.c | 8 +++++ tests-clar/config/global.c | 67 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 tests-clar/config/global.c diff --git a/include/git2/config.h b/include/git2/config.h index 5a2f956fd..724788ae0 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -194,6 +194,21 @@ GIT_EXTERN(int) git_config_open_level( const git_config *parent, 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 * diff --git a/src/config.c b/src/config.c index 99aa00f50..bd629f7c4 100644 --- a/src/config.c +++ b/src/config.c @@ -227,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, diff --git a/tests-clar/config/global.c b/tests-clar/config/global.c new file mode 100644 index 000000000..2ecdf97d8 --- /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); +}