diff options
author | Patrick Steinhardt <ps@pks.im> | 2018-08-10 19:38:57 +0200 |
---|---|---|
committer | Patrick Steinhardt <ps@pks.im> | 2018-09-28 11:14:13 +0200 |
commit | 2be39cefd3ce1fecd4cd76122f8574c53add4dd2 (patch) | |
tree | aad17b7d30556e9142fb7e3a02a53ae9fee68729 | |
parent | b78f4ab082030bc02ee2e1a51eca441bfb6f1e8f (diff) | |
download | libgit2-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.h | 8 | ||||
-rw-r--r-- | src/config_mem.c | 224 | ||||
-rw-r--r-- | src/config_parse.c | 3 | ||||
-rw-r--r-- | tests/config/memory.c | 138 |
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); +} |