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() staticmaint
commit
13ce8f9f14
|
@ -159,6 +159,33 @@ all branches that begin with `foo/`. This is useful if your branches are
|
||||||
organized hierarchically and you would like to apply a configuration to
|
organized hierarchically and you would like to apply a configuration to
|
||||||
all the branches in that hierarchy.
|
all the branches in that hierarchy.
|
||||||
|
|
||||||
|
`hasconfig:remote.*.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.
|
||||||
|
+
|
||||||
|
Files included by this option (directly or indirectly) are not allowed
|
||||||
|
to contain remote URLs.
|
||||||
|
+
|
||||||
|
Note that unlike other includeIf conditions, resolving this condition
|
||||||
|
relies on information that is not yet known at the point of reading the
|
||||||
|
condition. A typical use case is this option being present as a
|
||||||
|
system-level or global-level config, and the remote URL being in a
|
||||||
|
local-level config; hence the need to scan ahead when resolving this
|
||||||
|
condition. In order to avoid the chicken-and-egg problem in which
|
||||||
|
potentially-included files can affect whether such files are potentially
|
||||||
|
included, Git breaks the cycle by prohibiting these files from affecting
|
||||||
|
the resolution of these conditions (thus, prohibiting them from
|
||||||
|
declaring remote URLs).
|
||||||
|
+
|
||||||
|
As for the naming of this keyword, it is for forwards compatibiliy with
|
||||||
|
a naming scheme that supports more variable-based include conditions,
|
||||||
|
but currently Git only supports the exact keyword described above.
|
||||||
|
|
||||||
A few more notes on matching via `gitdir` and `gitdir/i`:
|
A few more notes on matching via `gitdir` and `gitdir/i`:
|
||||||
|
|
||||||
* Symlinks in `$GIT_DIR` are not resolved before matching.
|
* Symlinks in `$GIT_DIR` are not resolved before matching.
|
||||||
|
@ -226,6 +253,14 @@ Example
|
||||||
; currently checked out
|
; currently checked out
|
||||||
[includeIf "onbranch:foo-branch"]
|
[includeIf "onbranch:foo-branch"]
|
||||||
path = foo.inc
|
path = foo.inc
|
||||||
|
|
||||||
|
; include only if a remote with the given URL exists (note
|
||||||
|
; that such a URL may be provided later in a file or in a
|
||||||
|
; file read after this file is read, as seen in this example)
|
||||||
|
[includeIf "hasconfig:remote.*.url:https://example.com/**"]
|
||||||
|
path = foo.inc
|
||||||
|
[remote "origin"]
|
||||||
|
url = https://example.com/git
|
||||||
----
|
----
|
||||||
|
|
||||||
Values
|
Values
|
||||||
|
|
132
config.c
132
config.c
|
@ -120,6 +120,22 @@ static long config_buf_ftell(struct config_source *conf)
|
||||||
return conf->u.buf.pos;
|
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
|
#define MAX_INCLUDE_DEPTH 10
|
||||||
static const char include_depth_advice[] = N_(
|
static const char include_depth_advice[] = N_(
|
||||||
"exceeded maximum include depth (%d) while including\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;
|
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 char *cond, size_t cond_len)
|
||||||
{
|
{
|
||||||
|
const struct config_options *opts = inc->opts;
|
||||||
|
|
||||||
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
|
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
|
||||||
return include_by_gitdir(opts, cond, cond_len, 0);
|
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);
|
return include_by_gitdir(opts, cond, cond_len, 1);
|
||||||
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
|
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
|
||||||
return include_by_branch(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 */
|
/* unknown conditionals are always false */
|
||||||
return 0;
|
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;
|
struct config_include_data *inc = data;
|
||||||
const char *cond, *key;
|
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);
|
ret = handle_path_include(value, inc);
|
||||||
|
|
||||||
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
|
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
|
||||||
(cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
|
cond && include_condition_is_true(inc, cond, cond_len) &&
|
||||||
!strcmp(key, "path"))
|
!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);
|
ret = handle_path_include(value, inc);
|
||||||
|
inc->fn = old_fn;
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1929,11 +2037,13 @@ int config_with_options(config_fn_t fn, void *data,
|
||||||
const struct config_options *opts)
|
const struct config_options *opts)
|
||||||
{
|
{
|
||||||
struct config_include_data inc = CONFIG_INCLUDE_INIT;
|
struct config_include_data inc = CONFIG_INCLUDE_INIT;
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (opts->respect_includes) {
|
if (opts->respect_includes) {
|
||||||
inc.fn = fn;
|
inc.fn = fn;
|
||||||
inc.data = data;
|
inc.data = data;
|
||||||
inc.opts = opts;
|
inc.opts = opts;
|
||||||
|
inc.config_source = config_source;
|
||||||
fn = git_config_include;
|
fn = git_config_include;
|
||||||
data = &inc;
|
data = &inc;
|
||||||
}
|
}
|
||||||
|
@ -1946,17 +2056,23 @@ int config_with_options(config_fn_t fn, void *data,
|
||||||
* regular lookup sequence.
|
* regular lookup sequence.
|
||||||
*/
|
*/
|
||||||
if (config_source && config_source->use_stdin) {
|
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) {
|
} 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) {
|
} else if (config_source && config_source->blob) {
|
||||||
struct repository *repo = config_source->repo ?
|
struct repository *repo = config_source->repo ?
|
||||||
config_source->repo : the_repository;
|
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);
|
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)
|
static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
|
||||||
|
|
46
config.h
46
config.h
|
@ -89,6 +89,15 @@ struct config_options {
|
||||||
unsigned int ignore_worktree : 1;
|
unsigned int ignore_worktree : 1;
|
||||||
unsigned int ignore_cmdline : 1;
|
unsigned int ignore_cmdline : 1;
|
||||||
unsigned int system_gently : 1;
|
unsigned int system_gently : 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For internal use. Include all includeif.hasremoteurl paths without
|
||||||
|
* checking if the repo has that remote URL, and when doing so, verify
|
||||||
|
* that files included in this way do not configure any remote URLs
|
||||||
|
* themselves.
|
||||||
|
*/
|
||||||
|
unsigned int unconditional_remote_url : 1;
|
||||||
|
|
||||||
const char *commondir;
|
const char *commondir;
|
||||||
const char *git_dir;
|
const char *git_dir;
|
||||||
config_parser_event_fn_t event_fn;
|
config_parser_event_fn_t event_fn;
|
||||||
|
@ -126,6 +135,8 @@ int git_default_config(const char *, const char *, void *);
|
||||||
/**
|
/**
|
||||||
* Read a specific file in git-config format.
|
* Read a specific file in git-config format.
|
||||||
* This function takes the same callback and data parameters as `git_config`.
|
* This function takes the same callback and data parameters as `git_config`.
|
||||||
|
*
|
||||||
|
* Unlike git_config(), this function does not respect includes.
|
||||||
*/
|
*/
|
||||||
int git_config_from_file(config_fn_t fn, const char *, void *);
|
int git_config_from_file(config_fn_t fn, const char *, void *);
|
||||||
|
|
||||||
|
@ -158,6 +169,8 @@ void read_very_early_config(config_fn_t cb, void *data);
|
||||||
* will first feed the user-wide one to the callback, and then the
|
* will first feed the user-wide one to the callback, and then the
|
||||||
* repo-specific one; by overwriting, the higher-priority repo-specific
|
* repo-specific one; by overwriting, the higher-priority repo-specific
|
||||||
* value is left at the end).
|
* value is left at the end).
|
||||||
|
*
|
||||||
|
* Unlike git_config_from_file(), this function respects includes.
|
||||||
*/
|
*/
|
||||||
void git_config(config_fn_t fn, void *);
|
void git_config(config_fn_t fn, void *);
|
||||||
|
|
||||||
|
@ -338,39 +351,6 @@ const char *current_config_origin_type(void);
|
||||||
const char *current_config_name(void);
|
const char *current_config_name(void);
|
||||||
int current_config_line(void);
|
int current_config_line(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Include Directives
|
|
||||||
* ------------------
|
|
||||||
*
|
|
||||||
* By default, the config parser does not respect include directives.
|
|
||||||
* However, a caller can use the special `git_config_include` wrapper
|
|
||||||
* callback to support them. To do so, you simply wrap your "real" callback
|
|
||||||
* function and data pointer in a `struct config_include_data`, and pass
|
|
||||||
* the wrapper to the regular config-reading functions. For example:
|
|
||||||
*
|
|
||||||
* -------------------------------------------
|
|
||||||
* int read_file_with_include(const char *file, config_fn_t fn, void *data)
|
|
||||||
* {
|
|
||||||
* struct config_include_data inc = CONFIG_INCLUDE_INIT;
|
|
||||||
* inc.fn = fn;
|
|
||||||
* inc.data = data;
|
|
||||||
* return git_config_from_file(git_config_include, file, &inc);
|
|
||||||
* }
|
|
||||||
* -------------------------------------------
|
|
||||||
*
|
|
||||||
* `git_config` respects includes automatically. The lower-level
|
|
||||||
* `git_config_from_file` does not.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
struct config_include_data {
|
|
||||||
int depth;
|
|
||||||
config_fn_t fn;
|
|
||||||
void *data;
|
|
||||||
const struct config_options *opts;
|
|
||||||
};
|
|
||||||
#define CONFIG_INCLUDE_INIT { 0 }
|
|
||||||
int git_config_include(const char *name, const char *value, void *data);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Match and parse a config key of the form:
|
* Match and parse a config key of the form:
|
||||||
*
|
*
|
||||||
|
|
|
@ -2388,4 +2388,122 @@ test_expect_success '--get and --get-all with --fixed-value' '
|
||||||
test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent
|
test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'includeIf.hasconfig:remote.*.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.*.url:foourl"]
|
||||||
|
path = "$(pwd)/include-this"
|
||||||
|
[includeIf "hasconfig:remote.*.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.*.url respects last-config-wins' '
|
||||||
|
git init hasremoteurlTest &&
|
||||||
|
test_when_finished "rm -rf hasremoteurlTest" &&
|
||||||
|
|
||||||
|
cat >include-two-three <<-\EOF &&
|
||||||
|
[user]
|
||||||
|
two = included-config
|
||||||
|
three = included-config
|
||||||
|
EOF
|
||||||
|
cat >>hasremoteurlTest/.git/config <<-EOF &&
|
||||||
|
[remote "foo"]
|
||||||
|
url = foourl
|
||||||
|
[user]
|
||||||
|
one = main-config
|
||||||
|
two = main-config
|
||||||
|
[includeIf "hasconfig:remote.*.url:foourl"]
|
||||||
|
path = "$(pwd)/include-two-three"
|
||||||
|
[user]
|
||||||
|
three = main-config
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo main-config >expect-main-config &&
|
||||||
|
echo included-config >expect-included-config &&
|
||||||
|
|
||||||
|
git -C hasremoteurlTest config --get user.one >actual &&
|
||||||
|
test_cmp expect-main-config actual &&
|
||||||
|
|
||||||
|
git -C hasremoteurlTest config --get user.two >actual &&
|
||||||
|
test_cmp expect-included-config actual &&
|
||||||
|
|
||||||
|
git -C hasremoteurlTest config --get user.three >actual &&
|
||||||
|
test_cmp expect-main-config actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'includeIf.hasconfig:remote.*.url globs' '
|
||||||
|
git init hasremoteurlTest &&
|
||||||
|
test_when_finished "rm -rf hasremoteurlTest" &&
|
||||||
|
|
||||||
|
printf "[user]\ndss = yes\n" >double-star-start &&
|
||||||
|
printf "[user]\ndse = yes\n" >double-star-end &&
|
||||||
|
printf "[user]\ndsm = yes\n" >double-star-middle &&
|
||||||
|
printf "[user]\nssm = yes\n" >single-star-middle &&
|
||||||
|
printf "[user]\nno = no\n" >no &&
|
||||||
|
|
||||||
|
cat >>hasremoteurlTest/.git/config <<-EOF &&
|
||||||
|
[remote "foo"]
|
||||||
|
url = https://foo/bar/baz
|
||||||
|
[includeIf "hasconfig:remote.*.url:**/baz"]
|
||||||
|
path = "$(pwd)/double-star-start"
|
||||||
|
[includeIf "hasconfig:remote.*.url:**/nomatch"]
|
||||||
|
path = "$(pwd)/no"
|
||||||
|
[includeIf "hasconfig:remote.*.url:https:/**"]
|
||||||
|
path = "$(pwd)/double-star-end"
|
||||||
|
[includeIf "hasconfig:remote.*.url:nomatch:/**"]
|
||||||
|
path = "$(pwd)/no"
|
||||||
|
[includeIf "hasconfig:remote.*.url:https:/**/baz"]
|
||||||
|
path = "$(pwd)/double-star-middle"
|
||||||
|
[includeIf "hasconfig:remote.*.url:https:/**/nomatch"]
|
||||||
|
path = "$(pwd)/no"
|
||||||
|
[includeIf "hasconfig:remote.*.url:https://*/bar/baz"]
|
||||||
|
path = "$(pwd)/single-star-middle"
|
||||||
|
[includeIf "hasconfig:remote.*.url:https://*/baz"]
|
||||||
|
path = "$(pwd)/no"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C hasremoteurlTest config --get user.dss &&
|
||||||
|
git -C hasremoteurlTest config --get user.dse &&
|
||||||
|
git -C hasremoteurlTest config --get user.dsm &&
|
||||||
|
git -C hasremoteurlTest config --get user.ssm &&
|
||||||
|
test_must_fail git -C hasremoteurlTest config --get user.no
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' '
|
||||||
|
git init hasremoteurlTest &&
|
||||||
|
test_when_finished "rm -rf hasremoteurlTest" &&
|
||||||
|
|
||||||
|
cat >include-with-url <<-\EOF &&
|
||||||
|
[remote "bar"]
|
||||||
|
url = barurl
|
||||||
|
EOF
|
||||||
|
cat >>hasremoteurlTest/.git/config <<-EOF &&
|
||||||
|
[includeIf "hasconfig:remote.*.url:foourl"]
|
||||||
|
path = "$(pwd)/include-with-url"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test with any Git command
|
||||||
|
test_must_fail git -C hasremoteurlTest status 2>err &&
|
||||||
|
grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in New Issue