summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2021-09-04 10:16:41 -0400
committerEdward Thomson <ethomson@edwardthomson.com>2021-09-04 13:00:18 -0400
commita24e656a4e6278157d2aec885e0d300f47f74938 (patch)
tree793b046f71667ccb448c2138402c1be9afbb77b6
parent2f3074da512624c9522683f9aa6bca6642a3e4f7 (diff)
downloadlibgit2-ethomson/extensions.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.h20
-rw-r--r--src/libgit2.c23
-rw-r--r--src/repository.c115
-rw-r--r--src/repository.h4
-rw-r--r--tests/core/opts.c46
-rw-r--r--tests/repo/extensions.c28
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);
+}