Merge branch 'nd/conditional-config-include'

The configuration file learned a new "includeIf.<condition>.path"
that includes the contents of the given path only when the
condition holds.  This allows you to say "include this work-related
bit only in the repositories under my ~/work/ directory".

* nd/conditional-config-include:
  config: add conditional include
  config.txt: reflow the second include.path paragraph
  config.txt: clarify multiple key values in include.path
This commit is contained in:
Junio C Hamano
2017-03-21 15:07:18 -07:00
3 changed files with 218 additions and 8 deletions

View File

@ -13,6 +13,7 @@
#include "hashmap.h"
#include "string-list.h"
#include "utf8.h"
#include "dir.h"
struct config_source {
struct config_source *prev;
@ -170,9 +171,94 @@ static int handle_path_include(const char *path, struct config_include_data *inc
return ret;
}
static int prepare_include_condition_pattern(struct strbuf *pat)
{
struct strbuf path = STRBUF_INIT;
char *expanded;
int prefix = 0;
expanded = expand_user_path(pat->buf);
if (expanded) {
strbuf_reset(pat);
strbuf_addstr(pat, expanded);
free(expanded);
}
if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
const char *slash;
if (!cf || !cf->path)
return error(_("relative config include "
"conditionals must come from files"));
strbuf_add_absolute_path(&path, cf->path);
slash = find_last_dir_sep(path.buf);
if (!slash)
die("BUG: how is this possible?");
strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
prefix = slash - path.buf + 1 /* slash */;
} else if (!is_absolute_path(pat->buf))
strbuf_insert(pat, 0, "**/", 3);
if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
strbuf_addstr(pat, "**");
strbuf_release(&path);
return prefix;
}
static int include_by_gitdir(const char *cond, size_t cond_len, int icase)
{
struct strbuf text = STRBUF_INIT;
struct strbuf pattern = STRBUF_INIT;
int ret = 0, prefix;
strbuf_add_absolute_path(&text, get_git_dir());
strbuf_add(&pattern, cond, cond_len);
prefix = prepare_include_condition_pattern(&pattern);
if (prefix < 0)
goto done;
if (prefix > 0) {
/*
* perform literal matching on the prefix part so that
* any wildcard character in it can't create side effects.
*/
if (text.len < prefix)
goto done;
if (!icase && strncmp(pattern.buf, text.buf, prefix))
goto done;
if (icase && strncasecmp(pattern.buf, text.buf, prefix))
goto done;
}
ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
icase ? WM_CASEFOLD : 0, NULL);
done:
strbuf_release(&pattern);
strbuf_release(&text);
return ret;
}
static int include_condition_is_true(const char *cond, size_t cond_len)
{
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
return include_by_gitdir(cond, cond_len, 0);
else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
return include_by_gitdir(cond, cond_len, 1);
/* unknown conditionals are always false */
return 0;
}
int git_config_include(const char *var, const char *value, void *data)
{
struct config_include_data *inc = data;
const char *cond, *key;
int cond_len;
int ret;
/*
@ -185,6 +271,12 @@ int git_config_include(const char *var, const char *value, void *data)
if (!strcmp(var, "include.path"))
ret = handle_path_include(value, inc);
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
(cond && include_condition_is_true(cond, cond_len)) &&
!strcmp(key, "path"))
ret = handle_path_include(value, inc);
return ret;
}