Skip to content

Commit 4f24a93

Browse files
authored
Merge pull request #6031 from libgit2/ethomson/extensions
Support custom git extensions
2 parents 5bd49ae + a24e656 commit 4f24a93

File tree

7 files changed

+276
-46
lines changed

7 files changed

+276
-46
lines changed

include/git2/common.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ typedef enum {
209209
GIT_OPT_GET_MWINDOW_FILE_LIMIT,
210210
GIT_OPT_SET_MWINDOW_FILE_LIMIT,
211211
GIT_OPT_SET_ODB_PACKED_PRIORITY,
212-
GIT_OPT_SET_ODB_LOOSE_PRIORITY
212+
GIT_OPT_SET_ODB_LOOSE_PRIORITY,
213+
GIT_OPT_GET_EXTENSIONS,
214+
GIT_OPT_SET_EXTENSIONS
213215
} git_libgit2_opt_t;
214216

215217
/**
@@ -431,6 +433,22 @@ typedef enum {
431433
* > Override the default priority of the loose ODB backend which
432434
* > is added when default backends are assigned to a repository
433435
*
436+
* opts(GIT_OPT_GET_EXTENSIONS, git_strarray *out)
437+
* > Returns the list of git extensions that are supported. This
438+
* > is the list of built-in extensions supported by libgit2 and
439+
* > custom extensions that have been added with
440+
* > `GIT_OPT_SET_EXTENSIONS`. Extensions that have been negated
441+
* > will not be returned. The returned list should be released
442+
* > with `git_strarray_dispose`.
443+
*
444+
* opts(GIT_OPT_SET_EXTENSIONS, const char **extensions, size_t len)
445+
* > Set that the given git extensions are supported by the caller.
446+
* > Extensions supported by libgit2 may be negated by prefixing
447+
* > them with a `!`. For example: setting extensions to
448+
* > { "!noop", "newext" } indicates that the caller does not want
449+
* > to support repositories with the `noop` extension but does want
450+
* > to support repositories with the `newext` extension.
451+
*
434452
* @param option Option key
435453
* @param ... value to set the option
436454
* @return 0 on success, <0 on failure

src/libgit2.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ static void libgit2_settings_global_shutdown(void)
5252
{
5353
git__free(git__user_agent);
5454
git__free(git__ssl_ciphers);
55+
git_repository__free_extensions();
5556
}
5657

5758
static int git_libgit2_settings_global_init(void)
@@ -367,6 +368,28 @@ int git_libgit2_opts(int key, ...)
367368
git_odb__loose_priority = va_arg(ap, int);
368369
break;
369370

371+
case GIT_OPT_SET_EXTENSIONS:
372+
{
373+
const char **extensions = va_arg(ap, const char **);
374+
size_t len = va_arg(ap, size_t);
375+
error = git_repository__set_extensions(extensions, len);
376+
}
377+
break;
378+
379+
case GIT_OPT_GET_EXTENSIONS:
380+
{
381+
git_strarray *out = va_arg(ap, git_strarray *);
382+
char **extensions;
383+
size_t len;
384+
385+
if ((error = git_repository__extensions(&extensions, &len)) < 0)
386+
break;
387+
388+
out->strings = extensions;
389+
out->count = len;
390+
}
391+
break;
392+
370393
default:
371394
git_error_set(GIT_ERROR_INVALID, "invalid option key");
372395
error = -1;

src/repository.c

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,15 +1427,60 @@ static int check_repositoryformatversion(int *version, git_config *config)
14271427
return 0;
14281428
}
14291429

1430+
static const char *builtin_extensions[] = {
1431+
"noop"
1432+
};
1433+
1434+
static git_vector user_extensions = GIT_VECTOR_INIT;
1435+
14301436
static int check_valid_extension(const git_config_entry *entry, void *payload)
14311437
{
1438+
git_buf cfg = GIT_BUF_INIT;
1439+
bool reject;
1440+
const char *extension;
1441+
size_t i;
1442+
int error = 0;
1443+
14321444
GIT_UNUSED(payload);
14331445

1434-
if (!strcmp(entry->name, "extensions.noop"))
1435-
return 0;
1446+
git_vector_foreach (&user_extensions, i, extension) {
1447+
git_buf_clear(&cfg);
1448+
1449+
/*
1450+
* Users can specify that they don't want to support an
1451+
* extension with a '!' prefix.
1452+
*/
1453+
if ((reject = (extension[0] == '!')) == true)
1454+
extension = &extension[1];
1455+
1456+
if ((error = git_buf_printf(&cfg, "extensions.%s", extension)) < 0)
1457+
goto done;
14361458

1459+
if (strcmp(entry->name, cfg.ptr) == 0) {
1460+
if (reject)
1461+
goto fail;
1462+
1463+
goto done;
1464+
}
1465+
}
1466+
1467+
for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) {
1468+
extension = builtin_extensions[i];
1469+
1470+
if ((error = git_buf_printf(&cfg, "extensions.%s", extension)) < 0)
1471+
goto done;
1472+
1473+
if (strcmp(entry->name, cfg.ptr) == 0)
1474+
goto done;
1475+
}
1476+
1477+
fail:
14371478
git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name);
1438-
return -1;
1479+
error = -1;
1480+
1481+
done:
1482+
git_buf_dispose(&cfg);
1483+
return error;
14391484
}
14401485

14411486
static int check_extensions(git_config *config, int version)
@@ -1446,6 +1491,70 @@ static int check_extensions(git_config *config, int version)
14461491
return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL);
14471492
}
14481493

1494+
int git_repository__extensions(char ***out, size_t *out_len)
1495+
{
1496+
git_vector extensions;
1497+
const char *builtin, *user;
1498+
char *extension;
1499+
size_t i, j;
1500+
1501+
if (git_vector_init(&extensions, 8, NULL) < 0)
1502+
return -1;
1503+
1504+
for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) {
1505+
bool match = false;
1506+
1507+
builtin = builtin_extensions[i];
1508+
1509+
git_vector_foreach (&user_extensions, j, user) {
1510+
if (user[0] == '!' && strcmp(builtin, &user[1]) == 0) {
1511+
match = true;
1512+
break;
1513+
}
1514+
}
1515+
1516+
if (match)
1517+
continue;
1518+
1519+
if ((extension = git__strdup(builtin)) == NULL ||
1520+
git_vector_insert(&extensions, extension) < 0)
1521+
return -1;
1522+
}
1523+
1524+
git_vector_foreach (&user_extensions, i, user) {
1525+
if (user[0] == '!')
1526+
continue;
1527+
1528+
if ((extension = git__strdup(user)) == NULL ||
1529+
git_vector_insert(&extensions, extension) < 0)
1530+
return -1;
1531+
}
1532+
1533+
*out = (char **)git_vector_detach(out_len, NULL, &extensions);
1534+
return 0;
1535+
}
1536+
1537+
int git_repository__set_extensions(const char **extensions, size_t len)
1538+
{
1539+
char *extension;
1540+
size_t i;
1541+
1542+
git_repository__free_extensions();
1543+
1544+
for (i = 0; i < len; i++) {
1545+
if ((extension = git__strdup(extensions[i])) == NULL ||
1546+
git_vector_insert(&user_extensions, extension) < 0)
1547+
return -1;
1548+
}
1549+
1550+
return 0;
1551+
}
1552+
1553+
void git_repository__free_extensions(void)
1554+
{
1555+
git_vector_free_deep(&user_extensions);
1556+
}
1557+
14491558
int git_repository_create_head(const char *git_dir, const char *ref_name)
14501559
{
14511560
git_buf ref_path = GIT_BUF_INIT;

src/repository.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,8 @@ int git_repository_initialbranch(git_buf *out, git_repository *repo);
249249
*/
250250
int git_repository_workdir_path(git_buf *out, git_repository *repo, const char *path);
251251

252+
int git_repository__extensions(char ***out, size_t *out_len);
253+
int git_repository__set_extensions(const char **extensions, size_t len);
254+
void git_repository__free_extensions(void);
255+
252256
#endif

tests/core/opts.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#include "clar_libgit2.h"
22
#include "cache.h"
33

4+
void test_core_opts__cleanup(void)
5+
{
6+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0));
7+
}
8+
49
void test_core_opts__readwrite(void)
510
{
611
size_t old_val = 0;
@@ -23,3 +28,44 @@ void test_core_opts__invalid_option(void)
2328
cl_git_fail(git_libgit2_opts(-1, "foobar"));
2429
}
2530

31+
void test_core_opts__extensions_query(void)
32+
{
33+
git_strarray out = { 0 };
34+
35+
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
36+
37+
cl_assert_equal_sz(out.count, 1);
38+
cl_assert_equal_s("noop", out.strings[0]);
39+
40+
git_strarray_dispose(&out);
41+
}
42+
43+
void test_core_opts__extensions_add(void)
44+
{
45+
const char *in[] = { "foo" };
46+
git_strarray out = { 0 };
47+
48+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
49+
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
50+
51+
cl_assert_equal_sz(out.count, 2);
52+
cl_assert_equal_s("noop", out.strings[0]);
53+
cl_assert_equal_s("foo", out.strings[1]);
54+
55+
git_strarray_dispose(&out);
56+
}
57+
58+
void test_core_opts__extensions_remove(void)
59+
{
60+
const char *in[] = { "bar", "!negate", "!noop", "baz" };
61+
git_strarray out = { 0 };
62+
63+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
64+
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
65+
66+
cl_assert_equal_sz(out.count, 2);
67+
cl_assert_equal_s("bar", out.strings[0]);
68+
cl_assert_equal_s("baz", out.strings[1]);
69+
70+
git_strarray_dispose(&out);
71+
}

tests/repo/extensions.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include "clar_libgit2.h"
2+
#include "futils.h"
3+
#include "sysdir.h"
4+
#include <ctype.h>
5+
6+
git_repository *repo;
7+
8+
void test_repo_extensions__initialize(void)
9+
{
10+
git_config *config;
11+
12+
repo = cl_git_sandbox_init("empty_bare.git");
13+
14+
cl_git_pass(git_repository_config(&config, repo));
15+
cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
16+
git_config_free(config);
17+
}
18+
19+
void test_repo_extensions__cleanup(void)
20+
{
21+
cl_git_sandbox_cleanup();
22+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0));
23+
}
24+
25+
void test_repo_extensions__builtin(void)
26+
{
27+
git_repository *extended;
28+
29+
cl_repo_set_string(repo, "extensions.noop", "foobar");
30+
31+
cl_git_pass(git_repository_open(&extended, "empty_bare.git"));
32+
cl_assert(git_repository_path(extended) != NULL);
33+
cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0);
34+
git_repository_free(extended);
35+
}
36+
37+
void test_repo_extensions__negate_builtin(void)
38+
{
39+
const char *in[] = { "foo", "!noop", "baz" };
40+
git_repository *extended;
41+
42+
cl_repo_set_string(repo, "extensions.noop", "foobar");
43+
44+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
45+
46+
cl_git_fail(git_repository_open(&extended, "empty_bare.git"));
47+
git_repository_free(extended);
48+
}
49+
50+
void test_repo_extensions__unsupported(void)
51+
{
52+
git_repository *extended = NULL;
53+
54+
cl_repo_set_string(repo, "extensions.unknown", "foobar");
55+
56+
cl_git_fail(git_repository_open(&extended, "empty_bare.git"));
57+
git_repository_free(extended);
58+
}
59+
60+
void test_repo_extensions__adds_extension(void)
61+
{
62+
const char *in[] = { "foo", "!noop", "newextension", "baz" };
63+
git_repository *extended;
64+
65+
cl_repo_set_string(repo, "extensions.newextension", "foobar");
66+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
67+
68+
cl_git_pass(git_repository_open(&extended, "empty_bare.git"));
69+
cl_assert(git_repository_path(extended) != NULL);
70+
cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0);
71+
git_repository_free(extended);
72+
}

tests/repo/open.c

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,48 +42,6 @@ void test_repo_open__format_version_1(void)
4242
git_repository_free(repo);
4343
}
4444

45-
void test_repo_open__format_version_1_with_valid_extension(void)
46-
{
47-
git_repository *repo;
48-
git_config *config;
49-
50-
repo = cl_git_sandbox_init("empty_bare.git");
51-
52-
cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
53-
cl_git_pass(git_repository_config(&config, repo));
54-
55-
cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
56-
cl_git_pass(git_config_set_int32(config, "extensions.noop", 1));
57-
58-
git_config_free(config);
59-
git_repository_free(repo);
60-
61-
cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
62-
cl_assert(git_repository_path(repo) != NULL);
63-
cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0);
64-
git_repository_free(repo);
65-
}
66-
67-
void test_repo_open__format_version_1_with_invalid_extension(void)
68-
{
69-
git_repository *repo;
70-
git_config *config;
71-
72-
repo = cl_git_sandbox_init("empty_bare.git");
73-
74-
cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
75-
cl_git_pass(git_repository_config(&config, repo));
76-
77-
cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
78-
cl_git_pass(git_config_set_int32(config, "extensions.invalid", 1));
79-
80-
git_config_free(config);
81-
git_repository_free(repo);
82-
83-
cl_git_fail(git_repository_open(&repo, "empty_bare.git"));
84-
git_repository_free(repo);
85-
}
86-
8745
void test_repo_open__standard_empty_repo_through_gitdir(void)
8846
{
8947
git_repository *repo;

0 commit comments

Comments
 (0)