Re: [PATCH v3] config: support remote name in includeIf.hasconfig condition

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Ping.

P.S. Some users have been attempting to use
hasconfig:remote.origin.url, so I believe this patch is a logical
improvement.

https://github.com/search?q=%22hasconfig%3Aremote.origin.url%22&type=code

On Sunday, October 20th, 2024 at 1:32 PM, Ken Matsui <ken@xxxxxxxxxx> wrote:

>
>
> Changes in v3:
>
> * Updated the description based on Ramsay's review.
> * Added more tests.
>
> Changes in v2:
>
> * Updated the description based on Kristoffer's review.
>
> -- >8 --
>
>
> includeIf.hasconfig only accepts remote..url, making it difficult to
> apply configuration based on a specific remote, especially in projects
> with multiple remotes (e.g., GitHub and non-GitHub hosting). This often
> leads to undesired application of multiple config files.
>
> For example, the following configuration:
>
> [remote "origin"]
> url = https://git.kernel.org/pub/scm/git/git.git
> [remote "github"]
> url = https://github.com/myfork/git.git
>
> [includeIf "hasconfig:remote..url:https://github.com/";]
> path = github.inc
> [includeIf "hasconfig:remote.*.url:https://git.kernel.org/";]
> path = git.inc
>
> would apply both github.inc and git.inc, even when only one config is
> intended for the repository.
>
> Introduce support for specifying a remote name (e.g., origin) to enable
> more precise configuration management:
>
> [includeIf "hasconfig:remote.origin.url:https://github.com/";]
> path = github.inc
> [includeIf "hasconfig:remote.origin.url:https://git.kernel.org/";]
> path = git.inc
>
> This ensures that only git.inc is included in this configuration while
> other repositories that use github.com for origin can only include
> github.inc.
>
> Signed-off-by: Ken Matsui ken@xxxxxxxxxx
>
> ---
> Documentation/config.txt | 13 +++-
> config.c | 141 ++++++++++++++++++++++++++++++++++-----
> t/t1300-config.sh | 84 +++++++++++++++++++++++
> 3 files changed, 219 insertions(+), 19 deletions(-)
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 8c0b3ed807..22b50d523d 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -161,14 +161,17 @@ all branches that begin with `foo/`. This is useful if your branches are
> organized hierarchically and you would like to apply a configuration to
> all the branches in that hierarchy.
>
> -`hasconfig:remote.*.url:`::
> +`hasconfig:remote.(<name>|*).url:`::
>
> The data that follows this keyword is taken to
> be a pattern with standard globbing wildcards and two
> additional ones, `**/` and `/**`, that can match multiple
> components. The first time this keyword is seen, the rest of
> the config files will be scanned for remote URLs (without
> applying any values). If there exists at least one remote URL
> - that matches this pattern, the include condition is met.
> + that matches this pattern, the include condition is met. `<name>`
>
> + is the name of the remote, and `*` matches any remote name. Note
> + that `<name>` is not globbed, so it must be an exact match (e.g.,
>
> + `dev-*` will not match `dev-foo`).
> +
> Files included by this option (directly or indirectly) are not allowed
> to contain remote URLs.
> @@ -263,6 +266,12 @@ Example
> path = foo.inc
> [remote "origin"]
> url = https://example.com/git
> +
> +; include only if the given remote with the given URL exist.
> +[includeIf "hasconfig:remote.origin.url:https://example.com/**";]
> + path = foo.inc
> +[remote "origin"]
> + url = https://example.com/git
> ----
>
> Values
> diff --git a/config.c b/config.c
> index a11bb85da3..9de58eec7a 100644
> --- a/config.c
> +++ b/config.c
> @@ -123,6 +123,37 @@ static long config_buf_ftell(struct config_source *conf)
> return conf->u.buf.pos;
>
> }
>
> +struct remote_urls_entry {
> + struct hashmap_entry ent;
> + char *name;
> + struct string_list urls;
> +};
> +
> +static struct remote_urls_entry *remote_urls_find_entry(struct hashmap *remote_urls,
> + char *name)
> +{
> + struct remote_urls_entry k;
> + struct remote_urls_entry *found_entry;
> +
> + hashmap_entry_init(&k.ent, strhash(name));
> + k.name = name;
> + found_entry = hashmap_get_entry(remote_urls, &k, ent, NULL);
> + return found_entry;
> +}
> +
> +static int remote_urls_entry_cmp(const void *cmp_data UNUSED,
> + const struct hashmap_entry *eptr,
> + const struct hashmap_entry *entry_or_key,
> + const void *keydata UNUSED)
> +{
> + const struct remote_urls_entry *e1, *e2;
> +
> + e1 = container_of(eptr, const struct remote_urls_entry, ent);
> + e2 = container_of(entry_or_key, const struct remote_urls_entry, ent);
> +
> + return strcmp(e1->name, e2->name);
>
> +}
> +
> struct config_include_data {
> int depth;
> config_fn_t fn;
> @@ -132,9 +163,10 @@ struct config_include_data {
> struct repository repo;
>
> /
> - * All remote URLs discovered when reading all config files.
> + * All remote names & URLs discovered when reading all config files.
> */
> - struct string_list *remote_urls;
> + struct hashmap remote_urls;
> + int remote_urls_initialized;
> };
> #define CONFIG_INCLUDE_INIT { 0 }
>
> @@ -328,16 +360,36 @@ static int include_by_branch(struct config_include_data *data,
> static int add_remote_url(const char *var, const char *value,
> const struct config_context *ctx UNUSED, void *data)
> {
> - struct string_list *remote_urls = data;
> - const char *remote_name;
> + struct hashmap *remote_urls = data;
> + const char *remote_section;
> size_t remote_name_len;
> const char *key;
>
> - if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
> + if (!parse_config_key(var, "remote", &remote_section, &remote_name_len,
> &key) &&
> - remote_name &&
> - !strcmp(key, "url"))
> - string_list_append(remote_urls, value);
> + remote_section &&
> + !strcmp(key, "url")) {
> + const char *dot;
> + char *remote_name;
> + struct remote_urls_entry *e;
> +
> + dot = strchr(remote_section, '.');
> + if (!dot)
> + return 0;
> +
> + remote_name = xstrndup(remote_section, dot - remote_section);
> + e = remote_urls_find_entry(remote_urls, remote_name);
> + if (!e) {
> + e = xmalloc(sizeof(*e));
> + hashmap_entry_init(&e->ent, strhash(remote_name));
>
> + e->name = remote_name;
>
> + string_list_init_dup(&e->urls);
>
> + string_list_append(&e->urls, value);
>
> + hashmap_add(remote_urls, &e->ent);
>
> + } else {
> + string_list_append(&e->urls, value);
>
> + }
> + }
> return 0;
> }
>
> @@ -348,9 +400,9 @@ static void populate_remote_urls(struct config_include_data *inc)
> opts = *inc->opts;
>
> opts.unconditional_remote_url = 1;
>
> - inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
>
> - string_list_init_dup(inc->remote_urls);
>
> - config_with_options(add_remote_url, inc->remote_urls,
>
> + hashmap_init(&inc->remote_urls, remote_urls_entry_cmp, NULL, 0);
>
> + inc->remote_urls_initialized = 1;
>
> + config_with_options(add_remote_url, &inc->remote_urls,
>
> inc->config_source, inc->repo, &opts);
>
> }
>
> @@ -391,12 +443,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,
> static int include_by_remote_url(struct config_include_data *inc,
> const char *cond, size_t cond_len)
> {
> + struct hashmap_iter iter;
> + struct remote_urls_entry *remote;
> +
> + if (inc->opts->unconditional_remote_url)
>
> + return 1;
> + if (!inc->remote_urls_initialized)
>
> + populate_remote_urls(inc);
> +
> + hashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)
>
> + if (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))
>
> + return 1;
> + return 0;
> +}
> +
> +static int include_by_remote_name_and_url(struct config_include_data *inc,
> + const char *cond, size_t cond_len,
> + char *remote_name)
> +{
> + struct remote_urls_entry *e;
> +
> if (inc->opts->unconditional_remote_url)
>
> return 1;
> - if (!inc->remote_urls)
>
> + if (!inc->remote_urls_initialized)
>
> populate_remote_urls(inc);
> - return at_least_one_url_matches_glob(cond, cond_len,
> - inc->remote_urls);
>
> +
> + e = remote_urls_find_entry(&inc->remote_urls, remote_name);
>
> + if (!e)
> + return 0;
> + return at_least_one_url_matches_glob(cond, cond_len, &e->urls);
>
> }
>
> static int include_condition_is_true(const struct key_value_info *kvi,
> @@ -414,6 +489,32 @@ static int include_condition_is_true(const struct key_value_info kvi,
> else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote..url:", &cond,
> &cond_len))
> return include_by_remote_url(inc, cond, cond_len);
> + else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.", &cond,
> + &cond_len)) {
> + const char *dot;
> + char *remote_name;
> + char cond_prefix;
> + int ret;
> +
> + dot = strchr(cond, '.');
> + if (!dot)
> + return 0;
> +
> + remote_name = xstrndup(cond, dot - cond);
> + cond_prefix = xstrfmt("%s.url:", remote_name);
> + if (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,
> + &cond_len)) {
> + free(cond_prefix);
> + free(remote_name);
> + return 0;
> + }
> + free(cond_prefix);
> +
> + ret = include_by_remote_name_and_url(inc, cond, cond_len,
> + remote_name);
> + free(remote_name);
> + return ret;
> + }
>
> / unknown conditionals are always false */
> return 0;
> @@ -2151,9 +2252,15 @@ int config_with_options(config_fn_t fn, void *data,
> ret = do_git_config_sequence(opts, repo, fn, data);
> }
>
> - if (inc.remote_urls) {
> - string_list_clear(inc.remote_urls, 0);
> - FREE_AND_NULL(inc.remote_urls);
> + if (inc.remote_urls_initialized) {
> + struct hashmap_iter iter;
> + struct remote_urls_entry *remote;
> + hashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {
> + string_list_clear(&remote->urls, 0);
>
> + free(remote->name);
>
> + }
> + hashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);
> + inc.remote_urls_initialized = 0;
> }
> return ret;
> }
> diff --git a/t/t1300-config.sh b/t/t1300-config.sh
> index f13277c8f3..81f149a712 100755
> --- a/t/t1300-config.sh
> +++ b/t/t1300-config.sh
> @@ -2831,6 +2831,90 @@ test_expect_success 'includeIf.hasconfig:remote..url forbids remote url in such
> grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote..url" err
> '
>
> +test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and the same URL' '
>
> + git init hasremoteurlTest &&
> + test_when_finished "rm -rf hasremoteurlTest" &&
> +
> + cat >include-this <<-\EOF &&
>
> + [user]
> + this = this-is-included
> + EOF
> + cat >dont-include-that <<-\EOF &&
>
> + [user]
> + that = that-is-not-included
> + EOF
> + cat >>hasremoteurlTest/.git/config <<-EOF &&
>
> + [includeIf "hasconfig:remote.foo.url:sameurl"]
> + path = "$(pwd)/include-this"
> + [includeIf "hasconfig:remote.bar.url:sameurl"]
> + path = "$(pwd)/dont-include-that"
> + [remote "foo"]
> + url = sameurl
> + EOF
> +
> + echo this-is-included >expect-this &&
>
> + git -C hasremoteurlTest config --get user.this >actual-this &&
>
> + test_cmp expect-this actual-this &&
> +
> + test_must_fail git -C hasremoteurlTest config --get user.that
> +'
> +
> +test_expect_success 'includeIf.hasconfig:remote.<name>.url with the same remote name and different URLs' '
>
> + git init hasremoteurlTest &&
> + test_when_finished "rm -rf hasremoteurlTest" &&
> +
> + cat >include-this <<-\EOF &&
>
> + [user]
> + this = this-is-included
> + EOF
> + cat >dont-include-that <<-\EOF &&
>
> + [user]
> + that = that-is-not-included
> + EOF
> + cat >>hasremoteurlTest/.git/config <<-EOF &&
>
> + [includeIf "hasconfig:remote.foo.url:foourl"]
> + path = "$(pwd)/include-this"
> + [includeIf "hasconfig:remote.foo.url:barurl"]
> + path = "$(pwd)/dont-include-that"
> + [remote "foo"]
> + url = foourl
> + EOF
> +
> + echo this-is-included >expect-this &&
>
> + git -C hasremoteurlTest config --get user.this >actual-this &&
>
> + test_cmp expect-this actual-this &&
> +
> + test_must_fail git -C hasremoteurlTest config --get user.that
> +'
> +
> +test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and URLs' '
>
> + git init hasremoteurlTest &&
> + test_when_finished "rm -rf hasremoteurlTest" &&
> +
> + cat >include-this <<-\EOF &&
>
> + [user]
> + this = this-is-included
> + EOF
> + cat >dont-include-that <<-\EOF &&
>
> + [user]
> + that = that-is-not-included
> + EOF
> + cat >>hasremoteurlTest/.git/config <<-EOF &&
>
> + [includeIf "hasconfig:remote.foo.url:foourl"]
> + path = "$(pwd)/include-this"
> + [includeIf "hasconfig:remote.bar.url:barurl"]
> + path = "$(pwd)/dont-include-that"
> + [remote "foo"]
> + url = foourl
> + EOF
> +
> + echo this-is-included >expect-this &&
>
> + git -C hasremoteurlTest config --get user.this >actual-this &&
>
> + test_cmp expect-this actual-this &&
> +
> + test_must_fail git -C hasremoteurlTest config --get user.that
> +'
> +
> test_expect_success 'negated mode causes failure' '
> test_must_fail git config --no-get 2>err &&
>
> grep "unknown option \`no-get${SQ}" err
> --
> 2.47.0





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux