1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-04 22:56:34 +02:00

Merge branch 'gc/config-parsing-cleanup'

Config API clean-up to reduce its dependence on static variables

* gc/config-parsing-cleanup:
  config.c: rename "struct config_source cf"
  config: report cached filenames in die_bad_number()
  config.c: remove current_parsing_scope
  config.c: remove current_config_kvi
  config.c: plumb the_reader through callbacks
  config.c: create config_reader and the_reader
  config.c: don't assign to "cf_global" directly
  config.c: plumb config_source through static fns
This commit is contained in:
Junio C Hamano 2023-04-06 13:38:29 -07:00
commit 06e9e726d4
4 changed files with 370 additions and 245 deletions

588
config.c
View File

@ -52,34 +52,79 @@ struct config_source {
int (*do_ungetc)(int c, struct config_source *conf); int (*do_ungetc)(int c, struct config_source *conf);
long (*do_ftell)(struct config_source *c); long (*do_ftell)(struct config_source *c);
}; };
#define CONFIG_SOURCE_INIT { 0 }
struct config_reader {
/*
* These members record the "current" config source, which can be
* accessed by parsing callbacks.
*
* The "source" variable will be non-NULL only when we are actually
* parsing a real config source (file, blob, cmdline, etc).
*
* The "config_kvi" variable will be non-NULL only when we are feeding
* cached config from a configset into a callback.
*
* They cannot be non-NULL at the same time. If they are both NULL, then
* we aren't parsing anything (and depending on the function looking at
* the variables, it's either a bug for it to be called in the first
* place, or it's a function which can be reused for non-config
* purposes, and should fall back to some sane behavior).
*/
struct config_source *source;
struct key_value_info *config_kvi;
/*
* The "scope" of the current config source being parsed (repo, global,
* etc). Like "source", this is only set when parsing a config source.
* It's not part of "source" because it transcends a single file (i.e.,
* a file included from .git/config is still in "repo" scope).
*
* When iterating through a configset, the equivalent value is
* "config_kvi.scope" (see above).
*/
enum config_scope parsing_scope;
};
/* /*
* These variables record the "current" config source, which * Where possible, prefer to accept "struct config_reader" as an arg than to use
* can be accessed by parsing callbacks. * "the_reader". "the_reader" should only be used if that is infeasible, e.g. in
* * a public function.
* The "cf" variable will be non-NULL only when we are actually parsing a real
* config source (file, blob, cmdline, etc).
*
* The "current_config_kvi" variable will be non-NULL only when we are feeding
* cached config from a configset into a callback.
*
* They should generally never be non-NULL at the same time. If they are both
* NULL, then we aren't parsing anything (and depending on the function looking
* at the variables, it's either a bug for it to be called in the first place,
* or it's a function which can be reused for non-config purposes, and should
* fall back to some sane behavior).
*/ */
static struct config_source *cf; static struct config_reader the_reader;
static struct key_value_info *current_config_kvi;
/* static inline void config_reader_push_source(struct config_reader *reader,
* Similar to the variables above, this gives access to the "scope" of the struct config_source *top)
* current value (repo, global, etc). For cached values, it can be found via {
* the current_config_kvi as above. During parsing, the current value can be if (reader->config_kvi)
* found in this variable. It's not part of "cf" because it transcends a single BUG("source should not be set while iterating a config set");
* file (i.e., a file included from .git/config is still in "repo" scope). top->prev = reader->source;
*/ reader->source = top;
static enum config_scope current_parsing_scope; }
static inline struct config_source *config_reader_pop_source(struct config_reader *reader)
{
struct config_source *ret;
if (!reader->source)
BUG("tried to pop config source, but we weren't reading config");
ret = reader->source;
reader->source = reader->source->prev;
return ret;
}
static inline void config_reader_set_kvi(struct config_reader *reader,
struct key_value_info *kvi)
{
if (kvi && (reader->source || reader->parsing_scope))
BUG("kvi should not be set while parsing a config source");
reader->config_kvi = kvi;
}
static inline void config_reader_set_scope(struct config_reader *reader,
enum config_scope scope)
{
if (scope && reader->config_kvi)
BUG("scope should only be set when iterating through a config source");
reader->parsing_scope = scope;
}
static int pack_compression_seen; static int pack_compression_seen;
static int zlib_compression_seen; static int zlib_compression_seen;
@ -142,6 +187,7 @@ struct config_include_data {
void *data; void *data;
const struct config_options *opts; const struct config_options *opts;
struct git_config_source *config_source; struct git_config_source *config_source;
struct config_reader *config_reader;
/* /*
* All remote URLs discovered when reading all config files. * All remote URLs discovered when reading all config files.
@ -159,7 +205,8 @@ static const char include_depth_advice[] = N_(
"from\n" "from\n"
" %s\n" " %s\n"
"This might be due to circular includes."); "This might be due to circular includes.");
static int handle_path_include(const char *path, struct config_include_data *inc) static int handle_path_include(struct config_source *cs, const char *path,
struct config_include_data *inc)
{ {
int ret = 0; int ret = 0;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
@ -180,14 +227,14 @@ static int handle_path_include(const char *path, struct config_include_data *inc
if (!is_absolute_path(path)) { if (!is_absolute_path(path)) {
char *slash; char *slash;
if (!cf || !cf->path) { if (!cs || !cs->path) {
ret = error(_("relative config includes must come from files")); ret = error(_("relative config includes must come from files"));
goto cleanup; goto cleanup;
} }
slash = find_last_dir_sep(cf->path); slash = find_last_dir_sep(cs->path);
if (slash) if (slash)
strbuf_add(&buf, cf->path, slash - cf->path + 1); strbuf_add(&buf, cs->path, slash - cs->path + 1);
strbuf_addstr(&buf, path); strbuf_addstr(&buf, path);
path = buf.buf; path = buf.buf;
} }
@ -195,8 +242,8 @@ static int handle_path_include(const char *path, struct config_include_data *inc
if (!access_or_die(path, R_OK, 0)) { if (!access_or_die(path, R_OK, 0)) {
if (++inc->depth > MAX_INCLUDE_DEPTH) if (++inc->depth > MAX_INCLUDE_DEPTH)
die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path, die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
!cf ? "<unknown>" : !cs ? "<unknown>" :
cf->name ? cf->name : cs->name ? cs->name :
"the command line"); "the command line");
ret = git_config_from_file(git_config_include, path, inc); ret = git_config_from_file(git_config_include, path, inc);
inc->depth--; inc->depth--;
@ -213,7 +260,8 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
strbuf_addstr(pat, "**"); strbuf_addstr(pat, "**");
} }
static int prepare_include_condition_pattern(struct strbuf *pat) static int prepare_include_condition_pattern(struct config_source *cs,
struct strbuf *pat)
{ {
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;
char *expanded; char *expanded;
@ -229,11 +277,11 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) { if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
const char *slash; const char *slash;
if (!cf || !cf->path) if (!cs || !cs->path)
return error(_("relative config include " return error(_("relative config include "
"conditionals must come from files")); "conditionals must come from files"));
strbuf_realpath(&path, cf->path, 1); strbuf_realpath(&path, cs->path, 1);
slash = find_last_dir_sep(path.buf); slash = find_last_dir_sep(path.buf);
if (!slash) if (!slash)
BUG("how is this possible?"); BUG("how is this possible?");
@ -248,7 +296,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
return prefix; return prefix;
} }
static int include_by_gitdir(const struct config_options *opts, static int include_by_gitdir(struct config_source *cs,
const struct config_options *opts,
const char *cond, size_t cond_len, int icase) const char *cond, size_t cond_len, int icase)
{ {
struct strbuf text = STRBUF_INIT; struct strbuf text = STRBUF_INIT;
@ -264,7 +313,7 @@ static int include_by_gitdir(const struct config_options *opts,
strbuf_realpath(&text, git_dir, 1); strbuf_realpath(&text, git_dir, 1);
strbuf_add(&pattern, cond, cond_len); strbuf_add(&pattern, cond, cond_len);
prefix = prepare_include_condition_pattern(&pattern); prefix = prepare_include_condition_pattern(cs, &pattern);
again: again:
if (prefix < 0) if (prefix < 0)
@ -345,24 +394,18 @@ static void populate_remote_urls(struct config_include_data *inc)
{ {
struct config_options opts; struct config_options opts;
struct config_source *store_cf = cf; enum config_scope store_scope = inc->config_reader->parsing_scope;
struct key_value_info *store_kvi = current_config_kvi;
enum config_scope store_scope = current_parsing_scope;
opts = *inc->opts; opts = *inc->opts;
opts.unconditional_remote_url = 1; opts.unconditional_remote_url = 1;
cf = NULL; config_reader_set_scope(inc->config_reader, 0);
current_config_kvi = NULL;
current_parsing_scope = 0;
inc->remote_urls = xmalloc(sizeof(*inc->remote_urls)); inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
string_list_init_dup(inc->remote_urls); string_list_init_dup(inc->remote_urls);
config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts); config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
cf = store_cf; config_reader_set_scope(inc->config_reader, store_scope);
current_config_kvi = store_kvi;
current_parsing_scope = store_scope;
} }
static int forbid_remote_url(const char *var, const char *value UNUSED, static int forbid_remote_url(const char *var, const char *value UNUSED,
@ -409,15 +452,16 @@ static int include_by_remote_url(struct config_include_data *inc,
inc->remote_urls); inc->remote_urls);
} }
static int include_condition_is_true(struct config_include_data *inc, static int include_condition_is_true(struct config_source *cs,
struct config_include_data *inc,
const char *cond, size_t cond_len) const char *cond, size_t cond_len)
{ {
const struct config_options *opts = inc->opts; const struct config_options *opts = inc->opts;
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
return include_by_gitdir(opts, cond, cond_len, 0); return include_by_gitdir(cs, opts, cond, cond_len, 0);
else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len)) else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
return include_by_gitdir(opts, cond, cond_len, 1); return include_by_gitdir(cs, opts, cond, cond_len, 1);
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
return include_by_branch(cond, cond_len); return include_by_branch(cond, cond_len);
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@ -431,6 +475,7 @@ static int include_condition_is_true(struct config_include_data *inc,
static int git_config_include(const char *var, const char *value, void *data) static int git_config_include(const char *var, const char *value, void *data)
{ {
struct config_include_data *inc = data; struct config_include_data *inc = data;
struct config_source *cs = inc->config_reader->source;
const char *cond, *key; const char *cond, *key;
size_t cond_len; size_t cond_len;
int ret; int ret;
@ -444,16 +489,16 @@ static int git_config_include(const char *var, const char *value, void *data)
return ret; return ret;
if (!strcmp(var, "include.path")) if (!strcmp(var, "include.path"))
ret = handle_path_include(value, inc); ret = handle_path_include(cs, value, inc);
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) && if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
cond && include_condition_is_true(inc, cond, cond_len) && cond && include_condition_is_true(cs, inc, cond, cond_len) &&
!strcmp(key, "path")) { !strcmp(key, "path")) {
config_fn_t old_fn = inc->fn; config_fn_t old_fn = inc->fn;
if (inc->opts->unconditional_remote_url) if (inc->opts->unconditional_remote_url)
inc->fn = forbid_remote_url; inc->fn = forbid_remote_url;
ret = handle_path_include(value, inc); ret = handle_path_include(cs, value, inc);
inc->fn = old_fn; inc->fn = old_fn;
} }
@ -713,12 +758,10 @@ int git_config_from_parameters(config_fn_t fn, void *data)
struct strvec to_free = STRVEC_INIT; struct strvec to_free = STRVEC_INIT;
int ret = 0; int ret = 0;
char *envw = NULL; char *envw = NULL;
struct config_source source; struct config_source source = CONFIG_SOURCE_INIT;
memset(&source, 0, sizeof(source));
source.prev = cf;
source.origin_type = CONFIG_ORIGIN_CMDLINE; source.origin_type = CONFIG_ORIGIN_CMDLINE;
cf = &source; config_reader_push_source(&the_reader, &source);
env = getenv(CONFIG_COUNT_ENVIRONMENT); env = getenv(CONFIG_COUNT_ENVIRONMENT);
if (env) { if (env) {
@ -776,25 +819,25 @@ int git_config_from_parameters(config_fn_t fn, void *data)
strbuf_release(&envvar); strbuf_release(&envvar);
strvec_clear(&to_free); strvec_clear(&to_free);
free(envw); free(envw);
cf = source.prev; config_reader_pop_source(&the_reader);
return ret; return ret;
} }
static int get_next_char(void) static int get_next_char(struct config_source *cs)
{ {
int c = cf->do_fgetc(cf); int c = cs->do_fgetc(cs);
if (c == '\r') { if (c == '\r') {
/* DOS like systems */ /* DOS like systems */
c = cf->do_fgetc(cf); c = cs->do_fgetc(cs);
if (c != '\n') { if (c != '\n') {
if (c != EOF) if (c != EOF)
cf->do_ungetc(c, cf); cs->do_ungetc(c, cs);
c = '\r'; c = '\r';
} }
} }
if (c != EOF && ++cf->total_len > INT_MAX) { if (c != EOF && ++cs->total_len > INT_MAX) {
/* /*
* This is an absurdly long config file; refuse to parse * This is an absurdly long config file; refuse to parse
* further in order to protect downstream code from integer * further in order to protect downstream code from integer
@ -802,38 +845,38 @@ static int get_next_char(void)
* but we can mark EOF and put trash in the return value, * but we can mark EOF and put trash in the return value,
* which will trigger a parse error. * which will trigger a parse error.
*/ */
cf->eof = 1; cs->eof = 1;
return 0; return 0;
} }
if (c == '\n') if (c == '\n')
cf->linenr++; cs->linenr++;
if (c == EOF) { if (c == EOF) {
cf->eof = 1; cs->eof = 1;
cf->linenr++; cs->linenr++;
c = '\n'; c = '\n';
} }
return c; return c;
} }
static char *parse_value(void) static char *parse_value(struct config_source *cs)
{ {
int quote = 0, comment = 0, space = 0; int quote = 0, comment = 0, space = 0;
strbuf_reset(&cf->value); strbuf_reset(&cs->value);
for (;;) { for (;;) {
int c = get_next_char(); int c = get_next_char(cs);
if (c == '\n') { if (c == '\n') {
if (quote) { if (quote) {
cf->linenr--; cs->linenr--;
return NULL; return NULL;
} }
return cf->value.buf; return cs->value.buf;
} }
if (comment) if (comment)
continue; continue;
if (isspace(c) && !quote) { if (isspace(c) && !quote) {
if (cf->value.len) if (cs->value.len)
space++; space++;
continue; continue;
} }
@ -844,9 +887,9 @@ static char *parse_value(void)
} }
} }
for (; space; space--) for (; space; space--)
strbuf_addch(&cf->value, ' '); strbuf_addch(&cs->value, ' ');
if (c == '\\') { if (c == '\\') {
c = get_next_char(); c = get_next_char(cs);
switch (c) { switch (c) {
case '\n': case '\n':
continue; continue;
@ -866,18 +909,19 @@ static char *parse_value(void)
default: default:
return NULL; return NULL;
} }
strbuf_addch(&cf->value, c); strbuf_addch(&cs->value, c);
continue; continue;
} }
if (c == '"') { if (c == '"') {
quote = 1-quote; quote = 1-quote;
continue; continue;
} }
strbuf_addch(&cf->value, c); strbuf_addch(&cs->value, c);
} }
} }
static int get_value(config_fn_t fn, void *data, struct strbuf *name) static int get_value(struct config_source *cs, config_fn_t fn, void *data,
struct strbuf *name)
{ {
int c; int c;
char *value; char *value;
@ -885,8 +929,8 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
/* Get the full name */ /* Get the full name */
for (;;) { for (;;) {
c = get_next_char(); c = get_next_char(cs);
if (cf->eof) if (cs->eof)
break; break;
if (!iskeychar(c)) if (!iskeychar(c))
break; break;
@ -894,13 +938,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
} }
while (c == ' ' || c == '\t') while (c == ' ' || c == '\t')
c = get_next_char(); c = get_next_char(cs);
value = NULL; value = NULL;
if (c != '\n') { if (c != '\n') {
if (c != '=') if (c != '=')
return -1; return -1;
value = parse_value(); value = parse_value(cs);
if (!value) if (!value)
return -1; return -1;
} }
@ -909,20 +953,21 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
* the line we just parsed during the call to fn to get * the line we just parsed during the call to fn to get
* accurate line number in error messages. * accurate line number in error messages.
*/ */
cf->linenr--; cs->linenr--;
ret = fn(name->buf, value, data); ret = fn(name->buf, value, data);
if (ret >= 0) if (ret >= 0)
cf->linenr++; cs->linenr++;
return ret; return ret;
} }
static int get_extended_base_var(struct strbuf *name, int c) static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
int c)
{ {
cf->subsection_case_sensitive = 0; cs->subsection_case_sensitive = 0;
do { do {
if (c == '\n') if (c == '\n')
goto error_incomplete_line; goto error_incomplete_line;
c = get_next_char(); c = get_next_char(cs);
} while (isspace(c)); } while (isspace(c));
/* We require the format to be '[base "extension"]' */ /* We require the format to be '[base "extension"]' */
@ -931,13 +976,13 @@ static int get_extended_base_var(struct strbuf *name, int c)
strbuf_addch(name, '.'); strbuf_addch(name, '.');
for (;;) { for (;;) {
int c = get_next_char(); int c = get_next_char(cs);
if (c == '\n') if (c == '\n')
goto error_incomplete_line; goto error_incomplete_line;
if (c == '"') if (c == '"')
break; break;
if (c == '\\') { if (c == '\\') {
c = get_next_char(); c = get_next_char(cs);
if (c == '\n') if (c == '\n')
goto error_incomplete_line; goto error_incomplete_line;
} }
@ -945,25 +990,25 @@ static int get_extended_base_var(struct strbuf *name, int c)
} }
/* Final ']' */ /* Final ']' */
if (get_next_char() != ']') if (get_next_char(cs) != ']')
return -1; return -1;
return 0; return 0;
error_incomplete_line: error_incomplete_line:
cf->linenr--; cs->linenr--;
return -1; return -1;
} }
static int get_base_var(struct strbuf *name) static int get_base_var(struct config_source *cs, struct strbuf *name)
{ {
cf->subsection_case_sensitive = 1; cs->subsection_case_sensitive = 1;
for (;;) { for (;;) {
int c = get_next_char(); int c = get_next_char(cs);
if (cf->eof) if (cs->eof)
return -1; return -1;
if (c == ']') if (c == ']')
return 0; return 0;
if (isspace(c)) if (isspace(c))
return get_extended_base_var(name, c); return get_extended_base_var(cs, name, c);
if (!iskeychar(c) && c != '.') if (!iskeychar(c) && c != '.')
return -1; return -1;
strbuf_addch(name, tolower(c)); strbuf_addch(name, tolower(c));
@ -976,7 +1021,8 @@ struct parse_event_data {
const struct config_options *opts; const struct config_options *opts;
}; };
static int do_event(enum config_event_t type, struct parse_event_data *data) static int do_event(struct config_source *cs, enum config_event_t type,
struct parse_event_data *data)
{ {
size_t offset; size_t offset;
@ -987,7 +1033,7 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
data->previous_type == type) data->previous_type == type)
return 0; return 0;
offset = cf->do_ftell(cf); offset = cs->do_ftell(cs);
/* /*
* At EOF, the parser always "inserts" an extra '\n', therefore * At EOF, the parser always "inserts" an extra '\n', therefore
* the end offset of the event is the current file position, otherwise * the end offset of the event is the current file position, otherwise
@ -1007,12 +1053,12 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
return 0; return 0;
} }
static int git_parse_source(config_fn_t fn, void *data, static int git_parse_source(struct config_source *cs, config_fn_t fn,
const struct config_options *opts) void *data, const struct config_options *opts)
{ {
int comment = 0; int comment = 0;
size_t baselen = 0; size_t baselen = 0;
struct strbuf *var = &cf->var; struct strbuf *var = &cs->var;
int error_return = 0; int error_return = 0;
char *error_msg = NULL; char *error_msg = NULL;
@ -1027,7 +1073,7 @@ static int git_parse_source(config_fn_t fn, void *data,
for (;;) { for (;;) {
int c; int c;
c = get_next_char(); c = get_next_char(cs);
if (bomptr && *bomptr) { if (bomptr && *bomptr) {
/* We are at the file beginning; skip UTF8-encoded BOM /* We are at the file beginning; skip UTF8-encoded BOM
* if present. Sane editors won't put this in on their * if present. Sane editors won't put this in on their
@ -1044,12 +1090,12 @@ static int git_parse_source(config_fn_t fn, void *data,
} }
} }
if (c == '\n') { if (c == '\n') {
if (cf->eof) { if (cs->eof) {
if (do_event(CONFIG_EVENT_EOF, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
return -1; return -1;
return 0; return 0;
} }
if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
return -1; return -1;
comment = 0; comment = 0;
continue; continue;
@ -1057,23 +1103,23 @@ static int git_parse_source(config_fn_t fn, void *data,
if (comment) if (comment)
continue; continue;
if (isspace(c)) { if (isspace(c)) {
if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
return -1; return -1;
continue; continue;
} }
if (c == '#' || c == ';') { if (c == '#' || c == ';') {
if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
return -1; return -1;
comment = 1; comment = 1;
continue; continue;
} }
if (c == '[') { if (c == '[') {
if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
return -1; return -1;
/* Reset prior to determining a new stem */ /* Reset prior to determining a new stem */
strbuf_reset(var); strbuf_reset(var);
if (get_base_var(var) < 0 || var->len < 1) if (get_base_var(cs, var) < 0 || var->len < 1)
break; break;
strbuf_addch(var, '.'); strbuf_addch(var, '.');
baselen = var->len; baselen = var->len;
@ -1082,7 +1128,7 @@ static int git_parse_source(config_fn_t fn, void *data,
if (!isalpha(c)) if (!isalpha(c))
break; break;
if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
return -1; return -1;
/* /*
@ -1092,42 +1138,42 @@ static int git_parse_source(config_fn_t fn, void *data,
*/ */
strbuf_setlen(var, baselen); strbuf_setlen(var, baselen);
strbuf_addch(var, tolower(c)); strbuf_addch(var, tolower(c));
if (get_value(fn, data, var) < 0) if (get_value(cs, fn, data, var) < 0)
break; break;
} }
if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0) if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
return -1; return -1;
switch (cf->origin_type) { switch (cs->origin_type) {
case CONFIG_ORIGIN_BLOB: case CONFIG_ORIGIN_BLOB:
error_msg = xstrfmt(_("bad config line %d in blob %s"), error_msg = xstrfmt(_("bad config line %d in blob %s"),
cf->linenr, cf->name); cs->linenr, cs->name);
break; break;
case CONFIG_ORIGIN_FILE: case CONFIG_ORIGIN_FILE:
error_msg = xstrfmt(_("bad config line %d in file %s"), error_msg = xstrfmt(_("bad config line %d in file %s"),
cf->linenr, cf->name); cs->linenr, cs->name);
break; break;
case CONFIG_ORIGIN_STDIN: case CONFIG_ORIGIN_STDIN:
error_msg = xstrfmt(_("bad config line %d in standard input"), error_msg = xstrfmt(_("bad config line %d in standard input"),
cf->linenr); cs->linenr);
break; break;
case CONFIG_ORIGIN_SUBMODULE_BLOB: case CONFIG_ORIGIN_SUBMODULE_BLOB:
error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"), error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
cf->linenr, cf->name); cs->linenr, cs->name);
break; break;
case CONFIG_ORIGIN_CMDLINE: case CONFIG_ORIGIN_CMDLINE:
error_msg = xstrfmt(_("bad config line %d in command line %s"), error_msg = xstrfmt(_("bad config line %d in command line %s"),
cf->linenr, cf->name); cs->linenr, cs->name);
break; break;
default: default:
error_msg = xstrfmt(_("bad config line %d in %s"), error_msg = xstrfmt(_("bad config line %d in %s"),
cf->linenr, cf->name); cs->linenr, cs->name);
} }
switch (opts && opts->error_action ? switch (opts && opts->error_action ?
opts->error_action : opts->error_action :
cf->default_error_action) { cs->default_error_action) {
case CONFIG_ERROR_DIE: case CONFIG_ERROR_DIE:
die("%s", error_msg); die("%s", error_msg);
break; break;
@ -1268,38 +1314,48 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)
return 1; return 1;
} }
static int reader_config_name(struct config_reader *reader, const char **out);
static int reader_origin_type(struct config_reader *reader,
enum config_origin_type *type);
NORETURN NORETURN
static void die_bad_number(const char *name, const char *value) static void die_bad_number(struct config_reader *reader, const char *name,
const char *value)
{ {
const char *error_type = (errno == ERANGE) ? const char *error_type = (errno == ERANGE) ?
N_("out of range") : N_("invalid unit"); N_("out of range") : N_("invalid unit");
const char *bad_numeric = N_("bad numeric config value '%s' for '%s': %s"); const char *bad_numeric = N_("bad numeric config value '%s' for '%s': %s");
const char *config_name = NULL;
enum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;
if (!value) if (!value)
value = ""; value = "";
if (!(cf && cf->name)) /* Ignoring the return value is okay since we handle missing values. */
reader_config_name(reader, &config_name);
reader_origin_type(reader, &config_origin);
if (!config_name)
die(_(bad_numeric), value, name, _(error_type)); die(_(bad_numeric), value, name, _(error_type));
switch (cf->origin_type) { switch (config_origin) {
case CONFIG_ORIGIN_BLOB: case CONFIG_ORIGIN_BLOB:
die(_("bad numeric config value '%s' for '%s' in blob %s: %s"), die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
value, name, cf->name, _(error_type)); value, name, config_name, _(error_type));
case CONFIG_ORIGIN_FILE: case CONFIG_ORIGIN_FILE:
die(_("bad numeric config value '%s' for '%s' in file %s: %s"), die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
value, name, cf->name, _(error_type)); value, name, config_name, _(error_type));
case CONFIG_ORIGIN_STDIN: case CONFIG_ORIGIN_STDIN:
die(_("bad numeric config value '%s' for '%s' in standard input: %s"), die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
value, name, _(error_type)); value, name, _(error_type));
case CONFIG_ORIGIN_SUBMODULE_BLOB: case CONFIG_ORIGIN_SUBMODULE_BLOB:
die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"), die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
value, name, cf->name, _(error_type)); value, name, config_name, _(error_type));
case CONFIG_ORIGIN_CMDLINE: case CONFIG_ORIGIN_CMDLINE:
die(_("bad numeric config value '%s' for '%s' in command line %s: %s"), die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
value, name, cf->name, _(error_type)); value, name, config_name, _(error_type));
default: default:
die(_("bad numeric config value '%s' for '%s' in %s: %s"), die(_("bad numeric config value '%s' for '%s' in %s: %s"),
value, name, cf->name, _(error_type)); value, name, config_name, _(error_type));
} }
} }
@ -1307,7 +1363,7 @@ int git_config_int(const char *name, const char *value)
{ {
int ret; int ret;
if (!git_parse_int(value, &ret)) if (!git_parse_int(value, &ret))
die_bad_number(name, value); die_bad_number(&the_reader, name, value);
return ret; return ret;
} }
@ -1315,7 +1371,7 @@ int64_t git_config_int64(const char *name, const char *value)
{ {
int64_t ret; int64_t ret;
if (!git_parse_int64(value, &ret)) if (!git_parse_int64(value, &ret))
die_bad_number(name, value); die_bad_number(&the_reader, name, value);
return ret; return ret;
} }
@ -1323,7 +1379,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
{ {
unsigned long ret; unsigned long ret;
if (!git_parse_ulong(value, &ret)) if (!git_parse_ulong(value, &ret))
die_bad_number(name, value); die_bad_number(&the_reader, name, value);
return ret; return ret;
} }
@ -1331,7 +1387,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
{ {
ssize_t ret; ssize_t ret;
if (!git_parse_ssize_t(value, &ret)) if (!git_parse_ssize_t(value, &ret))
die_bad_number(name, value); die_bad_number(&the_reader, name, value);
return ret; return ret;
} }
@ -1937,36 +1993,37 @@ int git_default_config(const char *var, const char *value, void *cb)
* fgetc, ungetc, ftell of top need to be initialized before calling * fgetc, ungetc, ftell of top need to be initialized before calling
* this function. * this function.
*/ */
static int do_config_from(struct config_source *top, config_fn_t fn, void *data, static int do_config_from(struct config_reader *reader,
struct config_source *top, config_fn_t fn, void *data,
const struct config_options *opts) const struct config_options *opts)
{ {
int ret; int ret;
/* push config-file parsing state stack */ /* push config-file parsing state stack */
top->prev = cf;
top->linenr = 1; top->linenr = 1;
top->eof = 0; top->eof = 0;
top->total_len = 0; top->total_len = 0;
strbuf_init(&top->value, 1024); strbuf_init(&top->value, 1024);
strbuf_init(&top->var, 1024); strbuf_init(&top->var, 1024);
cf = top; config_reader_push_source(reader, top);
ret = git_parse_source(fn, data, opts); ret = git_parse_source(top, fn, data, opts);
/* pop config-file parsing state stack */ /* pop config-file parsing state stack */
strbuf_release(&top->value); strbuf_release(&top->value);
strbuf_release(&top->var); strbuf_release(&top->var);
cf = top->prev; config_reader_pop_source(reader);
return ret; return ret;
} }
static int do_config_from_file(config_fn_t fn, static int do_config_from_file(struct config_reader *reader,
const enum config_origin_type origin_type, config_fn_t fn,
const char *name, const char *path, FILE *f, const enum config_origin_type origin_type,
void *data, const struct config_options *opts) const char *name, const char *path, FILE *f,
void *data, const struct config_options *opts)
{ {
struct config_source top; struct config_source top = CONFIG_SOURCE_INIT;
int ret; int ret;
top.u.file = f; top.u.file = f;
@ -1979,15 +2036,15 @@ static int do_config_from_file(config_fn_t fn,
top.do_ftell = config_file_ftell; top.do_ftell = config_file_ftell;
flockfile(f); flockfile(f);
ret = do_config_from(&top, fn, data, opts); ret = do_config_from(reader, &top, fn, data, opts);
funlockfile(f); funlockfile(f);
return ret; return ret;
} }
static int git_config_from_stdin(config_fn_t fn, void *data) static int git_config_from_stdin(config_fn_t fn, void *data)
{ {
return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, return do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, "",
data, NULL); NULL, stdin, data, NULL);
} }
int git_config_from_file_with_options(config_fn_t fn, const char *filename, int git_config_from_file_with_options(config_fn_t fn, const char *filename,
@ -2001,8 +2058,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
BUG("filename cannot be NULL"); BUG("filename cannot be NULL");
f = fopen_or_warn(filename, "r"); f = fopen_or_warn(filename, "r");
if (f) { if (f) {
ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, ret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,
filename, f, data, opts); filename, filename, f, data, opts);
fclose(f); fclose(f);
} }
return ret; return ret;
@ -2018,7 +2075,7 @@ int git_config_from_mem(config_fn_t fn,
const char *name, const char *buf, size_t len, const char *name, const char *buf, size_t len,
void *data, const struct config_options *opts) void *data, const struct config_options *opts)
{ {
struct config_source top; struct config_source top = CONFIG_SOURCE_INIT;
top.u.buf.buf = buf; top.u.buf.buf = buf;
top.u.buf.len = len; top.u.buf.len = len;
@ -2031,7 +2088,7 @@ int git_config_from_mem(config_fn_t fn,
top.do_ungetc = config_buf_ungetc; top.do_ungetc = config_buf_ungetc;
top.do_ftell = config_buf_ftell; top.do_ftell = config_buf_ftell;
return do_config_from(&top, fn, data, opts); return do_config_from(&the_reader, &top, fn, data, opts);
} }
int git_config_from_blob_oid(config_fn_t fn, int git_config_from_blob_oid(config_fn_t fn,
@ -2122,7 +2179,8 @@ int git_config_system(void)
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
} }
static int do_git_config_sequence(const struct config_options *opts, static int do_git_config_sequence(struct config_reader *reader,
const struct config_options *opts,
config_fn_t fn, void *data) config_fn_t fn, void *data)
{ {
int ret = 0; int ret = 0;
@ -2130,7 +2188,7 @@ static int do_git_config_sequence(const struct config_options *opts,
char *xdg_config = NULL; char *xdg_config = NULL;
char *user_config = NULL; char *user_config = NULL;
char *repo_config; char *repo_config;
enum config_scope prev_parsing_scope = current_parsing_scope; enum config_scope prev_parsing_scope = reader->parsing_scope;
if (opts->commondir) if (opts->commondir)
repo_config = mkpathdup("%s/config", opts->commondir); repo_config = mkpathdup("%s/config", opts->commondir);
@ -2139,13 +2197,13 @@ static int do_git_config_sequence(const struct config_options *opts,
else else
repo_config = NULL; repo_config = NULL;
current_parsing_scope = CONFIG_SCOPE_SYSTEM; config_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);
if (git_config_system() && system_config && if (git_config_system() && system_config &&
!access_or_die(system_config, R_OK, !access_or_die(system_config, R_OK,
opts->system_gently ? ACCESS_EACCES_OK : 0)) opts->system_gently ? ACCESS_EACCES_OK : 0))
ret += git_config_from_file(fn, system_config, data); ret += git_config_from_file(fn, system_config, data);
current_parsing_scope = CONFIG_SCOPE_GLOBAL; config_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);
git_global_config(&user_config, &xdg_config); git_global_config(&user_config, &xdg_config);
if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
@ -2154,12 +2212,12 @@ static int do_git_config_sequence(const struct config_options *opts,
if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
ret += git_config_from_file(fn, user_config, data); ret += git_config_from_file(fn, user_config, data);
current_parsing_scope = CONFIG_SCOPE_LOCAL; config_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);
if (!opts->ignore_repo && repo_config && if (!opts->ignore_repo && repo_config &&
!access_or_die(repo_config, R_OK, 0)) !access_or_die(repo_config, R_OK, 0))
ret += git_config_from_file(fn, repo_config, data); ret += git_config_from_file(fn, repo_config, data);
current_parsing_scope = CONFIG_SCOPE_WORKTREE; config_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);
if (!opts->ignore_worktree && repository_format_worktree_config) { if (!opts->ignore_worktree && repository_format_worktree_config) {
char *path = git_pathdup("config.worktree"); char *path = git_pathdup("config.worktree");
if (!access_or_die(path, R_OK, 0)) if (!access_or_die(path, R_OK, 0))
@ -2167,11 +2225,11 @@ static int do_git_config_sequence(const struct config_options *opts,
free(path); free(path);
} }
current_parsing_scope = CONFIG_SCOPE_COMMAND; config_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);
if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0) if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
die(_("unable to parse command-line config")); die(_("unable to parse command-line config"));
current_parsing_scope = prev_parsing_scope; config_reader_set_scope(reader, prev_parsing_scope);
free(system_config); free(system_config);
free(xdg_config); free(xdg_config);
free(user_config); free(user_config);
@ -2184,6 +2242,7 @@ int config_with_options(config_fn_t fn, void *data,
const struct config_options *opts) const struct config_options *opts)
{ {
struct config_include_data inc = CONFIG_INCLUDE_INIT; struct config_include_data inc = CONFIG_INCLUDE_INIT;
enum config_scope prev_scope = the_reader.parsing_scope;
int ret; int ret;
if (opts->respect_includes) { if (opts->respect_includes) {
@ -2191,12 +2250,13 @@ int config_with_options(config_fn_t fn, void *data,
inc.data = data; inc.data = data;
inc.opts = opts; inc.opts = opts;
inc.config_source = config_source; inc.config_source = config_source;
inc.config_reader = &the_reader;
fn = git_config_include; fn = git_config_include;
data = &inc; data = &inc;
} }
if (config_source) if (config_source)
current_parsing_scope = config_source->scope; config_reader_set_scope(&the_reader, config_source->scope);
/* /*
* If we have a specific filename, use it. Otherwise, follow the * If we have a specific filename, use it. Otherwise, follow the
@ -2212,36 +2272,38 @@ int config_with_options(config_fn_t fn, void *data,
ret = git_config_from_blob_ref(fn, repo, config_source->blob, ret = git_config_from_blob_ref(fn, repo, config_source->blob,
data); data);
} else { } else {
ret = do_git_config_sequence(opts, fn, data); ret = do_git_config_sequence(&the_reader, opts, fn, data);
} }
if (inc.remote_urls) { if (inc.remote_urls) {
string_list_clear(inc.remote_urls, 0); string_list_clear(inc.remote_urls, 0);
FREE_AND_NULL(inc.remote_urls); FREE_AND_NULL(inc.remote_urls);
} }
config_reader_set_scope(&the_reader, prev_scope);
return ret; return ret;
} }
static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) static void configset_iter(struct config_reader *reader, struct config_set *set,
config_fn_t fn, void *data)
{ {
int i, value_index; int i, value_index;
struct string_list *values; struct string_list *values;
struct config_set_element *entry; struct config_set_element *entry;
struct configset_list *list = &cs->list; struct configset_list *list = &set->list;
for (i = 0; i < list->nr; i++) { for (i = 0; i < list->nr; i++) {
entry = list->items[i].e; entry = list->items[i].e;
value_index = list->items[i].value_index; value_index = list->items[i].value_index;
values = &entry->value_list; values = &entry->value_list;
current_config_kvi = values->items[value_index].util; config_reader_set_kvi(reader, values->items[value_index].util);
if (fn(entry->key, values->items[value_index].string, data) < 0) if (fn(entry->key, values->items[value_index].string, data) < 0)
git_die_config_linenr(entry->key, git_die_config_linenr(entry->key,
current_config_kvi->filename, reader->config_kvi->filename,
current_config_kvi->linenr); reader->config_kvi->linenr);
current_config_kvi = NULL; config_reader_set_kvi(reader, NULL);
} }
} }
@ -2293,7 +2355,7 @@ void read_very_early_config(config_fn_t cb, void *data)
} }
RESULT_MUST_BE_USED RESULT_MUST_BE_USED
static int configset_find_element(struct config_set *cs, const char *key, static int configset_find_element(struct config_set *set, const char *key,
struct config_set_element **dest) struct config_set_element **dest)
{ {
struct config_set_element k; struct config_set_element k;
@ -2311,13 +2373,15 @@ static int configset_find_element(struct config_set *cs, const char *key,
hashmap_entry_init(&k.ent, strhash(normalized_key)); hashmap_entry_init(&k.ent, strhash(normalized_key));
k.key = normalized_key; k.key = normalized_key;
found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL); found_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);
free(normalized_key); free(normalized_key);
*dest = found_entry; *dest = found_entry;
return 0; return 0;
} }
static int configset_add_value(struct config_set *cs, const char *key, const char *value) static int configset_add_value(struct config_reader *reader,
struct config_set *set, const char *key,
const char *value)
{ {
struct config_set_element *e; struct config_set_element *e;
struct string_list_item *si; struct string_list_item *si;
@ -2325,7 +2389,7 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
struct key_value_info *kv_info = xmalloc(sizeof(*kv_info)); struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
int ret; int ret;
ret = configset_find_element(cs, key, &e); ret = configset_find_element(set, key, &e);
if (ret) if (ret)
return ret; return ret;
/* /*
@ -2337,28 +2401,28 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
hashmap_entry_init(&e->ent, strhash(key)); hashmap_entry_init(&e->ent, strhash(key));
e->key = xstrdup(key); e->key = xstrdup(key);
string_list_init_dup(&e->value_list); string_list_init_dup(&e->value_list);
hashmap_add(&cs->config_hash, &e->ent); hashmap_add(&set->config_hash, &e->ent);
} }
si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value)); si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc); ALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);
l_item = &cs->list.items[cs->list.nr++]; l_item = &set->list.items[set->list.nr++];
l_item->e = e; l_item->e = e;
l_item->value_index = e->value_list.nr - 1; l_item->value_index = e->value_list.nr - 1;
if (!cf) if (!reader->source)
BUG("configset_add_value has no source"); BUG("configset_add_value has no source");
if (cf->name) { if (reader->source->name) {
kv_info->filename = strintern(cf->name); kv_info->filename = strintern(reader->source->name);
kv_info->linenr = cf->linenr; kv_info->linenr = reader->source->linenr;
kv_info->origin_type = cf->origin_type; kv_info->origin_type = reader->source->origin_type;
} else { } else {
/* for values read from `git_config_from_parameters()` */ /* for values read from `git_config_from_parameters()` */
kv_info->filename = NULL; kv_info->filename = NULL;
kv_info->linenr = -1; kv_info->linenr = -1;
kv_info->origin_type = CONFIG_ORIGIN_CMDLINE; kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
} }
kv_info->scope = current_parsing_scope; kv_info->scope = reader->parsing_scope;
si->util = kv_info; si->util = kv_info;
return 0; return 0;
@ -2377,48 +2441,57 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,
return strcmp(e1->key, e2->key); return strcmp(e1->key, e2->key);
} }
void git_configset_init(struct config_set *cs) void git_configset_init(struct config_set *set)
{ {
hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0); hashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);
cs->hash_initialized = 1; set->hash_initialized = 1;
cs->list.nr = 0; set->list.nr = 0;
cs->list.alloc = 0; set->list.alloc = 0;
cs->list.items = NULL; set->list.items = NULL;
} }
void git_configset_clear(struct config_set *cs) void git_configset_clear(struct config_set *set)
{ {
struct config_set_element *entry; struct config_set_element *entry;
struct hashmap_iter iter; struct hashmap_iter iter;
if (!cs->hash_initialized) if (!set->hash_initialized)
return; return;
hashmap_for_each_entry(&cs->config_hash, &iter, entry, hashmap_for_each_entry(&set->config_hash, &iter, entry,
ent /* member name */) { ent /* member name */) {
free(entry->key); free(entry->key);
string_list_clear(&entry->value_list, 1); string_list_clear(&entry->value_list, 1);
} }
hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent); hashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);
cs->hash_initialized = 0; set->hash_initialized = 0;
free(cs->list.items); free(set->list.items);
cs->list.nr = 0; set->list.nr = 0;
cs->list.alloc = 0; set->list.alloc = 0;
cs->list.items = NULL; set->list.items = NULL;
} }
struct configset_add_data {
struct config_set *config_set;
struct config_reader *config_reader;
};
#define CONFIGSET_ADD_INIT { 0 }
static int config_set_callback(const char *key, const char *value, void *cb) static int config_set_callback(const char *key, const char *value, void *cb)
{ {
struct config_set *cs = cb; struct configset_add_data *data = cb;
configset_add_value(cs, key, value); configset_add_value(data->config_reader, data->config_set, key, value);
return 0; return 0;
} }
int git_configset_add_file(struct config_set *cs, const char *filename) int git_configset_add_file(struct config_set *set, const char *filename)
{ {
return git_config_from_file(config_set_callback, filename, cs); struct configset_add_data data = CONFIGSET_ADD_INIT;
data.config_reader = &the_reader;
data.config_set = set;
return git_config_from_file(config_set_callback, filename, &data);
} }
int git_configset_get_value(struct config_set *cs, const char *key, const char **value) int git_configset_get_value(struct config_set *set, const char *key, const char **value)
{ {
const struct string_list *values = NULL; const struct string_list *values = NULL;
int ret; int ret;
@ -2428,7 +2501,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
* queried key in the files of the configset, the value returned will be the last * queried key in the files of the configset, the value returned will be the last
* value in the value list for that key. * value in the value list for that key.
*/ */
if ((ret = git_configset_get_value_multi(cs, key, &values))) if ((ret = git_configset_get_value_multi(set, key, &values)))
return ret; return ret;
assert(values->nr > 0); assert(values->nr > 0);
@ -2436,13 +2509,13 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *
return 0; return 0;
} }
int git_configset_get_value_multi(struct config_set *cs, const char *key, int git_configset_get_value_multi(struct config_set *set, const char *key,
const struct string_list **dest) const struct string_list **dest)
{ {
struct config_set_element *e; struct config_set_element *e;
int ret; int ret;
if ((ret = configset_find_element(cs, key, &e))) if ((ret = configset_find_element(set, key, &e)))
return ret; return ret;
else if (!e) else if (!e)
return 1; return 1;
@ -2470,32 +2543,32 @@ int git_configset_get_string_multi(struct config_set *cs, const char *key,
return 0; return 0;
} }
int git_configset_get(struct config_set *cs, const char *key) int git_configset_get(struct config_set *set, const char *key)
{ {
struct config_set_element *e; struct config_set_element *e;
int ret; int ret;
if ((ret = configset_find_element(cs, key, &e))) if ((ret = configset_find_element(set, key, &e)))
return ret; return ret;
else if (!e) else if (!e)
return 1; return 1;
return 0; return 0;
} }
int git_configset_get_string(struct config_set *cs, const char *key, char **dest) int git_configset_get_string(struct config_set *set, const char *key, char **dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) if (!git_configset_get_value(set, key, &value))
return git_config_string((const char **)dest, key, value); return git_config_string((const char **)dest, key, value);
else else
return 1; return 1;
} }
static int git_configset_get_string_tmp(struct config_set *cs, const char *key, static int git_configset_get_string_tmp(struct config_set *set, const char *key,
const char **dest) const char **dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) { if (!git_configset_get_value(set, key, &value)) {
if (!value) if (!value)
return config_error_nonbool(key); return config_error_nonbool(key);
*dest = value; *dest = value;
@ -2505,51 +2578,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
} }
} }
int git_configset_get_int(struct config_set *cs, const char *key, int *dest) int git_configset_get_int(struct config_set *set, const char *key, int *dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) { if (!git_configset_get_value(set, key, &value)) {
*dest = git_config_int(key, value); *dest = git_config_int(key, value);
return 0; return 0;
} else } else
return 1; return 1;
} }
int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest) int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) { if (!git_configset_get_value(set, key, &value)) {
*dest = git_config_ulong(key, value); *dest = git_config_ulong(key, value);
return 0; return 0;
} else } else
return 1; return 1;
} }
int git_configset_get_bool(struct config_set *cs, const char *key, int *dest) int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) { if (!git_configset_get_value(set, key, &value)) {
*dest = git_config_bool(key, value); *dest = git_config_bool(key, value);
return 0; return 0;
} else } else
return 1; return 1;
} }
int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int git_configset_get_bool_or_int(struct config_set *set, const char *key,
int *is_bool, int *dest) int *is_bool, int *dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) { if (!git_configset_get_value(set, key, &value)) {
*dest = git_config_bool_or_int(key, value, is_bool); *dest = git_config_bool_or_int(key, value, is_bool);
return 0; return 0;
} else } else
return 1; return 1;
} }
int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest) int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) { if (!git_configset_get_value(set, key, &value)) {
*dest = git_parse_maybe_bool(value); *dest = git_parse_maybe_bool(value);
if (*dest == -1) if (*dest == -1)
return -1; return -1;
@ -2558,10 +2631,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
return 1; return 1;
} }
int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest) int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
{ {
const char *value; const char *value;
if (!git_configset_get_value(cs, key, &value)) if (!git_configset_get_value(set, key, &value))
return git_config_pathname(dest, key, value); return git_config_pathname(dest, key, value);
else else
return 1; return 1;
@ -2571,6 +2644,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha
static void repo_read_config(struct repository *repo) static void repo_read_config(struct repository *repo)
{ {
struct config_options opts = { 0 }; struct config_options opts = { 0 };
struct configset_add_data data = CONFIGSET_ADD_INIT;
opts.respect_includes = 1; opts.respect_includes = 1;
opts.commondir = repo->commondir; opts.commondir = repo->commondir;
@ -2582,8 +2656,10 @@ static void repo_read_config(struct repository *repo)
git_configset_clear(repo->config); git_configset_clear(repo->config);
git_configset_init(repo->config); git_configset_init(repo->config);
data.config_set = repo->config;
data.config_reader = &the_reader;
if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0) if (config_with_options(config_set_callback, &data, NULL, &opts) < 0)
/* /*
* config_with_options() normally returns only * config_with_options() normally returns only
* zero, as most errors are fatal, and * zero, as most errors are fatal, and
@ -2615,7 +2691,7 @@ static void repo_config_clear(struct repository *repo)
void repo_config(struct repository *repo, config_fn_t fn, void *data) void repo_config(struct repository *repo, config_fn_t fn, void *data)
{ {
git_config_check_init(repo); git_config_check_init(repo);
configset_iter(repo->config, fn, data); configset_iter(&the_reader, repo->config, fn, data);
} }
int repo_config_get(struct repository *repo, const char *key) int repo_config_get(struct repository *repo, const char *key)
@ -2722,16 +2798,19 @@ static void read_protected_config(void)
.ignore_worktree = 1, .ignore_worktree = 1,
.system_gently = 1, .system_gently = 1,
}; };
struct configset_add_data data = CONFIGSET_ADD_INIT;
git_configset_init(&protected_config); git_configset_init(&protected_config);
config_with_options(config_set_callback, &protected_config, data.config_set = &protected_config;
NULL, &opts); data.config_reader = &the_reader;
config_with_options(config_set_callback, &data, NULL, &opts);
} }
void git_protected_config(config_fn_t fn, void *data) void git_protected_config(config_fn_t fn, void *data)
{ {
if (!protected_config.hash_initialized) if (!protected_config.hash_initialized)
read_protected_config(); read_protected_config();
configset_iter(&protected_config, fn, data); configset_iter(&the_reader, &protected_config, fn, data);
} }
/* Functions used historically to read configuration from 'the_repository' */ /* Functions used historically to read configuration from 'the_repository' */
@ -2921,6 +3000,7 @@ void git_die_config(const char *key, const char *err, ...)
*/ */
struct config_store_data { struct config_store_data {
struct config_reader *config_reader;
size_t baselen; size_t baselen;
char *key; char *key;
int do_not_match; int do_not_match;
@ -2935,6 +3015,7 @@ struct config_store_data {
unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc; unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
unsigned int key_seen:1, section_seen:1, is_keys_section:1; unsigned int key_seen:1, section_seen:1, is_keys_section:1;
}; };
#define CONFIG_STORE_INIT { 0 }
static void config_store_data_clear(struct config_store_data *store) static void config_store_data_clear(struct config_store_data *store)
{ {
@ -2969,6 +3050,7 @@ static int store_aux_event(enum config_event_t type,
size_t begin, size_t end, void *data) size_t begin, size_t end, void *data)
{ {
struct config_store_data *store = data; struct config_store_data *store = data;
struct config_source *cs = store->config_reader->source;
ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc); ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
store->parsed[store->parsed_nr].begin = begin; store->parsed[store->parsed_nr].begin = begin;
@ -2978,10 +3060,10 @@ static int store_aux_event(enum config_event_t type,
if (type == CONFIG_EVENT_SECTION) { if (type == CONFIG_EVENT_SECTION) {
int (*cmpfn)(const char *, const char *, size_t); int (*cmpfn)(const char *, const char *, size_t);
if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.') if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
return error(_("invalid section name '%s'"), cf->var.buf); return error(_("invalid section name '%s'"), cs->var.buf);
if (cf->subsection_case_sensitive) if (cs->subsection_case_sensitive)
cmpfn = strncasecmp; cmpfn = strncasecmp;
else else
cmpfn = strncmp; cmpfn = strncmp;
@ -2989,8 +3071,8 @@ static int store_aux_event(enum config_event_t type,
/* Is this the section we were looking for? */ /* Is this the section we were looking for? */
store->is_keys_section = store->is_keys_section =
store->parsed[store->parsed_nr].is_keys_section = store->parsed[store->parsed_nr].is_keys_section =
cf->var.len - 1 == store->baselen && cs->var.len - 1 == store->baselen &&
!cmpfn(cf->var.buf, store->key, store->baselen); !cmpfn(cs->var.buf, store->key, store->baselen);
if (store->is_keys_section) { if (store->is_keys_section) {
store->section_seen = 1; store->section_seen = 1;
ALLOC_GROW(store->seen, store->seen_nr + 1, ALLOC_GROW(store->seen, store->seen_nr + 1,
@ -3286,9 +3368,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
char *filename_buf = NULL; char *filename_buf = NULL;
char *contents = NULL; char *contents = NULL;
size_t contents_sz; size_t contents_sz;
struct config_store_data store; struct config_store_data store = CONFIG_STORE_INIT;
memset(&store, 0, sizeof(store)); store.config_reader = &the_reader;
/* 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);
@ -3844,14 +3926,23 @@ int parse_config_key(const char *var,
return 0; return 0;
} }
static int reader_origin_type(struct config_reader *reader,
enum config_origin_type *type)
{
if (the_reader.config_kvi)
*type = reader->config_kvi->origin_type;
else if(the_reader.source)
*type = reader->source->origin_type;
else
return 1;
return 0;
}
const char *current_config_origin_type(void) const char *current_config_origin_type(void)
{ {
int type; enum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;
if (current_config_kvi)
type = current_config_kvi->origin_type; if (reader_origin_type(&the_reader, &type))
else if(cf)
type = cf->origin_type;
else
BUG("current_config_origin_type called outside config callback"); BUG("current_config_origin_type called outside config callback");
switch (type) { switch (type) {
@ -3890,32 +3981,39 @@ const char *config_scope_name(enum config_scope scope)
} }
} }
static int reader_config_name(struct config_reader *reader, const char **out)
{
if (the_reader.config_kvi)
*out = reader->config_kvi->filename;
else if (the_reader.source)
*out = reader->source->name;
else
return 1;
return 0;
}
const char *current_config_name(void) const char *current_config_name(void)
{ {
const char *name; const char *name;
if (current_config_kvi) if (reader_config_name(&the_reader, &name))
name = current_config_kvi->filename;
else if (cf)
name = cf->name;
else
BUG("current_config_name called outside config callback"); BUG("current_config_name called outside config callback");
return name ? name : ""; return name ? name : "";
} }
enum config_scope current_config_scope(void) enum config_scope current_config_scope(void)
{ {
if (current_config_kvi) if (the_reader.config_kvi)
return current_config_kvi->scope; return the_reader.config_kvi->scope;
else else
return current_parsing_scope; return the_reader.parsing_scope;
} }
int current_config_line(void) int current_config_line(void)
{ {
if (current_config_kvi) if (the_reader.config_kvi)
return current_config_kvi->linenr; return the_reader.config_kvi->linenr;
else else
return cf->linenr; return the_reader.source->linenr;
} }
int lookup_config(const char **mapping, int nr_mapping, const char *var) int lookup_config(const char **mapping, int nr_mapping, const char *var)

View File

@ -56,6 +56,7 @@ struct git_config_source {
}; };
enum config_origin_type { enum config_origin_type {
CONFIG_ORIGIN_UNKNOWN = 0,
CONFIG_ORIGIN_BLOB, CONFIG_ORIGIN_BLOB,
CONFIG_ORIGIN_FILE, CONFIG_ORIGIN_FILE,
CONFIG_ORIGIN_STDIN, CONFIG_ORIGIN_STDIN,

View File

@ -32,6 +32,9 @@
* iterate -> iterate over all values using git_config(), and print some * iterate -> iterate over all values using git_config(), and print some
* data for each * data for each
* *
* git_config_int -> iterate over all values using git_config() and print the
* integer value for the entered key or die
*
* Examples: * Examples:
* *
* To print the value with highest priority for key "foo.bAr Baz.rock": * To print the value with highest priority for key "foo.bAr Baz.rock":
@ -56,6 +59,17 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)
return 0; return 0;
} }
static int parse_int_cb(const char *var, const char *value, void *data)
{
const char *key_to_match = data;
if (!strcmp(key_to_match, var)) {
int parsed = git_config_int(value, value);
printf("%d\n", parsed);
}
return 0;
}
static int early_config_cb(const char *var, const char *value, void *vdata) static int early_config_cb(const char *var, const char *value, void *vdata)
{ {
const char *key = vdata; const char *key = vdata;
@ -196,6 +210,9 @@ int cmd__config(int argc, const char **argv)
} else if (!strcmp(argv[1], "iterate")) { } else if (!strcmp(argv[1], "iterate")) {
git_config(iterate_cb, NULL); git_config(iterate_cb, NULL);
goto exit0; goto exit0;
} else if (argc == 3 && !strcmp(argv[1], "git_config_int")) {
git_config(parse_int_cb, (void *) argv[2]);
goto exit0;
} }
die("%s: Please check the syntax and the function name", argv[0]); die("%s: Please check the syntax and the function name", argv[0]);

View File

@ -161,6 +161,10 @@ test_expect_success 'find integer value for a key' '
check_config get_int lamb.chop 65 check_config get_int lamb.chop 65
' '
test_expect_success 'parse integer value during iteration' '
check_config git_config_int lamb.chop 65
'
test_expect_success 'find string value for a key' ' test_expect_success 'find string value for a key' '
check_config get_string case.baz hask && check_config get_string case.baz hask &&
check_config expect_code 1 get_string case.ba "Value not found for \"case.ba\"" check_config expect_code 1 get_string case.ba "Value not found for \"case.ba\""
@ -175,6 +179,11 @@ test_expect_success 'find integer if value is non parse-able' '
check_config expect_code 128 get_int lamb.head check_config expect_code 128 get_int lamb.head
' '
test_expect_success 'non parse-able integer value during iteration' '
check_config expect_code 128 git_config_int lamb.head 2>result &&
grep "fatal: bad numeric config value .* in file \.git/config" result
'
test_expect_success 'find bool value for the entered key' ' test_expect_success 'find bool value for the entered key' '
check_config get_bool goat.head 1 && check_config get_bool goat.head 1 &&
check_config get_bool goat.skin 0 && check_config get_bool goat.skin 0 &&