From 0e0781f6f3d51b6eda93010db3c56530cb3953af Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 20 Jul 2023 10:29:41 +0100 Subject: [PATCH] config: provide origin in git_config_entry A git_config_entry now knows the type of the origin for the entry ("file", "memory", etc) and the path details (for files, the path on disk). This is propagated through snapshots. --- include/git2/config.h | 24 ++++++++++++++--- src/libgit2/config_file.c | 6 +++++ src/libgit2/config_list.c | 47 +++++++++++++++++++++++++++++---- src/libgit2/config_list.h | 1 + src/libgit2/config_mem.c | 1 + tests/libgit2/config/read.c | 2 ++ tests/libgit2/config/snapshot.c | 35 +++++++++++++++++++++++- 7 files changed, 106 insertions(+), 10 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 155a29962..332e62036 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -62,10 +62,26 @@ typedef enum { * An entry in a configuration file */ typedef struct git_config_entry { - const char *name; /**< Name of the entry (normalised) */ - const char *value; /**< String value of the entry */ - unsigned int include_depth; /**< Depth of includes where this variable was found */ - git_config_level_t level; /**< Which config file this was found in */ + /** Name of the configuration entry (normalized) */ + const char *name; + + /** Literal (string) value of the entry */ + const char *value; + + /** The type of backend that this entry exists in (eg, "file") */ + const char *backend_type; + + /** + * The path to the origin of this entry. For config files, this is + * the path to the file. + */ + const char *origin_path; + + /** Depth of includes where this variable was found */ + unsigned int include_depth; + + /** Configuration level for the file this was found in */ + git_config_level_t level; /** * Free function for this entry; for internal purposes. Callers diff --git a/src/libgit2/config_file.c b/src/libgit2/config_file.c index 46ef07533..c86e98bf2 100644 --- a/src/libgit2/config_file.c +++ b/src/libgit2/config_file.c @@ -24,6 +24,8 @@ /* Max depth for [include] directives */ #define MAX_INCLUDE_DEPTH 10 +#define CONFIG_FILE_TYPE "file" + typedef struct config_file { git_futils_filestamp stamp; unsigned char checksum[GIT_HASH_SHA256_SIZE]; @@ -801,7 +803,11 @@ static int read_on_variable( GIT_ERROR_CHECK_ALLOC(entry->base.value); } + entry->base.origin_path = git_config_list_add_path(parse_data->config_list, parse_data->file->path); + GIT_ERROR_CHECK_ALLOC(entry->base.origin_path); + entry->base.level = parse_data->level; + entry->base.backend_type = CONFIG_FILE_TYPE; entry->base.include_depth = parse_data->depth; entry->base.free = git_config_list_entry_free; entry->config_list = parse_data->config_list; diff --git a/src/libgit2/config_list.c b/src/libgit2/config_list.c index 979c6ccff..f642c87a4 100644 --- a/src/libgit2/config_list.c +++ b/src/libgit2/config_list.c @@ -26,6 +26,11 @@ typedef struct config_list_iterator { struct git_config_list { git_refcount rc; + + /* Paths to config files that contribute to these entries */ + git_strmap *paths; + + /* Config entries */ git_strmap *map; config_entry_list *entries; }; @@ -33,18 +38,22 @@ struct git_config_list { int git_config_list_new(git_config_list **out) { git_config_list *config_list; - int error; config_list = git__calloc(1, sizeof(git_config_list)); GIT_ERROR_CHECK_ALLOC(config_list); GIT_REFCOUNT_INC(config_list); - if ((error = git_strmap_new(&config_list->map)) < 0) + if (git_strmap_new(&config_list->paths) < 0 || + git_strmap_new(&config_list->map) < 0) { + git_strmap_free(config_list->paths); + git_strmap_free(config_list->map); git__free(config_list); - else - *out = config_list; - return error; + return -1; + } + + *out = config_list; + return 0; } int git_config_list_dup_entry(git_config_list *config_list, const git_config_entry *entry) @@ -63,6 +72,12 @@ int git_config_list_dup_entry(git_config_list *config_list, const git_config_ent GIT_ERROR_CHECK_ALLOC(duplicated->base.value); } + if (entry->origin_path) { + duplicated->base.origin_path = git_config_list_add_path(config_list, entry->origin_path); + GIT_ERROR_CHECK_ALLOC(duplicated->base.origin_path); + } + + duplicated->base.backend_type = entry->backend_type; duplicated->base.level = entry->level; duplicated->base.include_depth = entry->include_depth; duplicated->base.free = git_config_list_entry_free; @@ -110,6 +125,12 @@ static void config_list_free(git_config_list *config_list) { config_entry_list *entry_list = NULL, *next; config_entry_map_head *head; + char *path; + + git_strmap_foreach_value(config_list->paths, path, { + git__free(path); + }); + git_strmap_free(config_list->paths); git_strmap_foreach_value(config_list->map, head, { git__free((char *) head->entry->base.name); @@ -247,3 +268,19 @@ void git_config_list_entry_free(git_config_entry *e) git_config_list_entry *entry = (git_config_list_entry *)e; git_config_list_free(entry->config_list); } + +const char *git_config_list_add_path( + git_config_list *config_list, + const char *path) +{ + const char *p; + + if ((p = git_strmap_get(config_list->paths, path)) != NULL) + return p; + + if ((p = git__strdup(path)) == NULL || + git_strmap_set(config_list->paths, p, (void *)p) < 0) + return NULL; + + return p; +} diff --git a/src/libgit2/config_list.h b/src/libgit2/config_list.h index 0cacabb11..83c43b9a0 100644 --- a/src/libgit2/config_list.h +++ b/src/libgit2/config_list.h @@ -27,5 +27,6 @@ int git_config_list_append(git_config_list *list, git_config_list_entry *entry); int git_config_list_get(git_config_list_entry **out, git_config_list *list, const char *key); int git_config_list_get_unique(git_config_list_entry **out, git_config_list *list, const char *key); int git_config_list_iterator_new(git_config_iterator **out, git_config_list *list); +const char *git_config_list_add_path(git_config_list *list, const char *path); void git_config_list_entry_free(git_config_entry *entry); diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c index 37f5b01f9..748a14716 100644 --- a/src/libgit2/config_mem.c +++ b/src/libgit2/config_mem.c @@ -68,6 +68,7 @@ static int read_variable_cb( entry->base.value = var_value ? git__strdup(var_value) : NULL; entry->base.level = parse_data->level; entry->base.include_depth = 0; + entry->base.backend_type = "memory"; entry->base.free = git_config_list_entry_free; entry->config_list = parse_data->config_list; diff --git a/tests/libgit2/config/read.c b/tests/libgit2/config/read.c index ac6459b9e..25e7b963c 100644 --- a/tests/libgit2/config/read.c +++ b/tests/libgit2/config/read.c @@ -495,6 +495,8 @@ void test_config_read__read_git_config_entry(void) cl_assert_equal_s("core.dummy2", entry->name); cl_assert_equal_s("42", entry->value); cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s(cl_fixture("config/config9"), entry->origin_path); git_config_entry_free(entry); git_config_free(cfg); diff --git a/tests/libgit2/config/snapshot.c b/tests/libgit2/config/snapshot.c index 5cc08a721..bfb68e21e 100644 --- a/tests/libgit2/config/snapshot.c +++ b/tests/libgit2/config/snapshot.c @@ -79,6 +79,7 @@ void test_config_snapshot__multivar(void) void test_config_snapshot__includes(void) { + git_config_entry *entry; int i; cl_git_mkfile("including", "[include]\npath = included"); @@ -99,6 +100,16 @@ void test_config_snapshot__includes(void) cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); cl_assert_equal_i(i, 1); + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s("./included", entry->origin_path); + + git_config_entry_free(entry); + cl_git_pass(p_unlink("including")); cl_git_pass(p_unlink("included")); } @@ -106,6 +117,7 @@ void test_config_snapshot__includes(void) void test_config_snapshot__snapshot(void) { git_config *snapshot_snapshot; + git_config_entry *entry; int i; cl_git_mkfile("configfile", "[section]\nkey = 1\n"); @@ -118,15 +130,26 @@ void test_config_snapshot__snapshot(void) cl_git_pass(git_config_get_int32(&i, snapshot_snapshot, "section.key")); cl_assert_equal_i(i, 1); + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot_snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("file", entry->backend_type); + cl_assert_equal_s("configfile", entry->origin_path); + + git_config_entry_free(entry); + git_config_free(snapshot_snapshot); cl_git_pass(p_unlink("configfile")); } -void test_config_snapshot__snapshot_from_in_memony(void) +void test_config_snapshot__snapshot_from_in_memory(void) { const char *configuration = "[section]\nkey = 1\n"; git_config_backend *backend; + git_config_entry *entry; int i; cl_git_pass(git_config_new(&cfg)); @@ -136,4 +159,14 @@ void test_config_snapshot__snapshot_from_in_memony(void) cl_git_pass(git_config_snapshot(&snapshot, cfg)); cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); cl_assert_equal_i(i, 1); + + /* Ensure that the config entry is populated with origin */ + cl_git_pass(git_config_get_entry(&entry, snapshot, "section.key")); + + cl_assert_equal_s("section.key", entry->name); + cl_assert_equal_s("1", entry->value); + cl_assert_equal_s("memory", entry->backend_type); + cl_assert_equal_p(NULL, entry->origin_path); + + git_config_entry_free(entry); }