summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/config_file.c214
-rw-r--r--tests/resources/config/config92
-rw-r--r--tests/t15-config.c22
3 files changed, 231 insertions, 7 deletions
diff --git a/src/config_file.c b/src/config_file.c
index d76c6024d..916b4d081 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -26,9 +26,11 @@
#include "common.h"
#include "config.h"
#include "fileops.h"
+#include "filebuf.h"
#include "git2/config.h"
#include "git2/types.h"
+
#include <ctype.h>
typedef struct cvar_t {
@@ -98,6 +100,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, cvar_t *var);
static void cvar_free(cvar_t *var)
{
@@ -130,7 +133,7 @@ static void cvar_list_free(cvar_t_list *list)
*/
static int cvar_match_section(const char *local, const char *input)
{
- char *first_dot, *last_dot;
+ char *first_dot;
char *local_sp = strchr(local, ' ');
int comparison_len;
@@ -159,12 +162,8 @@ static int cvar_match_section(const char *local, const char *input)
*/
first_dot = strchr(input, '.');
- last_dot = strrchr(input, '.');
comparison_len = strlen(local_sp + 2) - 1;
- if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len)
- return 0;
-
return !strncmp(local_sp + 2, first_dot + 1, comparison_len);
}
@@ -241,6 +240,39 @@ static int cvar_normalize_name(cvar_t *var, char **output)
return GIT_SUCCESS;
}
+static char *interiorize_section(const char *orig)
+{
+ char *dot, *last_dot, *section, *ret;
+ int len;
+
+ dot = strchr(orig, '.');
+ last_dot = strrchr(orig, '.');
+ len = last_dot - orig;
+
+ /* No subsection, this is easy */
+ if (last_dot == dot)
+ return git__strndup(orig, dot - orig);
+
+ section = git__malloc(len + 4);
+ if (section == NULL)
+ return NULL;
+
+ memset(section, 0x0, len + 4);
+ ret = section;
+ len = dot - orig;
+ memcpy(section, orig, len);
+ section += len;
+ len = STRLEN(" \"");
+ memcpy(section, " \"", len);
+ section += len;
+ len = last_dot - dot - 1;
+ memcpy(section, dot + 1, len);
+ section += len;
+ *section = '"';
+
+ return ret;
+}
+
static int config_open(git_config_file *cfg)
{
int error;
@@ -320,7 +352,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
free(existing->value);
existing->value = tmp;
- return GIT_SUCCESS;
+ return config_write(b, existing);
}
/*
@@ -338,7 +370,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
memset(var, 0x0, sizeof(cvar_t));
- var->section = git__strndup(name, last_dot - name);
+ var->section = interiorize_section(name);
if (var->section == NULL) {
error = GIT_ENOMEM;
goto out;
@@ -357,6 +389,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
}
CVAR_LIST_APPEND(&b->var_list, var);
+ error = config_write(b, var);
out:
if (error < GIT_SUCCESS)
@@ -863,6 +896,173 @@ 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)
+{
+ int error;
+
+ error = git_filebuf_printf(file, "[%s]\n", var->section);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ 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)
+{
+ int error = GIT_SUCCESS, c;
+ int section_matches = 0, last_section_matched = 0;
+ char *current_section = NULL;
+ char *var_name, *var_value, *data_start;
+ git_filebuf file;
+ const char *pre_end = NULL, *post_start = NULL;
+
+ /* We need to read in our own config file */
+ error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path);
+ if (error < GIT_SUCCESS) {
+ return git__rethrow(error, "Failed to read existing config file %s", cfg->file_path);
+ }
+
+ /* Initialise the reading position */
+ cfg->reader.read_ptr = cfg->reader.buffer.data;
+ cfg->reader.eof = 0;
+ data_start = cfg->reader.read_ptr;
+
+ /* Lock the file */
+ error = git_filebuf_open(&file, cfg->file_path, 0);
+ if (error < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to lock config file");
+
+ skip_bom(cfg);
+
+ while (error == GIT_SUCCESS && !cfg->reader.eof) {
+ c = cfg_peek(cfg, SKIP_WHITESPACE);
+
+ switch (c) {
+ case '\0': /* We've arrived at the end of the file */
+ break;
+
+ case '[': /* section header, new section begins */
+ /*
+ * We set both positions to the current one in case we
+ * need to add a variable to the end of a section. In that
+ * case, we want both variables to point just before the
+ * new section. If we actually want to replace it, the
+ * default case will take care of updating them.
+ */
+ pre_end = post_start = cfg->reader.read_ptr;
+ free(current_section);
+ error = parse_section_header(cfg, &current_section);
+ if (error < GIT_SUCCESS)
+ break;
+
+ /* Keep track of when it stops matching */
+ last_section_matched = section_matches;
+ section_matches = !strcmp(current_section, var->section);
+ break;
+
+ case ';':
+ case '#':
+ cfg_consume_line(cfg);
+ break;
+
+ default:
+ /*
+ * If the section doesn't match, but the last section did,
+ * it means we need to add a variable (so skip the line
+ * otherwise). If both the section and name match, we need
+ * to overwrite the variable (so skip the line
+ * otherwise). pre_end needs to be updated each time so we
+ * don't loose that information, but we only need to
+ * update post_start if we're going to use it in this
+ * iteration.
+ */
+ if (!section_matches) {
+ if (!last_section_matched) {
+ cfg_consume_line(cfg);
+ break;
+ }
+ } else {
+ pre_end = cfg->reader.read_ptr;
+ error = parse_variable(cfg, &var_name, &var_value);
+ if (error < GIT_SUCCESS || strcasecmp(var->name, var_name))
+ break;
+ post_start = cfg->reader.read_ptr;
+ }
+
+ /*
+ * We've found the variable we wanted to change, so
+ * write anything up to it
+ */
+ error = git_filebuf_write(&file, data_start, pre_end - data_start);
+ if (error < GIT_SUCCESS) {
+ git__rethrow(error, "Failed to write the first part of the file");
+ break;
+ }
+
+ /* Then replace the variable */
+ error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
+ if (error < GIT_SUCCESS) {
+ git__rethrow(error, "Failed to overwrite the variable");
+ break;
+ }
+
+ /* And then the write out rest of the file */
+ error = git_filebuf_write(&file, post_start,
+ cfg->reader.buffer.len - (post_start - data_start));
+
+ if (error < GIT_SUCCESS) {
+ git__rethrow(error, "Failed to write the rest of the file");
+ break;
+ }
+
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Being here can mean that
+ *
+ * 1) our section is the last one in the file and we're
+ * adding a variable
+ *
+ * 2) we didn't find a section for us so we need to create it
+ * ourselves.
+ *
+ * Either way we need to write out the whole file.
+ */
+
+ error = git_filebuf_write(&file, cfg->reader.buffer.data, cfg->reader.buffer.len);
+ if (error < GIT_SUCCESS) {
+ git__rethrow(error, "Failed to write original config content");
+ goto cleanup;
+ }
+
+ /* 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);
+ goto cleanup;
+ }
+
+ /* Or maybe we need to write out a whole section */
+ error = write_section(&file, var);
+ if (error < GIT_SUCCESS)
+ git__rethrow(error, "Failed to write new section");
+
+ cleanup:
+ free(current_section);
+
+ if (error < GIT_SUCCESS)
+ git_filebuf_cleanup(&file);
+ else
+ error = git_filebuf_commit(&file);
+
+ return error;
+}
+
static int is_multiline_var(const char *str)
{
char *end = strrchr(str, '\0') - 1;
diff --git a/tests/resources/config/config9 b/tests/resources/config/config9
new file mode 100644
index 000000000..4359c7826
--- /dev/null
+++ b/tests/resources/config/config9
@@ -0,0 +1,2 @@
+[core]
+ dummy = 1
diff --git a/tests/t15-config.c b/tests/t15-config.c
index 08a2cdbf2..c11c5a932 100644
--- a/tests/t15-config.c
+++ b/tests/t15-config.c
@@ -189,6 +189,27 @@ BEGIN_TEST(config8, "don't fail on empty files")
git_config_free(cfg);
END_TEST
+BEGIN_TEST
+(config9, "replace a value")
+ git_config *cfg;
+ int i;
+
+ /* By freeing the config, we make sure we flush the values */
+ must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config9"));
+ must_pass(git_config_set_int(cfg, "core.dummy", 5));
+ git_config_free(cfg);
+
+ must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config9"));
+ must_pass(git_config_get_int(cfg, "core.dummy", &i));
+ must_be_true(i == 5);
+ git_config_free(cfg);
+
+ must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config9"));
+ must_pass(git_config_set_int(cfg, "core.dummy", 1));
+ git_config_free(cfg);
+
+END_TEST
+
BEGIN_SUITE(config)
ADD_TEST(config0);
ADD_TEST(config1);
@@ -199,4 +220,5 @@ BEGIN_SUITE(config)
ADD_TEST(config6);
ADD_TEST(config7);
ADD_TEST(config8);
+ ADD_TEST(config9);
END_SUITE