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>

Huynh Khoi Nguyen Nguyen committed Jun 22, 2012 at 11:03 UTC 21cf32279120799a766d22416be7d82d9ecfbd04
6 files changed +158 -21
Documentation/git-config.txt
+11 -3
@@ -99,8 +99,8 @@ OPTIONS
99 For writing options: write to global ~/.gitconfig file rather than
100 the repository .git/config.
101 +
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.
104 +
105 See also <<FILES>>.
106
@@ -194,7 +194,7 @@ See also <<FILES>>.
194 FILES
195 -----
196
197 -If not set explicitly with '--file', there are three files where
197 +If not set explicitly with '--file', there are four files where
198 'git config' will search for configuration options:
199
200 $GIT_DIR/config::
@@ -204,6 +204,14 @@ $GIT_DIR/config::
204 User-specific configuration file. Also called "global"
205 configuration file.
206
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 +
215 $(prefix)/etc/gitconfig::
216 System-wide configuration file.
217
builtin/config.c
+19 -9
@@ -161,7 +161,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
161 static int get_value(const char *key_, const char *regex_)
162 {
163 int ret = -1;
164 - char *global = NULL, *repo_config = NULL;
164 + char *global = NULL, *xdg = NULL, *repo_config = NULL;
165 const char *system_wide = NULL, *local;
166 struct config_include_data inc = CONFIG_INCLUDE_INIT;
167 config_fn_t fn;
@@ -169,12 +169,10 @@ static int get_value(const char *key_, const char *regex_)
169
170 local = given_config_file;
171 if (!local) {
172 - const char *home = getenv("HOME");
172 local = repo_config = git_pathdup("config");
174 - if (home)
175 - global = xstrdup(mkpath("%s/.gitconfig", home));
173 if (git_config_system())
174 system_wide = git_etc_gitconfig();
175 + home_config_paths(&global, &xdg, "config");
176 }
177
178 if (use_key_regexp) {
@@ -229,6 +227,8 @@ static int get_value(const char *key_, const char *regex_)
227
228 if (do_all && system_wide)
229 git_config_from_file(fn, system_wide, data);
230 + if (do_all && xdg)
231 + git_config_from_file(fn, xdg, data);
232 if (do_all && global)
233 git_config_from_file(fn, global, data);
234 if (do_all)
@@ -238,6 +238,8 @@ static int get_value(const char *key_, const char *regex_)
238 git_config_from_file(fn, local, data);
239 if (!do_all && !seen && global)
240 git_config_from_file(fn, global, data);
241 + if (!do_all && !seen && xdg)
242 + git_config_from_file(fn, xdg, data);
243 if (!do_all && !seen && system_wide)
244 git_config_from_file(fn, system_wide, data);
245
@@ -255,6 +257,7 @@ static int get_value(const char *key_, const char *regex_)
257 free_strings:
258 free(repo_config);
259 free(global);
260 + free(xdg);
261 return ret;
262 }
263
@@ -379,13 +382,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
382 }
383
384 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)
396 given_config_file = user_config;
386 - } else {
397 + else
398 die("$HOME not set");
388 - }
399 }
400 else if (use_system_config)
401 given_config_file = git_etc_gitconfig();
cache.h
+3
@@ -619,6 +619,8 @@ extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
619 __attribute__((format (printf, 3, 4)));
620 extern char *git_pathdup(const char *fmt, ...)
621 __attribute__((format (printf, 1, 2)));
622 +extern char *mkpathdup(const char *fmt, ...)
623 + __attribute__((format (printf, 1, 2)));
624
625 /* Return a statically allocated filename matching the sha1 signature */
626 extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
@@ -708,6 +710,7 @@ int set_shared_perm(const char *path, int mode);
710 int safe_create_leading_directories(char *path);
711 int safe_create_leading_directories_const(const char *path);
712 int mkdir_in_gitdir(const char *path);
713 +extern void home_config_paths(char **global, char **xdg, char *file);
714 extern char *expand_user_path(const char *path);
715 const char *enter_repo(const char *path, int strict);
716 static inline int is_absolute_path(const char *path)
config.c
+14 -9
@@ -929,7 +929,10 @@ int git_config_system(void)
929 int git_config_early(config_fn_t fn, void *data, const char *repo_config)
930 {
931 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");
936
937 if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
938 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)
940 found += 1;
941 }
942
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;
951 }
952
953 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)
966 break;
967 }
968
969 + free(xdg_config);
970 + free(user_config);
971 return ret == 0 ? found : ret;
972 }
973
path.c
+41
@@ -87,6 +87,21 @@ char *git_pathdup(const char *fmt, ...)
87 return xstrdup(path);
88 }
89
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 +
105 char *mkpath(const char *fmt, ...)
106 {
107 va_list args;
@@ -122,6 +137,32 @@ char *git_path(const char *fmt, ...)
137 return cleanup_path(pathname);
138 }
139
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 +
166 char *git_path_submodule(const char *path, const char *fmt, ...)
167 {
168 char *pathname = get_pathname();
t/t1306-xdg-files.sh new
+70
@@ -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