diff --git a/builtin/config.c b/builtin/config.c index 0882899c3fbd2a..a7468e86d3e5b7 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -957,6 +957,17 @@ static void location_options_init(struct config_location_options *opts, } if (opts->use_global_config) { + /* + * Since global config is sourced from more than one location, + * read it using `do_git_config_sequence()` with other scopes + * ignored. However, writing global config should point to a + * single destination, set in `opts->source.file`. + */ + opts->options.ignore_repo = 1; + opts->options.ignore_cmdline = 1; + opts->options.ignore_worktree = 1; + opts->options.ignore_system = 1; + opts->source.file = opts->file_to_free = git_global_config(); if (!opts->source.file) /* diff --git a/config.c b/config.c index 1bdd702e7a3969..65be08c7b8eb9b 100644 --- a/config.c +++ b/config.c @@ -1544,11 +1544,26 @@ int git_config_system(void) return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); } +static void attempt_git_config_from_file_with_options(config_fn_t fn, + const char *filename, + void *data, + enum config_scope scope, + const struct config_options *opts, + int *success_count, + int *cumulative_ret) +{ + int ret = git_config_from_file_with_options(fn, filename, data, scope, opts); + if (!ret) + (*success_count)++; + *cumulative_ret += ret; +} + static int do_git_config_sequence(const struct config_options *opts, - const struct repository *repo, - config_fn_t fn, void *data) + const struct repository *repo, config_fn_t fn, + void *data, int require_successful_config) { int ret = 0; + int success_count = 0; char *system_config = git_system_config(); char *xdg_config = NULL; char *user_config = NULL; @@ -1571,44 +1586,54 @@ static int do_git_config_sequence(const struct config_options *opts, worktree_config = NULL; } - if (git_config_system() && system_config && + if (!opts->ignore_system && git_config_system() && system_config && !access_or_die(system_config, R_OK, opts->system_gently ? ACCESS_EACCES_OK : 0)) - ret += git_config_from_file_with_options(fn, system_config, - data, CONFIG_SCOPE_SYSTEM, - NULL); + attempt_git_config_from_file_with_options(fn, system_config, data, + CONFIG_SCOPE_SYSTEM, NULL, + &success_count, &ret); - git_global_config_paths(&user_config, &xdg_config); + if (!opts->ignore_global) { + git_global_config_paths(&user_config, &xdg_config); - if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) - ret += git_config_from_file_with_options(fn, xdg_config, data, - CONFIG_SCOPE_GLOBAL, NULL); + if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) + attempt_git_config_from_file_with_options(fn, xdg_config, + data, + CONFIG_SCOPE_GLOBAL, + NULL, &success_count, &ret); - if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) - ret += git_config_from_file_with_options(fn, user_config, data, - CONFIG_SCOPE_GLOBAL, NULL); + if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) + attempt_git_config_from_file_with_options(fn, user_config, + data, + CONFIG_SCOPE_GLOBAL, + NULL, &success_count, &ret); + + free(xdg_config); + free(user_config); + } if (!opts->ignore_repo && repo_config && !access_or_die(repo_config, R_OK, 0)) - ret += git_config_from_file_with_options(fn, repo_config, data, - CONFIG_SCOPE_LOCAL, NULL); + attempt_git_config_from_file_with_options(fn, repo_config, data, + CONFIG_SCOPE_LOCAL, NULL, &success_count, &ret); if (!opts->ignore_worktree && worktree_config && repo && repo->repository_format_worktree_config && - !access_or_die(worktree_config, R_OK, 0)) { - ret += git_config_from_file_with_options(fn, worktree_config, data, - CONFIG_SCOPE_WORKTREE, - NULL); - } + !access_or_die(worktree_config, R_OK, 0)) + attempt_git_config_from_file_with_options(fn, worktree_config, data, + CONFIG_SCOPE_WORKTREE, + NULL, &success_count, &ret); if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0) die(_("unable to parse command-line config")); free(system_config); - free(xdg_config); - free(user_config); free(repo_config); free(worktree_config); + + if (require_successful_config && !success_count && !ret) + ret = -1; + return ret; } @@ -1636,7 +1661,8 @@ int config_with_options(config_fn_t fn, void *data, */ if (config_source && config_source->use_stdin) { ret = git_config_from_stdin(fn, data, config_source->scope); - } else if (config_source && config_source->file) { + } else if (config_source && config_source->file && + config_source->scope != CONFIG_SCOPE_GLOBAL) { ret = git_config_from_file_with_options(fn, config_source->file, data, config_source->scope, NULL); @@ -1644,7 +1670,8 @@ int config_with_options(config_fn_t fn, void *data, ret = git_config_from_blob_ref(fn, repo, config_source->blob, data, config_source->scope); } else { - ret = do_git_config_sequence(opts, repo, fn, data); + ret = do_git_config_sequence(opts, repo, fn, data, + config_source && config_source->scope == CONFIG_SCOPE_GLOBAL); } if (inc.remote_urls) { diff --git a/config.h b/config.h index 31fe3e29611e11..eb2d7a2843c499 100644 --- a/config.h +++ b/config.h @@ -87,6 +87,8 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type, struct config_options { unsigned int respect_includes : 1; + unsigned int ignore_system : 1; + unsigned int ignore_global : 1; unsigned int ignore_repo : 1; unsigned int ignore_worktree : 1; unsigned int ignore_cmdline : 1; diff --git a/path.c b/path.c index c3a709a9284b7f..f17595fd1bbb2b 100644 --- a/path.c +++ b/path.c @@ -1544,19 +1544,23 @@ int looks_like_command_line_option(const char *str) char *xdg_config_home_for(const char *subdir, const char *filename) { + char *ret; const char *home, *config_home; assert(subdir); assert(filename); config_home = getenv("XDG_CONFIG_HOME"); if (config_home && *config_home) - return mkpathdup("%s/%s/%s", config_home, subdir, filename); - - home = getenv("HOME"); - if (home) - return mkpathdup("%s/.config/%s/%s", home, subdir, filename); + ret = mkpathdup("%s/%s/%s", config_home, subdir, filename); + else if ((home = getenv("HOME"))) + ret = mkpathdup("%s/.config/%s/%s", home, subdir, filename); + else + return NULL; - return NULL; +#ifdef GIT_WINDOWS_NATIVE + convert_slashes(ret); +#endif + return ret; } char *xdg_config_home(const char *filename) diff --git a/t/t1300-config.sh b/t/t1300-config.sh index e3f8064889210a..b51ddd4ce44eff 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2425,6 +2425,89 @@ test_expect_success '--show-scope with --default' ' test_cmp expect actual ' +test_expect_success 'list with nonexistent global config gracefully exits' ' + rm -f "$HOME"/.gitconfig "$HOME"/.config/git/config && + git config ${mode_prefix}list && + git config ${mode_prefix}list --show-scope +' + +test_expect_success 'list --global with nonexistent global config fails' ' + rm -f "$HOME"/.gitconfig "$HOME"/.config/git/config && + test_must_fail git config ${mode_prefix}list --global && + test_must_fail git config ${mode_prefix}list --global --show-scope +' + +test_expect_success 'list and get --global with only home' ' + rm -f "$HOME"/.config/git/config && + + test_when_finished rm -f \"\$HOME\"/.gitconfig && + cat >"$HOME"/.gitconfig <<-EOF && + [home] + config = true + EOF + + cat >expect <<-EOF && + global home.config=true + EOF + git config ${mode_prefix}list --global --show-scope >actual && + test_cmp expect actual && + + echo true >expect && + git config ${mode_get} --global home.config >actual && + test_cmp expect actual +' + +test_expect_success 'list and get --global with only xdg' ' + rm -f "$HOME"/.gitconfig && + + test_when_finished rm -rf \"\$HOME\"/.config/git && + mkdir -p "$HOME"/.config/git && + cat >"$HOME"/.config/git/config <<-EOF && + [xdg] + config = true + EOF + + cat >expect <<-EOF && + global xdg.config=true + EOF + git config ${mode_prefix}list --global --show-scope >actual && + test_cmp expect actual && + + echo true >expect && + git config ${mode_get} --global xdg.config >actual && + test_cmp expect actual +' + +test_expect_success 'list and get --global with both home and xdg' ' + test_when_finished rm -f \"\$HOME\"/.gitconfig && + cat >"$HOME"/.gitconfig <<-EOF && + [home] + config = home + EOF + + test_when_finished rm -rf \"\$HOME\"/.config/git && + mkdir -p "$HOME"/.config/git && + cat >"$HOME"/.config/git/config <<-EOF && + [xdg] + config = xdg + EOF + + cat >expect <<-EOF && + global file:$HOME/.config/git/config xdg.config=xdg + global file:$HOME/.gitconfig home.config=home + EOF + git config ${mode_prefix}list --global --show-scope --show-origin >actual && + test_cmp expect actual && + + echo xdg >expect && + git config ${mode_get} --global xdg.config >actual && + test_cmp expect actual && + + echo home >expect && + git config ${mode_get} --global home.config >actual && + test_cmp expect actual +' + test_expect_success 'override global and system config' ' test_when_finished rm -f \"\$HOME\"/.gitconfig && cat >"$HOME"/.gitconfig <<-EOF && diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index 40d3c42618c04f..3a9a04bcc143fd 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -52,6 +52,8 @@ test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' ' echo " name = read_gitconfig" >>.gitconfig && echo read_gitconfig >expected && git config --get user.name >actual && + test_cmp expected actual && + git config --global --get user.name >actual && test_cmp expected actual ' @@ -68,7 +70,8 @@ test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists' >.gitconfig && echo "[user]" >.gitconfig && echo " name = read_gitconfig" >>.gitconfig && - echo user.name=read_gitconfig >expected && + echo user.name=read_config >expected && + echo user.name=read_gitconfig >>expected && git config --global --list >actual && test_cmp expected actual '