diff options
author | Carlos MartÃn Nieto <carlosmn@github.com> | 2018-09-28 18:04:23 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-28 18:04:23 +0200 |
commit | 0530d7d91c886c09333998e34d370c8ca3e3a7d6 (patch) | |
tree | cb289bb108165cd1a6853870782b7ade34ffdd08 | |
parent | ba1cd4950f47e696281888e2ffa94cc77140f59b (diff) | |
parent | 2be39cefd3ce1fecd4cd76122f8574c53add4dd2 (diff) | |
download | libgit2-0530d7d91c886c09333998e34d370c8ca3e3a7d6.tar.gz |
Merge pull request #4767 from pks-t/pks/config-mem
In-memory configuration
-rw-r--r-- | src/config.c | 233 | ||||
-rw-r--r-- | src/config.h | 15 | ||||
-rw-r--r-- | src/config_backend.h | 83 | ||||
-rw-r--r-- | src/config_entries.c | 259 | ||||
-rw-r--r-- | src/config_entries.h | 23 | ||||
-rw-r--r-- | src/config_file.c | 421 | ||||
-rw-r--r-- | src/config_file.h | 73 | ||||
-rw-r--r-- | src/config_mem.c | 224 | ||||
-rw-r--r-- | src/config_parse.c | 6 | ||||
-rw-r--r-- | src/config_parse.h | 4 | ||||
-rw-r--r-- | src/submodule.c | 36 | ||||
-rw-r--r-- | tests/config/memory.c | 138 | ||||
-rw-r--r-- | tests/config/readonly.c | 12 | ||||
-rw-r--r-- | tests/config/write.c | 1 |
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) |