Skip to content

Commit 21cf322

Browse files
Huynh Khoi Nguyen Nguyengitster
authored andcommitted
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 <[email protected]> Signed-off-by: Valentin Duperray <[email protected]> Signed-off-by: Franck Jonas <[email protected]> Signed-off-by: Lucien Kong <[email protected]> Signed-off-by: Thomas Nguy <[email protected]> Signed-off-by: Matthieu Moy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0e18bef commit 21cf322

File tree

6 files changed

+158
-21
lines changed

6 files changed

+158
-21
lines changed

Documentation/git-config.txt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ OPTIONS
9999
For writing options: write to global ~/.gitconfig file rather than
100100
the repository .git/config.
101101
+
102-
For reading options: read only from global ~/.gitconfig rather than
103-
from all available files.
102+
For reading options: read only from global ~/.gitconfig and from
103+
$XDG_CONFIG_HOME/git/config rather than from all available files.
104104
+
105105
See also <<FILES>>.
106106

@@ -194,7 +194,7 @@ See also <<FILES>>.
194194
FILES
195195
-----
196196

197-
If not set explicitly with '--file', there are three files where
197+
If not set explicitly with '--file', there are four files where
198198
'git config' will search for configuration options:
199199

200200
$GIT_DIR/config::
@@ -204,6 +204,14 @@ $GIT_DIR/config::
204204
User-specific configuration file. Also called "global"
205205
configuration file.
206206

207+
$XDG_CONFIG_HOME/git/config::
208+
Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
209+
or empty, $HOME/.config/git/config will be used. Any single-valued
210+
variable set in this file will be overwritten by whatever is in
211+
~/.gitconfig. It is a good idea not to create this file if
212+
you sometimes use older versions of Git, as support for this
213+
file was added fairly recently.
214+
207215
$(prefix)/etc/gitconfig::
208216
System-wide configuration file.
209217

builtin/config.c

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,18 @@ static int show_config(const char *key_, const char *value_, void *cb)
161161
static int get_value(const char *key_, const char *regex_)
162162
{
163163
int ret = -1;
164-
char *global = NULL, *repo_config = NULL;
164+
char *global = NULL, *xdg = NULL, *repo_config = NULL;
165165
const char *system_wide = NULL, *local;
166166
struct config_include_data inc = CONFIG_INCLUDE_INIT;
167167
config_fn_t fn;
168168
void *data;
169169

170170
local = given_config_file;
171171
if (!local) {
172-
const char *home = getenv("HOME");
173172
local = repo_config = git_pathdup("config");
174-
if (home)
175-
global = xstrdup(mkpath("%s/.gitconfig", home));
176173
if (git_config_system())
177174
system_wide = git_etc_gitconfig();
175+
home_config_paths(&global, &xdg, "config");
178176
}
179177

180178
if (use_key_regexp) {
@@ -229,6 +227,8 @@ static int get_value(const char *key_, const char *regex_)
229227

230228
if (do_all && system_wide)
231229
git_config_from_file(fn, system_wide, data);
230+
if (do_all && xdg)
231+
git_config_from_file(fn, xdg, data);
232232
if (do_all && global)
233233
git_config_from_file(fn, global, data);
234234
if (do_all)
@@ -238,6 +238,8 @@ static int get_value(const char *key_, const char *regex_)
238238
git_config_from_file(fn, local, data);
239239
if (!do_all && !seen && global)
240240
git_config_from_file(fn, global, data);
241+
if (!do_all && !seen && xdg)
242+
git_config_from_file(fn, xdg, data);
241243
if (!do_all && !seen && system_wide)
242244
git_config_from_file(fn, system_wide, data);
243245

@@ -255,6 +257,7 @@ static int get_value(const char *key_, const char *regex_)
255257
free_strings:
256258
free(repo_config);
257259
free(global);
260+
free(xdg);
258261
return ret;
259262
}
260263

@@ -379,13 +382,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
379382
}
380383

381384
if (use_global_config) {
382-
char *home = getenv("HOME");
383-
if (home) {
384-
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
385+
char *user_config = NULL;
386+
char *xdg_config = NULL;
387+
388+
home_config_paths(&user_config, &xdg_config, "config");
389+
390+
if (access(user_config, R_OK) && !access(xdg_config, R_OK) &&
391+
(actions == ACTION_LIST ||
392+
actions == ACTION_GET_COLOR ||
393+
actions == ACTION_GET_COLORBOOL))
394+
given_config_file = xdg_config;
395+
else if (user_config)
385396
given_config_file = user_config;
386-
} else {
397+
else
387398
die("$HOME not set");
388-
}
389399
}
390400
else if (use_system_config)
391401
given_config_file = git_etc_gitconfig();

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
619619
__attribute__((format (printf, 3, 4)));
620620
extern char *git_pathdup(const char *fmt, ...)
621621
__attribute__((format (printf, 1, 2)));
622+
extern char *mkpathdup(const char *fmt, ...)
623+
__attribute__((format (printf, 1, 2)));
622624

623625
/* Return a statically allocated filename matching the sha1 signature */
624626
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
@@ -708,6 +710,7 @@ int set_shared_perm(const char *path, int mode);
708710
int safe_create_leading_directories(char *path);
709711
int safe_create_leading_directories_const(const char *path);
710712
int mkdir_in_gitdir(const char *path);
713+
extern void home_config_paths(char **global, char **xdg, char *file);
711714
extern char *expand_user_path(const char *path);
712715
const char *enter_repo(const char *path, int strict);
713716
static inline int is_absolute_path(const char *path)

config.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -929,22 +929,25 @@ int git_config_system(void)
929929
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
930930
{
931931
int ret = 0, found = 0;
932-
const char *home = NULL;
932+
char *xdg_config = NULL;
933+
char *user_config = NULL;
934+
935+
home_config_paths(&user_config, &xdg_config, "config");
933936

934937
if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
935938
ret += git_config_from_file(fn, git_etc_gitconfig(),
936939
data);
937940
found += 1;
938941
}
939942

940-
home = getenv("HOME");
941-
if (home) {
942-
char buf[PATH_MAX];
943-
char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home);
944-
if (!access(user_config, R_OK)) {
945-
ret += git_config_from_file(fn, user_config, data);
946-
found += 1;
947-
}
943+
if (!access(xdg_config, R_OK)) {
944+
ret += git_config_from_file(fn, xdg_config, data);
945+
found += 1;
946+
}
947+
948+
if (!access(user_config, R_OK)) {
949+
ret += git_config_from_file(fn, user_config, data);
950+
found += 1;
948951
}
949952

950953
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)
963966
break;
964967
}
965968

969+
free(xdg_config);
970+
free(user_config);
966971
return ret == 0 ? found : ret;
967972
}
968973

path.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ char *git_pathdup(const char *fmt, ...)
8787
return xstrdup(path);
8888
}
8989

90+
char *mkpathdup(const char *fmt, ...)
91+
{
92+
char *path;
93+
struct strbuf sb = STRBUF_INIT;
94+
va_list args;
95+
96+
va_start(args, fmt);
97+
strbuf_vaddf(&sb, fmt, args);
98+
va_end(args);
99+
path = xstrdup(cleanup_path(sb.buf));
100+
101+
strbuf_release(&sb);
102+
return path;
103+
}
104+
90105
char *mkpath(const char *fmt, ...)
91106
{
92107
va_list args;
@@ -122,6 +137,32 @@ char *git_path(const char *fmt, ...)
122137
return cleanup_path(pathname);
123138
}
124139

140+
void home_config_paths(char **global, char **xdg, char *file)
141+
{
142+
char *xdg_home = getenv("XDG_CONFIG_HOME");
143+
char *home = getenv("HOME");
144+
char *to_free = NULL;
145+
146+
if (!home) {
147+
if (global)
148+
*global = NULL;
149+
} else {
150+
if (!xdg_home) {
151+
to_free = mkpathdup("%s/.config", home);
152+
xdg_home = to_free;
153+
}
154+
if (global)
155+
*global = mkpathdup("%s/.gitconfig", home);
156+
}
157+
158+
if (!xdg_home)
159+
*xdg = NULL;
160+
else
161+
*xdg = mkpathdup("%s/git/%s", xdg_home, file);
162+
163+
free(to_free);
164+
}
165+
125166
char *git_path_submodule(const char *path, const char *fmt, ...)
126167
{
127168
char *pathname = get_pathname();

t/t1306-xdg-files.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
4+
# Thomas Nguy, Khoi Nguyen
5+
# Grenoble INP Ensimag
6+
#
7+
8+
test_description='Compatibility with $XDG_CONFIG_HOME/git/ files'
9+
10+
. ./test-lib.sh
11+
12+
test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
13+
mkdir -p .config/git &&
14+
echo "[alias]" >.config/git/config &&
15+
echo " myalias = !echo in_config" >>.config/git/config &&
16+
echo in_config >expected &&
17+
git myalias >actual &&
18+
test_cmp expected actual
19+
'
20+
21+
22+
test_expect_success 'read config: xdg file exists and ~/.gitconfig exists' '
23+
>.gitconfig &&
24+
echo "[alias]" >.gitconfig &&
25+
echo " myalias = !echo in_gitconfig" >>.gitconfig &&
26+
echo in_gitconfig >expected &&
27+
git myalias >actual &&
28+
test_cmp expected actual
29+
'
30+
31+
32+
test_expect_success 'read with --get: xdg file exists and ~/.gitconfig doesn'\''t' '
33+
rm .gitconfig &&
34+
echo "[user]" >.config/git/config &&
35+
echo " name = read_config" >>.config/git/config &&
36+
echo read_config >expected &&
37+
git config --get user.name >actual &&
38+
test_cmp expected actual
39+
'
40+
41+
42+
test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' '
43+
>.gitconfig &&
44+
echo "[user]" >.gitconfig &&
45+
echo " name = read_gitconfig" >>.gitconfig &&
46+
echo read_gitconfig >expected &&
47+
git config --get user.name >actual &&
48+
test_cmp expected actual
49+
'
50+
51+
52+
test_expect_success 'read with --list: xdg file exists and ~/.gitconfig doesn'\''t' '
53+
rm .gitconfig &&
54+
echo user.name=read_config >expected &&
55+
git config --global --list >actual &&
56+
test_cmp expected actual
57+
'
58+
59+
60+
test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists' '
61+
>.gitconfig &&
62+
echo "[user]" >.gitconfig &&
63+
echo " name = read_gitconfig" >>.gitconfig &&
64+
echo user.name=read_gitconfig >expected &&
65+
git config --global --list >actual &&
66+
test_cmp expected actual
67+
'
68+
69+
70+
test_done

0 commit comments

Comments
 (0)