diff options
Diffstat (limited to 'config.c')
-rw-r--r-- | config.c | 1234 |
1 files changed, 1024 insertions, 210 deletions
@@ -6,32 +6,155 @@ * */ #include "cache.h" +#include "lockfile.h" #include "exec_cmd.h" #include "strbuf.h" #include "quote.h" - -#define MAXNAME (256) - -typedef struct config_file { - struct config_file *prev; - FILE *f; +#include "hashmap.h" +#include "string-list.h" +#include "utf8.h" + +struct config_source { + struct config_source *prev; + union { + FILE *file; + struct config_buf { + const char *buf; + size_t len; + size_t pos; + } buf; + } u; const char *name; + const char *path; + int die_on_error; int linenr; int eof; struct strbuf value; - char var[MAXNAME]; -} config_file; + struct strbuf var; -static config_file *cf; + int (*do_fgetc)(struct config_source *c); + int (*do_ungetc)(int c, struct config_source *conf); + long (*do_ftell)(struct config_source *c); +}; + +static struct config_source *cf; static int zlib_compression_seen; -const char *config_exclusive_filename = NULL; +/* + * 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); +} + +static int config_file_ungetc(int c, struct config_source *conf) +{ + return ungetc(c, conf->u.file); +} + +static long config_file_ftell(struct config_source *conf) +{ + return ftell(conf->u.file); +} + + +static int config_buf_fgetc(struct config_source *conf) +{ + if (conf->u.buf.pos < conf->u.buf.len) + return conf->u.buf.buf[conf->u.buf.pos++]; + + return EOF; +} + +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"); + return c; + } + + return EOF; +} + +static long config_buf_ftell(struct config_source *conf) +{ + return conf->u.buf.pos; +} + +#define MAX_INCLUDE_DEPTH 10 +static const char include_depth_advice[] = +"exceeded maximum include depth (%d) while including\n" +" %s\n" +"from\n" +" %s\n" +"Do you have circular includes?"; +static int handle_path_include(const char *path, struct config_include_data *inc) +{ + int ret = 0; + struct strbuf buf = STRBUF_INIT; + char *expanded; + + if (!path) + return config_error_nonbool("include.path"); + + expanded = expand_user_path(path); + if (!expanded) + return error("Could not expand include path '%s'", path); + path = expanded; + + /* + * Use an absolute path as-is, but interpret relative paths + * based on the including config file. + */ + if (!is_absolute_path(path)) { + char *slash; + + if (!cf || !cf->path) + return error("relative config includes must come from files"); -static void lowercase(char *p) + slash = find_last_dir_sep(cf->path); + if (slash) + strbuf_add(&buf, cf->path, slash - cf->path + 1); + strbuf_addstr(&buf, path); + path = buf.buf; + } + + if (!access_or_die(path, R_OK, 0)) { + if (++inc->depth > MAX_INCLUDE_DEPTH) + die(include_depth_advice, MAX_INCLUDE_DEPTH, path, + cf && cf->name ? cf->name : "the command line"); + ret = git_config_from_file(git_config_include, path, inc); + inc->depth--; + } + strbuf_release(&buf); + free(expanded); + return ret; +} + +int git_config_include(const char *var, const char *value, void *data) { - for (; *p; p++) - *p = tolower(*p); + struct config_include_data *inc = data; + int ret; + + /* + * Pass along all values, including "include" directives; this makes it + * possible to query information on the includes themselves. + */ + ret = inc->fn(var, value, inc->data); + if (ret < 0) + return ret; + + if (!strcmp(var, "include.path")) + ret = handle_path_include(value, inc); + return ret; } void git_config_push_parameter(const char *text) @@ -50,19 +173,27 @@ void git_config_push_parameter(const char *text) int git_config_parse_parameter(const char *text, config_fn_t fn, void *data) { + const char *value; struct strbuf **pair; + pair = strbuf_split_str(text, '=', 2); if (!pair[0]) return error("bogus config parameter: %s", text); - if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') + + if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') { strbuf_setlen(pair[0], pair[0]->len - 1); + value = pair[1] ? pair[1]->buf : ""; + } else { + value = NULL; + } + strbuf_trim(pair[0]); if (!pair[0]->len) { strbuf_list_free(pair); return error("bogus config parameter: %s", text); } - lowercase(pair[0]->buf); - if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) { + strbuf_tolower(pair[0]); + if (fn(pair[0]->buf, value, data) < 0) { strbuf_list_free(pair); return -1; } @@ -103,27 +234,24 @@ int git_config_from_parameters(config_fn_t fn, void *data) static int get_next_char(void) { - int c; - FILE *f; - - c = '\n'; - if (cf && ((f = cf->f) != NULL)) { - c = fgetc(f); - if (c == '\r') { - /* DOS like systems */ - c = fgetc(f); - if (c != '\n') { - ungetc(c, f); - c = '\r'; - } - } - if (c == '\n') - cf->linenr++; - if (c == EOF) { - cf->eof = 1; - c = '\n'; + int c = cf->do_fgetc(cf); + + if (c == '\r') { + /* DOS like systems */ + c = cf->do_fgetc(cf); + if (c != '\n') { + if (c != EOF) + cf->do_ungetc(c, cf); + c = '\r'; } } + if (c == '\n') + cf->linenr++; + if (c == EOF) { + cf->eof = 1; + cf->linenr++; + c = '\n'; + } return c; } @@ -193,10 +321,11 @@ static inline int iskeychar(int c) return isalnum(c) || c == '-'; } -static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) +static int get_value(config_fn_t fn, void *data, struct strbuf *name) { int c; char *value; + int ret; /* Get the full name */ for (;;) { @@ -205,11 +334,9 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) break; if (!iskeychar(c)) break; - name[len++] = tolower(c); - if (len >= MAXNAME) - return -1; + strbuf_addch(name, tolower(c)); } - name[len] = 0; + while (c == ' ' || c == '\t') c = get_next_char(); @@ -221,10 +348,18 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) if (!value) return -1; } - return fn(name, value, data); + /* + * We already consumed the \n, but we need linenr to point to + * the line we just parsed during the call to fn to get + * accurate line number in error messages. + */ + cf->linenr--; + ret = fn(name->buf, value, data); + cf->linenr++; + return ret; } -static int get_extended_base_var(char *name, int baselen, int c) +static int get_extended_base_var(struct strbuf *name, int c) { do { if (c == '\n') @@ -235,7 +370,7 @@ static int get_extended_base_var(char *name, int baselen, int c) /* We require the format to be '[base "extension"]' */ if (c != '"') return -1; - name[baselen++] = '.'; + strbuf_addch(name, '.'); for (;;) { int c = get_next_char(); @@ -248,49 +383,42 @@ static int get_extended_base_var(char *name, int baselen, int c) if (c == '\n') goto error_incomplete_line; } - name[baselen++] = c; - if (baselen > MAXNAME / 2) - return -1; + strbuf_addch(name, c); } /* Final ']' */ if (get_next_char() != ']') return -1; - return baselen; + return 0; error_incomplete_line: cf->linenr--; return -1; } -static int get_base_var(char *name) +static int get_base_var(struct strbuf *name) { - int baselen = 0; - for (;;) { int c = get_next_char(); if (cf->eof) return -1; if (c == ']') - return baselen; + return 0; if (isspace(c)) - return get_extended_base_var(name, baselen, c); + return get_extended_base_var(name, c); if (!iskeychar(c) && c != '.') return -1; - if (baselen > MAXNAME / 2) - return -1; - name[baselen++] = tolower(c); + strbuf_addch(name, tolower(c)); } } -static int git_parse_file(config_fn_t fn, void *data) +static int git_parse_source(config_fn_t fn, void *data) { int comment = 0; int baselen = 0; - char *var = cf->var; + struct strbuf *var = &cf->var; /* U+FEFF Byte Order Mark in UTF8 */ - static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf"; - const unsigned char *bomptr = utf8_bom; + const char *bomptr = utf8_bom; for (;;) { int c = get_next_char(); @@ -298,7 +426,7 @@ static int git_parse_file(config_fn_t fn, void *data) /* We are at the file beginning; skip UTF8-encoded BOM * if present. Sane editors won't put this in on their * own, but e.g. Windows Notepad will do it happily. */ - if ((unsigned char) c == *bomptr) { + if (c == (*bomptr & 0377)) { bomptr++; continue; } else { @@ -322,20 +450,30 @@ static int git_parse_file(config_fn_t fn, void *data) continue; } if (c == '[') { - baselen = get_base_var(var); - if (baselen <= 0) + /* Reset prior to determining a new stem */ + strbuf_reset(var); + if (get_base_var(var) < 0 || var->len < 1) break; - var[baselen++] = '.'; - var[baselen] = 0; + strbuf_addch(var, '.'); + baselen = var->len; continue; } if (!isalpha(c)) break; - var[baselen] = tolower(c); - if (get_value(fn, data, var, baselen+1) < 0) + /* + * Truncate the var name back to the section header + * stem prior to grabbing the suffix part of the name + * and the value. + */ + strbuf_setlen(var, baselen); + strbuf_addch(var, tolower(c)); + if (get_value(fn, data, var) < 0) break; } - die("bad config file line %d in %s", cf->linenr, cf->name); + if (cf->die_on_error) + die(_("bad config file line %d in %s"), cf->linenr, cf->name); + else + return error(_("bad config file line %d in %s"), cf->linenr, cf->name); } static int parse_unit_factor(const char *end, uintmax_t *val) @@ -357,7 +495,7 @@ static int parse_unit_factor(const char *end, uintmax_t *val) return 0; } -static int git_parse_long(const char *value, long *ret) +static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) { if (value && *value) { char *end; @@ -369,21 +507,25 @@ static int git_parse_long(const char *value, long *ret) val = strtoimax(value, &end, 0); if (errno == ERANGE) return 0; - if (!parse_unit_factor(end, &factor)) + if (!parse_unit_factor(end, &factor)) { + errno = EINVAL; return 0; - uval = abs(val); + } + uval = labs(val); uval *= factor; - if ((uval > maximum_signed_value_of_type(long)) || - (abs(val) > uval)) + if (uval > max || labs(val) > uval) { + errno = ERANGE; return 0; + } val *= factor; *ret = val; return 1; } + errno = EINVAL; return 0; } -int git_parse_ulong(const char *value, unsigned long *ret) +static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) { if (value && *value) { char *end; @@ -395,29 +537,76 @@ int git_parse_ulong(const char *value, unsigned long *ret) if (errno == ERANGE) return 0; oldval = val; - if (!parse_unit_factor(end, &val)) + if (!parse_unit_factor(end, &val)) { + errno = EINVAL; return 0; - if ((val > maximum_unsigned_value_of_type(long)) || - (oldval > val)) + } + if (val > max || oldval > val) { + errno = ERANGE; return 0; + } *ret = val; return 1; } + errno = EINVAL; return 0; } -static void die_bad_config(const char *name) +static int git_parse_int(const char *value, int *ret) +{ + intmax_t tmp; + if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int))) + return 0; + *ret = tmp; + return 1; +} + +static int git_parse_int64(const char *value, int64_t *ret) +{ + intmax_t tmp; + if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int64_t))) + return 0; + *ret = tmp; + return 1; +} + +int git_parse_ulong(const char *value, unsigned long *ret) +{ + uintmax_t tmp; + if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(long))) + 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"; + if (!value) + value = ""; + if (cf && cf->name) - die("bad config value for '%s' in %s", name, cf->name); - die("bad config value for '%s'", name); + die(_("bad numeric config value '%s' for '%s' in %s: %s"), + value, name, cf->name, reason); + die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason); } int git_config_int(const char *name, const char *value) { - long ret = 0; - if (!git_parse_long(value, &ret)) - die_bad_config(name); + int ret; + if (!git_parse_int(value, &ret)) + die_bad_number(name, value); + return ret; +} + +int64_t git_config_int64(const char *name, const char *value) +{ + int64_t ret; + if (!git_parse_int64(value, &ret)) + die_bad_number(name, value); return ret; } @@ -425,11 +614,11 @@ unsigned long git_config_ulong(const char *name, const char *value) { unsigned long ret; if (!git_parse_ulong(value, &ret)) - die_bad_config(name); + die_bad_number(name, value); return ret; } -static int git_config_maybe_bool_text(const char *name, const char *value) +int git_parse_maybe_bool(const char *value) { if (!value) return 1; @@ -448,17 +637,17 @@ static int git_config_maybe_bool_text(const char *name, const char *value) int git_config_maybe_bool(const char *name, const char *value) { - long v = git_config_maybe_bool_text(name, value); + int v = git_parse_maybe_bool(value); if (0 <= v) return v; - if (git_parse_long(value, &v)) + if (git_parse_int(value, &v)) return !!v; return -1; } int git_config_bool_or_int(const char *name, const char *value, int *is_bool) { - int v = git_config_maybe_bool_text(name, value); + int v = git_parse_maybe_bool(value); if (0 <= v) { *is_bool = 1; return v; @@ -487,7 +676,7 @@ int git_config_pathname(const char **dest, const char *var, const char *value) return config_error_nonbool(var); *dest = expand_user_path(value); if (!*dest) - die("Failed to expand user dir in: '%s'", value); + die(_("failed to expand user dir in: '%s'"), value); return 0; } @@ -502,6 +691,12 @@ static int git_default_core_config(const char *var, const char *value) trust_ctime = git_config_bool(var, value); return 0; } + if (!strcmp(var, "core.checkstat")) { + if (!strcasecmp(value, "default")) + check_stat = 1; + else if (!strcasecmp(value, "minimal")) + check_stat = 0; + } if (!strcmp(var, "core.quotepath")) { quote_path_fully = git_config_bool(var, value); @@ -559,7 +754,7 @@ static int git_default_core_config(const char *var, const char *value) if (level == -1) level = Z_DEFAULT_COMPRESSION; else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad zlib compression level %d", level); + die(_("bad zlib compression level %d"), level); zlib_compression_level = level; zlib_compression_seen = 1; return 0; @@ -570,7 +765,7 @@ static int git_default_core_config(const char *var, const char *value) if (level == -1) level = Z_DEFAULT_COMPRESSION; else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad zlib compression level %d", level); + die(_("bad zlib compression level %d"), level); core_compression_level = level; core_compression_seen = 1; if (!zlib_compression_seen) @@ -605,9 +800,6 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "core.logpackaccess")) - return git_config_string(&log_pack_access, var, value); - if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { if (core_eol == EOL_CRLF) @@ -653,6 +845,19 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.editor")) return git_config_string(&editor_program, var, value); + if (!strcmp(var, "core.commentchar")) { + if (!value) + return config_error_nonbool(var); + else if (!strcasecmp(value, "auto")) + auto_comment_line_char = 1; + else if (value[0] && !value[1]) { + comment_line_char = value[0]; + auto_comment_line_char = 0; + } else + return error("core.commentChar should only be one character"); + return 0; + } + if (!strcmp(var, "core.askpass")) return git_config_string(&askpass_program, var, value); @@ -682,7 +887,7 @@ static int git_default_core_config(const char *var, const char *value) else if (!strcmp(value, "link")) object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; else - die("Invalid mode for object creation: %s", value); + die(_("invalid mode for object creation: %s"), value); return 0; } @@ -691,25 +896,18 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - /* Add other config variables here and to Documentation/config.txt. */ - return 0; -} + if (!strcmp(var, "core.precomposeunicode")) { + precomposed_unicode = git_config_bool(var, value); + return 0; + } -static int git_default_user_config(const char *var, const char *value) -{ - if (!strcmp(var, "user.name")) { - if (!value) - return config_error_nonbool(var); - strlcpy(git_default_name, value, sizeof(git_default_name)); - user_ident_explicitly_given |= IDENT_NAME_GIVEN; + if (!strcmp(var, "core.protecthfs")) { + protect_hfs = git_config_bool(var, value); return 0; } - if (!strcmp(var, "user.email")) { - if (!value) - return config_error_nonbool(var); - strlcpy(git_default_email, value, sizeof(git_default_email)); - user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + if (!strcmp(var, "core.protectntfs")) { + protect_ntfs = git_config_bool(var, value); return 0; } @@ -768,6 +966,8 @@ static int git_default_push_config(const char *var, const char *value) push_default = PUSH_DEFAULT_NOTHING; else if (!strcmp(value, "matching")) push_default = PUSH_DEFAULT_MATCHING; + else if (!strcmp(value, "simple")) + push_default = PUSH_DEFAULT_SIMPLE; else if (!strcmp(value, "upstream")) push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "tracking")) /* deprecated */ @@ -776,8 +976,8 @@ static int git_default_push_config(const char *var, const char *value) push_default = PUSH_DEFAULT_CURRENT; else { error("Malformed value for %s: %s", var, value); - return error("Must be one of nothing, matching, " - "tracking or current."); + return error("Must be one of nothing, matching, simple, " + "upstream or current."); } return 0; } @@ -789,7 +989,9 @@ static int git_default_push_config(const char *var, const char *value) static int git_default_mailmap_config(const char *var, const char *value) { if (!strcmp(var, "mailmap.file")) - return git_config_string(&git_mailmap_file, var, value); + return git_config_pathname(&git_mailmap_file, var, value); + if (!strcmp(var, "mailmap.blob")) + return git_config_string(&git_mailmap_blob, var, value); /* Add other config variables here and to Documentation/config.txt. */ return 0; @@ -797,25 +999,25 @@ static int git_default_mailmap_config(const char *var, const char *value) int git_default_config(const char *var, const char *value, void *dummy) { - if (!prefixcmp(var, "core.")) + if (starts_with(var, "core.")) return git_default_core_config(var, value); - if (!prefixcmp(var, "user.")) - return git_default_user_config(var, value); + if (starts_with(var, "user.")) + return git_ident_config(var, value, dummy); - if (!prefixcmp(var, "i18n.")) + if (starts_with(var, "i18n.")) return git_default_i18n_config(var, value); - if (!prefixcmp(var, "branch.")) + if (starts_with(var, "branch.")) return git_default_branch_config(var, value); - if (!prefixcmp(var, "push.")) + if (starts_with(var, "push.")) return git_default_push_config(var, value); - if (!prefixcmp(var, "mailmap.")) + if (starts_with(var, "mailmap.")) return git_default_mailmap_config(var, value); - if (!prefixcmp(var, "advice.")) + if (starts_with(var, "advice.")) return git_default_advice_config(var, value); if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { @@ -831,35 +1033,122 @@ int git_default_config(const char *var, const char *value, void *dummy) return 0; } -int git_config_from_file(config_fn_t fn, const char *filename, void *data) +/* + * All source specific fields in the union, die_on_error, name and the callbacks + * 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) { int ret; - FILE *f = fopen(filename, "r"); - ret = -1; - if (f) { - config_file top; + /* push config-file parsing state stack */ + top->prev = cf; + top->linenr = 1; + top->eof = 0; + strbuf_init(&top->value, 1024); + strbuf_init(&top->var, 1024); + cf = top; + + ret = git_parse_source(fn, data); + + /* pop config-file parsing state stack */ + strbuf_release(&top->value); + strbuf_release(&top->var); + cf = top->prev; + + return ret; +} + +static int do_config_from_file(config_fn_t fn, + const char *name, const char *path, FILE *f, void *data) +{ + struct config_source top; - /* push config-file parsing state stack */ - top.prev = cf; - top.f = f; - top.name = filename; - top.linenr = 1; - top.eof = 0; - strbuf_init(&top.value, 1024); - cf = ⊤ + top.u.file = f; + top.name = name; + top.path = path; + top.die_on_error = 1; + top.do_fgetc = config_file_fgetc; + top.do_ungetc = config_file_ungetc; + top.do_ftell = config_file_ftell; - ret = git_parse_file(fn, data); + return do_config_from(&top, fn, data); +} + +static int git_config_from_stdin(config_fn_t fn, void *data) +{ + return do_config_from_file(fn, "<stdin>", NULL, stdin, data); +} - /* pop config-file parsing state stack */ - strbuf_release(&top.value); - cf = top.prev; +int git_config_from_file(config_fn_t fn, const char *filename, void *data) +{ + int ret = -1; + FILE *f; + f = fopen(filename, "r"); + if (f) { + flockfile(f); + ret = do_config_from_file(fn, filename, filename, f, data); + funlockfile(f); fclose(f); } return ret; } +int git_config_from_buf(config_fn_t fn, const char *name, const char *buf, + size_t len, void *data) +{ + struct config_source top; + + top.u.buf.buf = buf; + top.u.buf.len = len; + top.u.buf.pos = 0; + top.name = name; + top.path = NULL; + top.die_on_error = 0; + 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); +} + +static int git_config_from_blob_sha1(config_fn_t fn, + const char *name, + const unsigned char *sha1, + void *data) +{ + enum object_type type; + char *buf; + unsigned long size; + int ret; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + 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); + } + + ret = git_config_from_buf(fn, name, buf, size, data); + free(buf); + + return ret; +} + +static int git_config_from_blob_ref(config_fn_t fn, + const char *name, + void *data) +{ + unsigned char sha1[20]; + + 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); +} + const char *git_etc_gitconfig(void) { static const char *system_wide; @@ -868,12 +1157,28 @@ const char *git_etc_gitconfig(void) return system_wide; } +/* + * Parse environment variable 'k' as a boolean (in various + * possible spellings); if missing, use the default value 'def'. + */ int git_env_bool(const char *k, int def) { const char *v = getenv(k); return v ? git_config_bool(k, v) : def; } +/* + * Parse environment variable 'k' as ulong with possibly a unit + * suffix; if missing, use the default value 'val'. + */ +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); + return val; +} + int git_config_system(void) { return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); @@ -882,35 +1187,33 @@ int git_config_system(void) int git_config_early(config_fn_t fn, void *data, const char *repo_config) { int ret = 0, found = 0; - const char *home = NULL; + char *xdg_config = xdg_config_home("config"); + char *user_config = expand_user_path("~/.gitconfig"); - /* Setting $GIT_CONFIG makes git read _only_ the given config file. */ - if (config_exclusive_filename) - return git_config_from_file(fn, config_exclusive_filename, data); - if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) { + if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) { ret += git_config_from_file(fn, git_etc_gitconfig(), data); found += 1; } - home = getenv("HOME"); - if (home) { - char buf[PATH_MAX]; - char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home); - if (!access(user_config, R_OK)) { - ret += git_config_from_file(fn, user_config, data); - found += 1; - } + if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) { + ret += git_config_from_file(fn, xdg_config, data); + found += 1; + } + + if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) { + ret += git_config_from_file(fn, user_config, data); + found += 1; } - if (repo_config && !access(repo_config, R_OK)) { + if (repo_config && !access_or_die(repo_config, R_OK, 0)) { ret += git_config_from_file(fn, repo_config, data); found += 1; } switch (git_config_from_parameters(fn, data)) { case -1: /* error */ - die("unable to parse command-line config"); + die(_("unable to parse command-line config")); break; case 0: /* found nothing */ break; @@ -919,13 +1222,36 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) break; } + free(xdg_config); + free(user_config); return ret == 0 ? found : ret; } -int git_config(config_fn_t fn, void *data) +int git_config_with_options(config_fn_t fn, void *data, + struct git_config_source *config_source, + int respect_includes) { char *repo_config = NULL; int ret; + struct config_include_data inc = CONFIG_INCLUDE_INIT; + + if (respect_includes) { + inc.fn = fn; + inc.data = data; + fn = git_config_include; + data = &inc; + } + + /* + * If we have a specific filename, use it. Otherwise, follow the + * regular lookup sequence. + */ + if (config_source && config_source->use_stdin) + return git_config_from_stdin(fn, data); + else if (config_source && config_source->file) + return git_config_from_file(fn, config_source->file, data); + else if (config_source && config_source->blob) + return git_config_from_blob_ref(fn, config_source->blob, data); repo_config = git_pathdup("config"); ret = git_config_early(fn, data, repo_config); @@ -934,48 +1260,412 @@ int git_config(config_fn_t fn, void *data) return ret; } +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 + * positive values, 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 occured while reading the configuration files")); +} + +static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) +{ + int i, value_index; + struct string_list *values; + struct config_set_element *entry; + struct configset_list *list = &cs->list; + struct key_value_info *kv_info; + + for (i = 0; i < list->nr; i++) { + entry = list->items[i].e; + value_index = list->items[i].value_index; + values = &entry->value_list; + if (fn(entry->key, values->items[value_index].string, data) < 0) { + kv_info = values->items[value_index].util; + git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr); + } + } +} + +static void git_config_check_init(void); + +void git_config(config_fn_t fn, void *data) +{ + git_config_check_init(); + configset_iter(&the_config_set, fn, data); +} + +static struct config_set_element *configset_find_element(struct config_set *cs, const char *key) +{ + struct config_set_element k; + struct config_set_element *found_entry; + char *normalized_key; + int ret; + /* + * `key` may come from the user, so normalize it before using it + * for querying entries from the hashmap. + */ + ret = git_config_parse_key(key, &normalized_key, NULL); + + if (ret) + return NULL; + + hashmap_entry_init(&k, strhash(normalized_key)); + k.key = normalized_key; + found_entry = hashmap_get(&cs->config_hash, &k, NULL); + free(normalized_key); + return found_entry; +} + +static int configset_add_value(struct config_set *cs, const char *key, const char *value) +{ + struct config_set_element *e; + struct string_list_item *si; + struct configset_list_item *l_item; + struct key_value_info *kv_info = xmalloc(sizeof(*kv_info)); + + e = configset_find_element(cs, key); + /* + * Since the keys are being fed by git_config*() callback mechanism, they + * are already normalized. So simply add them without any further munging. + */ + if (!e) { + e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, strhash(key)); + e->key = xstrdup(key); + string_list_init(&e->value_list, 1); + hashmap_add(&cs->config_hash, e); + } + si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value)); + + ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc); + l_item = &cs->list.items[cs->list.nr++]; + l_item->e = e; + l_item->value_index = e->value_list.nr - 1; + + if (cf) { + kv_info->filename = strintern(cf->name); + kv_info->linenr = cf->linenr; + } else { + /* for values read from `git_config_from_parameters()` */ + kv_info->filename = NULL; + kv_info->linenr = -1; + } + si->util = kv_info; + + return 0; +} + +static int config_set_element_cmp(const struct config_set_element *e1, + const struct config_set_element *e2, const void *unused) +{ + 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); + cs->hash_initialized = 1; + cs->list.nr = 0; + cs->list.alloc = 0; + cs->list.items = NULL; +} + +void git_configset_clear(struct config_set *cs) +{ + struct config_set_element *entry; + struct hashmap_iter iter; + if (!cs->hash_initialized) + return; + + hashmap_iter_init(&cs->config_hash, &iter); + while ((entry = hashmap_iter_next(&iter))) { + free(entry->key); + string_list_clear(&entry->value_list, 1); + } + hashmap_free(&cs->config_hash, 1); + cs->hash_initialized = 0; + free(cs->list.items); + cs->list.nr = 0; + cs->list.alloc = 0; + cs->list.items = NULL; +} + +static int config_set_callback(const char *key, const char *value, void *cb) +{ + struct config_set *cs = cb; + configset_add_value(cs, key, value); + return 0; +} + +int git_configset_add_file(struct config_set *cs, const char *filename) +{ + return git_config_from_file(config_set_callback, filename, cs); +} + +int git_configset_get_value(struct config_set *cs, const char *key, const char **value) +{ + const struct string_list *values = NULL; + /* + * Follows "last one wins" semantic, i.e., if there are multiple matches for the + * queried key in the files of the configset, the value returned will be the last + * value in the value list for that key. + */ + values = git_configset_get_value_multi(cs, key); + + if (!values) + return 1; + assert(values->nr > 0); + *value = values->items[values->nr - 1].string; + return 0; +} + +const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key) +{ + struct config_set_element *e = configset_find_element(cs, key); + return e ? &e->value_list : NULL; +} + +int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) + return git_config_string(dest, key, value); + else + return 1; +} + +int git_configset_get_string(struct config_set *cs, const char *key, char **dest) +{ + return git_configset_get_string_const(cs, key, (const char **)dest); +} + +int git_configset_get_int(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_int(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_ulong(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_bool(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_bool(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_bool_or_int(struct config_set *cs, const char *key, + int *is_bool, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_bool_or_int(key, value, is_bool); + return 0; + } else + return 1; +} + +int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_maybe_bool(key, value); + if (*dest == -1) + return -1; + return 0; + } else + return 1; +} + +int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) + return git_config_pathname(dest, key, value); + else + return 1; +} + +static void git_config_check_init(void) +{ + if (the_config_set.hash_initialized) + return; + git_configset_init(&the_config_set); + git_config_raw(config_set_callback, &the_config_set); +} + +void git_config_clear(void) +{ + if (!the_config_set.hash_initialized) + return; + git_configset_clear(&the_config_set); +} + +int git_config_get_value(const char *key, const char **value) +{ + git_config_check_init(); + return git_configset_get_value(&the_config_set, key, value); +} + +const struct string_list *git_config_get_value_multi(const char *key) +{ + git_config_check_init(); + return git_configset_get_value_multi(&the_config_set, key); +} + +int git_config_get_string_const(const char *key, const char **dest) +{ + int ret; + git_config_check_init(); + ret = git_configset_get_string_const(&the_config_set, key, dest); + if (ret < 0) + git_die_config(key, NULL); + return ret; +} + +int git_config_get_string(const char *key, char **dest) +{ + git_config_check_init(); + return git_config_get_string_const(key, (const char **)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); +} + +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); +} + +int git_config_get_bool(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_bool(&the_config_set, 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); +} + +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); +} + +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 ret; +} + +NORETURN +void git_die_config_linenr(const char *key, const char *filename, int linenr) +{ + if (!filename) + die(_("unable to parse '%s' from command-line config"), key); + else + die(_("bad config variable '%s' in file '%s' at line %d"), + key, filename, linenr); +} + +NORETURN __attribute__((format(printf, 2, 3))) +void git_die_config(const char *key, const char *err, ...) +{ + const struct string_list *values; + struct key_value_info *kv_info; + + if (err) { + va_list params; + va_start(params, err); + vreportf("error: ", err, params); + va_end(params); + } + values = git_config_get_value_multi(key); + kv_info = values->items[values->nr - 1].util; + git_die_config_linenr(key, kv_info->filename, kv_info->linenr); +} + /* * Find all the stuff for git_config_set() below. */ -#define MAX_MATCHES 512 - static struct { int baselen; char *key; int do_not_match; regex_t *value_regex; int multi_replace; - size_t offset[MAX_MATCHES]; + size_t *offset; + unsigned int offset_alloc; enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state; int seen; } store; static int matches(const char *key, const char *value) { - return !strcmp(key, store.key) && - (store.value_regex == NULL || - (store.do_not_match ^ - !regexec(store.value_regex, value, 0, NULL, 0))); + if (strcmp(key, store.key)) + return 0; /* not ours */ + if (!store.value_regex) + return 1; /* always matches */ + 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)); } static int store_aux(const char *key, const char *value, void *cb) { const char *ep; size_t section_len; - FILE *f = cf->f; switch (store.state) { case KEY_SEEN: if (matches(key, value)) { if (store.seen == 1 && store.multi_replace == 0) { - warning("%s has multiple values", key); - } else if (store.seen >= MAX_MATCHES) { - error("too many matches for %s", key); - return 1; + warning(_("%s has multiple values"), key); } - store.offset[store.seen] = ftell(f); + ALLOC_GROW(store.offset, store.seen + 1, + store.offset_alloc); + + store.offset[store.seen] = cf->do_ftell(cf); store.seen++; } break; @@ -1002,19 +1692,26 @@ static int store_aux(const char *key, const char *value, void *cb) * Do not increment matches: this is no match, but we * just made sure we are in the desired section. */ - store.offset[store.seen] = ftell(f); + 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)) { - store.offset[store.seen] = ftell(f); + 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; - store.offset[store.seen] = ftell(f); + ALLOC_GROW(store.offset, + store.seen + 1, + store.offset_alloc); + store.offset[store.seen] = cf->do_ftell(cf); } } } @@ -1151,7 +1848,7 @@ int git_config_set(const char *key, const char *value) * baselen - pointer to int which will hold the length of the * section + subsection part, can be NULL */ -int git_config_parse_key(const char *key, char **store_key, int *baselen_) +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, '.'); @@ -1162,12 +1859,14 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) */ if (last_dot == NULL || last_dot == key) { - error("key does not contain a section: %s", key); + if (!quiet) + error("key does not contain a section: %s", key); return -CONFIG_NO_SECTION_OR_NAME; } if (!last_dot[1]) { - error("key does not contain variable name: %s", key); + if (!quiet) + error("key does not contain variable name: %s", key); return -CONFIG_NO_SECTION_OR_NAME; } @@ -1178,7 +1877,8 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) /* * Validate the key and while at it, lower case it for matching. */ - *store_key = xmalloc(strlen(key) + 1); + if (store_key) + *store_key = xmalloc(strlen(key) + 1); dot = 0; for (i = 0; key[i]; i++) { @@ -1189,28 +1889,47 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) if (!dot || i > baselen) { if (!iskeychar(c) || (i == baselen + 1 && !isalpha(c))) { - error("invalid key: %s", key); + if (!quiet) + error("invalid key: %s", key); goto out_free_ret_1; } c = tolower(c); } else if (c == '\n') { - error("invalid key (newline): %s", key); + if (!quiet) + error("invalid key (newline): %s", key); goto out_free_ret_1; } - (*store_key)[i] = c; + if (store_key) + (*store_key)[i] = c; } - (*store_key)[i] = 0; + if (store_key) + (*store_key)[i] = 0; return 0; out_free_ret_1: - free(*store_key); + 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 + * (only add a new one) * if multi_replace==0, nothing, or only one matching key/value is replaced, * else all matching key/values (regardless how many) are removed, * before the new pair is written. @@ -1235,9 +1954,12 @@ int git_config_set_multivar_in_file(const char *config_filename, const char *key, const char *value, const char *value_regex, int multi_replace) { - int fd = -1, in_fd; + int fd = -1, in_fd = -1; int ret; struct lock_file *lock = NULL; + char *filename_buf = NULL; + char *contents = NULL; + size_t contents_sz; /* parse-key returns negative; flip the sign to feed exit(3) */ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); @@ -1246,12 +1968,14 @@ int git_config_set_multivar_in_file(const char *config_filename, store.multi_replace = multi_replace; + if (!config_filename) + config_filename = filename_buf = git_pathdup("config"); /* * The lock serves a purpose in addition to locking: the new * contents of .git/config will be written into it. */ - lock = xcalloc(sizeof(struct lock_file), 1); + lock = xcalloc(1, sizeof(struct lock_file)); fd = hold_lock_file_for_update(lock, config_filename, 0); if (fd < 0) { error("could not lock config file %s: %s", config_filename, strerror(errno)); @@ -1285,12 +2009,13 @@ int git_config_set_multivar_in_file(const char *config_filename, goto write_err_out; } else { struct stat st; - char *contents; - size_t contents_sz, copy_begin, copy_end; + size_t copy_begin, copy_end; int i, new_line = 0; if (value_regex == NULL) store.value_regex = NULL; + else if (value_regex == CONFIG_REGEX_NONE) + store.value_regex = CONFIG_REGEX_NONE; else { if (value_regex[0] == '!') { store.do_not_match = 1; @@ -1308,6 +2033,7 @@ int git_config_set_multivar_in_file(const char *config_filename, } } + ALLOC_GROW(store.offset, 1, store.offset_alloc); store.offset[0] = 0; store.state = START; store.seen = 0; @@ -1321,7 +2047,8 @@ int git_config_set_multivar_in_file(const char *config_filename, 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) { + if (store.value_regex != NULL && + store.value_regex != CONFIG_REGEX_NONE) { regfree(store.value_regex); free(store.value_regex); } @@ -1330,7 +2057,8 @@ int git_config_set_multivar_in_file(const char *config_filename, } free(store.key); - if (store.value_regex != NULL) { + if (store.value_regex != NULL && + store.value_regex != CONFIG_REGEX_NONE) { regfree(store.value_regex); free(store.value_regex); } @@ -1344,9 +2072,26 @@ int git_config_set_multivar_in_file(const char *config_filename, fstat(in_fd, &st); contents_sz = xsize_t(st.st_size); - contents = xmmap(NULL, contents_sz, PROT_READ, - MAP_PRIVATE, in_fd, 0); + 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("unable to mmap '%s': %s", + config_filename, strerror(errno)); + ret = CONFIG_INVALID_FILE; + contents = NULL; + goto out_free; + } close(in_fd); + in_fd = -1; + + if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { + error("chmod on %s failed: %s", + get_lock_file_path(lock), strerror(errno)); + ret = CONFIG_NO_WRITE; + goto out_free; + } if (store.seen == 0) store.seen = 1; @@ -1395,11 +2140,13 @@ int git_config_set_multivar_in_file(const char *config_filename, goto write_err_out; munmap(contents, contents_sz); + contents = NULL; } if (commit_lock_file(lock) < 0) { error("could not commit config file %s", config_filename); ret = CONFIG_NO_WRITE; + lock = NULL; goto out_free; } @@ -1412,13 +2159,21 @@ int git_config_set_multivar_in_file(const char *config_filename, lock = NULL; ret = 0; + /* Invalidate the config cache */ + git_config_clear(); + out_free: if (lock) rollback_lock_file(lock); + free(filename_buf); + if (contents) + munmap(contents, contents_sz); + if (in_fd >= 0) + close(in_fd); return ret; write_err_out: - ret = write_error(lock->filename); + ret = write_error(get_lock_file_path(lock)); goto out_free; } @@ -1426,19 +2181,8 @@ write_err_out: int git_config_set_multivar(const char *key, const char *value, const char *value_regex, int multi_replace) { - const char *config_filename; - char *buf = NULL; - int ret; - - if (config_exclusive_filename) - config_filename = config_exclusive_filename; - else - config_filename = buf = git_pathdup("config"); - - ret = git_config_set_multivar_in_file(config_filename, key, value, - value_regex, multi_replace); - free(buf); - return ret; + return git_config_set_multivar_in_file(NULL, key, value, value_regex, + multi_replace); } static int section_name_match (const char *buf, const char *name) @@ -1480,20 +2224,43 @@ static int section_name_match (const char *buf, const char *name) return 0; } +static int section_name_is_ok(const char *name) +{ + /* Empty section names are bogus. */ + if (!*name) + return 0; + + /* + * Before a dot, we must be alphanumeric or dash. After the first dot, + * anything goes, so we can stop checking. + */ + for (; *name && *name != '.'; name++) + if (*name != '-' && !isalnum(*name)) + return 0; + return 1; +} + /* if new_name == NULL, the section is removed instead */ -int git_config_rename_section(const char *old_name, const char *new_name) +int git_config_rename_section_in_file(const char *config_filename, + const char *old_name, const char *new_name) { int ret = 0, remove = 0; - char *config_filename; - struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); + char *filename_buf = NULL; + struct lock_file *lock; int out_fd; char buf[1024]; FILE *config_file; + struct stat st; - if (config_exclusive_filename) - config_filename = xstrdup(config_exclusive_filename); - else - config_filename = git_pathdup("config"); + if (new_name && !section_name_is_ok(new_name)) { + ret = error("invalid section name: %s", new_name); + goto out; + } + + 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); if (out_fd < 0) { ret = error("could not lock config file %s", config_filename); @@ -1505,6 +2272,14 @@ int git_config_rename_section(const char *old_name, const char *new_name) goto unlock_and_out; } + fstat(fileno(config_file), &st); + + if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { + ret = error("chmod on %s failed: %s", + get_lock_file_path(lock), strerror(errno)); + goto out; + } + while (fgets(buf, sizeof(buf), config_file)) { int i; int length; @@ -1522,7 +2297,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) } store.baselen = strlen(new_name); if (!store_write_section(out_fd, new_name)) { - ret = write_error(lock->filename); + ret = write_error(get_lock_file_path(lock)); goto out; } /* @@ -1548,7 +2323,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) continue; length = strlen(output); if (write_in_full(out_fd, output, length) != length) { - ret = write_error(lock->filename); + ret = write_error(get_lock_file_path(lock)); goto out; } } @@ -1557,15 +2332,54 @@ unlock_and_out: if (commit_lock_file(lock) < 0) ret = error("could not commit config file %s", config_filename); out: - free(config_filename); + free(filename_buf); return ret; } +int git_config_rename_section(const char *old_name, const char *new_name) +{ + return git_config_rename_section_in_file(NULL, old_name, new_name); +} + /* * Call this to report error for your variable that should not * get a boolean value (i.e. "[my] var" means "true"). */ +#undef config_error_nonbool int config_error_nonbool(const char *var) { return error("Missing value for '%s'", var); } + +int parse_config_key(const char *var, + const char *section, + 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] != '.') + return -1; + + /* + * Find the key; we don't know yet if we have a subsection, but we must + * parse backwards from the end, since the subsection may have dots in + * it, too. + */ + dot = strrchr(var, '.'); + *key = dot + 1; + + /* Did we have a subsection at all? */ + if (dot == var + section_len) { + *subsection = NULL; + *subsection_len = 0; + } + else { + *subsection = var + section_len + 1; + *subsection_len = dot - *subsection; + } + + return 0; +} |