summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <carlosmn@github.com>2018-09-28 18:04:23 +0200
committerGitHub <noreply@github.com>2018-09-28 18:04:23 +0200
commit0530d7d91c886c09333998e34d370c8ca3e3a7d6 (patch)
treecb289bb108165cd1a6853870782b7ade34ffdd08
parentba1cd4950f47e696281888e2ffa94cc77140f59b (diff)
parent2be39cefd3ce1fecd4cd76122f8574c53add4dd2 (diff)
downloadlibgit2-0530d7d91c886c09333998e34d370c8ca3e3a7d6.tar.gz
Merge pull request #4767 from pks-t/pks/config-mem
In-memory configuration
-rw-r--r--src/config.c233
-rw-r--r--src/config.h15
-rw-r--r--src/config_backend.h83
-rw-r--r--src/config_entries.c259
-rw-r--r--src/config_entries.h23
-rw-r--r--src/config_file.c421
-rw-r--r--src/config_file.h73
-rw-r--r--src/config_mem.c224
-rw-r--r--src/config_parse.c6
-rw-r--r--src/config_parse.h4
-rw-r--r--src/submodule.c36
-rw-r--r--tests/config/memory.c138
-rw-r--r--tests/config/readonly.c12
-rw-r--r--tests/config/write.c1
14 files changed, 983 insertions, 545 deletions
diff --git a/src/config.c b/src/config.c
index 03473af8e..8d2e12f98 100644
--- a/src/config.c
+++ b/src/config.c
@@ -12,7 +12,7 @@
#include "git2/sys/config.h"
#include "vector.h"
#include "buf_text.h"
-#include "config_file.h"
+#include "config_backend.h"
#include "transaction.h"
#if GIT_WIN32
# include <windows.h>
@@ -31,30 +31,30 @@ void git_config_entry_free(git_config_entry *entry)
typedef struct {
git_refcount rc;
- git_config_backend *file;
+ git_config_backend *backend;
git_config_level_t level;
-} file_internal;
+} backend_internal;
-static void file_internal_free(file_internal *internal)
+static void backend_internal_free(backend_internal *internal)
{
- git_config_backend *file;
+ git_config_backend *backend;
- file = internal->file;
- file->free(file);
+ backend = internal->backend;
+ backend->free(backend);
git__free(internal);
}
static void config_free(git_config *cfg)
{
size_t i;
- file_internal *internal;
+ backend_internal *internal;
- for (i = 0; i < cfg->files.length; ++i) {
- internal = git_vector_get(&cfg->files, i);
- GIT_REFCOUNT_DEC(internal, file_internal_free);
+ for (i = 0; i < cfg->backends.length; ++i) {
+ internal = git_vector_get(&cfg->backends, i);
+ GIT_REFCOUNT_DEC(internal, backend_internal_free);
}
- git_vector_free(&cfg->files);
+ git_vector_free(&cfg->backends);
git__memzero(cfg, sizeof(*cfg));
git__free(cfg);
@@ -70,8 +70,8 @@ void git_config_free(git_config *cfg)
static int config_backend_cmp(const void *a, const void *b)
{
- const file_internal *bk_a = (const file_internal *)(a);
- const file_internal *bk_b = (const file_internal *)(b);
+ const backend_internal *bk_a = (const backend_internal *)(a);
+ const backend_internal *bk_b = (const backend_internal *)(b);
return bk_b->level - bk_a->level;
}
@@ -85,7 +85,7 @@ int git_config_new(git_config **out)
memset(cfg, 0x0, sizeof(git_config));
- if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) {
+ if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) {
git__free(cfg);
return -1;
}
@@ -114,7 +114,7 @@ int git_config_add_file_ondisk(
return -1;
}
- if (git_config_file__ondisk(&file, path) < 0)
+ if (git_config_backend_from_file(&file, path) < 0)
return -1;
if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) {
@@ -151,7 +151,7 @@ int git_config_snapshot(git_config **out, git_config *in)
{
int error = 0;
size_t i;
- file_internal *internal;
+ backend_internal *internal;
git_config *config;
*out = NULL;
@@ -159,10 +159,10 @@ int git_config_snapshot(git_config **out, git_config *in)
if (git_config_new(&config) < 0)
return -1;
- git_vector_foreach(&in->files, i, internal) {
+ git_vector_foreach(&in->backends, i, internal) {
git_config_backend *b;
- if ((error = internal->file->snapshot(&b, internal->file)) < 0)
+ if ((error = internal->backend->snapshot(&b, internal->backend)) < 0)
break;
if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) {
@@ -179,24 +179,24 @@ int git_config_snapshot(git_config **out, git_config *in)
return error;
}
-static int find_internal_file_by_level(
- file_internal **internal_out,
+static int find_backend_by_level(
+ backend_internal **out,
const git_config *cfg,
git_config_level_t level)
{
int pos = -1;
- file_internal *internal;
+ backend_internal *internal;
size_t i;
- /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file
- * which has the highest level. As config files are stored in a vector
- * sorted by decreasing order of level, getting the file at position 0
+ /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend
+ * which has the highest level. As config backends are stored in a vector
+ * sorted by decreasing order of level, getting the backend at position 0
* will do the job.
*/
if (level == GIT_CONFIG_HIGHEST_LEVEL) {
pos = 0;
} else {
- git_vector_foreach(&cfg->files, i, internal) {
+ git_vector_foreach(&cfg->backends, i, internal) {
if (internal->level == level)
pos = (int)i;
}
@@ -204,34 +204,34 @@ static int find_internal_file_by_level(
if (pos == -1) {
giterr_set(GITERR_CONFIG,
- "no config file exists for the given level '%i'", (int)level);
+ "no configuration exists for the given level '%i'", (int)level);
return GIT_ENOTFOUND;
}
- *internal_out = git_vector_get(&cfg->files, pos);
+ *out = git_vector_get(&cfg->backends, pos);
return 0;
}
static int duplicate_level(void **old_raw, void *new_raw)
{
- file_internal **old = (file_internal **)old_raw;
+ backend_internal **old = (backend_internal **)old_raw;
GIT_UNUSED(new_raw);
- giterr_set(GITERR_CONFIG, "a file with the same level (%i) has already been added to the config", (int)(*old)->level);
+ giterr_set(GITERR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level);
return GIT_EEXISTS;
}
-static void try_remove_existing_file_internal(
+static void try_remove_existing_backend(
git_config *cfg,
git_config_level_t level)
{
int pos = -1;
- file_internal *internal;
+ backend_internal *internal;
size_t i;
- git_vector_foreach(&cfg->files, i, internal) {
+ git_vector_foreach(&cfg->backends, i, internal) {
if (internal->level == level)
pos = (int)i;
}
@@ -239,32 +239,32 @@ static void try_remove_existing_file_internal(
if (pos == -1)
return;
- internal = git_vector_get(&cfg->files, pos);
+ internal = git_vector_get(&cfg->backends, pos);
- if (git_vector_remove(&cfg->files, pos) < 0)
+ if (git_vector_remove(&cfg->backends, pos) < 0)
return;
- GIT_REFCOUNT_DEC(internal, file_internal_free);
+ GIT_REFCOUNT_DEC(internal, backend_internal_free);
}
static int git_config__add_internal(
git_config *cfg,
- file_internal *internal,
+ backend_internal *internal,
git_config_level_t level,
int force)
{
int result;
- /* delete existing config file for level if it exists */
+ /* delete existing config backend for level if it exists */
if (force)
- try_remove_existing_file_internal(cfg, level);
+ try_remove_existing_backend(cfg, level);
- if ((result = git_vector_insert_sorted(&cfg->files,
+ if ((result = git_vector_insert_sorted(&cfg->backends,
internal, &duplicate_level)) < 0)
return result;
- git_vector_sort(&cfg->files);
- internal->file->cfg = cfg;
+ git_vector_sort(&cfg->backends);
+ internal->backend->cfg = cfg;
GIT_REFCOUNT_INC(internal);
@@ -285,10 +285,10 @@ int git_config_open_level(
git_config_level_t level)
{
git_config *cfg;
- file_internal *internal;
+ backend_internal *internal;
int res;
- if ((res = find_internal_file_by_level(&internal, cfg_parent, level)) < 0)
+ if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0)
return res;
if ((res = git_config_new(&cfg)) < 0)
@@ -306,27 +306,27 @@ int git_config_open_level(
int git_config_add_backend(
git_config *cfg,
- git_config_backend *file,
+ git_config_backend *backend,
git_config_level_t level,
const git_repository *repo,
int force)
{
- file_internal *internal;
+ backend_internal *internal;
int result;
- assert(cfg && file);
+ assert(cfg && backend);
- GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");
+ GITERR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");
- if ((result = file->open(file, level, repo)) < 0)
+ if ((result = backend->open(backend, level, repo)) < 0)
return result;
- internal = git__malloc(sizeof(file_internal));
+ internal = git__malloc(sizeof(backend_internal));
GITERR_CHECK_ALLOC(internal);
- memset(internal, 0x0, sizeof(file_internal));
+ memset(internal, 0x0, sizeof(backend_internal));
- internal->file = file;
+ internal->backend = backend;
internal->level = level;
if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) {
@@ -351,11 +351,11 @@ typedef struct {
static int find_next_backend(size_t *out, const git_config *cfg, size_t i)
{
- file_internal *internal;
+ backend_internal *internal;
for (; i > 0; --i) {
- internal = git_vector_get(&cfg->files, i - 1);
- if (!internal || !internal->file)
+ internal = git_vector_get(&cfg->backends, i - 1);
+ if (!internal || !internal->backend)
continue;
*out = i;
@@ -368,7 +368,7 @@ static int find_next_backend(size_t *out, const git_config *cfg, size_t i)
static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
{
all_iter *iter = (all_iter *) _iter;
- file_internal *internal;
+ backend_internal *internal;
git_config_backend *backend;
size_t i;
int error = 0;
@@ -385,8 +385,8 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
if (find_next_backend(&i, iter->cfg, iter->i) < 0)
return GIT_ITEROVER;
- internal = git_vector_get(&iter->cfg->files, i - 1);
- backend = internal->file;
+ internal = git_vector_get(&iter->cfg->backends, i - 1);
+ backend = internal->backend;
iter->i = i - 1;
if (iter->current)
@@ -461,7 +461,7 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg)
iter->parent.free = all_iter_free;
iter->parent.next = all_iter_next;
- iter->i = cfg->files.length;
+ iter->i = cfg->backends.length;
iter->cfg = cfg;
*out = (git_config_iterator *) iter;
@@ -488,7 +488,7 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf
iter->parent.next = all_iter_glob_next;
iter->parent.free = all_iter_glob_free;
- iter->i = cfg->files.length;
+ iter->i = cfg->backends.length;
iter->cfg = cfg;
*out = (git_config_iterator *) iter;
@@ -592,38 +592,38 @@ static int get_backend_for_use(git_config_backend **out,
git_config *cfg, const char *name, backend_use use)
{
size_t i;
- file_internal *f;
+ backend_internal *backend;
*out = NULL;
- if (git_vector_length(&cfg->files) == 0) {
+ if (git_vector_length(&cfg->backends) == 0) {
giterr_set(GITERR_CONFIG,
- "cannot %s value for '%s' when no config files exist",
+ "cannot %s value for '%s' when no config backends exist",
uses[use], name);
return GIT_ENOTFOUND;
}
- git_vector_foreach(&cfg->files, i, f) {
- if (!f->file->readonly) {
- *out = f->file;
+ git_vector_foreach(&cfg->backends, i, backend) {
+ if (!backend->backend->readonly) {
+ *out = backend->backend;
return 0;
}
}
giterr_set(GITERR_CONFIG,
- "cannot %s value for '%s' when all config files are readonly",
+ "cannot %s value for '%s' when all config backends are readonly",
uses[use], name);
return GIT_ENOTFOUND;
}
int git_config_delete_entry(git_config *cfg, const char *name)
{
- git_config_backend *file;
+ git_config_backend *backend;
- if (get_backend_for_use(&file, cfg, name, BACKEND_USE_DELETE) < 0)
+ if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0)
return GIT_ENOTFOUND;
- return file->del(file, name);
+ return backend->del(backend, name);
}
int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
@@ -646,17 +646,17 @@ int git_config_set_bool(git_config *cfg, const char *name, int value)
int git_config_set_string(git_config *cfg, const char *name, const char *value)
{
int error;
- git_config_backend *file;
+ git_config_backend *backend;
if (!value) {
giterr_set(GITERR_CONFIG, "the value to set cannot be NULL");
return -1;
}
- if (get_backend_for_use(&file, cfg, name, BACKEND_USE_SET) < 0)
+ if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0)
return GIT_ENOTFOUND;
- error = file->set(file, name, value);
+ error = backend->set(backend, name, value);
if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg));
@@ -722,7 +722,7 @@ static int get_entry(
const char *key = name;
char *normalized = NULL;
size_t i;
- file_internal *internal;
+ backend_internal *internal;
*out = NULL;
@@ -733,11 +733,11 @@ static int get_entry(
}
res = GIT_ENOTFOUND;
- git_vector_foreach(&cfg->files, i, internal) {
- if (!internal || !internal->file)
+ git_vector_foreach(&cfg->backends, i, internal) {
+ if (!internal || !internal->backend)
continue;
- res = internal->file->get(internal->file, key, out);
+ res = internal->backend->get(internal->backend, key, out);
if (res != GIT_ENOTFOUND)
break;
}
@@ -835,13 +835,13 @@ int git_config_get_bool(int *out, const git_config *cfg, const char *name)
static int is_readonly(const git_config *cfg)
{
size_t i;
- file_internal *internal;
+ backend_internal *internal;
- git_vector_foreach(&cfg->files, i, internal) {
- if (!internal || !internal->file)
+ git_vector_foreach(&cfg->backends, i, internal) {
+ if (!internal || !internal->backend)
continue;
- if (!internal->file->readonly)
+ if (!internal->backend->readonly)
return 0;
}
@@ -1058,22 +1058,22 @@ on_error:
int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
{
- git_config_backend *file;
+ git_config_backend *backend;
- if (get_backend_for_use(&file, cfg, name, BACKEND_USE_DELETE) < 0)
+ if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0)
return GIT_ENOTFOUND;
- return file->set_multivar(file, name, regexp, value);
+ return backend->set_multivar(backend, name, regexp, value);
}
int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp)
{
- git_config_backend *file;
+ git_config_backend *backend;
- if (get_backend_for_use(&file, cfg, name, BACKEND_USE_DELETE) < 0)
+ if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0)
return GIT_ENOTFOUND;
- return file->del_multivar(file, name, regexp);
+ return backend->del_multivar(backend, name, regexp);
}
int git_config_next(git_config_entry **entry, git_config_iterator *iter)
@@ -1179,17 +1179,17 @@ int git_config_open_default(git_config **out)
int git_config_lock(git_transaction **out, git_config *cfg)
{
int error;
- git_config_backend *file;
- file_internal *internal;
+ git_config_backend *backend;
+ backend_internal *internal;
- internal = git_vector_get(&cfg->files, 0);
- if (!internal || !internal->file) {
- giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files");
+ internal = git_vector_get(&cfg->backends, 0);
+ if (!internal || !internal->backend) {
+ giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends");
return -1;
}
- file = internal->file;
+ backend = internal->backend;
- if ((error = file->lock(file)) < 0)
+ if ((error = backend->lock(backend)) < 0)
return error;
return git_transaction_config_new(out, cfg);
@@ -1197,18 +1197,18 @@ int git_config_lock(git_transaction **out, git_config *cfg)
int git_config_unlock(git_config *cfg, int commit)
{
- git_config_backend *file;
- file_internal *internal;
+ git_config_backend *backend;
+ backend_internal *internal;
- internal = git_vector_get(&cfg->files, 0);
- if (!internal || !internal->file) {
- giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files");
+ internal = git_vector_get(&cfg->backends, 0);
+ if (!internal || !internal->backend) {
+ giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends");
return -1;
}
- file = internal->file;
+ backend = internal->backend;
- return file->unlock(file, commit);
+ return backend->unlock(backend, commit);
}
/***********
@@ -1376,6 +1376,30 @@ int git_config_parse_path(git_buf *out, const char *value)
return git_buf_sets(out, value);
}
+static int normalize_section(char *start, char *end)
+{
+ char *scan;
+
+ if (start == end)
+ return GIT_EINVALIDSPEC;
+
+ /* Validate and downcase range */
+ for (scan = start; *scan; ++scan) {
+ if (end && scan >= end)
+ break;
+ if (isalnum(*scan))
+ *scan = (char)git__tolower(*scan);
+ else if (*scan != '-' || scan == start)
+ return GIT_EINVALIDSPEC;
+ }
+
+ if (scan == start)
+ return GIT_EINVALIDSPEC;
+
+ return 0;
+}
+
+
/* Take something the user gave us and make it nice for our hash function */
int git_config__normalize_name(const char *in, char **out)
{
@@ -1393,8 +1417,8 @@ int git_config__normalize_name(const char *in, char **out)
goto invalid;
/* Validate and downcase up to first dot and after last dot */
- if (git_config_file_normalize_section(name, fdot) < 0 ||
- git_config_file_normalize_section(ldot + 1, NULL) < 0)
+ if (normalize_section(name, fdot) < 0 ||
+ normalize_section(ldot + 1, NULL) < 0)
goto invalid;
/* If there is a middle range, make sure it doesn't have newlines */
@@ -1466,8 +1490,7 @@ int git_config_rename_section(
goto cleanup;
if (new_section_name != NULL &&
- (error = git_config_file_normalize_section(
- replace.ptr, strchr(replace.ptr, '.'))) < 0)
+ (error = normalize_section(replace.ptr, strchr(replace.ptr, '.'))) < 0)
{
giterr_set(
GITERR_CONFIG, "invalid config section '%s'", new_section_name);
diff --git a/src/config.h b/src/config.h
index a5fcf2e84..f5855113f 100644
--- a/src/config.h
+++ b/src/config.h
@@ -24,7 +24,7 @@
struct git_config {
git_refcount rc;
- git_vector files;
+ git_vector backends;
};
extern int git_config__global_location(git_buf *buf);
@@ -34,19 +34,6 @@ extern int git_config_rename_section(
const char *old_section_name, /* eg "branch.dummy" */
const char *new_section_name); /* NULL to drop the old section */
-/**
- * Create a configuration file backend for ondisk files
- *
- * These are the normal `.gitconfig` files that Core Git
- * processes. Note that you first have to add this file to a
- * configuration object before you can query it for configuration
- * variables.
- *
- * @param out the new backend
- * @param path where the config file is located
- */
-extern int git_config_file__ondisk(git_config_backend **out, const char *path);
-
extern int git_config__normalize_name(const char *in, char **out);
/* internal only: does not normalize key and sets out to NULL if not found */
diff --git a/src/config_backend.h b/src/config_backend.h
new file mode 100644
index 000000000..2451f9a1c
--- /dev/null
+++ b/src/config_backend.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_config_file_h__
+#define INCLUDE_config_file_h__
+
+#include "common.h"
+
+#include "git2/sys/config.h"
+#include "git2/config.h"
+
+/**
+ * Create a configuration file backend for ondisk files
+ *
+ * These are the normal `.gitconfig` files that Core Git
+ * processes. Note that you first have to add this file to a
+ * configuration object before you can query it for configuration
+ * variables.
+ *
+ * @param out the new backend
+ * @param path where the config file is located
+ */
+extern int git_config_backend_from_file(git_config_backend **out, const char *path);
+
+/**
+ * Create an in-memory configuration file backend
+ *
+ * @param out the new backend
+ * @param cfg the configuration that is to be parsed
+ */
+extern int git_config_backend_from_string(git_config_backend **out, const char *cfg);
+
+GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo)
+{
+ return cfg->open(cfg, level, repo);
+}
+
+GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg)
+{
+ if (cfg)
+ cfg->free(cfg);
+}
+
+GIT_INLINE(int) git_config_backend_get_string(
+ git_config_entry **out, git_config_backend *cfg, const char *name)
+{
+ return cfg->get(cfg, name, out);
+}
+
+GIT_INLINE(int) git_config_backend_set_string(
+ git_config_backend *cfg, const char *name, const char *value)
+{
+ return cfg->set(cfg, name, value);
+}
+
+GIT_INLINE(int) git_config_backend_delete(
+ git_config_backend *cfg, const char *name)
+{
+ return cfg->del(cfg, name);
+}
+
+GIT_INLINE(int) git_config_backend_foreach(
+ git_config_backend *cfg,
+ int (*fn)(const git_config_entry *entry, void *data),
+ void *data)
+{
+ return git_config_backend_foreach_match(cfg, NULL, fn, data);
+}
+
+GIT_INLINE(int) git_config_backend_lock(git_config_backend *cfg)
+{
+ return cfg->lock(cfg);
+}
+
+GIT_INLINE(int) git_config_backend_unlock(git_config_backend *cfg, int success)
+{
+ return cfg->unlock(cfg, success);
+}
+
+#endif
diff --git a/src/config_entries.c b/src/config_entries.c
new file mode 100644
index 000000000..fccce2773
--- /dev/null
+++ b/src/config_entries.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "config_entries.h"
+
+typedef struct config_entry_list {
+ struct config_entry_list *next;
+ struct config_entry_list *last;
+ git_config_entry *entry;
+} config_entry_list;
+
+typedef struct config_entries_iterator {
+ git_config_iterator parent;
+ git_config_entries *entries;
+ config_entry_list *head;
+} config_entries_iterator;
+
+struct git_config_entries {
+ git_refcount rc;
+ git_strmap *map;
+ config_entry_list *list;
+};
+
+static void config_entry_list_free(config_entry_list *list)
+{
+ config_entry_list *next;
+
+ while (list != NULL) {
+ next = list->next;
+
+ git__free((char*) list->entry->name);
+ git__free((char *) list->entry->value);
+ git__free(list->entry);
+ git__free(list);
+
+ list = next;
+ };
+}
+
+static void config_entry_list_append(config_entry_list **list, config_entry_list *entry)
+{
+ if (*list)
+ (*list)->last->next = entry;
+ else
+ *list = entry;
+ (*list)->last = entry;
+}
+
+int git_config_entries_new(git_config_entries **out)
+{
+ git_config_entries *entries;
+ int error;
+
+ entries = git__calloc(1, sizeof(git_config_entries));
+ GITERR_CHECK_ALLOC(entries);
+ GIT_REFCOUNT_INC(entries);
+
+ if ((error = git_strmap_alloc(&entries->map)) < 0)
+ git__free(entries);
+ else
+ *out = entries;
+
+ return error;
+}
+
+int git_config_entries_dup(git_config_entries **out, git_config_entries *entries)
+{
+ git_config_entries *result = NULL;
+ config_entry_list *head;
+ int error;
+
+ if ((error = git_config_entries_new(&result)) < 0)
+ goto out;
+
+ for (head = entries->list; head; head = head->next) {
+ git_config_entry *dup;
+
+ dup = git__calloc(1, sizeof(git_config_entry));
+ dup->name = git__strdup(head->entry->name);
+ GITERR_CHECK_ALLOC(dup->name);
+ if (head->entry->value) {
+ dup->value = git__strdup(head->entry->value);
+ GITERR_CHECK_ALLOC(dup->value);
+ }
+ dup->level = head->entry->level;
+ dup->include_depth = head->entry->include_depth;
+
+ if ((error = git_config_entries_append(result, dup)) < 0)
+ goto out;
+ }
+
+ *out = result;
+ result = NULL;
+
+out:
+ git_config_entries_free(result);
+ return error;
+}
+
+void git_config_entries_incref(git_config_entries *entries)
+{
+ GIT_REFCOUNT_INC(entries);
+}
+
+static void config_entries_free(git_config_entries *entries)
+{
+ config_entry_list *list = NULL, *next;
+
+ git_strmap_foreach_value(entries->map, list, config_entry_list_free(list));
+ git_strmap_free(entries->map);
+
+ list = entries->list;
+ while (list != NULL) {
+ next = list->next;
+ git__free(list);
+ list = next;
+ }
+
+ git__free(entries);
+}
+
+void git_config_entries_free(git_config_entries *entries)
+{
+ if (entries)
+ GIT_REFCOUNT_DEC(entries, config_entries_free);
+}
+
+int git_config_entries_append(git_config_entries *entries, git_config_entry *entry)
+{
+ git_strmap_iter pos;
+ config_entry_list *existing, *var;
+ int error = 0;
+
+ var = git__calloc(1, sizeof(config_entry_list));
+ GITERR_CHECK_ALLOC(var);
+ var->entry = entry;
+
+ pos = git_strmap_lookup_index(entries->map, entry->name);
+ if (!git_strmap_valid_index(entries->map, pos)) {
+ /*
+ * We only ever inspect `last` from the first config
+ * entry in a multivar. In case where this new entry is
+ * the first one in the entry map, it will also be the
+ * last one at the time of adding it, which is
+ * why we set `last` here to itself. Otherwise we
+ * do not have to set `last` and leave it set to
+ * `NULL`.
+ */
+ var->last = var;
+
+ git_strmap_insert(entries->map, entry->name, var, &error);
+
+ if (error > 0)
+ error = 0;
+ } else {
+ existing = git_strmap_value_at(entries->map, pos);
+ config_entry_list_append(&existing, var);
+ }
+
+ var = git__calloc(1, sizeof(config_entry_list));
+ GITERR_CHECK_ALLOC(var);
+ var->entry = entry;
+ config_entry_list_append(&entries->list, var);
+
+ return error;
+}
+
+int config_entry_get(config_entry_list **out, git_config_entries *entries, const char *key)
+{
+ khiter_t pos;
+
+ pos = git_strmap_lookup_index(entries->map, key);
+
+ /* no error message; the config system will write one */
+ if (!git_strmap_valid_index(entries->map, pos))
+ return GIT_ENOTFOUND;
+
+ *out = git_strmap_value_at(entries->map, pos);
+
+ return 0;
+}
+
+int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key)
+{
+ config_entry_list *entry;
+ int error;
+
+ if ((error = config_entry_get(&entry, entries, key)) < 0)
+ return error;
+ *out = entry->last->entry;
+
+ return 0;
+}
+
+int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key)
+{
+ config_entry_list *entry;
+ int error;
+
+ if ((error = config_entry_get(&entry, entries, key)) < 0)
+ return error;
+
+ if (entry->next != NULL) {
+ giterr_set(GITERR_CONFIG, "entry is not unique due to being a multivar");
+ return -1;
+ }
+
+ if (entry->entry->include_depth) {
+ giterr_set(GITERR_CONFIG, "entry is not unique due to being included");
+ return -1;
+ }
+
+ *out = entry->entry;
+
+ return 0;
+}
+
+void config_iterator_free(git_config_iterator *iter)
+{
+ config_entries_iterator *it = (config_entries_iterator *) iter;
+ git_config_entries_free(it->entries);
+ git__free(it);
+}
+
+int config_iterator_next(
+ git_config_entry **entry,
+ git_config_iterator *iter)
+{
+ config_entries_iterator *it = (config_entries_iterator *) iter;
+
+ if (!it->head)
+ return GIT_ITEROVER;
+
+ *entry = it->head->entry;
+ it->head = it->head->next;
+
+ return 0;
+}
+
+int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries)
+{
+ config_entries_iterator *it;
+
+ it = git__calloc(1, sizeof(config_entries_iterator));
+ GITERR_CHECK_ALLOC(it);
+ it->parent.next = config_iterator_next;
+ it->parent.free = config_iterator_free;
+ it->head = entries->list;
+ it->entries = entries;
+
+ git_config_entries_incref(entries);
+ *out = &it->parent;
+
+ return 0;
+}
diff --git a/src/config_entries.h b/src/config_entries.h
new file mode 100644
index 000000000..6fdbc41ba
--- /dev/null
+++ b/src/config_entries.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+
+#include "git2/sys/config.h"
+#include "config.h"
+
+typedef struct git_config_entries git_config_entries;
+
+int git_config_entries_new(git_config_entries **out);
+int git_config_entries_dup(git_config_entries **out, git_config_entries *entries);
+void git_config_entries_incref(git_config_entries *entries);
+void git_config_entries_free(git_config_entries *entries);
+/* Add or append the new config option */
+int git_config_entries_append(git_config_entries *entries, git_config_entry *entry);
+int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key);
+int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key);
+int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries);
diff --git a/src/config_file.c b/src/config_file.c
index 26bc200df..e8740d35f 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -5,9 +5,8 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "config_file.h"
-
#include "config.h"
+
#include "filebuf.h"
#include "sysdir.h"
#include "buffer.h"
@@ -18,36 +17,20 @@
#include "strmap.h"
#include "array.h"
#include "config_parse.h"
+#include "config_entries.h"
#include <ctype.h>
#include <sys/types.h>
#include <regex.h>
-typedef struct config_entry_list {
- struct config_entry_list *next;
- struct config_entry_list *last;
- git_config_entry *entry;
-} config_entry_list;
-
-typedef struct git_config_file_iter {
- git_config_iterator parent;
- config_entry_list *head;
-} git_config_file_iter;
-
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
typedef struct {
- git_atomic refcount;
- git_strmap *map;
- config_entry_list *list;
-} diskfile_entries;
-
-typedef struct {
git_config_backend parent;
/* mutex to coordinate accessing the values */
git_mutex values_mutex;
- diskfile_entries *entries;
+ git_config_entries *entries;
const git_repository *repo;
git_config_level_t level;
} diskfile_header;
@@ -73,16 +56,15 @@ typedef struct {
typedef struct {
const git_repository *repo;
const char *file_path;
- diskfile_entries *entries;
+ git_config_entries *entries;
git_config_level_t level;
unsigned int depth;
} diskfile_parse_state;
-static int config_read(diskfile_entries *entries, const git_repository *repo, git_config_file *file, git_config_level_t level, int depth);
+static int config_read(git_config_entries *entries, const git_repository *repo, git_config_file *file, git_config_level_t level, int depth);
static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char *value);
static char *escape_value(const char *ptr);
-int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
static int config_snapshot(git_config_backend **out, git_config_backend *in);
static int config_error_readonly(void)
@@ -91,126 +73,14 @@ static int config_error_readonly(void)
return -1;
}
-static void config_entry_list_free(config_entry_list *list)
-{
- config_entry_list *next;
-
- while (list != NULL) {
- next = list->next;
-
- git__free((char*) list->entry->name);
- git__free((char *) list->entry->value);
- git__free(list->entry);
- git__free(list);
-
- list = next;
- };
-}
-
-int git_config_file_normalize_section(char *start, char *end)
-{
- char *scan;
-
- if (start == end)
- return GIT_EINVALIDSPEC;
-
- /* Validate and downcase range */
- for (scan = start; *scan; ++scan) {
- if (end && scan >= end)
- break;
- if (isalnum(*scan))
- *scan = (char)git__tolower(*scan);
- else if (*scan != '-' || scan == start)
- return GIT_EINVALIDSPEC;
- }
-
- if (scan == start)
- return GIT_EINVALIDSPEC;
-
- return 0;
-}
-
-static void config_entry_list_append(config_entry_list **list, config_entry_list *entry)
-{
- if (*list)
- (*list)->last->next = entry;
- else
- *list = entry;
- (*list)->last = entry;
-}
-
-/* Add or append the new config option */
-static int diskfile_entries_append(diskfile_entries *entries, git_config_entry *entry)
-{
- git_strmap_iter pos;
- config_entry_list *existing, *var;
- int error = 0;
-
- var = git__calloc(1, sizeof(config_entry_list));
- GITERR_CHECK_ALLOC(var);
- var->entry = entry;
-
- pos = git_strmap_lookup_index(entries->map, entry->name);
- if (!git_strmap_valid_index(entries->map, pos)) {
- /*
- * We only ever inspect `last` from the first config
- * entry in a multivar. In case where this new entry is
- * the first one in the entry map, it will also be the
- * last one at the time of adding it, which is
- * why we set `last` here to itself. Otherwise we
- * do not have to set `last` and leave it set to
- * `NULL`.
- */
- var->last = var;
-
- git_strmap_insert(entries->map, entry->name, var, &error);
-
- if (error > 0)
- error = 0;
- } else {
- existing = git_strmap_value_at(entries->map, pos);
- config_entry_list_append(&existing, var);
- }
-
- var = git__calloc(1, sizeof(config_entry_list));
- GITERR_CHECK_ALLOC(var);
- var->entry = entry;
- config_entry_list_append(&entries->list, var);
-
- return error;
-}
-
-static void diskfile_entries_free(diskfile_entries *entries)
-{
- config_entry_list *list = NULL, *next;
-
- if (!entries)
- return;
-
- if (git_atomic_dec(&entries->refcount) != 0)
- return;
-
- git_strmap_foreach_value(entries->map, list, config_entry_list_free(list));
- git_strmap_free(entries->map);
-
- list = entries->list;
- while (list != NULL) {
- next = list->next;
- git__free(list);
- list = next;
- }
-
- git__free(entries);
-}
-
/**
* Take the current values map from the backend and increase its
* refcount. This is its own function to make sure we use the mutex to
* avoid the map pointer from changing under us.
*/
-static diskfile_entries *diskfile_entries_take(diskfile_header *h)
+static git_config_entries *diskfile_entries_take(diskfile_header *h)
{
- diskfile_entries *entries;
+ git_config_entries *entries;
if (git_mutex_lock(&h->values_mutex) < 0) {
giterr_set(GITERR_OS, "failed to lock config backend");
@@ -218,31 +88,13 @@ static diskfile_entries *diskfile_entries_take(diskfile_header *h)
}
entries = h->entries;
- git_atomic_inc(&entries->refcount);
+ git_config_entries_incref(entries);
git_mutex_unlock(&h->values_mutex);
return entries;
}
-static int diskfile_entries_alloc(diskfile_entries **out)
-{
- diskfile_entries *entries;
- int error;
-
- entries = git__calloc(1, sizeof(diskfile_entries));
- GITERR_CHECK_ALLOC(entries);
-
- git_atomic_set(&entries->refcount, 1);
-
- if ((error = git_strmap_alloc(&entries->map)) < 0)
- git__free(entries);
- else
- *out = entries;
-
- return error;
-}
-
static void config_file_clear(struct config_file *file)
{
struct config_file *include;
@@ -267,14 +119,14 @@ static int config_open(git_config_backend *cfg, git_config_level_t level, const
b->header.level = level;
b->header.repo = repo;
- if ((res = diskfile_entries_alloc(&b->header.entries)) < 0)
+ if ((res = git_config_entries_new(&b->header.entries)) < 0)
return res;
if (!git_path_exists(b->file.path))
return 0;
if (res < 0 || (res = config_read(b->header.entries, repo, &b->file, level, 0)) < 0) {
- diskfile_entries_free(b->header.entries);
+ git_config_entries_free(b->header.entries);
b->header.entries = NULL;
}
@@ -316,7 +168,7 @@ out:
static int config_refresh(git_config_backend *cfg)
{
diskfile_backend *b = (diskfile_backend *)cfg;
- diskfile_entries *entries = NULL, *tmp;
+ git_config_entries *entries = NULL, *tmp;
git_config_file *include;
int error, modified;
uint32_t i;
@@ -331,7 +183,7 @@ static int config_refresh(git_config_backend *cfg)
if (!modified)
return 0;
- if ((error = diskfile_entries_alloc(&entries)) < 0)
+ if ((error = git_config_entries_new(&entries)) < 0)
goto out;
/* Reparse the current configuration */
@@ -355,7 +207,7 @@ static int config_refresh(git_config_backend *cfg)
git_mutex_unlock(&b->header.values_mutex);
out:
- diskfile_entries_free(entries);
+ git_config_entries_free(entries);
return (error == GIT_ENOTFOUND) ? 0 : error;
}
@@ -368,133 +220,80 @@ static void backend_free(git_config_backend *_backend)
return;
config_file_clear(&backend->file);
- diskfile_entries_free(backend->header.entries);
+ git_config_entries_free(backend->header.entries);
git_mutex_free(&backend->header.values_mutex);
git__free(backend);
}
-static void config_iterator_free(
- git_config_iterator* iter)
-{
- iter->backend->free(iter->backend);
- git__free(iter);
-}
-
-static int config_iterator_next(
- git_config_entry **entry,
- git_config_iterator *iter)
-{
- git_config_file_iter *it = (git_config_file_iter *) iter;
-
- if (!it->head)
- return GIT_ITEROVER;
-
- *entry = it->head->entry;
- it->head = it->head->next;
-
- return 0;
-}
-
static int config_iterator_new(
git_config_iterator **iter,
struct git_config_backend* backend)
{
- diskfile_header *h;
- git_config_file_iter *it;
- git_config_backend *snapshot;
diskfile_header *bh = (diskfile_header *) backend;
+ git_config_entries *entries;
int error;
- if ((error = config_snapshot(&snapshot, backend)) < 0)
- return error;
-
- if ((error = snapshot->open(snapshot, bh->level, bh->repo)) < 0)
+ if ((error = git_config_entries_dup(&entries, bh->entries)) < 0)
return error;
- it = git__calloc(1, sizeof(git_config_file_iter));
- GITERR_CHECK_ALLOC(it);
-
- h = (diskfile_header *)snapshot;
-
- it->parent.backend = snapshot;
- it->head = h->entries->list;
- it->parent.next = config_iterator_next;
- it->parent.free = config_iterator_free;
-
- *iter = (git_config_iterator *) it;
+ if ((error = git_config_entries_iterator_new(iter, entries)) < 0)
+ goto out;
- return 0;
+out:
+ /* Let iterator delete duplicated entries when it's done */
+ git_config_entries_free(entries);
+ return error;
}
static int config_set(git_config_backend *cfg, const char *name, const char *value)
{
diskfile_backend *b = (diskfile_backend *)cfg;
- diskfile_entries *entries;
- git_strmap *entry_map;
+ git_config_entries *entries;
+ git_config_entry *existing;
char *key, *esc_value = NULL;
- khiter_t pos;
- int rval, ret;
+ int error;
- if ((rval = git_config__normalize_name(name, &key)) < 0)
- return rval;
+ if ((error = git_config__normalize_name(name, &key)) < 0)
+ return error;
if ((entries = diskfile_entries_take(&b->header)) == NULL)
return -1;
- entry_map = entries->map;
- /*
- * Try to find it in the existing values and update it if it
- * only has one value.
- */
- pos = git_strmap_lookup_index(entry_map, key);
- if (git_strmap_valid_index(entry_map, pos)) {
- config_entry_list *existing = git_strmap_value_at(entry_map, pos);
-
- if (existing->next != NULL) {
- giterr_set(GITERR_CONFIG, "multivar incompatible with simple set");
- ret = -1;
- goto out;
- }
-
- if (existing->entry->include_depth) {
- giterr_set(GITERR_CONFIG, "modifying included variable is not supported");
- ret = -1;
+ /* Check whether we'd be modifying an included or multivar key */
+ if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) {
+ if (error != GIT_ENOTFOUND)
goto out;
- }
-
+ error = 0;
+ } else if ((!existing->value && !value) ||
+ (existing->value && value && !strcmp(existing->value, value))) {
/* don't update if old and new values already match */
- if ((!existing->entry->value && !value) ||
- (existing->entry->value && value &&
- !strcmp(existing->entry->value, value))) {
- ret = 0;
- goto out;
- }
+ error = 0;
+ goto out;
}
/* No early returns due to sanity checks, let's write it out and refresh */
-
if (value) {
esc_value = escape_value(value);
GITERR_CHECK_ALLOC(esc_value);
}
- if ((ret = config_write(b, name, key, NULL, esc_value)) < 0)
+ if ((error = config_write(b, name, key, NULL, esc_value)) < 0)
goto out;
- ret = config_refresh(cfg);
+ error = config_refresh(cfg);
out:
- diskfile_entries_free(entries);
+ git_config_entries_free(entries);
git__free(esc_value);
git__free(key);
- return ret;
+ return error;
}
/* release the map containing the entry as an equivalent to freeing it */
static void free_diskfile_entry(git_config_entry *entry)
{
- diskfile_entries *map = (diskfile_entries *) entry->payload;
- diskfile_entries_free(map);
+ git_config_entries *entries = (git_config_entries *) entry->payload;
+ git_config_entries_free(entries);
}
/*
@@ -503,10 +302,8 @@ static void free_diskfile_entry(git_config_entry *entry)
static int config_get(git_config_backend *cfg, const char *key, git_config_entry **out)
{
diskfile_header *h = (diskfile_header *)cfg;
- diskfile_entries *entries;
- git_strmap *entry_map;
- khiter_t pos;
- config_entry_list *var;
+ git_config_entries *entries = NULL;
+ git_config_entry *entry;
int error = 0;
if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0))
@@ -514,22 +311,17 @@ static int config_get(git_config_backend *cfg, const char *key, git_config_entry
if ((entries = diskfile_entries_take(h)) == NULL)
return -1;
- entry_map = entries->map;
-
- pos = git_strmap_lookup_index(entry_map, key);
- /* no error message; the config system will write one */
- if (!git_strmap_valid_index(entry_map, pos)) {
- diskfile_entries_free(entries);
- return GIT_ENOTFOUND;
+ if ((error = (git_config_entries_get(&entry, entries, key))) < 0) {
+ git_config_entries_free(entries);
+ return error;
}
- var = git_strmap_value_at(entry_map, pos);
- *out = var->last->entry;
- (*out)->free = free_diskfile_entry;
- (*out)->payload = entries;
+ entry->free = free_diskfile_entry;
+ entry->payload = entries;
+ *out = entry;
- return error;
+ return 0;
}
static int config_set_multivar(
@@ -567,79 +359,61 @@ out:
static int config_delete(git_config_backend *cfg, const char *name)
{
- config_entry_list *var;
diskfile_backend *b = (diskfile_backend *)cfg;
- diskfile_entries *map;
- git_strmap *entry_map;
- char *key;
- int result;
- khiter_t pos;
-
- if ((result = git_config__normalize_name(name, &key)) < 0)
- return result;
-
- if ((map = diskfile_entries_take(&b->header)) == NULL)
- return -1;
- entry_map = b->header.entries->map;
-
- pos = git_strmap_lookup_index(entry_map, key);
- git__free(key);
+ git_config_entries *entries = NULL;
+ git_config_entry *entry;
+ char *key = NULL;
+ int error;
- if (!git_strmap_valid_index(entry_map, pos)) {
- diskfile_entries_free(map);
- giterr_set(GITERR_CONFIG, "could not find key '%s' to delete", name);
- return GIT_ENOTFOUND;
- }
+ if ((error = git_config__normalize_name(name, &key)) < 0)
+ goto out;
- var = git_strmap_value_at(entry_map, pos);
- diskfile_entries_free(map);
+ if ((entries = diskfile_entries_take(&b->header)) == NULL)
+ goto out;
- if (var->entry->include_depth) {
- giterr_set(GITERR_CONFIG, "cannot delete included variable");
- return -1;
+ /* Check whether we'd be modifying an included or multivar key */
+ if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_CONFIG, "could not find key '%s' to delete", name);
+ goto out;
}
- if (var->next != NULL) {
- giterr_set(GITERR_CONFIG, "cannot delete multivar with a single delete");
- return -1;
- }
+ if ((error = config_write(b, name, entry->name, NULL, NULL)) < 0)
+ goto out;
- if ((result = config_write(b, name, var->entry->name, NULL, NULL)) < 0)
- return result;
+ if ((error = config_refresh(cfg)) < 0)
+ goto out;
- return config_refresh(cfg);
+out:
+ git_config_entries_free(entries);
+ git__free(key);
+ return error;
}
static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
diskfile_backend *b = (diskfile_backend *)cfg;
- diskfile_entries *map;
- git_strmap *entry_map;
- char *key;
- regex_t preg;
+ git_config_entries *entries = NULL;
+ git_config_entry *entry = NULL;
+ regex_t preg = { 0 };
+ char *key = NULL;
int result;
- khiter_t pos;
if ((result = git_config__normalize_name(name, &key)) < 0)
- return result;
-
- if ((map = diskfile_entries_take(&b->header)) == NULL)
- return -1;
- entry_map = b->header.entries->map;
-
- pos = git_strmap_lookup_index(entry_map, key);
+ goto out;
- if (!git_strmap_valid_index(entry_map, pos)) {
- diskfile_entries_free(map);
- git__free(key);
- giterr_set(GITERR_CONFIG, "could not find key '%s' to delete", name);
- return GIT_ENOTFOUND;
+ if ((entries = diskfile_entries_take(&b->header)) == NULL) {
+ result = -1;
+ goto out;
}
- diskfile_entries_free(map);
+ if ((result = git_config_entries_get(&entry, entries, key)) < 0) {
+ if (result == GIT_ENOTFOUND)
+ giterr_set(GITERR_CONFIG, "could not find key '%s' to delete", name);
+ goto out;
+ }
- result = p_regcomp(&preg, regexp, REG_EXTENDED);
- if (result != 0) {
+ if ((result = p_regcomp(&preg, regexp, REG_EXTENDED)) != 0) {
giterr_set_regex(&preg, result);
result = -1;
goto out;
@@ -648,21 +422,16 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con
if ((result = config_write(b, name, key, &preg, NULL)) < 0)
goto out;
- result = config_refresh(cfg);
+ if ((result = config_refresh(cfg)) < 0)
+ goto out;
out:
+ git_config_entries_free(entries);
git__free(key);
regfree(&preg);
return result;
}
-static int config_snapshot(git_config_backend **out, git_config_backend *in)
-{
- diskfile_backend *b = (diskfile_backend *) in;
-
- return git_config_file__snapshot(out, b);
-}
-
static int config_lock(git_config_backend *_cfg)
{
diskfile_backend *cfg = (diskfile_backend *) _cfg;
@@ -699,7 +468,7 @@ static int config_unlock(git_config_backend *_cfg, int success)
return error;
}
-int git_config_file__ondisk(git_config_backend **out, const char *path)
+int git_config_backend_from_file(git_config_backend **out, const char *path)
{
diskfile_backend *backend;
@@ -789,7 +558,7 @@ static void backend_readonly_free(git_config_backend *_backend)
if (backend == NULL)
return;
- diskfile_entries_free(backend->header.entries);
+ git_config_entries_free(backend->header.entries);
git_mutex_free(&backend->header.values_mutex);
git__free(backend);
}
@@ -799,7 +568,7 @@ static int config_readonly_open(git_config_backend *cfg, git_config_level_t leve
diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
diskfile_backend *src = b->snapshot_from;
diskfile_header *src_header = &src->header;
- diskfile_entries *entries;
+ git_config_entries *entries;
int error;
if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0)
@@ -816,7 +585,7 @@ static int config_readonly_open(git_config_backend *cfg, git_config_level_t leve
return 0;
}
-int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in)
+static int config_snapshot(git_config_backend **out, git_config_backend *in)
{
diskfile_readonly_backend *backend;
@@ -826,7 +595,7 @@ int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in)
backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
git_mutex_init(&backend->header.values_mutex);
- backend->snapshot_from = in;
+ backend->snapshot_from = (diskfile_backend *) in;
backend->header.parent.readonly = 1;
backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
@@ -1062,7 +831,7 @@ static int read_on_variable(
entry->level = parse_data->level;
entry->include_depth = parse_data->depth;
- if ((result = diskfile_entries_append(parse_data->entries, entry)) < 0)
+ if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
return result;
result = 0;
@@ -1079,7 +848,7 @@ static int read_on_variable(
}
static int config_read(
- diskfile_entries *entries,
+ git_config_entries *entries,
const git_repository *repo,
git_config_file *file,
git_config_level_t level,
diff --git a/src/config_file.h b/src/config_file.h
deleted file mode 100644
index 72818e58c..000000000
--- a/src/config_file.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#ifndef INCLUDE_config_file_h__
-#define INCLUDE_config_file_h__
-
-#include "common.h"
-
-#include "git2/sys/config.h"
-#include "git2/config.h"
-
-GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level, const git_repository *repo)
-{
- return cfg->open(cfg, level, repo);
-}
-
-GIT_INLINE(void) git_config_file_free(git_config_backend *cfg)
-{
- if (cfg)
- cfg->free(cfg);
-}
-
-GIT_INLINE(int) git_config_file_get_string(
- git_config_entry **out, git_config_backend *cfg, const char *name)
-{
- return cfg->get(cfg, name, out);
-}
-
-GIT_INLINE(int) git_config_file_set_string(
- git_config_backend *cfg, const char *name, const char *value)
-{
- return cfg->set(cfg, name, value);
-}
-
-GIT_INLINE(int) git_config_file_delete(
- git_config_backend *cfg, const char *name)
-{
- return cfg->del(cfg, name);
-}
-
-GIT_INLINE(int) git_config_file_foreach(
- git_config_backend *cfg,
- int (*fn)(const git_config_entry *entry, void *data),
- void *data)
-{
- return git_config_backend_foreach_match(cfg, NULL, fn, data);
-}
-
-GIT_INLINE(int) git_config_file_foreach_match(
- git_config_backend *cfg,
- const char *regexp,
- int (*fn)(const git_config_entry *entry, void *data),
- void *data)
-{
- return git_config_backend_foreach_match(cfg, regexp, fn, data);
-}
-
-GIT_INLINE(int) git_config_file_lock(git_config_backend *cfg)
-{
- return cfg->lock(cfg);
-}
-
-GIT_INLINE(int) git_config_file_unlock(git_config_backend *cfg, int success)
-{
- return cfg->unlock(cfg, success);
-}
-
-extern int git_config_file_normalize_section(char *start, char *end);
-
-#endif
diff --git a/src/config_mem.c b/src/config_mem.c
new file mode 100644
index 000000000..fbb6373c3
--- /dev/null
+++ b/src/config_mem.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "config.h"
+
+#include "config_backend.h"
+#include "config_parse.h"
+#include "config_entries.h"
+
+typedef struct {
+ git_config_backend parent;
+ git_config_entries *entries;
+ git_buf cfg;
+} config_memory_backend;
+
+typedef struct {
+ git_config_entries *entries;
+ git_config_level_t level;
+} config_memory_parse_data;
+
+static int config_error_readonly(void)
+{
+ giterr_set(GITERR_CONFIG, "this backend is read-only");
+ return -1;
+}
+
+static int read_variable_cb(
+ git_config_parser *reader,
+ const char *current_section,
+ const char *var_name,
+ const char *var_value,
+ const char *line,
+ size_t line_len,
+ void *payload)
+{
+ config_memory_parse_data *parse_data = (config_memory_parse_data *) payload;
+ git_buf buf = GIT_BUF_INIT;
+ git_config_entry *entry;
+ const char *c;
+ int result;
+
+ GIT_UNUSED(reader);
+ GIT_UNUSED(line);
+ GIT_UNUSED(line_len);
+
+ if (current_section) {
+ /* TODO: Once warnings land, we should likely warn
+ * here. Git appears to warn in most cases if it sees
+ * un-namespaced config options.
+ */
+ git_buf_puts(&buf, current_section);
+ git_buf_putc(&buf, '.');
+ }
+
+ for (c = var_name; *c; c++)
+ git_buf_putc(&buf, git__tolower(*c));
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ entry = git__calloc(1, sizeof(git_config_entry));
+ GITERR_CHECK_ALLOC(entry);
+ entry->name = git_buf_detach(&buf);
+ entry->value = var_value ? git__strdup(var_value) : NULL;
+ entry->level = parse_data->level;
+ entry->include_depth = 0;
+
+ if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
+ return result;
+
+ return result;
+}
+
+static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo)
+{
+ config_memory_backend *memory_backend = (config_memory_backend *) backend;
+ config_memory_parse_data parse_data;
+ git_config_parser reader;
+
+ GIT_UNUSED(repo);
+
+ if (memory_backend->cfg.size == 0)
+ return 0;
+
+ git_parse_ctx_init(&reader.ctx, memory_backend->cfg.ptr, memory_backend->cfg.size);
+ reader.file = NULL;
+ parse_data.entries = memory_backend->entries;
+ parse_data.level = level;
+
+ return git_config_parse(&reader, NULL, read_variable_cb, NULL, NULL, &parse_data);
+}
+
+static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out)
+{
+ config_memory_backend *memory_backend = (config_memory_backend *) backend;
+ return git_config_entries_get(out, memory_backend->entries, key);
+}
+
+static int config_memory_iterator(
+ git_config_iterator **iter,
+ git_config_backend *backend)
+{
+ config_memory_backend *memory_backend = (config_memory_backend *) backend;
+ git_config_entries *entries;
+ int error;
+
+ if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0)
+ goto out;
+
+ if ((error = git_config_entries_iterator_new(iter, entries)) < 0)
+ goto out;
+
+out:
+ /* Let iterator delete duplicated entries when it's done */
+ git_config_entries_free(entries);
+ return error;
+}
+
+static int config_memory_set(git_config_backend *backend, const char *name, const char *value)
+{
+ GIT_UNUSED(backend);
+ GIT_UNUSED(name);
+ GIT_UNUSED(value);
+ return config_error_readonly();
+}
+
+static int config_memory_set_multivar(
+ git_config_backend *backend, const char *name, const char *regexp, const char *value)
+{
+ GIT_UNUSED(backend);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+ GIT_UNUSED(value);
+ return config_error_readonly();
+}
+
+static int config_memory_delete(git_config_backend *backend, const char *name)
+{
+ GIT_UNUSED(backend);
+ GIT_UNUSED(name);
+ return config_error_readonly();
+}
+
+static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp)
+{
+ GIT_UNUSED(backend);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+ return config_error_readonly();
+}
+
+static int config_memory_lock(git_config_backend *backend)
+{
+ GIT_UNUSED(backend);
+ return config_error_readonly();
+}
+
+static int config_memory_unlock(git_config_backend *backend, int success)
+{
+ GIT_UNUSED(backend);
+ GIT_UNUSED(success);
+ return config_error_readonly();
+}
+
+static int config_memory_snapshot(git_config_backend **out, git_config_backend *backend)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(backend);
+ giterr_set(GITERR_CONFIG, "this backend does not support snapshots");
+ return -1;
+}
+
+static void config_memory_free(git_config_backend *_backend)
+{
+ config_memory_backend *backend = (config_memory_backend *)_backend;
+
+ if (backend == NULL)
+ return;
+
+ git_config_entries_free(backend->entries);
+ git_buf_dispose(&backend->cfg);
+ git__free(backend);
+}
+
+int git_config_backend_from_string(git_config_backend **out, const char *cfg)
+{
+ config_memory_backend *backend;
+
+ backend = git__calloc(1, sizeof(config_memory_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ if (git_config_entries_new(&backend->entries) < 0) {
+ git__free(backend);
+ return -1;
+ }
+
+ if (git_buf_sets(&backend->cfg, cfg) < 0) {
+ git_config_entries_free(backend->entries);
+ git__free(backend);
+ return -1;
+ }
+
+ backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
+ backend->parent.readonly = 1;
+ backend->parent.open = config_memory_open;
+ backend->parent.get = config_memory_get;
+ backend->parent.set = config_memory_set;
+ backend->parent.set_multivar = config_memory_set_multivar;
+ backend->parent.del = config_memory_delete;
+ backend->parent.del_multivar = config_memory_delete_multivar;
+ backend->parent.iterator = config_memory_iterator;
+ backend->parent.lock = config_memory_lock;
+ backend->parent.unlock = config_memory_unlock;
+ backend->parent.snapshot = config_memory_snapshot;
+ backend->parent.free = config_memory_free;
+
+ *out = (git_config_backend *)backend;
+
+ return 0;
+}
diff --git a/src/config_parse.c b/src/config_parse.c
index 282aabe17..0d7054927 100644
--- a/src/config_parse.c
+++ b/src/config_parse.c
@@ -11,10 +11,14 @@
#include <ctype.h>
+const char *git_config_escapes = "ntb\"\\";
+const char *git_config_escaped = "\n\t\b\"\\";
+
static void set_parse_error(git_config_parser *reader, int col, const char *error_str)
{
+ const char *file = reader->file ? reader->file->path : "in-memory";
giterr_set(GITERR_CONFIG, "failed to parse config file: %s (in %s:%"PRIuZ", column %d)",
- error_str, reader->file->path, reader->ctx.line_num, col);
+ error_str, file, reader->ctx.line_num, col);
}
diff --git a/src/config_parse.h b/src/config_parse.h
index 6650b87f3..81a13fceb 100644
--- a/src/config_parse.h
+++ b/src/config_parse.h
@@ -12,8 +12,8 @@
#include "oid.h"
#include "parse.h"
-static const char *git_config_escapes = "ntb\"\\";
-static const char *git_config_escaped = "\n\t\b\"\\";
+extern const char *git_config_escapes;
+extern const char *git_config_escaped;
typedef struct config_file {
git_oid checksum;
diff --git a/src/submodule.c b/src/submodule.c
index 3cbddfa3e..9231f08b1 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -15,7 +15,7 @@
#include "buf_text.h"
#include "vector.h"
#include "posix.h"
-#include "config_file.h"
+#include "config_backend.h"
#include "config.h"
#include "repository.h"
#include "tree.h"
@@ -199,13 +199,15 @@ out:
*/
static void free_submodule_names(git_strmap *names)
{
- git_buf *name;
+ const char *key;
+ char *value;
if (names == NULL)
return;
- git_strmap_foreach_value(names, name, {
- git__free(name);
+ git_strmap_foreach(names, key, value, {
+ git__free((char *) key);
+ git__free(value);
});
git_strmap_free(names);
@@ -257,7 +259,7 @@ static int load_submodule_names(git_strmap **out, git_repository *repo, git_conf
if (!isvalid)
continue;
- git_strmap_insert(names, entry->value, git_buf_detach(&buf), &rval);
+ git_strmap_insert(names, git__strdup(entry->value), git_buf_detach(&buf), &rval);
if (rval < 0) {
giterr_set(GITERR_NOMEMORY, "error inserting submodule into hash table");
error = -1;
@@ -333,9 +335,9 @@ int git_submodule_lookup(
mods = open_gitmodules(repo, GITMODULES_EXISTING);
if (mods)
- error = git_config_file_foreach_match(mods, pattern, find_by_path, &data);
+ error = git_config_backend_foreach_match(mods, pattern, find_by_path, &data);
- git_config_file_free(mods);
+ git_config_backend_free(mods);
if (error < 0) {
git_submodule_free(sm);
@@ -792,11 +794,11 @@ int git_submodule_add_setup(
}
if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
- (error = git_config_file_set_string(mods, name.ptr, path)) < 0)
+ (error = git_config_backend_set_string(mods, name.ptr, path)) < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
- (error = git_config_file_set_string(mods, name.ptr, url)) < 0)
+ (error = git_config_backend_set_string(mods, name.ptr, url)) < 0)
goto cleanup;
git_buf_clear(&name);
@@ -834,7 +836,7 @@ cleanup:
if (out != NULL)
*out = sm;
- git_config_file_free(mods);
+ git_config_backend_free(mods);
git_repository_free(subrepo);
git_buf_dispose(&real_url);
git_buf_dispose(&name);
@@ -1033,14 +1035,14 @@ static int write_var(git_repository *repo, const char *name, const char *var, co
goto cleanup;
if (val)
- error = git_config_file_set_string(mods, key.ptr, val);
+ error = git_config_backend_set_string(mods, key.ptr, val);
else
- error = git_config_file_delete(mods, key.ptr);
+ error = git_config_backend_delete(mods, key.ptr);
git_buf_dispose(&key);
cleanup:
- git_config_file_free(mods);
+ git_config_backend_free(mods);
return error;
}
@@ -2070,12 +2072,12 @@ static git_config_backend *open_gitmodules(
return NULL;
if (okay_to_create || git_path_isfile(path.ptr)) {
- /* git_config_file__ondisk should only fail if OOM */
- if (git_config_file__ondisk(&mods, path.ptr) < 0)
+ /* git_config_backend_from_file should only fail if OOM */
+ if (git_config_backend_from_file(&mods, path.ptr) < 0)
mods = NULL;
/* open should only fail here if the file is malformed */
- else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) {
- git_config_file_free(mods);
+ else if (git_config_backend_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) {
+ git_config_backend_free(mods);
mods = NULL;
}
}
diff --git a/tests/config/memory.c b/tests/config/memory.c
new file mode 100644
index 000000000..aed221c0d
--- /dev/null
+++ b/tests/config/memory.c
@@ -0,0 +1,138 @@
+#include "clar_libgit2.h"
+
+#include "config_backend.h"
+
+static git_config_backend *backend;
+
+void test_config_memory__initialize(void)
+{
+ backend = NULL;
+}
+
+void test_config_memory__cleanup(void)
+{
+ git_config_backend_free(backend);
+}
+
+static void assert_config_contains(git_config_backend *backend,
+ const char *name, const char *value)
+{
+ git_config_entry *entry;
+ cl_git_pass(git_config_backend_get_string(&entry, backend, name));
+ cl_assert_equal_s(entry->value, value);
+}
+
+struct expected_entry {
+ const char *name;
+ const char *value;
+ int seen;
+};
+
+static int contains_all_cb(const git_config_entry *entry, void *payload)
+{
+ struct expected_entry *entries = (struct expected_entry *) payload;
+ int i;
+
+ for (i = 0; entries[i].name; i++) {
+ if (strcmp(entries[i].name, entry->name) ||
+ strcmp(entries[i].value , entry->value))
+ continue;
+
+ if (entries[i].seen)
+ cl_fail("Entry seen more than once");
+ entries[i].seen = 1;
+ return 0;
+ }
+
+ cl_fail("Unexpected entry");
+ return -1;
+}
+
+static void assert_config_contains_all(git_config_backend *backend,
+ struct expected_entry *entries)
+{
+ int i;
+
+ cl_git_pass(git_config_backend_foreach(backend, contains_all_cb, entries));
+
+ for (i = 0; entries[i].name; i++)
+ cl_assert(entries[i].seen);
+}
+
+static void setup_backend(const char *cfg)
+{
+ cl_git_pass(git_config_backend_from_string(&backend, cfg));
+ cl_git_pass(git_config_backend_open(backend, 0, NULL));
+}
+
+void test_config_memory__write_operations_fail(void)
+{
+ setup_backend("");
+ cl_git_fail(git_config_backend_set_string(backend, "general.foo", "var"));
+ cl_git_fail(git_config_backend_delete(backend, "general.foo"));
+ cl_git_fail(git_config_backend_lock(backend));
+ cl_git_fail(git_config_backend_unlock(backend, 0));
+}
+
+void test_config_memory__simple(void)
+{
+ setup_backend(
+ "[general]\n"
+ "foo=bar\n");
+
+ assert_config_contains(backend, "general.foo", "bar");
+}
+
+void test_config_memory__malformed_fails_to_open(void)
+{
+ cl_git_pass(git_config_backend_from_string(&backend,
+ "[general\n"
+ "foo=bar\n"));
+ cl_git_fail(git_config_backend_open(backend, 0, NULL));
+}
+
+void test_config_memory__multiple_vars(void)
+{
+ setup_backend(
+ "[general]\n"
+ "foo=bar\n"
+ "key=value\n");
+ assert_config_contains(backend, "general.foo", "bar");
+ assert_config_contains(backend, "general.key", "value");
+}
+
+void test_config_memory__multiple_sections(void)
+{
+ setup_backend(
+ "[general]\n"
+ "foo=bar\n"
+ "\n"
+ "[other]\n"
+ "key=value\n");
+ assert_config_contains(backend, "general.foo", "bar");
+ assert_config_contains(backend, "other.key", "value");
+}
+
+void test_config_memory__multivar_gets_correct_string(void)
+{
+ setup_backend(
+ "[general]\n"
+ "foo=bar1\n"
+ "foo=bar2\n");
+ assert_config_contains(backend, "general.foo", "bar2");
+}
+
+void test_config_memory__foreach_sees_multivar(void)
+{
+ struct expected_entry entries[] = {
+ { "general.foo", "bar1", 0 },
+ { "general.foo", "bar2", 0 },
+ { NULL, NULL, 0 },
+ };
+
+ setup_backend(
+ "[general]\n"
+ "foo=bar1\n"
+ "foo=bar2\n");
+ assert_config_contains_all(backend, entries);
+}
diff --git a/tests/config/readonly.c b/tests/config/readonly.c
index a424922c1..5d544b8cb 100644
--- a/tests/config/readonly.c
+++ b/tests/config/readonly.c
@@ -1,5 +1,5 @@
#include "clar_libgit2.h"
-#include "config_file.h"
+#include "config_backend.h"
#include "config.h"
#include "path.h"
@@ -20,7 +20,7 @@ void test_config_readonly__writing_to_readonly_fails(void)
{
git_config_backend *backend;
- cl_git_pass(git_config_file__ondisk(&backend, "global"));
+ cl_git_pass(git_config_backend_from_file(&backend, "global"));
backend->readonly = 1;
cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0));
@@ -32,11 +32,11 @@ void test_config_readonly__writing_to_cfg_with_rw_precedence_succeeds(void)
{
git_config_backend *backend;
- cl_git_pass(git_config_file__ondisk(&backend, "global"));
+ cl_git_pass(git_config_backend_from_file(&backend, "global"));
backend->readonly = 1;
cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0));
- cl_git_pass(git_config_file__ondisk(&backend, "local"));
+ cl_git_pass(git_config_backend_from_file(&backend, "local"));
cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0));
cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz"));
@@ -50,11 +50,11 @@ void test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds(void)
{
git_config_backend *backend;
- cl_git_pass(git_config_file__ondisk(&backend, "local"));
+ cl_git_pass(git_config_backend_from_file(&backend, "local"));
backend->readonly = 1;
cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0));
- cl_git_pass(git_config_file__ondisk(&backend, "global"));
+ cl_git_pass(git_config_backend_from_file(&backend, "global"));
cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0));
cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz"));
diff --git a/tests/config/write.c b/tests/config/write.c
index 521dcb0ae..bd0f5b277 100644
--- a/tests/config/write.c
+++ b/tests/config/write.c
@@ -2,7 +2,6 @@
#include "buffer.h"
#include "fileops.h"
#include "git2/sys/config.h"
-#include "config_file.h"
#include "config.h"
void test_config_write__initialize(void)