From 79543e760d0219063ab21f687e7538c3e89b2e99 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 12 Dec 2023 08:00:41 +0100 Subject: setup: extract function to create the refdb We're about to let callers skip creation of the reference database when calling `init_db()`. Extract the logic into a standalone function so that it becomes easier to do this refactoring. While at it, expand the comment that explains why we always create the "refs/" directory. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- setup.c | 103 ++++++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 38 deletions(-) (limited to 'setup.c') diff --git a/setup.c b/setup.c index fc592dc6dd..865cfe6743 100644 --- a/setup.c +++ b/setup.c @@ -1885,6 +1885,68 @@ void initialize_repository_version(int hash_algo, int reinit) git_config_set_gently("extensions.objectformat", NULL); } +static int is_reinit(void) +{ + struct strbuf buf = STRBUF_INIT; + char junk[2]; + int ret; + + git_path_buf(&buf, "HEAD"); + ret = !access(buf.buf, R_OK) || readlink(buf.buf, junk, sizeof(junk) - 1) != -1; + strbuf_release(&buf); + return ret; +} + +static void create_reference_database(const char *initial_branch, int quiet) +{ + struct strbuf err = STRBUF_INIT; + int reinit = is_reinit(); + + /* + * We need to create a "refs" dir in any case so that older versions of + * Git can tell that this is a repository. This serves two main purposes: + * + * - Clients will know to stop walking the parent-directory chain when + * detecting the Git repository. Otherwise they may end up detecting + * a Git repository in a parent directory instead. + * + * - Instead of failing to detect a repository with unknown reference + * format altogether, old clients will print an error saying that + * they do not understand the reference format extension. + */ + safe_create_dir(git_path("refs"), 1); + adjust_shared_perm(git_path("refs")); + + if (refs_init_db(&err)) + die("failed to set up refs db: %s", err.buf); + + /* + * Point the HEAD symref to the initial branch with if HEAD does + * not yet exist. + */ + if (!reinit) { + char *ref; + + if (!initial_branch) + initial_branch = git_default_branch_name(quiet); + + ref = xstrfmt("refs/heads/%s", initial_branch); + if (check_refname_format(ref, 0) < 0) + die(_("invalid initial branch name: '%s'"), + initial_branch); + + if (create_symref("HEAD", ref, NULL) < 0) + exit(1); + free(ref); + } + + if (reinit && initial_branch) + warning(_("re-init: ignored --initial-branch=%s"), + initial_branch); + + strbuf_release(&err); +} + static int create_default_files(const char *template_path, const char *original_git_dir, const char *initial_branch, @@ -1896,10 +1958,8 @@ static int create_default_files(const char *template_path, struct stat st1; struct strbuf buf = STRBUF_INIT; char *path; - char junk[2]; int reinit; int filemode; - struct strbuf err = STRBUF_INIT; const char *init_template_dir = NULL; const char *work_tree = get_git_work_tree(); @@ -1919,6 +1979,8 @@ static int create_default_files(const char *template_path, reset_shared_repository(); git_config(git_default_config, NULL); + reinit = is_reinit(); + /* * We must make sure command-line options continue to override any * values we might have just re-read from the config. @@ -1962,39 +2024,7 @@ static int create_default_files(const char *template_path, adjust_shared_perm(get_git_dir()); } - /* - * We need to create a "refs" dir in any case so that older - * versions of git can tell that this is a repository. - */ - safe_create_dir(git_path("refs"), 1); - adjust_shared_perm(git_path("refs")); - - if (refs_init_db(&err)) - die("failed to set up refs db: %s", err.buf); - - /* - * Point the HEAD symref to the initial branch with if HEAD does - * not yet exist. - */ - path = git_path_buf(&buf, "HEAD"); - reinit = (!access(path, R_OK) - || readlink(path, junk, sizeof(junk)-1) != -1); - if (!reinit) { - char *ref; - - if (!initial_branch) - initial_branch = git_default_branch_name(quiet); - - ref = xstrfmt("refs/heads/%s", initial_branch); - if (check_refname_format(ref, 0) < 0) - die(_("invalid initial branch name: '%s'"), - initial_branch); - - if (create_symref("HEAD", ref, NULL) < 0) - exit(1); - free(ref); - } - + create_reference_database(initial_branch, quiet); initialize_repository_version(fmt->hash_algo, 0); /* Check filemode trustability */ @@ -2158,9 +2188,6 @@ int init_db(const char *git_dir, const char *real_git_dir, prev_bare_repository, init_shared_repository, flags & INIT_DB_QUIET); - if (reinit && initial_branch) - warning(_("re-init: ignored --initial-branch=%s"), - initial_branch); create_object_directory(); -- cgit v1.3-5-g9baa From 56cd0334f7664feffcd8ed8f8f0d3929374c69e0 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 12 Dec 2023 08:00:46 +0100 Subject: setup: allow skipping creation of the refdb Allow callers to skip creation of the reference database via a new flag `INIT_DB_SKIP_REFDB`, which is required for git-clone(1) so that we can create it at a later point once the object format has been discovered from the remote repository. Note that we also uplift the call to `create_reference_database()` into `init_db()`, which makes it easier to handle the new flag for us. This changes the order in which we do initialization so that we now set up the Git configuration before we create the reference database. In practice this move should not result in any change in behaviour. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- setup.c | 13 +++++-------- setup.h | 5 +++-- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'setup.c') diff --git a/setup.c b/setup.c index 865cfe6743..d6a1c59b7b 100644 --- a/setup.c +++ b/setup.c @@ -1949,11 +1949,9 @@ static void create_reference_database(const char *initial_branch, int quiet) static int create_default_files(const char *template_path, const char *original_git_dir, - const char *initial_branch, const struct repository_format *fmt, int prev_bare_repository, - int init_shared_repository, - int quiet) + int init_shared_repository) { struct stat st1; struct strbuf buf = STRBUF_INIT; @@ -2024,7 +2022,6 @@ static int create_default_files(const char *template_path, adjust_shared_perm(get_git_dir()); } - create_reference_database(initial_branch, quiet); initialize_repository_version(fmt->hash_algo, 0); /* Check filemode trustability */ @@ -2184,11 +2181,11 @@ int init_db(const char *git_dir, const char *real_git_dir, validate_hash_algorithm(&repo_fmt, hash); reinit = create_default_files(template_dir, original_git_dir, - initial_branch, &repo_fmt, - prev_bare_repository, - init_shared_repository, - flags & INIT_DB_QUIET); + &repo_fmt, prev_bare_repository, + init_shared_repository); + if (!(flags & INIT_DB_SKIP_REFDB)) + create_reference_database(initial_branch, flags & INIT_DB_QUIET); create_object_directory(); if (get_shared_repository()) { diff --git a/setup.h b/setup.h index b48cf1c43b..cbf538286b 100644 --- a/setup.h +++ b/setup.h @@ -169,8 +169,9 @@ int verify_repository_format(const struct repository_format *format, */ void check_repository_format(struct repository_format *fmt); -#define INIT_DB_QUIET 0x0001 -#define INIT_DB_EXIST_OK 0x0002 +#define INIT_DB_QUIET (1 << 0) +#define INIT_DB_EXIST_OK (1 << 1) +#define INIT_DB_SKIP_REFDB (1 << 2) int init_db(const char *git_dir, const char *real_git_dir, const char *template_dir, int hash_algo, -- cgit v1.3-5-g9baa From 18c9cb7524c13ca88ee334a707f281c2e80d5fdf Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 12 Dec 2023 08:01:07 +0100 Subject: builtin/clone: create the refdb with the correct object format We're currently creating the reference database with a potentially incorrect object format when the remote repository's object format is different from the local default object format. This works just fine for now because the files backend never records the object format anywhere. But this logic will fail with any new reference backend that encodes this information in some form either on-disk or in-memory. The preceding commits have reshuffled code in git-clone(1) so that there is no code path that will access the reference database before we have detected the remote's object format. With these refactorings we can now defer initialization of the reference database until after we have learned the remote's object format and thus initialize it with the correct format from the get-go. These refactorings are required to make git-clone(1) work with the upcoming reftable backend when cloning repositories with the SHA256 object format. This change breaks a test in "t5550-http-fetch-dumb.sh" when cloning an empty repository with `GIT_TEST_DEFAULT_HASH=sha256`. The test expects the resulting hash format of the empty cloned repository to match the default hash, but now we always end up with a sha1 repository. The problem is that for dumb HTTP fetches, we have no easy way to figure out the remote's hash function except for deriving it based on the hash length of refs in `info/refs`. But as the remote repository is empty we cannot rely on this detection mechanism. Before the change in this commit we already initialized the repository with the default hash function and then left it as-is. With this patch we always use the hash function detected via the remote, where we fall back to "sha1" in case we cannot detect it. Neither the old nor the new behaviour are correct as we second-guess the remote hash function in both cases. But given that this is a rather unlikely edge case (we use the dumb HTTP protocol, the remote repository uses SHA256 and the remote repository is empty), let's simply adapt the test to assert the new behaviour. If we want to properly address this edge case in the future we will have to extend the dumb HTTP protocol so that we can properly detect the hash function for empty repositories. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 9 ++++++++- setup.c | 2 +- setup.h | 1 + t/t5550-http-fetch-dumb.sh | 4 ++-- 4 files changed, 12 insertions(+), 4 deletions(-) (limited to 'setup.c') diff --git a/builtin/clone.c b/builtin/clone.c index 06966c5d4c..fd052b8b54 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1097,8 +1097,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } + /* + * Initialize the repository, but skip initializing the reference + * database. We do not yet know about the object format of the + * repository, and reference backends may persist that information into + * their on-disk data structures. + */ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL, - do_not_override_repo_unix_permissions, INIT_DB_QUIET); + do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); if (real_git_dir) { free((char *)git_dir); @@ -1282,6 +1288,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); initialize_repository_version(hash_algo, 1); repo_set_hash_algo(the_repository, hash_algo); + create_reference_database(NULL, 1); /* * Before fetching from the remote, download and install bundle diff --git a/setup.c b/setup.c index d6a1c59b7b..155fe13f70 100644 --- a/setup.c +++ b/setup.c @@ -1897,7 +1897,7 @@ static int is_reinit(void) return ret; } -static void create_reference_database(const char *initial_branch, int quiet) +void create_reference_database(const char *initial_branch, int quiet) { struct strbuf err = STRBUF_INIT; int reinit = is_reinit(); diff --git a/setup.h b/setup.h index cbf538286b..3f0f17c351 100644 --- a/setup.h +++ b/setup.h @@ -178,6 +178,7 @@ int init_db(const char *git_dir, const char *real_git_dir, const char *initial_branch, int init_shared_repository, unsigned int flags); void initialize_repository_version(int hash_algo, int reinit); +void create_reference_database(const char *initial_branch, int quiet); /* * NOTE NOTE NOTE!! diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index e444b30bf6..4c3b32785d 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -66,11 +66,11 @@ test_expect_success 'create empty remote repository' ' setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" ' -test_expect_success 'empty dumb HTTP repository has default hash algorithm' ' +test_expect_success 'empty dumb HTTP repository falls back to SHA1' ' test_when_finished "rm -fr clone-empty" && git clone $HTTPD_URL/dumb/empty.git clone-empty && git -C clone-empty rev-parse --show-object-format >empty-format && - test "$(cat empty-format)" = "$(test_oid algo)" + test "$(cat empty-format)" = sha1 ' setup_askpass_helper -- cgit v1.3-5-g9baa From 173761e21b2978fe7c0f7af7276e8b14511fed23 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:39 +0100 Subject: setup: start tracking ref storage format In order to discern which ref storage format a repository is supposed to use we need to start setting up and/or discovering the format. This needs to happen in two separate code paths. - The first path is when we create a repository via `init_db()`. When we are re-initializing a preexisting repository we need to retain the previously used ref storage format -- if the user asked for a different format then this indicates an error and we error out. Otherwise we either initialize the repository with the format asked for by the user or the default format, which currently is the "files" backend. - The second path is when discovering repositories, where we need to read the config of that repository. There is not yet any way to configure something other than the "files" backend, so we can just blindly set the ref storage format to this backend. Wire up this logic so that we have the ref storage format always readily available when needed. As there is only a single backend and because it is not configurable we cannot yet verify that this tracking works as expected via tests, but tests will be added in subsequent commits. To countermand this ommission now though, raise a BUG() in case the ref storage format is not set up properly in `ref_store_init()`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 5 +++-- builtin/init-db.c | 4 +++- refs.c | 4 ++-- repository.c | 6 ++++++ repository.h | 4 ++++ setup.c | 28 +++++++++++++++++++++++++--- setup.h | 6 +++++- 7 files changed, 48 insertions(+), 9 deletions(-) (limited to 'setup.c') diff --git a/builtin/clone.c b/builtin/clone.c index 343f536cf8..48aeb1b90b 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1107,7 +1107,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * repository, and reference backends may persist that information into * their on-disk data structures. */ - init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL, + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, + REF_STORAGE_FORMAT_UNKNOWN, NULL, do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); if (real_git_dir) { @@ -1292,7 +1293,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); initialize_repository_version(hash_algo, 1); repo_set_hash_algo(the_repository, hash_algo); - create_reference_database(NULL, 1); + create_reference_database(the_repository->ref_storage_format, NULL, 1); /* * Before fetching from the remote, download and install bundle diff --git a/builtin/init-db.c b/builtin/init-db.c index cb727c826f..b6e80feab6 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -11,6 +11,7 @@ #include "object-file.h" #include "parse-options.h" #include "path.h" +#include "refs.h" #include "setup.h" #include "strbuf.h" @@ -236,5 +237,6 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) flags |= INIT_DB_EXIST_OK; return init_db(git_dir, real_git_dir, template_dir, hash_algo, - initial_branch, init_shared_repository, flags); + REF_STORAGE_FORMAT_UNKNOWN, initial_branch, + init_shared_repository, flags); } diff --git a/refs.c b/refs.c index dea3d5c9a0..fdbf5f4cb1 100644 --- a/refs.c +++ b/refs.c @@ -2045,10 +2045,10 @@ static struct ref_store *ref_store_init(struct repository *repo, const char *gitdir, unsigned int flags) { - unsigned int format = REF_STORAGE_FORMAT_FILES; - const struct ref_storage_be *be = find_ref_storage_backend(format); + const struct ref_storage_be *be; struct ref_store *refs; + be = find_ref_storage_backend(repo->ref_storage_format); if (!be) BUG("reference backend is unknown"); diff --git a/repository.c b/repository.c index a7679ceeaa..d7d24d416a 100644 --- a/repository.c +++ b/repository.c @@ -104,6 +104,11 @@ void repo_set_hash_algo(struct repository *repo, int hash_algo) repo->hash_algo = &hash_algos[hash_algo]; } +void repo_set_ref_storage_format(struct repository *repo, unsigned int format) +{ + repo->ref_storage_format = format; +} + /* * Attempt to resolve and set the provided 'gitdir' for repository 'repo'. * Return 0 upon success and a non-zero value upon failure. @@ -184,6 +189,7 @@ int repo_init(struct repository *repo, goto error; repo_set_hash_algo(repo, format.hash_algo); + repo_set_ref_storage_format(repo, format.ref_storage_format); repo->repository_format_worktree_config = format.worktree_config; /* take ownership of format.partial_clone */ diff --git a/repository.h b/repository.h index ea4c488b81..f5269b3730 100644 --- a/repository.h +++ b/repository.h @@ -163,6 +163,9 @@ struct repository { /* Repository's current hash algorithm, as serialized on disk. */ const struct git_hash_algo *hash_algo; + /* Repository's reference storage format, as serialized on disk. */ + unsigned int ref_storage_format; + /* A unique-id for tracing purposes. */ int trace2_repo_id; @@ -202,6 +205,7 @@ void repo_set_gitdir(struct repository *repo, const char *root, const struct set_gitdir_args *extra_args); void repo_set_worktree(struct repository *repo, const char *path); void repo_set_hash_algo(struct repository *repo, int algo); +void repo_set_ref_storage_format(struct repository *repo, unsigned int format); void initialize_the_repository(void); RESULT_MUST_BE_USED int repo_init(struct repository *r, const char *gitdir, const char *worktree); diff --git a/setup.c b/setup.c index bc90bbd033..9c9a167f52 100644 --- a/setup.c +++ b/setup.c @@ -1566,6 +1566,8 @@ const char *setup_git_directory_gently(int *nongit_ok) } if (startup_info->have_repository) { repo_set_hash_algo(the_repository, repo_fmt.hash_algo); + repo_set_ref_storage_format(the_repository, + repo_fmt.ref_storage_format); the_repository->repository_format_worktree_config = repo_fmt.worktree_config; /* take ownership of repo_fmt.partial_clone */ @@ -1659,6 +1661,8 @@ void check_repository_format(struct repository_format *fmt) check_repository_format_gently(get_git_dir(), fmt, NULL); startup_info->have_repository = 1; repo_set_hash_algo(the_repository, fmt->hash_algo); + repo_set_ref_storage_format(the_repository, + fmt->ref_storage_format); the_repository->repository_format_worktree_config = fmt->worktree_config; the_repository->repository_format_partial_clone = @@ -1899,7 +1903,8 @@ static int is_reinit(void) return ret; } -void create_reference_database(const char *initial_branch, int quiet) +void create_reference_database(unsigned int ref_storage_format, + const char *initial_branch, int quiet) { struct strbuf err = STRBUF_INIT; int reinit = is_reinit(); @@ -1919,6 +1924,7 @@ void create_reference_database(const char *initial_branch, int quiet) safe_create_dir(git_path("refs"), 1); adjust_shared_perm(git_path("refs")); + repo_set_ref_storage_format(the_repository, ref_storage_format); if (refs_init_db(&err)) die("failed to set up refs db: %s", err.buf); @@ -2137,8 +2143,22 @@ static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash } } +static void validate_ref_storage_format(struct repository_format *repo_fmt, + unsigned int format) +{ + if (repo_fmt->version >= 0 && + format != REF_STORAGE_FORMAT_UNKNOWN && + format != repo_fmt->ref_storage_format) { + die(_("attempt to reinitialize repository with different reference storage format")); + } else if (format != REF_STORAGE_FORMAT_UNKNOWN) { + repo_fmt->ref_storage_format = format; + } +} + int init_db(const char *git_dir, const char *real_git_dir, - const char *template_dir, int hash, const char *initial_branch, + const char *template_dir, int hash, + unsigned int ref_storage_format, + const char *initial_branch, int init_shared_repository, unsigned int flags) { int reinit; @@ -2181,13 +2201,15 @@ int init_db(const char *git_dir, const char *real_git_dir, check_repository_format(&repo_fmt); validate_hash_algorithm(&repo_fmt, hash); + validate_ref_storage_format(&repo_fmt, ref_storage_format); reinit = create_default_files(template_dir, original_git_dir, &repo_fmt, prev_bare_repository, init_shared_repository); if (!(flags & INIT_DB_SKIP_REFDB)) - create_reference_database(initial_branch, flags & INIT_DB_QUIET); + create_reference_database(repo_fmt.ref_storage_format, + initial_branch, flags & INIT_DB_QUIET); create_object_directory(); if (get_shared_repository()) { diff --git a/setup.h b/setup.h index 3f0f17c351..3d3eda7967 100644 --- a/setup.h +++ b/setup.h @@ -115,6 +115,7 @@ struct repository_format { int worktree_config; int is_bare; int hash_algo; + unsigned int ref_storage_format; int sparse_index; char *work_tree; struct string_list unknown_extensions; @@ -131,6 +132,7 @@ struct repository_format { .version = -1, \ .is_bare = -1, \ .hash_algo = GIT_HASH_SHA1, \ + .ref_storage_format = REF_STORAGE_FORMAT_FILES, \ .unknown_extensions = STRING_LIST_INIT_DUP, \ .v1_only_extensions = STRING_LIST_INIT_DUP, \ } @@ -175,10 +177,12 @@ void check_repository_format(struct repository_format *fmt); int init_db(const char *git_dir, const char *real_git_dir, const char *template_dir, int hash_algo, + unsigned int ref_storage_format, const char *initial_branch, int init_shared_repository, unsigned int flags); void initialize_repository_version(int hash_algo, int reinit); -void create_reference_database(const char *initial_branch, int quiet); +void create_reference_database(unsigned int ref_storage_format, + const char *initial_branch, int quiet); /* * NOTE NOTE NOTE!! -- cgit v1.3-5-g9baa From 58be32fff9d191ef8312a2c2c2f9deaf5d221c69 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:43 +0100 Subject: setup: set repository's formats on init The proper hash algorithm and ref storage format that will be used for a newly initialized repository will be figured out in `init_db()` via `validate_hash_algorithm()` and `validate_ref_storage_format()`. Until now though, we never set up the hash algorithm or ref storage format of `the_repository` accordingly. There are only two callsites of `init_db()`, one in git-init(1) and one in git-clone(1). The former function doesn't care for the formats to be set up properly because it never access the repository after calling the function in the first place. For git-clone(1) it's a different story though, as we call `init_db()` before listing remote refs. While we do indeed have the wrong hash function in `the_repository` when `init_db()` sets up a non-default object format for the repository, it never mattered because we adjust the hash after learning about the remote's hash function via the listed refs. So the current state is correct for the hash algo, but it's not for the ref storage format because git-clone(1) wouldn't know to set it up properly. But instead of adjusting only the `ref_storage_format`, set both the hash algo and the ref storage format so that `the_repository` is in the correct state when `init_db()` exits. This is fine as we will adjust the hash later on anyway and makes it easier to reason about the end state of `the_repository`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- setup.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'setup.c') diff --git a/setup.c b/setup.c index 9c9a167f52..49570e6b3a 100644 --- a/setup.c +++ b/setup.c @@ -2207,6 +2207,13 @@ int init_db(const char *git_dir, const char *real_git_dir, &repo_fmt, prev_bare_repository, init_shared_repository); + /* + * Now that we have set up both the hash algorithm and the ref storage + * format we can update the repository's settings accordingly. + */ + repo_set_hash_algo(the_repository, repo_fmt.hash_algo); + repo_set_ref_storage_format(the_repository, repo_fmt.ref_storage_format); + if (!(flags & INIT_DB_SKIP_REFDB)) create_reference_database(repo_fmt.ref_storage_format, initial_branch, flags & INIT_DB_QUIET); -- cgit v1.3-5-g9baa From d7497a42b05bb810afeb6acc8c9447b77b1f075d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:47 +0100 Subject: 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]: Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/config/extensions.txt | 11 +++++++++++ Documentation/ref-storage-format.txt | 1 + Documentation/technical/repository-version.txt | 5 +++++ builtin/clone.c | 2 +- setup.c | 24 +++++++++++++++++++++--- setup.h | 4 +++- t/t0001-init.sh | 26 ++++++++++++++++++++++++++ t/test-lib.sh | 2 +- 8 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 Documentation/ref-storage-format.txt (limited to 'setup.c') diff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt index bccaec7a96..66db0e15da 100644 --- a/Documentation/config/extensions.txt +++ b/Documentation/config/extensions.txt @@ -7,6 +7,17 @@ Note that this setting should only be set by linkgit:git-init[1] or linkgit:git-clone[1]. Trying to change it after initialization will not work and will produce hard-to-diagnose issues. +extensions.refStorage:: + Specify the ref storage format to use. The acceptable values are: ++ +include::../ref-storage-format.txt[] ++ +It is an error to specify this key unless `core.repositoryFormatVersion` is 1. ++ +Note that this setting should only be set by linkgit:git-init[1] or +linkgit:git-clone[1]. Trying to change it after initialization will not +work and will produce hard-to-diagnose issues. + extensions.worktreeConfig:: If enabled, then worktrees will load config settings from the `$GIT_DIR/config.worktree` file in addition to the diff --git a/Documentation/ref-storage-format.txt b/Documentation/ref-storage-format.txt new file mode 100644 index 0000000000..1a65cac468 --- /dev/null +++ b/Documentation/ref-storage-format.txt @@ -0,0 +1 @@ +* `files` for loose files with packed-refs. This is the default. diff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt index 045a76756f..27be3741e6 100644 --- a/Documentation/technical/repository-version.txt +++ b/Documentation/technical/repository-version.txt @@ -100,3 +100,8 @@ If set, by default "git config" reads from both "config" and multiple working directory mode, "config" file is shared while "config.worktree" is per-working directory (i.e., it's in GIT_COMMON_DIR/worktrees//config.worktree) + +==== `refStorage` + +Specifies the file format for the ref database. The only valid value +is `files` (loose references with a packed-refs file). diff --git a/builtin/clone.c b/builtin/clone.c index 48aeb1b90b..0fb3816d0c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1291,7 +1291,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * ours to the same thing. */ hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); - initialize_repository_version(hash_algo, 1); + 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); diff --git a/setup.c b/setup.c index 49570e6b3a..fb1413cabd 100644 --- a/setup.c +++ b/setup.c @@ -592,6 +592,17 @@ static enum extension_result handle_extension(const char *var, "extensions.objectformat", value); data->hash_algo = format; return EXTENSION_OK; + } else if (!strcmp(ext, "refstorage")) { + unsigned int format; + + if (!value) + return config_error_nonbool(var); + format = ref_storage_format_by_name(value); + if (format == REF_STORAGE_FORMAT_UNKNOWN) + return error(_("invalid value for '%s': '%s'"), + "extensions.refstorage", value); + data->ref_storage_format = format; + return EXTENSION_OK; } return EXTENSION_UNKNOWN; } @@ -1871,12 +1882,15 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } -void initialize_repository_version(int hash_algo, int reinit) +void initialize_repository_version(int hash_algo, + unsigned int ref_storage_format, + int reinit) { char repo_version_string[10]; int repo_version = GIT_REPO_VERSION; - if (hash_algo != GIT_HASH_SHA1) + if (hash_algo != GIT_HASH_SHA1 || + ref_storage_format != REF_STORAGE_FORMAT_FILES) repo_version = GIT_REPO_VERSION_READ; /* This forces creation of new config file */ @@ -1889,6 +1903,10 @@ void initialize_repository_version(int hash_algo, int reinit) hash_algos[hash_algo].name); else if (reinit) git_config_set_gently("extensions.objectformat", NULL); + + if (ref_storage_format != REF_STORAGE_FORMAT_FILES) + git_config_set("extensions.refstorage", + ref_storage_format_to_name(ref_storage_format)); } static int is_reinit(void) @@ -2030,7 +2048,7 @@ static int create_default_files(const char *template_path, adjust_shared_perm(get_git_dir()); } - initialize_repository_version(fmt->hash_algo, 0); + initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0); /* Check filemode trustability */ path = git_path_buf(&buf, "config"); diff --git a/setup.h b/setup.h index 3d3eda7967..3599aec93c 100644 --- a/setup.h +++ b/setup.h @@ -180,7 +180,9 @@ int init_db(const char *git_dir, const char *real_git_dir, unsigned int ref_storage_format, const char *initial_branch, int init_shared_repository, unsigned int flags); -void initialize_repository_version(int hash_algo, int reinit); +void initialize_repository_version(int hash_algo, + unsigned int ref_storage_format, + int reinit); void create_reference_database(unsigned int ref_storage_format, const char *initial_branch, int quiet); diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 2b78e3be47..38b3e4c39e 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -532,6 +532,32 @@ test_expect_success 'init rejects attempts to initialize with different hash' ' test_must_fail git -C sha256 init --object-format=sha1 ' +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage is not allowed with repo version 0' ' + test_when_finished "rm -rf refstorage" && + git init refstorage && + git -C refstorage config extensions.refStorage files && + test_must_fail git -C refstorage rev-parse 2>err && + grep "repo version is 0, but v1-only extension found" err +' + +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with files backend' ' + test_when_finished "rm -rf refstorage" && + git init refstorage && + git -C refstorage config core.repositoryformatversion 1 && + git -C refstorage config extensions.refStorage files && + test_commit -C refstorage A && + git -C refstorage rev-parse --verify HEAD +' + +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown backend' ' + test_when_finished "rm -rf refstorage" && + git init refstorage && + git -C refstorage config core.repositoryformatversion 1 && + git -C refstorage config extensions.refStorage garbage && + test_must_fail git -C refstorage rev-parse 2>err && + grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err +' + test_expect_success MINGW 'core.hidedotfiles = false' ' git config --global core.hidedotfiles false && rm -rf newdir && diff --git a/t/test-lib.sh b/t/test-lib.sh index dc03f06b8e..4685cc3d48 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1937,7 +1937,7 @@ test_lazy_prereq SHA1 ' ' test_lazy_prereq DEFAULT_REPO_FORMAT ' - test_have_prereq SHA1 + test_have_prereq SHA1,REFFILES ' # Ensure that no test accidentally triggers a Git command -- cgit v1.3-5-g9baa From aa19619a9835a9558630c25a5b8f361343af75ce Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:52 +0100 Subject: setup: introduce GIT_DEFAULT_REF_FORMAT envvar Introduce a new GIT_DEFAULT_REF_FORMAT environment variable that lets users control the default ref format used by both git-init(1) and git-clone(1). This is modeled after GIT_DEFAULT_OBJECT_FORMAT, which does the same thing for the repository's object format. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/git.txt | 5 +++++ setup.c | 7 +++++++ t/t0001-init.sh | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+) (limited to 'setup.c') diff --git a/Documentation/git.txt b/Documentation/git.txt index 4698d7a42b..0ab19eef3b 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -556,6 +556,11 @@ double-quotes and respecting backslash escapes. E.g., the value is always used. The default is "sha1". See `--object-format` in linkgit:git-init[1]. +`GIT_DEFAULT_REF_FORMAT`:: + If this variable is set, the default reference backend format for new + repositories will be set to this value. The default is "files". + See `--ref-format` in linkgit:git-init[1]. + Git Commits ~~~~~~~~~~~ `GIT_AUTHOR_NAME`:: diff --git a/setup.c b/setup.c index fb1413cabd..1ab1a66bcb 100644 --- a/setup.c +++ b/setup.c @@ -2164,12 +2164,19 @@ static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash static void validate_ref_storage_format(struct repository_format *repo_fmt, unsigned int format) { + const char *name = getenv("GIT_DEFAULT_REF_FORMAT"); + if (repo_fmt->version >= 0 && format != REF_STORAGE_FORMAT_UNKNOWN && format != repo_fmt->ref_storage_format) { die(_("attempt to reinitialize repository with different reference storage format")); } else if (format != REF_STORAGE_FORMAT_UNKNOWN) { repo_fmt->ref_storage_format = format; + } else if (name) { + format = ref_storage_format_by_name(name); + if (format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format '%s'"), name); + repo_fmt->ref_storage_format = format; } } diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 38b3e4c39e..30ce752cc1 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -558,6 +558,24 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown back grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err ' +test_expect_success DEFAULT_REPO_FORMAT 'init with GIT_DEFAULT_REF_FORMAT=files' ' + test_when_finished "rm -rf refformat" && + GIT_DEFAULT_REF_FORMAT=files git init refformat && + echo 0 >expect && + git -C refformat config core.repositoryformatversion >actual && + test_cmp expect actual && + test_must_fail git -C refformat config extensions.refstorage +' + +test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' ' + test_when_finished "rm -rf refformat" && + cat >expect <<-EOF && + fatal: unknown ref storage format ${SQ}garbage${SQ} + EOF + test_must_fail env GIT_DEFAULT_REF_FORMAT=garbage git init refformat 2>err && + test_cmp expect err +' + test_expect_success MINGW 'core.hidedotfiles = false' ' git config --global core.hidedotfiles false && rm -rf newdir && -- cgit v1.3-5-g9baa