Skip to content

Commit 9b25a0b

Browse files
peffgitster
authored andcommitted
config: add include directive
It can be useful to split your ~/.gitconfig across multiple files. For example, you might have a "main" file which is used on many machines, but a small set of per-machine tweaks. Or you may want to make some of your config public (e.g., clever aliases) while keeping other data back (e.g., your name or other identifying information). Or you may want to include a number of config options in some subset of your repos without copying and pasting (e.g., you want to reference them from the .git/config of participating repos). This patch introduces an include directive for config files. It looks like: [include] path = /path/to/file This is syntactically backwards-compatible with existing git config parsers (i.e., they will see it as another config entry and ignore it unless you are looking up include.path). The implementation provides a "git_config_include" callback which wraps regular config callbacks. Callers can pass it to git_config_from_file, and it will transparently follow any include directives, passing all of the discovered options to the real callback. Include directives are turned on automatically for "regular" git config parsing. This includes calls to git_config, as well as calls to the "git config" program that do not specify a single file (e.g., using "-f", "--global", etc). They are not turned on in other cases, including: 1. Parsing of other config-like files, like .gitmodules. There isn't a real need, and I'd rather be conservative and avoid unnecessary incompatibility or confusion. 2. Reading single files via "git config". This is for two reasons: a. backwards compatibility with scripts looking at config-like files. b. inspection of a specific file probably means you care about just what's in that file, not a general lookup for "do we have this value anywhere at all". If that is not the case, the caller can always specify "--includes". 3. Writing files via "git config"; we want to treat include.* variables as literal items to be copied (or modified), and not expand them. So "git config --unset-all foo.bar" would operate _only_ on .git/config, not any of its included files (just as it also does not operate on ~/.gitconfig). Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 4a7bb5b commit 9b25a0b

File tree

7 files changed

+292
-14
lines changed

7 files changed

+292
-14
lines changed

Documentation/config.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ customary UNIX fashion.
8484

8585
Some variables may require a special value format.
8686

87+
Includes
88+
~~~~~~~~
89+
90+
You can include one config file from another by setting the special
91+
`include.path` variable to the name of the file to be included. The
92+
included file is expanded immediately, as if its contents had been
93+
found at the location of the include directive. If the value of the
94+
`include.path` variable is a relative path, the path is considered to be
95+
relative to the configuration file in which the include directive was
96+
found. See below for examples.
97+
8798
Example
8899
~~~~~~~
89100

@@ -106,6 +117,10 @@ Example
106117
gitProxy="ssh" for "kernel.org"
107118
gitProxy=default-proxy ; for the rest
108119

120+
[include]
121+
path = /path/to/foo.inc ; include by absolute path
122+
path = foo ; expand "foo" relative to the current file
123+
109124
Variables
110125
~~~~~~~~~
111126

Documentation/git-config.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ See also <<FILES>>.
178178
Opens an editor to modify the specified config file; either
179179
'--system', '--global', or repository (default).
180180

181+
--includes::
182+
--no-includes::
183+
Respect `include.*` directives in config files when looking up
184+
values. Defaults to on.
185+
181186
[[FILES]]
182187
FILES
183188
-----

Documentation/technical/api-config.txt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,17 @@ while adjusting some of the default behavior of `git_config`. It should
5252
almost never be used by "regular" git code that is looking up
5353
configuration variables. It is intended for advanced callers like
5454
`git-config`, which are intentionally tweaking the normal config-lookup
55-
process. It takes one extra parameter:
55+
process. It takes two extra parameters:
5656

5757
`filename`::
5858
If this parameter is non-NULL, it specifies the name of a file to
5959
parse for configuration, rather than looking in the usual files. Regular
6060
`git_config` defaults to `NULL`.
6161

62+
`respect_includes`::
63+
Specify whether include directives should be followed in parsed files.
64+
Regular `git_config` defaults to `1`.
65+
6266
There is a special version of `git_config` called `git_config_early`.
6367
This version takes an additional parameter to specify the repository
6468
config, instead of having it looked up via `git_path`. This is useful
@@ -108,6 +112,28 @@ string is given, prints an error message and returns -1.
108112
Similar to `git_config_string`, but expands `~` or `~user` into the
109113
user's home directory when found at the beginning of the path.
110114

115+
Include Directives
116+
------------------
117+
118+
By default, the config parser does not respect include directives.
119+
However, a caller can use the special `git_config_include` wrapper
120+
callback to support them. To do so, you simply wrap your "real" callback
121+
function and data pointer in a `struct config_include_data`, and pass
122+
the wrapper to the regular config-reading functions. For example:
123+
124+
-------------------------------------------
125+
int read_file_with_include(const char *file, config_fn_t fn, void *data)
126+
{
127+
struct config_include_data inc = CONFIG_INCLUDE_INIT;
128+
inc.fn = fn;
129+
inc.data = data;
130+
return git_config_from_file(git_config_include, file, &inc);
131+
}
132+
-------------------------------------------
133+
134+
`git_config` respects includes automatically. The lower-level
135+
`git_config_from_file` does not.
136+
111137
Writing Config Files
112138
--------------------
113139

builtin/config.c

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ static const char *given_config_file;
2525
static int actions, types;
2626
static const char *get_color_slot, *get_colorbool_slot;
2727
static int end_null;
28+
static int respect_includes = -1;
2829

2930
#define ACTION_GET (1<<0)
3031
#define ACTION_GET_ALL (1<<1)
@@ -74,6 +75,7 @@ static struct option builtin_config_options[] = {
7475
OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
7576
OPT_GROUP("Other"),
7677
OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
78+
OPT_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"),
7779
OPT_END(),
7880
};
7981

@@ -161,6 +163,9 @@ static int get_value(const char *key_, const char *regex_)
161163
int ret = -1;
162164
char *global = NULL, *repo_config = NULL;
163165
const char *system_wide = NULL, *local;
166+
struct config_include_data inc = CONFIG_INCLUDE_INIT;
167+
config_fn_t fn;
168+
void *data;
164169

165170
local = given_config_file;
166171
if (!local) {
@@ -213,19 +218,28 @@ static int get_value(const char *key_, const char *regex_)
213218
}
214219
}
215220

221+
fn = show_config;
222+
data = NULL;
223+
if (respect_includes) {
224+
inc.fn = fn;
225+
inc.data = data;
226+
fn = git_config_include;
227+
data = &inc;
228+
}
229+
216230
if (do_all && system_wide)
217-
git_config_from_file(show_config, system_wide, NULL);
231+
git_config_from_file(fn, system_wide, data);
218232
if (do_all && global)
219-
git_config_from_file(show_config, global, NULL);
233+
git_config_from_file(fn, global, data);
220234
if (do_all)
221-
git_config_from_file(show_config, local, NULL);
222-
git_config_from_parameters(show_config, NULL);
235+
git_config_from_file(fn, local, data);
236+
git_config_from_parameters(fn, data);
223237
if (!do_all && !seen)
224-
git_config_from_file(show_config, local, NULL);
238+
git_config_from_file(fn, local, data);
225239
if (!do_all && !seen && global)
226-
git_config_from_file(show_config, global, NULL);
240+
git_config_from_file(fn, global, data);
227241
if (!do_all && !seen && system_wide)
228-
git_config_from_file(show_config, system_wide, NULL);
242+
git_config_from_file(fn, system_wide, data);
229243

230244
free(key);
231245
if (regexp) {
@@ -302,7 +316,7 @@ static void get_color(const char *def_color)
302316
get_color_found = 0;
303317
parsed_color[0] = '\0';
304318
git_config_with_options(git_get_color_config, NULL,
305-
given_config_file);
319+
given_config_file, respect_includes);
306320

307321
if (!get_color_found && def_color)
308322
color_parse(def_color, "command line", parsed_color);
@@ -330,7 +344,7 @@ static int get_colorbool(int print)
330344
get_colorbool_found = -1;
331345
get_diff_color_found = -1;
332346
git_config_with_options(git_get_colorbool_config, NULL,
333-
given_config_file);
347+
given_config_file, respect_includes);
334348

335349
if (get_colorbool_found < 0) {
336350
if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -387,6 +401,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
387401
given_config_file = given_config_file;
388402
}
389403

404+
if (respect_includes == -1)
405+
respect_includes = !given_config_file;
406+
390407
if (end_null) {
391408
term = '\0';
392409
delim = '\n';
@@ -424,7 +441,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
424441
if (actions == ACTION_LIST) {
425442
check_argc(argc, 0, 0);
426443
if (git_config_with_options(show_all_config, NULL,
427-
given_config_file) < 0) {
444+
given_config_file,
445+
respect_includes) < 0) {
428446
if (given_config_file)
429447
die_errno("unable to read config file '%s'",
430448
given_config_file);

cache.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1113,7 +1113,8 @@ extern int git_config_from_file(config_fn_t fn, const char *, void *);
11131113
extern void git_config_push_parameter(const char *text);
11141114
extern int git_config_from_parameters(config_fn_t fn, void *data);
11151115
extern int git_config(config_fn_t fn, void *);
1116-
extern int git_config_with_options(config_fn_t fn, void *, const char *filename);
1116+
extern int git_config_with_options(config_fn_t fn, void *,
1117+
const char *filename, int respect_includes);
11171118
extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
11181119
extern int git_parse_ulong(const char *, unsigned long *);
11191120
extern int git_config_int(const char *, const char *);
@@ -1140,6 +1141,14 @@ extern const char *get_commit_output_encoding(void);
11401141

11411142
extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
11421143

1144+
struct config_include_data {
1145+
int depth;
1146+
config_fn_t fn;
1147+
void *data;
1148+
};
1149+
#define CONFIG_INCLUDE_INIT { 0 }
1150+
extern int git_config_include(const char *name, const char *value, void *data);
1151+
11431152
#define MAX_GITNAME (1000)
11441153
extern char git_default_email[MAX_GITNAME];
11451154
extern char git_default_name[MAX_GITNAME];

config.c

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,69 @@ static config_file *cf;
2626

2727
static int zlib_compression_seen;
2828

29+
#define MAX_INCLUDE_DEPTH 10
30+
static const char include_depth_advice[] =
31+
"exceeded maximum include depth (%d) while including\n"
32+
" %s\n"
33+
"from\n"
34+
" %s\n"
35+
"Do you have circular includes?";
36+
static int handle_path_include(const char *path, struct config_include_data *inc)
37+
{
38+
int ret = 0;
39+
struct strbuf buf = STRBUF_INIT;
40+
41+
/*
42+
* Use an absolute path as-is, but interpret relative paths
43+
* based on the including config file.
44+
*/
45+
if (!is_absolute_path(path)) {
46+
char *slash;
47+
48+
if (!cf || !cf->name)
49+
return error("relative config includes must come from files");
50+
51+
slash = find_last_dir_sep(cf->name);
52+
if (slash)
53+
strbuf_add(&buf, cf->name, slash - cf->name + 1);
54+
strbuf_addstr(&buf, path);
55+
path = buf.buf;
56+
}
57+
58+
if (!access(path, R_OK)) {
59+
if (++inc->depth > MAX_INCLUDE_DEPTH)
60+
die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
61+
cf && cf->name ? cf->name : "the command line");
62+
ret = git_config_from_file(git_config_include, path, inc);
63+
inc->depth--;
64+
}
65+
strbuf_release(&buf);
66+
return ret;
67+
}
68+
69+
int git_config_include(const char *var, const char *value, void *data)
70+
{
71+
struct config_include_data *inc = data;
72+
const char *type;
73+
int ret;
74+
75+
/*
76+
* Pass along all values, including "include" directives; this makes it
77+
* possible to query information on the includes themselves.
78+
*/
79+
ret = inc->fn(var, value, inc->data);
80+
if (ret < 0)
81+
return ret;
82+
83+
type = skip_prefix(var, "include.");
84+
if (!type)
85+
return ret;
86+
87+
if (!strcmp(type, "path"))
88+
ret = handle_path_include(value, inc);
89+
return ret;
90+
}
91+
2992
static void lowercase(char *p)
3093
{
3194
for (; *p; p++)
@@ -913,10 +976,18 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
913976
}
914977

915978
int git_config_with_options(config_fn_t fn, void *data,
916-
const char *filename)
979+
const char *filename, int respect_includes)
917980
{
918981
char *repo_config = NULL;
919982
int ret;
983+
struct config_include_data inc = CONFIG_INCLUDE_INIT;
984+
985+
if (respect_includes) {
986+
inc.fn = fn;
987+
inc.data = data;
988+
fn = git_config_include;
989+
data = &inc;
990+
}
920991

921992
/*
922993
* If we have a specific filename, use it. Otherwise, follow the
@@ -934,7 +1005,7 @@ int git_config_with_options(config_fn_t fn, void *data,
9341005

9351006
int git_config(config_fn_t fn, void *data)
9361007
{
937-
return git_config_with_options(fn, data, NULL);
1008+
return git_config_with_options(fn, data, NULL, 1);
9381009
}
9391010

9401011
/*

0 commit comments

Comments
 (0)