mirror of
https://git.proxmox.com/git/libgit2
synced 2026-01-04 15:17:48 +00:00
commit
607d164380
@ -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, ¤t_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;
|
||||
|
||||
BIN
tests/resources/config/config9
Normal file
BIN
tests/resources/config/config9
Normal file
Binary file not shown.
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user