Commit a24e656a by Edward Thomson

common: support custom repository 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.
parent 2f3074da
......@@ -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
......
......@@ -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;
......
......@@ -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;
......
......@@ -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
#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);
}
......@@ -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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment