Merge branch 'jk/config-include'
* jk/config-include: : An assignment to the include.path pseudo-variable causes the named file : to be included in-place when Git looks up configuration variables. config: add include directive config: eliminate config_exclusive_filename config: stop using config_exclusive_filename config: provide a version of git_config with more options config: teach git_config_rename_section a file argument config: teach git_config_set_multivar_in_file a default path config: copy the return value of prefix_filename t1300: add missing &&-chaining docs/api-config: minor clarifications docs: add a basic description of the config API
This commit is contained in:
		| @ -84,6 +84,17 @@ customary UNIX fashion. | |||||||
|  |  | ||||||
| Some variables may require a special value format. | Some variables may require a special value format. | ||||||
|  |  | ||||||
|  | Includes | ||||||
|  | ~~~~~~~~ | ||||||
|  |  | ||||||
|  | You can include one config file from another by setting the special | ||||||
|  | `include.path` variable to the name of the file to be included. The | ||||||
|  | included file is expanded immediately, as if its contents had been | ||||||
|  | found at the location of the include directive. If the value of the | ||||||
|  | `include.path` variable is a relative path, the path is considered to be | ||||||
|  | relative to the configuration file in which the include directive was | ||||||
|  | found. See below for examples. | ||||||
|  |  | ||||||
| Example | Example | ||||||
| ~~~~~~~ | ~~~~~~~ | ||||||
|  |  | ||||||
| @ -106,6 +117,10 @@ Example | |||||||
| 		gitProxy="ssh" for "kernel.org" | 		gitProxy="ssh" for "kernel.org" | ||||||
| 		gitProxy=default-proxy ; for the rest | 		gitProxy=default-proxy ; for the rest | ||||||
|  |  | ||||||
|  | 	[include] | ||||||
|  | 		path = /path/to/foo.inc ; include by absolute path | ||||||
|  | 		path = foo ; expand "foo" relative to the current file | ||||||
|  |  | ||||||
| Variables | Variables | ||||||
| ~~~~~~~~~ | ~~~~~~~~~ | ||||||
|  |  | ||||||
|  | |||||||
| @ -178,6 +178,11 @@ See also <<FILES>>. | |||||||
| 	Opens an editor to modify the specified config file; either | 	Opens an editor to modify the specified config file; either | ||||||
| 	'--system', '--global', or repository (default). | 	'--system', '--global', or repository (default). | ||||||
|  |  | ||||||
|  | --includes:: | ||||||
|  | --no-includes:: | ||||||
|  | 	Respect `include.*` directives in config files when looking up | ||||||
|  | 	values. Defaults to on. | ||||||
|  |  | ||||||
| [[FILES]] | [[FILES]] | ||||||
| FILES | FILES | ||||||
| ----- | ----- | ||||||
|  | |||||||
							
								
								
									
										140
									
								
								Documentation/technical/api-config.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Documentation/technical/api-config.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | config API | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | The config API gives callers a way to access git configuration files | ||||||
|  | (and files which have the same syntax). See linkgit:git-config[1] for a | ||||||
|  | discussion of the config file syntax. | ||||||
|  |  | ||||||
|  | General Usage | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | Config files are parsed linearly, and each variable found is passed to a | ||||||
|  | caller-provided callback function. The callback function is responsible | ||||||
|  | for any actions to be taken on the config option, and is free to ignore | ||||||
|  | some options. It is not uncommon for the configuration to be parsed | ||||||
|  | several times during the run of a git program, with different callbacks | ||||||
|  | picking out different variables useful to themselves. | ||||||
|  |  | ||||||
|  | A config callback function takes three parameters: | ||||||
|  |  | ||||||
|  | - the name of the parsed variable. This is in canonical "flat" form: the | ||||||
|  |   section, subsection, and variable segments will be separated by dots, | ||||||
|  |   and the section and variable segments will be all lowercase. E.g., | ||||||
|  |   `core.ignorecase`, `diff.SomeType.textconv`. | ||||||
|  |  | ||||||
|  | - the value of the found variable, as a string. If the variable had no | ||||||
|  |   value specified, the value will be NULL (typically this means it | ||||||
|  |   should be interpreted as boolean true). | ||||||
|  |  | ||||||
|  | - a void pointer passed in by the caller of the config API; this can | ||||||
|  |   contain callback-specific data | ||||||
|  |  | ||||||
|  | A config callback should return 0 for success, or -1 if the variable | ||||||
|  | could not be parsed properly. | ||||||
|  |  | ||||||
|  | Basic Config Querying | ||||||
|  | --------------------- | ||||||
|  |  | ||||||
|  | Most programs will simply want to look up variables in all config files | ||||||
|  | that git knows about, using the normal precedence rules. To do this, | ||||||
|  | call `git_config` with a callback function and void data pointer. | ||||||
|  |  | ||||||
|  | `git_config` will read all config sources in order of increasing | ||||||
|  | priority. Thus a callback should typically overwrite previously-seen | ||||||
|  | entries with new ones (e.g., if both the user-wide `~/.gitconfig` and | ||||||
|  | repo-specific `.git/config` contain `color.ui`, the config machinery | ||||||
|  | will first feed the user-wide one to the callback, and then the | ||||||
|  | repo-specific one; by overwriting, the higher-priority repo-specific | ||||||
|  | value is left at the end). | ||||||
|  |  | ||||||
|  | The `git_config_with_options` function lets the caller examine config | ||||||
|  | while adjusting some of the default behavior of `git_config`. It should | ||||||
|  | almost never be used by "regular" git code that is looking up | ||||||
|  | configuration variables. It is intended for advanced callers like | ||||||
|  | `git-config`, which are intentionally tweaking the normal config-lookup | ||||||
|  | process. It takes two extra parameters: | ||||||
|  |  | ||||||
|  | `filename`:: | ||||||
|  | If this parameter is non-NULL, it specifies the name of a file to | ||||||
|  | parse for configuration, rather than looking in the usual files. Regular | ||||||
|  | `git_config` defaults to `NULL`. | ||||||
|  |  | ||||||
|  | `respect_includes`:: | ||||||
|  | Specify whether include directives should be followed in parsed files. | ||||||
|  | Regular `git_config` defaults to `1`. | ||||||
|  |  | ||||||
|  | There is a special version of `git_config` called `git_config_early`. | ||||||
|  | This version takes an additional parameter to specify the repository | ||||||
|  | config, instead of having it looked up via `git_path`. This is useful | ||||||
|  | early in a git program before the repository has been found. Unless | ||||||
|  | you're working with early setup code, you probably don't want to use | ||||||
|  | this. | ||||||
|  |  | ||||||
|  | Reading Specific Files | ||||||
|  | ---------------------- | ||||||
|  |  | ||||||
|  | To read a specific file in git-config format, use | ||||||
|  | `git_config_from_file`. This takes the same callback and data parameters | ||||||
|  | as `git_config`. | ||||||
|  |  | ||||||
|  | Value Parsing Helpers | ||||||
|  | --------------------- | ||||||
|  |  | ||||||
|  | To aid in parsing string values, the config API provides callbacks with | ||||||
|  | a number of helper functions, including: | ||||||
|  |  | ||||||
|  | `git_config_int`:: | ||||||
|  | Parse the string to an integer, including unit factors. Dies on error; | ||||||
|  | otherwise, returns the parsed result. | ||||||
|  |  | ||||||
|  | `git_config_ulong`:: | ||||||
|  | Identical to `git_config_int`, but for unsigned longs. | ||||||
|  |  | ||||||
|  | `git_config_bool`:: | ||||||
|  | Parse a string into a boolean value, respecting keywords like "true" and | ||||||
|  | "false". Integer values are converted into true/false values (when they | ||||||
|  | are non-zero or zero, respectively). Other values cause a die(). If | ||||||
|  | parsing is successful, the return value is the result. | ||||||
|  |  | ||||||
|  | `git_config_bool_or_int`:: | ||||||
|  | Same as `git_config_bool`, except that integers are returned as-is, and | ||||||
|  | an `is_bool` flag is unset. | ||||||
|  |  | ||||||
|  | `git_config_maybe_bool`:: | ||||||
|  | Same as `git_config_bool`, except that it returns -1 on error rather | ||||||
|  | than dying. | ||||||
|  |  | ||||||
|  | `git_config_string`:: | ||||||
|  | Allocates and copies the value string into the `dest` parameter; if no | ||||||
|  | string is given, prints an error message and returns -1. | ||||||
|  |  | ||||||
|  | `git_config_pathname`:: | ||||||
|  | Similar to `git_config_string`, but expands `~` or `~user` into the | ||||||
|  | user's home directory when found at the beginning of the path. | ||||||
|  |  | ||||||
|  | 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. | ||||||
|  |  | ||||||
|  | Writing Config Files | ||||||
|  | -------------------- | ||||||
|  |  | ||||||
|  | TODO | ||||||
| @ -25,6 +25,7 @@ static const char *given_config_file; | |||||||
| static int actions, types; | static int actions, types; | ||||||
| static const char *get_color_slot, *get_colorbool_slot; | static const char *get_color_slot, *get_colorbool_slot; | ||||||
| static int end_null; | static int end_null; | ||||||
|  | static int respect_includes = -1; | ||||||
|  |  | ||||||
| #define ACTION_GET (1<<0) | #define ACTION_GET (1<<0) | ||||||
| #define ACTION_GET_ALL (1<<1) | #define ACTION_GET_ALL (1<<1) | ||||||
| @ -74,6 +75,7 @@ static struct option builtin_config_options[] = { | |||||||
| 	OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH), | 	OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH), | ||||||
| 	OPT_GROUP("Other"), | 	OPT_GROUP("Other"), | ||||||
| 	OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), | 	OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), | ||||||
|  | 	OPT_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"), | ||||||
| 	OPT_END(), | 	OPT_END(), | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @ -161,8 +163,11 @@ static int get_value(const char *key_, const char *regex_) | |||||||
| 	int ret = -1; | 	int ret = -1; | ||||||
| 	char *global = NULL, *repo_config = NULL; | 	char *global = NULL, *repo_config = NULL; | ||||||
| 	const char *system_wide = NULL, *local; | 	const char *system_wide = NULL, *local; | ||||||
|  | 	struct config_include_data inc = CONFIG_INCLUDE_INIT; | ||||||
|  | 	config_fn_t fn; | ||||||
|  | 	void *data; | ||||||
|  |  | ||||||
| 	local = config_exclusive_filename; | 	local = given_config_file; | ||||||
| 	if (!local) { | 	if (!local) { | ||||||
| 		const char *home = getenv("HOME"); | 		const char *home = getenv("HOME"); | ||||||
| 		local = repo_config = git_pathdup("config"); | 		local = repo_config = git_pathdup("config"); | ||||||
| @ -213,19 +218,28 @@ static int get_value(const char *key_, const char *regex_) | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	fn = show_config; | ||||||
|  | 	data = NULL; | ||||||
|  | 	if (respect_includes) { | ||||||
|  | 		inc.fn = fn; | ||||||
|  | 		inc.data = data; | ||||||
|  | 		fn = git_config_include; | ||||||
|  | 		data = &inc; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if (do_all && system_wide) | 	if (do_all && system_wide) | ||||||
| 		git_config_from_file(show_config, system_wide, NULL); | 		git_config_from_file(fn, system_wide, data); | ||||||
| 	if (do_all && global) | 	if (do_all && global) | ||||||
| 		git_config_from_file(show_config, global, NULL); | 		git_config_from_file(fn, global, data); | ||||||
| 	if (do_all) | 	if (do_all) | ||||||
| 		git_config_from_file(show_config, local, NULL); | 		git_config_from_file(fn, local, data); | ||||||
| 	git_config_from_parameters(show_config, NULL); | 	git_config_from_parameters(fn, data); | ||||||
| 	if (!do_all && !seen) | 	if (!do_all && !seen) | ||||||
| 		git_config_from_file(show_config, local, NULL); | 		git_config_from_file(fn, local, data); | ||||||
| 	if (!do_all && !seen && global) | 	if (!do_all && !seen && global) | ||||||
| 		git_config_from_file(show_config, global, NULL); | 		git_config_from_file(fn, global, data); | ||||||
| 	if (!do_all && !seen && system_wide) | 	if (!do_all && !seen && system_wide) | ||||||
| 		git_config_from_file(show_config, system_wide, NULL); | 		git_config_from_file(fn, system_wide, data); | ||||||
|  |  | ||||||
| 	free(key); | 	free(key); | ||||||
| 	if (regexp) { | 	if (regexp) { | ||||||
| @ -301,7 +315,8 @@ static void get_color(const char *def_color) | |||||||
| { | { | ||||||
| 	get_color_found = 0; | 	get_color_found = 0; | ||||||
| 	parsed_color[0] = '\0'; | 	parsed_color[0] = '\0'; | ||||||
| 	git_config(git_get_color_config, NULL); | 	git_config_with_options(git_get_color_config, NULL, | ||||||
|  | 				given_config_file, respect_includes); | ||||||
|  |  | ||||||
| 	if (!get_color_found && def_color) | 	if (!get_color_found && def_color) | ||||||
| 		color_parse(def_color, "command line", parsed_color); | 		color_parse(def_color, "command line", parsed_color); | ||||||
| @ -328,7 +343,8 @@ static int get_colorbool(int print) | |||||||
| { | { | ||||||
| 	get_colorbool_found = -1; | 	get_colorbool_found = -1; | ||||||
| 	get_diff_color_found = -1; | 	get_diff_color_found = -1; | ||||||
| 	git_config(git_get_colorbool_config, NULL); | 	git_config_with_options(git_get_colorbool_config, NULL, | ||||||
|  | 				given_config_file, respect_includes); | ||||||
|  |  | ||||||
| 	if (get_colorbool_found < 0) { | 	if (get_colorbool_found < 0) { | ||||||
| 		if (!strcmp(get_colorbool_slot, "color.diff")) | 		if (!strcmp(get_colorbool_slot, "color.diff")) | ||||||
| @ -351,7 +367,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) | |||||||
| 	int nongit = !startup_info->have_repository; | 	int nongit = !startup_info->have_repository; | ||||||
| 	char *value; | 	char *value; | ||||||
|  |  | ||||||
| 	config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); | 	given_config_file = getenv(CONFIG_ENVIRONMENT); | ||||||
|  |  | ||||||
| 	argc = parse_options(argc, argv, prefix, builtin_config_options, | 	argc = parse_options(argc, argv, prefix, builtin_config_options, | ||||||
| 			     builtin_config_usage, | 			     builtin_config_usage, | ||||||
| @ -366,24 +382,28 @@ int cmd_config(int argc, const char **argv, const char *prefix) | |||||||
| 		char *home = getenv("HOME"); | 		char *home = getenv("HOME"); | ||||||
| 		if (home) { | 		if (home) { | ||||||
| 			char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); | 			char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); | ||||||
| 			config_exclusive_filename = user_config; | 			given_config_file = user_config; | ||||||
| 		} else { | 		} else { | ||||||
| 			die("$HOME not set"); | 			die("$HOME not set"); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	else if (use_system_config) | 	else if (use_system_config) | ||||||
| 		config_exclusive_filename = git_etc_gitconfig(); | 		given_config_file = git_etc_gitconfig(); | ||||||
| 	else if (use_local_config) | 	else if (use_local_config) | ||||||
| 		config_exclusive_filename = git_pathdup("config"); | 		given_config_file = git_pathdup("config"); | ||||||
| 	else if (given_config_file) { | 	else if (given_config_file) { | ||||||
| 		if (!is_absolute_path(given_config_file) && prefix) | 		if (!is_absolute_path(given_config_file) && prefix) | ||||||
| 			config_exclusive_filename = prefix_filename(prefix, | 			given_config_file = | ||||||
| 								    strlen(prefix), | 				xstrdup(prefix_filename(prefix, | ||||||
| 								    given_config_file); | 							strlen(prefix), | ||||||
|  | 							given_config_file)); | ||||||
| 		else | 		else | ||||||
| 			config_exclusive_filename = given_config_file; | 			given_config_file = given_config_file; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (respect_includes == -1) | ||||||
|  | 		respect_includes = !given_config_file; | ||||||
|  |  | ||||||
| 	if (end_null) { | 	if (end_null) { | ||||||
| 		term = '\0'; | 		term = '\0'; | ||||||
| 		delim = '\n'; | 		delim = '\n'; | ||||||
| @ -420,28 +440,30 @@ int cmd_config(int argc, const char **argv, const char *prefix) | |||||||
|  |  | ||||||
| 	if (actions == ACTION_LIST) { | 	if (actions == ACTION_LIST) { | ||||||
| 		check_argc(argc, 0, 0); | 		check_argc(argc, 0, 0); | ||||||
| 		if (git_config(show_all_config, NULL) < 0) { | 		if (git_config_with_options(show_all_config, NULL, | ||||||
| 			if (config_exclusive_filename) | 					    given_config_file, | ||||||
|  | 					    respect_includes) < 0) { | ||||||
|  | 			if (given_config_file) | ||||||
| 				die_errno("unable to read config file '%s'", | 				die_errno("unable to read config file '%s'", | ||||||
| 					  config_exclusive_filename); | 					  given_config_file); | ||||||
| 			else | 			else | ||||||
| 				die("error processing config file(s)"); | 				die("error processing config file(s)"); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_EDIT) { | 	else if (actions == ACTION_EDIT) { | ||||||
| 		check_argc(argc, 0, 0); | 		check_argc(argc, 0, 0); | ||||||
| 		if (!config_exclusive_filename && nongit) | 		if (!given_config_file && nongit) | ||||||
| 			die("not in a git directory"); | 			die("not in a git directory"); | ||||||
| 		git_config(git_default_config, NULL); | 		git_config(git_default_config, NULL); | ||||||
| 		launch_editor(config_exclusive_filename ? | 		launch_editor(given_config_file ? | ||||||
| 			      config_exclusive_filename : git_path("config"), | 			      given_config_file : git_path("config"), | ||||||
| 			      NULL, NULL); | 			      NULL, NULL); | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_SET) { | 	else if (actions == ACTION_SET) { | ||||||
| 		int ret; | 		int ret; | ||||||
| 		check_argc(argc, 2, 2); | 		check_argc(argc, 2, 2); | ||||||
| 		value = normalize_value(argv[0], argv[1]); | 		value = normalize_value(argv[0], argv[1]); | ||||||
| 		ret = git_config_set(argv[0], value); | 		ret = git_config_set_in_file(given_config_file, argv[0], value); | ||||||
| 		if (ret == CONFIG_NOTHING_SET) | 		if (ret == CONFIG_NOTHING_SET) | ||||||
| 			error("cannot overwrite multiple values with a single value\n" | 			error("cannot overwrite multiple values with a single value\n" | ||||||
| 			"       Use a regexp, --add or --replace-all to change %s.", argv[0]); | 			"       Use a regexp, --add or --replace-all to change %s.", argv[0]); | ||||||
| @ -450,17 +472,20 @@ int cmd_config(int argc, const char **argv, const char *prefix) | |||||||
| 	else if (actions == ACTION_SET_ALL) { | 	else if (actions == ACTION_SET_ALL) { | ||||||
| 		check_argc(argc, 2, 3); | 		check_argc(argc, 2, 3); | ||||||
| 		value = normalize_value(argv[0], argv[1]); | 		value = normalize_value(argv[0], argv[1]); | ||||||
| 		return git_config_set_multivar(argv[0], value, argv[2], 0); | 		return git_config_set_multivar_in_file(given_config_file, | ||||||
|  | 						       argv[0], value, argv[2], 0); | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_ADD) { | 	else if (actions == ACTION_ADD) { | ||||||
| 		check_argc(argc, 2, 2); | 		check_argc(argc, 2, 2); | ||||||
| 		value = normalize_value(argv[0], argv[1]); | 		value = normalize_value(argv[0], argv[1]); | ||||||
| 		return git_config_set_multivar(argv[0], value, "^$", 0); | 		return git_config_set_multivar_in_file(given_config_file, | ||||||
|  | 						       argv[0], value, "^$", 0); | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_REPLACE_ALL) { | 	else if (actions == ACTION_REPLACE_ALL) { | ||||||
| 		check_argc(argc, 2, 3); | 		check_argc(argc, 2, 3); | ||||||
| 		value = normalize_value(argv[0], argv[1]); | 		value = normalize_value(argv[0], argv[1]); | ||||||
| 		return git_config_set_multivar(argv[0], value, argv[2], 1); | 		return git_config_set_multivar_in_file(given_config_file, | ||||||
|  | 						       argv[0], value, argv[2], 1); | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_GET) { | 	else if (actions == ACTION_GET) { | ||||||
| 		check_argc(argc, 1, 2); | 		check_argc(argc, 1, 2); | ||||||
| @ -481,18 +506,22 @@ int cmd_config(int argc, const char **argv, const char *prefix) | |||||||
| 	else if (actions == ACTION_UNSET) { | 	else if (actions == ACTION_UNSET) { | ||||||
| 		check_argc(argc, 1, 2); | 		check_argc(argc, 1, 2); | ||||||
| 		if (argc == 2) | 		if (argc == 2) | ||||||
| 			return git_config_set_multivar(argv[0], NULL, argv[1], 0); | 			return git_config_set_multivar_in_file(given_config_file, | ||||||
|  | 							       argv[0], NULL, argv[1], 0); | ||||||
| 		else | 		else | ||||||
| 			return git_config_set(argv[0], NULL); | 			return git_config_set_in_file(given_config_file, | ||||||
|  | 						      argv[0], NULL); | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_UNSET_ALL) { | 	else if (actions == ACTION_UNSET_ALL) { | ||||||
| 		check_argc(argc, 1, 2); | 		check_argc(argc, 1, 2); | ||||||
| 		return git_config_set_multivar(argv[0], NULL, argv[1], 1); | 		return git_config_set_multivar_in_file(given_config_file, | ||||||
|  | 						       argv[0], NULL, argv[1], 1); | ||||||
| 	} | 	} | ||||||
| 	else if (actions == ACTION_RENAME_SECTION) { | 	else if (actions == ACTION_RENAME_SECTION) { | ||||||
| 		int ret; | 		int ret; | ||||||
| 		check_argc(argc, 2, 2); | 		check_argc(argc, 2, 2); | ||||||
| 		ret = git_config_rename_section(argv[0], argv[1]); | 		ret = git_config_rename_section_in_file(given_config_file, | ||||||
|  | 							argv[0], argv[1]); | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			return ret; | 			return ret; | ||||||
| 		if (ret == 0) | 		if (ret == 0) | ||||||
| @ -501,7 +530,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) | |||||||
| 	else if (actions == ACTION_REMOVE_SECTION) { | 	else if (actions == ACTION_REMOVE_SECTION) { | ||||||
| 		int ret; | 		int ret; | ||||||
| 		check_argc(argc, 1, 1); | 		check_argc(argc, 1, 1); | ||||||
| 		ret = git_config_rename_section(argv[0], NULL); | 		ret = git_config_rename_section_in_file(given_config_file, | ||||||
|  | 							argv[0], NULL); | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			return ret; | 			return ret; | ||||||
| 		if (ret == 0) | 		if (ret == 0) | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								cache.h
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cache.h
									
									
									
									
									
								
							| @ -1115,6 +1115,8 @@ extern int git_config_from_file(config_fn_t fn, const char *, void *); | |||||||
| extern void git_config_push_parameter(const char *text); | extern void git_config_push_parameter(const char *text); | ||||||
| extern int git_config_from_parameters(config_fn_t fn, void *data); | extern int git_config_from_parameters(config_fn_t fn, void *data); | ||||||
| extern int git_config(config_fn_t fn, void *); | extern int git_config(config_fn_t fn, void *); | ||||||
|  | extern int git_config_with_options(config_fn_t fn, void *, | ||||||
|  | 				   const char *filename, int respect_includes); | ||||||
| extern int git_config_early(config_fn_t fn, void *, const char *repo_config); | extern int git_config_early(config_fn_t fn, void *, const char *repo_config); | ||||||
| extern int git_parse_ulong(const char *, unsigned long *); | extern int git_parse_ulong(const char *, unsigned long *); | ||||||
| extern int git_config_int(const char *, const char *); | extern int git_config_int(const char *, const char *); | ||||||
| @ -1130,6 +1132,7 @@ extern int git_config_parse_key(const char *, char **, int *); | |||||||
| extern int git_config_set_multivar(const char *, const char *, const char *, int); | extern int git_config_set_multivar(const char *, const char *, const char *, int); | ||||||
| extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); | extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); | ||||||
| extern int git_config_rename_section(const char *, const char *); | extern int git_config_rename_section(const char *, const char *); | ||||||
|  | extern int git_config_rename_section_in_file(const char *, const char *, const char *); | ||||||
| extern const char *git_etc_gitconfig(void); | extern const char *git_etc_gitconfig(void); | ||||||
| extern int check_repository_format_version(const char *var, const char *value, void *cb); | extern int check_repository_format_version(const char *var, const char *value, void *cb); | ||||||
| extern int git_env_bool(const char *, int); | extern int git_env_bool(const char *, int); | ||||||
| @ -1140,7 +1143,13 @@ extern const char *get_commit_output_encoding(void); | |||||||
|  |  | ||||||
| extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data); | extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data); | ||||||
|  |  | ||||||
| extern const char *config_exclusive_filename; | struct config_include_data { | ||||||
|  | 	int depth; | ||||||
|  | 	config_fn_t fn; | ||||||
|  | 	void *data; | ||||||
|  | }; | ||||||
|  | #define CONFIG_INCLUDE_INIT { 0 } | ||||||
|  | extern int git_config_include(const char *name, const char *value, void *data); | ||||||
|  |  | ||||||
| #define MAX_GITNAME (1000) | #define MAX_GITNAME (1000) | ||||||
| extern char git_default_email[MAX_GITNAME]; | extern char git_default_email[MAX_GITNAME]; | ||||||
|  | |||||||
							
								
								
									
										127
									
								
								config.c
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								config.c
									
									
									
									
									
								
							| @ -26,7 +26,68 @@ static config_file *cf; | |||||||
|  |  | ||||||
| static int zlib_compression_seen; | static int zlib_compression_seen; | ||||||
|  |  | ||||||
| const char *config_exclusive_filename = NULL; | #define MAX_INCLUDE_DEPTH 10 | ||||||
|  | static const char include_depth_advice[] = | ||||||
|  | "exceeded maximum include depth (%d) while including\n" | ||||||
|  | "	%s\n" | ||||||
|  | "from\n" | ||||||
|  | "	%s\n" | ||||||
|  | "Do you have circular includes?"; | ||||||
|  | static int handle_path_include(const char *path, struct config_include_data *inc) | ||||||
|  | { | ||||||
|  | 	int ret = 0; | ||||||
|  | 	struct strbuf buf = STRBUF_INIT; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Use an absolute path as-is, but interpret relative paths | ||||||
|  | 	 * based on the including config file. | ||||||
|  | 	 */ | ||||||
|  | 	if (!is_absolute_path(path)) { | ||||||
|  | 		char *slash; | ||||||
|  |  | ||||||
|  | 		if (!cf || !cf->name) | ||||||
|  | 			return error("relative config includes must come from files"); | ||||||
|  |  | ||||||
|  | 		slash = find_last_dir_sep(cf->name); | ||||||
|  | 		if (slash) | ||||||
|  | 			strbuf_add(&buf, cf->name, slash - cf->name + 1); | ||||||
|  | 		strbuf_addstr(&buf, path); | ||||||
|  | 		path = buf.buf; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!access(path, R_OK)) { | ||||||
|  | 		if (++inc->depth > MAX_INCLUDE_DEPTH) | ||||||
|  | 			die(include_depth_advice, MAX_INCLUDE_DEPTH, path, | ||||||
|  | 			    cf && cf->name ? cf->name : "the command line"); | ||||||
|  | 		ret = git_config_from_file(git_config_include, path, inc); | ||||||
|  | 		inc->depth--; | ||||||
|  | 	} | ||||||
|  | 	strbuf_release(&buf); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int git_config_include(const char *var, const char *value, void *data) | ||||||
|  | { | ||||||
|  | 	struct config_include_data *inc = data; | ||||||
|  | 	const char *type; | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Pass along all values, including "include" directives; this makes it | ||||||
|  | 	 * possible to query information on the includes themselves. | ||||||
|  | 	 */ | ||||||
|  | 	ret = inc->fn(var, value, inc->data); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  |  | ||||||
|  | 	type = skip_prefix(var, "include."); | ||||||
|  | 	if (!type) | ||||||
|  | 		return ret; | ||||||
|  |  | ||||||
|  | 	if (!strcmp(type, "path")) | ||||||
|  | 		ret = handle_path_include(value, inc); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
| static void lowercase(char *p) | static void lowercase(char *p) | ||||||
| { | { | ||||||
| @ -879,9 +940,6 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) | |||||||
| 	int ret = 0, found = 0; | 	int ret = 0, found = 0; | ||||||
| 	const char *home = NULL; | 	const char *home = NULL; | ||||||
|  |  | ||||||
| 	/* Setting $GIT_CONFIG makes git read _only_ the given config file. */ |  | ||||||
| 	if (config_exclusive_filename) |  | ||||||
| 		return git_config_from_file(fn, config_exclusive_filename, data); |  | ||||||
| 	if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) { | 	if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) { | ||||||
| 		ret += git_config_from_file(fn, git_etc_gitconfig(), | 		ret += git_config_from_file(fn, git_etc_gitconfig(), | ||||||
| 					    data); | 					    data); | ||||||
| @ -917,10 +975,26 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) | |||||||
| 	return ret == 0 ? found : ret; | 	return ret == 0 ? found : ret; | ||||||
| } | } | ||||||
|  |  | ||||||
| int git_config(config_fn_t fn, void *data) | int git_config_with_options(config_fn_t fn, void *data, | ||||||
|  | 			    const char *filename, int respect_includes) | ||||||
| { | { | ||||||
| 	char *repo_config = NULL; | 	char *repo_config = NULL; | ||||||
| 	int ret; | 	int ret; | ||||||
|  | 	struct config_include_data inc = CONFIG_INCLUDE_INIT; | ||||||
|  |  | ||||||
|  | 	if (respect_includes) { | ||||||
|  | 		inc.fn = fn; | ||||||
|  | 		inc.data = data; | ||||||
|  | 		fn = git_config_include; | ||||||
|  | 		data = &inc; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * If we have a specific filename, use it. Otherwise, follow the | ||||||
|  | 	 * regular lookup sequence. | ||||||
|  | 	 */ | ||||||
|  | 	if (filename) | ||||||
|  | 		return git_config_from_file(fn, filename, data); | ||||||
|  |  | ||||||
| 	repo_config = git_pathdup("config"); | 	repo_config = git_pathdup("config"); | ||||||
| 	ret = git_config_early(fn, data, repo_config); | 	ret = git_config_early(fn, data, repo_config); | ||||||
| @ -929,6 +1003,11 @@ int git_config(config_fn_t fn, void *data) | |||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int git_config(config_fn_t fn, void *data) | ||||||
|  | { | ||||||
|  | 	return git_config_with_options(fn, data, NULL, 1); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Find all the stuff for git_config_set() below. |  * Find all the stuff for git_config_set() below. | ||||||
|  */ |  */ | ||||||
| @ -1233,6 +1312,7 @@ int git_config_set_multivar_in_file(const char *config_filename, | |||||||
| 	int fd = -1, in_fd; | 	int fd = -1, in_fd; | ||||||
| 	int ret; | 	int ret; | ||||||
| 	struct lock_file *lock = NULL; | 	struct lock_file *lock = NULL; | ||||||
|  | 	char *filename_buf = NULL; | ||||||
|  |  | ||||||
| 	/* parse-key returns negative; flip the sign to feed exit(3) */ | 	/* parse-key returns negative; flip the sign to feed exit(3) */ | ||||||
| 	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); | 	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); | ||||||
| @ -1241,6 +1321,8 @@ int git_config_set_multivar_in_file(const char *config_filename, | |||||||
|  |  | ||||||
| 	store.multi_replace = multi_replace; | 	store.multi_replace = multi_replace; | ||||||
|  |  | ||||||
|  | 	if (!config_filename) | ||||||
|  | 		config_filename = filename_buf = git_pathdup("config"); | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * The lock serves a purpose in addition to locking: the new | 	 * The lock serves a purpose in addition to locking: the new | ||||||
| @ -1410,6 +1492,7 @@ int git_config_set_multivar_in_file(const char *config_filename, | |||||||
| out_free: | out_free: | ||||||
| 	if (lock) | 	if (lock) | ||||||
| 		rollback_lock_file(lock); | 		rollback_lock_file(lock); | ||||||
|  | 	free(filename_buf); | ||||||
| 	return ret; | 	return ret; | ||||||
|  |  | ||||||
| write_err_out: | write_err_out: | ||||||
| @ -1421,19 +1504,8 @@ write_err_out: | |||||||
| int git_config_set_multivar(const char *key, const char *value, | int git_config_set_multivar(const char *key, const char *value, | ||||||
| 			const char *value_regex, int multi_replace) | 			const char *value_regex, int multi_replace) | ||||||
| { | { | ||||||
| 	const char *config_filename; | 	return git_config_set_multivar_in_file(NULL, key, value, value_regex, | ||||||
| 	char *buf = NULL; | 					       multi_replace); | ||||||
| 	int ret; |  | ||||||
|  |  | ||||||
| 	if (config_exclusive_filename) |  | ||||||
| 		config_filename = config_exclusive_filename; |  | ||||||
| 	else |  | ||||||
| 		config_filename = buf = git_pathdup("config"); |  | ||||||
|  |  | ||||||
| 	ret = git_config_set_multivar_in_file(config_filename, key, value, |  | ||||||
| 					value_regex, multi_replace); |  | ||||||
| 	free(buf); |  | ||||||
| 	return ret; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static int section_name_match (const char *buf, const char *name) | static int section_name_match (const char *buf, const char *name) | ||||||
| @ -1476,19 +1548,19 @@ static int section_name_match (const char *buf, const char *name) | |||||||
| } | } | ||||||
|  |  | ||||||
| /* if new_name == NULL, the section is removed instead */ | /* if new_name == NULL, the section is removed instead */ | ||||||
| int git_config_rename_section(const char *old_name, const char *new_name) | int git_config_rename_section_in_file(const char *config_filename, | ||||||
|  | 				      const char *old_name, const char *new_name) | ||||||
| { | { | ||||||
| 	int ret = 0, remove = 0; | 	int ret = 0, remove = 0; | ||||||
| 	char *config_filename; | 	char *filename_buf = NULL; | ||||||
| 	struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); | 	struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); | ||||||
| 	int out_fd; | 	int out_fd; | ||||||
| 	char buf[1024]; | 	char buf[1024]; | ||||||
| 	FILE *config_file; | 	FILE *config_file; | ||||||
|  |  | ||||||
| 	if (config_exclusive_filename) | 	if (!config_filename) | ||||||
| 		config_filename = xstrdup(config_exclusive_filename); | 		config_filename = filename_buf = git_pathdup("config"); | ||||||
| 	else |  | ||||||
| 		config_filename = git_pathdup("config"); |  | ||||||
| 	out_fd = hold_lock_file_for_update(lock, config_filename, 0); | 	out_fd = hold_lock_file_for_update(lock, config_filename, 0); | ||||||
| 	if (out_fd < 0) { | 	if (out_fd < 0) { | ||||||
| 		ret = error("could not lock config file %s", config_filename); | 		ret = error("could not lock config file %s", config_filename); | ||||||
| @ -1552,10 +1624,15 @@ unlock_and_out: | |||||||
| 	if (commit_lock_file(lock) < 0) | 	if (commit_lock_file(lock) < 0) | ||||||
| 		ret = error("could not commit config file %s", config_filename); | 		ret = error("could not commit config file %s", config_filename); | ||||||
| out: | out: | ||||||
| 	free(config_filename); | 	free(filename_buf); | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int git_config_rename_section(const char *old_name, const char *new_name) | ||||||
|  | { | ||||||
|  | 	return git_config_rename_section_in_file(NULL, old_name, new_name); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Call this to report error for your variable that should not |  * Call this to report error for your variable that should not | ||||||
|  * get a boolean value (i.e. "[my] var" means "true"). |  * get a boolean value (i.e. "[my] var" means "true"). | ||||||
|  | |||||||
| @ -451,13 +451,21 @@ test_expect_success 'refer config from subdirectory' ' | |||||||
| 	mkdir x && | 	mkdir x && | ||||||
| 	( | 	( | ||||||
| 		cd x && | 		cd x && | ||||||
| 		echo strasse >expect | 		echo strasse >expect && | ||||||
| 		git config --get --file ../other-config ein.bahn >actual && | 		git config --get --file ../other-config ein.bahn >actual && | ||||||
| 		test_cmp expect actual | 		test_cmp expect actual | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'refer config from subdirectory via GIT_CONFIG' ' | ||||||
|  | 	( | ||||||
|  | 		cd x && | ||||||
|  | 		GIT_CONFIG=../other-config git config --get ein.bahn >actual && | ||||||
|  | 		test_cmp expect actual | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| cat > expect << EOF | cat > expect << EOF | ||||||
| [ein] | [ein] | ||||||
| 	bahn = strasse | 	bahn = strasse | ||||||
| @ -960,4 +968,21 @@ test_expect_success 'git -c complains about empty key and value' ' | |||||||
| 	test_must_fail git -c "" rev-parse | 	test_must_fail git -c "" rev-parse | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'git config --edit works' ' | ||||||
|  | 	git config -f tmp test.value no && | ||||||
|  | 	echo test.value=yes >expect && | ||||||
|  | 	GIT_EDITOR="echo [test]value=yes >" git config -f tmp --edit && | ||||||
|  | 	git config -f tmp --list >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'git config --edit respects core.editor' ' | ||||||
|  | 	git config -f tmp test.value no && | ||||||
|  | 	echo test.value=yes >expect && | ||||||
|  | 	test_config core.editor "echo [test]value=yes >" && | ||||||
|  | 	git config -f tmp --edit && | ||||||
|  | 	git config -f tmp --list >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  | |||||||
							
								
								
									
										134
									
								
								t/t1305-config-include.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										134
									
								
								t/t1305-config-include.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,134 @@ | |||||||
|  | #!/bin/sh | ||||||
|  |  | ||||||
|  | test_description='test config file include directives' | ||||||
|  | . ./test-lib.sh | ||||||
|  |  | ||||||
|  | test_expect_success 'include file by absolute path' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = \"$(pwd)/one\"" >.gitconfig && | ||||||
|  | 	echo 1 >expect && | ||||||
|  | 	git config test.one >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'include file by relative path' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	echo 1 >expect && | ||||||
|  | 	git config test.one >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'chained relative paths' ' | ||||||
|  | 	mkdir subdir && | ||||||
|  | 	echo "[test]three = 3" >subdir/three && | ||||||
|  | 	echo "[include]path = three" >subdir/two && | ||||||
|  | 	echo "[include]path = subdir/two" >.gitconfig && | ||||||
|  | 	echo 3 >expect && | ||||||
|  | 	git config test.three >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'include options can still be examined' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	echo one >expect && | ||||||
|  | 	git config include.path >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'listing includes option and expansion' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	cat >expect <<-\EOF && | ||||||
|  | 	include.path=one | ||||||
|  | 	test.one=1 | ||||||
|  | 	EOF | ||||||
|  | 	git config --list >actual.full && | ||||||
|  | 	grep -v ^core actual.full >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'single file lookup does not expand includes by default' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	test_must_fail git config -f .gitconfig test.one && | ||||||
|  | 	test_must_fail git config --global test.one && | ||||||
|  | 	echo 1 >expect && | ||||||
|  | 	git config --includes -f .gitconfig test.one >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'single file list does not expand includes by default' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	echo "include.path=one" >expect && | ||||||
|  | 	git config -f .gitconfig --list >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'writing config file does not expand includes' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	git config test.two 2 && | ||||||
|  | 	echo 2 >expect && | ||||||
|  | 	git config --no-includes test.two >actual && | ||||||
|  | 	test_cmp expect actual && | ||||||
|  | 	test_must_fail git config --no-includes test.one | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'config modification does not affect includes' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo "[include]path = one" >.gitconfig && | ||||||
|  | 	git config test.one 2 && | ||||||
|  | 	echo 1 >expect && | ||||||
|  | 	git config -f one test.one >actual && | ||||||
|  | 	test_cmp expect actual && | ||||||
|  | 	cat >expect <<-\EOF && | ||||||
|  | 	1 | ||||||
|  | 	2 | ||||||
|  | 	EOF | ||||||
|  | 	git config --get-all test.one >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'missing include files are ignored' ' | ||||||
|  | 	cat >.gitconfig <<-\EOF && | ||||||
|  | 	[include]path = foo | ||||||
|  | 	[test]value = yes | ||||||
|  | 	EOF | ||||||
|  | 	echo yes >expect && | ||||||
|  | 	git config test.value >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'absolute includes from command line work' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	echo 1 >expect && | ||||||
|  | 	git -c include.path="$PWD/one" config test.one >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'relative includes from command line fail' ' | ||||||
|  | 	echo "[test]one = 1" >one && | ||||||
|  | 	test_must_fail git -c include.path=one config test.one | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'include cycles are detected' ' | ||||||
|  | 	cat >.gitconfig <<-\EOF && | ||||||
|  | 	[test]value = gitconfig | ||||||
|  | 	[include]path = cycle | ||||||
|  | 	EOF | ||||||
|  | 	cat >cycle <<-\EOF && | ||||||
|  | 	[test]value = cycle | ||||||
|  | 	[include]path = .gitconfig | ||||||
|  | 	EOF | ||||||
|  | 	cat >expect <<-\EOF && | ||||||
|  | 	gitconfig | ||||||
|  | 	cycle | ||||||
|  | 	EOF | ||||||
|  | 	test_must_fail git config --get-all test.value 2>stderr && | ||||||
|  | 	grep "exceeded maximum include depth" stderr | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_done | ||||||
		Reference in New Issue
	
	Block a user
	 Junio C Hamano
					Junio C Hamano