Git fork

Merge branch 'ls/config-origin'

The configuration system has been taught to phrase where it found a
bad configuration variable in a better way in its error messages.
"git config" learnt a new "--show-origin" option to indicate where
the values come from.

* ls/config-origin:
config: add '--show-origin' option to print the origin of a config value
config: add 'origin_type' to config_source struct
rename git_config_from_buf to git_config_from_mem
t: do not hide Git's exit code in tests using 'nul_to_q'

+237 -26
+11 -5
Documentation/git-config.txt
··· 9 9 SYNOPSIS 10 10 -------- 11 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 13 'git config' [<file-option>] [type] --add name value 14 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 18 'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL 19 19 'git config' [<file-option>] --unset name [value_regex] 20 20 'git config' [<file-option>] --unset-all name [value_regex] 21 21 'git config' [<file-option>] --rename-section old_name new_name 22 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 24 'git config' [<file-option>] --get-color name [default] 25 25 'git config' [<file-option>] --get-colorbool name [stdout-is-tty] 26 26 'git config' [<file-option>] -e | --edit ··· 193 193 --name-only:: 194 194 Output only the names of config variables for `--list` or 195 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). 196 202 197 203 --get-colorbool name [stdout-is-tty]:: 198 204
+33
builtin/config.c
··· 3 3 #include "color.h" 4 4 #include "parse-options.h" 5 5 #include "urlmatch.h" 6 + #include "quote.h" 6 7 7 8 static const char *const builtin_config_usage[] = { 8 9 N_("git config [<options>]"), ··· 27 28 static const char *get_color_slot, *get_colorbool_slot; 28 29 static int end_null; 29 30 static int respect_includes = -1; 31 + static int show_origin; 30 32 31 33 #define ACTION_GET (1<<0) 32 34 #define ACTION_GET_ALL (1<<1) ··· 81 83 OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), 82 84 OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), 83 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)")), 84 87 OPT_END(), 85 88 }; 86 89 ··· 91 94 usage_with_options(builtin_config_usage, builtin_config_options); 92 95 } 93 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 + 94 110 static int show_all_config(const char *key_, const char *value_, void *cb) 95 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 + } 96 119 if (!omit_values && value_) 97 120 printf("%s%c%s%c", key_, delim, value_, term); 98 121 else ··· 108 131 109 132 static int format_config(struct strbuf *buf, const char *key_, const char *value_) 110 133 { 134 + if (show_origin) 135 + show_config_origin(buf); 111 136 if (show_keys) 112 137 strbuf_addstr(buf, key_); 113 138 if (!omit_values) { ··· 538 563 error("--name-only is only applicable to --list or --get-regexp"); 539 564 usage_with_options(builtin_config_usage, builtin_config_options); 540 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 + 541 574 if (actions == ACTION_LIST) { 542 575 check_argc(argc, 0, 0); 543 576 if (git_config_with_options(show_all_config, NULL,
+4 -2
cache.h
··· 1508 1508 typedef int (*config_fn_t)(const char *, const char *, void *); 1509 1509 extern int git_default_config(const char *, const char *, void *); 1510 1510 extern int git_config_from_file(config_fn_t fn, const char *, void *); 1511 - extern int git_config_from_buf(config_fn_t fn, const char *name, 1512 - const char *buf, size_t len, void *data); 1511 + extern int git_config_from_mem(config_fn_t fn, const char *origin_type, 1512 + const char *name, const char *buf, size_t len, void *data); 1513 1513 extern void git_config_push_parameter(const char *text); 1514 1514 extern int git_config_from_parameters(config_fn_t fn, void *data); 1515 1515 extern void git_config(config_fn_t fn, void *); ··· 1548 1548 extern const char *get_commit_output_encoding(void); 1549 1549 1550 1550 extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data); 1551 + extern const char *current_config_origin_type(void); 1552 + extern const char *current_config_name(void); 1551 1553 1552 1554 struct config_include_data { 1553 1555 int depth;
+25 -11
config.c
··· 24 24 size_t pos; 25 25 } buf; 26 26 } u; 27 + const char *origin_type; 27 28 const char *name; 28 29 const char *path; 29 30 int die_on_error; ··· 471 472 break; 472 473 } 473 474 if (cf->die_on_error) 474 - die(_("bad config file line %d in %s"), cf->linenr, cf->name); 475 + die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name); 475 476 else 476 - return error(_("bad config file line %d in %s"), cf->linenr, cf->name); 477 + return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name); 477 478 } 478 479 479 480 static int parse_unit_factor(const char *end, uintmax_t *val) ··· 588 589 if (!value) 589 590 value = ""; 590 591 591 - if (cf && cf->name) 592 - die(_("bad numeric config value '%s' for '%s' in %s: %s"), 593 - value, name, cf->name, reason); 592 + if (cf && cf->origin_type && cf->name) 593 + die(_("bad numeric config value '%s' for '%s' in %s %s: %s"), 594 + value, name, cf->origin_type, cf->name, reason); 594 595 die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason); 595 596 } 596 597 ··· 1061 1062 } 1062 1063 1063 1064 static int do_config_from_file(config_fn_t fn, 1064 - const char *name, const char *path, FILE *f, void *data) 1065 + const char *origin_type, const char *name, const char *path, FILE *f, 1066 + void *data) 1065 1067 { 1066 1068 struct config_source top; 1067 1069 1068 1070 top.u.file = f; 1071 + top.origin_type = origin_type; 1069 1072 top.name = name; 1070 1073 top.path = path; 1071 1074 top.die_on_error = 1; ··· 1078 1081 1079 1082 static int git_config_from_stdin(config_fn_t fn, void *data) 1080 1083 { 1081 - return do_config_from_file(fn, "<stdin>", NULL, stdin, data); 1084 + return do_config_from_file(fn, "standard input", "", NULL, stdin, data); 1082 1085 } 1083 1086 1084 1087 int git_config_from_file(config_fn_t fn, const char *filename, void *data) ··· 1089 1092 f = fopen(filename, "r"); 1090 1093 if (f) { 1091 1094 flockfile(f); 1092 - ret = do_config_from_file(fn, filename, filename, f, data); 1095 + ret = do_config_from_file(fn, "file", filename, filename, f, data); 1093 1096 funlockfile(f); 1094 1097 fclose(f); 1095 1098 } 1096 1099 return ret; 1097 1100 } 1098 1101 1099 - int git_config_from_buf(config_fn_t fn, const char *name, const char *buf, 1100 - size_t len, void *data) 1102 + int git_config_from_mem(config_fn_t fn, const char *origin_type, 1103 + const char *name, const char *buf, size_t len, void *data) 1101 1104 { 1102 1105 struct config_source top; 1103 1106 1104 1107 top.u.buf.buf = buf; 1105 1108 top.u.buf.len = len; 1106 1109 top.u.buf.pos = 0; 1110 + top.origin_type = origin_type; 1107 1111 top.name = name; 1108 1112 top.path = NULL; 1109 1113 top.die_on_error = 0; ··· 1132 1136 return error("reference '%s' does not point to a blob", name); 1133 1137 } 1134 1138 1135 - ret = git_config_from_buf(fn, name, buf, size, data); 1139 + ret = git_config_from_mem(fn, "blob", name, buf, size, data); 1136 1140 free(buf); 1137 1141 1138 1142 return ret; ··· 2407 2411 2408 2412 return 0; 2409 2413 } 2414 + 2415 + const char *current_config_origin_type(void) 2416 + { 2417 + return cf && cf->origin_type ? cf->origin_type : "command line"; 2418 + } 2419 + 2420 + const char *current_config_name(void) 2421 + { 2422 + return cf && cf->name ? cf->name : ""; 2423 + }
+2 -2
submodule-config.c
··· 427 427 parameter.commit_sha1 = commit_sha1; 428 428 parameter.gitmodules_sha1 = sha1; 429 429 parameter.overwrite = 0; 430 - git_config_from_buf(parse_config, rev.buf, config, config_size, 431 - &parameter); 430 + git_config_from_mem(parse_config, "submodule-blob", rev.buf, 431 + config, config_size, &parameter); 432 432 free(config); 433 433 434 434 switch (lookup_type) {
+158 -3
t/t1300-repo-config.sh
··· 700 700 git config aninvalid.unit >actual && 701 701 test_cmp expect actual && 702 702 cat >expect <<-\EOF && 703 - fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'' in .git/config: invalid unit 703 + fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'' in file .git/config: invalid unit 704 704 EOF 705 705 test_must_fail git config --int --get aninvalid.unit 2>actual && 706 706 test_i18ncmp expect actual 707 + ' 708 + 709 + test_expect_success 'invalid stdin config' ' 710 + echo "fatal: bad config line 1 in standard input " >expect && 711 + echo "[broken" | test_must_fail git config --list --file - >output 2>&1 && 712 + test_cmp expect output 707 713 ' 708 714 709 715 cat > expect << EOF ··· 957 963 Qsection.sub=section.val5Q 958 964 EOF 959 965 test_expect_success '--null --list' ' 960 - git config --null --list | nul_to_q >result && 966 + git config --null --list >result.raw && 967 + nul_to_q <result.raw >result && 961 968 echo >>result && 962 969 test_cmp expect result 963 970 ' 964 971 965 972 test_expect_success '--null --get-regexp' ' 966 - git config --null --get-regexp "val[0-9]" | nul_to_q >result && 973 + git config --null --get-regexp "val[0-9]" >result.raw && 974 + nul_to_q <result.raw >result && 967 975 echo >>result && 968 976 test_cmp expect result 969 977 ' ··· 1199 1207 git config --rename-section imap pop && 1200 1208 perl -e \ 1201 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 1202 1357 ' 1203 1358 1204 1359 test_done
+2 -2
t/t1308-config-set.sh
··· 195 195 cp .git/config .git/config.old && 196 196 test_when_finished "mv .git/config.old .git/config" && 197 197 echo "[" >>.git/config && 198 - echo "fatal: bad config file line 34 in .git/config" >expect && 198 + echo "fatal: bad config line 34 in file .git/config" >expect && 199 199 test_expect_code 128 test-config get_value foo.bar 2>actual && 200 200 test_cmp expect actual 201 201 ' 202 202 203 203 test_expect_success 'proper error on error in custom config files' ' 204 204 echo "[" >>syntax-error && 205 - echo "fatal: bad config file line 1 in syntax-error" >expect && 205 + echo "fatal: bad config line 1 in file syntax-error" >expect && 206 206 test_expect_code 128 test-config configset_get_value foo.bar syntax-error 2>actual && 207 207 test_cmp expect actual 208 208 '
+2 -1
t/t7008-grep-binary.sh
··· 141 141 test_cmp expect actual && 142 142 echo "b diff" >.gitattributes && 143 143 echo "b:binQary" >expect && 144 - git grep bin b | nul_to_q >actual && 144 + git grep bin b >actual.raw && 145 + nul_to_q <actual.raw >actual && 145 146 test_cmp expect actual 146 147 ' 147 148