Merge branch 'jt/conditional-config-on-remote-url'

The conditional inclusion mechanism of configuration files using
"[includeIf <condition>]" learns to base its decision on the
URL of the remote repository the repository interacts with.

* jt/conditional-config-on-remote-url:
  config: include file if remote URL matches a glob
  config: make git_config_include() static
This commit is contained in:
Junio C Hamano
2022-02-09 14:20:58 -08:00
4 changed files with 290 additions and 41 deletions

132
config.c
View File

@ -120,6 +120,22 @@ static long config_buf_ftell(struct config_source *conf)
return conf->u.buf.pos;
}
struct config_include_data {
int depth;
config_fn_t fn;
void *data;
const struct config_options *opts;
struct git_config_source *config_source;
/*
* All remote URLs discovered when reading all config files.
*/
struct string_list *remote_urls;
};
#define CONFIG_INCLUDE_INIT { 0 }
static int git_config_include(const char *var, const char *value, void *data);
#define MAX_INCLUDE_DEPTH 10
static const char include_depth_advice[] = N_(
"exceeded maximum include depth (%d) while including\n"
@ -294,9 +310,92 @@ static int include_by_branch(const char *cond, size_t cond_len)
return ret;
}
static int include_condition_is_true(const struct config_options *opts,
static int add_remote_url(const char *var, const char *value, void *data)
{
struct string_list *remote_urls = data;
const char *remote_name;
size_t remote_name_len;
const char *key;
if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
&key) &&
remote_name &&
!strcmp(key, "url"))
string_list_append(remote_urls, value);
return 0;
}
static void populate_remote_urls(struct config_include_data *inc)
{
struct config_options opts;
struct config_source *store_cf = cf;
struct key_value_info *store_kvi = current_config_kvi;
enum config_scope store_scope = current_parsing_scope;
opts = *inc->opts;
opts.unconditional_remote_url = 1;
cf = NULL;
current_config_kvi = NULL;
current_parsing_scope = 0;
inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
string_list_init_dup(inc->remote_urls);
config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
cf = store_cf;
current_config_kvi = store_kvi;
current_parsing_scope = store_scope;
}
static int forbid_remote_url(const char *var, const char *value, void *data)
{
const char *remote_name;
size_t remote_name_len;
const char *key;
if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
&key) &&
remote_name &&
!strcmp(key, "url"))
die(_("remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url"));
return 0;
}
static int at_least_one_url_matches_glob(const char *glob, int glob_len,
struct string_list *remote_urls)
{
struct strbuf pattern = STRBUF_INIT;
struct string_list_item *url_item;
int found = 0;
strbuf_add(&pattern, glob, glob_len);
for_each_string_list_item(url_item, remote_urls) {
if (!wildmatch(pattern.buf, url_item->string, WM_PATHNAME)) {
found = 1;
break;
}
}
strbuf_release(&pattern);
return found;
}
static int include_by_remote_url(struct config_include_data *inc,
const char *cond, size_t cond_len)
{
if (inc->opts->unconditional_remote_url)
return 1;
if (!inc->remote_urls)
populate_remote_urls(inc);
return at_least_one_url_matches_glob(cond, cond_len,
inc->remote_urls);
}
static int include_condition_is_true(struct config_include_data *inc,
const char *cond, size_t cond_len)
{
const struct config_options *opts = inc->opts;
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
return include_by_gitdir(opts, cond, cond_len, 0);
@ -304,12 +403,15 @@ static int include_condition_is_true(const struct config_options *opts,
return include_by_gitdir(opts, cond, cond_len, 1);
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
return include_by_branch(cond, cond_len);
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
&cond_len))
return include_by_remote_url(inc, cond, cond_len);
/* unknown conditionals are always false */
return 0;
}
int git_config_include(const char *var, const char *value, void *data)
static int git_config_include(const char *var, const char *value, void *data)
{
struct config_include_data *inc = data;
const char *cond, *key;
@ -328,9 +430,15 @@ int git_config_include(const char *var, const char *value, void *data)
ret = handle_path_include(value, inc);
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
(cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
!strcmp(key, "path"))
cond && include_condition_is_true(inc, cond, cond_len) &&
!strcmp(key, "path")) {
config_fn_t old_fn = inc->fn;
if (inc->opts->unconditional_remote_url)
inc->fn = forbid_remote_url;
ret = handle_path_include(value, inc);
inc->fn = old_fn;
}
return ret;
}
@ -1929,11 +2037,13 @@ int config_with_options(config_fn_t fn, void *data,
const struct config_options *opts)
{
struct config_include_data inc = CONFIG_INCLUDE_INIT;
int ret;
if (opts->respect_includes) {
inc.fn = fn;
inc.data = data;
inc.opts = opts;
inc.config_source = config_source;
fn = git_config_include;
data = &inc;
}
@ -1946,17 +2056,23 @@ int config_with_options(config_fn_t fn, void *data,
* regular lookup sequence.
*/
if (config_source && config_source->use_stdin) {
return git_config_from_stdin(fn, data);
ret = git_config_from_stdin(fn, data);
} else if (config_source && config_source->file) {
return git_config_from_file(fn, config_source->file, data);
ret = git_config_from_file(fn, config_source->file, data);
} else if (config_source && config_source->blob) {
struct repository *repo = config_source->repo ?
config_source->repo : the_repository;
return git_config_from_blob_ref(fn, repo, config_source->blob,
ret = git_config_from_blob_ref(fn, repo, config_source->blob,
data);
} else {
ret = do_git_config_sequence(opts, fn, data);
}
return do_git_config_sequence(opts, fn, data);
if (inc.remote_urls) {
string_list_clear(inc.remote_urls, 0);
FREE_AND_NULL(inc.remote_urls);
}
return ret;
}
static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)