setup: introduce "extensions.refStorage" extension

Introduce a new "extensions.refStorage" extension that allows us to specify the ref storage format used by a repository. For now, the only supported format is the "files" format, but this list will likely soon be extended to also support the upcoming "reftable" format. There have been discussions on the Git mailing list in the past around how exactly this extension should look like. One alternative [1] that was discussed was whether it would make sense to model the extension in such a way that backends are arbitrarily stackable. This would allow for a combined value of e.g. "loose,packed-refs" or "loose,reftable", which indicates that new refs would be written via "loose" files backend and compressed into "packed-refs" or "reftable" backends, respectively. It is arguable though whether this flexibility and the complexity that it brings with it is really required for now. It is not foreseeable that there will be a proliferation of backends in the near-term future, and the current set of existing formats and formats which are on the horizon can easily be configured with the much simpler proposal where we have a single value, only. Furthermore, if we ever see that we indeed want to gain the ability to arbitrarily stack the ref formats, then we can adapt the current extension rather easily. Given that Git clients will refuse any unknown value for the "extensions.refStorage" extension they would also know to ignore a stacked "loose,packed-refs" in the future. So let's stick with the easy proposal for the time being and wire up the extension. [1]: <pull.1408.git.1667846164.gitgitgadget@gmail.com> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Dec 29, 2023 at 08:26 UTC d7497a42b05bb810afeb6acc8c9447b77b1f075d
8 files changed +69 -6
Documentation/config/extensions.txt
+11
@@ -7,6 +7,17 @@ Note that this setting should only be set by linkgit:git-init[1] or
7 linkgit:git-clone[1]. Trying to change it after initialization will not
8 work and will produce hard-to-diagnose issues.
9
10 +extensions.refStorage::
11 + Specify the ref storage format to use. The acceptable values are:
12 ++
13 +include::../ref-storage-format.txt[]
14 ++
15 +It is an error to specify this key unless `core.repositoryFormatVersion` is 1.
16 ++
17 +Note that this setting should only be set by linkgit:git-init[1] or
18 +linkgit:git-clone[1]. Trying to change it after initialization will not
19 +work and will produce hard-to-diagnose issues.
20 +
21 extensions.worktreeConfig::
22 If enabled, then worktrees will load config settings from the
23 `$GIT_DIR/config.worktree` file in addition to the
Documentation/ref-storage-format.txt new
+1
@@ -0,0 +1 @@
1 +* `files` for loose files with packed-refs. This is the default.
Documentation/technical/repository-version.txt
+5
@@ -100,3 +100,8 @@ If set, by default "git config" reads from both "config" and
100 multiple working directory mode, "config" file is shared while
101 "config.worktree" is per-working directory (i.e., it's in
102 GIT_COMMON_DIR/worktrees/<id>/config.worktree)
103 +
104 +==== `refStorage`
105 +
106 +Specifies the file format for the ref database. The only valid value
107 +is `files` (loose references with a packed-refs file).
builtin/clone.c
+1 -1
@@ -1291,7 +1291,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1291 * ours to the same thing.
1292 */
1293 hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
1294 - initialize_repository_version(hash_algo, 1);
1294 + initialize_repository_version(hash_algo, the_repository->ref_storage_format, 1);
1295 repo_set_hash_algo(the_repository, hash_algo);
1296 create_reference_database(the_repository->ref_storage_format, NULL, 1);
1297
setup.c
+21 -3
@@ -592,6 +592,17 @@ static enum extension_result handle_extension(const char *var,
592 "extensions.objectformat", value);
593 data->hash_algo = format;
594 return EXTENSION_OK;
595 + } else if (!strcmp(ext, "refstorage")) {
596 + unsigned int format;
597 +
598 + if (!value)
599 + return config_error_nonbool(var);
600 + format = ref_storage_format_by_name(value);
601 + if (format == REF_STORAGE_FORMAT_UNKNOWN)
602 + return error(_("invalid value for '%s': '%s'"),
603 + "extensions.refstorage", value);
604 + data->ref_storage_format = format;
605 + return EXTENSION_OK;
606 }
607 return EXTENSION_UNKNOWN;
608 }
@@ -1871,12 +1882,15 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
1882 return 1;
1883 }
1884
1874 -void initialize_repository_version(int hash_algo, int reinit)
1885 +void initialize_repository_version(int hash_algo,
1886 + unsigned int ref_storage_format,
1887 + int reinit)
1888 {
1889 char repo_version_string[10];
1890 int repo_version = GIT_REPO_VERSION;
1891
1879 - if (hash_algo != GIT_HASH_SHA1)
1892 + if (hash_algo != GIT_HASH_SHA1 ||
1893 + ref_storage_format != REF_STORAGE_FORMAT_FILES)
1894 repo_version = GIT_REPO_VERSION_READ;
1895
1896 /* This forces creation of new config file */
@@ -1889,6 +1903,10 @@ void initialize_repository_version(int hash_algo, int reinit)
1903 hash_algos[hash_algo].name);
1904 else if (reinit)
1905 git_config_set_gently("extensions.objectformat", NULL);
1906 +
1907 + if (ref_storage_format != REF_STORAGE_FORMAT_FILES)
1908 + git_config_set("extensions.refstorage",
1909 + ref_storage_format_to_name(ref_storage_format));
1910 }
1911
1912 static int is_reinit(void)
@@ -2030,7 +2048,7 @@ static int create_default_files(const char *template_path,
2048 adjust_shared_perm(get_git_dir());
2049 }
2050
2033 - initialize_repository_version(fmt->hash_algo, 0);
2051 + initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0);
2052
2053 /* Check filemode trustability */
2054 path = git_path_buf(&buf, "config");
setup.h
+3 -1
@@ -180,7 +180,9 @@ int init_db(const char *git_dir, const char *real_git_dir,
180 unsigned int ref_storage_format,
181 const char *initial_branch, int init_shared_repository,
182 unsigned int flags);
183 -void initialize_repository_version(int hash_algo, int reinit);
183 +void initialize_repository_version(int hash_algo,
184 + unsigned int ref_storage_format,
185 + int reinit);
186 void create_reference_database(unsigned int ref_storage_format,
187 const char *initial_branch, int quiet);
188
t/t0001-init.sh
+26
@@ -532,6 +532,32 @@ test_expect_success 'init rejects attempts to initialize with different hash' '
532 test_must_fail git -C sha256 init --object-format=sha1
533 '
534
535 +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage is not allowed with repo version 0' '
536 + test_when_finished "rm -rf refstorage" &&
537 + git init refstorage &&
538 + git -C refstorage config extensions.refStorage files &&
539 + test_must_fail git -C refstorage rev-parse 2>err &&
540 + grep "repo version is 0, but v1-only extension found" err
541 +'
542 +
543 +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with files backend' '
544 + test_when_finished "rm -rf refstorage" &&
545 + git init refstorage &&
546 + git -C refstorage config core.repositoryformatversion 1 &&
547 + git -C refstorage config extensions.refStorage files &&
548 + test_commit -C refstorage A &&
549 + git -C refstorage rev-parse --verify HEAD
550 +'
551 +
552 +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown backend' '
553 + test_when_finished "rm -rf refstorage" &&
554 + git init refstorage &&
555 + git -C refstorage config core.repositoryformatversion 1 &&
556 + git -C refstorage config extensions.refStorage garbage &&
557 + test_must_fail git -C refstorage rev-parse 2>err &&
558 + grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
559 +'
560 +
561 test_expect_success MINGW 'core.hidedotfiles = false' '
562 git config --global core.hidedotfiles false &&
563 rm -rf newdir &&
t/test-lib.sh
+1 -1
@@ -1937,7 +1937,7 @@ test_lazy_prereq SHA1 '
1937 '
1938
1939 test_lazy_prereq DEFAULT_REPO_FORMAT '
1940 - test_have_prereq SHA1
1940 + test_have_prereq SHA1,REFFILES
1941 '
1942
1943 # Ensure that no test accidentally triggers a Git command