]> git.proxmox.com Git - libgit2.git/commitdiff
First round of config multimap changes
authorCarlos Martín Nieto <carlos@cmartin.tk>
Mon, 23 Jan 2012 03:26:49 +0000 (04:26 +0100)
committerCarlos Martín Nieto <carlos@cmartin.tk>
Fri, 17 Feb 2012 18:43:43 +0000 (19:43 +0100)
Move the configuration to use a multimap instead of a list. This
commit doesn't provide any functional changes but changes the support
structures.

src/config_file.c

index 939bccc166c40db7393fb52a2b4294a8ee64a0c7..9c4128d2b8220f5497ee443c631bcaaf4e02c7c1 100644 (file)
@@ -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;
-}
+       assert(in && out);
 
-static int cvar_normalize_name(cvar_t *var, char **output)
-{
-       char *section_sp = strchr(var->section, ' ');
-       char *quote, *name;
-       size_t len;
-       int ret;
-
-       /*
-        * 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) {
-                       git__free(name);
-                       return git__throw(GIT_EOSERR, "Failed to normalize name. OS err: %s", strerror(errno));
-               }
+       fdot = strchr(name, '.');
+       ldot = strrchr(name, '.');
 
-               *output = name;
-               return GIT_SUCCESS;
+       if (fdot == NULL || ldot == NULL) {
+               git__free(name);
+               return git__throw(GIT_EINVALIDARGS, "Bad format. No dot in '%s'", in);
        }
 
-       /*
-        * 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;
+       /* Downcase up to the first dot and after the last one */
+       git__strntolower(name, fdot - name);
+       git__strtolower(ldot);
+
+       *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;
+       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);
+       )
 
-               ret = cvar_normalize_name(var, &normalized);
-               if (ret < GIT_SUCCESS)
-                       return ret;
-
-               ret = fn(normalized, var->value, data);
-               git__free(normalized);
-               if (ret)
-                       break;
-       }
 
        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;
 
-       var = cvar_list_find(&b->var_list, name);
+       if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
+               return error;
+
+       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;
+
+       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");
 
-       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;
-       }
 
-       return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
+       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)