aboutsummaryrefslogtreecommitdiff
path: root/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'config.c')
-rw-r--r--config.c611
1 files changed, 538 insertions, 73 deletions
diff --git a/config.c b/config.c
index 61a9c296f..248a21ab9 100644
--- a/config.c
+++ b/config.c
@@ -6,9 +6,13 @@
*
*/
#include "cache.h"
+#include "lockfile.h"
#include "exec_cmd.h"
#include "strbuf.h"
#include "quote.h"
+#include "hashmap.h"
+#include "string-list.h"
+#include "utf8.h"
struct config_source {
struct config_source *prev;
@@ -37,9 +41,16 @@ static struct config_source *cf;
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 fgetc(conf->u.file);
+ return getc_unlocked(conf->u.file);
}
static int config_file_ungetc(int c, struct config_source *conf)
@@ -63,8 +74,12 @@ static int config_buf_fgetc(struct config_source *conf)
static int config_buf_ungetc(int c, struct config_source *conf)
{
- if (conf->u.buf.pos > 0)
- return conf->u.buf.buf[--conf->u.buf.pos];
+ 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;
}
@@ -127,7 +142,6 @@ static int handle_path_include(const char *path, struct config_include_data *inc
int git_config_include(const char *var, const char *value, void *data)
{
struct config_include_data *inc = data;
- const char *type;
int ret;
/*
@@ -138,21 +152,11 @@ int git_config_include(const char *var, const char *value, void *data)
if (ret < 0)
return ret;
- type = skip_prefix(var, "include.");
- if (!type)
- return ret;
-
- if (!strcmp(type, "path"))
+ if (!strcmp(var, "include.path"))
ret = handle_path_include(value, inc);
return ret;
}
-static void lowercase(char *p)
-{
- for (; *p; p++)
- *p = tolower(*p);
-}
-
void git_config_push_parameter(const char *text)
{
struct strbuf env = STRBUF_INIT;
@@ -169,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;
}
@@ -228,7 +240,8 @@ static int get_next_char(void)
/* DOS like systems */
c = cf->do_fgetc(cf);
if (c != '\n') {
- cf->do_ungetc(c, cf);
+ if (c != EOF)
+ cf->do_ungetc(c, cf);
c = '\r';
}
}
@@ -236,6 +249,7 @@ static int get_next_char(void)
cf->linenr++;
if (c == EOF) {
cf->eof = 1;
+ cf->linenr++;
c = '\n';
}
return c;
@@ -311,6 +325,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
{
int c;
char *value;
+ int ret;
/* Get the full name */
for (;;) {
@@ -333,7 +348,15 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
if (!value)
return -1;
}
- return fn(name->buf, 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(struct strbuf *name, int c)
@@ -395,8 +418,7 @@ static int git_parse_source(config_fn_t fn, void *data)
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();
@@ -404,7 +426,7 @@ static int git_parse_source(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 {
@@ -449,9 +471,9 @@ static int git_parse_source(config_fn_t fn, void *data)
break;
}
if (cf->die_on_error)
- die("bad config file line %d in %s", cf->linenr, cf->name);
+ 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);
+ return error(_("bad config file line %d in %s"), cf->linenr, cf->name);
}
static int parse_unit_factor(const char *end, uintmax_t *val)
@@ -489,9 +511,9 @@ static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)
errno = EINVAL;
return 0;
}
- uval = abs(val);
+ uval = labs(val);
uval *= factor;
- if (uval > max || abs(val) > uval) {
+ if (uval > max || labs(val) > uval) {
errno = ERANGE;
return 0;
}
@@ -567,9 +589,9 @@ static void die_bad_number(const char *name, const char *value)
value = "";
if (cf && cf->name)
- die("bad numeric config value '%s' for '%s' in %s: %s",
+ die(_("bad numeric config value '%s' for '%s' in %s: %s"),
value, name, cf->name, reason);
- die("bad numeric config value '%s' for '%s': %s", value, name, reason);
+ die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
}
int git_config_int(const char *name, const char *value)
@@ -596,7 +618,7 @@ unsigned long git_config_ulong(const char *name, const char *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;
@@ -615,7 +637,7 @@ static int git_config_maybe_bool_text(const char *name, const char *value)
int git_config_maybe_bool(const char *name, const char *value)
{
- int v = git_config_maybe_bool_text(name, value);
+ int v = git_parse_maybe_bool(value);
if (0 <= v)
return v;
if (git_parse_int(value, &v))
@@ -625,7 +647,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_config_maybe_bool_text(name, value);
+ int v = git_parse_maybe_bool(value);
if (0 <= v) {
*is_bool = 1;
return v;
@@ -654,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;
}
@@ -732,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;
@@ -743,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)
@@ -824,11 +846,16 @@ static int git_default_core_config(const char *var, const char *value)
return git_config_string(&editor_program, var, value);
if (!strcmp(var, "core.commentchar")) {
- const char *comment;
- int ret = git_config_string(&comment, var, value);
- if (!ret)
- comment_line_char = comment[0];
- return ret;
+ 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"))
@@ -860,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;
}
@@ -1061,7 +1088,9 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
f = fopen(filename, "r");
if (f) {
+ flockfile(f);
ret = do_config_from_file(fn, filename, filename, f, data);
+ funlockfile(f);
fclose(f);
}
return ret;
@@ -1128,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);
@@ -1142,10 +1187,8 @@ int git_config_system(void)
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
- char *xdg_config = NULL;
- char *user_config = NULL;
-
- home_config_paths(&user_config, &xdg_config, "config");
+ char *xdg_config = xdg_config_home("config");
+ char *user_config = expand_user_path("~/.gitconfig");
if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
@@ -1170,7 +1213,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
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;
@@ -1217,9 +1260,365 @@ int git_config_with_options(config_fn_t fn, void *data,
return ret;
}
-int git_config(config_fn_t fn, void *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
+ * 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)
{
- return git_config_with_options(fn, data, NULL, 1);
+ 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);
}
/*
@@ -1240,10 +1639,15 @@ static struct {
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)
@@ -1255,7 +1659,7 @@ static int store_aux(const char *key, const char *value, void *cb)
case KEY_SEEN:
if (matches(key, value)) {
if (store.seen == 1 && store.multi_replace == 0) {
- warning("%s has multiple values", key);
+ warning(_("%s has multiple values"), key);
}
ALLOC_GROW(store.offset, store.seen + 1,
@@ -1444,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, '.');
@@ -1455,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;
}
@@ -1471,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++) {
@@ -1482,29 +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);
- *store_key = NULL;
+ 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.
@@ -1529,10 +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);
@@ -1582,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;
@@ -1619,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);
}
@@ -1628,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);
}
@@ -1642,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;
@@ -1693,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;
}
@@ -1710,14 +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;
}
@@ -1794,6 +2250,7 @@ int git_config_rename_section_in_file(const char *config_filename,
int out_fd;
char buf[1024];
FILE *config_file;
+ struct stat st;
if (new_name && !section_name_is_ok(new_name)) {
ret = error("invalid section name: %s", new_name);
@@ -1815,6 +2272,14 @@ int git_config_rename_section_in_file(const char *config_filename,
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;
@@ -1832,7 +2297,7 @@ int git_config_rename_section_in_file(const char *config_filename,
}
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;
}
/*
@@ -1858,7 +2323,7 @@ int git_config_rename_section_in_file(const char *config_filename,
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;
}
}