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:
Junio C Hamano 2012-02-23 13:30:14 -08:00
Родитель 883a2a3504 9b25a0b52e
Коммит fd1727f5fa
8 изменённых файлов: 495 добавлений и 60 удалений

Просмотреть файл

@ -84,6 +84,17 @@ customary UNIX fashion.
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
~~~~~~~
@ -106,6 +117,10 @@ Example
gitProxy="ssh" for "kernel.org"
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
~~~~~~~~~

Просмотреть файл

@ -178,6 +178,11 @@ See also <<FILES>>.
Opens an editor to modify the specified config file; either
'--system', '--global', or repository (default).
--includes::
--no-includes::
Respect `include.*` directives in config files when looking up
values. Defaults to on.
[[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 const char *get_color_slot, *get_colorbool_slot;
static int end_null;
static int respect_includes = -1;
#define ACTION_GET (1<<0)
#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_GROUP("Other"),
OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
OPT_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"),
OPT_END(),
};
@ -161,8 +163,11 @@ static int get_value(const char *key_, const char *regex_)
int ret = -1;
char *global = NULL, *repo_config = NULL;
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) {
const char *home = getenv("HOME");
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)
git_config_from_file(show_config, system_wide, NULL);
git_config_from_file(fn, system_wide, data);
if (do_all && global)
git_config_from_file(show_config, global, NULL);
git_config_from_file(fn, global, data);
if (do_all)
git_config_from_file(show_config, local, NULL);
git_config_from_parameters(show_config, NULL);
git_config_from_file(fn, local, data);
git_config_from_parameters(fn, data);
if (!do_all && !seen)
git_config_from_file(show_config, local, NULL);
git_config_from_file(fn, local, data);
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)
git_config_from_file(show_config, system_wide, NULL);
git_config_from_file(fn, system_wide, data);
free(key);
if (regexp) {
@ -301,7 +315,8 @@ static void get_color(const char *def_color)
{
get_color_found = 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)
color_parse(def_color, "command line", parsed_color);
@ -328,7 +343,8 @@ static int get_colorbool(int print)
{
get_colorbool_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 (!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;
char *value;
config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
given_config_file = getenv(CONFIG_ENVIRONMENT);
argc = parse_options(argc, argv, prefix, builtin_config_options,
builtin_config_usage,
@ -366,24 +382,28 @@ int cmd_config(int argc, const char **argv, const char *prefix)
char *home = getenv("HOME");
if (home) {
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
config_exclusive_filename = user_config;
given_config_file = user_config;
} else {
die("$HOME not set");
}
}
else if (use_system_config)
config_exclusive_filename = git_etc_gitconfig();
given_config_file = git_etc_gitconfig();
else if (use_local_config)
config_exclusive_filename = git_pathdup("config");
given_config_file = git_pathdup("config");
else if (given_config_file) {
if (!is_absolute_path(given_config_file) && prefix)
config_exclusive_filename = prefix_filename(prefix,
strlen(prefix),
given_config_file);
given_config_file =
xstrdup(prefix_filename(prefix,
strlen(prefix),
given_config_file));
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) {
term = '\0';
delim = '\n';
@ -420,28 +440,30 @@ int cmd_config(int argc, const char **argv, const char *prefix)
if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
if (git_config(show_all_config, NULL) < 0) {
if (config_exclusive_filename)
if (git_config_with_options(show_all_config, NULL,
given_config_file,
respect_includes) < 0) {
if (given_config_file)
die_errno("unable to read config file '%s'",
config_exclusive_filename);
given_config_file);
else
die("error processing config file(s)");
}
}
else if (actions == ACTION_EDIT) {
check_argc(argc, 0, 0);
if (!config_exclusive_filename && nongit)
if (!given_config_file && nongit)
die("not in a git directory");
git_config(git_default_config, NULL);
launch_editor(config_exclusive_filename ?
config_exclusive_filename : git_path("config"),
launch_editor(given_config_file ?
given_config_file : git_path("config"),
NULL, NULL);
}
else if (actions == ACTION_SET) {
int ret;
check_argc(argc, 2, 2);
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)
error("cannot overwrite multiple values with a single value\n"
" 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) {
check_argc(argc, 2, 3);
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) {
check_argc(argc, 2, 2);
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) {
check_argc(argc, 2, 3);
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) {
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) {
check_argc(argc, 1, 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
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) {
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) {
int ret;
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)
return ret;
if (ret == 0)
@ -501,7 +530,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
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)
return ret;
if (ret == 0)

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 int git_config_from_parameters(config_fn_t fn, void *data);
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_parse_ulong(const char *, unsigned long *);
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_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_in_file(const char *, const char *, const char *);
extern const char *git_etc_gitconfig(void);
extern int check_repository_format_version(const char *var, const char *value, void *cb);
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 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)
extern char git_default_email[MAX_GITNAME];

127
config.c
Просмотреть файл

@ -26,7 +26,68 @@ static config_file *cf;
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)
{
@ -879,9 +940,6 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
int ret = 0, found = 0;
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)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
data);
@ -917,10 +975,26 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
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;
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");
ret = git_config_early(fn, data, repo_config);
@ -929,6 +1003,11 @@ int git_config(config_fn_t fn, void *data)
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.
*/
@ -1233,6 +1312,7 @@ int git_config_set_multivar_in_file(const char *config_filename,
int fd = -1, in_fd;
int ret;
struct lock_file *lock = NULL;
char *filename_buf = NULL;
/* parse-key returns negative; flip the sign to feed exit(3) */
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;
if (!config_filename)
config_filename = filename_buf = git_pathdup("config");
/*
* 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:
if (lock)
rollback_lock_file(lock);
free(filename_buf);
return ret;
write_err_out:
@ -1421,19 +1504,8 @@ write_err_out:
int git_config_set_multivar(const char *key, const char *value,
const char *value_regex, int multi_replace)
{
const char *config_filename;
char *buf = NULL;
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;
return git_config_set_multivar_in_file(NULL, key, value, value_regex,
multi_replace);
}
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 */
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;
char *config_filename;
char *filename_buf = NULL;
struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
int out_fd;
char buf[1024];
FILE *config_file;
if (config_exclusive_filename)
config_filename = xstrdup(config_exclusive_filename);
else
config_filename = git_pathdup("config");
if (!config_filename)
config_filename = filename_buf = git_pathdup("config");
out_fd = hold_lock_file_for_update(lock, config_filename, 0);
if (out_fd < 0) {
ret = error("could not lock config file %s", config_filename);
@ -1552,10 +1624,15 @@ unlock_and_out:
if (commit_lock_file(lock) < 0)
ret = error("could not commit config file %s", config_filename);
out:
free(config_filename);
free(filename_buf);
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
* get a boolean value (i.e. "[my] var" means "true").

Просмотреть файл

@ -451,13 +451,21 @@ test_expect_success 'refer config from subdirectory' '
mkdir x &&
(
cd x &&
echo strasse >expect
echo strasse >expect &&
git config --get --file ../other-config ein.bahn >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
[ein]
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_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

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