diff options
Diffstat (limited to 'config.c')
-rw-r--r-- | config.c | 1708 |
1 files changed, 1235 insertions, 473 deletions
@@ -6,13 +6,19 @@ * */ #include "cache.h" +#include "branch.h" +#include "config.h" +#include "repository.h" #include "lockfile.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "strbuf.h" #include "quote.h" #include "hashmap.h" #include "string-list.h" +#include "object-store.h" #include "utf8.h" +#include "dir.h" +#include "color.h" struct config_source { struct config_source *prev; @@ -24,14 +30,15 @@ struct config_source { size_t pos; } buf; } u; - const char *origin_type; + enum config_origin_type origin_type; const char *name; const char *path; - int die_on_error; + enum config_error_action default_error_action; int linenr; int eof; struct strbuf value; struct strbuf var; + unsigned subsection_case_sensitive : 1; int (*do_fgetc)(struct config_source *c); int (*do_ungetc)(int c, struct config_source *conf); @@ -66,15 +73,10 @@ static struct key_value_info *current_config_kvi; */ static enum config_scope current_parsing_scope; +static int core_compression_seen; +static int pack_compression_seen; static int zlib_compression_seen; -/* - * Default config_set that contains key-value pairs from the usual set of config - * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG - * config file and the global /etc/gitconfig) - */ -static struct config_set the_config_set; - static int config_file_fgetc(struct config_source *conf) { return getc_unlocked(conf->u.file); @@ -104,7 +106,7 @@ static int config_buf_ungetc(int c, struct config_source *conf) if (conf->u.buf.pos > 0) { conf->u.buf.pos--; if (conf->u.buf.buf[conf->u.buf.pos] != c) - die("BUG: config_buf can only ungetc the same character"); + BUG("config_buf can only ungetc the same character"); return c; } @@ -117,12 +119,12 @@ static long config_buf_ftell(struct config_source *conf) } #define MAX_INCLUDE_DEPTH 10 -static const char include_depth_advice[] = +static const char include_depth_advice[] = N_( "exceeded maximum include depth (%d) while including\n" " %s\n" "from\n" " %s\n" -"Do you have circular includes?"; +"This might be due to circular includes."); static int handle_path_include(const char *path, struct config_include_data *inc) { int ret = 0; @@ -132,9 +134,9 @@ static int handle_path_include(const char *path, struct config_include_data *inc if (!path) return config_error_nonbool("include.path"); - expanded = expand_user_path(path); + expanded = expand_user_path(path, 0); if (!expanded) - return error("could not expand include path '%s'", path); + return error(_("could not expand include path '%s'"), path); path = expanded; /* @@ -145,7 +147,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc char *slash; if (!cf || !cf->path) - return error("relative config includes must come from files"); + return error(_("relative config includes must come from files")); slash = find_last_dir_sep(cf->path); if (slash) @@ -156,7 +158,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc if (!access_or_die(path, R_OK, 0)) { if (++inc->depth > MAX_INCLUDE_DEPTH) - die(include_depth_advice, MAX_INCLUDE_DEPTH, path, + die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path, !cf ? "<unknown>" : cf->name ? cf->name : "the command line"); @@ -168,9 +170,118 @@ 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, 1); + 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_realpath(&path, cf->path, 1); + slash = find_last_dir_sep(path.buf); + if (!slash) + 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 struct config_options *opts, + const char *cond, size_t cond_len, int icase) +{ + struct strbuf text = STRBUF_INIT; + struct strbuf pattern = STRBUF_INIT; + int ret = 0, prefix; + const char *git_dir; + int already_tried_absolute = 0; + + if (opts->git_dir) + git_dir = opts->git_dir; + else + goto done; + + strbuf_realpath(&text, git_dir, 1); + strbuf_add(&pattern, cond, cond_len); + prefix = prepare_include_condition_pattern(&pattern); + +again: + 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); + + if (!ret && !already_tried_absolute) { + /* + * We've tried e.g. matching gitdir:~/work, but if + * ~/work is a symlink to /mnt/storage/work + * strbuf_realpath() will expand it, so the rule won't + * match. Let's match against a + * strbuf_add_absolute_path() version of the path, + * which'll do the right thing + */ + strbuf_reset(&text); + strbuf_add_absolute_path(&text, git_dir); + already_tried_absolute = 1; + goto again; + } +done: + strbuf_release(&pattern); + strbuf_release(&text); + return ret; +} + +static int include_condition_is_true(const struct config_options *opts, + const char *cond, size_t cond_len) +{ + + if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) + return include_by_gitdir(opts, cond, cond_len, 0); + else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len)) + return include_by_gitdir(opts, 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; /* @@ -183,6 +294,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(inc->opts, cond, cond_len)) && + !strcmp(key, "path")) + ret = handle_path_include(value, inc); + return ret; } @@ -199,15 +316,108 @@ void git_config_push_parameter(const char *text) strbuf_release(&env); } +static inline int iskeychar(int c) +{ + return isalnum(c) || c == '-'; +} + +/* + * Auxiliary function to sanity-check and split the key into the section + * identifier and variable name. + * + * Returns 0 on success, -1 when there is an invalid character in the key and + * -2 if there is no section name in the key. + * + * store_key - pointer to char* which will hold a copy of the key with + * lowercase section and variable name + * baselen - pointer to int which will hold the length of the + * section + subsection part, can be NULL + */ +static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet) +{ + int i, dot, baselen; + const char *last_dot = strrchr(key, '.'); + + /* + * Since "key" actually contains the section name and the real + * key name separated by a dot, we have to know where the dot is. + */ + + if (last_dot == NULL || last_dot == key) { + if (!quiet) + error(_("key does not contain a section: %s"), key); + return -CONFIG_NO_SECTION_OR_NAME; + } + + if (!last_dot[1]) { + if (!quiet) + error(_("key does not contain variable name: %s"), key); + return -CONFIG_NO_SECTION_OR_NAME; + } + + baselen = last_dot - key; + if (baselen_) + *baselen_ = baselen; + + /* + * Validate the key and while at it, lower case it for matching. + */ + if (store_key) + *store_key = xmallocz(strlen(key)); + + dot = 0; + for (i = 0; key[i]; i++) { + unsigned char c = key[i]; + if (c == '.') + dot = 1; + /* Leave the extended basename untouched.. */ + if (!dot || i > baselen) { + if (!iskeychar(c) || + (i == baselen + 1 && !isalpha(c))) { + if (!quiet) + error(_("invalid key: %s"), key); + goto out_free_ret_1; + } + c = tolower(c); + } else if (c == '\n') { + if (!quiet) + error(_("invalid key (newline): %s"), key); + goto out_free_ret_1; + } + if (store_key) + (*store_key)[i] = c; + } + + return 0; + +out_free_ret_1: + if (store_key) { + FREE_AND_NULL(*store_key); + } + return -CONFIG_INVALID_KEY; +} + +int git_config_parse_key(const char *key, char **store_key, int *baselen) +{ + return git_config_parse_key_1(key, store_key, baselen, 0); +} + +int git_config_key_is_valid(const char *key) +{ + return !git_config_parse_key_1(key, NULL, NULL, 1); +} + int git_config_parse_parameter(const char *text, config_fn_t fn, void *data) { const char *value; + char *canonical_name; struct strbuf **pair; + int ret; pair = strbuf_split_str(text, '=', 2); if (!pair[0]) - return error("bogus config parameter: %s", text); + return error(_("bogus config parameter: %s"), text); if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') { strbuf_setlen(pair[0], pair[0]->len - 1); @@ -219,15 +429,17 @@ int git_config_parse_parameter(const char *text, strbuf_trim(pair[0]); if (!pair[0]->len) { strbuf_list_free(pair); - return error("bogus config parameter: %s", text); + return error(_("bogus config parameter: %s"), text); } - strbuf_tolower(pair[0]); - if (fn(pair[0]->buf, value, data) < 0) { - strbuf_list_free(pair); - return -1; + + if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) { + ret = -1; + } else { + ret = (fn(canonical_name, value, data) < 0) ? -1 : 0; + free(canonical_name); } strbuf_list_free(pair); - return 0; + return ret; } int git_config_from_parameters(config_fn_t fn, void *data) @@ -245,13 +457,14 @@ int git_config_from_parameters(config_fn_t fn, void *data) memset(&source, 0, sizeof(source)); source.prev = cf; + source.origin_type = CONFIG_ORIGIN_CMDLINE; cf = &source; /* sq_dequote will write over it */ envw = xstrdup(env); if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) { - ret = error("bogus format in " CONFIG_DATA_ENVIRONMENT); + ret = error(_("bogus format in %s"), CONFIG_DATA_ENVIRONMENT); goto out; } @@ -353,11 +566,6 @@ static char *parse_value(void) } } -static inline int iskeychar(int c) -{ - return isalnum(c) || c == '-'; -} - static int get_value(config_fn_t fn, void *data, struct strbuf *name) { int c; @@ -392,12 +600,14 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name) */ cf->linenr--; ret = fn(name->buf, value, data); - cf->linenr++; + if (ret >= 0) + cf->linenr++; return ret; } static int get_extended_base_var(struct strbuf *name, int c) { + cf->subsection_case_sensitive = 0; do { if (c == '\n') goto error_incomplete_line; @@ -434,6 +644,7 @@ error_incomplete_line: static int get_base_var(struct strbuf *name) { + cf->subsection_case_sensitive = 1; for (;;) { int c = get_next_char(); if (cf->eof) @@ -448,17 +659,64 @@ static int get_base_var(struct strbuf *name) } } -static int git_parse_source(config_fn_t fn, void *data) +struct parse_event_data { + enum config_event_t previous_type; + size_t previous_offset; + const struct config_options *opts; +}; + +static int do_event(enum config_event_t type, struct parse_event_data *data) +{ + size_t offset; + + if (!data->opts || !data->opts->event_fn) + return 0; + + if (type == CONFIG_EVENT_WHITESPACE && + data->previous_type == type) + return 0; + + offset = cf->do_ftell(cf); + /* + * At EOF, the parser always "inserts" an extra '\n', therefore + * the end offset of the event is the current file position, otherwise + * we will already have advanced to the next event. + */ + if (type != CONFIG_EVENT_EOF) + offset--; + + if (data->previous_type != CONFIG_EVENT_EOF && + data->opts->event_fn(data->previous_type, data->previous_offset, + offset, data->opts->event_fn_data) < 0) + return -1; + + data->previous_type = type; + data->previous_offset = offset; + + return 0; +} + +static int git_parse_source(config_fn_t fn, void *data, + const struct config_options *opts) { int comment = 0; int baselen = 0; struct strbuf *var = &cf->var; + int error_return = 0; + char *error_msg = NULL; /* U+FEFF Byte Order Mark in UTF8 */ const char *bomptr = utf8_bom; + /* For the parser event callback */ + struct parse_event_data event_data = { + CONFIG_EVENT_EOF, 0, opts + }; + for (;;) { - int c = get_next_char(); + int c; + + c = get_next_char(); if (bomptr && *bomptr) { /* We are at the file beginning; skip UTF8-encoded BOM * if present. Sane editors won't put this in on their @@ -475,18 +733,33 @@ static int git_parse_source(config_fn_t fn, void *data) } } if (c == '\n') { - if (cf->eof) + if (cf->eof) { + if (do_event(CONFIG_EVENT_EOF, &event_data) < 0) + return -1; return 0; + } + if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0) + return -1; comment = 0; continue; } - if (comment || isspace(c)) + if (comment) continue; + if (isspace(c)) { + if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0) + return -1; + continue; + } if (c == '#' || c == ';') { + if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0) + return -1; comment = 1; continue; } if (c == '[') { + if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0) + return -1; + /* Reset prior to determining a new stem */ strbuf_reset(var); if (get_base_var(var) < 0 || var->len < 1) @@ -497,6 +770,10 @@ static int git_parse_source(config_fn_t fn, void *data) } if (!isalpha(c)) break; + + if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0) + return -1; + /* * Truncate the var name back to the section header * stem prior to grabbing the suffix part of the name @@ -507,10 +784,54 @@ static int git_parse_source(config_fn_t fn, void *data) if (get_value(fn, data, var) < 0) break; } - if (cf->die_on_error) - die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name); - else - return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name); + + if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0) + return -1; + + switch (cf->origin_type) { + case CONFIG_ORIGIN_BLOB: + error_msg = xstrfmt(_("bad config line %d in blob %s"), + cf->linenr, cf->name); + break; + case CONFIG_ORIGIN_FILE: + error_msg = xstrfmt(_("bad config line %d in file %s"), + cf->linenr, cf->name); + break; + case CONFIG_ORIGIN_STDIN: + error_msg = xstrfmt(_("bad config line %d in standard input"), + cf->linenr); + break; + case CONFIG_ORIGIN_SUBMODULE_BLOB: + error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"), + cf->linenr, cf->name); + break; + case CONFIG_ORIGIN_CMDLINE: + error_msg = xstrfmt(_("bad config line %d in command line %s"), + cf->linenr, cf->name); + break; + default: + error_msg = xstrfmt(_("bad config line %d in %s"), + cf->linenr, cf->name); + } + + switch (opts && opts->error_action ? + opts->error_action : + cf->default_error_action) { + case CONFIG_ERROR_DIE: + die("%s", error_msg); + break; + case CONFIG_ERROR_ERROR: + error_return = error("%s", error_msg); + break; + case CONFIG_ERROR_SILENT: + error_return = -1; + break; + case CONFIG_ERROR_UNSET: + BUG("config error action unset"); + } + + free(error_msg); + return error_return; } static int parse_unit_factor(const char *end, uintmax_t *val) @@ -616,19 +937,47 @@ int git_parse_ulong(const char *value, unsigned long *ret) return 1; } +int git_parse_ssize_t(const char *value, ssize_t *ret) +{ + intmax_t tmp; + if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t))) + return 0; + *ret = tmp; + return 1; +} + NORETURN static void die_bad_number(const char *name, const char *value) { - const char *reason = errno == ERANGE ? - "out of range" : - "invalid unit"; + const char * error_type = (errno == ERANGE)? _("out of range"):_("invalid unit"); + if (!value) value = ""; - if (cf && cf->origin_type && cf->name) - die(_("bad numeric config value '%s' for '%s' in %s %s: %s"), - value, name, cf->origin_type, cf->name, reason); - die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason); + if (!(cf && cf->name)) + die(_("bad numeric config value '%s' for '%s': %s"), + value, name, error_type); + + switch (cf->origin_type) { + case CONFIG_ORIGIN_BLOB: + die(_("bad numeric config value '%s' for '%s' in blob %s: %s"), + value, name, cf->name, error_type); + case CONFIG_ORIGIN_FILE: + die(_("bad numeric config value '%s' for '%s' in file %s: %s"), + value, name, cf->name, error_type); + case CONFIG_ORIGIN_STDIN: + die(_("bad numeric config value '%s' for '%s' in standard input: %s"), + value, name, error_type); + case CONFIG_ORIGIN_SUBMODULE_BLOB: + die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"), + value, name, cf->name, error_type); + case CONFIG_ORIGIN_CMDLINE: + die(_("bad numeric config value '%s' for '%s' in command line %s: %s"), + value, name, cf->name, error_type); + default: + die(_("bad numeric config value '%s' for '%s' in %s: %s"), + value, name, cf->name, error_type); + } } int git_config_int(const char *name, const char *value) @@ -655,7 +1004,15 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_parse_maybe_bool(const char *value) +ssize_t git_config_ssize_t(const char *name, const char *value) +{ + ssize_t ret; + if (!git_parse_ssize_t(value, &ret)) + die_bad_number(name, value); + return ret; +} + +static int git_parse_maybe_bool_text(const char *value) { if (!value) return 1; @@ -672,9 +1029,9 @@ int git_parse_maybe_bool(const char *value) return -1; } -int git_config_maybe_bool(const char *name, const char *value) +int git_parse_maybe_bool(const char *value) { - int v = git_parse_maybe_bool(value); + int v = git_parse_maybe_bool_text(value); if (0 <= v) return v; if (git_parse_int(value, &v)) @@ -684,7 +1041,7 @@ int git_config_maybe_bool(const char *name, const char *value) int git_config_bool_or_int(const char *name, const char *value, int *is_bool) { - int v = git_parse_maybe_bool(value); + int v = git_parse_maybe_bool_text(value); if (0 <= v) { *is_bool = 1; return v; @@ -711,12 +1068,31 @@ int git_config_pathname(const char **dest, const char *var, const char *value) { if (!value) return config_error_nonbool(var); - *dest = expand_user_path(value); + *dest = expand_user_path(value, 0); if (!*dest) die(_("failed to expand user dir in: '%s'"), value); return 0; } +int git_config_expiry_date(timestamp_t *timestamp, const char *var, const char *value) +{ + if (!value) + return config_error_nonbool(var); + if (parse_expiry_date(value, timestamp)) + return error(_("'%s' for '%s' is not a valid timestamp"), + value, var); + return 0; +} + +int git_config_color(char *dest, const char *var, const char *value) +{ + if (!value) + return config_error_nonbool(var); + if (color_parse(value, dest) < 0) + return -1; + return 0; +} + static int git_default_core_config(const char *var, const char *value) { /* This needs a better name */ @@ -772,7 +1148,12 @@ static int git_default_core_config(const char *var, const char *value) } if (!strcmp(var, "core.logallrefupdates")) { - log_all_ref_updates = git_config_bool(var, value); + if (value && !strcasecmp(value, "always")) + log_all_ref_updates = LOG_REFS_ALWAYS; + else if (git_config_bool(var, value)) + log_all_ref_updates = LOG_REFS_NORMAL; + else + log_all_ref_updates = LOG_REFS_NONE; return 0; } @@ -782,13 +1163,22 @@ static int git_default_core_config(const char *var, const char *value) } if (!strcmp(var, "core.abbrev")) { - int abbrev = git_config_int(var, value); - if (abbrev < minimum_abbrev || abbrev > 40) - return -1; - default_abbrev = abbrev; + if (!value) + return config_error_nonbool(var); + if (!strcasecmp(value, "auto")) + default_abbrev = -1; + else { + int abbrev = git_config_int(var, value); + if (abbrev < minimum_abbrev || abbrev > 40) + return error(_("abbrev length out of range: %d"), abbrev); + default_abbrev = abbrev; + } return 0; } + if (!strcmp(var, "core.disambiguate")) + return set_disambiguate_hint_config(var, value); + if (!strcmp(var, "core.loosecompression")) { int level = git_config_int(var, value); if (level == -1) @@ -810,6 +1200,8 @@ static int git_default_core_config(const char *var, const char *value) core_compression_seen = 1; if (!zlib_compression_seen) zlib_compression_level = level; + if (!pack_compression_seen) + pack_compression_level = level; return 0; } @@ -850,11 +1242,14 @@ static int git_default_core_config(const char *var, const char *value) } if (!strcmp(var, "core.safecrlf")) { + int eol_rndtrp_die; if (value && !strcasecmp(value, "warn")) { - safe_crlf = SAFE_CRLF_WARN; + global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; return 0; } - safe_crlf = git_config_bool(var, value); + eol_rndtrp_die = git_config_bool(var, value); + global_conv_flags_eol = eol_rndtrp_die ? + CONV_EOL_RNDTRP_DIE : 0; return 0; } @@ -870,14 +1265,16 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.checkroundtripencoding")) { + check_roundtrip_encoding = xstrdup(value); + return 0; + } + if (!strcmp(var, "core.notesref")) { notes_ref_name = xstrdup(value); return 0; } - if (!strcmp(var, "core.pager")) - return git_config_string(&pager_program, var, value); - if (!strcmp(var, "core.editor")) return git_config_string(&editor_program, var, value); @@ -890,7 +1287,7 @@ static int git_default_core_config(const char *var, const char *value) comment_line_char = value[0]; auto_comment_line_char = 0; } else - return error("core.commentChar should only be one character"); + return error(_("core.commentChar should only be one character")); return 0; } @@ -955,6 +1352,16 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.partialclonefilter")) { + return git_config_string(&core_partial_clone_filter_default, + var, value); + } + + if (!strcmp(var, "core.usereplacerefs")) { + read_replace_refs = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -993,7 +1400,7 @@ static int git_default_branch_config(const char *var, const char *value) else if (!strcmp(value, "always")) autorebase = AUTOREBASE_ALWAYS; else - return error("malformed value for %s", var); + return error(_("malformed value for %s"), var); return 0; } @@ -1019,9 +1426,9 @@ static int git_default_push_config(const char *var, const char *value) else if (!strcmp(value, "current")) push_default = PUSH_DEFAULT_CURRENT; else { - error("malformed value for %s: %s", var, value); - return error("Must be one of nothing, matching, simple, " - "upstream or current."); + error(_("malformed value for %s: %s"), var, value); + return error(_("must be one of nothing, matching, simple, " + "upstream or current")); } return 0; } @@ -1061,7 +1468,7 @@ int git_default_config(const char *var, const char *value, void *dummy) if (starts_with(var, "mailmap.")) return git_default_mailmap_config(var, value); - if (starts_with(var, "advice.")) + if (starts_with(var, "advice.") || starts_with(var, "color.advice")) return git_default_advice_config(var, value); if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { @@ -1073,6 +1480,18 @@ int git_default_config(const char *var, const char *value, void *dummy) pack_size_limit_cfg = git_config_ulong(var, value); return 0; } + + if (!strcmp(var, "pack.compression")) { + int level = git_config_int(var, value); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die(_("bad pack compression level %d"), level); + pack_compression_level = level; + pack_compression_seen = 1; + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -1082,7 +1501,8 @@ int git_default_config(const char *var, const char *value, void *dummy) * fgetc, ungetc, ftell of top need to be initialized before calling * this function. */ -static int do_config_from(struct config_source *top, config_fn_t fn, void *data) +static int do_config_from(struct config_source *top, config_fn_t fn, void *data, + const struct config_options *opts) { int ret; @@ -1094,7 +1514,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data) strbuf_init(&top->var, 1024); cf = top; - ret = git_parse_source(fn, data); + ret = git_parse_source(fn, data, opts); /* pop config-file parsing state stack */ strbuf_release(&top->value); @@ -1105,45 +1525,59 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data) } static int do_config_from_file(config_fn_t fn, - const char *origin_type, const char *name, const char *path, FILE *f, - void *data) + const enum config_origin_type origin_type, + const char *name, const char *path, FILE *f, + void *data, const struct config_options *opts) { struct config_source top; + int ret; top.u.file = f; top.origin_type = origin_type; top.name = name; top.path = path; - top.die_on_error = 1; + top.default_error_action = CONFIG_ERROR_DIE; top.do_fgetc = config_file_fgetc; top.do_ungetc = config_file_ungetc; top.do_ftell = config_file_ftell; - return do_config_from(&top, fn, data); + flockfile(f); + ret = do_config_from(&top, fn, data, opts); + funlockfile(f); + return ret; } static int git_config_from_stdin(config_fn_t fn, void *data) { - return do_config_from_file(fn, "standard input", "", NULL, stdin, data); + return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, + data, NULL); } -int git_config_from_file(config_fn_t fn, const char *filename, void *data) +int git_config_from_file_with_options(config_fn_t fn, const char *filename, + void *data, + const struct config_options *opts) { int ret = -1; FILE *f; - f = fopen(filename, "r"); + f = fopen_or_warn(filename, "r"); if (f) { - flockfile(f); - ret = do_config_from_file(fn, "file", filename, filename, f, data); - funlockfile(f); + ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, + filename, f, data, opts); fclose(f); } return ret; } -int git_config_from_mem(config_fn_t fn, const char *origin_type, - const char *name, const char *buf, size_t len, void *data) +int git_config_from_file(config_fn_t fn, const char *filename, void *data) +{ + return git_config_from_file_with_options(fn, filename, data, NULL); +} + +int git_config_from_mem(config_fn_t fn, + const enum config_origin_type origin_type, + const char *name, const char *buf, size_t len, + void *data, const struct config_options *opts) { struct config_source top; @@ -1153,33 +1587,34 @@ int git_config_from_mem(config_fn_t fn, const char *origin_type, top.origin_type = origin_type; top.name = name; top.path = NULL; - top.die_on_error = 0; + top.default_error_action = CONFIG_ERROR_ERROR; top.do_fgetc = config_buf_fgetc; top.do_ungetc = config_buf_ungetc; top.do_ftell = config_buf_ftell; - return do_config_from(&top, fn, data); + return do_config_from(&top, fn, data, opts); } -static int git_config_from_blob_sha1(config_fn_t fn, - const char *name, - const unsigned char *sha1, - void *data) +int git_config_from_blob_oid(config_fn_t fn, + const char *name, + const struct object_id *oid, + void *data) { enum object_type type; char *buf; unsigned long size; int ret; - buf = read_sha1_file(sha1, &type, &size); + buf = read_object_file(oid, &type, &size); if (!buf) - return error("unable to load config blob object '%s'", name); + return error(_("unable to load config blob object '%s'"), name); if (type != OBJ_BLOB) { free(buf); - return error("reference '%s' does not point to a blob", name); + return error(_("reference '%s' does not point to a blob"), name); } - ret = git_config_from_mem(fn, "blob", name, buf, size, data); + ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size, + data, NULL); free(buf); return ret; @@ -1189,11 +1624,11 @@ static int git_config_from_blob_ref(config_fn_t fn, const char *name, void *data) { - unsigned char sha1[20]; + struct object_id oid; - if (get_sha1(name, sha1) < 0) - return error("unable to resolve config blob '%s'", name); - return git_config_from_blob_sha1(fn, name, sha1, data); + if (get_oid(name, &oid) < 0) + return error(_("unable to resolve config blob '%s'"), name); + return git_config_from_blob_oid(fn, name, &oid, data); } const char *git_etc_gitconfig(void) @@ -1222,7 +1657,7 @@ unsigned long git_env_ulong(const char *k, unsigned long val) { const char *v = getenv(k); if (v && !git_parse_ulong(v, &val)) - die("failed to parse %s", k); + die(_("failed to parse %s"), k); return val; } @@ -1231,12 +1666,18 @@ int git_config_system(void) return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); } -static int do_git_config_sequence(config_fn_t fn, void *data) +static int do_git_config_sequence(const struct config_options *opts, + config_fn_t fn, void *data) { int ret = 0; char *xdg_config = xdg_config_home("config"); - char *user_config = expand_user_path("~/.gitconfig"); - char *repo_config = git_pathdup("config"); + char *user_config = expand_user_path("~/.gitconfig", 0); + char *repo_config; + + if (opts->commondir) + repo_config = mkpathdup("%s/config", opts->commondir); + else + repo_config = NULL; current_parsing_scope = CONFIG_SCOPE_SYSTEM; if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) @@ -1265,15 +1706,16 @@ static int do_git_config_sequence(config_fn_t fn, void *data) return ret; } -int git_config_with_options(config_fn_t fn, void *data, - struct git_config_source *config_source, - int respect_includes) +int config_with_options(config_fn_t fn, void *data, + struct git_config_source *config_source, + const struct config_options *opts) { struct config_include_data inc = CONFIG_INCLUDE_INIT; - if (respect_includes) { + if (opts->respect_includes) { inc.fn = fn; inc.data = data; + inc.opts = opts; fn = git_config_include; data = &inc; } @@ -1289,24 +1731,7 @@ int git_config_with_options(config_fn_t fn, void *data, else if (config_source && config_source->blob) return git_config_from_blob_ref(fn, config_source->blob, data); - return do_git_config_sequence(fn, data); -} - -static void git_config_raw(config_fn_t fn, void *data) -{ - if (git_config_with_options(fn, data, NULL, 1) < 0) - /* - * git_config_with_options() normally returns only - * zero, as most errors are fatal, and - * non-fatal potential errors are guarded by "if" - * statements that are entered only when no error is - * possible. - * - * If we ever encounter a non-fatal error, it means - * something went really wrong and we should stop - * immediately. - */ - die(_("unknown error occurred while reading the configuration files")); + return do_git_config_sequence(opts, fn, data); } static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) @@ -1332,12 +1757,34 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) } } -static void git_config_check_init(void); - -void git_config(config_fn_t fn, void *data) +void read_early_config(config_fn_t cb, void *data) { - git_config_check_init(); - configset_iter(&the_config_set, fn, data); + struct config_options opts = {0}; + struct strbuf commondir = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + + opts.respect_includes = 1; + + if (have_git_dir()) { + opts.commondir = get_git_common_dir(); + opts.git_dir = get_git_dir(); + /* + * When setup_git_directory() was not yet asked to discover the + * GIT_DIR, we ask discover_git_directory() to figure out whether there + * is any repository config we should use (but unlike + * setup_git_directory_gently(), no global state is changed, most + * notably, the current working directory is still the same after the + * call). + */ + } else if (!discover_git_directory(&commondir, &gitdir)) { + opts.commondir = commondir.buf; + opts.git_dir = gitdir.buf; + } + + config_with_options(cb, data, NULL, &opts); + + strbuf_release(&commondir); + strbuf_release(&gitdir); } static struct config_set_element *configset_find_element(struct config_set *cs, const char *key) @@ -1386,16 +1833,16 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha l_item->value_index = e->value_list.nr - 1; if (!cf) - die("BUG: configset_add_value has no source"); + BUG("configset_add_value has no source"); if (cf->name) { kv_info->filename = strintern(cf->name); kv_info->linenr = cf->linenr; - kv_info->origin_type = strintern(cf->origin_type); + kv_info->origin_type = cf->origin_type; } else { /* for values read from `git_config_from_parameters()` */ kv_info->filename = NULL; kv_info->linenr = -1; - kv_info->origin_type = NULL; + kv_info->origin_type = CONFIG_ORIGIN_CMDLINE; } kv_info->scope = current_parsing_scope; si->util = kv_info; @@ -1403,15 +1850,20 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha return 0; } -static int config_set_element_cmp(const struct config_set_element *e1, - const struct config_set_element *e2, const void *unused) +static int config_set_element_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *unused_keydata) { + const struct config_set_element *e1 = entry; + const struct config_set_element *e2 = entry_or_key; + return strcmp(e1->key, e2->key); } void git_configset_init(struct config_set *cs) { - hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0); + hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0); cs->hash_initialized = 1; cs->list.nr = 0; cs->list.alloc = 0; @@ -1532,7 +1984,7 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de { const char *value; if (!git_configset_get_value(cs, key, &value)) { - *dest = git_config_maybe_bool(key, value); + *dest = git_parse_maybe_bool(value); if (*dest == -1) return -1; return 0; @@ -1549,89 +2001,231 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha return 1; } -static void git_config_check_init(void) +/* Functions use to read configuration from a repository */ +static void repo_read_config(struct repository *repo) { - if (the_config_set.hash_initialized) + struct config_options opts; + + opts.respect_includes = 1; + opts.commondir = repo->commondir; + opts.git_dir = repo->gitdir; + + if (!repo->config) + repo->config = xcalloc(1, sizeof(struct config_set)); + else + git_configset_clear(repo->config); + + git_configset_init(repo->config); + + if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0) + /* + * config_with_options() normally returns only + * zero, as most errors are fatal, and + * non-fatal potential errors are guarded by "if" + * statements that are entered only when no error is + * possible. + * + * If we ever encounter a non-fatal error, it means + * something went really wrong and we should stop + * immediately. + */ + die(_("unknown error occurred while reading the configuration files")); +} + +static void git_config_check_init(struct repository *repo) +{ + if (repo->config && repo->config->hash_initialized) return; - git_configset_init(&the_config_set); - git_config_raw(config_set_callback, &the_config_set); + repo_read_config(repo); } -void git_config_clear(void) +static void repo_config_clear(struct repository *repo) { - if (!the_config_set.hash_initialized) + if (!repo->config || !repo->config->hash_initialized) return; - git_configset_clear(&the_config_set); + git_configset_clear(repo->config); } -int git_config_get_value(const char *key, const char **value) +void repo_config(struct repository *repo, config_fn_t fn, void *data) { - git_config_check_init(); - return git_configset_get_value(&the_config_set, key, value); + git_config_check_init(repo); + configset_iter(repo->config, fn, data); } -const struct string_list *git_config_get_value_multi(const char *key) +int repo_config_get_value(struct repository *repo, + const char *key, const char **value) { - git_config_check_init(); - return git_configset_get_value_multi(&the_config_set, key); + git_config_check_init(repo); + return git_configset_get_value(repo->config, key, value); } -int git_config_get_string_const(const char *key, const char **dest) +const struct string_list *repo_config_get_value_multi(struct repository *repo, + const char *key) +{ + git_config_check_init(repo); + return git_configset_get_value_multi(repo->config, key); +} + +int repo_config_get_string_const(struct repository *repo, + const char *key, const char **dest) +{ + int ret; + git_config_check_init(repo); + ret = git_configset_get_string_const(repo->config, key, dest); + if (ret < 0) + git_die_config(key, NULL); + return ret; +} + +int repo_config_get_string(struct repository *repo, + const char *key, char **dest) +{ + git_config_check_init(repo); + return repo_config_get_string_const(repo, key, (const char **)dest); +} + +int repo_config_get_int(struct repository *repo, + const char *key, int *dest) +{ + git_config_check_init(repo); + return git_configset_get_int(repo->config, key, dest); +} + +int repo_config_get_ulong(struct repository *repo, + const char *key, unsigned long *dest) +{ + git_config_check_init(repo); + return git_configset_get_ulong(repo->config, key, dest); +} + +int repo_config_get_bool(struct repository *repo, + const char *key, int *dest) +{ + git_config_check_init(repo); + return git_configset_get_bool(repo->config, key, dest); +} + +int repo_config_get_bool_or_int(struct repository *repo, + const char *key, int *is_bool, int *dest) +{ + git_config_check_init(repo); + return git_configset_get_bool_or_int(repo->config, key, is_bool, dest); +} + +int repo_config_get_maybe_bool(struct repository *repo, + const char *key, int *dest) +{ + git_config_check_init(repo); + return git_configset_get_maybe_bool(repo->config, key, dest); +} + +int repo_config_get_pathname(struct repository *repo, + const char *key, const char **dest) { int ret; - git_config_check_init(); - ret = git_configset_get_string_const(&the_config_set, key, dest); + git_config_check_init(repo); + ret = git_configset_get_pathname(repo->config, key, dest); if (ret < 0) git_die_config(key, NULL); return ret; } +/* Functions used historically to read configuration from 'the_repository' */ +void git_config(config_fn_t fn, void *data) +{ + repo_config(the_repository, fn, data); +} + +void git_config_clear(void) +{ + repo_config_clear(the_repository); +} + +int git_config_get_value(const char *key, const char **value) +{ + return repo_config_get_value(the_repository, key, value); +} + +const struct string_list *git_config_get_value_multi(const char *key) +{ + return repo_config_get_value_multi(the_repository, key); +} + +int git_config_get_string_const(const char *key, const char **dest) +{ + return repo_config_get_string_const(the_repository, key, dest); +} + int git_config_get_string(const char *key, char **dest) { - git_config_check_init(); - return git_config_get_string_const(key, (const char **)dest); + return repo_config_get_string(the_repository, key, dest); } int git_config_get_int(const char *key, int *dest) { - git_config_check_init(); - return git_configset_get_int(&the_config_set, key, dest); + return repo_config_get_int(the_repository, key, dest); } int git_config_get_ulong(const char *key, unsigned long *dest) { - git_config_check_init(); - return git_configset_get_ulong(&the_config_set, key, dest); + return repo_config_get_ulong(the_repository, key, dest); } int git_config_get_bool(const char *key, int *dest) { - git_config_check_init(); - return git_configset_get_bool(&the_config_set, key, dest); + return repo_config_get_bool(the_repository, key, dest); } int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest) { - git_config_check_init(); - return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest); + return repo_config_get_bool_or_int(the_repository, key, is_bool, dest); } int git_config_get_maybe_bool(const char *key, int *dest) { - git_config_check_init(); - return git_configset_get_maybe_bool(&the_config_set, key, dest); + return repo_config_get_maybe_bool(the_repository, key, dest); } int git_config_get_pathname(const char *key, const char **dest) { - int ret; - git_config_check_init(); - ret = git_configset_get_pathname(&the_config_set, key, dest); - if (ret < 0) - git_die_config(key, NULL); + return repo_config_get_pathname(the_repository, key, dest); +} + +int git_config_get_expiry(const char *key, const char **output) +{ + int ret = git_config_get_string_const(key, output); + if (ret) + return ret; + if (strcmp(*output, "now")) { + timestamp_t now = approxidate("now"); + if (approxidate(*output) >= now) + git_die_config(key, _("Invalid %s: '%s'"), key, *output); + } return ret; } +int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now) +{ + char *expiry_string; + intmax_t days; + timestamp_t when; + + if (git_config_get_string(key, &expiry_string)) + return 1; /* no such thing */ + + if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) { + const int scale = 86400; + *expiry = now - days * scale; + return 0; + } + + if (!parse_expiry_date(expiry_string, &when)) { + *expiry = when; + return 0; + } + return -1; /* thing exists but cannot be parsed */ +} + int git_config_get_untracked_cache(void) { int val = -1; @@ -1648,14 +2242,53 @@ int git_config_get_untracked_cache(void) if (!strcasecmp(v, "keep")) return -1; - error("unknown core.untrackedCache value '%s'; " - "using 'keep' default value", v); + error(_("unknown core.untrackedCache value '%s'; " + "using 'keep' default value"), v); return -1; } return -1; /* default value */ } +int git_config_get_split_index(void) +{ + int val; + + if (!git_config_get_maybe_bool("core.splitindex", &val)) + return val; + + return -1; /* default value */ +} + +int git_config_get_max_percent_split_change(void) +{ + int val = -1; + + if (!git_config_get_int("splitindex.maxpercentchange", &val)) { + if (0 <= val && val <= 100) + return val; + + return error(_("splitIndex.maxPercentChange value '%d' " + "should be between 0 and 100"), val); + } + + return -1; /* default value */ +} + +int git_config_get_fsmonitor(void) +{ + if (git_config_get_pathname("core.fsmonitor", &core_fsmonitor)) + core_fsmonitor = getenv("GIT_FSMONITOR_TEST"); + + if (core_fsmonitor && !*core_fsmonitor) + core_fsmonitor = NULL; + + if (core_fsmonitor) + return 1; + + return 0; +} + NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr) { @@ -1687,136 +2320,170 @@ void git_die_config(const char *key, const char *err, ...) * Find all the stuff for git_config_set() below. */ -static struct { +struct config_store_data { int baselen; char *key; int do_not_match; regex_t *value_regex; int multi_replace; - size_t *offset; - unsigned int offset_alloc; - enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state; - int seen; -} store; + struct { + size_t begin, end; + enum config_event_t type; + int is_keys_section; + } *parsed; + unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc; + unsigned int key_seen:1, section_seen:1, is_keys_section:1; +}; -static int matches(const char *key, const char *value) +static void config_store_data_clear(struct config_store_data *store) { - if (strcmp(key, store.key)) + free(store->key); + if (store->value_regex != NULL && + store->value_regex != CONFIG_REGEX_NONE) { + regfree(store->value_regex); + free(store->value_regex); + } + free(store->parsed); + free(store->seen); + memset(store, 0, sizeof(*store)); +} + +static int matches(const char *key, const char *value, + const struct config_store_data *store) +{ + if (strcmp(key, store->key)) return 0; /* not ours */ - if (!store.value_regex) + if (!store->value_regex) return 1; /* always matches */ - if (store.value_regex == CONFIG_REGEX_NONE) + if (store->value_regex == CONFIG_REGEX_NONE) return 0; /* never matches */ - return store.do_not_match ^ - (value && !regexec(store.value_regex, value, 0, NULL, 0)); + return store->do_not_match ^ + (value && !regexec(store->value_regex, value, 0, NULL, 0)); +} + +static int store_aux_event(enum config_event_t type, + size_t begin, size_t end, void *data) +{ + struct config_store_data *store = data; + + ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc); + store->parsed[store->parsed_nr].begin = begin; + store->parsed[store->parsed_nr].end = end; + store->parsed[store->parsed_nr].type = type; + + if (type == CONFIG_EVENT_SECTION) { + int (*cmpfn)(const char *, const char *, size_t); + + if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.') + return error(_("invalid section name '%s'"), cf->var.buf); + + if (cf->subsection_case_sensitive) + cmpfn = strncasecmp; + else + cmpfn = strncmp; + + /* Is this the section we were looking for? */ + store->is_keys_section = + store->parsed[store->parsed_nr].is_keys_section = + cf->var.len - 1 == store->baselen && + !cmpfn(cf->var.buf, store->key, store->baselen); + if (store->is_keys_section) { + store->section_seen = 1; + ALLOC_GROW(store->seen, store->seen_nr + 1, + store->seen_alloc); + store->seen[store->seen_nr] = store->parsed_nr; + } + } + + store->parsed_nr++; + + return 0; } static int store_aux(const char *key, const char *value, void *cb) { - const char *ep; - size_t section_len; + struct config_store_data *store = cb; - switch (store.state) { - case KEY_SEEN: - if (matches(key, value)) { - if (store.seen == 1 && store.multi_replace == 0) { + if (store->key_seen) { + if (matches(key, value, store)) { + if (store->seen_nr == 1 && store->multi_replace == 0) { warning(_("%s has multiple values"), key); } - ALLOC_GROW(store.offset, store.seen + 1, - store.offset_alloc); + ALLOC_GROW(store->seen, store->seen_nr + 1, + store->seen_alloc); - store.offset[store.seen] = cf->do_ftell(cf); - store.seen++; + store->seen[store->seen_nr] = store->parsed_nr; + store->seen_nr++; } - break; - case SECTION_SEEN: + } else if (store->is_keys_section) { /* - * What we are looking for is in store.key (both - * section and var), and its section part is baselen - * long. We found key (again, both section and var). - * We would want to know if this key is in the same - * section as what we are looking for. We already - * know we are in the same section as what should - * hold store.key. + * Do not increment matches yet: this may not be a match, but we + * are in the desired section. */ - ep = strrchr(key, '.'); - section_len = ep - key; + ALLOC_GROW(store->seen, store->seen_nr + 1, store->seen_alloc); + store->seen[store->seen_nr] = store->parsed_nr; + store->section_seen = 1; - if ((section_len != store.baselen) || - memcmp(key, store.key, section_len+1)) { - store.state = SECTION_END_SEEN; - break; - } - - /* - * Do not increment matches: this is no match, but we - * just made sure we are in the desired section. - */ - ALLOC_GROW(store.offset, store.seen + 1, - store.offset_alloc); - store.offset[store.seen] = cf->do_ftell(cf); - /* fallthru */ - case SECTION_END_SEEN: - case START: - if (matches(key, value)) { - ALLOC_GROW(store.offset, store.seen + 1, - store.offset_alloc); - store.offset[store.seen] = cf->do_ftell(cf); - store.state = KEY_SEEN; - store.seen++; - } else { - if (strrchr(key, '.') - key == store.baselen && - !strncmp(key, store.key, store.baselen)) { - store.state = SECTION_SEEN; - ALLOC_GROW(store.offset, - store.seen + 1, - store.offset_alloc); - store.offset[store.seen] = cf->do_ftell(cf); - } + if (matches(key, value, store)) { + store->seen_nr++; + store->key_seen = 1; } } + return 0; } static int write_error(const char *filename) { - error("failed to write new configuration file %s", filename); + error(_("failed to write new configuration file %s"), filename); /* Same error code as "failed to rename". */ return 4; } -static int store_write_section(int fd, const char *key) +static struct strbuf store_create_section(const char *key, + const struct config_store_data *store) { const char *dot; - int i, success; + int i; struct strbuf sb = STRBUF_INIT; - dot = memchr(key, '.', store.baselen); + dot = memchr(key, '.', store->baselen); if (dot) { strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key); - for (i = dot - key + 1; i < store.baselen; i++) { + for (i = dot - key + 1; i < store->baselen; i++) { if (key[i] == '"' || key[i] == '\\') strbuf_addch(&sb, '\\'); strbuf_addch(&sb, key[i]); } strbuf_addstr(&sb, "\"]\n"); } else { - strbuf_addf(&sb, "[%.*s]\n", store.baselen, key); + strbuf_addf(&sb, "[%.*s]\n", store->baselen, key); } - success = write_in_full(fd, sb.buf, sb.len) == sb.len; + return sb; +} + +static ssize_t write_section(int fd, const char *key, + const struct config_store_data *store) +{ + struct strbuf sb = store_create_section(key, store); + ssize_t ret; + + ret = write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); - return success; + return ret; } -static int store_write_pair(int fd, const char *key, const char *value) +static ssize_t write_pair(int fd, const char *key, const char *value, + const struct config_store_data *store) { - int i, success; - int length = strlen(key + store.baselen + 1); + int i; + ssize_t ret; + int length = strlen(key + store->baselen + 1); const char *quote = ""; struct strbuf sb = STRBUF_INIT; @@ -1836,7 +2503,7 @@ static int store_write_pair(int fd, const char *key, const char *value) quote = "\""; strbuf_addf(&sb, "\t%.*s = %s", - length, key + store.baselen + 1, quote); + length, key + store->baselen + 1, quote); for (i = 0; value[i]; i++) switch (value[i]) { @@ -1849,42 +2516,98 @@ static int store_write_pair(int fd, const char *key, const char *value) case '"': case '\\': strbuf_addch(&sb, '\\'); + /* fallthrough */ default: strbuf_addch(&sb, value[i]); break; } strbuf_addf(&sb, "%s\n", quote); - success = write_in_full(fd, sb.buf, sb.len) == sb.len; + ret = write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); - return success; + return ret; } -static ssize_t find_beginning_of_line(const char *contents, size_t size, - size_t offset_, int *found_bracket) +/* + * If we are about to unset the last key(s) in a section, and if there are + * no comments surrounding (or included in) the section, we will want to + * extend begin/end to remove the entire section. + * + * Note: the parameter `seen_ptr` points to the index into the store.seen + * array. * This index may be incremented if a section has more than one + * entry (which all are to be removed). + */ +static void maybe_remove_section(struct config_store_data *store, + const char *contents, + size_t *begin_offset, size_t *end_offset, + int *seen_ptr) { - size_t equal_offset = size, bracket_offset = size; - ssize_t offset; + size_t begin; + int i, seen, section_seen = 0; -contline: - for (offset = offset_-2; offset > 0 - && contents[offset] != '\n'; offset--) - switch (contents[offset]) { - case '=': equal_offset = offset; break; - case ']': bracket_offset = offset; break; + /* + * First, ensure that this is the first key, and that there are no + * comments before the entry nor before the section header. + */ + seen = *seen_ptr; + for (i = store->seen[seen]; i > 0; i--) { + enum config_event_t type = store->parsed[i - 1].type; + + if (type == CONFIG_EVENT_COMMENT) + /* There is a comment before this entry or section */ + return; + if (type == CONFIG_EVENT_ENTRY) { + if (!section_seen) + /* This is not the section's first entry. */ + return; + /* We encountered no comment before the section. */ + break; + } + if (type == CONFIG_EVENT_SECTION) { + if (!store->parsed[i - 1].is_keys_section) + break; + section_seen = 1; } - if (offset > 0 && contents[offset-1] == '\\') { - offset_ = offset; - goto contline; } - if (bracket_offset < equal_offset) { - *found_bracket = 1; - offset = bracket_offset+1; - } else - offset++; + begin = store->parsed[i].begin; - return offset; + /* + * Next, make sure that we are removing he last key(s) in the section, + * and that there are no comments that are possibly about the current + * section. + */ + for (i = store->seen[seen] + 1; i < store->parsed_nr; i++) { + enum config_event_t type = store->parsed[i].type; + + if (type == CONFIG_EVENT_COMMENT) + return; + if (type == CONFIG_EVENT_SECTION) { + if (store->parsed[i].is_keys_section) + continue; + break; + } + if (type == CONFIG_EVENT_ENTRY) { + if (++seen < store->seen_nr && + i == store->seen[seen]) + /* We want to remove this entry, too */ + continue; + /* There is another entry in this section. */ + return; + } + } + + /* + * We are really removing the last entry/entries from this section, and + * there are no enclosed or surrounding comments. Remove the entire, + * now-empty section. + */ + *seen_ptr = seen; + *begin_offset = begin; + if (i < store->parsed_nr) + *end_offset = store->parsed[i].begin; + else + *end_offset = store->parsed[store->parsed_nr - 1].end; } int git_config_set_in_file_gently(const char *config_filename, @@ -1910,93 +2633,6 @@ void git_config_set(const char *key, const char *value) } /* - * Auxiliary function to sanity-check and split the key into the section - * identifier and variable name. - * - * Returns 0 on success, -1 when there is an invalid character in the key and - * -2 if there is no section name in the key. - * - * store_key - pointer to char* which will hold a copy of the key with - * lowercase section and variable name - * baselen - pointer to int which will hold the length of the - * section + subsection part, can be NULL - */ -static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet) -{ - int i, dot, baselen; - const char *last_dot = strrchr(key, '.'); - - /* - * Since "key" actually contains the section name and the real - * key name separated by a dot, we have to know where the dot is. - */ - - if (last_dot == NULL || last_dot == key) { - if (!quiet) - error("key does not contain a section: %s", key); - return -CONFIG_NO_SECTION_OR_NAME; - } - - if (!last_dot[1]) { - if (!quiet) - error("key does not contain variable name: %s", key); - return -CONFIG_NO_SECTION_OR_NAME; - } - - baselen = last_dot - key; - if (baselen_) - *baselen_ = baselen; - - /* - * Validate the key and while at it, lower case it for matching. - */ - if (store_key) - *store_key = xmallocz(strlen(key)); - - dot = 0; - for (i = 0; key[i]; i++) { - unsigned char c = key[i]; - if (c == '.') - dot = 1; - /* Leave the extended basename untouched.. */ - if (!dot || i > baselen) { - if (!iskeychar(c) || - (i == baselen + 1 && !isalpha(c))) { - if (!quiet) - error("invalid key: %s", key); - goto out_free_ret_1; - } - c = tolower(c); - } else if (c == '\n') { - if (!quiet) - error("invalid key (newline): %s", key); - goto out_free_ret_1; - } - if (store_key) - (*store_key)[i] = c; - } - - return 0; - -out_free_ret_1: - if (store_key) { - free(*store_key); - *store_key = NULL; - } - return -CONFIG_INVALID_KEY; -} - -int git_config_parse_key(const char *key, char **store_key, int *baselen) -{ - return git_config_parse_key_1(key, store_key, baselen, 0); -} - -int git_config_key_is_valid(const char *key) -{ - return !git_config_parse_key_1(key, NULL, NULL, 1); -} - -/* * If value==NULL, unset in (remove from) config, * if value_regex!=NULL, disregard key/value pairs where value does not match. * if value_regex==CONFIG_REGEX_NONE, do not match any existing values @@ -2028,10 +2664,13 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, { int fd = -1, in_fd = -1; int ret; - struct lock_file *lock = NULL; + struct lock_file lock = LOCK_INIT; char *filename_buf = NULL; char *contents = NULL; size_t contents_sz; + struct config_store_data store; + + memset(&store, 0, sizeof(store)); /* parse-key returns negative; flip the sign to feed exit(3) */ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); @@ -2047,11 +2686,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, * The lock serves a purpose in addition to locking: the new * contents of .git/config will be written into it. */ - lock = xcalloc(1, sizeof(struct lock_file)); - fd = hold_lock_file_for_update(lock, config_filename, 0); + fd = hold_lock_file_for_update(&lock, config_filename, 0); if (fd < 0) { - error_errno("could not lock config file %s", config_filename); - free(store.key); + error_errno(_("could not lock config file %s"), config_filename); ret = CONFIG_NO_LOCK; goto out_free; } @@ -2061,10 +2698,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, */ in_fd = open(config_filename, O_RDONLY); if ( in_fd < 0 ) { - free(store.key); - if ( ENOENT != errno ) { - error_errno("opening %s", config_filename); + error_errno(_("opening %s"), config_filename); ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */ goto out_free; } @@ -2074,14 +2709,16 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, goto out_free; } - store.key = (char *)key; - if (!store_write_section(fd, key) || - !store_write_pair(fd, key, value)) + free(store.key); + store.key = xstrdup(key); + if (write_section(fd, key, &store) < 0 || + write_pair(fd, key, value, &store) < 0) goto write_err_out; } else { struct stat st; size_t copy_begin, copy_end; int i, new_line = 0; + struct config_options opts; if (value_regex == NULL) store.value_regex = NULL; @@ -2097,58 +2734,56 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, store.value_regex = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(store.value_regex, value_regex, REG_EXTENDED)) { - error("invalid pattern: %s", value_regex); - free(store.value_regex); + error(_("invalid pattern: %s"), value_regex); + FREE_AND_NULL(store.value_regex); ret = CONFIG_INVALID_PATTERN; goto out_free; } } - ALLOC_GROW(store.offset, 1, store.offset_alloc); - store.offset[0] = 0; - store.state = START; - store.seen = 0; + ALLOC_GROW(store.parsed, 1, store.parsed_alloc); + store.parsed[0].end = 0; + + memset(&opts, 0, sizeof(opts)); + opts.event_fn = store_aux_event; + opts.event_fn_data = &store; /* - * After this, store.offset will contain the *end* offset - * of the last match, or remain at 0 if no match was found. + * After this, store.parsed will contain offsets of all the + * parsed elements, and store.seen will contain a list of + * matches, as indices into store.parsed. + * * As a side effect, we make sure to transform only a valid * existing config file. */ - if (git_config_from_file(store_aux, config_filename, NULL)) { - error("invalid config file %s", config_filename); - free(store.key); - if (store.value_regex != NULL && - store.value_regex != CONFIG_REGEX_NONE) { - regfree(store.value_regex); - free(store.value_regex); - } + if (git_config_from_file_with_options(store_aux, + config_filename, + &store, &opts)) { + error(_("invalid config file %s"), config_filename); ret = CONFIG_INVALID_FILE; goto out_free; } - free(store.key); - if (store.value_regex != NULL && - store.value_regex != CONFIG_REGEX_NONE) { - regfree(store.value_regex); - free(store.value_regex); - } - /* if nothing to unset, or too many matches, error out */ - if ((store.seen == 0 && value == NULL) || - (store.seen > 1 && multi_replace == 0)) { + if ((store.seen_nr == 0 && value == NULL) || + (store.seen_nr > 1 && multi_replace == 0)) { ret = CONFIG_NOTHING_SET; goto out_free; } - fstat(in_fd, &st); + if (fstat(in_fd, &st) == -1) { + error_errno(_("fstat on %s failed"), config_filename); + ret = CONFIG_INVALID_FILE; + goto out_free; + } + contents_sz = xsize_t(st.st_size); contents = xmmap_gently(NULL, contents_sz, PROT_READ, MAP_PRIVATE, in_fd, 0); if (contents == MAP_FAILED) { if (errno == ENODEV && S_ISDIR(st.st_mode)) errno = EISDIR; - error_errno("unable to mmap '%s'", config_filename); + error_errno(_("unable to mmap '%s'"), config_filename); ret = CONFIG_INVALID_FILE; contents = NULL; goto out_free; @@ -2156,24 +2791,55 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, close(in_fd); in_fd = -1; - if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { - error_errno("chmod on %s failed", get_lock_file_path(lock)); + if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) { + error_errno(_("chmod on %s failed"), get_lock_file_path(&lock)); ret = CONFIG_NO_WRITE; goto out_free; } - if (store.seen == 0) - store.seen = 1; + if (store.seen_nr == 0) { + if (!store.seen_alloc) { + /* Did not see key nor section */ + ALLOC_GROW(store.seen, 1, store.seen_alloc); + store.seen[0] = store.parsed_nr + - !!store.parsed_nr; + } + store.seen_nr = 1; + } - for (i = 0, copy_begin = 0; i < store.seen; i++) { - if (store.offset[i] == 0) { - store.offset[i] = copy_end = contents_sz; - } else if (store.state != KEY_SEEN) { - copy_end = store.offset[i]; - } else - copy_end = find_beginning_of_line( - contents, contents_sz, - store.offset[i]-2, &new_line); + for (i = 0, copy_begin = 0; i < store.seen_nr; i++) { + size_t replace_end; + int j = store.seen[i]; + + new_line = 0; + if (!store.key_seen) { + copy_end = store.parsed[j].end; + /* include '\n' when copying section header */ + if (copy_end > 0 && copy_end < contents_sz && + contents[copy_end - 1] != '\n' && + contents[copy_end] == '\n') + copy_end++; + replace_end = copy_end; + } else { + replace_end = store.parsed[j].end; + copy_end = store.parsed[j].begin; + if (!value) + maybe_remove_section(&store, contents, + ©_end, + &replace_end, &i); + /* + * Swallow preceding white-space on the same + * line. + */ + while (copy_end > 0 ) { + char c = contents[copy_end - 1]; + + if (isspace(c) && c != '\n') + copy_end--; + else + break; + } + } if (copy_end > 0 && contents[copy_end-1] != '\n') new_line = 1; @@ -2181,68 +2847,58 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, /* write the first part of the config */ if (copy_end > copy_begin) { if (write_in_full(fd, contents + copy_begin, - copy_end - copy_begin) < - copy_end - copy_begin) + copy_end - copy_begin) < 0) goto write_err_out; if (new_line && - write_str_in_full(fd, "\n") != 1) + write_str_in_full(fd, "\n") < 0) goto write_err_out; } - copy_begin = store.offset[i]; + copy_begin = replace_end; } /* write the pair (value == NULL means unset) */ if (value != NULL) { - if (store.state == START) { - if (!store_write_section(fd, key)) + if (!store.section_seen) { + if (write_section(fd, key, &store) < 0) goto write_err_out; } - if (!store_write_pair(fd, key, value)) + if (write_pair(fd, key, value, &store) < 0) goto write_err_out; } /* write the rest of the config */ if (copy_begin < contents_sz) if (write_in_full(fd, contents + copy_begin, - contents_sz - copy_begin) < - contents_sz - copy_begin) + contents_sz - copy_begin) < 0) goto write_err_out; munmap(contents, contents_sz); contents = NULL; } - if (commit_lock_file(lock) < 0) { - error_errno("could not write config file %s", config_filename); + if (commit_lock_file(&lock) < 0) { + error_errno(_("could not write config file %s"), config_filename); ret = CONFIG_NO_WRITE; - lock = NULL; goto out_free; } - /* - * lock is committed, so don't try to roll it back below. - * NOTE: Since lockfile.c keeps a linked list of all created - * lock_file structures, it isn't safe to free(lock). It's - * better to just leave it hanging around. - */ - lock = NULL; ret = 0; /* Invalidate the config cache */ git_config_clear(); out_free: - if (lock) - rollback_lock_file(lock); + rollback_lock_file(&lock); free(filename_buf); if (contents) munmap(contents, contents_sz); if (in_fd >= 0) close(in_fd); + config_store_data_clear(&store); return ret; write_err_out: - ret = write_error(get_lock_file_path(lock)); + ret = write_error(get_lock_file_path(&lock)); goto out_free; } @@ -2330,54 +2986,82 @@ static int section_name_is_ok(const char *name) } /* if new_name == NULL, the section is removed instead */ -int git_config_rename_section_in_file(const char *config_filename, - const char *old_name, const char *new_name) +static int git_config_copy_or_rename_section_in_file(const char *config_filename, + const char *old_name, + const char *new_name, int copy) { int ret = 0, remove = 0; char *filename_buf = NULL; - struct lock_file *lock; + struct lock_file lock = LOCK_INIT; int out_fd; char buf[1024]; - FILE *config_file; + FILE *config_file = NULL; struct stat st; + struct strbuf copystr = STRBUF_INIT; + struct config_store_data store; + + memset(&store, 0, sizeof(store)); if (new_name && !section_name_is_ok(new_name)) { - ret = error("invalid section name: %s", new_name); - goto out; + ret = error(_("invalid section name: %s"), new_name); + goto out_no_rollback; } if (!config_filename) config_filename = filename_buf = git_pathdup("config"); - lock = xcalloc(1, sizeof(struct lock_file)); - 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) { - ret = error("could not lock config file %s", config_filename); + ret = error(_("could not lock config file %s"), config_filename); goto out; } if (!(config_file = fopen(config_filename, "rb"))) { + ret = warn_on_fopen_errors(config_filename); + if (ret) + goto out; /* no config file means nothing to rename, no error */ - goto unlock_and_out; + goto commit_and_out; } - fstat(fileno(config_file), &st); + if (fstat(fileno(config_file), &st) == -1) { + ret = error_errno(_("fstat on %s failed"), config_filename); + goto out; + } - if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { - ret = error_errno("chmod on %s failed", - get_lock_file_path(lock)); + if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) { + ret = error_errno(_("chmod on %s failed"), + get_lock_file_path(&lock)); goto out; } while (fgets(buf, sizeof(buf), config_file)) { int i; int length; + int is_section = 0; char *output = buf; for (i = 0; buf[i] && isspace(buf[i]); i++) ; /* do nothing */ if (buf[i] == '[') { /* it's a section */ - int offset = section_name_match(&buf[i], old_name); + int offset; + is_section = 1; + + /* + * When encountering a new section under -c we + * need to flush out any section we're already + * coping and begin anew. There might be + * multiple [branch "$name"] sections. + */ + if (copystr.len > 0) { + if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) { + ret = write_error(get_lock_file_path(&lock)); + goto out; + } + strbuf_reset(©str); + } + + offset = section_name_match(&buf[i], old_name); if (offset > 0) { ret++; if (new_name == NULL) { @@ -2385,25 +3069,29 @@ int git_config_rename_section_in_file(const char *config_filename, continue; } store.baselen = strlen(new_name); - if (!store_write_section(out_fd, new_name)) { - ret = write_error(get_lock_file_path(lock)); - goto out; - } - /* - * We wrote out the new section, with - * a newline, now skip the old - * section's length - */ - output += offset + i; - if (strlen(output) > 0) { + if (!copy) { + if (write_section(out_fd, new_name, &store) < 0) { + ret = write_error(get_lock_file_path(&lock)); + goto out; + } /* - * More content means there's - * a declaration to put on the - * next line; indent with a - * tab + * We wrote out the new section, with + * a newline, now skip the old + * section's length */ - output -= 1; - output[0] = '\t'; + output += offset + i; + if (strlen(output) > 0) { + /* + * More content means there's + * a declaration to put on the + * next line; indent with a + * tab + */ + output -= 1; + output[0] = '\t'; + } + } else { + copystr = store_create_section(new_name, &store); } } remove = 0; @@ -2411,26 +3099,70 @@ int git_config_rename_section_in_file(const char *config_filename, if (remove) continue; length = strlen(output); - if (write_in_full(out_fd, output, length) != length) { - ret = write_error(get_lock_file_path(lock)); + + if (!is_section && copystr.len > 0) { + strbuf_add(©str, output, length); + } + + if (write_in_full(out_fd, output, length) < 0) { + ret = write_error(get_lock_file_path(&lock)); goto out; } } + + /* + * Copy a trailing section at the end of the config, won't be + * flushed by the usual "flush because we have a new section + * logic in the loop above. + */ + if (copystr.len > 0) { + if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) { + ret = write_error(get_lock_file_path(&lock)); + goto out; + } + strbuf_reset(©str); + } + fclose(config_file); -unlock_and_out: - if (commit_lock_file(lock) < 0) - ret = error_errno("could not write config file %s", + config_file = NULL; +commit_and_out: + if (commit_lock_file(&lock) < 0) + ret = error_errno(_("could not write config file %s"), config_filename); out: + if (config_file) + fclose(config_file); + rollback_lock_file(&lock); +out_no_rollback: free(filename_buf); + config_store_data_clear(&store); return ret; } +int git_config_rename_section_in_file(const char *config_filename, + const char *old_name, const char *new_name) +{ + return git_config_copy_or_rename_section_in_file(config_filename, + old_name, new_name, 0); +} + 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); } +int git_config_copy_section_in_file(const char *config_filename, + const char *old_name, const char *new_name) +{ + return git_config_copy_or_rename_section_in_file(config_filename, + old_name, new_name, 1); +} + +int git_config_copy_section(const char *old_name, const char *new_name) +{ + return git_config_copy_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"). @@ -2438,7 +3170,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) #undef config_error_nonbool int config_error_nonbool(const char *var) { - return error("missing value for '%s'", var); + return error(_("missing value for '%s'"), var); } int parse_config_key(const char *var, @@ -2446,11 +3178,10 @@ int parse_config_key(const char *var, const char **subsection, int *subsection_len, const char **key) { - int section_len = strlen(section); const char *dot; /* Does it start with "section." ? */ - if (!starts_with(var, section) || var[section_len] != '.') + if (!skip_prefix(var, section, &var) || *var != '.') return -1; /* @@ -2462,12 +3193,16 @@ int parse_config_key(const char *var, *key = dot + 1; /* Did we have a subsection at all? */ - if (dot == var + section_len) { - *subsection = NULL; - *subsection_len = 0; + if (dot == var) { + if (subsection) { + *subsection = NULL; + *subsection_len = 0; + } } else { - *subsection = var + section_len + 1; + if (!subsection) + return -1; + *subsection = var + 1; *subsection_len = dot - *subsection; } @@ -2476,14 +3211,28 @@ int parse_config_key(const char *var, const char *current_config_origin_type(void) { - const char *type; + int type; if (current_config_kvi) type = current_config_kvi->origin_type; else if(cf) type = cf->origin_type; else - die("BUG: current_config_origin_type called outside config callback"); - return type ? type : "command line"; + BUG("current_config_origin_type called outside config callback"); + + switch (type) { + case CONFIG_ORIGIN_BLOB: + return "blob"; + case CONFIG_ORIGIN_FILE: + return "file"; + case CONFIG_ORIGIN_STDIN: + return "standard input"; + case CONFIG_ORIGIN_SUBMODULE_BLOB: + return "submodule-blob"; + case CONFIG_ORIGIN_CMDLINE: + return "command line"; + default: + BUG("unknown config origin type"); + } } const char *current_config_name(void) @@ -2494,7 +3243,7 @@ const char *current_config_name(void) else if (cf) name = cf->name; else - die("BUG: current_config_name called outside config callback"); + BUG("current_config_name called outside config callback"); return name ? name : ""; } @@ -2505,3 +3254,16 @@ enum config_scope current_config_scope(void) else return current_parsing_scope; } + +int lookup_config(const char **mapping, int nr_mapping, const char *var) +{ + int i; + + for (i = 0; i < nr_mapping; i++) { + const char *name = mapping[i]; + + if (name && !strcasecmp(var, name)) + return i; + } + return -1; +} |