From 3793fa9b181f3595c24a1cc517646f8e7a4a7175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Rodri=CC=81guez=20Troitin=CC=83o?= Date: Thu, 31 Oct 2013 01:08:50 +0100 Subject: Fix saving remotes with several fetch/push ref specs. At some moment git_config_delete_entry lost the ability to delete one entry of a multivar configuration. The moment you had more than one fetch or push ref spec for a remote you will not be able to save that remote anymore. The changes in network::remote::remotes::save show that problem. I needed to create a new git_config_delete_multivar because I was not able to remove one or several entries of a multivar config with the current API. Several tries modifying how git_config_set_multivar(..., NULL) behaved were not successful. git_config_delete_multivar is very similar to git_config_set_multivar, and delegates into config_delete_multivar of config_file. This function search for the cvar_t that will be deleted, storing them in a temporal array, and rebuilding the linked list. After calling config_write to delete the entries, the cvar_t stored in the temporal array are freed. There is a little fix in config_write, it avoids an infinite loop when using a regular expression (case for the multivars). This error was found by the test network::remote::remotes::tagopt. --- src/config_file.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) (limited to 'src/config_file.c') diff --git a/src/config_file.c b/src/config_file.c index 8fb43b990..8c3d8121c 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -120,6 +120,18 @@ static void cvar_free(cvar_t *var) git__free(var); } +static int cvar_length(cvar_t *var) +{ + int length = 0; + + while (var) { + length += 1; + var = var->next; + } + + return length; +} + int git_config_file_normalize_section(char *start, char *end) { char *scan; @@ -531,6 +543,81 @@ static int config_delete(git_config_backend *cfg, const char *name) return result; } +static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +{ + cvar_t *var, *prev = NULL, *new_head = NULL; + cvar_t **to_delete; + int to_delete_idx; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + regex_t preg; + int result; + khiter_t pos; + + if ((result = git_config__normalize_name(name, &key)) < 0) + return result; + + pos = git_strmap_lookup_index(b->values, key); + + if (!git_strmap_valid_index(b->values, pos)) { + giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); + git__free(key); + return GIT_ENOTFOUND; + } + + var = git_strmap_value_at(b->values, pos); + + result = regcomp(&preg, regexp, REG_EXTENDED); + if (result < 0) { + git__free(key); + giterr_set_regex(&preg, result); + regfree(&preg); + return -1; + } + + to_delete = git__calloc(cvar_length(var), sizeof(cvar_t *)); + GITERR_CHECK_ALLOC(to_delete); + to_delete_idx = 0; + + for (;;) { + cvar_t *var_next = var->next; + + if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) { + // If we are past the head, reattach previous node to next one, + // otherwise set the new head for the strmap. + if (prev != NULL) { + prev->next = var_next; + } else { + new_head = var_next; + } + + to_delete[to_delete_idx++] = var; + } else { + prev = var; + } + + if (var_next == NULL) + break; + + var = var_next; + } + + if (new_head != NULL) { + git_strmap_set_value_at(b->values, pos, new_head); + } else { + git_strmap_delete_at(b->values, pos); + } + + if (to_delete_idx > 0) + result = config_write(b, key, &preg, NULL); + + while (to_delete_idx-- > 0) + cvar_free(to_delete[to_delete_idx]); + + git__free(key); + return result; +} + int git_config_file__ondisk(git_config_backend **out, const char *path) { diskfile_backend *backend; @@ -548,6 +635,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; + backend->parent.del_multivar = config_delete_multivar; backend->parent.iterator = config_iterator_new; backend->parent.refresh = config_refresh; backend->parent.free = backend_free; @@ -1214,7 +1302,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p } /* multiline variable? we need to keep reading lines to match */ - if (preg != NULL) { + if (preg != NULL && section_matches) { data_start = post_start; continue; } -- cgit v1.2.1