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
	
	 Junio C Hamano
						Junio C Hamano