diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2021-09-04 10:16:41 -0400 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2021-09-04 13:00:18 -0400 |
commit | a24e656a4e6278157d2aec885e0d300f47f74938 (patch) | |
tree | 793b046f71667ccb448c2138402c1be9afbb77b6 | |
parent | 2f3074da512624c9522683f9aa6bca6642a3e4f7 (diff) | |
download | libgit2-a24e656a4e6278157d2aec885e0d300f47f74938.tar.gz |
common: support custom repository extensionsethomson/extensions
Allow users to specify additional repository extensions that they want
to support. For example, callers can specify that they support
`preciousObjects` and then may open repositories that support
`extensions.preciousObjects`.
Similarly, callers may opt out of supporting extensions that the library
itself supports.
-rw-r--r-- | include/git2/common.h | 20 | ||||
-rw-r--r-- | src/libgit2.c | 23 | ||||
-rw-r--r-- | src/repository.c | 115 | ||||
-rw-r--r-- | src/repository.h | 4 | ||||
-rw-r--r-- | tests/core/opts.c | 46 | ||||
-rw-r--r-- | tests/repo/extensions.c | 28 |
6 files changed, 232 insertions, 4 deletions
diff --git a/include/git2/common.h b/include/git2/common.h index d278c01b6..2ee829025 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -209,7 +209,9 @@ typedef enum { GIT_OPT_GET_MWINDOW_FILE_LIMIT, GIT_OPT_SET_MWINDOW_FILE_LIMIT, GIT_OPT_SET_ODB_PACKED_PRIORITY, - GIT_OPT_SET_ODB_LOOSE_PRIORITY + GIT_OPT_SET_ODB_LOOSE_PRIORITY, + GIT_OPT_GET_EXTENSIONS, + GIT_OPT_SET_EXTENSIONS } git_libgit2_opt_t; /** @@ -431,6 +433,22 @@ typedef enum { * > Override the default priority of the loose ODB backend which * > is added when default backends are assigned to a repository * + * opts(GIT_OPT_GET_EXTENSIONS, git_strarray *out) + * > Returns the list of git extensions that are supported. This + * > is the list of built-in extensions supported by libgit2 and + * > custom extensions that have been added with + * > `GIT_OPT_SET_EXTENSIONS`. Extensions that have been negated + * > will not be returned. The returned list should be released + * > with `git_strarray_dispose`. + * + * opts(GIT_OPT_SET_EXTENSIONS, const char **extensions, size_t len) + * > Set that the given git extensions are supported by the caller. + * > Extensions supported by libgit2 may be negated by prefixing + * > them with a `!`. For example: setting extensions to + * > { "!noop", "newext" } indicates that the caller does not want + * > to support repositories with the `noop` extension but does want + * > to support repositories with the `newext` extension. + * * @param option Option key * @param ... value to set the option * @return 0 on success, <0 on failure diff --git a/src/libgit2.c b/src/libgit2.c index 09f7ab533..cc793b458 100644 --- a/src/libgit2.c +++ b/src/libgit2.c @@ -52,6 +52,7 @@ static void libgit2_settings_global_shutdown(void) { git__free(git__user_agent); git__free(git__ssl_ciphers); + git_repository__free_extensions(); } static int git_libgit2_settings_global_init(void) @@ -367,6 +368,28 @@ int git_libgit2_opts(int key, ...) git_odb__loose_priority = va_arg(ap, int); break; + case GIT_OPT_SET_EXTENSIONS: + { + const char **extensions = va_arg(ap, const char **); + size_t len = va_arg(ap, size_t); + error = git_repository__set_extensions(extensions, len); + } + break; + + case GIT_OPT_GET_EXTENSIONS: + { + git_strarray *out = va_arg(ap, git_strarray *); + char **extensions; + size_t len; + + if ((error = git_repository__extensions(&extensions, &len)) < 0) + break; + + out->strings = extensions; + out->count = len; + } + break; + default: git_error_set(GIT_ERROR_INVALID, "invalid option key"); error = -1; diff --git a/src/repository.c b/src/repository.c index aae0c910b..d32111379 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1427,15 +1427,60 @@ static int check_repositoryformatversion(int *version, git_config *config) return 0; } +static const char *builtin_extensions[] = { + "noop" +}; + +static git_vector user_extensions = GIT_VECTOR_INIT; + static int check_valid_extension(const git_config_entry *entry, void *payload) { + git_buf cfg = GIT_BUF_INIT; + bool reject; + const char *extension; + size_t i; + int error = 0; + GIT_UNUSED(payload); - if (!strcmp(entry->name, "extensions.noop")) - return 0; + git_vector_foreach (&user_extensions, i, extension) { + git_buf_clear(&cfg); + + /* + * Users can specify that they don't want to support an + * extension with a '!' prefix. + */ + if ((reject = (extension[0] == '!')) == true) + extension = &extension[1]; + + if ((error = git_buf_printf(&cfg, "extensions.%s", extension)) < 0) + goto done; + if (strcmp(entry->name, cfg.ptr) == 0) { + if (reject) + goto fail; + + goto done; + } + } + + for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { + extension = builtin_extensions[i]; + + if ((error = git_buf_printf(&cfg, "extensions.%s", extension)) < 0) + goto done; + + if (strcmp(entry->name, cfg.ptr) == 0) + goto done; + } + +fail: git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name); - return -1; + error = -1; + +done: + git_buf_dispose(&cfg); + return error; } static int check_extensions(git_config *config, int version) @@ -1446,6 +1491,70 @@ static int check_extensions(git_config *config, int version) return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL); } +int git_repository__extensions(char ***out, size_t *out_len) +{ + git_vector extensions; + const char *builtin, *user; + char *extension; + size_t i, j; + + if (git_vector_init(&extensions, 8, NULL) < 0) + return -1; + + for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { + bool match = false; + + builtin = builtin_extensions[i]; + + git_vector_foreach (&user_extensions, j, user) { + if (user[0] == '!' && strcmp(builtin, &user[1]) == 0) { + match = true; + break; + } + } + + if (match) + continue; + + if ((extension = git__strdup(builtin)) == NULL || + git_vector_insert(&extensions, extension) < 0) + return -1; + } + + git_vector_foreach (&user_extensions, i, user) { + if (user[0] == '!') + continue; + + if ((extension = git__strdup(user)) == NULL || + git_vector_insert(&extensions, extension) < 0) + return -1; + } + + *out = (char **)git_vector_detach(out_len, NULL, &extensions); + return 0; +} + +int git_repository__set_extensions(const char **extensions, size_t len) +{ + char *extension; + size_t i; + + git_repository__free_extensions(); + + for (i = 0; i < len; i++) { + if ((extension = git__strdup(extensions[i])) == NULL || + git_vector_insert(&user_extensions, extension) < 0) + return -1; + } + + return 0; +} + +void git_repository__free_extensions(void) +{ + git_vector_free_deep(&user_extensions); +} + int git_repository_create_head(const char *git_dir, const char *ref_name) { git_buf ref_path = GIT_BUF_INIT; diff --git a/src/repository.h b/src/repository.h index f48dd9edf..cbc160140 100644 --- a/src/repository.h +++ b/src/repository.h @@ -249,4 +249,8 @@ int git_repository_initialbranch(git_buf *out, git_repository *repo); */ int git_repository_workdir_path(git_buf *out, git_repository *repo, const char *path); +int git_repository__extensions(char ***out, size_t *out_len); +int git_repository__set_extensions(const char **extensions, size_t len); +void git_repository__free_extensions(void); + #endif diff --git a/tests/core/opts.c b/tests/core/opts.c index 72408cbe8..e8f65d510 100644 --- a/tests/core/opts.c +++ b/tests/core/opts.c @@ -1,6 +1,11 @@ #include "clar_libgit2.h" #include "cache.h" +void test_core_opts__cleanup(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0)); +} + void test_core_opts__readwrite(void) { size_t old_val = 0; @@ -23,3 +28,44 @@ void test_core_opts__invalid_option(void) cl_git_fail(git_libgit2_opts(-1, "foobar")); } +void test_core_opts__extensions_query(void) +{ + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 1); + cl_assert_equal_s("noop", out.strings[0]); + + git_strarray_dispose(&out); +} + +void test_core_opts__extensions_add(void) +{ + const char *in[] = { "foo" }; + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 2); + cl_assert_equal_s("noop", out.strings[0]); + cl_assert_equal_s("foo", out.strings[1]); + + git_strarray_dispose(&out); +} + +void test_core_opts__extensions_remove(void) +{ + const char *in[] = { "bar", "!negate", "!noop", "baz" }; + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 2); + cl_assert_equal_s("bar", out.strings[0]); + cl_assert_equal_s("baz", out.strings[1]); + + git_strarray_dispose(&out); +} diff --git a/tests/repo/extensions.c b/tests/repo/extensions.c index 8ba89f1a9..e7772acd5 100644 --- a/tests/repo/extensions.c +++ b/tests/repo/extensions.c @@ -19,6 +19,7 @@ void test_repo_extensions__initialize(void) void test_repo_extensions__cleanup(void) { cl_git_sandbox_cleanup(); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0)); } void test_repo_extensions__builtin(void) @@ -33,6 +34,19 @@ void test_repo_extensions__builtin(void) git_repository_free(extended); } +void test_repo_extensions__negate_builtin(void) +{ + const char *in[] = { "foo", "!noop", "baz" }; + git_repository *extended; + + cl_repo_set_string(repo, "extensions.noop", "foobar"); + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + + cl_git_fail(git_repository_open(&extended, "empty_bare.git")); + git_repository_free(extended); +} + void test_repo_extensions__unsupported(void) { git_repository *extended = NULL; @@ -42,3 +56,17 @@ void test_repo_extensions__unsupported(void) cl_git_fail(git_repository_open(&extended, "empty_bare.git")); git_repository_free(extended); } + +void test_repo_extensions__adds_extension(void) +{ + const char *in[] = { "foo", "!noop", "newextension", "baz" }; + git_repository *extended; + + cl_repo_set_string(repo, "extensions.newextension", "foobar"); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + + cl_git_pass(git_repository_open(&extended, "empty_bare.git")); + cl_assert(git_repository_path(extended) != NULL); + cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0); + git_repository_free(extended); +} |