From bb0372c9799915fc3e6c2cf8e17815ce22622acd Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:26 +0100 Subject: t: introduce DEFAULT_REPO_FORMAT prereq A limited number of tests require repositories to have the default repository format or otherwise they would fail to run, e.g. because they fail to detect the correct hash function. While the hash function is the only extension right now that creates problems like this, we are about to add a second extension for the ref format. Introduce a new DEFAULT_REPO_FORMAT prereq that can easily be amended whenever we add new format extensions. Next to making any such changes easier on us, the prerequisite's name should also help to clarify the intent better. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t3200-branch.sh | 2 +- t/test-lib.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 6a316f081e..de7d3014e4 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -519,7 +519,7 @@ EOF mv .git/config .git/config-saved -test_expect_success SHA1 'git branch -m q q2 without config should succeed' ' +test_expect_success DEFAULT_REPO_FORMAT 'git branch -m q q2 without config should succeed' ' git branch -m q q2 && git branch -m q2 q ' diff --git a/t/test-lib.sh b/t/test-lib.sh index 876b99562a..dc03f06b8e 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1936,6 +1936,10 @@ test_lazy_prereq SHA1 ' esac ' +test_lazy_prereq DEFAULT_REPO_FORMAT ' + test_have_prereq SHA1 +' + # Ensure that no test accidentally triggers a Git command # that runs the actual maintenance scheduler, affecting a user's # system permanently. -- cgit v1.2.3 From 465a22b338a05a5ffeeffc6bd9174ef5d9a090a0 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:30 +0100 Subject: worktree: skip reading HEAD when repairing worktrees When calling `git init --separate-git-dir=` on a preexisting repository, we move the Git directory of that repository to the new path specified by the user. If there are worktrees present in the repository, we need to repair the worktrees so that their gitlinks point to the new location of the repository. This repair logic will load repositories via `get_worktrees()`, which will enumerate up and initialize all worktrees. Part of initialization is logic that we resolve their respective worktree HEADs, even though that information may not actually be needed in the end by all callers. Although not a problem presently with the file-based reference backend, it will become a problem with the upcoming reftable backend. In the context of git-init(1) we do not have a fully-initialized repository set up via `setup_git_directory()` or friends. Consequently, we do not know about the repository format when `repair_worktrees()` is called, and properly setting up all parts of the repositroy in `init_db()` before we try to repair worktrees is not an easy task. With the introduction of the reftable backend, we would ultimately try to look up the worktree HEADs before we have figured out the reference format, which does not work. We do not require the worktree HEADs at all to repair worktrees. So let's fix this issue by skipping over the step that reads them. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- worktree.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/worktree.c b/worktree.c index a56a6c2a3d..cc34a3419b 100644 --- a/worktree.c +++ b/worktree.c @@ -51,7 +51,7 @@ static void add_head_info(struct worktree *wt) /** * get the main worktree */ -static struct worktree *get_main_worktree(void) +static struct worktree *get_main_worktree(int skip_reading_head) { struct worktree *worktree = NULL; struct strbuf worktree_path = STRBUF_INIT; @@ -70,11 +70,13 @@ static struct worktree *get_main_worktree(void) */ worktree->is_bare = (is_bare_repository_cfg == 1) || is_bare_repository(); - add_head_info(worktree); + if (!skip_reading_head) + add_head_info(worktree); return worktree; } -static struct worktree *get_linked_worktree(const char *id) +static struct worktree *get_linked_worktree(const char *id, + int skip_reading_head) { struct worktree *worktree = NULL; struct strbuf path = STRBUF_INIT; @@ -93,7 +95,8 @@ static struct worktree *get_linked_worktree(const char *id) CALLOC_ARRAY(worktree, 1); worktree->path = strbuf_detach(&worktree_path, NULL); worktree->id = xstrdup(id); - add_head_info(worktree); + if (!skip_reading_head) + add_head_info(worktree); done: strbuf_release(&path); @@ -118,7 +121,14 @@ static void mark_current_worktree(struct worktree **worktrees) free(git_dir); } -struct worktree **get_worktrees(void) +/* + * NEEDSWORK: This function exists so that we can look up metadata of a + * worktree without trying to access any of its internals like the refdb. It + * would be preferable to instead have a corruption-tolerant function for + * retrieving worktree metadata that could be used when the worktree is known + * to not be in a healthy state, e.g. when creating or repairing it. + */ +static struct worktree **get_worktrees_internal(int skip_reading_head) { struct worktree **list = NULL; struct strbuf path = STRBUF_INIT; @@ -128,7 +138,7 @@ struct worktree **get_worktrees(void) ALLOC_ARRAY(list, alloc); - list[counter++] = get_main_worktree(); + list[counter++] = get_main_worktree(skip_reading_head); strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); dir = opendir(path.buf); @@ -137,7 +147,7 @@ struct worktree **get_worktrees(void) while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) { struct worktree *linked = NULL; - if ((linked = get_linked_worktree(d->d_name))) { + if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) { ALLOC_GROW(list, counter + 1, alloc); list[counter++] = linked; } @@ -151,6 +161,11 @@ struct worktree **get_worktrees(void) return list; } +struct worktree **get_worktrees(void) +{ + return get_worktrees_internal(0); +} + const char *get_worktree_git_dir(const struct worktree *wt) { if (!wt) @@ -591,7 +606,7 @@ static void repair_noop(int iserr UNUSED, void repair_worktrees(worktree_repair_fn fn, void *cb_data) { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees_internal(1); struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ if (!fn) -- cgit v1.2.3 From 0fcc285c5eaab904ea9a8ade2696fe61355cf587 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:34 +0100 Subject: refs: refactor logic to look up storage backends In order to look up ref storage backends, we're currently using a linked list of backends, where each backend is expected to set up its `next` pointer to the next ref storage backend. This is kind of a weird setup as backends need to be aware of other backends without much of a reason. Refactor the code so that the array of backends is centrally defined in "refs.c", where each backend is now identified by an integer constant. Expose functions to translate from those integer constants to the name and vice versa, which will be required by subsequent patches. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- refs.c | 34 +++++++++++++++++++++++++--------- refs.h | 3 +++ refs/debug.c | 1 - refs/files-backend.c | 1 - refs/packed-backend.c | 1 - refs/refs-internal.h | 1 - repository.h | 3 +++ 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/refs.c b/refs.c index 16bfa21df7..dea3d5c9a0 100644 --- a/refs.c +++ b/refs.c @@ -33,17 +33,33 @@ /* * List of all available backends */ -static struct ref_storage_be *refs_backends = &refs_be_files; +static const struct ref_storage_be *refs_backends[] = { + [REF_STORAGE_FORMAT_FILES] = &refs_be_files, +}; -static struct ref_storage_be *find_ref_storage_backend(const char *name) +static const struct ref_storage_be *find_ref_storage_backend(unsigned int ref_storage_format) { - struct ref_storage_be *be; - for (be = refs_backends; be; be = be->next) - if (!strcmp(be->name, name)) - return be; + if (ref_storage_format < ARRAY_SIZE(refs_backends)) + return refs_backends[ref_storage_format]; return NULL; } +unsigned int ref_storage_format_by_name(const char *name) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(refs_backends); i++) + if (refs_backends[i] && !strcmp(refs_backends[i]->name, name)) + return i; + return REF_STORAGE_FORMAT_UNKNOWN; +} + +const char *ref_storage_format_to_name(unsigned int ref_storage_format) +{ + const struct ref_storage_be *be = find_ref_storage_backend(ref_storage_format); + if (!be) + return "unknown"; + return be->name; +} + /* * How to handle various characters in refnames: * 0: An acceptable character for refs @@ -2029,12 +2045,12 @@ static struct ref_store *ref_store_init(struct repository *repo, const char *gitdir, unsigned int flags) { - const char *be_name = "files"; - struct ref_storage_be *be = find_ref_storage_backend(be_name); + unsigned int format = REF_STORAGE_FORMAT_FILES; + const struct ref_storage_be *be = find_ref_storage_backend(format); struct ref_store *refs; if (!be) - BUG("reference backend %s is unknown", be_name); + BUG("reference backend is unknown"); refs = be->init(repo, gitdir, flags); return refs; diff --git a/refs.h b/refs.h index 23211a5ea1..916b874ae3 100644 --- a/refs.h +++ b/refs.h @@ -11,6 +11,9 @@ struct string_list; struct string_list_item; struct worktree; +unsigned int ref_storage_format_by_name(const char *name); +const char *ref_storage_format_to_name(unsigned int ref_storage_format); + /* * Resolve a reference, recursively following symbolic refererences. * diff --git a/refs/debug.c b/refs/debug.c index 83b7a0ba65..b9775f2c37 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -426,7 +426,6 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname, } struct ref_storage_be refs_be_debug = { - .next = NULL, .name = "debug", .init = NULL, .init_db = debug_init_db, diff --git a/refs/files-backend.c b/refs/files-backend.c index ad8b1d143f..43fd0ac760 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -3241,7 +3241,6 @@ static int files_init_db(struct ref_store *ref_store, struct strbuf *err UNUSED) } struct ref_storage_be refs_be_files = { - .next = NULL, .name = "files", .init = files_ref_store_create, .init_db = files_init_db, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index b9fa097a29..8d1090e284 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1705,7 +1705,6 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s } struct ref_storage_be refs_be_packed = { - .next = NULL, .name = "packed", .init = packed_ref_store_create, .init_db = packed_init_db, diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 4af83bf9a5..8e9f04cc67 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -663,7 +663,6 @@ typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refnam struct strbuf *referent); struct ref_storage_be { - struct ref_storage_be *next; const char *name; ref_store_init_fn *init; ref_init_db_fn *init_db; diff --git a/repository.h b/repository.h index 5f18486f64..ea4c488b81 100644 --- a/repository.h +++ b/repository.h @@ -24,6 +24,9 @@ enum fetch_negotiation_setting { FETCH_NEGOTIATION_NOOP, }; +#define REF_STORAGE_FORMAT_UNKNOWN 0 +#define REF_STORAGE_FORMAT_FILES 1 + struct repo_settings { int initialized; -- cgit v1.2.3 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(-) 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.2.3 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(+) 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.2.3 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 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.2.3 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(+) 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.2.3 From 58aaf59133b4167aefaab6083e80deb369894f79 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:26:56 +0100 Subject: t: introduce GIT_TEST_DEFAULT_REF_FORMAT envvar Introduce a new GIT_TEST_DEFAULT_REF_FORMAT environment variable that lets developers run the test suite with a different default ref format without impacting the ref format used by non-test Git invocations. This is modeled after GIT_TEST_DEFAULT_OBJECT_FORMAT, which does the same thing for the repository's object format. Adapt the setup of the `REFFILES` test prerequisite to be conditionally set based on the default ref format. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/README | 3 +++ t/test-lib-functions.sh | 5 +++++ t/test-lib.sh | 11 ++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/t/README b/t/README index 36463d0742..621d3b8c09 100644 --- a/t/README +++ b/t/README @@ -479,6 +479,9 @@ GIT_TEST_DEFAULT_HASH= specifies which hash algorithm to use in the test scripts. Recognized values for are "sha1" and "sha256". +GIT_TEST_DEFAULT_REF_FORMAT= specifies which ref storage format +to use in the test scripts. Recognized values for are "files". + GIT_TEST_NO_WRITE_REV_INDEX=, when true disables the 'pack.writeReverseIndex' setting. diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 03292602fb..61871b2a3b 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1659,6 +1659,11 @@ test_detect_hash () { test_hash_algo="${GIT_TEST_DEFAULT_HASH:-sha1}" } +# Detect the hash algorithm in use. +test_detect_ref_format () { + echo "${GIT_TEST_DEFAULT_REF_FORMAT:-files}" +} + # Load common hash metadata and common placeholder object IDs for use with # test_oid. test_oid_init () { diff --git a/t/test-lib.sh b/t/test-lib.sh index 4685cc3d48..fc93aa57e6 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -542,6 +542,8 @@ export EDITOR GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}" export GIT_DEFAULT_HASH +GIT_DEFAULT_REF_FORMAT="${GIT_TEST_DEFAULT_REF_FORMAT:-files}" +export GIT_DEFAULT_REF_FORMAT GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}" export GIT_TEST_MERGE_ALGORITHM @@ -1745,7 +1747,14 @@ parisc* | hppa*) ;; esac -test_set_prereq REFFILES +case "$GIT_DEFAULT_REF_FORMAT" in +files) + test_set_prereq REFFILES;; +*) + echo 2>&1 "error: unknown ref format $GIT_DEFAULT_REF_FORMAT" + exit 1 + ;; +esac ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_CURL" && test_set_prereq LIBCURL -- cgit v1.2.3 From 3c4a5318afb4978a037c83ccd9bbeb870b3f0543 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:27:00 +0100 Subject: builtin/rev-parse: introduce `--show-ref-format` flag Introduce a new `--show-ref-format` to git-rev-parse(1) that causes it to print the ref format used by a repository. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 3 +++ builtin/rev-parse.c | 4 ++++ t/t1500-rev-parse.sh | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 912fab9f5e..546faf9017 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -307,6 +307,9 @@ The following options are unaffected by `--path-format`: input, multiple algorithms may be printed, space-separated. If not specified, the default is "storage". +--show-ref-format:: + Show the reference storage format used for the repository. + Other Options ~~~~~~~~~~~~~ diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 917f122440..d08987646a 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -1062,6 +1062,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) puts(the_hash_algo->name); continue; } + if (!strcmp(arg, "--show-ref-format")) { + puts(ref_storage_format_to_name(the_repository->ref_storage_format)); + continue; + } if (!strcmp(arg, "--end-of-options")) { seen_end_of_options = 1; if (filter & (DO_FLAGS | DO_REVS)) diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 3f9e7f62e4..a669e592f1 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -208,6 +208,23 @@ test_expect_success 'rev-parse --show-object-format in repo' ' grep "unknown mode for --show-object-format: squeamish-ossifrage" err ' +test_expect_success 'rev-parse --show-ref-format' ' + test_detect_ref_format >expect && + git rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse --show-ref-format with invalid storage' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config extensions.refstorage broken && + test_must_fail git rev-parse --show-ref-format 2>err && + grep "error: invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}broken${SQ}" err + ) +' + test_expect_success '--show-toplevel from subdir of working tree' ' pwd >expect && git -C sub/dir rev-parse --show-toplevel >actual && -- cgit v1.2.3 From 48fa45f5fb80de821a380a7ff81ab2c568d69e35 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:27:04 +0100 Subject: builtin/init: introduce `--ref-format=` value flag Introduce a new `--ref-format` value flag for git-init(1) that allows the user to specify the ref format that is to be used for a newly initialized repository. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/git-init.txt | 7 +++++++ builtin/init-db.c | 13 ++++++++++++- t/t0001-init.sh | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 6f0d2973bf..e8dc645bb5 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git init' [-q | --quiet] [--bare] [--template=] [--separate-git-dir ] [--object-format=] + [--ref-format=] [-b | --initial-branch=] [--shared[=]] [] @@ -57,6 +58,12 @@ values are 'sha1' and (if enabled) 'sha256'. 'sha1' is the default. + include::object-format-disclaimer.txt[] +--ref-format=:: + +Specify the given ref storage format for the repository. The valid values are: ++ +include::ref-storage-format.txt[] + --template=:: Specify the directory from which templates will be used. (See the "TEMPLATE diff --git a/builtin/init-db.c b/builtin/init-db.c index b6e80feab6..a4f81e2af5 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -58,6 +58,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset) static const char *const init_db_usage[] = { N_("git init [-q | --quiet] [--bare] [--template=]\n" " [--separate-git-dir ] [--object-format=]\n" + " [--ref-format=]\n" " [-b | --initial-branch=]\n" " [--shared[=]] []"), NULL @@ -77,8 +78,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *template_dir = NULL; unsigned int flags = 0; const char *object_format = NULL; + const char *ref_format = NULL; const char *initial_branch = NULL; int hash_algo = GIT_HASH_UNKNOWN; + unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; int init_shared_repository = -1; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), @@ -96,6 +99,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) N_("override the name of the initial branch")), OPT_STRING(0, "object-format", &object_format, N_("hash"), N_("specify the hash algorithm to use")), + OPT_STRING(0, "ref-format", &ref_format, N_("format"), + N_("specify the reference format to use")), OPT_END() }; @@ -159,6 +164,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) die(_("unknown hash algorithm '%s'"), object_format); } + if (ref_format) { + ref_storage_format = ref_storage_format_by_name(ref_format); + if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format '%s'"), ref_format); + } + if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -237,6 +248,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, - REF_STORAGE_FORMAT_UNKNOWN, initial_branch, + ref_storage_format, initial_branch, init_shared_repository, flags); } diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 30ce752cc1..b131d665db 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -576,6 +576,32 @@ test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' ' test_cmp expect err ' +test_expect_success 'init with --ref-format=files' ' + test_when_finished "rm -rf refformat" && + git init --ref-format=files refformat && + echo files >expect && + git -C refformat rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 're-init with same format' ' + test_when_finished "rm -rf refformat" && + git init --ref-format=files refformat && + git init --ref-format=files refformat && + echo files >expect && + git -C refformat rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'init with --ref-format=garbage' ' + test_when_finished "rm -rf refformat" && + cat >expect <<-EOF && + fatal: unknown ref storage format ${SQ}garbage${SQ} + EOF + test_must_fail git init --ref-format=garbage 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.2.3 From 5ed860f51b7fa1ca1087c1b0502b199a86db964e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:27:09 +0100 Subject: builtin/clone: introduce `--ref-format=` value flag Introduce a new `--ref-format` value flag for git-clone(1) that allows the user to specify the ref format that is to be used for a newly initialized repository. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/git-clone.txt | 6 ++++++ builtin/clone.c | 12 +++++++++++- t/t5601-clone.sh | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index c37c4a37f7..6e43eb9c20 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -311,6 +311,12 @@ or `--mirror` is given) The result is Git repository can be separated from working tree. +--ref-format=:: --jobs :: The number of submodules fetched at the same time. diff --git a/builtin/clone.c b/builtin/clone.c index 0fb3816d0c..f1635e0e8c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -72,6 +72,7 @@ static char *remote_name = NULL; static char *option_branch = NULL; static struct string_list option_not = STRING_LIST_INIT_NODUP; static const char *real_git_dir; +static const char *ref_format; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; @@ -157,6 +158,8 @@ static struct option builtin_clone_options[] = { N_("any cloned submodules will be shallow")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), + OPT_STRING(0, "ref-format", &ref_format, N_("format"), + N_("specify the reference format to use")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), N_("set config inside the new repository")), OPT_STRING_LIST(0, "server-option", &server_options, @@ -932,6 +935,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int submodule_progress; int filter_submodules = 0; int hash_algo; + unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; const int do_not_override_repo_unix_permissions = -1; struct transport_ls_refs_options transport_ls_refs_options = @@ -957,6 +961,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_single_branch == -1) option_single_branch = deepen ? 1 : 0; + if (ref_format) { + ref_storage_format = ref_storage_format_by_name(ref_format); + if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format '%s'"), ref_format); + } + if (option_mirror) option_bare = 1; @@ -1108,7 +1118,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * their on-disk data structures. */ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, - REF_STORAGE_FORMAT_UNKNOWN, NULL, + ref_storage_format, NULL, do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); if (real_git_dir) { diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 47eae641f0..fb1b9c686d 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -157,6 +157,23 @@ test_expect_success 'clone --mirror does not repeat tags' ' ' +test_expect_success 'clone with files ref format' ' + test_when_finished "rm -rf ref-storage" && + git clone --ref-format=files --mirror src ref-storage && + echo files >expect && + git -C ref-storage rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'clone with garbage ref format' ' + cat >expect <<-EOF && + fatal: unknown ref storage format ${SQ}garbage${SQ} + EOF + test_must_fail git clone --ref-format=garbage --mirror src ref-storage 2>err && + test_cmp expect err && + test_path_is_missing ref-storage +' + test_expect_success 'clone to destination with trailing /' ' git clone src target-1/ && -- cgit v1.2.3 From 1b2234079b24da99dd78e4ce4bfe338a2a841aed Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 29 Dec 2023 08:27:13 +0100 Subject: t9500: write "extensions.refstorage" into config In t9500 we're writing a custom configuration that sets up gitweb. This requires us to manually ensure that the repository format is configured as required, including both the repository format version and extensions. With the introduction of the "extensions.refStorage" extension we need to update the test to also write this new one. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t9500-gitweb-standalone-no-errors.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 0333065d4d..7679780fb8 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -627,6 +627,7 @@ test_expect_success \ test_expect_success 'setup' ' version=$(git config core.repositoryformatversion) && algo=$(test_might_fail git config extensions.objectformat) && + refstorage=$(test_might_fail git config extensions.refstorage) && cat >.git/config <<-\EOF && # testing noval and alternate separator [gitweb] @@ -637,6 +638,10 @@ test_expect_success 'setup' ' if test -n "$algo" then git config extensions.objectformat "$algo" + fi && + if test -n "$refstorage" + then + git config extensions.refstorage "$refstorage" fi ' -- cgit v1.2.3