Skip to content

Commit

Permalink
config: add '--show-origin' option to print the origin of a config value
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Lars Schneider authored and Junio C Hamano committed Feb 22, 2016
1 parent 473166b commit 70bd879
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 5 deletions.
16 changes: 11 additions & 5 deletions Documentation/git-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ git-config - Get and set repository or global options
SYNOPSIS
--------
[verse]
'git config' [<file-option>] [type] [-z|--null] name [value [value_regex]]
'git config' [<file-option>] [type] [--show-origin] [-z|--null] name [value [value_regex]]
'git config' [<file-option>] [type] --add name value
'git config' [<file-option>] [type] --replace-all name value [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
'git config' [<file-option>] [type] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
'git config' [<file-option>] [type] [--show-origin] [-z|--null] --get name [value_regex]
'git config' [<file-option>] [type] [--show-origin] [-z|--null] --get-all name [value_regex]
'git config' [<file-option>] [type] [--show-origin] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL
'git config' [<file-option>] --unset name [value_regex]
'git config' [<file-option>] --unset-all name [value_regex]
'git config' [<file-option>] --rename-section old_name new_name
'git config' [<file-option>] --remove-section name
'git config' [<file-option>] [-z|--null] [--name-only] -l | --list
'git config' [<file-option>] [--show-origin] [-z|--null] [--name-only] -l | --list
'git config' [<file-option>] --get-color name [default]
'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
'git config' [<file-option>] -e | --edit
Expand Down Expand Up @@ -194,6 +194,12 @@ See also <<FILES>>.
Output only the names of config variables for `--list` or
`--get-regexp`.

--show-origin::
Augment the output of all queried config options with the
origin type (file, standard input, blob, command line) and
the actual origin (config file path, ref, or blob id if
applicable).

--get-colorbool name [stdout-is-tty]::

Find the color setting for `name` (e.g. `color.diff`) and output
Expand Down
33 changes: 33 additions & 0 deletions builtin/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "color.h"
#include "parse-options.h"
#include "urlmatch.h"
#include "quote.h"

static const char *const builtin_config_usage[] = {
N_("git config [<options>]"),
Expand All @@ -27,6 +28,7 @@ static int actions, types;
static const char *get_color_slot, *get_colorbool_slot;
static int end_null;
static int respect_includes = -1;
static int show_origin;

#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
Expand Down Expand Up @@ -81,6 +83,7 @@ static struct option builtin_config_options[] = {
OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")),
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
OPT_END(),
};

Expand All @@ -91,8 +94,28 @@ static void check_argc(int argc, int min, int max) {
usage_with_options(builtin_config_usage, builtin_config_options);
}

static void show_config_origin(struct strbuf *buf)
{
const char term = end_null ? '\0' : '\t';

strbuf_addstr(buf, current_config_origin_type());
strbuf_addch(buf, ':');
if (end_null)
strbuf_addstr(buf, current_config_name());
else
quote_c_style(current_config_name(), buf, NULL, 0);
strbuf_addch(buf, term);
}

static int show_all_config(const char *key_, const char *value_, void *cb)
{
if (show_origin) {
struct strbuf buf = STRBUF_INIT;
show_config_origin(&buf);
/* Use fwrite as "buf" can contain \0's if "end_null" is set. */
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
}
if (!omit_values && value_)
printf("%s%c%s%c", key_, delim, value_, term);
else
Expand All @@ -108,6 +131,8 @@ struct strbuf_list {

static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
if (show_origin)
show_config_origin(buf);
if (show_keys)
strbuf_addstr(buf, key_);
if (!omit_values) {
Expand Down Expand Up @@ -538,6 +563,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
error("--name-only is only applicable to --list or --get-regexp");
usage_with_options(builtin_config_usage, builtin_config_options);
}

if (show_origin && !(actions &
(ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) {
error("--show-origin is only applicable to --get, --get-all, "
"--get-regexp, and --list.");
usage_with_options(builtin_config_usage, builtin_config_options);
}

if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
if (git_config_with_options(show_all_config, NULL,
Expand Down
147 changes: 147 additions & 0 deletions t/t1300-repo-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1209,4 +1209,151 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' '
"die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600"
'

test_expect_success 'set up --show-origin tests' '
INCLUDE_DIR="$HOME/include" &&
mkdir -p "$INCLUDE_DIR" &&
cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
[user]
absolute = include
EOF
cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
[user]
relative = include
EOF
cat >"$HOME"/.gitconfig <<-EOF &&
[user]
global = true
override = global
[include]
path = "$INCLUDE_DIR/absolute.include"
EOF
cat >.git/config <<-\EOF
[user]
local = true
override = local
[include]
path = ../include/relative.include
EOF
'

test_expect_success '--show-origin with --list' '
cat >expect <<-EOF &&
file:$HOME/.gitconfig user.global=true
file:$HOME/.gitconfig user.override=global
file:$HOME/.gitconfig include.path=$INCLUDE_DIR/absolute.include
file:$INCLUDE_DIR/absolute.include user.absolute=include
file:.git/config user.local=true
file:.git/config user.override=local
file:.git/config include.path=../include/relative.include
file:.git/../include/relative.include user.relative=include
command line: user.cmdline=true
EOF
git -c user.cmdline=true config --list --show-origin >output &&
test_cmp expect output
'

test_expect_success '--show-origin with --list --null' '
cat >expect <<-EOF &&
file:$HOME/.gitconfigQuser.global
trueQfile:$HOME/.gitconfigQuser.override
globalQfile:$HOME/.gitconfigQinclude.path
$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
includeQfile:.git/configQuser.local
trueQfile:.git/configQuser.override
localQfile:.git/configQinclude.path
../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
includeQcommand line:Quser.cmdline
trueQ
EOF
git -c user.cmdline=true config --null --list --show-origin >output.raw &&
nul_to_q <output.raw >output &&
# The here-doc above adds a newline that the --null output would not
# include. Add it here to make the two comparable.
echo >>output &&
test_cmp expect output
'

test_expect_success '--show-origin with single file' '
cat >expect <<-\EOF &&
file:.git/config user.local=true
file:.git/config user.override=local
file:.git/config include.path=../include/relative.include
EOF
git config --local --list --show-origin >output &&
test_cmp expect output
'

test_expect_success '--show-origin with --get-regexp' '
cat >expect <<-EOF &&
file:$HOME/.gitconfig user.global true
file:.git/config user.local true
EOF
git config --show-origin --get-regexp "user\.[g|l].*" >output &&
test_cmp expect output
'

test_expect_success '--show-origin getting a single key' '
cat >expect <<-\EOF &&
file:.git/config local
EOF
git config --show-origin user.override >output &&
test_cmp expect output
'

test_expect_success 'set up custom config file' '
CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
[user]
custom = true
EOF
'

test_expect_success '--show-origin escape special file name characters' '
cat >expect <<-\EOF &&
file:"file\" (dq) and spaces.conf" user.custom=true
EOF
git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
test_cmp expect output
'

test_expect_success '--show-origin stdin' '
cat >expect <<-\EOF &&
standard input: user.custom=true
EOF
git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
test_cmp expect output
'

test_expect_success '--show-origin stdin with file include' '
cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
[user]
stdin = include
EOF
cat >expect <<-EOF &&
file:$INCLUDE_DIR/stdin.include include
EOF
echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" \
| git config --show-origin --includes --file - user.stdin >output &&
test_cmp expect output
'

test_expect_success '--show-origin blob' '
cat >expect <<-\EOF &&
blob:a9d9f9e555b5c6f07cbe09d3f06fe3df11e09c08 user.custom=true
EOF
blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
git config --blob=$blob --show-origin --list >output &&
test_cmp expect output
'

test_expect_success '--show-origin blob ref' '
cat >expect <<-\EOF &&
blob:"master:file\" (dq) and spaces.conf" user.custom=true
EOF
git add "$CUSTOM_CONFIG_FILE" &&
git commit -m "new config file" &&
git config --blob=master:"$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
test_cmp expect output
'

test_done

0 comments on commit 70bd879

Please sign in to comment.