Commit 3005855f7e3980185adc63a68c5b8b5f9e3b506f

Carlos Martín Nieto 2012-02-05T00:29:26

Implement setting multivars

diff --git a/include/git2/config.h b/include/git2/config.h
index 1f037c8..82d9de8 100644
--- a/include/git2/config.h
+++ b/include/git2/config.h
@@ -31,6 +31,7 @@ struct git_config_file {
 	int (*get)(struct git_config_file *, const char *key, const char **value);
 	int (*get_multivar)(struct git_config_file *, const char *key, const char *regexp, int (*fn)(const char *, void *), void *data);
 	int (*set)(struct git_config_file *, const char *key, const char *value);
+	int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value);
 	int (*del)(struct git_config_file *, const char *key);
 	int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data);
 	void (*free)(struct git_config_file *);
@@ -255,6 +256,12 @@ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value
  */
 GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value);
 
+
+/**
+ * Set a multivar
+ */
+GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value);
+
 /**
  * Delete a config variable
  *
diff --git a/src/config.c b/src/config.c
index ccc7362..4ff1b2e 100644
--- a/src/config.c
+++ b/src/config.c
@@ -364,6 +364,24 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex
 	return GIT_SUCCESS;
 }
 
+int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
+{
+	file_internal *internal;
+	git_config_file *file;
+	int error = GIT_ENOTFOUND;
+	unsigned int i;
+
+	for (i = cfg->files.length; i > 0; --i) {
+		internal = git_vector_get(&cfg->files, i - 1);
+		file = internal->file;
+		error = file->set_multivar(file, name, regexp, value);
+		if (error < GIT_SUCCESS && error != GIT_ENOTFOUND)
+			git__rethrow(error, "Failed to replace multivar");
+	}
+
+	return GIT_SUCCESS;
+}
+
 int git_config_find_global_r(git_buf *path)
 {
 	return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
diff --git a/src/config_file.c b/src/config_file.c
index 3d29b20..346bb7a 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -84,7 +84,7 @@ 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, const char *key, const char *value);
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
 
 static void cvar_free(cvar_t *var)
 {
@@ -240,7 +240,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
 		git__free(existing->value);
 		existing->value = tmp;
 
-		return config_write(b, existing->key, value);
+		return config_write(b, existing->key, NULL, value);
 	}
 
 	var = git__malloc(sizeof(cvar_t));
@@ -263,7 +263,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
 
 	cvar_free(old_value);
 
-	error = config_write(b, key, value);
+	error = config_write(b, key, NULL, value);
 
  out:
 	if (error < GIT_SUCCESS)
@@ -314,7 +314,7 @@ static int config_get_multivar(git_config_file *cfg, const char *name, const cha
 		return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
 
 	if (regexp != NULL) {
-		error = regcomp(&preg, regexp, 0);
+		error = regcomp(&preg, regexp, REG_EXTENDED);
 		if (error < 0)
 			return git__throw(GIT_EINVALIDARGS, "Failed to compile regex");
 	}
@@ -335,6 +335,88 @@ static int config_get_multivar(git_config_file *cfg, const char *name, const cha
 	return error;
 }
 
+static int config_set_multivar(git_config_file *cfg, const char *name, const char *regexp, const char *value)
+{
+	int error;
+	cvar_t *var;
+	diskfile_backend *b = (diskfile_backend *)cfg;
+	char *key;
+	regex_t preg;
+
+	if (regexp == NULL)
+		return git__throw(GIT_EINVALIDARGS, "No regex supplied");
+
+	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);
+
+	error = regcomp(&preg, regexp, REG_EXTENDED);
+	if (error < 0)
+		return git__throw(GIT_EINVALIDARGS, "Failed to compile regex");
+
+
+	/* "^$" means we need to addd */
+	if (!regexec(&preg, "", 0, NULL, 0)) {
+		cvar_t *newvar = git__malloc(sizeof(cvar_t));
+		if (newvar == NULL) {
+			error = GIT_ENOMEM;
+			goto exit;
+		}
+
+		memset(newvar, 0x0, sizeof(cvar_t));
+		newvar->key = git__strdup(var->key);
+		if (newvar->key == NULL) {
+			error = GIT_ENOMEM;
+			goto exit;
+		}
+		newvar->value = git__strdup(value);
+		if (newvar->value == NULL) {
+			error = GIT_ENOMEM;
+			goto exit;
+		}
+
+		while (var->next != NULL) {
+			var = var->next;
+		}
+
+		var->next = newvar;
+		error = config_write(b, var->key, &preg, value);
+		if (error < GIT_SUCCESS) {
+			error = git__rethrow(error, "Failed to update value in file");
+			goto exit;
+		}
+	}
+
+	do {
+		if (!regexec(&preg, var->value, 0, NULL, 0)) {
+			char *tmp = git__strdup(value);
+			if (tmp == NULL) {
+				error = GIT_ENOMEM;
+				goto exit;
+			}
+
+			free(var->value);
+			var->value = tmp;
+			error = config_write(b, var->key, &preg, var->value);
+			if (error < GIT_SUCCESS) {
+				error = git__rethrow(error, "Failed to update value in file");
+				goto exit;
+			}
+		}
+
+		var = var->next;
+	} while (var != NULL);
+
+ exit:
+	regfree(&preg);
+	return error;
+}
+
 static int config_delete(git_config_file *cfg, const char *name)
 {
 	int error;
@@ -359,7 +441,7 @@ static int config_delete(git_config_file *cfg, const char *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);
+	error = config_write(b, var->key, NULL, NULL);
 	cvar_free(old_value);
 
 	return error;
@@ -385,6 +467,7 @@ int git_config_file__ondisk(git_config_file **out, const char *path)
 	backend->parent.get = config_get;
 	backend->parent.get_multivar = config_get_multivar;
 	backend->parent.set = config_set;
+	backend->parent.set_multivar = config_set_multivar;
 	backend->parent.del = config_delete;
 	backend->parent.foreach = file_foreach;
 	backend->parent.free = backend_free;
@@ -874,7 +957,7 @@ static int write_section(git_filebuf *file, const char *key)
 /*
  * This is pretty much the parsing, except we write out anything we don't have
  */
-static int config_write(diskfile_backend *cfg, const char *key, const char* value)
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
 {
 	int error = GIT_SUCCESS, c;
 	int section_matches = 0, last_section_matched = 0;
@@ -960,6 +1043,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const char* valu
 				if (!last_section_matched) {
 					cfg_consume_line(cfg);
 					break;
+				} else {
+					/* As a last attempt, if we were given "^$", we should add it */
+					if (preg != NULL && regexec(preg, "", 0, NULL, 0))
+						break;
 				}
 			} else {
 				int cmp = -1;
@@ -968,6 +1055,9 @@ static int config_write(diskfile_backend *cfg, const char *key, const char* valu
 				if ((error = parse_variable(cfg, &var_name, &var_value)) == GIT_SUCCESS)
 					cmp = strcasecmp(name, var_name);
 
+				if (preg != NULL)
+					cmp = regexec(preg, var_value, 0, NULL, 0);
+
 				git__free(var_name);
 				git__free(var_value);
 
@@ -1034,8 +1124,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const char* valu
 
 	/* And now if we just need to add a variable */
 	if (section_matches) {
-		error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
-		goto cleanup;
+		if (preg == NULL || !regexec(preg, "", 0, NULL, 0)) {
+			error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
+			goto cleanup;
+		}
 	}
 
 	/* Or maybe we need to write out a whole section */
@@ -1043,7 +1135,8 @@ static int config_write(diskfile_backend *cfg, const char *key, const char* valu
 	if (error < GIT_SUCCESS)
 		git__rethrow(error, "Failed to write new section");
 
-	error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
+	if (preg == NULL || !regexec(preg, "", 0, NULL, 0))
+		error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
  cleanup:
 	git__free(section);
 	git__free(current_section);
diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c
index 48d284d..4cf5a37 100644
--- a/tests-clar/config/multivar.c
+++ b/tests-clar/config/multivar.c
@@ -4,7 +4,7 @@ static int mv_read_cb(const char *name, const char *GIT_UNUSED(value), void *dat
 {
 	int *n = (int *) data;
 
-	if (!strcmp(name, "remote.fancy.fetch"))
+	if (!strcmp(name, "remote.fancy.url"))
 		(*n)++;
 
 	return 0;
@@ -35,7 +35,7 @@ static int cb(const char *GIT_UNUSED(val), void *data)
 void test_config_multivar__get(void)
 {
 	git_config *cfg;
-	const char *name = "remote.fancy.fetch";
+	const char *name = "remote.fancy.url";
 	int n;
 
 	cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config11")));
@@ -50,3 +50,39 @@ void test_config_multivar__get(void)
 
 	git_config_free(cfg);
 }
+
+void test_config_multivar__add(void)
+{
+	git_config *cfg;
+	const char *name = "remote.fancy.url";
+	int n;
+
+	cl_fixture_sandbox("config");
+	cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+	cl_git_pass(git_config_set_multivar(cfg, name, "^$", "git://git.otherplace.org/libgit2"));
+
+	n = 0;
+	cl_git_pass(git_config_get_multivar(cfg, name, NULL, cb, &n));
+	cl_assert(n == 3);
+
+	n = 0;
+	cl_git_pass(git_config_get_multivar(cfg, name, "otherplace", cb, &n));
+	cl_assert(n == 1);
+
+	git_config_free(cfg);
+
+	/* We know it works in memory, let's see if the file is written correctly */
+
+	cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+	n = 0;
+	cl_git_pass(git_config_get_multivar(cfg, name, NULL, cb, &n));
+	cl_assert(n == 3);
+
+
+	n = 0;
+	cl_git_pass(git_config_get_multivar(cfg, name, "otherplace", cb, &n));
+	cl_assert(n == 1);
+
+	git_config_free(cfg);
+}
diff --git a/tests/resources/config/config11 b/tests/resources/config/config11
index bda653b..880c945 100644
--- a/tests/resources/config/config11
+++ b/tests/resources/config/config11
@@ -1,3 +1,3 @@
 [remote "fancy"]
-    fetch = git://github.com/libgit2/libgit2
-    fetch = git://git.example.com/libgit2
+    url = git://github.com/libgit2/libgit2
+    url = git://git.example.com/libgit2