Skip to content

Commit ab24d63

Browse files
committed
Merge branch 'km/config-remote-by-name' into seen
Support conditionally including configuration by remote name, instead of just URL. Will discard? * km/config-remote-by-name: config: support remote name in includeIf.hasconfig condition
2 parents 686c57b + ed573c4 commit ab24d63

File tree

3 files changed

+219
-19
lines changed

3 files changed

+219
-19
lines changed

Documentation/config.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,17 @@ all branches that begin with `foo/`. This is useful if your branches are
161161
organized hierarchically and you would like to apply a configuration to
162162
all the branches in that hierarchy.
163163

164-
`hasconfig:remote.*.url:`::
164+
`hasconfig:remote.(<name>|*).url:`::
165165
The data that follows this keyword is taken to
166166
be a pattern with standard globbing wildcards and two
167167
additional ones, `**/` and `/**`, that can match multiple
168168
components. The first time this keyword is seen, the rest of
169169
the config files will be scanned for remote URLs (without
170170
applying any values). If there exists at least one remote URL
171-
that matches this pattern, the include condition is met.
171+
that matches this pattern, the include condition is met. `<name>`
172+
is the name of the remote, and `*` matches any remote name. Note
173+
that `<name>` is not globbed, so it must be an exact match (e.g.,
174+
`dev-*` will not match `dev-foo`).
172175
+
173176
Files included by this option (directly or indirectly) are not allowed
174177
to contain remote URLs.
@@ -263,6 +266,12 @@ Example
263266
path = foo.inc
264267
[remote "origin"]
265268
url = https://example.com/git
269+
270+
; include only if the given remote with the given URL exist.
271+
[includeIf "hasconfig:remote.origin.url:https://example.com/**"]
272+
path = foo.inc
273+
[remote "origin"]
274+
url = https://example.com/git
266275
----
267276

268277
Values

config.c

Lines changed: 124 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ static long config_buf_ftell(struct config_source *conf)
124124
return conf->u.buf.pos;
125125
}
126126

127+
struct remote_urls_entry {
128+
struct hashmap_entry ent;
129+
char *name;
130+
struct string_list urls;
131+
};
132+
133+
static struct remote_urls_entry *remote_urls_find_entry(struct hashmap *remote_urls,
134+
char *name)
135+
{
136+
struct remote_urls_entry k;
137+
struct remote_urls_entry *found_entry;
138+
139+
hashmap_entry_init(&k.ent, strhash(name));
140+
k.name = name;
141+
found_entry = hashmap_get_entry(remote_urls, &k, ent, NULL);
142+
return found_entry;
143+
}
144+
145+
static int remote_urls_entry_cmp(const void *cmp_data UNUSED,
146+
const struct hashmap_entry *eptr,
147+
const struct hashmap_entry *entry_or_key,
148+
const void *keydata UNUSED)
149+
{
150+
const struct remote_urls_entry *e1, *e2;
151+
152+
e1 = container_of(eptr, const struct remote_urls_entry, ent);
153+
e2 = container_of(entry_or_key, const struct remote_urls_entry, ent);
154+
155+
return strcmp(e1->name, e2->name);
156+
}
157+
127158
struct config_include_data {
128159
int depth;
129160
config_fn_t fn;
@@ -133,9 +164,10 @@ struct config_include_data {
133164
struct repository *repo;
134165

135166
/*
136-
* All remote URLs discovered when reading all config files.
167+
* All remote names & URLs discovered when reading all config files.
137168
*/
138-
struct string_list *remote_urls;
169+
struct hashmap remote_urls;
170+
int remote_urls_initialized;
139171
};
140172
#define CONFIG_INCLUDE_INIT { 0 }
141173

@@ -329,16 +361,36 @@ static int include_by_branch(struct config_include_data *data,
329361
static int add_remote_url(const char *var, const char *value,
330362
const struct config_context *ctx UNUSED, void *data)
331363
{
332-
struct string_list *remote_urls = data;
333-
const char *remote_name;
364+
struct hashmap *remote_urls = data;
365+
const char *remote_section;
334366
size_t remote_name_len;
335367
const char *key;
336368

337-
if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
369+
if (!parse_config_key(var, "remote", &remote_section, &remote_name_len,
338370
&key) &&
339-
remote_name &&
340-
!strcmp(key, "url"))
341-
string_list_append(remote_urls, value);
371+
remote_section &&
372+
!strcmp(key, "url")) {
373+
const char *dot;
374+
char *remote_name;
375+
struct remote_urls_entry *e;
376+
377+
dot = strchr(remote_section, '.');
378+
if (!dot)
379+
return 0;
380+
381+
remote_name = xstrndup(remote_section, dot - remote_section);
382+
e = remote_urls_find_entry(remote_urls, remote_name);
383+
if (!e) {
384+
e = xmalloc(sizeof(*e));
385+
hashmap_entry_init(&e->ent, strhash(remote_name));
386+
e->name = remote_name;
387+
string_list_init_dup(&e->urls);
388+
string_list_append(&e->urls, value);
389+
hashmap_add(remote_urls, &e->ent);
390+
} else {
391+
string_list_append(&e->urls, value);
392+
}
393+
}
342394
return 0;
343395
}
344396

@@ -349,9 +401,9 @@ static void populate_remote_urls(struct config_include_data *inc)
349401
opts = *inc->opts;
350402
opts.unconditional_remote_url = 1;
351403

352-
inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
353-
string_list_init_dup(inc->remote_urls);
354-
config_with_options(add_remote_url, inc->remote_urls,
404+
hashmap_init(&inc->remote_urls, remote_urls_entry_cmp, NULL, 0);
405+
inc->remote_urls_initialized = 1;
406+
config_with_options(add_remote_url, &inc->remote_urls,
355407
inc->config_source, inc->repo, &opts);
356408
}
357409

@@ -392,12 +444,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,
392444
static int include_by_remote_url(struct config_include_data *inc,
393445
const char *cond, size_t cond_len)
394446
{
447+
struct hashmap_iter iter;
448+
struct remote_urls_entry *remote;
449+
450+
if (inc->opts->unconditional_remote_url)
451+
return 1;
452+
if (!inc->remote_urls_initialized)
453+
populate_remote_urls(inc);
454+
455+
hashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)
456+
if (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))
457+
return 1;
458+
return 0;
459+
}
460+
461+
static int include_by_remote_name_and_url(struct config_include_data *inc,
462+
const char *cond, size_t cond_len,
463+
char *remote_name)
464+
{
465+
struct remote_urls_entry *e;
466+
395467
if (inc->opts->unconditional_remote_url)
396468
return 1;
397-
if (!inc->remote_urls)
469+
if (!inc->remote_urls_initialized)
398470
populate_remote_urls(inc);
399-
return at_least_one_url_matches_glob(cond, cond_len,
400-
inc->remote_urls);
471+
472+
e = remote_urls_find_entry(&inc->remote_urls, remote_name);
473+
if (!e)
474+
return 0;
475+
return at_least_one_url_matches_glob(cond, cond_len, &e->urls);
401476
}
402477

403478
static int include_condition_is_true(const struct key_value_info *kvi,
@@ -415,6 +490,32 @@ static int include_condition_is_true(const struct key_value_info *kvi,
415490
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
416491
&cond_len))
417492
return include_by_remote_url(inc, cond, cond_len);
493+
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.", &cond,
494+
&cond_len)) {
495+
const char *dot;
496+
char *remote_name;
497+
char *cond_prefix;
498+
int ret;
499+
500+
dot = strchr(cond, '.');
501+
if (!dot)
502+
return 0;
503+
504+
remote_name = xstrndup(cond, dot - cond);
505+
cond_prefix = xstrfmt("%s.url:", remote_name);
506+
if (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,
507+
&cond_len)) {
508+
free(cond_prefix);
509+
free(remote_name);
510+
return 0;
511+
}
512+
free(cond_prefix);
513+
514+
ret = include_by_remote_name_and_url(inc, cond, cond_len,
515+
remote_name);
516+
free(remote_name);
517+
return ret;
518+
}
418519

419520
/* unknown conditionals are always false */
420521
return 0;
@@ -2130,9 +2231,15 @@ int config_with_options(config_fn_t fn, void *data,
21302231
ret = do_git_config_sequence(opts, repo, fn, data);
21312232
}
21322233

2133-
if (inc.remote_urls) {
2134-
string_list_clear(inc.remote_urls, 0);
2135-
FREE_AND_NULL(inc.remote_urls);
2234+
if (inc.remote_urls_initialized) {
2235+
struct hashmap_iter iter;
2236+
struct remote_urls_entry *remote;
2237+
hashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {
2238+
string_list_clear(&remote->urls, 0);
2239+
free(remote->name);
2240+
}
2241+
hashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);
2242+
inc.remote_urls_initialized = 0;
21362243
}
21372244
return ret;
21382245
}

t/t1300-config.sh

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2830,6 +2830,90 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such
28302830
grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
28312831
'
28322832

2833+
test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and the same URL' '
2834+
git init hasremoteurlTest &&
2835+
test_when_finished "rm -rf hasremoteurlTest" &&
2836+
2837+
cat >include-this <<-\EOF &&
2838+
[user]
2839+
this = this-is-included
2840+
EOF
2841+
cat >dont-include-that <<-\EOF &&
2842+
[user]
2843+
that = that-is-not-included
2844+
EOF
2845+
cat >>hasremoteurlTest/.git/config <<-EOF &&
2846+
[includeIf "hasconfig:remote.foo.url:sameurl"]
2847+
path = "$(pwd)/include-this"
2848+
[includeIf "hasconfig:remote.bar.url:sameurl"]
2849+
path = "$(pwd)/dont-include-that"
2850+
[remote "foo"]
2851+
url = sameurl
2852+
EOF
2853+
2854+
echo this-is-included >expect-this &&
2855+
git -C hasremoteurlTest config --get user.this >actual-this &&
2856+
test_cmp expect-this actual-this &&
2857+
2858+
test_must_fail git -C hasremoteurlTest config --get user.that
2859+
'
2860+
2861+
test_expect_success 'includeIf.hasconfig:remote.<name>.url with the same remote name and different URLs' '
2862+
git init hasremoteurlTest &&
2863+
test_when_finished "rm -rf hasremoteurlTest" &&
2864+
2865+
cat >include-this <<-\EOF &&
2866+
[user]
2867+
this = this-is-included
2868+
EOF
2869+
cat >dont-include-that <<-\EOF &&
2870+
[user]
2871+
that = that-is-not-included
2872+
EOF
2873+
cat >>hasremoteurlTest/.git/config <<-EOF &&
2874+
[includeIf "hasconfig:remote.foo.url:foourl"]
2875+
path = "$(pwd)/include-this"
2876+
[includeIf "hasconfig:remote.foo.url:barurl"]
2877+
path = "$(pwd)/dont-include-that"
2878+
[remote "foo"]
2879+
url = foourl
2880+
EOF
2881+
2882+
echo this-is-included >expect-this &&
2883+
git -C hasremoteurlTest config --get user.this >actual-this &&
2884+
test_cmp expect-this actual-this &&
2885+
2886+
test_must_fail git -C hasremoteurlTest config --get user.that
2887+
'
2888+
2889+
test_expect_success 'includeIf.hasconfig:remote.<name>.url with different remote names and URLs' '
2890+
git init hasremoteurlTest &&
2891+
test_when_finished "rm -rf hasremoteurlTest" &&
2892+
2893+
cat >include-this <<-\EOF &&
2894+
[user]
2895+
this = this-is-included
2896+
EOF
2897+
cat >dont-include-that <<-\EOF &&
2898+
[user]
2899+
that = that-is-not-included
2900+
EOF
2901+
cat >>hasremoteurlTest/.git/config <<-EOF &&
2902+
[includeIf "hasconfig:remote.foo.url:foourl"]
2903+
path = "$(pwd)/include-this"
2904+
[includeIf "hasconfig:remote.bar.url:barurl"]
2905+
path = "$(pwd)/dont-include-that"
2906+
[remote "foo"]
2907+
url = foourl
2908+
EOF
2909+
2910+
echo this-is-included >expect-this &&
2911+
git -C hasremoteurlTest config --get user.this >actual-this &&
2912+
test_cmp expect-this actual-this &&
2913+
2914+
test_must_fail git -C hasremoteurlTest config --get user.that
2915+
'
2916+
28332917
test_expect_success 'negated mode causes failure' '
28342918
test_must_fail git config --no-get 2>err &&
28352919
grep "unknown option \`no-get${SQ}" err

0 commit comments

Comments
 (0)