config: read (but not write) from $XDG_CONFIG_HOME/git/config file

Teach git to read the "gitconfig" information from a new location,
$XDG_CONFIG_HOME/git/config; this allows the user to avoid
cluttering $HOME with many per-application configuration files.

In the order of reading, this file comes between the global
configuration file (typically $HOME/.gitconfig) and the system wide
configuration file (typically /etc/gitconfig).

We do not write to this new location (yet).

If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/config
will be used. This is in line with XDG specification.

If the new file does not exist, the behavior is unchanged.

Signed-off-by: Huynh Khoi Nguyen Nguyen <Huynh-Khoi-Nguyen.Nguyen@ensimag.imag.fr>
Signed-off-by: Valentin Duperray <Valentin.Duperray@ensimag.imag.fr>
Signed-off-by: Franck Jonas <Franck.Jonas@ensimag.imag.fr>
Signed-off-by: Lucien Kong <Lucien.Kong@ensimag.imag.fr>
Signed-off-by: Thomas Nguy <Thomas.Nguy@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Huynh Khoi Nguyen Nguyen 2012-06-22 11:03:23 +02:00 committed by Junio C Hamano
parent 0e18bef7e6
commit 21cf322791
6 changed files with 158 additions and 21 deletions

View File

@ -99,8 +99,8 @@ OPTIONS
For writing options: write to global ~/.gitconfig file rather than
the repository .git/config.
+
For reading options: read only from global ~/.gitconfig rather than
from all available files.
For reading options: read only from global ~/.gitconfig and from
$XDG_CONFIG_HOME/git/config rather than from all available files.
+
See also <<FILES>>.
@ -194,7 +194,7 @@ See also <<FILES>>.
FILES
-----
If not set explicitly with '--file', there are three files where
If not set explicitly with '--file', there are four files where
'git config' will search for configuration options:
$GIT_DIR/config::
@ -204,6 +204,14 @@ $GIT_DIR/config::
User-specific configuration file. Also called "global"
configuration file.
$XDG_CONFIG_HOME/git/config::
Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
or empty, $HOME/.config/git/config will be used. Any single-valued
variable set in this file will be overwritten by whatever is in
~/.gitconfig. It is a good idea not to create this file if
you sometimes use older versions of Git, as support for this
file was added fairly recently.
$(prefix)/etc/gitconfig::
System-wide configuration file.

View File

@ -161,7 +161,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
static int get_value(const char *key_, const char *regex_)
{
int ret = -1;
char *global = NULL, *repo_config = NULL;
char *global = NULL, *xdg = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
struct config_include_data inc = CONFIG_INCLUDE_INIT;
config_fn_t fn;
@ -169,12 +169,10 @@ static int get_value(const char *key_, const char *regex_)
local = given_config_file;
if (!local) {
const char *home = getenv("HOME");
local = repo_config = git_pathdup("config");
if (home)
global = xstrdup(mkpath("%s/.gitconfig", home));
if (git_config_system())
system_wide = git_etc_gitconfig();
home_config_paths(&global, &xdg, "config");
}
if (use_key_regexp) {
@ -229,6 +227,8 @@ static int get_value(const char *key_, const char *regex_)
if (do_all && system_wide)
git_config_from_file(fn, system_wide, data);
if (do_all && xdg)
git_config_from_file(fn, xdg, data);
if (do_all && global)
git_config_from_file(fn, global, data);
if (do_all)
@ -238,6 +238,8 @@ static int get_value(const char *key_, const char *regex_)
git_config_from_file(fn, local, data);
if (!do_all && !seen && global)
git_config_from_file(fn, global, data);
if (!do_all && !seen && xdg)
git_config_from_file(fn, xdg, data);
if (!do_all && !seen && system_wide)
git_config_from_file(fn, system_wide, data);
@ -255,6 +257,7 @@ static int get_value(const char *key_, const char *regex_)
free_strings:
free(repo_config);
free(global);
free(xdg);
return ret;
}
@ -379,13 +382,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
char *home = getenv("HOME");
if (home) {
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
char *user_config = NULL;
char *xdg_config = NULL;
home_config_paths(&user_config, &xdg_config, "config");
if (access(user_config, R_OK) && !access(xdg_config, R_OK) &&
(actions == ACTION_LIST ||
actions == ACTION_GET_COLOR ||
actions == ACTION_GET_COLORBOOL))
given_config_file = xdg_config;
else if (user_config)
given_config_file = user_config;
} else {
else
die("$HOME not set");
}
}
else if (use_system_config)
given_config_file = git_etc_gitconfig();

View File

@ -619,6 +619,8 @@ extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
extern char *git_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
extern char *mkpathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
/* Return a statically allocated filename matching the sha1 signature */
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
@ -708,6 +710,7 @@ int set_shared_perm(const char *path, int mode);
int safe_create_leading_directories(char *path);
int safe_create_leading_directories_const(const char *path);
int mkdir_in_gitdir(const char *path);
extern void home_config_paths(char **global, char **xdg, char *file);
extern char *expand_user_path(const char *path);
const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)

View File

@ -929,7 +929,10 @@ int git_config_system(void)
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
const char *home = NULL;
char *xdg_config = NULL;
char *user_config = NULL;
home_config_paths(&user_config, &xdg_config, "config");
if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
@ -937,14 +940,14 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
found += 1;
}
home = getenv("HOME");
if (home) {
char buf[PATH_MAX];
char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home);
if (!access(user_config, R_OK)) {
ret += git_config_from_file(fn, user_config, data);
found += 1;
}
if (!access(xdg_config, R_OK)) {
ret += git_config_from_file(fn, xdg_config, data);
found += 1;
}
if (!access(user_config, R_OK)) {
ret += git_config_from_file(fn, user_config, data);
found += 1;
}
if (repo_config && !access(repo_config, R_OK)) {
@ -963,6 +966,8 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
break;
}
free(xdg_config);
free(user_config);
return ret == 0 ? found : ret;
}

41
path.c
View File

@ -87,6 +87,21 @@ char *git_pathdup(const char *fmt, ...)
return xstrdup(path);
}
char *mkpathdup(const char *fmt, ...)
{
char *path;
struct strbuf sb = STRBUF_INIT;
va_list args;
va_start(args, fmt);
strbuf_vaddf(&sb, fmt, args);
va_end(args);
path = xstrdup(cleanup_path(sb.buf));
strbuf_release(&sb);
return path;
}
char *mkpath(const char *fmt, ...)
{
va_list args;
@ -122,6 +137,32 @@ char *git_path(const char *fmt, ...)
return cleanup_path(pathname);
}
void home_config_paths(char **global, char **xdg, char *file)
{
char *xdg_home = getenv("XDG_CONFIG_HOME");
char *home = getenv("HOME");
char *to_free = NULL;
if (!home) {
if (global)
*global = NULL;
} else {
if (!xdg_home) {
to_free = mkpathdup("%s/.config", home);
xdg_home = to_free;
}
if (global)
*global = mkpathdup("%s/.gitconfig", home);
}
if (!xdg_home)
*xdg = NULL;
else
*xdg = mkpathdup("%s/git/%s", xdg_home, file);
free(to_free);
}
char *git_path_submodule(const char *path, const char *fmt, ...)
{
char *pathname = get_pathname();

70
t/t1306-xdg-files.sh Executable file
View File

@ -0,0 +1,70 @@
#!/bin/sh
#
# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
# Thomas Nguy, Khoi Nguyen
# Grenoble INP Ensimag
#
test_description='Compatibility with $XDG_CONFIG_HOME/git/ files'
. ./test-lib.sh
test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
mkdir -p .config/git &&
echo "[alias]" >.config/git/config &&
echo " myalias = !echo in_config" >>.config/git/config &&
echo in_config >expected &&
git myalias >actual &&
test_cmp expected actual
'
test_expect_success 'read config: xdg file exists and ~/.gitconfig exists' '
>.gitconfig &&
echo "[alias]" >.gitconfig &&
echo " myalias = !echo in_gitconfig" >>.gitconfig &&
echo in_gitconfig >expected &&
git myalias >actual &&
test_cmp expected actual
'
test_expect_success 'read with --get: xdg file exists and ~/.gitconfig doesn'\''t' '
rm .gitconfig &&
echo "[user]" >.config/git/config &&
echo " name = read_config" >>.config/git/config &&
echo read_config >expected &&
git config --get user.name >actual &&
test_cmp expected actual
'
test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' '
>.gitconfig &&
echo "[user]" >.gitconfig &&
echo " name = read_gitconfig" >>.gitconfig &&
echo read_gitconfig >expected &&
git config --get user.name >actual &&
test_cmp expected actual
'
test_expect_success 'read with --list: xdg file exists and ~/.gitconfig doesn'\''t' '
rm .gitconfig &&
echo user.name=read_config >expected &&
git config --global --list >actual &&
test_cmp expected actual
'
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 &&
git config --global --list >actual &&
test_cmp expected actual
'
test_done