repository: wire up refStorage extension

To support multiple different reference backend implementations,
Git introduced a "refStorage" extension that stores the reference
storage format a Git client should try to use.

Wire up the logic to read this new extension when we open a repository
from disk. For now, only the "files" backend is supported by us. When
trying to open a repository that has a refstorage format that we don't
understand we now error out.

There are two functions that create a new repository that doesn't really
have references. While those are mostly non-functional when it comes to
references, we do expect that you can access the refdb, even if it's not
yielding any refs. For now we mark those to use the "files" backend, so
that the status quo is retained. Eventually though it might not be the
worst idea to introduce an explicit "in-memory" reference database. But
that is outside the scope of this patch series.
This commit is contained in:
Patrick Steinhardt
2025-02-06 12:25:07 +01:00
parent 38382ce3bc
commit 806a0062fd
7 changed files with 120 additions and 20 deletions

View File

@@ -21,6 +21,11 @@
*/
GIT_BEGIN_DECL
/** The type of the refdb as determined by "extensions.refStorage". */
typedef enum {
GIT_REFDB_FILES = 1 /**< Files backend using loose and packed refs. */
} git_refdb_t;
/**
* Create a new reference database with no backends.
*

View File

@@ -39,28 +39,36 @@ int git_refdb_new(git_refdb **out, git_repository *repo)
int git_refdb_open(git_refdb **out, git_repository *repo)
{
git_refdb_backend *backend;
git_refdb *db;
git_refdb_backend *dir;
int error;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(repo);
*out = NULL;
if (git_refdb_new(&db, repo) < 0)
return -1;
if ((error = git_refdb_new(&db, repo)) < 0)
goto out;
/* Add the default (filesystem) backend */
if (git_refdb_backend_fs(&dir, repo) < 0) {
git_refdb_free(db);
return -1;
switch (repo->refdb_type) {
case GIT_REFDB_FILES:
if ((error = git_refdb_backend_fs(&backend, repo)) < 0)
goto out;
break;
default:
git_error_set(GIT_ERROR_REFERENCE, "unknown reference storage format");
error = GIT_EINVALID;
goto out;
}
db->repo = repo;
db->backend = dir;
db->backend = backend;
*out = db;
return 0;
out:
if (error)
git_refdb_free(db);
return error;
}
static void refdb_free_backend(git_refdb *db)

View File

@@ -125,4 +125,11 @@ int git_refdb_ensure_log(git_refdb *refdb, const char *refname);
int git_refdb_lock(void **payload, git_refdb *db, const char *refname);
int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message);
GIT_INLINE(git_refdb_t) git_refdb_type_fromstr(const char *name)
{
if (strcmp(name, "files") == 0)
return GIT_REFDB_FILES;
return 0;
}
#endif

View File

@@ -69,6 +69,7 @@ static int check_repositoryformatversion(int *version, git_config *config);
static int check_extensions(git_config *config, int version);
static int load_global_config(git_config **config, bool use_env);
static int load_objectformat(git_repository *repo, git_config *config);
static int load_refstorage_format(git_repository *repo, git_config *config);
#define GIT_COMMONDIR_FILE "commondir"
#define GIT_GITDIR_FILE "gitdir"
@@ -350,6 +351,17 @@ int git_repository_new_ext(
repo->oid_type = opts && opts->oid_type ? opts->oid_type :
GIT_OID_DEFAULT;
/*
* This is a bit dirty, as this repository doesn't really have a refdb
* in the first place. But we do expect that we can create an "empty"
* ref iterator from such a repository, and things keep on working like
* this.
*
* It might make sense to eventually create an "in-memory" refdb type
* to serve this purpose.
*/
repo->refdb_type = GIT_REFDB_FILES;
return 0;
}
@@ -1016,10 +1028,12 @@ static int read_repository_format(git_repository *repo)
goto out;
if (version > 0) {
if ((error = load_objectformat(repo, config)) < 0)
if ((error = load_objectformat(repo, config)) < 0 ||
(error = load_refstorage_format(repo, config)) < 0)
goto out;
} else {
repo->oid_type = GIT_OID_DEFAULT;
repo->refdb_type = GIT_REFDB_FILES;
}
out:
@@ -1245,6 +1259,17 @@ int git_repository_wrap_odb(git_repository **out, git_odb *odb)
GIT_ASSERT(git_oid_type_is_valid(odb->options.oid_type));
repo->oid_type = odb->options.oid_type;
/*
* This is a bit dirty, as this repository doesn't really have a refdb
* in the first place. But we do expect that we can create an "empty"
* ref iterator from such a repository, and things keep on working like
* this.
*
* It might make sense to eventually create an "in-memory" refdb type
* to serve this purpose.
*/
repo->refdb_type = GIT_REFDB_FILES;
git_repository_set_odb(repo, odb);
*out = repo;
@@ -1879,7 +1904,8 @@ static const char *builtin_extensions[] = {
"noop",
"objectformat",
"worktreeconfig",
"preciousobjects"
"preciousobjects",
"refstorage",
};
static git_vector user_extensions = { 0, git__strcmp_cb };
@@ -2016,6 +2042,32 @@ int git_repository__set_objectformat(
return 0;
}
static int load_refstorage_format(git_repository *repo, git_config *config)
{
git_config_entry *entry = NULL;
int error;
if ((error = git_config_get_entry(&entry, config, "extensions.refstorage")) < 0) {
if (error == GIT_ENOTFOUND) {
repo->refdb_type = GIT_REFDB_FILES;
git_error_clear();
error = 0;
}
goto done;
}
if ((repo->refdb_type = git_refdb_type_fromstr(entry->value)) == 0) {
git_error_set(GIT_ERROR_REPOSITORY,
"unknown refstorage format '%s'", entry->value);
error = GIT_EINVALID;
}
done:
git_config_entry_free(entry);
return error;
}
int git_repository__extensions(char ***out, size_t *out_len)
{
git_vector extensions;

View File

@@ -159,6 +159,7 @@ struct git_repository {
is_bare:1,
is_worktree:1;
git_oid_t oid_type;
git_refdb_t refdb_type;
unsigned int lru_counter;

View File

@@ -34,11 +34,12 @@ void test_core_opts__extensions_query(void)
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
cl_assert_equal_sz(out.count, 4);
cl_assert_equal_sz(out.count, 5);
cl_assert_equal_s("noop", out.strings[0]);
cl_assert_equal_s("objectformat", out.strings[1]);
cl_assert_equal_s("preciousobjects", out.strings[2]);
cl_assert_equal_s("worktreeconfig", out.strings[3]);
cl_assert_equal_s("refstorage", out.strings[3]);
cl_assert_equal_s("worktreeconfig", out.strings[4]);
git_strarray_dispose(&out);
}
@@ -51,12 +52,13 @@ void test_core_opts__extensions_add(void)
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, 5);
cl_assert_equal_sz(out.count, 6);
cl_assert_equal_s("foo", out.strings[0]);
cl_assert_equal_s("noop", out.strings[1]);
cl_assert_equal_s("objectformat", out.strings[2]);
cl_assert_equal_s("preciousobjects", out.strings[3]);
cl_assert_equal_s("worktreeconfig", out.strings[4]);
cl_assert_equal_s("refstorage", out.strings[4]);
cl_assert_equal_s("worktreeconfig", out.strings[5]);
git_strarray_dispose(&out);
}
@@ -69,12 +71,13 @@ void test_core_opts__extensions_remove(void)
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, 5);
cl_assert_equal_sz(out.count, 6);
cl_assert_equal_s("bar", out.strings[0]);
cl_assert_equal_s("baz", out.strings[1]);
cl_assert_equal_s("objectformat", out.strings[2]);
cl_assert_equal_s("preciousobjects", out.strings[3]);
cl_assert_equal_s("worktreeconfig", out.strings[4]);
cl_assert_equal_s("refstorage", out.strings[4]);
cl_assert_equal_s("worktreeconfig", out.strings[5]);
git_strarray_dispose(&out);
}
@@ -87,13 +90,14 @@ void test_core_opts__extensions_uniq(void)
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, 6);
cl_assert_equal_sz(out.count, 7);
cl_assert_equal_s("bar", out.strings[0]);
cl_assert_equal_s("foo", out.strings[1]);
cl_assert_equal_s("noop", out.strings[2]);
cl_assert_equal_s("objectformat", out.strings[3]);
cl_assert_equal_s("preciousobjects", out.strings[4]);
cl_assert_equal_s("worktreeconfig", out.strings[5]);
cl_assert_equal_s("refstorage", out.strings[5]);
cl_assert_equal_s("worktreeconfig", out.strings[6]);
git_strarray_dispose(&out);
}

View File

@@ -874,3 +874,26 @@ void test_repo_open__can_reset_safe_directory_list(void)
git_str_dispose(&config_filename);
git_str_dispose(&config_data);
}
void test_repo_open__refstorage_extension(void)
{
git_repository *repo, *tmp;
git_config *config;
repo = cl_git_sandbox_init("empty_bare.git");
cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
cl_git_pass(git_repository_config(&config, repo));
cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
cl_git_pass(git_config_set_string(config, "extensions.refStorage", "files"));
cl_git_pass(git_repository_open(&tmp, "empty_bare.git"));
git_repository_free(tmp);
cl_git_pass(git_config_set_string(config, "extensions.refStorage", "garbage"));
cl_git_fail_with(GIT_EINVALID, git_repository_open(&tmp, "empty_bare.git"));
git_repository_free(tmp);
git_config_free(config);
git_repository_free(repo);
}