summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2018-08-10 19:38:57 +0200
committerPatrick Steinhardt <ps@pks.im>2018-09-28 11:14:13 +0200
commit2be39cefd3ce1fecd4cd76122f8574c53add4dd2 (patch)
treeaad17b7d30556e9142fb7e3a02a53ae9fee68729
parentb78f4ab082030bc02ee2e1a51eca441bfb6f1e8f (diff)
downloadlibgit2-2be39cefd3ce1fecd4cd76122f8574c53add4dd2.tar.gz
config: introduce new read-only in-memory backend
Now that we have abstracted away how to store and retrieve config entries, it became trivial to implement a new in-memory backend by making use of this. And thus we do so. This commit implements a new read-only in-memory backend that can parse a chunk of memory into a `git_config_backend` structure.
-rw-r--r--src/config_backend.h8
-rw-r--r--src/config_mem.c224
-rw-r--r--src/config_parse.c3
-rw-r--r--tests/config/memory.c138
4 files changed, 372 insertions, 1 deletions
diff --git a/src/config_backend.h b/src/config_backend.h
index ea2beafc4..2451f9a1c 100644
--- a/src/config_backend.h
+++ b/src/config_backend.h
@@ -25,6 +25,14 @@
*/
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);
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 6e85bbe0d..0d7054927 100644
--- a/src/config_parse.c
+++ b/src/config_parse.c
@@ -16,8 +16,9 @@ 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/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);
+}