From: Philip Oakley <philipoakley@iee.email>
To: Emily Shaffer <emilyshaffer@google.com>, git@vger.kernel.org
Subject: Re: [RFC PATCH 2/2] config: add 'config.superproject' file
Date: Fri, 9 Apr 2021 12:10:27 +0100 [thread overview]
Message-ID: <e19a250a-c4ca-fc32-83f9-a03aa03cd88a@iee.email> (raw)
In-Reply-To: <20210408233936.533342-3-emilyshaffer@google.com>
On 09/04/2021 00:39, Emily Shaffer wrote:
> Some configs, such as wrapper directives like gerrit.createChangeId, or
> forthcoming hook configs, should apply to a superproject as well as all
> its submodules. It may not be appropriate to apply them globally - for
> example, if the user also contributes to many projects which do not use
> the configs necessary for one project-with-submodules - and it may be
> burdensome to apply them locally to the superproject and each of its
> submodules. Even if the user runs 'git submodule foreach "git config
> --local foo.bar', if a new submodule is added later on, that config is
> not applied to the new submodule.
>
> It is also inappropriate to share the entire superproject config, since
> some items - like remote URLs or partial-clone filters - would not apply
> to a submodule.
>
> To make life easier for projects with many submodules, then, create a
> new "config.superproject" config scope, which is included in the config
> parse for the superproject as well as for all the submodules of that
> superproject.
>
> For the superproject, this new config file is equally local to the local
> config; for the submodule, the new config file is less local than the
> local config. So let's include it directly before the local config
> during the config parse.
>
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> ---
Does this need an update to the `git config --show-origin --show-scope`
capability?
--
Philip
> Documentation/git-config.txt | 21 +++++-
> builtin/config.c | 10 ++-
> config.c | 26 +++++++
> config.h | 3 +
> submodule.c | 29 ++++++++
> submodule.h | 6 ++
> t/t1311-superproject-config.sh | 124 +++++++++++++++++++++++++++++++++
> 7 files changed, 216 insertions(+), 3 deletions(-)
> create mode 100755 t/t1311-superproject-config.sh
>
> diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
> index 4b4cc5c5e8..a33136fb08 100644
> --- a/Documentation/git-config.txt
> +++ b/Documentation/git-config.txt
> @@ -48,7 +48,7 @@ unset an existing `--type` specifier with `--no-type`.
>
> When reading, the values are read from the system, global and
> repository local configuration files by default, and options
> -`--system`, `--global`, `--local`, `--worktree` and
> +`--system`, `--global`, `--superproject`, `--local`, `--worktree` and
> `--file <filename>` can be used to tell the command to read from only
> that location (see <<FILES>>).
>
> @@ -127,6 +127,17 @@ rather than from all available files.
> +
> See also <<FILES>>.
>
> +--superproject::
> + For writing options: write to the superproject's
> + `.git/config.superproject` file, even if run from a submodule of that
> + superproject.
> ++
> +For reading options: read only from the superproject's
> +`.git/config.superproject` file, even if run from a submodule of that
> +superproject, rather than from all available files.
> ++
> +See also <<FILES>>.
> +
> --local::
> For writing options: write to the repository `.git/config` file.
> This is the default behavior.
> @@ -283,7 +294,7 @@ The default is to use a pager.
> FILES
> -----
>
> -If not set explicitly with `--file`, there are four files where
> +If not set explicitly with `--file`, there are five files where
> 'git config' will search for configuration options:
>
> $(prefix)/etc/gitconfig::
> @@ -301,6 +312,12 @@ $XDG_CONFIG_HOME/git/config::
> User-specific configuration file. Also called "global"
> configuration file.
>
> +$GIT_DIR/config.superproject::
> + When `git config` is run from a project which is a submodule of another
> + project, that superproject's $GIT_DIR will be used. Use this config file
> + to set configurations which need to be the same across a superproject
> + and all its submodules.
> +
> $GIT_DIR/config::
> Repository specific configuration file.
>
> diff --git a/builtin/config.c b/builtin/config.c
> index f71fa39b38..f0a57a89ca 100644
> --- a/builtin/config.c
> +++ b/builtin/config.c
> @@ -26,7 +26,7 @@ static char key_delim = ' ';
> static char term = '\n';
>
> static int use_global_config, use_system_config, use_local_config;
> -static int use_worktree_config;
> +static int use_worktree_config, use_superproject_config;
> static struct git_config_source given_config_source;
> static int actions, type;
> static char *default_value;
> @@ -130,6 +130,8 @@ static struct option builtin_config_options[] = {
> OPT_GROUP(N_("Config file location")),
> OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
> OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
> + OPT_BOOL(0, "superproject",
> + &use_superproject_config, N_("use superproject config file")),
> OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
> OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")),
> OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
> @@ -697,6 +699,12 @@ int cmd_config(int argc, const char **argv, const char *prefix)
> else if (use_system_config) {
> given_config_source.file = git_etc_gitconfig();
> given_config_source.scope = CONFIG_SCOPE_SYSTEM;
> + } else if (use_superproject_config) {
> + struct strbuf superproject_cfg = STRBUF_INIT;
> + git_config_superproject(&superproject_cfg, get_git_dir());
> + given_config_source.file = xstrdup(superproject_cfg.buf);
> + given_config_source.scope = CONFIG_SCOPE_SUPERPROJECT;
> + strbuf_release(&superproject_cfg);
> } else if (use_local_config) {
> given_config_source.file = git_pathdup("config");
> given_config_source.scope = CONFIG_SCOPE_LOCAL;
> diff --git a/config.c b/config.c
> index 67d9bf2238..28bb80fd0d 100644
> --- a/config.c
> +++ b/config.c
> @@ -21,6 +21,7 @@
> #include "dir.h"
> #include "color.h"
> #include "refs.h"
> +#include "submodule.h"
>
> struct config_source {
> struct config_source *prev;
> @@ -1852,6 +1853,17 @@ const char *git_etc_gitconfig(void)
> return system_wide;
> }
>
> +void git_config_superproject(struct strbuf *sb, const char *gitdir)
> +{
> + if (!get_superproject_gitdir(sb)) {
> + /* not a submodule */
> + strbuf_reset(sb);
> + strbuf_addstr(sb, gitdir);
> + }
> +
> + strbuf_addstr(sb, "/config.superproject");
> +}
> +
> /*
> * Parse environment variable 'k' as a boolean (in various
> * possible spellings); if missing, use the default value 'def'.
> @@ -1909,6 +1921,17 @@ static int do_git_config_sequence(const struct config_options *opts,
> if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
> ret += git_config_from_file(fn, user_config, data);
>
> + current_parsing_scope = CONFIG_SCOPE_SUPERPROJECT;
> + if (opts->git_dir && !opts->ignore_superproject) {
> +
> + struct strbuf superproject_gitdir = STRBUF_INIT;
> + git_config_superproject(&superproject_gitdir, opts->git_dir);
> + if (!access_or_die(superproject_gitdir.buf, R_OK, 0))
> + ret += git_config_from_file(fn, superproject_gitdir.buf, data);
> +
> + strbuf_release(&superproject_gitdir);
> + }
> +
> current_parsing_scope = CONFIG_SCOPE_LOCAL;
> if (!opts->ignore_repo && repo_config &&
> !access_or_die(repo_config, R_OK, 0))
> @@ -2027,6 +2050,7 @@ void read_very_early_config(config_fn_t cb, void *data)
>
> opts.respect_includes = 1;
> opts.ignore_repo = 1;
> + opts.ignore_superproject = 1;
> opts.ignore_worktree = 1;
> opts.ignore_cmdline = 1;
> opts.system_gently = 1;
> @@ -3515,6 +3539,8 @@ const char *config_scope_name(enum config_scope scope)
> return "command";
> case CONFIG_SCOPE_GITMODULES:
> return "gitmodules";
> + case CONFIG_SCOPE_SUPERPROJECT:
> + return "superproject";
> default:
> return "unknown";
> }
> diff --git a/config.h b/config.h
> index 535f5517b8..b42e1d13eb 100644
> --- a/config.h
> +++ b/config.h
> @@ -43,6 +43,7 @@ enum config_scope {
> CONFIG_SCOPE_WORKTREE,
> CONFIG_SCOPE_COMMAND,
> CONFIG_SCOPE_GITMODULES,
> + CONFIG_SCOPE_SUPERPROJECT,
> };
> const char *config_scope_name(enum config_scope scope);
>
> @@ -84,6 +85,7 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,
> struct config_options {
> unsigned int respect_includes : 1;
> unsigned int ignore_repo : 1;
> + unsigned int ignore_superproject : 1;
> unsigned int ignore_worktree : 1;
> unsigned int ignore_cmdline : 1;
> unsigned int system_gently : 1;
> @@ -319,6 +321,7 @@ int git_config_rename_section_in_file(const char *, const char *, const char *);
> int git_config_copy_section(const char *, const char *);
> int git_config_copy_section_in_file(const char *, const char *, const char *);
> const char *git_etc_gitconfig(void);
> +void git_config_superproject(struct strbuf *, const char *);
> int git_env_bool(const char *, int);
> unsigned long git_env_ulong(const char *, unsigned long);
> int git_config_system(void);
> diff --git a/submodule.c b/submodule.c
> index 9767ba9893..92b00f8697 100644
> --- a/submodule.c
> +++ b/submodule.c
> @@ -2178,6 +2178,35 @@ void absorb_git_dir_into_superproject(const char *path,
> }
> }
>
> +int get_superproject_gitdir(struct strbuf *buf)
> +{
> + struct strbuf sb = STRBUF_INIT;
> + struct child_process cp = CHILD_PROCESS_INIT;
> + int rc = 0;
> +
> + /* NEEDSWORK: this call also calls out to a subprocess! */
> + rc = get_superproject_working_tree(&sb);
> + strbuf_release(&sb);
> +
> + if (!rc)
> + return rc;
> +
> + prepare_submodule_repo_env_no_git_dir(&cp.env_array);
> +
> + strvec_pushl(&cp.args, "-C", "..", "rev-parse", "--absolute-git-dir", NULL);
> + cp.git_cmd = 1;
> +
> + rc = capture_command(&cp, buf, 0);
> + strbuf_trim_trailing_newline(buf);
> +
> + /* leave buf empty if we didn't have a superproject gitdir */
> + if (rc)
> + strbuf_reset(buf);
> +
> + /* rc reflects the exit code of the rev-parse; invert into a bool */
> + return !rc;
> +}
> +
> int get_superproject_working_tree(struct strbuf *buf)
> {
> struct child_process cp = CHILD_PROCESS_INIT;
> diff --git a/submodule.h b/submodule.h
> index 4ac6e31cf1..1308d5ae2d 100644
> --- a/submodule.h
> +++ b/submodule.h
> @@ -149,6 +149,12 @@ void prepare_submodule_repo_env(struct strvec *out);
> void absorb_git_dir_into_superproject(const char *path,
> unsigned flags);
>
> +/*
> + * Return the gitdir of the superproject, which this project is a submodule of.
> + * If this repository is not a submodule of another repository, return 0.
> + */
> +int get_superproject_gitdir(struct strbuf *buf);
> +
> /*
> * Return the absolute path of the working tree of the superproject, which this
> * project is a submodule of. If this repository is not a submodule of
> diff --git a/t/t1311-superproject-config.sh b/t/t1311-superproject-config.sh
> new file mode 100755
> index 0000000000..650c4d24c7
> --- /dev/null
> +++ b/t/t1311-superproject-config.sh
> @@ -0,0 +1,124 @@
> +#!/bin/sh
> +
> +test_description='Test git config --superproject in different settings'
> +
> +. ./test-lib.sh
> +
> +# follow t7400's example and use the trash dir repo as a submodule to add
> +submodurl=$(pwd -P)
> +
> +# since only the configs are modified, set up the repo structure only once
> +test_expect_success 'setup repo structure' '
> + test_commit "base" &&
> + git submodule add "${submodurl}" sub/ &&
> + git commit -m "add a submodule"
> +'
> +
> +test_expect_success 'superproject config applies to super and submodule' '
> + cat >.git/config.superproject <<-EOF &&
> + [foo]
> + bar = baz
> + EOF
> +
> + git config --get foo.bar &&
> + git -C sub config --get foo.bar &&
> +
> + rm .git/config.superproject
> +'
> +
> +test_expect_success 'can add from super or sub' '
> + git config --superproject apple.species honeycrisp &&
> + git -C sub config --superproject banana.species cavendish &&
> +
> + cat >expect <<-EOF &&
> + apple.species=honeycrisp
> + banana.species=cavendish
> + EOF
> +
> + git config --list >actual &&
> + grep -Ff expect actual &&
> +
> + git -C sub config --list >actual &&
> + grep -Ff expect actual &&
> +
> + rm .git/config.superproject
> +'
> +
> +test_expect_success 'can --unset from super or sub' '
> + git config --superproject apple.species honeycrisp &&
> + git -C sub config --superproject banana.species cavendish &&
> +
> + git config --unset --superproject banana.species &&
> + git -C sub config --unset --superproject apple.species
> +'
> +
> +test_expect_success 'can --edit superproject config' '
> + test_config core.editor "echo [foo]bar=baz >" &&
> + git config --edit --superproject &&
> +
> + git config --get foo.bar &&
> +
> + rm .git/config.superproject
> +'
> +
> +test_expect_success 'can --show-origin the superproject config' '
> + git config --superproject --add foo.bar baz &&
> +
> + git config --list --show-origin >actual &&
> + grep -F "config.superproject" actual &&
> +
> + rm .git/config.superproject
> +'
> +
> +test_expect_success 'can --show-scope the superproject config' '
> + git config --superproject --add foo.bar baz &&
> +
> + git config --list --show-scope >actual &&
> + grep "superproject" actual &&
> +
> + rm .git/config.superproject
> +'
> +
> +test_expect_success 'pre-existing config applies to new submodule' '
> + git config --superproject --add foo.bar baz &&
> +
> + git submodule add "${submodurl}" sub2/ &&
> + git commit -m "add a second submodule" &&
> +
> + git -C sub2 config --get foo.bar &&
> +
> + # clean up
> + git reset HEAD^ &&
> + rm -fr sub2 &&
> + rm .git/config.superproject
> +'
> +
> +# NEEDSWORK: submodule.c:get_superproject_working_tree doesn't support worktree
> +test_expect_failure 'worktrees can still access config.superproject' '
> + git config --superproject --add foo.bar baz &&
> +
> + git worktree add wt &&
> + (
> + cd wt &&
> + git config --get foo.bar
> + ) &&
> +
> + # clean up
> + git worktree remove wt &&
> + rm .git/config.superproject
> +'
> +
> +# This test deletes the submodule! Keep it at the end of the test suite.
> +test_expect_success 'config.submodule works even with no submodules' '
> + # get rid of the submodule
> + git reset HEAD^ &&
> + rm -fr sub &&
> +
> + git config --superproject --add foo.bar baz &&
> +
> + git config --get foo.bar &&
> +
> + rm .git/config.superproject
> +'
> +
> +test_done
next prev parent reply other threads:[~2021-04-09 11:10 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-04-08 23:39 [RFC PATCH 0/2] share a config between submodule and superproject Emily Shaffer
2021-04-08 23:39 ` [RFC PATCH 1/2] config: rename "submodule" scope to "gitmodules" Emily Shaffer
2021-04-08 23:39 ` [RFC PATCH 2/2] config: add 'config.superproject' file Emily Shaffer
2021-04-09 11:10 ` Philip Oakley [this message]
2021-04-13 18:05 ` Emily Shaffer
2021-04-09 14:35 ` Matheus Tavares Bernardino
2021-04-09 22:29 ` Junio C Hamano
2021-04-13 19:45 ` Emily Shaffer
2021-04-13 18:48 ` Emily Shaffer
2021-04-14 10:32 ` Future structure of submodules and .git/, .git/modules/* organization Ævar Arnfjörð Bjarmason
2021-04-15 21:25 ` Emily Shaffer
2021-04-15 21:41 ` Junio C Hamano
2021-04-23 0:15 ` [RFC PATCH v2 0/4] share a config between submodule and superproject Emily Shaffer
2021-04-23 0:15 ` [RFC PATCH v2 1/4] config: rename "submodule" scope to "gitmodules" Emily Shaffer
2021-04-23 9:46 ` Phillip Wood
2021-04-23 0:15 ` [RFC PATCH v2 2/4] t1510-repo-setup: don't use exact matching on traces Emily Shaffer
2021-04-23 9:59 ` Phillip Wood
2021-04-23 0:15 ` [RFC PATCH v2 3/4] t7006-pager.sh: more lenient trace checking Emily Shaffer
2021-04-23 9:54 ` Phillip Wood
2021-04-23 12:45 ` Phillip Wood
2021-04-23 0:15 ` [RFC PATCH v2 4/4] config: add 'config.superproject' file Emily Shaffer
2021-04-23 12:08 ` Johannes Schindelin
2021-06-19 0:31 ` [PATCH v3 0/2] share a config between submodule and superproject Emily Shaffer
2021-06-19 0:31 ` [PATCH v3 1/2] config: rename "submodule" scope to "gitmodules" Emily Shaffer
2021-06-19 0:31 ` [PATCH v3 2/2] config: add 'config.superproject' file Emily Shaffer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e19a250a-c4ca-fc32-83f9-a03aa03cd88a@iee.email \
--to=philipoakley@iee.email \
--cc=emilyshaffer@google.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).