{"thread":{"id":"62373","subject":"[PATCH] config: support remote name in includeIf.hasconfig condition","startedAt":"2024-10-20T15:24:03Z","lastAt":"2024-12-03T07:28:13Z","messageCount":10,"participants":["Ken Matsui","Kristoffer Haugsbakk","Ramsay Jones","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"505557","messageId":"20241020152323.90543-1-ken@kmatsui.me","threadId":"62373","inReplyTo":null,"subject":"[PATCH] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-10-20T15:23:22Z","receivedAt":"2024-10-20T15:24:03Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"Before this patch, includeIf.hasconfig only accepted remote.*.url,\nmaking it difficult to apply configuration based on a specific remote,\nespecially in projects with multiple remotes (e.g., GitHub and\nnon-GitHub hosting).  This often led to undesired application of\nmultiple config files, such as:\n\n  [remote \"origin\"]\n    url = https://git.kernel.org/pub/scm/git/git.git\n  [remote \"github\"]\n    url = https://github.com/myfork/git.git\n\nFor example, the following configuration:\n\n  [includeIf \"hasconfig:remote.*.url:https://github.com/**\"]\n    path = github.inc\n  [includeIf \"hasconfig:remote.*.url:https://git.kernel.org/**\"]\n    path = git.inc\n\nwould apply both github.inc and git.inc, even when only one config is\nintended for the repository.\n\nThis patch introduces support for specifying a remote name (e.g.,\norigin) to enable more precise configuration management:\n\n  [includeIf \"hasconfig:remote.origin.url:https://github.com/**\"]\n    path = github.inc\n  [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/**\"]\n    path = git.inc\n\nThis ensures that only the intended config file is applied based on the\nspecific remote.\n\nSigned-off-by: Ken Matsui <ken@kmatsui.me>\n---\n Documentation/config.txt |  13 +++-\n config.c                 | 141 ++++++++++++++++++++++++++++++++++-----\n t/t1300-config.sh        |  28 ++++++++\n 3 files changed, 163 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 8c0b3ed807..22b50d523d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -161,14 +161,17 @@ all branches that begin with `foo/`. This is useful if your branches are\n organized hierarchically and you would like to apply a configuration to\n all the branches in that hierarchy.\n \n-`hasconfig:remote.*.url:`::\n+`hasconfig:remote.(<name>|*).url:`::\n \tThe data that follows this keyword is taken to\n \tbe a pattern with standard globbing wildcards and two\n \tadditional ones, `**/` and `/**`, that can match multiple\n \tcomponents. The first time this keyword is seen, the rest of\n \tthe config files will be scanned for remote URLs (without\n \tapplying any values). If there exists at least one remote URL\n-\tthat matches this pattern, the include condition is met.\n+\tthat matches this pattern, the include condition is met. `<name>`\n+\tis the name of the remote, and `*` matches any remote name. Note\n+\tthat `<name>` is not globbed, so it must be an exact match (e.g.,\n+\t`dev-*` will not match `dev-foo`).\n +\n Files included by this option (directly or indirectly) are not allowed\n to contain remote URLs.\n@@ -263,6 +266,12 @@ Example\n \tpath = foo.inc\n [remote \"origin\"]\n \turl = https://example.com/git\n+\n+; include only if the given remote with the given URL exist.\n+[includeIf \"hasconfig:remote.origin.url:https://example.com/**\"]\n+\tpath = foo.inc\n+[remote \"origin\"]\n+\turl = https://example.com/git\n ----\n \n Values\ndiff --git a/config.c b/config.c\nindex a11bb85da3..9de58eec7a 100644\n--- a/config.c\n+++ b/config.c\n@@ -123,6 +123,37 @@ static long config_buf_ftell(struct config_source *conf)\n \treturn conf->u.buf.pos;\n }\n \n+struct remote_urls_entry {\n+\tstruct hashmap_entry ent;\n+\tchar *name;\n+\tstruct string_list urls;\n+};\n+\n+static struct remote_urls_entry *remote_urls_find_entry(struct hashmap *remote_urls,\n+\t\t\t\t\t\t\tchar *name)\n+{\n+\tstruct remote_urls_entry k;\n+\tstruct remote_urls_entry *found_entry;\n+\n+\thashmap_entry_init(&k.ent, strhash(name));\n+\tk.name = name;\n+\tfound_entry = hashmap_get_entry(remote_urls, &k, ent, NULL);\n+\treturn found_entry;\n+}\n+\n+static int remote_urls_entry_cmp(const void *cmp_data UNUSED,\n+\t\t\t\t const struct hashmap_entry *eptr,\n+\t\t\t\t const struct hashmap_entry *entry_or_key,\n+\t\t\t\t const void *keydata UNUSED)\n+{\n+\tconst struct remote_urls_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct remote_urls_entry, ent);\n+\te2 = container_of(entry_or_key, const struct remote_urls_entry, ent);\n+\n+\treturn strcmp(e1->name, e2->name);\n+}\n+\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -132,9 +163,10 @@ struct config_include_data {\n \tstruct repository *repo;\n \n \t/*\n-\t * All remote URLs discovered when reading all config files.\n+\t * All remote names & URLs discovered when reading all config files.\n \t */\n-\tstruct string_list *remote_urls;\n+\tstruct hashmap remote_urls;\n+\tint remote_urls_initialized;\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n@@ -328,16 +360,36 @@ static int include_by_branch(struct config_include_data *data,\n static int add_remote_url(const char *var, const char *value,\n \t\t\t  const struct config_context *ctx UNUSED, void *data)\n {\n-\tstruct string_list *remote_urls = data;\n-\tconst char *remote_name;\n+\tstruct hashmap *remote_urls = data;\n+\tconst char *remote_section;\n \tsize_t remote_name_len;\n \tconst char *key;\n \n-\tif (!parse_config_key(var, \"remote\", &remote_name, &remote_name_len,\n+\tif (!parse_config_key(var, \"remote\", &remote_section, &remote_name_len,\n \t\t\t      &key) &&\n-\t    remote_name &&\n-\t    !strcmp(key, \"url\"))\n-\t\tstring_list_append(remote_urls, value);\n+\t    remote_section &&\n+\t    !strcmp(key, \"url\")) {\n+\t\tconst char *dot;\n+\t\tchar *remote_name;\n+\t\tstruct remote_urls_entry *e;\n+\n+\t\tdot = strchr(remote_section, '.');\n+\t\tif (!dot)\n+\t\t\treturn 0;\n+\n+\t\tremote_name = xstrndup(remote_section, dot - remote_section);\n+\t\te = remote_urls_find_entry(remote_urls, remote_name);\n+\t\tif (!e) {\n+\t\t\te = xmalloc(sizeof(*e));\n+\t\t\thashmap_entry_init(&e->ent, strhash(remote_name));\n+\t\t\te->name = remote_name;\n+\t\t\tstring_list_init_dup(&e->urls);\n+\t\t\tstring_list_append(&e->urls, value);\n+\t\t\thashmap_add(remote_urls, &e->ent);\n+\t\t} else {\n+\t\t\tstring_list_append(&e->urls, value);\n+\t\t}\n+\t}\n \treturn 0;\n }\n \n@@ -348,9 +400,9 @@ static void populate_remote_urls(struct config_include_data *inc)\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n-\tstring_list_init_dup(inc->remote_urls);\n-\tconfig_with_options(add_remote_url, inc->remote_urls,\n+\thashmap_init(&inc->remote_urls, remote_urls_entry_cmp, NULL, 0);\n+\tinc->remote_urls_initialized = 1;\n+\tconfig_with_options(add_remote_url, &inc->remote_urls,\n \t\t\t    inc->config_source, inc->repo, &opts);\n }\n \n@@ -391,12 +443,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,\n static int include_by_remote_url(struct config_include_data *inc,\n \t\tconst char *cond, size_t cond_len)\n {\n+\tstruct hashmap_iter iter;\n+\tstruct remote_urls_entry *remote;\n+\n+\tif (inc->opts->unconditional_remote_url)\n+\t\treturn 1;\n+\tif (!inc->remote_urls_initialized)\n+\t\tpopulate_remote_urls(inc);\n+\n+\thashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)\n+\t\tif (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))\n+\t\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int include_by_remote_name_and_url(struct config_include_data *inc,\n+\t\t\t\t\t  const char *cond, size_t cond_len,\n+\t\t\t\t\t  char *remote_name)\n+{\n+\tstruct remote_urls_entry *e;\n+\n \tif (inc->opts->unconditional_remote_url)\n \t\treturn 1;\n-\tif (!inc->remote_urls)\n+\tif (!inc->remote_urls_initialized)\n \t\tpopulate_remote_urls(inc);\n-\treturn at_least_one_url_matches_glob(cond, cond_len,\n-\t\t\t\t\t     inc->remote_urls);\n+\n+\te = remote_urls_find_entry(&inc->remote_urls, remote_name);\n+\tif (!e)\n+\t\treturn 0;\n+\treturn at_least_one_url_matches_glob(cond, cond_len, &e->urls);\n }\n \n static int include_condition_is_true(const struct key_value_info *kvi,\n@@ -414,6 +489,32 @@ static int include_condition_is_true(const struct key_value_info *kvi,\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n \t\t\t\t   &cond_len))\n \t\treturn include_by_remote_url(inc, cond, cond_len);\n+\telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.\", &cond,\n+\t\t\t\t &cond_len)) {\n+\t\tconst char *dot;\n+\t\tchar *remote_name;\n+\t\tchar *cond_prefix;\n+\t\tint ret;\n+\n+\t\tdot = strchr(cond, '.');\n+\t\tif (!dot)\n+\t\t\treturn 0;\n+\n+\t\tremote_name = xstrndup(cond, dot - cond);\n+\t\tcond_prefix = xstrfmt(\"%s.url:\", remote_name);\n+\t\tif (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,\n+\t\t\t\t     &cond_len)) {\n+\t\t\tfree(cond_prefix);\n+\t\t\tfree(remote_name);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tfree(cond_prefix);\n+\n+\t\tret = include_by_remote_name_and_url(inc, cond, cond_len,\n+\t\t\t\t\t\t     remote_name);\n+\t\tfree(remote_name);\n+\t\treturn ret;\n+\t}\n \n \t/* unknown conditionals are always false */\n \treturn 0;\n@@ -2151,9 +2252,15 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = do_git_config_sequence(opts, repo, fn, data);\n \t}\n \n-\tif (inc.remote_urls) {\n-\t\tstring_list_clear(inc.remote_urls, 0);\n-\t\tFREE_AND_NULL(inc.remote_urls);\n+\tif (inc.remote_urls_initialized) {\n+\t\tstruct hashmap_iter iter;\n+\t\tstruct remote_urls_entry *remote;\n+\t\thashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {\n+\t\t\tstring_list_clear(&remote->urls, 0);\n+\t\t\tfree(remote->name);\n+\t\t}\n+\t\thashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);\n+\t\tinc.remote_urls_initialized = 0;\n \t}\n \treturn ret;\n }\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex f13277c8f3..b04908b6ed 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2831,6 +2831,34 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such\n \tgrep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url\" err\n '\n \n+test_expect_success 'includeIf.hasconfig:remote.<name>.url' '\n+\tgit init hasremoteurlTest &&\n+\ttest_when_finished \"rm -rf hasremoteurlTest\" &&\n+\n+\tcat >include-this <<-\\EOF &&\n+\t[user]\n+\t\tthis = this-is-included\n+\tEOF\n+\tcat >dont-include-that <<-\\EOF &&\n+\t[user]\n+\t\tthat = that-is-not-included\n+\tEOF\n+\tcat >>hasremoteurlTest/.git/config <<-EOF &&\n+\t[includeIf \"hasconfig:remote.foo.url:sameurl\"]\n+\t\tpath = \"$(pwd)/include-this\"\n+\t[includeIf \"hasconfig:remote.bar.url:sameurl\"]\n+\t\tpath = \"$(pwd)/dont-include-that\"\n+\t[remote \"foo\"]\n+\t\turl = sameurl\n+\tEOF\n+\n+\techo this-is-included >expect-this &&\n+\tgit -C hasremoteurlTest config --get user.this >actual-this &&\n+\ttest_cmp expect-this actual-this &&\n+\n+\ttest_must_fail git -C hasremoteurlTest config --get user.that\n+'\n+\n test_expect_success 'negated mode causes failure' '\n \ttest_must_fail git config --no-get 2>err &&\n \tgrep \"unknown option \\`no-get${SQ}\" err\n-- \n2.47.0\n\n"},{"id":"505558","messageId":"2a9a4371-958f-491e-b78b-6d4da51483f2@app.fastmail.com","threadId":"62373","inReplyTo":"20241020152323.90543-1-ken@kmatsui.me","subject":"Re: [PATCH] config: support remote name in includeIf.hasconfig condition","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-10-20T15:33:37Z","receivedAt":"2024-10-20T15:34:43Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"Hi\n\nOn Sun, Oct 20, 2024, at 17:23, Ken Matsui wrote:\n> Before this patch, includeIf.hasconfig only accepted remote.*.url,\n\nThe conventional description of the current state of the code, without\nthis patch, is the present tense.  What the patch does uses the\nimperative mood.  E.g.\n\n    includeIf.hasconfig only accepts remote.*.url\n\nSee Documentation/SubmittingPatches, “present-tense” and\n“imperative-mood”.\n\n-- \nKristoffer Haugsbakk\n\n"},{"id":"505559","messageId":"xalChncOvSTxgRdafL-WiQtTfzneMRmhnIFHbEYeRLFYTHgdghc-Js34ml_XpkMBJQNPk68Jj2Q_aAZR73mhLig_Pmv_DSmweukWWqVDLLM=@kmatsui.me","threadId":"62373","inReplyTo":"2a9a4371-958f-491e-b78b-6d4da51483f2@app.fastmail.com","subject":"Re: [PATCH] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-10-20T15:43:09Z","receivedAt":"2024-10-20T15:43:31Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"On Sunday, October 20th, 2024 at 11:33 AM, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:\n\n> \n> \n> Hi\n> \n> On Sun, Oct 20, 2024, at 17:23, Ken Matsui wrote:\n> \n> > Before this patch, includeIf.hasconfig only accepted remote.*.url,\n> \n> \n> The conventional description of the current state of the code, without\n> this patch, is the present tense. What the patch does uses the\n> imperative mood. E.g.\n> \n> includeIf.hasconfig only accepts remote.*.url\n> \n> See Documentation/SubmittingPatches, “present-tense” and\n> “imperative-mood”.\n\nOops, thank you for pointing this out.  I will update the\ndescription and send an updated patch.\n\n> \n> --\n> Kristoffer Haugsbakk\n"},{"id":"505561","messageId":"20241020160143.92382-2-ken@kmatsui.me","threadId":"62373","inReplyTo":"20241020152323.90543-1-ken@kmatsui.me","subject":"[PATCH v2] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-10-20T16:01:44Z","receivedAt":"2024-10-20T16:02:30Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"Changes in v2:\n\n* Updated the description based on Kristoffer's review.\n\n-- >8 --\n\nincludeIf.hasconfig only accepts remote.*.url, making it difficult to\napply configuration based on a specific remote, especially in projects\nwith multiple remotes (e.g., GitHub and non-GitHub hosting).  This often\nleads to undesired application of multiple config files.\n\nFor example, the following configuration:\n\n  [remote \"origin\"]\n    url = https://git.kernel.org/pub/scm/git/git.git\n  [remote \"github\"]\n    url = https://github.com/myfork/git.git\n\n  [includeIf \"hasconfig:remote.*.url:https://github.com/**\"]\n    path = github.inc\n  [includeIf \"hasconfig:remote.*.url:https://git.kernel.org/**\"]\n    path = git.inc\n\nwould apply both github.inc and git.inc, even when only one config is\nintended for the repository.\n\nIntroduce support for specifying a remote name (e.g., origin) to enable\nmore precise configuration management:\n\n  [includeIf \"hasconfig:remote.origin.url:https://github.com/**\"]\n    path = github.inc\n  [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/**\"]\n    path = git.inc\n\nThis ensures that only the intended config file is applied based on the\nspecific remote.\n\nSigned-off-by: Ken Matsui <ken@kmatsui.me>\n---\n Documentation/config.txt |  13 +++-\n config.c                 | 141 ++++++++++++++++++++++++++++++++++-----\n t/t1300-config.sh        |  28 ++++++++\n 3 files changed, 163 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 8c0b3ed807..22b50d523d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -161,14 +161,17 @@ all branches that begin with `foo/`. This is useful if your branches are\n organized hierarchically and you would like to apply a configuration to\n all the branches in that hierarchy.\n \n-`hasconfig:remote.*.url:`::\n+`hasconfig:remote.(<name>|*).url:`::\n \tThe data that follows this keyword is taken to\n \tbe a pattern with standard globbing wildcards and two\n \tadditional ones, `**/` and `/**`, that can match multiple\n \tcomponents. The first time this keyword is seen, the rest of\n \tthe config files will be scanned for remote URLs (without\n \tapplying any values). If there exists at least one remote URL\n-\tthat matches this pattern, the include condition is met.\n+\tthat matches this pattern, the include condition is met. `<name>`\n+\tis the name of the remote, and `*` matches any remote name. Note\n+\tthat `<name>` is not globbed, so it must be an exact match (e.g.,\n+\t`dev-*` will not match `dev-foo`).\n +\n Files included by this option (directly or indirectly) are not allowed\n to contain remote URLs.\n@@ -263,6 +266,12 @@ Example\n \tpath = foo.inc\n [remote \"origin\"]\n \turl = https://example.com/git\n+\n+; include only if the given remote with the given URL exist.\n+[includeIf \"hasconfig:remote.origin.url:https://example.com/**\"]\n+\tpath = foo.inc\n+[remote \"origin\"]\n+\turl = https://example.com/git\n ----\n \n Values\ndiff --git a/config.c b/config.c\nindex a11bb85da3..9de58eec7a 100644\n--- a/config.c\n+++ b/config.c\n@@ -123,6 +123,37 @@ static long config_buf_ftell(struct config_source *conf)\n \treturn conf->u.buf.pos;\n }\n \n+struct remote_urls_entry {\n+\tstruct hashmap_entry ent;\n+\tchar *name;\n+\tstruct string_list urls;\n+};\n+\n+static struct remote_urls_entry *remote_urls_find_entry(struct hashmap *remote_urls,\n+\t\t\t\t\t\t\tchar *name)\n+{\n+\tstruct remote_urls_entry k;\n+\tstruct remote_urls_entry *found_entry;\n+\n+\thashmap_entry_init(&k.ent, strhash(name));\n+\tk.name = name;\n+\tfound_entry = hashmap_get_entry(remote_urls, &k, ent, NULL);\n+\treturn found_entry;\n+}\n+\n+static int remote_urls_entry_cmp(const void *cmp_data UNUSED,\n+\t\t\t\t const struct hashmap_entry *eptr,\n+\t\t\t\t const struct hashmap_entry *entry_or_key,\n+\t\t\t\t const void *keydata UNUSED)\n+{\n+\tconst struct remote_urls_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct remote_urls_entry, ent);\n+\te2 = container_of(entry_or_key, const struct remote_urls_entry, ent);\n+\n+\treturn strcmp(e1->name, e2->name);\n+}\n+\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -132,9 +163,10 @@ struct config_include_data {\n \tstruct repository *repo;\n \n \t/*\n-\t * All remote URLs discovered when reading all config files.\n+\t * All remote names & URLs discovered when reading all config files.\n \t */\n-\tstruct string_list *remote_urls;\n+\tstruct hashmap remote_urls;\n+\tint remote_urls_initialized;\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n@@ -328,16 +360,36 @@ static int include_by_branch(struct config_include_data *data,\n static int add_remote_url(const char *var, const char *value,\n \t\t\t  const struct config_context *ctx UNUSED, void *data)\n {\n-\tstruct string_list *remote_urls = data;\n-\tconst char *remote_name;\n+\tstruct hashmap *remote_urls = data;\n+\tconst char *remote_section;\n \tsize_t remote_name_len;\n \tconst char *key;\n \n-\tif (!parse_config_key(var, \"remote\", &remote_name, &remote_name_len,\n+\tif (!parse_config_key(var, \"remote\", &remote_section, &remote_name_len,\n \t\t\t      &key) &&\n-\t    remote_name &&\n-\t    !strcmp(key, \"url\"))\n-\t\tstring_list_append(remote_urls, value);\n+\t    remote_section &&\n+\t    !strcmp(key, \"url\")) {\n+\t\tconst char *dot;\n+\t\tchar *remote_name;\n+\t\tstruct remote_urls_entry *e;\n+\n+\t\tdot = strchr(remote_section, '.');\n+\t\tif (!dot)\n+\t\t\treturn 0;\n+\n+\t\tremote_name = xstrndup(remote_section, dot - remote_section);\n+\t\te = remote_urls_find_entry(remote_urls, remote_name);\n+\t\tif (!e) {\n+\t\t\te = xmalloc(sizeof(*e));\n+\t\t\thashmap_entry_init(&e->ent, strhash(remote_name));\n+\t\t\te->name = remote_name;\n+\t\t\tstring_list_init_dup(&e->urls);\n+\t\t\tstring_list_append(&e->urls, value);\n+\t\t\thashmap_add(remote_urls, &e->ent);\n+\t\t} else {\n+\t\t\tstring_list_append(&e->urls, value);\n+\t\t}\n+\t}\n \treturn 0;\n }\n \n@@ -348,9 +400,9 @@ static void populate_remote_urls(struct config_include_data *inc)\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n-\tstring_list_init_dup(inc->remote_urls);\n-\tconfig_with_options(add_remote_url, inc->remote_urls,\n+\thashmap_init(&inc->remote_urls, remote_urls_entry_cmp, NULL, 0);\n+\tinc->remote_urls_initialized = 1;\n+\tconfig_with_options(add_remote_url, &inc->remote_urls,\n \t\t\t    inc->config_source, inc->repo, &opts);\n }\n \n@@ -391,12 +443,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,\n static int include_by_remote_url(struct config_include_data *inc,\n \t\tconst char *cond, size_t cond_len)\n {\n+\tstruct hashmap_iter iter;\n+\tstruct remote_urls_entry *remote;\n+\n+\tif (inc->opts->unconditional_remote_url)\n+\t\treturn 1;\n+\tif (!inc->remote_urls_initialized)\n+\t\tpopulate_remote_urls(inc);\n+\n+\thashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)\n+\t\tif (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))\n+\t\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int include_by_remote_name_and_url(struct config_include_data *inc,\n+\t\t\t\t\t  const char *cond, size_t cond_len,\n+\t\t\t\t\t  char *remote_name)\n+{\n+\tstruct remote_urls_entry *e;\n+\n \tif (inc->opts->unconditional_remote_url)\n \t\treturn 1;\n-\tif (!inc->remote_urls)\n+\tif (!inc->remote_urls_initialized)\n \t\tpopulate_remote_urls(inc);\n-\treturn at_least_one_url_matches_glob(cond, cond_len,\n-\t\t\t\t\t     inc->remote_urls);\n+\n+\te = remote_urls_find_entry(&inc->remote_urls, remote_name);\n+\tif (!e)\n+\t\treturn 0;\n+\treturn at_least_one_url_matches_glob(cond, cond_len, &e->urls);\n }\n \n static int include_condition_is_true(const struct key_value_info *kvi,\n@@ -414,6 +489,32 @@ static int include_condition_is_true(const struct key_value_info *kvi,\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n \t\t\t\t   &cond_len))\n \t\treturn include_by_remote_url(inc, cond, cond_len);\n+\telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.\", &cond,\n+\t\t\t\t &cond_len)) {\n+\t\tconst char *dot;\n+\t\tchar *remote_name;\n+\t\tchar *cond_prefix;\n+\t\tint ret;\n+\n+\t\tdot = strchr(cond, '.');\n+\t\tif (!dot)\n+\t\t\treturn 0;\n+\n+\t\tremote_name = xstrndup(cond, dot - cond);\n+\t\tcond_prefix = xstrfmt(\"%s.url:\", remote_name);\n+\t\tif (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,\n+\t\t\t\t     &cond_len)) {\n+\t\t\tfree(cond_prefix);\n+\t\t\tfree(remote_name);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tfree(cond_prefix);\n+\n+\t\tret = include_by_remote_name_and_url(inc, cond, cond_len,\n+\t\t\t\t\t\t     remote_name);\n+\t\tfree(remote_name);\n+\t\treturn ret;\n+\t}\n \n \t/* unknown conditionals are always false */\n \treturn 0;\n@@ -2151,9 +2252,15 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = do_git_config_sequence(opts, repo, fn, data);\n \t}\n \n-\tif (inc.remote_urls) {\n-\t\tstring_list_clear(inc.remote_urls, 0);\n-\t\tFREE_AND_NULL(inc.remote_urls);\n+\tif (inc.remote_urls_initialized) {\n+\t\tstruct hashmap_iter iter;\n+\t\tstruct remote_urls_entry *remote;\n+\t\thashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {\n+\t\t\tstring_list_clear(&remote->urls, 0);\n+\t\t\tfree(remote->name);\n+\t\t}\n+\t\thashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);\n+\t\tinc.remote_urls_initialized = 0;\n \t}\n \treturn ret;\n }\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex f13277c8f3..b04908b6ed 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2831,6 +2831,34 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such\n \tgrep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url\" err\n '\n \n+test_expect_success 'includeIf.hasconfig:remote.<name>.url' '\n+\tgit init hasremoteurlTest &&\n+\ttest_when_finished \"rm -rf hasremoteurlTest\" &&\n+\n+\tcat >include-this <<-\\EOF &&\n+\t[user]\n+\t\tthis = this-is-included\n+\tEOF\n+\tcat >dont-include-that <<-\\EOF &&\n+\t[user]\n+\t\tthat = that-is-not-included\n+\tEOF\n+\tcat >>hasremoteurlTest/.git/config <<-EOF &&\n+\t[includeIf \"hasconfig:remote.foo.url:sameurl\"]\n+\t\tpath = \"$(pwd)/include-this\"\n+\t[includeIf \"hasconfig:remote.bar.url:sameurl\"]\n+\t\tpath = \"$(pwd)/dont-include-that\"\n+\t[remote \"foo\"]\n+\t\turl = sameurl\n+\tEOF\n+\n+\techo this-is-included >expect-this &&\n+\tgit -C hasremoteurlTest config --get user.this >actual-this &&\n+\ttest_cmp expect-this actual-this &&\n+\n+\ttest_must_fail git -C hasremoteurlTest config --get user.that\n+'\n+\n test_expect_success 'negated mode causes failure' '\n \ttest_must_fail git config --no-get 2>err &&\n \tgrep \"unknown option \\`no-get${SQ}\" err\n-- \n2.47.0\n\n"},{"id":"505563","messageId":"086ff955-bcdf-4aba-b6d4-c87f6fc6a724@ramsayjones.plus.com","threadId":"62373","inReplyTo":"20241020160143.92382-2-ken@kmatsui.me","subject":"Re: [PATCH v2] config: support remote name in includeIf.hasconfig condition","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2024-10-20T16:21:46Z","receivedAt":"2024-10-20T16:21:52Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 20/10/2024 17:01, Ken Matsui wrote:\n> Changes in v2:\n> \n> * Updated the description based on Kristoffer's review.\n> \n> -- >8 --\n> \n> includeIf.hasconfig only accepts remote.*.url, making it difficult to\n> apply configuration based on a specific remote, especially in projects\n> with multiple remotes (e.g., GitHub and non-GitHub hosting).  This often\n> leads to undesired application of multiple config files.\n> \n> For example, the following configuration:\n> \n>   [remote \"origin\"]\n>     url = https://git.kernel.org/pub/scm/git/git.git\n>   [remote \"github\"]\n>     url = https://github.com/myfork/git.git\n> \n>   [includeIf \"hasconfig:remote.*.url:https://github.com/**\"]\n>     path = github.inc\n>   [includeIf \"hasconfig:remote.*.url:https://git.kernel.org/**\"]\n>     path = git.inc\n> \n> would apply both github.inc and git.inc, even when only one config is\n> intended for the repository.\n> \n> Introduce support for specifying a remote name (e.g., origin) to enable\n> more precise configuration management:\n> \n>   [includeIf \"hasconfig:remote.origin.url:https://github.com/**\"]\n\ns/remote.origin.url/remote.github.url/ ?\n\nI haven't actually read the patch, so take with a pinch of salt. :)\n\n>     path = github.inc\n>   [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/**\"]\n>     path = git.inc\n> \n> This ensures that only the intended config file is applied based on the\n> specific remote.\n> \n\nATB,\nRamsay Jones\n\n"},{"id":"505567","messageId":"EEsVJheBDeI0kEJr8KABt7dw0F14feUrMlvzJsmzrGmUBwt19aSJd4iWClSlzQ1GSoIe6qMQ5OcoiWYF9LQDPu-26QWmDy57efnYaZX6ZSo=@kmatsui.me","threadId":"62373","inReplyTo":"086ff955-bcdf-4aba-b6d4-c87f6fc6a724@ramsayjones.plus.com","subject":"Re: [PATCH v2] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-10-20T17:18:51Z","receivedAt":"2024-10-20T17:19:02Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"On Sunday, October 20th, 2024 at 12:21 PM, Ramsay Jones <ramsay@ramsayjones.plus.com> wrote:\n\n> \n> \n> \n> \n> On 20/10/2024 17:01, Ken Matsui wrote:\n> \n> > Changes in v2:\n> > \n> > * Updated the description based on Kristoffer's review.\n> > \n> > -- >8 --\n> > \n> > includeIf.hasconfig only accepts remote.*.url, making it difficult to\n> > apply configuration based on a specific remote, especially in projects\n> > with multiple remotes (e.g., GitHub and non-GitHub hosting). This often\n> > leads to undesired application of multiple config files.\n> > \n> > For example, the following configuration:\n> > \n> > [remote \"origin\"]\n> > url = https://git.kernel.org/pub/scm/git/git.git\n> > [remote \"github\"]\n> > url = https://github.com/myfork/git.git\n> > \n> > [includeIf \"hasconfig:remote..url:https://github.com/**\"]\n> > path = github.inc\n> > [includeIf \"hasconfig:remote..url:https://git.kernel.org/**\"]\n> > path = git.inc\n> > \n> > would apply both github.inc and git.inc, even when only one config is\n> > intended for the repository.\n> > \n> > Introduce support for specifying a remote name (e.g., origin) to enable\n> > more precise configuration management:\n> > \n> > [includeIf \"hasconfig:remote.origin.url:https://github.com/**\"]\n> \n> \n> s/remote.origin.url/remote.github.url/ ?\n> \n> I haven't actually read the patch, so take with a pinch of salt. :)\n\nActually, this should be as-is.  The configuration means only if\nthe GitHub URL is used for origin, we include github.inc.  In this\nrepo, we won't include github.inc and only include git.inc for\nwhatever reason.\n\nThank you!\n\n> \n> > path = github.inc\n> > [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/**\"]\n> > path = git.inc\n> > \n> > This ensures that only the intended config file is applied based on the\n> > specific remote.\n> \n> \n> ATB,\n> Ramsay Jones\n"},{"id":"505568","messageId":"3QE-ZkcELIkznxtQD_EXgNaoyGwIe26lO2RJ8S2qnqcD1T1tcKWuvxgVaV_WxLooZMVld4wEJdkhbQD1HK494wbc52rrqzkbvPKHaosr8v0=@kmatsui.me","threadId":"62373","inReplyTo":"EEsVJheBDeI0kEJr8KABt7dw0F14feUrMlvzJsmzrGmUBwt19aSJd4iWClSlzQ1GSoIe6qMQ5OcoiWYF9LQDPu-26QWmDy57efnYaZX6ZSo=@kmatsui.me","subject":"Re: [PATCH v2] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-10-20T17:30:28Z","receivedAt":"2024-10-20T17:30:43Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"On Sunday, October 20th, 2024 at 1:18 PM, Ken Matsui <ken@kmatsui.me> wrote:\n\n> \n> \n> On Sunday, October 20th, 2024 at 12:21 PM, Ramsay Jones ramsay@ramsayjones.plus.com wrote:\n> \n> > On 20/10/2024 17:01, Ken Matsui wrote:\n> > \n> > > Changes in v2:\n> > > \n> > > * Updated the description based on Kristoffer's review.\n> > > \n> > > -- >8 --\n> > > \n> > > includeIf.hasconfig only accepts remote.*.url, making it difficult to\n> > > apply configuration based on a specific remote, especially in projects\n> > > with multiple remotes (e.g., GitHub and non-GitHub hosting). This often\n> > > leads to undesired application of multiple config files.\n> > > \n> > > For example, the following configuration:\n> > > \n> > > [remote \"origin\"]\n> > > url = https://git.kernel.org/pub/scm/git/git.git\n> > > [remote \"github\"]\n> > > url = https://github.com/myfork/git.git\n> > > \n> > > [includeIf \"hasconfig:remote..url:https://github.com/\"]\n> > > path = github.inc\n> > > [includeIf \"hasconfig:remote..url:https://git.kernel.org/\"]\n> > > path = git.inc\n> > > \n> > > would apply both github.inc and git.inc, even when only one config is\n> > > intended for the repository.\n> > > \n> > > Introduce support for specifying a remote name (e.g., origin) to enable\n> > > more precise configuration management:\n> > > \n> > > [includeIf \"hasconfig:remote.origin.url:https://github.com/**\"]\n> > \n> > s/remote.origin.url/remote.github.url/ ?\n> > \n> > I haven't actually read the patch, so take with a pinch of salt. :)\n> \n> \n> Actually, this should be as-is. The configuration means only if\n> the GitHub URL is used for origin, we include github.inc. In this\n> repo, we won't include github.inc and only include git.inc for\n> whatever reason.\n> \n> Thank you!\n\nI will soon update this patch's description and add more tests to avoid this kind of confusion.\n\n> \n> > > path = github.inc\n> > > [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/**\"]\n> > > path = git.inc\n> > > \n> > > This ensures that only the intended config file is applied based on the\n> > > specific remote.\n> > \n> > ATB,\n> > Ramsay Jones\n"},{"id":"505569","messageId":"20241020173216.40852-2-ken@kmatsui.me","threadId":"62373","inReplyTo":"20241020160143.92382-2-ken@kmatsui.me","subject":"[PATCH v3] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-10-20T17:32:16Z","receivedAt":"2024-10-20T17:33:44Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"Changes in v3:\n\n* Updated the description based on Ramsay's review.\n* Added more tests.\n\nChanges in v2:\n\n* Updated the description based on Kristoffer's review.\n\n-- >8 --\n\nincludeIf.hasconfig only accepts remote.*.url, making it difficult to\napply configuration based on a specific remote, especially in projects\nwith multiple remotes (e.g., GitHub and non-GitHub hosting).  This often\nleads to undesired application of multiple config files.\n\nFor example, the following configuration:\n\n  [remote \"origin\"]\n    url = https://git.kernel.org/pub/scm/git/git.git\n  [remote \"github\"]\n    url = https://github.com/myfork/git.git\n\n  [includeIf \"hasconfig:remote.*.url:https://github.com/**\"]\n    path = github.inc\n  [includeIf \"hasconfig:remote.*.url:https://git.kernel.org/**\"]\n    path = git.inc\n\nwould apply both github.inc and git.inc, even when only one config is\nintended for the repository.\n\nIntroduce support for specifying a remote name (e.g., origin) to enable\nmore precise configuration management:\n\n  [includeIf \"hasconfig:remote.origin.url:https://github.com/**\"]\n    path = github.inc\n  [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/**\"]\n    path = git.inc\n\nThis ensures that only git.inc is included in this configuration while\nother repositories that use github.com for origin can only include\ngithub.inc.\n\nSigned-off-by: Ken Matsui <ken@kmatsui.me>\n---\n Documentation/config.txt |  13 +++-\n config.c                 | 141 ++++++++++++++++++++++++++++++++++-----\n t/t1300-config.sh        |  84 +++++++++++++++++++++++\n 3 files changed, 219 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 8c0b3ed807..22b50d523d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -161,14 +161,17 @@ all branches that begin with `foo/`. This is useful if your branches are\n organized hierarchically and you would like to apply a configuration to\n all the branches in that hierarchy.\n \n-`hasconfig:remote.*.url:`::\n+`hasconfig:remote.(<name>|*).url:`::\n \tThe data that follows this keyword is taken to\n \tbe a pattern with standard globbing wildcards and two\n \tadditional ones, `**/` and `/**`, that can match multiple\n \tcomponents. The first time this keyword is seen, the rest of\n \tthe config files will be scanned for remote URLs (without\n \tapplying any values). If there exists at least one remote URL\n-\tthat matches this pattern, the include condition is met.\n+\tthat matches this pattern, the include condition is met. `<name>`\n+\tis the name of the remote, and `*` matches any remote name. Note\n+\tthat `<name>` is not globbed, so it must be an exact match (e.g.,\n+\t`dev-*` will not match `dev-foo`).\n +\n Files included by this option (directly or indirectly) are not allowed\n to contain remote URLs.\n@@ -263,6 +266,12 @@ Example\n \tpath = foo.inc\n [remote \"origin\"]\n \turl = https://example.com/git\n+\n+; include only if the given remote with the given URL exist.\n+[includeIf \"hasconfig:remote.origin.url:https://example.com/**\"]\n+\tpath = foo.inc\n+[remote \"origin\"]\n+\turl = https://example.com/git\n ----\n \n Values\ndiff --git a/config.c b/config.c\nindex a11bb85da3..9de58eec7a 100644\n--- a/config.c\n+++ b/config.c\n@@ -123,6 +123,37 @@ static long config_buf_ftell(struct config_source *conf)\n \treturn conf->u.buf.pos;\n }\n \n+struct remote_urls_entry {\n+\tstruct hashmap_entry ent;\n+\tchar *name;\n+\tstruct string_list urls;\n+};\n+\n+static struct remote_urls_entry *remote_urls_find_entry(struct hashmap *remote_urls,\n+\t\t\t\t\t\t\tchar *name)\n+{\n+\tstruct remote_urls_entry k;\n+\tstruct remote_urls_entry *found_entry;\n+\n+\thashmap_entry_init(&k.ent, strhash(name));\n+\tk.name = name;\n+\tfound_entry = hashmap_get_entry(remote_urls, &k, ent, NULL);\n+\treturn found_entry;\n+}\n+\n+static int remote_urls_entry_cmp(const void *cmp_data UNUSED,\n+\t\t\t\t const struct hashmap_entry *eptr,\n+\t\t\t\t const struct hashmap_entry *entry_or_key,\n+\t\t\t\t const void *keydata UNUSED)\n+{\n+\tconst struct remote_urls_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct remote_urls_entry, ent);\n+\te2 = container_of(entry_or_key, const struct remote_urls_entry, ent);\n+\n+\treturn strcmp(e1->name, e2->name);\n+}\n+\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -132,9 +163,10 @@ struct config_include_data {\n \tstruct repository *repo;\n \n \t/*\n-\t * All remote URLs discovered when reading all config files.\n+\t * All remote names & URLs discovered when reading all config files.\n \t */\n-\tstruct string_list *remote_urls;\n+\tstruct hashmap remote_urls;\n+\tint remote_urls_initialized;\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n@@ -328,16 +360,36 @@ static int include_by_branch(struct config_include_data *data,\n static int add_remote_url(const char *var, const char *value,\n \t\t\t  const struct config_context *ctx UNUSED, void *data)\n {\n-\tstruct string_list *remote_urls = data;\n-\tconst char *remote_name;\n+\tstruct hashmap *remote_urls = data;\n+\tconst char *remote_section;\n \tsize_t remote_name_len;\n \tconst char *key;\n \n-\tif (!parse_config_key(var, \"remote\", &remote_name, &remote_name_len,\n+\tif (!parse_config_key(var, \"remote\", &remote_section, &remote_name_len,\n \t\t\t      &key) &&\n-\t    remote_name &&\n-\t    !strcmp(key, \"url\"))\n-\t\tstring_list_append(remote_urls, value);\n+\t    remote_section &&\n+\t    !strcmp(key, \"url\")) {\n+\t\tconst char *dot;\n+\t\tchar *remote_name;\n+\t\tstruct remote_urls_entry *e;\n+\n+\t\tdot = strchr(remote_section, '.');\n+\t\tif (!dot)\n+\t\t\treturn 0;\n+\n+\t\tremote_name = xstrndup(remote_section, dot - remote_section);\n+\t\te = remote_urls_find_entry(remote_urls, remote_name);\n+\t\tif (!e) {\n+\t\t\te = xmalloc(sizeof(*e));\n+\t\t\thashmap_entry_init(&e->ent, strhash(remote_name));\n+\t\t\te->name = remote_name;\n+\t\t\tstring_list_init_dup(&e->urls);\n+\t\t\tstring_list_append(&e->urls, value);\n+\t\t\thashmap_add(remote_urls, &e->ent);\n+\t\t} else {\n+\t\t\tstring_list_append(&e->urls, value);\n+\t\t}\n+\t}\n \treturn 0;\n }\n \n@@ -348,9 +400,9 @@ static void populate_remote_urls(struct config_include_data *inc)\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n-\tstring_list_init_dup(inc->remote_urls);\n-\tconfig_with_options(add_remote_url, inc->remote_urls,\n+\thashmap_init(&inc->remote_urls, remote_urls_entry_cmp, NULL, 0);\n+\tinc->remote_urls_initialized = 1;\n+\tconfig_with_options(add_remote_url, &inc->remote_urls,\n \t\t\t    inc->config_source, inc->repo, &opts);\n }\n \n@@ -391,12 +443,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,\n static int include_by_remote_url(struct config_include_data *inc,\n \t\tconst char *cond, size_t cond_len)\n {\n+\tstruct hashmap_iter iter;\n+\tstruct remote_urls_entry *remote;\n+\n+\tif (inc->opts->unconditional_remote_url)\n+\t\treturn 1;\n+\tif (!inc->remote_urls_initialized)\n+\t\tpopulate_remote_urls(inc);\n+\n+\thashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)\n+\t\tif (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))\n+\t\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int include_by_remote_name_and_url(struct config_include_data *inc,\n+\t\t\t\t\t  const char *cond, size_t cond_len,\n+\t\t\t\t\t  char *remote_name)\n+{\n+\tstruct remote_urls_entry *e;\n+\n \tif (inc->opts->unconditional_remote_url)\n \t\treturn 1;\n-\tif (!inc->remote_urls)\n+\tif (!inc->remote_urls_initialized)\n \t\tpopulate_remote_urls(inc);\n-\treturn at_least_one_url_matches_glob(cond, cond_len,\n-\t\t\t\t\t     inc->remote_urls);\n+\n+\te = remote_urls_find_entry(&inc->remote_urls, remote_name);\n+\tif (!e)\n+\t\treturn 0;\n+\treturn at_least_one_url_matches_glob(cond, cond_len, &e->urls);\n }\n \n static int include_condition_is_true(const struct key_value_info *kvi,\n@@ -414,6 +489,32 @@ static int include_condition_is_true(const struct key_value_info *kvi,\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n \t\t\t\t   &cond_len))\n \t\treturn include_by_remote_url(inc, cond, cond_len);\n+\telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.\", &cond,\n+\t\t\t\t &cond_len)) {\n+\t\tconst char *dot;\n+\t\tchar *remote_name;\n+\t\tchar *cond_prefix;\n+\t\tint ret;\n+\n+\t\tdot = strchr(cond, '.');\n+\t\tif (!dot)\n+\t\t\treturn 0;\n+\n+\t\tremote_name = xstrndup(cond, dot - cond);\n+\t\tcond_prefix = xstrfmt(\"%s.url:\", remote_name);\n+\t\tif (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,\n+\t\t\t\t     &cond_len)) {\n+\t\t\tfree(cond_prefix);\n+\t\t\tfree(remote_name);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tfree(cond_prefix);\n+\n+\t\tret = include_by_remote_name_and_url(inc, cond, cond_len,\n+\t\t\t\t\t\t     remote_name);\n+\t\tfree(remote_name);\n+\t\treturn ret;\n+\t}\n \n \t/* unknown conditionals are always false */\n \treturn 0;\n@@ -2151,9 +2252,15 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = do_git_config_sequence(opts, repo, fn, data);\n \t}\n \n-\tif (inc.remote_urls) {\n-\t\tstring_list_clear(inc.remote_urls, 0);\n-\t\tFREE_AND_NULL(inc.remote_urls);\n+\tif (inc.remote_urls_initialized) {\n+\t\tstruct hashmap_iter iter;\n+\t\tstruct remote_urls_entry *remote;\n+\t\thashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {\n+\t\t\tstring_list_clear(&remote->urls, 0);\n+\t\t\tfree(remote->name);\n+\t\t}\n+\t\thashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);\n+\t\tinc.remote_urls_initialized = 0;\n \t}\n \treturn ret;\n }\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex f13277c8f3..81f149a712 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2831,6 +2831,90 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such\n \tgrep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url\" err\n '\n \n+test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and the same URL' '\n+\tgit init hasremoteurlTest &&\n+\ttest_when_finished \"rm -rf hasremoteurlTest\" &&\n+\n+\tcat >include-this <<-\\EOF &&\n+\t[user]\n+\t\tthis = this-is-included\n+\tEOF\n+\tcat >dont-include-that <<-\\EOF &&\n+\t[user]\n+\t\tthat = that-is-not-included\n+\tEOF\n+\tcat >>hasremoteurlTest/.git/config <<-EOF &&\n+\t[includeIf \"hasconfig:remote.foo.url:sameurl\"]\n+\t\tpath = \"$(pwd)/include-this\"\n+\t[includeIf \"hasconfig:remote.bar.url:sameurl\"]\n+\t\tpath = \"$(pwd)/dont-include-that\"\n+\t[remote \"foo\"]\n+\t\turl = sameurl\n+\tEOF\n+\n+\techo this-is-included >expect-this &&\n+\tgit -C hasremoteurlTest config --get user.this >actual-this &&\n+\ttest_cmp expect-this actual-this &&\n+\n+\ttest_must_fail git -C hasremoteurlTest config --get user.that\n+'\n+\n+test_expect_success 'includeIf.hasconfig:remote.<name>.url with the same remote name and different URLs' '\n+\tgit init hasremoteurlTest &&\n+\ttest_when_finished \"rm -rf hasremoteurlTest\" &&\n+\n+\tcat >include-this <<-\\EOF &&\n+\t[user]\n+\t\tthis = this-is-included\n+\tEOF\n+\tcat >dont-include-that <<-\\EOF &&\n+\t[user]\n+\t\tthat = that-is-not-included\n+\tEOF\n+\tcat >>hasremoteurlTest/.git/config <<-EOF &&\n+\t[includeIf \"hasconfig:remote.foo.url:foourl\"]\n+\t\tpath = \"$(pwd)/include-this\"\n+\t[includeIf \"hasconfig:remote.foo.url:barurl\"]\n+\t\tpath = \"$(pwd)/dont-include-that\"\n+\t[remote \"foo\"]\n+\t\turl = foourl\n+\tEOF\n+\n+\techo this-is-included >expect-this &&\n+\tgit -C hasremoteurlTest config --get user.this >actual-this &&\n+\ttest_cmp expect-this actual-this &&\n+\n+\ttest_must_fail git -C hasremoteurlTest config --get user.that\n+'\n+\n+test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and URLs' '\n+\tgit init hasremoteurlTest &&\n+\ttest_when_finished \"rm -rf hasremoteurlTest\" &&\n+\n+\tcat >include-this <<-\\EOF &&\n+\t[user]\n+\t\tthis = this-is-included\n+\tEOF\n+\tcat >dont-include-that <<-\\EOF &&\n+\t[user]\n+\t\tthat = that-is-not-included\n+\tEOF\n+\tcat >>hasremoteurlTest/.git/config <<-EOF &&\n+\t[includeIf \"hasconfig:remote.foo.url:foourl\"]\n+\t\tpath = \"$(pwd)/include-this\"\n+\t[includeIf \"hasconfig:remote.bar.url:barurl\"]\n+\t\tpath = \"$(pwd)/dont-include-that\"\n+\t[remote \"foo\"]\n+\t\turl = foourl\n+\tEOF\n+\n+\techo this-is-included >expect-this &&\n+\tgit -C hasremoteurlTest config --get user.this >actual-this &&\n+\ttest_cmp expect-this actual-this &&\n+\n+\ttest_must_fail git -C hasremoteurlTest config --get user.that\n+'\n+\n test_expect_success 'negated mode causes failure' '\n \ttest_must_fail git config --no-get 2>err &&\n \tgrep \"unknown option \\`no-get${SQ}\" err\n-- \n2.47.0\n\n"},{"id":"506717","messageId":"xFZco4IaItgwJDpEyko_Jb0z3REcs1FQsDeBdFXYHXfNAbipV9G87Otnv5SMK4J1eT4iT0E7OlVPvtSjs-ZiPZzHgcguaXLvBCs_iahqdbM=@kmatsui.me","threadId":"62373","inReplyTo":"20241020173216.40852-2-ken@kmatsui.me","subject":"Re: [PATCH v3] config: support remote name in includeIf.hasconfig condition","fromName":"Ken Matsui","fromEmail":"ken@kmatsui.me","sentAt":"2024-11-06T13:30:46Z","receivedAt":"2024-11-06T13:32:00Z","isPatch":true,"sender":{"key":"ken@kmatsui.me","avatar":null},"body":"Ping.\n\nP.S. Some users have been attempting to use\nhasconfig:remote.origin.url, so I believe this patch is a logical\nimprovement.\n\nhttps://github.com/search?q=%22hasconfig%3Aremote.origin.url%22&type=code\n\nOn Sunday, October 20th, 2024 at 1:32 PM, Ken Matsui <ken@kmatsui.me> wrote:\n\n>\n>\n> Changes in v3:\n>\n> * Updated the description based on Ramsay's review.\n> * Added more tests.\n>\n> Changes in v2:\n>\n> * Updated the description based on Kristoffer's review.\n>\n> -- >8 --\n>\n>\n> includeIf.hasconfig only accepts remote..url, making it difficult to\n> apply configuration based on a specific remote, especially in projects\n> with multiple remotes (e.g., GitHub and non-GitHub hosting). This often\n> leads to undesired application of multiple config files.\n>\n> For example, the following configuration:\n>\n> [remote \"origin\"]\n> url = https://git.kernel.org/pub/scm/git/git.git\n> [remote \"github\"]\n> url = https://github.com/myfork/git.git\n>\n> [includeIf \"hasconfig:remote..url:https://github.com/\"]\n> path = github.inc\n> [includeIf \"hasconfig:remote.*.url:https://git.kernel.org/\"]\n> path = git.inc\n>\n> would apply both github.inc and git.inc, even when only one config is\n> intended for the repository.\n>\n> Introduce support for specifying a remote name (e.g., origin) to enable\n> more precise configuration management:\n>\n> [includeIf \"hasconfig:remote.origin.url:https://github.com/\"]\n> path = github.inc\n> [includeIf \"hasconfig:remote.origin.url:https://git.kernel.org/\"]\n> path = git.inc\n>\n> This ensures that only git.inc is included in this configuration while\n> other repositories that use github.com for origin can only include\n> github.inc.\n>\n> Signed-off-by: Ken Matsui ken@kmatsui.me\n>\n> ---\n> Documentation/config.txt | 13 +++-\n> config.c | 141 ++++++++++++++++++++++++++++++++++-----\n> t/t1300-config.sh | 84 +++++++++++++++++++++++\n> 3 files changed, 219 insertions(+), 19 deletions(-)\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 8c0b3ed807..22b50d523d 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -161,14 +161,17 @@ all branches that begin with `foo/`. This is useful if your branches are\n> organized hierarchically and you would like to apply a configuration to\n> all the branches in that hierarchy.\n>\n> -`hasconfig:remote.*.url:`::\n> +`hasconfig:remote.(<name>|*).url:`::\n>\n> The data that follows this keyword is taken to\n> be a pattern with standard globbing wildcards and two\n> additional ones, `**/` and `/**`, that can match multiple\n> components. The first time this keyword is seen, the rest of\n> the config files will be scanned for remote URLs (without\n> applying any values). If there exists at least one remote URL\n> - that matches this pattern, the include condition is met.\n> + that matches this pattern, the include condition is met. `<name>`\n>\n> + is the name of the remote, and `*` matches any remote name. Note\n> + that `<name>` is not globbed, so it must be an exact match (e.g.,\n>\n> + `dev-*` will not match `dev-foo`).\n> +\n> Files included by this option (directly or indirectly) are not allowed\n> to contain remote URLs.\n> @@ -263,6 +266,12 @@ Example\n> path = foo.inc\n> [remote \"origin\"]\n> url = https://example.com/git\n> +\n> +; include only if the given remote with the given URL exist.\n> +[includeIf \"hasconfig:remote.origin.url:https://example.com/**\"]\n> + path = foo.inc\n> +[remote \"origin\"]\n> + url = https://example.com/git\n> ----\n>\n> Values\n> diff --git a/config.c b/config.c\n> index a11bb85da3..9de58eec7a 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -123,6 +123,37 @@ static long config_buf_ftell(struct config_source *conf)\n> return conf->u.buf.pos;\n>\n> }\n>\n> +struct remote_urls_entry {\n> + struct hashmap_entry ent;\n> + char *name;\n> + struct string_list urls;\n> +};\n> +\n> +static struct remote_urls_entry *remote_urls_find_entry(struct hashmap *remote_urls,\n> + char *name)\n> +{\n> + struct remote_urls_entry k;\n> + struct remote_urls_entry *found_entry;\n> +\n> + hashmap_entry_init(&k.ent, strhash(name));\n> + k.name = name;\n> + found_entry = hashmap_get_entry(remote_urls, &k, ent, NULL);\n> + return found_entry;\n> +}\n> +\n> +static int remote_urls_entry_cmp(const void *cmp_data UNUSED,\n> + const struct hashmap_entry *eptr,\n> + const struct hashmap_entry *entry_or_key,\n> + const void *keydata UNUSED)\n> +{\n> + const struct remote_urls_entry *e1, *e2;\n> +\n> + e1 = container_of(eptr, const struct remote_urls_entry, ent);\n> + e2 = container_of(entry_or_key, const struct remote_urls_entry, ent);\n> +\n> + return strcmp(e1->name, e2->name);\n>\n> +}\n> +\n> struct config_include_data {\n> int depth;\n> config_fn_t fn;\n> @@ -132,9 +163,10 @@ struct config_include_data {\n> struct repository repo;\n>\n> /\n> - * All remote URLs discovered when reading all config files.\n> + * All remote names & URLs discovered when reading all config files.\n> */\n> - struct string_list *remote_urls;\n> + struct hashmap remote_urls;\n> + int remote_urls_initialized;\n> };\n> #define CONFIG_INCLUDE_INIT { 0 }\n>\n> @@ -328,16 +360,36 @@ static int include_by_branch(struct config_include_data *data,\n> static int add_remote_url(const char *var, const char *value,\n> const struct config_context *ctx UNUSED, void *data)\n> {\n> - struct string_list *remote_urls = data;\n> - const char *remote_name;\n> + struct hashmap *remote_urls = data;\n> + const char *remote_section;\n> size_t remote_name_len;\n> const char *key;\n>\n> - if (!parse_config_key(var, \"remote\", &remote_name, &remote_name_len,\n> + if (!parse_config_key(var, \"remote\", &remote_section, &remote_name_len,\n> &key) &&\n> - remote_name &&\n> - !strcmp(key, \"url\"))\n> - string_list_append(remote_urls, value);\n> + remote_section &&\n> + !strcmp(key, \"url\")) {\n> + const char *dot;\n> + char *remote_name;\n> + struct remote_urls_entry *e;\n> +\n> + dot = strchr(remote_section, '.');\n> + if (!dot)\n> + return 0;\n> +\n> + remote_name = xstrndup(remote_section, dot - remote_section);\n> + e = remote_urls_find_entry(remote_urls, remote_name);\n> + if (!e) {\n> + e = xmalloc(sizeof(*e));\n> + hashmap_entry_init(&e->ent, strhash(remote_name));\n>\n> + e->name = remote_name;\n>\n> + string_list_init_dup(&e->urls);\n>\n> + string_list_append(&e->urls, value);\n>\n> + hashmap_add(remote_urls, &e->ent);\n>\n> + } else {\n> + string_list_append(&e->urls, value);\n>\n> + }\n> + }\n> return 0;\n> }\n>\n> @@ -348,9 +400,9 @@ static void populate_remote_urls(struct config_include_data *inc)\n> opts = *inc->opts;\n>\n> opts.unconditional_remote_url = 1;\n>\n> - inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n>\n> - string_list_init_dup(inc->remote_urls);\n>\n> - config_with_options(add_remote_url, inc->remote_urls,\n>\n> + hashmap_init(&inc->remote_urls, remote_urls_entry_cmp, NULL, 0);\n>\n> + inc->remote_urls_initialized = 1;\n>\n> + config_with_options(add_remote_url, &inc->remote_urls,\n>\n> inc->config_source, inc->repo, &opts);\n>\n> }\n>\n> @@ -391,12 +443,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,\n> static int include_by_remote_url(struct config_include_data *inc,\n> const char *cond, size_t cond_len)\n> {\n> + struct hashmap_iter iter;\n> + struct remote_urls_entry *remote;\n> +\n> + if (inc->opts->unconditional_remote_url)\n>\n> + return 1;\n> + if (!inc->remote_urls_initialized)\n>\n> + populate_remote_urls(inc);\n> +\n> + hashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)\n>\n> + if (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))\n>\n> + return 1;\n> + return 0;\n> +}\n> +\n> +static int include_by_remote_name_and_url(struct config_include_data *inc,\n> + const char *cond, size_t cond_len,\n> + char *remote_name)\n> +{\n> + struct remote_urls_entry *e;\n> +\n> if (inc->opts->unconditional_remote_url)\n>\n> return 1;\n> - if (!inc->remote_urls)\n>\n> + if (!inc->remote_urls_initialized)\n>\n> populate_remote_urls(inc);\n> - return at_least_one_url_matches_glob(cond, cond_len,\n> - inc->remote_urls);\n>\n> +\n> + e = remote_urls_find_entry(&inc->remote_urls, remote_name);\n>\n> + if (!e)\n> + return 0;\n> + return at_least_one_url_matches_glob(cond, cond_len, &e->urls);\n>\n> }\n>\n> static int include_condition_is_true(const struct key_value_info *kvi,\n> @@ -414,6 +489,32 @@ static int include_condition_is_true(const struct key_value_info kvi,\n> else if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote..url:\", &cond,\n> &cond_len))\n> return include_by_remote_url(inc, cond, cond_len);\n> + else if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.\", &cond,\n> + &cond_len)) {\n> + const char *dot;\n> + char *remote_name;\n> + char cond_prefix;\n> + int ret;\n> +\n> + dot = strchr(cond, '.');\n> + if (!dot)\n> + return 0;\n> +\n> + remote_name = xstrndup(cond, dot - cond);\n> + cond_prefix = xstrfmt(\"%s.url:\", remote_name);\n> + if (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,\n> + &cond_len)) {\n> + free(cond_prefix);\n> + free(remote_name);\n> + return 0;\n> + }\n> + free(cond_prefix);\n> +\n> + ret = include_by_remote_name_and_url(inc, cond, cond_len,\n> + remote_name);\n> + free(remote_name);\n> + return ret;\n> + }\n>\n> / unknown conditionals are always false */\n> return 0;\n> @@ -2151,9 +2252,15 @@ int config_with_options(config_fn_t fn, void *data,\n> ret = do_git_config_sequence(opts, repo, fn, data);\n> }\n>\n> - if (inc.remote_urls) {\n> - string_list_clear(inc.remote_urls, 0);\n> - FREE_AND_NULL(inc.remote_urls);\n> + if (inc.remote_urls_initialized) {\n> + struct hashmap_iter iter;\n> + struct remote_urls_entry *remote;\n> + hashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {\n> + string_list_clear(&remote->urls, 0);\n>\n> + free(remote->name);\n>\n> + }\n> + hashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);\n> + inc.remote_urls_initialized = 0;\n> }\n> return ret;\n> }\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index f13277c8f3..81f149a712 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -2831,6 +2831,90 @@ test_expect_success 'includeIf.hasconfig:remote..url forbids remote url in such\n> grep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote..url\" err\n> '\n>\n> +test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and the same URL' '\n>\n> + git init hasremoteurlTest &&\n> + test_when_finished \"rm -rf hasremoteurlTest\" &&\n> +\n> + cat >include-this <<-\\EOF &&\n>\n> + [user]\n> + this = this-is-included\n> + EOF\n> + cat >dont-include-that <<-\\EOF &&\n>\n> + [user]\n> + that = that-is-not-included\n> + EOF\n> + cat >>hasremoteurlTest/.git/config <<-EOF &&\n>\n> + [includeIf \"hasconfig:remote.foo.url:sameurl\"]\n> + path = \"$(pwd)/include-this\"\n> + [includeIf \"hasconfig:remote.bar.url:sameurl\"]\n> + path = \"$(pwd)/dont-include-that\"\n> + [remote \"foo\"]\n> + url = sameurl\n> + EOF\n> +\n> + echo this-is-included >expect-this &&\n>\n> + git -C hasremoteurlTest config --get user.this >actual-this &&\n>\n> + test_cmp expect-this actual-this &&\n> +\n> + test_must_fail git -C hasremoteurlTest config --get user.that\n> +'\n> +\n> +test_expect_success 'includeIf.hasconfig:remote.<name>.url with the same remote name and different URLs' '\n>\n> + git init hasremoteurlTest &&\n> + test_when_finished \"rm -rf hasremoteurlTest\" &&\n> +\n> + cat >include-this <<-\\EOF &&\n>\n> + [user]\n> + this = this-is-included\n> + EOF\n> + cat >dont-include-that <<-\\EOF &&\n>\n> + [user]\n> + that = that-is-not-included\n> + EOF\n> + cat >>hasremoteurlTest/.git/config <<-EOF &&\n>\n> + [includeIf \"hasconfig:remote.foo.url:foourl\"]\n> + path = \"$(pwd)/include-this\"\n> + [includeIf \"hasconfig:remote.foo.url:barurl\"]\n> + path = \"$(pwd)/dont-include-that\"\n> + [remote \"foo\"]\n> + url = foourl\n> + EOF\n> +\n> + echo this-is-included >expect-this &&\n>\n> + git -C hasremoteurlTest config --get user.this >actual-this &&\n>\n> + test_cmp expect-this actual-this &&\n> +\n> + test_must_fail git -C hasremoteurlTest config --get user.that\n> +'\n> +\n> +test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and URLs' '\n>\n> + git init hasremoteurlTest &&\n> + test_when_finished \"rm -rf hasremoteurlTest\" &&\n> +\n> + cat >include-this <<-\\EOF &&\n>\n> + [user]\n> + this = this-is-included\n> + EOF\n> + cat >dont-include-that <<-\\EOF &&\n>\n> + [user]\n> + that = that-is-not-included\n> + EOF\n> + cat >>hasremoteurlTest/.git/config <<-EOF &&\n>\n> + [includeIf \"hasconfig:remote.foo.url:foourl\"]\n> + path = \"$(pwd)/include-this\"\n> + [includeIf \"hasconfig:remote.bar.url:barurl\"]\n> + path = \"$(pwd)/dont-include-that\"\n> + [remote \"foo\"]\n> + url = foourl\n> + EOF\n> +\n> + echo this-is-included >expect-this &&\n>\n> + git -C hasremoteurlTest config --get user.this >actual-this &&\n>\n> + test_cmp expect-this actual-this &&\n> +\n> + test_must_fail git -C hasremoteurlTest config --get user.that\n> +'\n> +\n> test_expect_success 'negated mode causes failure' '\n> test_must_fail git config --no-get 2>err &&\n>\n> grep \"unknown option \\`no-get${SQ}\" err\n> --\n> 2.47.0\n"},{"id":"508519","messageId":"xmqq8qsxcjjp.fsf@gitster.g","threadId":"62373","inReplyTo":"20241020173216.40852-2-ken@kmatsui.me","subject":"Re: [PATCH v3] config: support remote name in includeIf.hasconfig condition","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T07:28:10Z","receivedAt":"2024-12-03T07:28:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ken Matsui <ken@kmatsui.me> writes:\n\n> Changes in v3:\n>\n> * Updated the description based on Ramsay's review.\n> * Added more tests.\n>\n> Changes in v2:\n>\n> * Updated the description based on Kristoffer's review.\n\nThis has never seen any serious review since its original iterations\nwere posted late October.  Anybody interested [*]?\n\nOtherwise since it is approaching to be more than a month and a\nhalf old, I am going to mark the topic to be discarded.\n\nThanks.\n\n\n[Footnote]\n\n * I ask because apparently I am not interested at all.  At least\n   not yet, with what I saw in the previous postings in this thread.\n\n   My take on it is that the \"if any remote has this URL\" change\n   earlier did make some sort of sense, but I do not see the utility\n   of this change.  If we need to access both of these two remote\n   repositories, \"to access github, we need these definition, and to\n   access k.org, we need those, so include them\" do make sense, but\n   because these include do *not* limit their effectiveness upon\n   which remote each Git operation accesses (i.e. You cannot say\n   \"use this configuration setting while running 'git fetch origin'\n   but use that other setting while running 'git push github'\"), it\n   does not make much sense to say \"if 'origin' is X, then do this\",\n   at least to me.\n"}]}