config: add '--show-origin' option to print the origin of a config value

If config values are queried using 'git config' (e.g. via --get, --get-all, --get-regexp, or --list flag) then it is sometimes hard to find the configuration file where the values were defined. Teach 'git config' the '--show-origin' option to print the source configuration file for every printed value. Based-on-patch-by: Jeff King <peff@peff.net> Signed-off-by: Lars Schneider <larsxschneider@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Lars Schneider committed Feb 19, 2016 at 10:16 UTC 70bd879ab66aeee809306908e3551d50cdf06802
3 files changed +191 -5
Documentation/git-config.txt
+11 -5
@@ -9,18 +9,18 @@ git-config - Get and set repository or global options
9 SYNOPSIS
10 --------
11 [verse]
12 -'git config' [<file-option>] [type] [-z|--null] name [value [value_regex]]
12 +'git config' [<file-option>] [type] [--show-origin] [-z|--null] name [value [value_regex]]
13 'git config' [<file-option>] [type] --add name value
14 'git config' [<file-option>] [type] --replace-all name value [value_regex]
15 -'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]
16 -'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
17 -'git config' [<file-option>] [type] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
15 +'git config' [<file-option>] [type] [--show-origin] [-z|--null] --get name [value_regex]
16 +'git config' [<file-option>] [type] [--show-origin] [-z|--null] --get-all name [value_regex]
17 +'git config' [<file-option>] [type] [--show-origin] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
18 'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL
19 'git config' [<file-option>] --unset name [value_regex]
20 'git config' [<file-option>] --unset-all name [value_regex]
21 'git config' [<file-option>] --rename-section old_name new_name
22 'git config' [<file-option>] --remove-section name
23 -'git config' [<file-option>] [-z|--null] [--name-only] -l | --list
23 +'git config' [<file-option>] [--show-origin] [-z|--null] [--name-only] -l | --list
24 'git config' [<file-option>] --get-color name [default]
25 'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
26 'git config' [<file-option>] -e | --edit
@@ -194,6 +194,12 @@ See also <<FILES>>.
194 Output only the names of config variables for `--list` or
195 `--get-regexp`.
196
197 +--show-origin::
198 + Augment the output of all queried config options with the
199 + origin type (file, standard input, blob, command line) and
200 + the actual origin (config file path, ref, or blob id if
201 + applicable).
202 +
203 --get-colorbool name [stdout-is-tty]::
204
205 Find the color setting for `name` (e.g. `color.diff`) and output
builtin/config.c
+33
@@ -3,6 +3,7 @@
3 #include "color.h"
4 #include "parse-options.h"
5 #include "urlmatch.h"
6 +#include "quote.h"
7
8 static const char *const builtin_config_usage[] = {
9 N_("git config [<options>]"),
@@ -27,6 +28,7 @@ static int actions, types;
28 static const char *get_color_slot, *get_colorbool_slot;
29 static int end_null;
30 static int respect_includes = -1;
31 +static int show_origin;
32
33 #define ACTION_GET (1<<0)
34 #define ACTION_GET_ALL (1<<1)
@@ -81,6 +83,7 @@ static struct option builtin_config_options[] = {
83 OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
84 OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
85 OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")),
86 + OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
87 OPT_END(),
88 };
89
@@ -91,8 +94,28 @@ static void check_argc(int argc, int min, int max) {
94 usage_with_options(builtin_config_usage, builtin_config_options);
95 }
96
97 +static void show_config_origin(struct strbuf *buf)
98 +{
99 + const char term = end_null ? '\0' : '\t';
100 +
101 + strbuf_addstr(buf, current_config_origin_type());
102 + strbuf_addch(buf, ':');
103 + if (end_null)
104 + strbuf_addstr(buf, current_config_name());
105 + else
106 + quote_c_style(current_config_name(), buf, NULL, 0);
107 + strbuf_addch(buf, term);
108 +}
109 +
110 static int show_all_config(const char *key_, const char *value_, void *cb)
111 {
112 + if (show_origin) {
113 + struct strbuf buf = STRBUF_INIT;
114 + show_config_origin(&buf);
115 + /* Use fwrite as "buf" can contain \0's if "end_null" is set. */
116 + fwrite(buf.buf, 1, buf.len, stdout);
117 + strbuf_release(&buf);
118 + }
119 if (!omit_values && value_)
120 printf("%s%c%s%c", key_, delim, value_, term);
121 else
@@ -108,6 +131,8 @@ struct strbuf_list {
131
132 static int format_config(struct strbuf *buf, const char *key_, const char *value_)
133 {
134 + if (show_origin)
135 + show_config_origin(buf);
136 if (show_keys)
137 strbuf_addstr(buf, key_);
138 if (!omit_values) {
@@ -538,6 +563,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
563 error("--name-only is only applicable to --list or --get-regexp");
564 usage_with_options(builtin_config_usage, builtin_config_options);
565 }
566 +
567 + if (show_origin && !(actions &
568 + (ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) {
569 + error("--show-origin is only applicable to --get, --get-all, "
570 + "--get-regexp, and --list.");
571 + usage_with_options(builtin_config_usage, builtin_config_options);
572 + }
573 +
574 if (actions == ACTION_LIST) {
575 check_argc(argc, 0, 0);
576 if (git_config_with_options(show_all_config, NULL,
t/t1300-repo-config.sh
+147
@@ -1209,4 +1209,151 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' '
1209 "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600"
1210 '
1211
1212 +test_expect_success 'set up --show-origin tests' '
1213 + INCLUDE_DIR="$HOME/include" &&
1214 + mkdir -p "$INCLUDE_DIR" &&
1215 + cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
1216 + [user]
1217 + absolute = include
1218 + EOF
1219 + cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
1220 + [user]
1221 + relative = include
1222 + EOF
1223 + cat >"$HOME"/.gitconfig <<-EOF &&
1224 + [user]
1225 + global = true
1226 + override = global
1227 + [include]
1228 + path = "$INCLUDE_DIR/absolute.include"
1229 + EOF
1230 + cat >.git/config <<-\EOF
1231 + [user]
1232 + local = true
1233 + override = local
1234 + [include]
1235 + path = ../include/relative.include
1236 + EOF
1237 +'
1238 +
1239 +test_expect_success '--show-origin with --list' '
1240 + cat >expect <<-EOF &&
1241 + file:$HOME/.gitconfig user.global=true
1242 + file:$HOME/.gitconfig user.override=global
1243 + file:$HOME/.gitconfig include.path=$INCLUDE_DIR/absolute.include
1244 + file:$INCLUDE_DIR/absolute.include user.absolute=include
1245 + file:.git/config user.local=true
1246 + file:.git/config user.override=local
1247 + file:.git/config include.path=../include/relative.include
1248 + file:.git/../include/relative.include user.relative=include
1249 + command line: user.cmdline=true
1250 + EOF
1251 + git -c user.cmdline=true config --list --show-origin >output &&
1252 + test_cmp expect output
1253 +'
1254 +
1255 +test_expect_success '--show-origin with --list --null' '
1256 + cat >expect <<-EOF &&
1257 + file:$HOME/.gitconfigQuser.global
1258 + trueQfile:$HOME/.gitconfigQuser.override
1259 + globalQfile:$HOME/.gitconfigQinclude.path
1260 + $INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
1261 + includeQfile:.git/configQuser.local
1262 + trueQfile:.git/configQuser.override
1263 + localQfile:.git/configQinclude.path
1264 + ../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
1265 + includeQcommand line:Quser.cmdline
1266 + trueQ
1267 + EOF
1268 + git -c user.cmdline=true config --null --list --show-origin >output.raw &&
1269 + nul_to_q <output.raw >output &&
1270 + # The here-doc above adds a newline that the --null output would not
1271 + # include. Add it here to make the two comparable.
1272 + echo >>output &&
1273 + test_cmp expect output
1274 +'
1275 +
1276 +test_expect_success '--show-origin with single file' '
1277 + cat >expect <<-\EOF &&
1278 + file:.git/config user.local=true
1279 + file:.git/config user.override=local
1280 + file:.git/config include.path=../include/relative.include
1281 + EOF
1282 + git config --local --list --show-origin >output &&
1283 + test_cmp expect output
1284 +'
1285 +
1286 +test_expect_success '--show-origin with --get-regexp' '
1287 + cat >expect <<-EOF &&
1288 + file:$HOME/.gitconfig user.global true
1289 + file:.git/config user.local true
1290 + EOF
1291 + git config --show-origin --get-regexp "user\.[g|l].*" >output &&
1292 + test_cmp expect output
1293 +'
1294 +
1295 +test_expect_success '--show-origin getting a single key' '
1296 + cat >expect <<-\EOF &&
1297 + file:.git/config local
1298 + EOF
1299 + git config --show-origin user.override >output &&
1300 + test_cmp expect output
1301 +'
1302 +
1303 +test_expect_success 'set up custom config file' '
1304 + CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
1305 + cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
1306 + [user]
1307 + custom = true
1308 + EOF
1309 +'
1310 +
1311 +test_expect_success '--show-origin escape special file name characters' '
1312 + cat >expect <<-\EOF &&
1313 + file:"file\" (dq) and spaces.conf" user.custom=true
1314 + EOF
1315 + git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
1316 + test_cmp expect output
1317 +'
1318 +
1319 +test_expect_success '--show-origin stdin' '
1320 + cat >expect <<-\EOF &&
1321 + standard input: user.custom=true
1322 + EOF
1323 + git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
1324 + test_cmp expect output
1325 +'
1326 +
1327 +test_expect_success '--show-origin stdin with file include' '
1328 + cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
1329 + [user]
1330 + stdin = include
1331 + EOF
1332 + cat >expect <<-EOF &&
1333 + file:$INCLUDE_DIR/stdin.include include
1334 + EOF
1335 + echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" \
1336 + | git config --show-origin --includes --file - user.stdin >output &&
1337 + test_cmp expect output
1338 +'
1339 +
1340 +test_expect_success '--show-origin blob' '
1341 + cat >expect <<-\EOF &&
1342 + blob:a9d9f9e555b5c6f07cbe09d3f06fe3df11e09c08 user.custom=true
1343 + EOF
1344 + blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
1345 + git config --blob=$blob --show-origin --list >output &&
1346 + test_cmp expect output
1347 +'
1348 +
1349 +test_expect_success '--show-origin blob ref' '
1350 + cat >expect <<-\EOF &&
1351 + blob:"master:file\" (dq) and spaces.conf" user.custom=true
1352 + EOF
1353 + git add "$CUSTOM_CONFIG_FILE" &&
1354 + git commit -m "new config file" &&
1355 + git config --blob=master:"$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
1356 + test_cmp expect output
1357 +'
1358 +
1359 test_done