зеркало из https://github.com/microsoft/git.git
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:
Коммит
fd1727f5fa
|
@ -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
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче