Merge branch 'kn/ref-location' into jch

Allow the directory in which reference backends store their data to
be specified.

* kn/ref-location:
  refs: add GIT_REFERENCE_BACKEND to specify reference backend
  refs: allow reference location in refstorage config
  refs: receive and use the reference storage payload
  refs: move out stub modification to generic layer
  refs: extract out `refs_create_refdir_stubs()`
  setup: don't modify repo in `create_reference_database()`
This commit is contained in:
Junio C Hamano
2026-02-23 15:56:54 -08:00
18 changed files with 625 additions and 81 deletions

View File

@@ -57,10 +57,24 @@ For historical reasons, this extension is respected regardless of the
`core.repositoryFormatVersion` setting.
refStorage:::
Specify the ref storage format to use. The acceptable values are:
Specify the ref storage format and a corresponding payload. The value
can be either a format name or a URI:
+
--
* A format name alone (e.g., `reftable` or `files`).
* A URI format `<format>://<payload>` explicitly specifies both the
format and payload (e.g., `reftable:///foo/bar`).
Supported format names are:
+
include::../ref-storage-format.adoc[]
+
The payload is passed directly to the reference backend. For the files and
reftable backends, this must be a filesystem path where the references will
be stored. Defaulting to the commondir when no payload is provided. Relative
paths are resolved relative to the $GIT_DIR. Future backends may support
other payload schemes, e.g., postgres://127.0.0.1:5432?database=myrepo.
--
+
Note that this setting should only be set by linkgit:git-init[1] or

View File

@@ -584,6 +584,11 @@ double-quotes and respecting backslash escapes. E.g., the value
repositories will be set to this value. The default is "files".
See `--ref-format` in linkgit:git-init[1].
`GIT_REFERENCE_BACKEND`::
Specify which reference backend to be used along with its URI.
See `extensions.refStorage` option in linkgit:git-config[1] for more
details. Overrides the config variable when used.
Git Commits
~~~~~~~~~~~
`GIT_AUTHOR_NAME`::

View File

@@ -1230,12 +1230,7 @@ int cmd_clone(int argc,
initialize_repository_version(GIT_HASH_UNKNOWN,
the_repository->ref_storage_format, 1);
strbuf_addf(&buf, "%s/HEAD", git_dir);
write_file(buf.buf, "ref: refs/heads/.invalid");
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/refs", git_dir);
safe_create_dir(the_repository, buf.buf, 1);
refs_create_refdir_stubs(the_repository, git_dir, NULL);
/*
* additional config can be injected with -c, make sure it's included
@@ -1447,7 +1442,7 @@ int cmd_clone(int argc,
hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
initialize_repository_version(hash_algo, the_repository->ref_storage_format, 1);
repo_set_hash_algo(the_repository, hash_algo);
create_reference_database(the_repository->ref_storage_format, NULL, 1);
create_reference_database(NULL, 1);
/*
* Before fetching from the remote, download and install bundle

View File

@@ -425,6 +425,39 @@ static int make_worktree_orphan(const char * ref, const struct add_opts *opts,
return run_command(&cp);
}
/*
* References for worktrees are generally stored in '$GIT_DIR/worktrees/<wt_id>'.
* But when using alternate reference directories, we want to store the worktree
* references in '$ALTERNATE_REFERENCE_DIR/worktrees/<wt_id>'.
*
* Create the necessary folder structure to facilitate the same. But to ensure
* that the former path is still considered a Git directory, add stubs.
*/
static void setup_alternate_ref_dir(struct worktree *wt, const char *wt_git_path)
{
struct strbuf sb = STRBUF_INIT;
char *path;
path = wt->repo->ref_storage_payload;
if (!path)
return;
if (!is_absolute_path(path))
strbuf_addf(&sb, "%s/", wt->repo->commondir);
strbuf_addf(&sb, "%s/worktrees", path);
safe_create_dir(wt->repo, sb.buf, 1);
strbuf_addf(&sb, "/%s", wt->id);
safe_create_dir(wt->repo, sb.buf, 1);
strbuf_reset(&sb);
strbuf_addf(&sb, "this worktree stores references in %s/worktrees/%s",
path, wt->id);
refs_create_refdir_stubs(wt->repo, wt_git_path, sb.buf);
strbuf_release(&sb);
}
static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
@@ -519,6 +552,7 @@ static int add_worktree(const char *path, const char *refname,
ret = error(_("could not find created worktree '%s'"), name);
goto done;
}
setup_alternate_ref_dir(wt, sb_repo.buf);
wt_refs = get_worktree_ref_store(wt);
ret = ref_store_create_on_disk(wt_refs, REF_STORE_CREATE_ON_DISK_IS_WORKTREE, &sb);

View File

@@ -43,6 +43,7 @@
#define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
#define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
#define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND"
/*
* Environment variable used to propagate the --no-advice global option to the

126
refs.c
View File

@@ -5,6 +5,7 @@
#define USE_THE_REPOSITORY_VARIABLE
#include "git-compat-util.h"
#include "abspath.h"
#include "advice.h"
#include "config.h"
#include "environment.h"
@@ -2156,15 +2157,93 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
return NULL;
}
void refs_create_refdir_stubs(struct repository *repo, const char *refdir,
const char *refs_heads_content)
{
struct strbuf path = STRBUF_INIT;
strbuf_addf(&path, "%s/HEAD", refdir);
write_file(path.buf, "ref: refs/heads/.invalid");
adjust_shared_perm(repo, path.buf);
strbuf_reset(&path);
strbuf_addf(&path, "%s/refs", refdir);
safe_create_dir(repo, path.buf, 1);
if (refs_heads_content) {
strbuf_reset(&path);
strbuf_addf(&path, "%s/refs/heads", refdir);
write_file(path.buf, "%s", refs_heads_content);
adjust_shared_perm(repo, path.buf);
}
strbuf_release(&path);
}
/* backend functions */
int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)
{
return refs->be->create_on_disk(refs, flags, err);
int ret = refs->be->create_on_disk(refs, flags, err);
if (!ret) {
/* Creation of stubs for linked worktrees are handled in the worktree code. */
if (!(flags & REF_STORE_CREATE_ON_DISK_IS_WORKTREE) && refs->repo->ref_storage_payload) {
refs_create_refdir_stubs(refs->repo, refs->repo->gitdir,
"repository uses alternate refs storage");
} else if (ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {
struct strbuf msg = STRBUF_INIT;
strbuf_addf(&msg, "this repository uses the %s format", refs->be->name);
refs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);
strbuf_release(&msg);
}
}
return ret;
}
int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)
{
return refs->be->remove_on_disk(refs, err);
int ret = refs->be->remove_on_disk(refs, err);
if (!ret) {
enum ref_storage_format format = ref_storage_format_by_name(refs->be->name);
struct strbuf sb = STRBUF_INIT;
/* Backends apart from the files backend create stubs. */
if (format == REF_STORAGE_FORMAT_FILES)
return ret;
/* Alternate refs backend require stubs in the gitdir. */
if (refs->repo->ref_storage_payload)
return ret;
strbuf_addf(&sb, "%s/HEAD", refs->gitdir);
if (unlink(sb.buf) < 0) {
strbuf_addf(err, "could not delete stub HEAD: %s",
strerror(errno));
ret = -1;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/refs/heads", refs->gitdir);
if (unlink(sb.buf) < 0) {
strbuf_addf(err, "could not delete stub heads: %s",
strerror(errno));
ret = -1;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/refs", refs->gitdir);
if (rmdir(sb.buf) < 0) {
strbuf_addf(err, "could not delete refs directory: %s",
strerror(errno));
ret = -1;
}
strbuf_release(&sb);
}
return ret;
}
int repo_resolve_gitlink_ref(struct repository *r,
@@ -2217,7 +2296,11 @@ static struct ref_store *ref_store_init(struct repository *repo,
if (!be)
BUG("reference backend is unknown");
refs = be->init(repo, gitdir, flags);
/*
* TODO Send in a 'struct worktree' instead of a 'gitdir', and
* allow the backend to handle how it wants to deal with worktrees.
*/
refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
return refs;
}
@@ -3405,3 +3488,40 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err)
return "unknown failure";
}
}
void refs_compute_filesystem_location(const char *gitdir, const char *payload,
bool *is_worktree, struct strbuf *refdir,
struct strbuf *ref_common_dir)
{
struct strbuf sb = STRBUF_INIT;
*is_worktree = get_common_dir_noenv(ref_common_dir, gitdir);
if (!payload) {
/*
* We can use the 'gitdir' as the 'refdir' without appending the
* worktree path, as the 'gitdir' here is already the worktree
* path and is different from 'commondir' denoted by 'ref_common_dir'.
*/
strbuf_addstr(refdir, gitdir);
return;
}
if (!is_absolute_path(payload)) {
strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload);
strbuf_realpath(ref_common_dir, sb.buf, 1);
} else {
strbuf_realpath(ref_common_dir, payload, 1);
}
strbuf_addbuf(refdir, ref_common_dir);
if (*is_worktree) {
const char *wt_id = strrchr(gitdir, '/');
if (!wt_id)
BUG("worktree path does not contain slash");
strbuf_addf(refdir, "/worktrees/%s", wt_id + 1);
}
strbuf_release(&sb);
}

13
refs.h
View File

@@ -1433,4 +1433,17 @@ void ref_iterator_free(struct ref_iterator *ref_iterator);
int do_for_each_ref_iterator(struct ref_iterator *iter,
refs_for_each_cb fn, void *cb_data);
/*
* Git only recognizes a directory as a repository if it contains:
* - HEAD file
* - refs/ folder
* While it is necessary within the files backend, newer backends may not
* follow the same structure. To go around this, we create stubs as necessary.
*
* If provided with a 'refs_heads_msg', we create the 'refs/heads/head' file
* with the provided message.
*/
void refs_create_refdir_stubs(struct repository *repo, const char *refdir,
const char *refs_heads_msg);
#endif /* REFS_H */

View File

@@ -106,19 +106,24 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
* set of caches.
*/
static struct ref_store *files_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
unsigned int flags)
{
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
struct strbuf sb = STRBUF_INIT;
struct strbuf ref_common_dir = STRBUF_INIT;
struct strbuf refdir = STRBUF_INIT;
bool is_worktree;
base_ref_store_init(ref_store, repo, gitdir, &refs_be_files);
refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,
&ref_common_dir);
base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
refs->store_flags = flags;
get_common_dir_noenv(&sb, gitdir);
refs->gitcommondir = strbuf_detach(&sb, NULL);
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
packed_ref_store_init(repo, refs->gitcommondir, flags);
packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
@@ -126,6 +131,8 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
chdir_notify_reparent("files-backend $GIT_COMMONDIR",
&refs->gitcommondir);
strbuf_release(&refdir);
return ref_store;
}
@@ -3702,7 +3709,11 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,
if (for_each_root_ref(refs, remove_one_root_ref, &data) < 0)
ret = -1;
if (ref_store_remove_on_disk(refs->packed_ref_store, err) < 0)
/*
* Directly access the cleanup functions for packed-refs as the generic function
* would try to clear stubs which isn't required for the files backend.
*/
if (refs->packed_ref_store->be->remove_on_disk(refs->packed_ref_store, err) < 0)
ret = -1;
strbuf_release(&sb);

View File

@@ -211,7 +211,12 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
return snapshot->refs->base.repo->hash_algo->hexsz;
}
/*
* Since packed-refs is only stored in the common dir, don't parse the
* payload and rely on the files-backend to set 'gitdir' correctly.
*/
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload UNUSED,
const char *gitdir,
unsigned int store_flags)
{

View File

@@ -14,6 +14,7 @@ struct ref_transaction;
*/
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
unsigned int store_flags);

View File

@@ -391,6 +391,7 @@ struct ref_store;
* the ref_store and to record the ref_store for later lookup.
*/
typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *payload,
const char *gitdir,
unsigned int flags);
/*
@@ -668,4 +669,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
unsigned int initial_transaction,
struct strbuf *err);
/*
* Given a gitdir and the reference storage payload provided, retrieve the
* 'refdir' and 'ref_common_dir'. The former is where references should be
* stored for the current worktree, the latter is the common reference
* directory if working with a linked worktree. If working with the main
* worktree, both values will be the same.
*
* This is used by backends that store references in the repository directly.
*/
void refs_compute_filesystem_location(const char *gitdir, const char *payload,
bool *is_worktree, struct strbuf *refdir,
struct strbuf *ref_common_dir);
#endif /* REFS_REFS_INTERNAL_H */

View File

@@ -372,18 +372,24 @@ static int reftable_be_fsync(int fd)
}
static struct ref_store *reftable_be_init(struct repository *repo,
const char *payload,
const char *gitdir,
unsigned int store_flags)
{
struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
struct strbuf ref_common_dir = STRBUF_INIT;
struct strbuf refdir = STRBUF_INIT;
struct strbuf path = STRBUF_INIT;
int is_worktree;
bool is_worktree;
mode_t mask;
mask = umask(0);
umask(mask);
base_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable);
refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,
&ref_common_dir);
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
refs->store_flags = store_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
@@ -419,14 +425,11 @@ static struct ref_store *reftable_be_init(struct repository *repo,
/*
* Set up the main reftable stack that is hosted in GIT_COMMON_DIR.
* This stack contains both the shared and the main worktree refs.
*
* Note that we don't try to resolve the path in case we have a
* worktree because `get_common_dir_noenv()` already does it for us.
*/
is_worktree = get_common_dir_noenv(&path, gitdir);
strbuf_addbuf(&path, &ref_common_dir);
if (!is_worktree) {
strbuf_reset(&path);
strbuf_realpath(&path, gitdir, 0);
strbuf_realpath(&path, ref_common_dir.buf, 0);
}
strbuf_addstr(&path, "/reftable");
refs->err = reftable_backend_init(&refs->main_backend, path.buf,
@@ -443,10 +446,9 @@ static struct ref_store *reftable_be_init(struct repository *repo,
* do it efficiently.
*/
if (is_worktree) {
strbuf_reset(&path);
strbuf_addf(&path, "%s/reftable", gitdir);
strbuf_addstr(&refdir, "/reftable");
refs->err = reftable_backend_init(&refs->worktree_backend, path.buf,
refs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf,
&refs->write_options);
if (refs->err)
goto done;
@@ -456,6 +458,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,
done:
assert(refs->err != REFTABLE_API_ERROR);
strbuf_release(&ref_common_dir);
strbuf_release(&refdir);
strbuf_release(&path);
return &refs->base;
}
@@ -491,19 +495,6 @@ static int reftable_be_create_on_disk(struct ref_store *ref_store,
safe_create_dir(the_repository, sb.buf, 1);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir);
write_file(sb.buf, "ref: refs/heads/.invalid");
adjust_shared_perm(the_repository, sb.buf);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/refs", refs->base.gitdir);
safe_create_dir(the_repository, sb.buf, 1);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir);
write_file(sb.buf, "this repository uses the reftable format");
adjust_shared_perm(the_repository, sb.buf);
strbuf_release(&sb);
return 0;
}
@@ -529,30 +520,6 @@ static int reftable_be_remove_on_disk(struct ref_store *ref_store,
strerror(errno));
ret = -1;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir);
if (unlink(sb.buf) < 0) {
strbuf_addf(err, "could not delete stub HEAD: %s",
strerror(errno));
ret = -1;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir);
if (unlink(sb.buf) < 0) {
strbuf_addf(err, "could not delete stub heads: %s",
strerror(errno));
ret = -1;
}
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/refs", refs->base.gitdir);
if (rmdir(sb.buf) < 0) {
strbuf_addf(err, "could not delete refs directory: %s",
strerror(errno));
ret = -1;
}
strbuf_release(&sb);
return ret;

View File

@@ -213,9 +213,12 @@ void repo_set_compat_hash_algo(struct repository *repo MAYBE_UNUSED, uint32_t al
}
void repo_set_ref_storage_format(struct repository *repo,
enum ref_storage_format format)
enum ref_storage_format format,
const char *payload)
{
repo->ref_storage_format = format;
free(repo->ref_storage_payload);
repo->ref_storage_payload = xstrdup_or_null(payload);
}
/*
@@ -297,7 +300,8 @@ int repo_init(struct repository *repo,
repo_set_hash_algo(repo, format.hash_algo);
repo_set_compat_hash_algo(repo, format.compat_hash_algo);
repo_set_ref_storage_format(repo, format.ref_storage_format);
repo_set_ref_storage_format(repo, format.ref_storage_format,
format.ref_storage_payload);
repo->repository_format_worktree_config = format.worktree_config;
repo->repository_format_relative_worktrees = format.relative_worktrees;
repo->repository_format_precious_objects = format.precious_objects;
@@ -390,6 +394,7 @@ void repo_clear(struct repository *repo)
FREE_AND_NULL(repo->index_file);
FREE_AND_NULL(repo->worktree);
FREE_AND_NULL(repo->submodule_prefix);
FREE_AND_NULL(repo->ref_storage_payload);
odb_free(repo->objects);
repo->objects = NULL;

View File

@@ -154,6 +154,11 @@ struct repository {
/* Repository's reference storage format, as serialized on disk. */
enum ref_storage_format ref_storage_format;
/*
* Reference storage information as needed for the backend. This contains
* only the payload from the reference URI without the schema.
*/
char *ref_storage_payload;
/* A unique-id for tracing purposes. */
int trace2_repo_id;
@@ -212,7 +217,8 @@ void repo_set_worktree(struct repository *repo, const char *path);
void repo_set_hash_algo(struct repository *repo, uint32_t algo);
void repo_set_compat_hash_algo(struct repository *repo, uint32_t compat_algo);
void repo_set_ref_storage_format(struct repository *repo,
enum ref_storage_format format);
enum ref_storage_format format,
const char *payload);
void initialize_repository(struct repository *repo);
RESULT_MUST_BE_USED
int repo_init(struct repository *r, const char *gitdir, const char *worktree);

96
setup.c
View File

@@ -631,6 +631,21 @@ static enum extension_result handle_extension_v0(const char *var,
return EXTENSION_UNKNOWN;
}
static void parse_reference_uri(const char *value, char **format,
char **payload)
{
const char *schema_end;
schema_end = strstr(value, "://");
if (!schema_end) {
*format = xstrdup(value);
*payload = NULL;
} else {
*format = xstrndup(value, schema_end - value);
*payload = xstrdup_or_null(schema_end + 3);
}
}
/*
* Record any new extensions in this function.
*/
@@ -673,10 +688,17 @@ static enum extension_result handle_extension(const char *var,
return EXTENSION_OK;
} else if (!strcmp(ext, "refstorage")) {
unsigned int format;
char *format_str;
if (!value)
return config_error_nonbool(var);
format = ref_storage_format_by_name(value);
parse_reference_uri(value, &format_str,
&data->ref_storage_payload);
format = ref_storage_format_by_name(format_str);
free(format_str);
if (format == REF_STORAGE_FORMAT_UNKNOWN)
return error(_("invalid value for '%s': '%s'"),
"extensions.refstorage", value);
@@ -852,6 +874,7 @@ void clear_repository_format(struct repository_format *format)
string_list_clear(&format->v1_only_extensions, 0);
free(format->work_tree);
free(format->partial_clone);
free(format->ref_storage_payload);
init_repository_format(format);
}
@@ -1817,6 +1840,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
static struct strbuf cwd = STRBUF_INIT;
struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;
const char *prefix = NULL;
const char *ref_backend_uri;
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
/*
@@ -1944,7 +1968,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
repo_set_compat_hash_algo(the_repository,
repo_fmt.compat_hash_algo);
repo_set_ref_storage_format(the_repository,
repo_fmt.ref_storage_format);
repo_fmt.ref_storage_format,
repo_fmt.ref_storage_payload);
the_repository->repository_format_worktree_config =
repo_fmt.worktree_config;
the_repository->repository_format_relative_worktrees =
@@ -1975,6 +2000,25 @@ const char *setup_git_directory_gently(int *nongit_ok)
setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
}
/*
* The env variable should override the repository config
* for 'extensions.refStorage'.
*/
ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
if (ref_backend_uri) {
char *backend, *payload;
enum ref_storage_format format;
parse_reference_uri(ref_backend_uri, &backend, &payload);
format = ref_storage_format_by_name(backend);
if (format == REF_STORAGE_FORMAT_UNKNOWN)
die(_("unknown ref storage format: '%s'"), backend);
repo_set_ref_storage_format(the_repository, format, payload);
free(backend);
free(payload);
}
setup_original_cwd();
strbuf_release(&dir);
@@ -2046,7 +2090,8 @@ void check_repository_format(struct repository_format *fmt)
repo_set_hash_algo(the_repository, fmt->hash_algo);
repo_set_compat_hash_algo(the_repository, fmt->compat_hash_algo);
repo_set_ref_storage_format(the_repository,
fmt->ref_storage_format);
fmt->ref_storage_format,
fmt->ref_storage_payload);
the_repository->repository_format_worktree_config =
fmt->worktree_config;
the_repository->repository_format_submodule_path_cfg =
@@ -2319,7 +2364,8 @@ void initialize_repository_version(int hash_algo,
* the remote repository's format.
*/
if (hash_algo != GIT_HASH_SHA1_LEGACY ||
ref_storage_format != REF_STORAGE_FORMAT_FILES)
ref_storage_format != REF_STORAGE_FORMAT_FILES ||
the_repository->ref_storage_payload)
target_version = GIT_REPO_VERSION_READ;
if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
@@ -2328,11 +2374,20 @@ void initialize_repository_version(int hash_algo,
else if (reinit)
repo_config_set_gently(the_repository, "extensions.objectformat", NULL);
if (ref_storage_format != REF_STORAGE_FORMAT_FILES)
if (the_repository->ref_storage_payload) {
struct strbuf ref_uri = STRBUF_INIT;
strbuf_addf(&ref_uri, "%s://%s",
ref_storage_format_to_name(ref_storage_format),
the_repository->ref_storage_payload);
repo_config_set(the_repository, "extensions.refstorage", ref_uri.buf);
strbuf_release(&ref_uri);
} else if (ref_storage_format != REF_STORAGE_FORMAT_FILES) {
repo_config_set(the_repository, "extensions.refstorage",
ref_storage_format_to_name(ref_storage_format));
else if (reinit)
} else if (reinit) {
repo_config_set_gently(the_repository, "extensions.refstorage", NULL);
}
if (reinit) {
struct strbuf config = STRBUF_INIT;
@@ -2375,14 +2430,12 @@ static int is_reinit(void)
return ret;
}
void create_reference_database(enum ref_storage_format ref_storage_format,
const char *initial_branch, int quiet)
void create_reference_database(const char *initial_branch, int quiet)
{
struct strbuf err = STRBUF_INIT;
char *to_free = NULL;
int reinit = is_reinit();
repo_set_ref_storage_format(the_repository, ref_storage_format);
if (ref_store_create_on_disk(get_main_ref_store(the_repository), 0, &err))
die("failed to set up refs db: %s", err.buf);
@@ -2616,6 +2669,7 @@ static void repository_format_configure(struct repository_format *repo_fmt,
.ignore_repo = 1,
.ignore_worktree = 1,
};
const char *ref_backend_uri;
const char *env;
config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);
@@ -2661,7 +2715,26 @@ static void repository_format_configure(struct repository_format *repo_fmt,
} else {
repo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
}
repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format);
ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
if (ref_backend_uri) {
char *backend, *payload;
enum ref_storage_format format;
parse_reference_uri(ref_backend_uri, &backend, &payload);
format = ref_storage_format_by_name(backend);
if (format == REF_STORAGE_FORMAT_UNKNOWN)
die(_("unknown ref storage format: '%s'"), backend);
repo_fmt->ref_storage_format = format;
repo_fmt->ref_storage_payload = payload;
free(backend);
}
repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format,
repo_fmt->ref_storage_payload);
}
int init_db(const char *git_dir, const char *real_git_dir,
@@ -2717,8 +2790,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
&repo_fmt, init_shared_repository);
if (!(flags & INIT_DB_SKIP_REFDB))
create_reference_database(repo_fmt.ref_storage_format,
initial_branch, flags & INIT_DB_QUIET);
create_reference_database(initial_branch, flags & INIT_DB_QUIET);
create_object_directory();
if (repo_settings_get_shared_repository(the_repository)) {

View File

@@ -172,6 +172,7 @@ struct repository_format {
int hash_algo;
int compat_hash_algo;
enum ref_storage_format ref_storage_format;
char *ref_storage_payload;
int sparse_index;
char *work_tree;
struct string_list unknown_extensions;
@@ -241,8 +242,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
void initialize_repository_version(int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(enum ref_storage_format ref_storage_format,
const char *initial_branch, int quiet);
void create_reference_database(const char *initial_branch, int quiet);
/*
* NOTE NOTE NOTE!!

View File

@@ -213,6 +213,7 @@ integration_tests = [
't1420-lost-found.sh',
't1421-reflog-write.sh',
't1422-show-ref-exists.sh',
't1423-ref-backend.sh',
't1430-bad-ref-name.sh',
't1450-fsck.sh',
't1451-fsck-buffer.sh',

280
t/t1423-ref-backend.sh Executable file
View File

@@ -0,0 +1,280 @@
#!/bin/sh
test_description='Test reference backend URIs'
. ./test-lib.sh
# Run a git command with the provided reference storage. Reset the backend
# post running the command.
# Usage: run_with_uri <repo> <backend> <uri> <cmd>
# <repo> is the relative path to the repo to run the command in.
# <backend> is the original ref storage of the repo.
# <uri> is the new URI to be set for the ref storage.
# <cmd> is the git subcommand to be run in the repository.
# <via> if 'config', set the backend via the 'extensions.refStorage' config.
# if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.
run_with_uri () {
repo=$1 &&
backend=$2 &&
uri=$3 &&
cmd=$4 &&
via=$5 &&
git -C "$repo" config set core.repositoryformatversion 1 &&
if test "$via" = "env"
then
test_env GIT_REFERENCE_BACKEND="$uri" git -C "$repo" $cmd
elif test "$via" = "config"
then
git -C "$repo" config set extensions.refStorage "$uri" &&
git -C "$repo" $cmd &&
git -C "$repo" config set extensions.refStorage "$backend"
fi
}
# Test a repository with a given reference storage by running and comparing
# 'git refs list' before and after setting the new reference backend. If
# err_msg is set, expect the command to fail and grep for the provided err_msg.
# Usage: run_with_uri <repo> <backend> <uri> <cmd>
# <repo> is the relative path to the repo to run the command in.
# <backend> is the original ref storage of the repo.
# <uri> is the new URI to be set for the ref storage.
# <via> if 'config', set the backend via the 'extensions.refStorage' config.
# if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.
# <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.
test_refs_backend () {
repo=$1 &&
backend=$2 &&
uri=$3 &&
via=$4 &&
err_msg=$5 &&
if test -n "$err_msg";
then
if test "$via" = "env"
then
test_env GIT_REFERENCE_BACKEND="$uri" test_must_fail git -C "$repo" refs list 2>err
elif test "$via" = "config"
then
git -C "$repo" config set extensions.refStorage "$uri" &&
test_must_fail git -C "$repo" refs list 2>err &&
test_grep "$err_msg" err
fi
else
git -C "$repo" refs list >expect &&
run_with_uri "$repo" "$backend" "$uri" "refs list" "$via">actual &&
test_cmp expect actual
fi
}
# Verify that the expected files are present in the gitdir and the refsdir.
# Usage: verify_files_exist <gitdir> <refdir>
# <gitdir> is the path for the gitdir.
# <refdir> is the path for the refdir.
verify_files_exist () {
gitdir=$1 &&
refdir=$2 &&
# verify that the stubs were added to the $GITDIR.
echo "repository uses alternate refs storage" >expect &&
test_cmp expect $gitdir/refs/heads &&
echo "ref: refs/heads/.invalid" >expect &&
test_cmp expect $gitdir/HEAD
# verify that backend specific files exist.
case "$GIT_DEFAULT_REF_FORMAT" in
files)
test_path_is_dir $refdir/refs/heads &&
test_path_is_file $refdir/HEAD;;
reftable)
test_path_is_dir $refdir/reftable &&
test_path_is_file $refdir/reftable/tables.list;;
*)
BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
esac
}
methods="config env"
for method in $methods
do
test_expect_success "$method: URI is invalid" '
test_when_finished "rm -rf repo" &&
git init repo &&
test_refs_backend repo files "reftable@/home/reftable" "$method" \
"invalid value for ${SQ}extensions.refstorage${SQ}"
'
test_expect_success "$method: URI ends with colon" '
test_when_finished "rm -rf repo" &&
git init repo &&
test_refs_backend repo files "reftable:" "$method" \
"invalid value for ${SQ}extensions.refstorage${SQ}"
'
test_expect_success "$method: unknown reference backend" '
test_when_finished "rm -rf repo" &&
git init repo &&
test_refs_backend repo files "db://.git" "$method" \
"invalid value for ${SQ}extensions.refstorage${SQ}"
'
ref_formats="files reftable"
for from_format in $ref_formats
do
for to_format in $ref_formats
do
if test "$from_format" = "$to_format"
then
continue
fi
for dir in "$(pwd)/repo/.git" "."
do
test_expect_success "$method: read from $to_format backend, $dir dir" '
test_when_finished "rm -rf repo" &&
git init --ref-format=$from_format repo &&
(
cd repo &&
test_commit 1 &&
test_commit 2 &&
test_commit 3 &&
git refs migrate --dry-run --ref-format=$to_format >out &&
BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method"
)
'
test_expect_success "$method: write to $to_format backend, $dir dir" '
test_when_finished "rm -rf repo" &&
git init --ref-format=$from_format repo &&
(
cd repo &&
test_commit 1 &&
test_commit 2 &&
test_commit 3 &&
git refs migrate --dry-run --ref-format=$to_format >out &&
BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" &&
git refs list >expect &&
run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
"tag -d 1" "$method" &&
git refs list >actual &&
test_cmp expect actual &&
git refs list | grep -v "refs/tags/1" >expect &&
run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
"refs list" "$method" >actual &&
test_cmp expect actual
)
'
test_expect_success "$method: with worktree and $to_format backend, $dir dir" '
test_when_finished "rm -rf repo wt" &&
git init --ref-format=$from_format repo &&
(
cd repo &&
test_commit 1 &&
test_commit 2 &&
test_commit 3 &&
git refs migrate --dry-run --ref-format=$to_format >out &&
BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" &&
run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
"worktree add ../wt 2" "$method" &&
run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
"for-each-ref --include-root-refs" "$method" >actual &&
run_with_uri ../wt "$from_format" "$to_format://$BACKEND_PATH" \
"for-each-ref --include-root-refs" "$method" >expect &&
! test_cmp expect actual &&
run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \
"rev-parse 2" "$method" >actual &&
run_with_uri ../wt "$from_format" "$to_format://$BACKEND_PATH" \
"rev-parse HEAD" "$method" >expect &&
test_cmp expect actual
)
'
done # closes dir
test_expect_success "migrating repository to $to_format with alternate refs directory" '
test_when_finished "rm -rf repo refdir" &&
mkdir refdir &&
GIT_REFERENCE_BACKEND="${from_format}://$(pwd)/refdir" git init repo &&
(
cd repo &&
test_commit 1 &&
test_commit 2 &&
test_commit 3 &&
git refs migrate --ref-format=$to_format &&
git refs list >out &&
test_grep "refs/tags/1" out &&
test_grep "refs/tags/2" out &&
test_grep "refs/tags/3" out
)
'
done # closes to_format
done # closes from_format
done # closes method
test_expect_success 'initializing repository with alt ref directory' '
test_when_finished "rm -rf repo refdir" &&
mkdir refdir &&
BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
GIT_REFERENCE_BACKEND=$BACKEND git init repo &&
verify_files_exist repo/.git refdir &&
(
cd repo &&
git config get extensions.refstorage >expect &&
echo $BACKEND >actual &&
test_cmp expect actual &&
test_commit 1 &&
test_commit 2 &&
test_commit 3 &&
git refs list >out &&
test_grep "refs/tags/1" out &&
test_grep "refs/tags/2" out &&
test_grep "refs/tags/3" out
)
'
test_expect_success 'cloning repository with alt ref directory' '
test_when_finished "rm -rf source repo refdir" &&
mkdir refdir &&
git init source &&
test_commit -C source 1 &&
test_commit -C source 2 &&
test_commit -C source 3 &&
BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" &&
GIT_REFERENCE_BACKEND=$BACKEND git clone source repo &&
git -C repo config get extensions.refstorage >expect &&
echo $BACKEND >actual &&
test_cmp expect actual &&
verify_files_exist repo/.git refdir &&
git -C source for-each-ref refs/tags/ >expect &&
git -C repo for-each-ref refs/tags/ >actual &&
test_cmp expect actual
'
test_done