Commit fefd4551 by Carlos Martín Nieto

First round of config multimap changes

Move the configuration to use a multimap instead of a list. This
commit doesn't provide any functional changes but changes the support
structures.
parent 555c81f3
......@@ -18,8 +18,7 @@
typedef struct cvar_t {
struct cvar_t *next;
char *section;
char *name;
char *key; /* TODO: we might be able to get rid of this */
char *value;
} cvar_t;
......@@ -69,7 +68,7 @@ typedef struct {
typedef struct {
git_config_file parent;
cvar_t_list var_list;
git_hashtable *values;
struct {
git_fbuffer buffer;
......@@ -83,161 +82,62 @@ typedef struct {
static int config_parse(diskfile_backend *cfg_file);
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
static int config_write(diskfile_backend *cfg, cvar_t *var);
static int config_write(diskfile_backend *cfg, const char *key, const char *value);
static void cvar_free(cvar_t *var)
{
if (var == NULL)
return;
git__free(var->section);
git__free(var->name);
git__free(var->key);
git__free(var->value);
git__free(var);
}
static void cvar_list_free(cvar_t_list *list)
/* Take something the user gave us and make it nice for our hash function */
static int normalize_name(const char *in, char **out)
{
cvar_t *cur;
char *name, *fdot, *ldot;
while (!CVAR_LIST_EMPTY(list)) {
cur = CVAR_LIST_HEAD(list);
CVAR_LIST_REMOVE_HEAD(list);
cvar_free(cur);
}
}
/*
* Compare according to the git rules. Section contains the section as
* it's stored internally. query is the full name as would be given to
* 'git config'.
*/
static int cvar_match_section(const char *section, const char *query)
{
const char *sdot, *qdot, *qsub;
size_t section_len;
sdot = strchr(section, '.');
/* If the section doesn't have any dots, it's easy */
if (sdot == NULL)
return !strncasecmp(section, query, strlen(section));
/*
* If it does have dots, compare the sections
* case-insensitively. The comparison includes the dots.
*/
section_len = sdot - section + 1;
if (strncasecmp(section, query, sdot - section))
return 0;
qsub = query + section_len;
qdot = strchr(qsub, '.');
/* Make sure the subsections are the same length */
if (strlen(sdot + 1) != (size_t) (qdot - qsub))
return 0;
/* The subsection is case-sensitive */
return !strncmp(sdot + 1, qsub, strlen(sdot + 1));
}
static int cvar_match_name(const cvar_t *var, const char *str)
{
const char *name_start;
if (!cvar_match_section(var->section, str)) {
return 0;
}
/* Early exit if the lengths are different */
name_start = strrchr(str, '.') + 1;
if (strlen(var->name) != strlen(name_start))
return 0;
return !strcasecmp(var->name, name_start);
}
static cvar_t *cvar_list_find(cvar_t_list *list, const char *name)
{
cvar_t *iter;
CVAR_LIST_FOREACH (list, iter) {
if (cvar_match_name(iter, name))
return iter;
}
return NULL;
}
static int cvar_normalize_name(cvar_t *var, char **output)
{
char *section_sp = strchr(var->section, ' ');
char *quote, *name;
size_t len;
int ret;
assert(in && out);
/*
* The final string is going to be at most one char longer than
* the input
*/
len = strlen(var->section) + strlen(var->name) + 1;
name = git__malloc(len + 1);
name = git__strdup(in);
if (name == NULL)
return GIT_ENOMEM;
/* If there aren't any spaces in the section, it's easy */
if (section_sp == NULL) {
ret = p_snprintf(name, len + 1, "%s.%s", var->section, var->name);
if (ret < 0) {
fdot = strchr(name, '.');
ldot = strrchr(name, '.');
if (fdot == NULL || ldot == NULL) {
git__free(name);
return git__throw(GIT_EOSERR, "Failed to normalize name. OS err: %s", strerror(errno));
return git__throw(GIT_EINVALIDARGS, "Bad format. No dot in '%s'", in);
}
*output = name;
return GIT_SUCCESS;
}
/* Downcase up to the first dot and after the last one */
git__strntolower(name, fdot - name);
git__strtolower(ldot);
/*
* If there are spaces, we replace the space by a dot, move
* section name so it overwrites the first quotation mark and
* replace the last quotation mark by a dot. We then append the
* variable name.
*/
strcpy(name, var->section);
section_sp = strchr(name, ' ');
*section_sp = '.';
/* Remove first quote */
quote = strchr(name, '"');
memmove(quote, quote+1, strlen(quote+1));
/* Remove second quote */
quote = strchr(name, '"');
*quote = '.';
strcpy(quote+1, var->name);
*output = name;
*out = name;
return GIT_SUCCESS;
}
static char *interiorize_section(const char *orig)
static void free_vars(git_hashtable *values)
{
char *dot, *last_dot, *section, *ret;
size_t len;
dot = strchr(orig, '.');
last_dot = strrchr(orig, '.');
len = last_dot - orig;
const char *GIT_UNUSED(_unused) = NULL;
cvar_t *var = NULL;
/* No subsection, this is easy */
if (last_dot == dot)
return git__strndup(orig, dot - orig);
if (values == NULL)
return;
section = git__strndup(orig, len);
if (section == NULL)
return NULL;
GIT_HASHTABLE_FOREACH(values, _unused, var,
do {
cvar_t *next = CVAR_LIST_NEXT(var);
cvar_free(var);
var = next;
} while (var != NULL);
)
ret = section;
len = dot - orig;
git__strntolower(section, len);
return ret;
git_hashtable_free(values);
}
static int config_open(git_config_file *cfg)
......@@ -245,6 +145,10 @@ static int config_open(git_config_file *cfg)
int error;
diskfile_backend *b = (diskfile_backend *)cfg;
b->values = git_hashtable_alloc (20, git_hash__strhash_cb, git_hash__strcmp_cb);
if (b->values == NULL)
return GIT_ENOMEM;
error = git_futils_readbuffer(&b->reader.buffer, b->file_path);
/* It's fine if the file doesn't exist */
......@@ -263,7 +167,8 @@ static int config_open(git_config_file *cfg)
return GIT_SUCCESS;
cleanup:
cvar_list_free(&b->var_list);
free_vars(b->values);
b->values = NULL;
git_futils_freebuffer(&b->reader.buffer);
return git__rethrow(error, "Failed to open config");
......@@ -277,7 +182,8 @@ static void backend_free(git_config_file *_backend)
return;
git__free(backend->file_path);
cvar_list_free(&backend->var_list);
free_vars(backend->values);
git__free(backend);
}
......@@ -287,19 +193,17 @@ static int file_foreach(git_config_file *backend, int (*fn)(const char *, const
int ret = GIT_SUCCESS;
cvar_t *var;
diskfile_backend *b = (diskfile_backend *)backend;
const char *key;
CVAR_LIST_FOREACH(&b->var_list, var) {
char *normalized = NULL;
ret = cvar_normalize_name(var, &normalized);
if (ret < GIT_SUCCESS)
return ret;
ret = fn(normalized, var->value, data);
git__free(normalized);
GIT_HASHTABLE_FOREACH(b->values, key, var,
do {
ret = fn(key, var->value, data);
if (ret)
break;
}
var = CVAR_LIST_NEXT(var);
} while (var != NULL);
)
return ret;
}
......@@ -307,38 +211,34 @@ static int file_foreach(git_config_file *backend, int (*fn)(const char *, const
static int config_set(git_config_file *cfg, const char *name, const char *value)
{
cvar_t *var = NULL;
cvar_t *existing = NULL;
cvar_t *existing = NULL, *old_value = NULL;
int error = GIT_SUCCESS;
const char *last_dot;
diskfile_backend *b = (diskfile_backend *)cfg;
char *key;
if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
return git__rethrow(error, "Failed to normalize variable name '%s'", name);
/*
* If it already exists, we just need to update its value.
* Try to find it in the existing values and update it if it
* only has one value.
*/
existing = cvar_list_find(&b->var_list, name);
existing = git_hashtable_lookup(b->values, key);
if (existing != NULL) {
char *tmp = value ? git__strdup(value) : NULL;
char *tmp;
git__free(key);
if (existing->next != NULL)
return git__throw(GIT_EINVALIDARGS, "Multivar incompatible with simple set");
tmp = value ? git__strdup(value) : NULL;
if (tmp == NULL && value != NULL)
return GIT_ENOMEM;
git__free(existing->value);
existing->value = tmp;
return config_write(b, existing);
}
/*
* Otherwise, create it and stick it at the end of the queue. If
* value is NULL, we return an error, because you can't delete a
* variable that doesn't exist.
*/
if (value == NULL)
return git__throw(GIT_ENOTFOUND, "Can't delete non-exitent variable");
last_dot = strrchr(name, '.');
if (last_dot == NULL) {
return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed");
return config_write(b, existing->key, value);
}
var = git__malloc(sizeof(cvar_t));
......@@ -347,17 +247,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
memset(var, 0x0, sizeof(cvar_t));
var->section = interiorize_section(name);
if (var->section == NULL) {
error = GIT_ENOMEM;
goto out;
}
var->name = git__strdup(last_dot + 1);
if (var->name == NULL) {
error = GIT_ENOMEM;
goto out;
}
var->key = key;
var->value = value ? git__strdup(value) : NULL;
if (var->value == NULL && value != NULL) {
......@@ -365,8 +255,13 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
goto out;
}
CVAR_LIST_APPEND(&b->var_list, var);
error = config_write(b, var);
error = git_hashtable_insert2(b->values, key, var, (void **)&old_value);
if (error < GIT_SUCCESS)
goto out;
cvar_free(old_value);
error = config_write(b, key, value);
out:
if (error < GIT_SUCCESS)
......@@ -383,8 +278,13 @@ static int config_get(git_config_file *cfg, const char *name, const char **out)
cvar_t *var;
int error = GIT_SUCCESS;
diskfile_backend *b = (diskfile_backend *)cfg;
char *key;
if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
return error;
var = cvar_list_find(&b->var_list, name);
var = git_hashtable_lookup(b->values, key);
git__free(key);
if (var == NULL)
return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
......@@ -397,30 +297,31 @@ static int config_get(git_config_file *cfg, const char *name, const char **out)
static int config_delete(git_config_file *cfg, const char *name)
{
int error;
cvar_t *iter, *prev = NULL;
const cvar_t *var;
cvar_t *old_value;
diskfile_backend *b = (diskfile_backend *)cfg;
char *key;
CVAR_LIST_FOREACH (&b->var_list, iter) {
/* This is a bit hacky because we use a singly-linked list */
if (cvar_match_name(iter, name)) {
if (CVAR_LIST_HEAD(&b->var_list) == iter)
CVAR_LIST_HEAD(&b->var_list) = CVAR_LIST_NEXT(iter);
else
CVAR_LIST_REMOVE_AFTER(prev);
git__free(iter->value);
iter->value = NULL;
error = config_write(b, iter);
cvar_free(iter);
return error == GIT_SUCCESS ?
GIT_SUCCESS :
git__rethrow(error, "Failed to update config file");
}
/* Store it for the next round */
prev = iter;
}
if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
return error;
var = git_hashtable_lookup(b->values, key);
free(key);
if (var == NULL)
return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
if (var->next != NULL)
return git__throw(GIT_EINVALIDARGS, "Multivar incompatible with simple delete");
if ((error = git_hashtable_remove2(b->values, var->key, (void **)&old_value)) < GIT_SUCCESS)
return git__rethrow(error, "Failed to remove %s from hashtable", key);
error = config_write(b, var->key, NULL);
cvar_free(old_value);
return error;
}
int git_config_file__ondisk(git_config_file **out, const char *path)
......@@ -631,6 +532,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha
*/
do {
if (quote_marks == 2) {
puts("too many marks");
error = git__throw(GIT_EOBJCORRUPTED, "Falied to parse ext header. Text after closing quote");
goto out;
......@@ -819,6 +721,7 @@ static int config_parse(diskfile_backend *cfg_file)
char *var_name;
char *var_value;
cvar_t *var;
git_buf buf = GIT_BUF_INIT;
/* Initialize the reading position */
cfg_file->reader.read_ptr = cfg_file->reader.buffer.data;
......@@ -864,18 +767,20 @@ static int config_parse(diskfile_backend *cfg_file)
memset(var, 0x0, sizeof(cvar_t));
var->section = git__strdup(current_section);
if (var->section == NULL) {
git__strtolower(var_name);
git_buf_printf(&buf, "%s.%s", current_section, var_name);
git__free(var_name);
if (git_buf_oom(&buf)) {
error = GIT_ENOMEM;
git__free(var);
break;
}
var->name = var_name;
var->key = git_buf_detach(&buf);
var->value = var_value;
git__strtolower(var->name);
CVAR_LIST_APPEND(&cfg_file->var_list, var);
/* FIXME: Actually support multivars, don't just overwrite */
error = git_hashtable_insert(cfg_file->values, var->key, var);
break;
}
......@@ -886,26 +791,44 @@ static int config_parse(diskfile_backend *cfg_file)
return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config");
}
static int write_section(git_filebuf *file, cvar_t *var)
static int write_section(git_filebuf *file, const char *key)
{
int error;
const char *fdot, *ldot;
git_buf buf = GIT_BUF_INIT;
error = git_filebuf_printf(file, "[%s]\n", var->section);
if (error < GIT_SUCCESS)
return error;
/* All of this just for [section "subsection"] */
fdot = strchr(key, '.');
git_buf_putc(&buf, '[');
if (fdot == NULL)
git_buf_puts(&buf, key);
else
git_buf_put(&buf, key, fdot - key);
ldot = strrchr(key, '.');
if (fdot != ldot && fdot != NULL) {
git_buf_putc(&buf, '"');
/* TODO: escape */
git_buf_put(&buf, fdot + 1, ldot - fdot - 1);
git_buf_putc(&buf, '"');
}
git_buf_puts(&buf, "]\n");
if (git_buf_oom(&buf))
return GIT_ENOMEM;
error = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
git_buf_free(&buf);
error = git_filebuf_printf(file, " %s = %s\n", var->name, var->value);
return error;
}
/*
* This is pretty much the parsing, except we write out anything we don't have
*/
static int config_write(diskfile_backend *cfg, cvar_t *var)
static int config_write(diskfile_backend *cfg, const char *key, const char* value)
{
int error = GIT_SUCCESS, c;
int section_matches = 0, last_section_matched = 0;
char *current_section = NULL;
char *current_section = NULL, *section, *name, *ldot;
char *var_name, *var_value, *data_start;
git_filebuf file = GIT_FILEBUF_INIT;
const char *pre_end = NULL, *post_start = NULL;
......@@ -936,6 +859,9 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
return git__rethrow(error, "Failed to lock config file");
skip_bom(cfg);
ldot = strrchr(key, '.');
name = ldot + 1;
section = git__strndup(key, ldot - key);
while (error == GIT_SUCCESS && !cfg->reader.eof) {
c = cfg_peek(cfg, SKIP_WHITESPACE);
......@@ -961,7 +887,7 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
/* Keep track of when it stops matching */
last_section_matched = section_matches;
section_matches = !strcmp(current_section, var->section);
section_matches = !strcmp(current_section, section);
break;
case ';':
......@@ -990,7 +916,7 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
pre_end = cfg->reader.read_ptr;
if ((error = parse_variable(cfg, &var_name, &var_value)) == GIT_SUCCESS)
cmp = strcasecmp(var->name, var_name);
cmp = strcasecmp(name, var_name);
git__free(var_name);
git__free(var_value);
......@@ -1016,10 +942,10 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
* means we want to delete it, so pretend everything went
* fine
*/
if (var->value == NULL)
if (value == NULL)
error = GIT_SUCCESS;
else
error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
if (error < GIT_SUCCESS) {
git__rethrow(error, "Failed to overwrite the variable");
break;
......@@ -1058,16 +984,18 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
/* And now if we just need to add a variable */
if (section_matches) {
error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
goto cleanup;
}
/* Or maybe we need to write out a whole section */
error = write_section(&file, var);
error = write_section(&file, section);
if (error < GIT_SUCCESS)
git__rethrow(error, "Failed to write new section");
error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
cleanup:
git__free(section);
git__free(current_section);
if (error < GIT_SUCCESS)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment