This was originally a pull request to the git-for-windows repository [https://github.com/git-for-windows/git/pull/2399]. It adds a new option --show-scope which would allow a user to see what scope a given configuration value has (sytem, local, global, etc.). changes since v1: * Cleaned up meaning of scoping, specifically separating "repo" into "local" and "worktree" as well as expanding "cmdline" into "command" scoping, added submodule scoping * Taught struct git_config_source to remember the scope of the command that is filling it out * do_git_config_sequence() now remembers the previous scope so that if it is called recursively it can remember where it was. * Added documentation for this new option Matthew Rogers (4): config: fix typo in variable name config: fix config scope enum config: clarify meaning of command line scoping config: add '--show-scope' to print the scope of a config value Documentation/git-config.txt | 15 +++++--- builtin/config.c | 66 ++++++++++++++++++++++++++++++------ config.c | 15 ++++---- config.h | 19 ++++++----- remote.c | 3 +- submodule-config.c | 4 ++- t/helper/test-config.c | 8 +++-- t/t1300-config.sh | 50 +++++++++++++++++++++++++++ upload-pack.c | 3 +- 9 files changed, 148 insertions(+), 35 deletions(-) base-commit: 7a6a90c6ec48fc78c83d7090d6c1b95d8f3739c0 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-478%2FROGERSM94%2Fadd-config-flags-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-478/ROGERSM94/add-config-flags-v2 Pull-Request: https://github.com/gitgitgadget/git/pull/478 Range-diff vs v1: -: ---------- > 1: b40480f03a config: fix typo in variable name -: ---------- > 2: e8e05f3940 config: fix config scope enum -: ---------- > 3: 8225273546 config: clarify meaning of command line scoping 1: ec699bb3e6 ! 4: 92ce9b7824 config: allow user to know scope of config options @@ -1,21 +1,62 @@ Author: Matthew Rogers <mattr94@xxxxxxxxx> - config: allow user to know scope of config options + config: add '--show-scope' to print the scope of a config value - Add new option --show-scope which allows a user to know what the scope - of listed config options are (local/global/system/etc.). + When a user queries config values with --show-origin, often it's + difficult to determine what the actual "scope" (local, global, etc.) of + a given value is based on just the origin file. + + Teach 'git config' the '--show-scope' option to print the scope of all + displayed config values. Note that we should never see anything of + "submodule" scope as that is only ever used by submodule-config.c when + parsing the '.gitmodules' file. Signed-off-by: Matthew Rogers <mattr94@xxxxxxxxx> + diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt + --- a/Documentation/git-config.txt + +++ b/Documentation/git-config.txt +@@ + SYNOPSIS + -------- + [verse] +-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] name [value [value_regex]] ++'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] name [value [value_regex]] + 'git config' [<file-option>] [--type=<type>] --add name value + 'git config' [<file-option>] [--type=<type>] --replace-all name value [value_regex] +-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] --get name [value_regex] +-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] --get-all name [value_regex] +-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] [--name-only] --get-regexp name_regex [value_regex] ++'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] --get name [value_regex] ++'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] --get-all name [value_regex] ++'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--name-only] --get-regexp name_regex [value_regex] + 'git config' [<file-option>] [--type=<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>] [--show-origin] [-z|--null] [--name-only] -l | --list ++'git config' [<file-option>] [--show-origin] [--show-scope] [-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 +@@ + the actual origin (config file path, ref, or blob id if + applicable). + ++--show-scope:: ++ Similar to `--show-origin` in that it augments the output of ++ all queried config options with the scope of that value ++ (local, global, system, command). ++ + --get-colorbool name [stdout-is-tty]:: + + Find the color setting for `name` (e.g. `color.diff`) and output + diff --git a/builtin/config.c b/builtin/config.c --- a/builtin/config.c +++ b/builtin/config.c @@ - static struct git_config_source given_config_source; - static int actions, type; - static char *default_value; --static int end_null; -+static int end_nul; static int respect_includes_opt = -1; static struct config_options config_options; static int show_origin; @@ -24,69 +65,41 @@ #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ - OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), - OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE), - OPT_GROUP(N_("Other")), -- OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), -+ OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), OPT_BOOL(0, "includes", &respect_includes_opt, 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_BOOL(0, "show-scope", &show_scope, N_("show scope of config (system, global, local, command line)")), ++ OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")), OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")), OPT_END(), }; @@ - - static void show_config_origin(struct strbuf *buf) - { -- const char term = end_null ? '\0' : '\t'; -+ const char term = end_nul ? '\0' : '\t'; - - strbuf_addstr(buf, current_config_origin_type()); - strbuf_addch(buf, ':'); -- if (end_null) -+ if (end_nul) - strbuf_addstr(buf, current_config_name()); - else - quote_c_style(current_config_name(), buf, NULL, 0); strbuf_addch(buf, term); } +static const char *scope_to_string(enum config_scope scope) { -+ /* -+ * --local, --global, and --system work the same as --file so there's -+ * no easy way for the parser to tell the difference when it is -+ * setting the scope, so we use our information about which options -+ * were passed -+ */ -+ if (use_local_config || scope == CONFIG_SCOPE_REPO) { ++ switch (scope) { ++ case CONFIG_SCOPE_LOCAL: + return "local"; -+ } else if (use_global_config || scope == CONFIG_SCOPE_GLOBAL) { ++ case CONFIG_SCOPE_GLOBAL: + return "global"; -+ } else if (use_system_config || scope == CONFIG_SCOPE_SYSTEM) { ++ case CONFIG_SCOPE_SYSTEM: + return "system"; -+ } else if (given_config_source.use_stdin || -+ given_config_source.blob || -+ given_config_source.file || -+ scope == CONFIG_SCOPE_CMDLINE) { -+ return "command line"; -+ } else { ++ case CONFIG_SCOPE_WORKTREE: ++ return "worktree"; ++ case CONFIG_SCOPE_COMMAND: ++ return "command"; ++ case CONFIG_SCOPE_SUBMODULE: ++ return "submodule"; ++ default: + return "unknown"; + } +} + -+static void show_config_scope(struct strbuf *buf) -+{ ++static void show_config_scope(struct strbuf *buf) { + const char term = end_nul ? '\0' : '\t'; + const char *scope = scope_to_string(current_config_scope()); + -+ strbuf_addch(buf, '('); -+ if (end_nul) -+ strbuf_addstr(buf, N_(scope)); -+ else -+ quote_c_style(scope, buf, NULL, 0); -+ strbuf_addch(buf, ')'); ++ strbuf_addstr(buf, N_(scope)); + strbuf_addch(buf, term); +} + @@ -96,33 +109,188 @@ + if (show_origin || show_scope) { struct strbuf buf = STRBUF_INIT; - show_config_origin(&buf); -- /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ + if (show_scope) + show_config_scope(&buf); + if (show_origin) + show_config_origin(&buf); -+ /* Use fwrite as "buf" can contain \0's if "end_nul" is set. */ + /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); - } @@ static int format_config(struct strbuf *buf, const char *key_, const char *value_) { + if (show_scope) -+ show_config_scope(buf); ++ show_config_origin(buf); if (show_origin) show_config_origin(buf); if (show_keys) @@ - config_options.git_dir = get_git_dir(); + int nongit = !startup_info->have_repository; + char *value; + ++ ++ + given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); + + argc = parse_options(argc, argv, prefix, builtin_config_options, +@@ + !strcmp(given_config_source.file, "-")) { + given_config_source.file = NULL; + given_config_source.use_stdin = 1; ++ given_config_source.scope = CONFIG_SCOPE_COMMAND; + } + + if (use_global_config) { +@@ + */ + die(_("$HOME not set")); + ++ given_config_source.scope = CONFIG_SCOPE_GLOBAL; ++ + if (access_or_warn(user_config, R_OK, 0) && + xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { + given_config_source.file = xdg_config; +@@ + free(xdg_config); + } + } +- else if (use_system_config) ++ else if (use_system_config) { + given_config_source.file = git_etc_gitconfig(); +- else if (use_local_config) ++ given_config_source.scope = CONFIG_SCOPE_SYSTEM; ++ } else if (use_local_config) { + given_config_source.file = git_pathdup("config"); +- else if (use_worktree_config) { ++ given_config_source.scope = CONFIG_SCOPE_LOCAL; ++ } else if (use_worktree_config) { + struct worktree **worktrees = get_worktrees(0); + if (repository_format_worktree_config) + given_config_source.file = git_pathdup("config.worktree"); +@@ + "section in \"git help worktree\" for details")); + else + given_config_source.file = git_pathdup("config"); ++ given_config_source.scope = CONFIG_SCOPE_LOCAL; + free_worktrees(worktrees); + } else if (given_config_source.file) { + if (!is_absolute_path(given_config_source.file) && prefix) + given_config_source.file = + prefix_filename(prefix, given_config_source.file); ++ given_config_source.scope = CONFIG_SCOPE_COMMAND; ++ } else if (given_config_source.blob) { ++ given_config_source.scope = CONFIG_SCOPE_COMMAND; + } + ++ + if (respect_includes_opt == -1) + config_options.respect_includes = !given_config_source.file; + else + + diff --git a/config.c b/config.c + --- a/config.c + +++ b/config.c +@@ + char *xdg_config = xdg_config_home("config"); + char *user_config = expand_user_path("~/.gitconfig", 0); + char *repo_config; ++ enum config_scope prev_parsing_scope = current_parsing_scope; + + if (opts->commondir) + repo_config = mkpathdup("%s/config", opts->commondir); +@@ + if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0) + die(_("unable to parse command-line config")); + +- current_parsing_scope = CONFIG_SCOPE_UNKNOWN; ++ current_parsing_scope = prev_parsing_scope; + free(xdg_config); + free(user_config); + free(repo_config); +@@ + data = &inc; } -- if (end_null) { -+ if (end_nul) { - term = '\0'; - delim = '\n'; - key_delim = '\n'; ++ if (config_source) ++ current_parsing_scope = config_source->scope; ++ + /* + * If we have a specific filename, use it. Otherwise, follow the + * regular lookup sequence. + + diff --git a/config.h b/config.h + --- a/config.h + +++ b/config.h +@@ + + #define CONFIG_REGEX_NONE ((void *)1) + ++enum config_scope { ++ CONFIG_SCOPE_UNKNOWN = 0, ++ CONFIG_SCOPE_SYSTEM, ++ CONFIG_SCOPE_GLOBAL, ++ CONFIG_SCOPE_LOCAL, ++ CONFIG_SCOPE_WORKTREE, ++ CONFIG_SCOPE_COMMAND, ++ CONFIG_SCOPE_SUBMODULE, ++}; ++ + struct git_config_source { + unsigned int use_stdin:1; + const char *file; + const char *blob; ++ enum config_scope scope; + }; + + enum config_origin_type { +@@ + + int git_config_parse_parameter(const char *, config_fn_t fn, void *data); + +-enum config_scope { +- CONFIG_SCOPE_UNKNOWN = 0, +- CONFIG_SCOPE_SYSTEM, +- CONFIG_SCOPE_GLOBAL, +- CONFIG_SCOPE_LOCAL, +- CONFIG_SCOPE_WORKTREE, +- CONFIG_SCOPE_COMMAND, +-}; +- + enum config_scope current_config_scope(void); + const char *current_config_origin_type(void); + const char *current_config_name(void); + + diff --git a/submodule-config.c b/submodule-config.c + --- a/submodule-config.c + +++ b/submodule-config.c +@@ + static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void *data) + { + if (repo->worktree) { +- struct git_config_source config_source = { 0 }; ++ struct git_config_source config_source = { ++ 0, .scope = CONFIG_SCOPE_SUBMODULE ++ }; + const struct config_options opts = { 0 }; + struct object_id oid; + char *file; + + diff --git a/t/helper/test-config.c b/t/helper/test-config.c + --- a/t/helper/test-config.c + +++ b/t/helper/test-config.c +@@ + return "repo"; + case CONFIG_SCOPE_WORKTREE: + return "worktree"; ++ case CONFIG_SCOPE_SUBMODULE: ++ return "submodule"; + case CONFIG_SCOPE_COMMAND: +- return "command"; ++ return "cmdline"; + default: + return "unknown"; + } diff --git a/t/t1300-config.sh b/t/t1300-config.sh --- a/t/t1300-config.sh @@ -131,18 +299,17 @@ test_cmp expect output ' -+ +test_expect_success '--show-scope with --list' ' + cat >expect <<-EOF && -+ (global) user.global=true -+ (global) user.override=global -+ (global) include.path=$INCLUDE_DIR/absolute.include -+ (global) user.absolute=include -+ (local) user.local=true -+ (local) user.override=local -+ (local) include.path=../include/relative.include -+ (local) user.relative=include -+ (command line) user.cmdline=true ++ global user.global=true ++ global user.override=global ++ global include.path=$INCLUDE_DIR/absolute.include ++ global user.absolute=include ++ local user.local=true ++ local user.override=local ++ local include.path=../include/relative.include ++ local user.relative=include ++ command user.cmdline=true + EOF + git -c user.cmdline=true config --list --show-scope >output && + test_cmp expect output @@ -151,16 +318,16 @@ +test_expect_success !MINGW '--show-scope with --blob' ' + blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") && + cat >expect <<-EOF && -+ (command line) user.custom=true ++ command user.custom=true + EOF + git config --blob=$blob --show-scope --list >output && + test_cmp expect output +' +test_expect_success '--show-scope with --local' ' + cat >expect <<-\EOF && -+ (local) user.local=true -+ (local) user.override=local -+ (local) include.path=../include/relative.include ++ local user.local=true ++ local user.override=local ++ local include.path=../include/relative.include + EOF + git config --local --list --show-scope >output && + test_cmp expect output @@ -168,15 +335,15 @@ + +test_expect_success '--show-scope with --show-origin' ' + cat >expect <<-EOF && -+ (global) file:$HOME/.gitconfig user.global=true -+ (global) file:$HOME/.gitconfig user.override=global -+ (global) file:$HOME/.gitconfig include.path=$INCLUDE_DIR/absolute.include -+ (global) file:$INCLUDE_DIR/absolute.include user.absolute=include -+ (local) file:.git/config user.local=true -+ (local) file:.git/config user.override=local -+ (local) file:.git/config include.path=../include/relative.include -+ (local) file:.git/../include/relative.include user.relative=include -+ (command line) command line: user.cmdline=true ++ global file:$HOME/.gitconfig user.global=true ++ global file:$HOME/.gitconfig user.override=global ++ global file:$HOME/.gitconfig include.path=$INCLUDE_DIR/absolute.include ++ global file:$INCLUDE_DIR/absolute.include user.absolute=include ++ local file:.git/config user.local=true ++ local file:.git/config user.override=local ++ local file:.git/config include.path=../include/relative.include ++ local file:.git/../include/relative.include user.relative=include ++ command command line: user.cmdline=true + EOF + git -c user.cmdline=true config --list --show-origin --show-scope >output && + test_cmp expect output -- gitgitgadget