Skip to content

Commit 68fdd60

Browse files
committed
Merge branch 'km/config-remote-by-name' into seen
Support conditionally including configuration by remote name, instead of just URL. * km/config-remote-by-name: config: support remote name in includeIf.hasconfig condition
2 parents 6be6a6f + ed573c4 commit 68fdd60

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
@@ -123,6 +123,37 @@ static long config_buf_ftell(struct config_source *conf)
123123
return conf->u.buf.pos;
124124
}
125125

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

134165
/*
135-
* All remote URLs discovered when reading all config files.
166+
* All remote names & URLs discovered when reading all config files.
136167
*/
137-
struct string_list *remote_urls;
168+
struct hashmap remote_urls;
169+
int remote_urls_initialized;
138170
};
139171
#define CONFIG_INCLUDE_INIT { 0 }
140172

@@ -328,16 +360,36 @@ static int include_by_branch(struct config_include_data *data,
328360
static int add_remote_url(const char *var, const char *value,
329361
const struct config_context *ctx UNUSED, void *data)
330362
{
331-
struct string_list *remote_urls = data;
332-
const char *remote_name;
363+
struct hashmap *remote_urls = data;
364+
const char *remote_section;
333365
size_t remote_name_len;
334366
const char *key;
335367

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

@@ -348,9 +400,9 @@ static void populate_remote_urls(struct config_include_data *inc)
348400
opts = *inc->opts;
349401
opts.unconditional_remote_url = 1;
350402

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

@@ -391,12 +443,35 @@ static int at_least_one_url_matches_glob(const char *glob, int glob_len,
391443
static int include_by_remote_url(struct config_include_data *inc,
392444
const char *cond, size_t cond_len)
393445
{
446+
struct hashmap_iter iter;
447+
struct remote_urls_entry *remote;
448+
449+
if (inc->opts->unconditional_remote_url)
450+
return 1;
451+
if (!inc->remote_urls_initialized)
452+
populate_remote_urls(inc);
453+
454+
hashmap_for_each_entry(&inc->remote_urls, &iter, remote, ent)
455+
if (at_least_one_url_matches_glob(cond, cond_len, &remote->urls))
456+
return 1;
457+
return 0;
458+
}
459+
460+
static int include_by_remote_name_and_url(struct config_include_data *inc,
461+
const char *cond, size_t cond_len,
462+
char *remote_name)
463+
{
464+
struct remote_urls_entry *e;
465+
394466
if (inc->opts->unconditional_remote_url)
395467
return 1;
396-
if (!inc->remote_urls)
468+
if (!inc->remote_urls_initialized)
397469
populate_remote_urls(inc);
398-
return at_least_one_url_matches_glob(cond, cond_len,
399-
inc->remote_urls);
470+
471+
e = remote_urls_find_entry(&inc->remote_urls, remote_name);
472+
if (!e)
473+
return 0;
474+
return at_least_one_url_matches_glob(cond, cond_len, &e->urls);
400475
}
401476

402477
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,
414489
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
415490
&cond_len))
416491
return include_by_remote_url(inc, cond, cond_len);
492+
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.", &cond,
493+
&cond_len)) {
494+
const char *dot;
495+
char *remote_name;
496+
char *cond_prefix;
497+
int ret;
498+
499+
dot = strchr(cond, '.');
500+
if (!dot)
501+
return 0;
502+
503+
remote_name = xstrndup(cond, dot - cond);
504+
cond_prefix = xstrfmt("%s.url:", remote_name);
505+
if (!skip_prefix_mem(cond, cond_len, cond_prefix, &cond,
506+
&cond_len)) {
507+
free(cond_prefix);
508+
free(remote_name);
509+
return 0;
510+
}
511+
free(cond_prefix);
512+
513+
ret = include_by_remote_name_and_url(inc, cond, cond_len,
514+
remote_name);
515+
free(remote_name);
516+
return ret;
517+
}
417518

418519
/* unknown conditionals are always false */
419520
return 0;
@@ -2163,9 +2264,15 @@ int config_with_options(config_fn_t fn, void *data,
21632264
ret = do_git_config_sequence(opts, repo, fn, data);
21642265
}
21652266

2166-
if (inc.remote_urls) {
2167-
string_list_clear(inc.remote_urls, 0);
2168-
FREE_AND_NULL(inc.remote_urls);
2267+
if (inc.remote_urls_initialized) {
2268+
struct hashmap_iter iter;
2269+
struct remote_urls_entry *remote;
2270+
hashmap_for_each_entry(&inc.remote_urls, &iter, remote, ent) {
2271+
string_list_clear(&remote->urls, 0);
2272+
free(remote->name);
2273+
}
2274+
hashmap_clear_and_free(&inc.remote_urls, struct remote_urls_entry, ent);
2275+
inc.remote_urls_initialized = 0;
21692276
}
21702277
return ret;
21712278
}

t/t1300-config.sh

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

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

0 commit comments

Comments
 (0)