Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions builtin/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/*
Expand Down
4 changes: 3 additions & 1 deletion builtin/var.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ static char *git_attr_val_global(int ident_flag UNUSED)

static char *git_config_val_system(int ident_flag UNUSED)
{
if (git_config_system()) {
const struct config_options opts = { 0 };

if (git_config_system(&opts)) {
char *file = git_system_config();
normalize_path_copy(file, file);
return file;
Expand Down
50 changes: 36 additions & 14 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1554,16 +1554,18 @@ void git_global_config_paths(char **user_out, char **xdg_out)
*xdg_out = xdg_config;
}

int git_config_system(void)
int git_config_system(const struct config_options *opts)
{
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
return !opts->ignore_system && !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
}

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_global_scope_success)
{
int tmp_ret;
int ret = 0;
int global_had_success = 0;
char *system_config = git_system_config();
char *xdg_config = NULL;
char *user_config = NULL;
Expand All @@ -1586,7 +1588,7 @@ static int do_git_config_sequence(const struct config_options *opts,
worktree_config = NULL;
}

if (git_config_system() && system_config &&
if (git_config_system(opts) && 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,
Expand All @@ -1595,13 +1597,25 @@ static int do_git_config_sequence(const struct config_options *opts,

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)) {
tmp_ret = git_config_from_file_with_options(fn, xdg_config,
data, CONFIG_SCOPE_GLOBAL,
NULL);
ret += tmp_ret;
if (!tmp_ret)
global_had_success = 1;
}

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)) {
tmp_ret = git_config_from_file_with_options(fn, user_config,
data, CONFIG_SCOPE_GLOBAL,
NULL);
ret += tmp_ret;
if (!tmp_ret)
global_had_success = 1;
}

if (!opts->ignore_repo && repo_config &&
!access_or_die(repo_config, R_OK, 0))
Expand All @@ -1624,6 +1638,10 @@ static int do_git_config_sequence(const struct config_options *opts,
free(user_config);
free(repo_config);
free(worktree_config);

if (require_global_scope_success && !global_had_success && !ret)
ret = -1;

return ret;
}

Expand All @@ -1646,11 +1664,15 @@ int config_with_options(config_fn_t fn, void *data,
}

/*
* If we have a specific filename, use it. Otherwise, follow the
* regular lookup sequence.
* Use the specified file when provided, except for the global scope,
* which can come from more than one file. With no explicit source,
* follow the regular lookup sequence.
*/
if (config_source && config_source->use_stdin) {
ret = git_config_from_stdin(fn, data, config_source->scope);
} else if (config_source && config_source->file &&
config_source->scope == CONFIG_SCOPE_GLOBAL) {
ret = do_git_config_sequence(opts, repo, fn, data, 1);
} else if (config_source && config_source->file) {
ret = git_config_from_file_with_options(fn, config_source->file,
data, config_source->scope,
Expand All @@ -1659,7 +1681,7 @@ 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, 0);
}

if (inc.remote_urls) {
Expand Down
3 changes: 2 additions & 1 deletion config.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ 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_repo : 1;
unsigned int ignore_worktree : 1;
unsigned int ignore_cmdline : 1;
Expand Down Expand Up @@ -423,7 +424,7 @@ int repo_config_rename_section(struct repository *, const char *, const char *);
int repo_config_rename_section_in_file(struct repository *, const char *, const char *, const char *);
int repo_config_copy_section(struct repository *, const char *, const char *);
int repo_config_copy_section_in_file(struct repository *, const char *, const char *, const char *);
int git_config_system(void);
int git_config_system(const struct config_options *opts);
int config_error_nonbool(const char *);
#if defined(__GNUC__)
#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
Expand Down
116 changes: 116 additions & 0 deletions t/t1300-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,122 @@ 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 uses HOME and XDG_CONFIG_HOME to determine the location of the
# XDG config file. On Windows, the default values lead to mixed forward
# and backward slashes in the output --show-origin path for XDG config.
# Explicitly set XDG_CONFIG_HOME with only forward slashes, allowing us
# to use the same expected path across all platforms.
XDG_CONFIG_HOME="$HOME/.config" \
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 'list --global ignores other scopes' '
test_when_finished rm -f system_config &&
cat >system_config <<-EOF &&
[system]
config = true
EOF

test_config core.repositoryformatversion 1 &&
test_config extensions.worktreeConfig true &&
test_config --worktree worktree.config true &&

test_when_finished rm -f \"\$HOME\"/.gitconfig &&
cat >"$HOME"/.gitconfig <<-EOF &&
[home]
config = home
EOF

test_config repo.config true &&

echo "global home.config=home" >expect &&
GIT_CONFIG_NOSYSTEM=false GIT_CONFIG_SYSTEM=system_config \
git -c cmdline.config=true config ${mode_prefix}list \
--global --show-scope >actual &&
test_cmp expect actual
'

test_expect_success 'override global and system config' '
test_when_finished rm -f \"\$HOME\"/.gitconfig &&
cat >"$HOME"/.gitconfig <<-EOF &&
Expand Down
5 changes: 4 additions & 1 deletion t/t1306-xdg-files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
'

Expand All @@ -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
'
Expand Down
Loading