diff --git a/Documentation/git.adoc b/Documentation/git.adoc index ce099e78b8..8c6a3f6042 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc @@ -584,6 +584,14 @@ 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_REF_URI`:: + Specify which reference backend to be used along with its URI. Reference + backends like the files, reftable backend use the $GIT_DIR as their URI. ++ +Expects the format `://`, where the +__ specifies the reference backend and the __ +specifies the URI used by the backend. + Git Commits ~~~~~~~~~~~ `GIT_AUTHOR_NAME`:: diff --git a/environment.h b/environment.h index 51898c99cd..9bc380bba4 100644 --- a/environment.h +++ b/environment.h @@ -42,6 +42,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_REF_URI_ENVIRONMENT "GIT_REF_URI" /* * Environment variable used to propagate the --no-advice global option to the diff --git a/refs.c b/refs.c index e06e0cb072..1d29d9b1c9 100644 --- a/refs.c +++ b/refs.c @@ -2191,17 +2191,79 @@ void ref_store_release(struct ref_store *ref_store) free(ref_store->gitdir); } +static struct ref_store *get_ref_store_for_dir(struct repository *r, + char *dir, + enum ref_storage_format format) +{ + struct ref_store *ref_store = ref_store_init(r, format, dir, + REF_STORE_ALL_CAPS); + return maybe_debug_wrap_ref_store(dir, ref_store); +} + +static struct ref_store *get_ref_store_from_uri(struct repository *repo, + const char *uri) +{ + struct string_list ref_backend_info = STRING_LIST_INIT_DUP; + enum ref_storage_format format; + struct ref_store *store = NULL; + char *format_string; + char *dir; + + if (!uri) { + error(_("reference backend uri is not provided")); + goto cleanup; + } + + if (string_list_split(&ref_backend_info, uri, ":", 2) != 2) { + error(_("invalid reference backend uri format '%s'"), uri); + goto cleanup; + } + + format_string = ref_backend_info.items[0].string; + if (!starts_with(ref_backend_info.items[1].string, "//")) { + error(_("invalid reference backend uri format '%s'"), uri); + goto cleanup; + } + dir = ref_backend_info.items[1].string + 2; + + if (!dir[0]) { + error(_("invalid path in uri '%s'"), uri); + goto cleanup; + } + + format = ref_storage_format_by_name(format_string); + if (format == REF_STORAGE_FORMAT_UNKNOWN) { + error(_("unknown reference backend '%s'"), format_string); + goto cleanup; + } + + store = get_ref_store_for_dir(repo, dir, format); + +cleanup: + string_list_clear(&ref_backend_info, 0); + return store; +} + struct ref_store *get_main_ref_store(struct repository *r) { + char *ref_uri; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); - r->refs_private = ref_store_init(r, r->ref_storage_format, - r->gitdir, REF_STORE_ALL_CAPS); - r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + ref_uri = getenv(GIT_REF_URI_ENVIRONMENT); + if (ref_uri) { + r->refs_private = get_ref_store_from_uri(r, ref_uri); + if (!r->refs_private) + die("failed to initialize ref store from URI: %s", ref_uri); + + } else { + r->refs_private = get_ref_store_for_dir(r, r->gitdir, + r->ref_storage_format); + } return r->refs_private; } diff --git a/t/meson.build b/t/meson.build index 0b1de3251a..0b44b5e06d 100644 --- a/t/meson.build +++ b/t/meson.build @@ -210,6 +210,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', diff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh new file mode 100755 index 0000000000..f36125bf64 --- /dev/null +++ b/t/t1423-ref-backend.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +test_description='Test different reference backend URIs' + +. ./test-lib.sh + +test_expect_success 'empty uri provided' ' + test_when_finished "rm -rf repo" && + git init --ref-format=files repo && + ( + cd repo && + GIT_REF_URI="" && + export GIT_REF_URI && + test_must_fail git refs list 2>err && + test_grep "invalid reference backend uri format" err + ) +' + +test_expect_success 'invalid uri provided' ' + test_when_finished "rm -rf repo" && + git init --ref-format=files repo && + ( + cd repo && + GIT_REF_URI="reftable@/home/reftable" && + export GIT_REF_URI && + test_must_fail git refs list 2>err && + test_grep "invalid reference backend uri format" err + ) +' + +test_expect_success 'empty path in uri' ' + test_when_finished "rm -rf repo" && + git init --ref-format=files repo && + ( + cd repo && + GIT_REF_URI="reftable://" && + export GIT_REF_URI && + test_must_fail git refs list 2>err && + test_grep "invalid path in uri" err + ) +' + +test_expect_success 'uri ends at colon' ' + test_when_finished "rm -rf repo" && + git init --ref-format=files repo && + ( + cd repo && + GIT_REF_URI="reftable:" && + export GIT_REF_URI && + test_must_fail git refs list 2>err && + test_grep "invalid reference backend uri format" err + ) +' + +test_expect_success 'unknown reference backend' ' + test_when_finished "rm -rf repo" && + git init --ref-format=files repo && + ( + cd repo && + GIT_REF_URI="db://.git" && + export GIT_REF_URI && + test_must_fail git refs list 2>err && + test_grep "unknown reference backend" err + ) +' + +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 + + test_expect_success "read from $to_format backend" ' + 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=$(cat out | sed "s/.* ${SQ}\(.*\)${SQ}/\1/") && + git refs list >expect && + GIT_REF_URI="$to_format://$BACKEND_PATH" git refs list >actual && + test_cmp expect actual + ) + ' + + test_expect_success "write to $to_format backend" ' + 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 && + git refs list >expect && + + BACKEND_PATH=$(cat out | sed "s/.* ${SQ}\(.*\)${SQ}/\1/") && + GIT_REF_URI="$to_format://$BACKEND_PATH" git tag -d 1 && + + git refs list >actual && + test_cmp expect actual && + + GIT_REF_URI="$to_format://$BACKEND_PATH" git refs list >expect && + git refs list >out && + cat out | grep -v "refs/tags/1" >actual && + test_cmp expect actual + ) + ' + done +done + +test_done