`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
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)
{
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);
if (!be)
BUG("reference backend is unknown");
- refs = be->init(repo, NULL, 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;
}
}
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);
}
/*
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;
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;
/* 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;
void repo_set_hash_algo(struct repository *repo, int algo);
void repo_set_compat_hash_algo(struct repository *repo, int 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);
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.
*/
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);
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);
}
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 =
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_relative_worktrees =
} else {
repo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
}
- repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format);
+ 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,
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;
'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',
--- /dev/null
+#!/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.
+run_with_uri () {
+ repo=$1 &&
+ backend=$2 &&
+ uri=$3 &&
+ cmd=$4 &&
+
+ git -C "$repo" config set core.repositoryformatversion 1
+ git -C "$repo" config set extensions.refStorage "$uri" &&
+ git -C "$repo" $cmd &&
+ git -C "$repo" config set extensions.refStorage "$backend"
+}
+
+# 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.
+# <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.
+test_refs_backend () {
+ repo=$1 &&
+ backend=$2 &&
+ uri=$3 &&
+ err_msg=$4 &&
+
+ git -C "$repo" config set core.repositoryformatversion 1 &&
+ if test -n "$err_msg";
+ then
+ git -C "$repo" config set extensions.refStorage "$uri" &&
+ test_must_fail git -C "$repo" refs list 2>err &&
+ test_grep "$err_msg" err
+ else
+ git -C "$repo" refs list >expect &&
+ run_with_uri "$repo" "$backend" "$uri" "refs list" >actual &&
+ test_cmp expect actual
+ fi
+}
+
+test_expect_success 'URI is invalid' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ test_refs_backend repo files "reftable@/home/reftable" \
+ "invalid value for ${SQ}extensions.refstorage${SQ}"
+'
+
+test_expect_success 'URI ends with colon' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ test_refs_backend repo files "reftable:" \
+ "invalid value for ${SQ}extensions.refstorage${SQ}"
+'
+
+test_expect_success 'unknown reference backend' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ test_refs_backend repo files "db://.git" \
+ "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 "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 "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" &&
+
+ git refs list >expect &&
+ run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" "tag -d 1" &&
+ 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" >actual &&
+ test_cmp expect actual
+ )
+ '
+
+ test_expect_success "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)" &&
+
+ git config set core.repositoryformatversion 1 &&
+ git config set extensions.refStorage "$to_format://$BACKEND_PATH" &&
+
+ git worktree add ../wt 2
+ ) &&
+
+ git -C repo for-each-ref --include-root-refs >expect &&
+ git -C wt for-each-ref --include-root-refs >expect &&
+ ! test_cmp expect actual &&
+
+ git -C wt rev-parse 2 >expect &&
+ git -C wt rev-parse HEAD >actual &&
+ test_cmp expect actual
+ '
+ done # closes dir
+done # closes to_format
+done # closes from_format
+
+test_done