{"thread":{"id":"65666","subject":"[PATCH 0/8] setup: centralize object database creation","startedAt":"2026-05-21T07:42:39Z","lastAt":"2026-06-08T12:06:35Z","messageCount":28,"participants":["Patrick Steinhardt","Junio C Hamano","Kristoffer Haugsbakk","Karthik Nayak"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"543775","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":null,"subject":"[PATCH 0/8] setup: centralize object database creation","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:27Z","receivedAt":"2026-05-21T07:42:39Z","isPatch":true,"body":"Hi,\n\nthis small patch series refactors the logic for how we discover and\nconfigure repositories. Most importantly, this involves the following\ntwo steps:\n\n  1. We unify the logic to apply the repository format, which is\n     currently open-coded across multiple sites. These sites have\n     already diverged, where some repository extensions are not\n     consistently applied.\n\n  2. We then centralize creation of the object database to happen at the\n     same time we apply the repository format.\n\nThe end result is that we apply the repository format exactly once, and\nthat's also the point in time where we can finalize the setup of the\nrepo's data structures as we know about all details of the repo at that\ntime. Ultimately, this makes it trivial to introduce the \"objectStorage\"\nextension, even though that's not part of this patch series.\n\nThe series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)\nwith ps/setup-wo-the-repository at df69f40c34 (setup: stop using\n`the_repository` in `init_db()`, 2026-05-19) merged into it.\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (8):\n      t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY\n      setup: drop `setup_git_env()`\n      setup: deduplicate logic to apply repository format\n      repository: stop initializing the object database in `repo_set_gitdir()`\n      setup: stop creating the object database in `setup_git_env()`\n      setup: stop initializing object database without repository\n      repository: stop reading loose object map twice on repo init\n      setup: construct object database in `apply_repository_format()`\n\n commit-graph.c  |   4 +-\n environment.h   |   8 +---\n refs.c          |   3 +-\n repository.c    |  40 +++++------------\n repository.h    |   3 --\n setup.c         | 130 +++++++++++++++++++++++++++++++-------------------------\n setup.h         |  19 +++++++++\n t/t0001-init.sh |  10 +++++\n 8 files changed, 117 insertions(+), 100 deletions(-)\n\n\n---\nbase-commit: 3398daa441965513c48744305d33bd36404547d6\nchange-id: 20260519-b4-pks-setup-centralize-odb-creation-3479c53fb11d\n\n"},{"id":"543776","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-1-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:28Z","receivedAt":"2026-05-21T07:42:40Z","isPatch":true,"body":"In subsequent commits we'll rework how we set up the repository. This is\na somewhat intricate and thus fragile sequence, there's many things that\ncan go subtly wrong, and there are lots of interesting interactions that\none can discover.\n\nOne such discovered edge case was the interaction between git-init(1)\nand the \"GIT_OBJECT_DIRECTORY\" enviroment variable. When set, the\nbehaviour is that the object directory should be created at the path\nthat the variable points to. This behaviour is documented as such in\nits man page:\n\n  If the object storage directory is specified via the\n  GIT_OBJECT_DIRECTORY environment variable then the sha1 directories\n  are created underneath; otherwise, the default $GIT_DIR/objects\n  directory is used.\n\nCuriously enough though we don't seem to have any tests that exercise\nthis directly, and thus a subsequent commit inadvertently broke this\nexpectation.\n\nPlug this test gap.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t0001-init.sh | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex e4d32bb4d2..e89feca544 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -980,4 +980,14 @@ test_expect_success 're-init reads matching includeIf.onbranch' '\n \ttest_cmp expect err\n '\n \n+test_expect_success 'init honors GIT_OBJECT_DIRECTORY' '\n+\ttest_when_finished \"rm -rf init-objdir custom-odb\" &&\n+\tmkdir custom-odb &&\n+\tenv GIT_OBJECT_DIRECTORY=\"$(pwd)/custom-odb\" \\\n+\t\tgit init init-objdir &&\n+\ttest_path_is_missing init-objdir/.git/objects/pack &&\n+\ttest_path_is_dir custom-odb/pack &&\n+\ttest_path_is_dir custom-odb/info\n+'\n+\n test_done\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543777","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-2-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 2/8] setup: drop `setup_git_env()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:29Z","receivedAt":"2026-05-21T07:42:42Z","isPatch":true,"body":"The `setup_git_env()` function is a trivial wrapper around\n`setup_git_env_internal()` and has a single call site only. Drop the\nfunction.\n\nWhile at it, drop stale documentation in \"environment.h\" that points to\nthis function, even though it hasn't been exposed to callers outside of\n\"setup.c\" since 43ad1047a9 (setup: stop using `the_repository` in\n`setup_git_env()`, 2026-03-27) anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.h | 8 +-------\n refs.c        | 3 ++-\n setup.c       | 7 +------\n 3 files changed, 4 insertions(+), 14 deletions(-)\n\ndiff --git a/environment.h b/environment.h\nindex 9eb97b3869..ccfcf37bfb 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -130,13 +130,6 @@ void repo_config_values_init(struct repo_config_values *cfg);\n  * `the_repository`. We should eventually get rid of these and make the\n  * dependency on a repository explicit:\n  *\n- *   - `setup_git_env()` ideally shouldn't exist as it modifies global state,\n- *     namely the environment. The current process shouldn't ever access that\n- *     state via envvars though, but should instead consult a `struct\n- *     repository`. When spawning new processes, we would ideally also pass a\n- *     `struct repository` and then set up the environment variables for the\n- *     child process, only.\n- *\n  *   - `have_git_dir()` should not have to exist at all. Instead, we should\n  *     decide on whether or not we have a `struct repository`.\n  *\n@@ -147,6 +140,7 @@ void repo_config_values_init(struct repo_config_values *cfg);\n  * Please do not add new global config variables here.\n  */\n # ifdef USE_THE_REPOSITORY_VARIABLE\n+\n /*\n  * Returns true iff we have a configured git repository (either via\n  * setup_git_directory, or in the environment via $GIT_DIR).\ndiff --git a/refs.c b/refs.c\nindex 0f3355d2ee..e7070eb743 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -126,7 +126,8 @@ struct ref_namespace_info ref_namespace[] = {\n \t\t * points to the content of another. Unlike the other\n \t\t * ref namespaces, this one can be changed by the\n \t\t * GIT_REPLACE_REF_BASE environment variable. This\n-\t\t * .namespace value will be overwritten in setup_git_env().\n+\t\t * .namespace value will be overwritten during repository\n+\t\t * setup.\n \t\t */\n \t\t.ref = \"refs/replace/\",\n \t\t.decoration = DECORATION_GRAFTED,\ndiff --git a/setup.c b/setup.c\nindex d723306dfe..252b443117 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1074,11 +1074,6 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tfetch_if_missing = 0;\n }\n \n-static void setup_git_env(struct repository *repo, const char *git_dir)\n-{\n-\tsetup_git_env_internal(repo, git_dir, false);\n-}\n-\n static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)\n {\n \txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n@@ -2023,7 +2018,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\tif (!gitdir)\n \t\t\t\tgitdir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\t\t\tsetup_git_env(repo, gitdir);\n+\t\t\tsetup_git_env_internal(repo, gitdir, false);\n \t\t}\n \t\tif (startup_info->have_repository) {\n \t\t\trepo_set_hash_algo(repo, repo_fmt.hash_algo);\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543778","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-3-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 3/8] setup: deduplicate logic to apply repository format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:30Z","receivedAt":"2026-05-21T07:42:45Z","isPatch":true,"body":"After having discovered the repository format we then apply it to the\nrepository so that it knows to use the proper repository extensions. The\nlogic to apply the format is duplicated across three callsites, which\nmakes it rather painfull to add new extensions.\n\nIntroduce a new function `apply_repository_format()` that takes a repo\nand applies a given format to it and adapt all callsites to use it.\nWhile at it, rename `check_repository_format()` to clarify that it\ndoesn't only _check_ the format, but that it also applies it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 31 +++++++-------------\n setup.c      | 93 ++++++++++++++++++++++++++++++++----------------------------\n setup.h      |  9 ++++++\n 3 files changed, 70 insertions(+), 63 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex db57b8308b..58a13f7c4f 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -262,8 +262,8 @@ void repo_set_worktree(struct repository *repo, const char *path)\n \ttrace2_def_repo(repo);\n }\n \n-static int read_and_verify_repository_format(struct repository_format *format,\n-\t\t\t\t\t     const char *commondir)\n+static int read_repository_format_from_commondir(struct repository_format *format,\n+\t\t\t\t\t\t const char *commondir)\n {\n \tint ret = 0;\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -272,11 +272,6 @@ static int read_and_verify_repository_format(struct repository_format *format,\n \tread_repository_format(format, sb.buf);\n \tstrbuf_reset(&sb);\n \n-\tif (verify_repository_format(format, &sb) < 0) {\n-\t\twarning(\"%s\", sb.buf);\n-\t\tret = -1;\n-\t}\n-\n \tstrbuf_release(&sb);\n \treturn ret;\n }\n@@ -290,6 +285,8 @@ int repo_init(struct repository *repo,\n \t      const char *worktree)\n {\n \tstruct repository_format format = REPOSITORY_FORMAT_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n \tmemset(repo, 0, sizeof(*repo));\n \n \tinitialize_repository(repo);\n@@ -297,21 +294,13 @@ int repo_init(struct repository *repo,\n \tif (repo_init_gitdir(repo, gitdir))\n \t\tgoto error;\n \n-\tif (read_and_verify_repository_format(&format, repo->commondir))\n+\tif (read_repository_format_from_commondir(&format, repo->commondir))\n \t\tgoto error;\n \n-\trepo_set_hash_algo(repo, format.hash_algo);\n-\trepo_set_compat_hash_algo(repo, format.compat_hash_algo);\n-\trepo_set_ref_storage_format(repo, format.ref_storage_format,\n-\t\t\t\t    format.ref_storage_payload);\n-\trepo->repository_format_worktree_config = format.worktree_config;\n-\trepo->repository_format_relative_worktrees = format.relative_worktrees;\n-\trepo->repository_format_precious_objects = format.precious_objects;\n-\trepo->repository_format_submodule_path_cfg = format.submodule_path_cfg;\n-\n-\t/* take ownership of format.partial_clone */\n-\trepo->repository_format_partial_clone = format.partial_clone;\n-\tformat.partial_clone = NULL;\n+\tif (apply_repository_format(repo, &format, &err) < 0) {\n+\t\twarning(\"%s\", err.buf);\n+\t\tgoto error;\n+\t}\n \n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n@@ -320,10 +309,12 @@ int repo_init(struct repository *repo,\n \t\trepo_read_loose_object_map(repo);\n \n \tclear_repository_format(&format);\n+\tstrbuf_release(&err);\n \treturn 0;\n \n error:\n \tclear_repository_format(&format);\n+\tstrbuf_release(&err);\n \trepo_clear(repo);\n \treturn -1;\n }\ndiff --git a/setup.c b/setup.c\nindex 252b443117..c5015923f1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -750,8 +750,7 @@ static int check_repo_format(const char *var, const char *value,\n \treturn read_worktree_config(var, value, ctx, vdata);\n }\n \n-static int check_repository_format_gently(struct repository *repo,\n-\t\t\t\t\t  const char *gitdir,\n+static int check_repository_format_gently(const char *gitdir,\n \t\t\t\t\t  struct repository_format *candidate,\n \t\t\t\t\t  int *nongit_ok)\n {\n@@ -765,7 +764,7 @@ static int check_repository_format_gently(struct repository *repo,\n \tstrbuf_release(&sb);\n \n \t/*\n-\t * For historical use of check_repository_format() in git-init,\n+\t * For historical use of check_and_apply_repository_format() in git-init,\n \t * we treat a missing config as a silent \"ok\", even when nongit_ok\n \t * is unset.\n \t */\n@@ -782,8 +781,6 @@ static int check_repository_format_gently(struct repository *repo,\n \t\tdie(\"%s\", err.buf);\n \t}\n \n-\trepo->repository_format_precious_objects = candidate->precious_objects;\n-\n \tstring_list_clear(&candidate->unknown_extensions, 0);\n \tstring_list_clear(&candidate->v1_only_extensions, 0);\n \n@@ -1140,7 +1137,7 @@ static const char *setup_explicit_git_dir(struct repository *repo,\n \t\tdie(_(\"not a git repository: '%s'\"), gitdirenv);\n \t}\n \n-\tif (check_repository_format_gently(repo, gitdirenv, repo_fmt, nongit_ok)) {\n+\tif (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) {\n \t\tfree(gitfile);\n \t\treturn NULL;\n \t}\n@@ -1217,7 +1214,7 @@ static const char *setup_discovered_git_dir(struct repository *repo,\n \t\t\t\t\t    struct repository_format *repo_fmt,\n \t\t\t\t\t    int *nongit_ok)\n {\n-\tif (check_repository_format_gently(repo, gitdir, repo_fmt, nongit_ok))\n+\tif (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))\n \t\treturn NULL;\n \n \t/* --work-tree is set without --git-dir; use discovered one */\n@@ -1265,7 +1262,7 @@ static const char *setup_bare_git_dir(struct repository *repo,\n {\n \tint root_len;\n \n-\tif (check_repository_format_gently(repo, \".\", repo_fmt, nongit_ok))\n+\tif (check_repository_format_gently(\".\", repo_fmt, nongit_ok))\n \t\treturn NULL;\n \n \tsetenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, \"0\", 1);\n@@ -1757,6 +1754,32 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n \treturn result;\n }\n \n+int apply_repository_format(struct repository *repo,\n+\t\t\t    const struct repository_format *format,\n+\t\t\t    struct strbuf *err)\n+{\n+\tif (verify_repository_format(format, err) < 0)\n+\t\treturn -1;\n+\n+\trepo_set_hash_algo(repo, format->hash_algo);\n+\trepo_set_compat_hash_algo(repo, format->compat_hash_algo);\n+\trepo_set_ref_storage_format(repo,\n+\t\t\t\t    format->ref_storage_format,\n+\t\t\t\t    format->ref_storage_payload);\n+\trepo->repository_format_worktree_config =\n+\t\tformat->worktree_config;\n+\trepo->repository_format_submodule_path_cfg =\n+\t\tformat->submodule_path_cfg;\n+\trepo->repository_format_relative_worktrees =\n+\t\tformat->relative_worktrees;\n+\trepo->repository_format_partial_clone =\n+\t\txstrdup_or_null(format->partial_clone);\n+\trepo->repository_format_precious_objects =\n+\t\tformat->precious_objects;\n+\n+\treturn 0;\n+}\n+\n /*\n  * Check the repository format version in the path found in repo_get_git_dir(repo),\n  * and die if it is a version we don't understand. Generally one would\n@@ -1765,26 +1788,20 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n  *\n  * If successful and fmt is not NULL, fill fmt with data.\n  */\n-static void check_repository_format(struct repository *repo, struct repository_format *fmt)\n+static void check_and_apply_repository_format(struct repository *repo,\n+\t\t\t\t\t      struct repository_format *fmt)\n {\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n \tif (!fmt)\n \t\tfmt = &repo_fmt;\n-\tcheck_repository_format_gently(repo, repo_get_git_dir(repo), fmt, NULL);\n+\n+\tcheck_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);\n+\tif (apply_repository_format(repo, fmt, &err) < 0)\n+\t\tdie(\"%s\", err.buf);\n \tstartup_info->have_repository = 1;\n-\trepo_set_hash_algo(repo, fmt->hash_algo);\n-\trepo_set_compat_hash_algo(repo, fmt->compat_hash_algo);\n-\trepo_set_ref_storage_format(repo,\n-\t\t\t\t    fmt->ref_storage_format,\n-\t\t\t\t    fmt->ref_storage_payload);\n-\trepo->repository_format_worktree_config =\n-\t\tfmt->worktree_config;\n-\trepo->repository_format_submodule_path_cfg =\n-\t\tfmt->submodule_path_cfg;\n-\trepo->repository_format_relative_worktrees =\n-\t\tfmt->relative_worktrees;\n-\trepo->repository_format_partial_clone =\n-\t\txstrdup_or_null(fmt->partial_clone);\n+\n \tclear_repository_format(&repo_fmt);\n }\n \n@@ -1862,7 +1879,7 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \n \tif (is_git_directory(\".\")) {\n \t\tset_git_dir(repo, \".\", 0);\n-\t\tcheck_repository_format(repo, NULL);\n+\t\tcheck_and_apply_repository_format(repo, NULL);\n \t\treturn path;\n \t}\n \n@@ -2020,25 +2037,15 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\t\tgitdir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \t\t\tsetup_git_env_internal(repo, gitdir, false);\n \t\t}\n+\n \t\tif (startup_info->have_repository) {\n-\t\t\trepo_set_hash_algo(repo, repo_fmt.hash_algo);\n-\t\t\trepo_set_compat_hash_algo(repo,\n-\t\t\t\t\t\t  repo_fmt.compat_hash_algo);\n-\t\t\trepo_set_ref_storage_format(repo,\n-\t\t\t\t\t\t    repo_fmt.ref_storage_format,\n-\t\t\t\t\t\t    repo_fmt.ref_storage_payload);\n-\t\t\trepo->repository_format_worktree_config =\n-\t\t\t\trepo_fmt.worktree_config;\n-\t\t\trepo->repository_format_relative_worktrees =\n-\t\t\t\trepo_fmt.relative_worktrees;\n-\t\t\trepo->repository_format_submodule_path_cfg =\n-\t\t\t\trepo_fmt.submodule_path_cfg;\n-\t\t\t/* take ownership of repo_fmt.partial_clone */\n-\t\t\trepo->repository_format_partial_clone =\n-\t\t\t\trepo_fmt.partial_clone;\n-\t\t\trepo_fmt.partial_clone = NULL;\n-\t\t\trepo->repository_format_precious_objects =\n-\t\t\t\trepo_fmt.precious_objects;\n+\t\t\tstruct strbuf err = STRBUF_INIT;\n+\n+\t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n+\t\t\t\tdie(\"%s\", err.buf);\n+\n+\t\t\tclear_repository_format(&repo_fmt);\n+\t\t\tstrbuf_release(&err);\n \t\t}\n \t}\n \t/*\n@@ -2814,7 +2821,7 @@ int init_db(struct repository *repo,\n \t * config file, so this will not fail.  What we are catching\n \t * is an attempt to reinitialize new repository with an old tool.\n \t */\n-\tcheck_repository_format(repo, &repo_fmt);\n+\tcheck_and_apply_repository_format(repo, &repo_fmt);\n \n \trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n \ndiff --git a/setup.h b/setup.h\nindex 9409326fe4..5ed92f53fa 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);\n int verify_repository_format(const struct repository_format *format,\n \t\t\t     struct strbuf *err);\n \n+/*\n+ * Apply the given repository format to the repo. This initializes extensions\n+ * and basic data structures required for normal operation. Returns 0 on\n+ * success, a negative error code otherwise.\n+ */\n+int apply_repository_format(struct repository *repo,\n+\t\t\t    const struct repository_format *format,\n+\t\t\t    struct strbuf *err);\n+\n const char *get_template_dir(const char *option_template);\n \n #define INIT_DB_QUIET      (1 << 0)\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543779","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-4-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 4/8] repository: stop initializing the object database in `repo_set_gitdir()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:31Z","receivedAt":"2026-05-21T07:42:47Z","isPatch":true,"body":"The function `repo_set_gitdir()` obviously sets the Git directory for a\ngiven repository. Less obviously though, the function also configures a\ncouple of auxiliary settings.\n\nOne such thing is that we create the object database in this function.\nThis logic only happens conditionally though, as `set_git_dir()` may be\ncalled multiple times during repository setup, and we don't want to\ncreate the object database multiple times. This is somewhat tangled and\nhard to follow.\n\nRemove the logic from `repo_set_gitdir()` and instead initialize the\nobject database outside of it. This leads to some duplication right now,\nbut that duplication will be removed in a subsequent step where we will\nstart initializing the object database as part of applying the repo's\nformat.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 8 ++------\n repository.h | 3 ---\n setup.c      | 7 ++++---\n 3 files changed, 6 insertions(+), 12 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex 58a13f7c4f..2c2395105f 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -181,12 +181,6 @@ void repo_set_gitdir(struct repository *repo,\n \tfree(old_gitdir);\n \n \trepo_set_commondir(repo, o->commondir);\n-\n-\tif (!repo->objects)\n-\t\trepo->objects = odb_new(repo, o->object_dir, o->alternate_db);\n-\telse if (!o->skip_initializing_odb)\n-\t\tBUG(\"cannot reinitialize an already-initialized object directory\");\n-\n \trepo->disable_ref_updates = o->disable_ref_updates;\n \n \texpand_base_dir(&repo->graft_file, o->graft_file,\n@@ -302,6 +296,8 @@ int repo_init(struct repository *repo,\n \t\tgoto error;\n \t}\n \n+\trepo->objects = odb_new(repo, NULL, NULL);\n+\n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n \ndiff --git a/repository.h b/repository.h\nindex c3ec0f4b79..36e2db2633 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -221,12 +221,9 @@ const char *repo_get_work_tree(struct repository *repo);\n  */\n struct set_gitdir_args {\n \tconst char *commondir;\n-\tconst char *object_dir;\n \tconst char *graft_file;\n \tconst char *index_file;\n-\tconst char *alternate_db;\n \tbool disable_ref_updates;\n-\tbool skip_initializing_odb;\n };\n \n void repo_set_gitdir(struct repository *repo, const char *root,\ndiff --git a/setup.c b/setup.c\nindex c5015923f1..3bd3f6c592 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1045,17 +1045,18 @@ static void setup_git_env_internal(struct repository *repo,\n \tstruct strvec to_free = STRVEC_INIT;\n \n \targs.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);\n-\targs.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);\n \targs.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);\n \targs.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);\n-\targs.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);\n \tif (getenv(GIT_QUARANTINE_ENVIRONMENT))\n \t\targs.disable_ref_updates = true;\n-\targs.skip_initializing_odb = skip_initializing_odb;\n \n \trepo_set_gitdir(repo, git_dir, &args);\n \tstrvec_clear(&to_free);\n \n+\tif (!skip_initializing_odb)\n+\t\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n+\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tdisable_replace_refs();\n \treplace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543780","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-5-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 5/8] setup: stop creating the object database in `setup_git_env()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:32Z","receivedAt":"2026-05-21T07:42:49Z","isPatch":true,"body":"In the preceding commit we have stopped creating the object database in\n`repo_set_gitdir()`. But the logic is still somewhat confusing as we\nstill end up creating it conditionally in `setup_git_dir()`, which is\ncalled multiple times.\n\nDrop the conditional logic and instead create the object database in all\nplaces where we have discovered and configured a repository.\n\nThis leads to even more duplication than we already had in the preceding\ncommit, but an alert reader may notice that we now (almost) always call\n`odb_new()` directly before having called `apply_repository_format()`.\nThe only exception to this is `setup_git_directory_gently()`, where we\nalso call the function when _not_ applying the repository format. This\nwill be fixed in the next commit, and once that's done we can then unify\ncreation of the object database into `apply_repository_format()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n setup.c | 37 ++++++++++++++++++++++++++-----------\n 1 file changed, 26 insertions(+), 11 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 3bd3f6c592..0dc9fe4565 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1035,8 +1035,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)\n }\n \n static void setup_git_env_internal(struct repository *repo,\n-\t\t\t\t   const char *git_dir,\n-\t\t\t\t   bool skip_initializing_odb)\n+\t\t\t\t   const char *git_dir)\n {\n \tchar *git_replace_ref_base;\n \tconst char *shallow_file;\n@@ -1053,10 +1052,6 @@ static void setup_git_env_internal(struct repository *repo,\n \trepo_set_gitdir(repo, git_dir, &args);\n \tstrvec_clear(&to_free);\n \n-\tif (!skip_initializing_odb)\n-\t\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tdisable_replace_refs();\n \treplace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);\n@@ -1072,10 +1067,10 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tfetch_if_missing = 0;\n }\n \n-static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)\n+static void set_git_dir_1(struct repository *repo, const char *path)\n {\n \txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n-\tsetup_git_env_internal(repo, path, skip_initializing_odb);\n+\tsetup_git_env_internal(repo, path);\n }\n \n static void update_relative_gitdir(const char *name UNUSED,\n@@ -1089,7 +1084,7 @@ static void update_relative_gitdir(const char *name UNUSED,\n \ttrace_printf_key(&trace_setup_key,\n \t\t\t \"setup: move $GIT_DIR to '%s'\",\n \t\t\t path);\n-\tset_git_dir_1(repo, path, true);\n+\tset_git_dir_1(repo, path);\n \tfree(path);\n }\n \n@@ -1102,7 +1097,7 @@ static void set_git_dir(struct repository *repo, const char *path, int make_real\n \t\tpath = realpath.buf;\n \t}\n \n-\tset_git_dir_1(repo, path, false);\n+\tset_git_dir_1(repo, path);\n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, update_relative_gitdir, repo);\n \n@@ -1879,8 +1874,15 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \t}\n \n \tif (is_git_directory(\".\")) {\n+\t\tstruct strvec to_free = STRVEC_INIT;\n+\n \t\tset_git_dir(repo, \".\", 0);\n+\t\trepo->objects = odb_new(repo,\n+\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n \t\tcheck_and_apply_repository_format(repo, NULL);\n+\n+\t\tstrvec_clear(&to_free);\n \t\treturn path;\n \t}\n \n@@ -2032,13 +2034,19 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t    startup_info->have_repository ||\n \t    /* GIT_DIR_EXPLICIT */\n \t    getenv(GIT_DIR_ENVIRONMENT)) {\n+\t\tstruct strvec to_free = STRVEC_INIT;\n+\n \t\tif (!repo->gitdir) {\n \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\tif (!gitdir)\n \t\t\t\tgitdir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\t\t\tsetup_git_env_internal(repo, gitdir, false);\n+\t\t\tsetup_git_env_internal(repo, gitdir);\n \t\t}\n \n+\t\trepo->objects = odb_new(repo,\n+\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n+\n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n \n@@ -2048,6 +2056,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\tclear_repository_format(&repo_fmt);\n \t\t\tstrbuf_release(&err);\n \t\t}\n+\n+\t\tstrvec_clear(&to_free);\n \t}\n \t/*\n \t * Since precompose_string_if_needed() needs to look at\n@@ -2796,6 +2806,7 @@ int init_db(struct repository *repo,\n \tint exist_ok = flags & INIT_DB_EXIST_OK;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \n \tif (real_git_dir) {\n \t\tstruct stat st;\n@@ -2816,6 +2827,9 @@ int init_db(struct repository *repo,\n \t}\n \tstartup_info->have_repository = 1;\n \n+\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n+\n \t/*\n \t * Check to see if the repository version is right.\n \t * Note that a newly created repository does not have\n@@ -2879,6 +2893,7 @@ int init_db(struct repository *repo,\n \t}\n \n \tclear_repository_format(&repo_fmt);\n+\tstrvec_clear(&to_free);\n \tfree(original_git_dir);\n \treturn 0;\n }\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543781","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-6-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 6/8] setup: stop initializing object database without repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:33Z","receivedAt":"2026-05-21T07:42:52Z","isPatch":true,"body":"The function `setup_git_directory_gently()` is responsible for\ndiscovering and setting up a Git repository based on various environment\nvariables and the current working directory. The result is thus a fully\nusable Git repository.\n\nOne oddity of this function is that we may set up the object database\neven in the case where we don't have a repository, namely in the case\nwhere the `GIT_DIR_EXPLICIT` environment variable is set but points to a\nnon-existent repository. If so, we call `setup_git_env_internal()` with\nthe value of the environment variable so that the repository's Git\ndirectory is configured, even if it points to a non-existent directory.\n\nHistorically though, this function didn't only configure the repository,\nbut also initialized the object database. We retained this behaviour\nfrom a preceding commit, even though it really doesn't make much sense\nin the first place -- there is no repository, so we don't have an object\ndatabase either. There seemingly isn't much of a reason to construct the\nobject database, as we typically won't try to read objects when we don't\nhave an object database.\n\nThere's one exception though: git-index-pack(1) may run outside of a\nrepository, which can be used to perform consistency checks for a\npackfile. The code path is _almost_ working: we already know to call\n`parse_object_buffer()`, which can read objects without an object\ndatabase being available. And that works for all object types except for\ncommits, because `parse_commit_buffer()` calls `parse_commit_graph()`,\nand that function doesn't handle the case where we don't have an object\ndatabase.\n\nFix this instance to check for the object database instead of checking\nfor the Git directory having been initialized. With this fixed, we can\nnow stop constructing an object database completely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n commit-graph.c | 4 ++--\n setup.c        | 7 +++----\n 2 files changed, 5 insertions(+), 6 deletions(-)\n\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 9abe62bd5a..0820cf5fb8 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -740,13 +740,13 @@ static struct commit_graph *prepare_commit_graph(struct repository *r)\n \tstruct odb_source *source;\n \n \t/*\n-\t * Early return if there is no git dir or if the commit graph is\n+\t * Early return if there is no object database or if the commit graph is\n \t * disabled.\n \t *\n \t * This must come before the \"already attempted?\" check below, because\n \t * we want to disable even an already-loaded graph file.\n \t */\n-\tif (!r->gitdir || r->commit_graph_disabled)\n+\tif (!r->objects || r->commit_graph_disabled)\n \t\treturn NULL;\n \n \tif (r->objects->commit_graph_attempted)\ndiff --git a/setup.c b/setup.c\nindex 0dc9fe4565..4a8d6230b1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2043,13 +2043,12 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\tsetup_git_env_internal(repo, gitdir);\n \t\t}\n \n-\t\trepo->objects = odb_new(repo,\n-\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n \n+\t\t\trepo->objects = odb_new(repo,\n+\t\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n \t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n \t\t\t\tdie(\"%s\", err.buf);\n \n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543782","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-7-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 7/8] repository: stop reading loose object map twice on repo init","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:34Z","receivedAt":"2026-05-21T07:42:55Z","isPatch":true,"body":"When initializing a repository via `repo_init()` we end up reading the\nloose object map twice:\n\n  - `apply_repository_format()` calls `repo_set_compat_hash_algo()`,\n    which in turn calls `repo_read_loose_object_map()` if we have a\n    compatibility hash configured.\n\n  - `repo_init()` calls `repo_read_loose_object_map()` directly a second\n    time.\n\nDrop the second read of the loose object map in `repo_init()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 3 ---\n 1 file changed, 3 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex 2c2395105f..61dfbb8be6 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -301,9 +301,6 @@ int repo_init(struct repository *repo,\n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n \n-\tif (repo->compat_hash_algo)\n-\t\trepo_read_loose_object_map(repo);\n-\n \tclear_repository_format(&format);\n \tstrbuf_release(&err);\n \treturn 0;\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543783","messageId":"20260521-b4-pks-setup-centralize-odb-creation-v1-8-f130d2a7e8ae@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH 8/8] setup: construct object database in `apply_repository_format()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T07:42:35Z","receivedAt":"2026-05-21T07:42:58Z","isPatch":true,"body":"With the preceding changes we now always construct the repository's\nobject database before applying the repository format. Remove this\nduplication by constructing it in `apply_repository_format()` instead.\n\nNote that we create the object database _after_ having set up the\nrepository's hash algorithm, but _before_ setting the compat hash\nalgorithm. This is intentional:\n\n  - Constructing the object database may require knowledge of its\n    intended object format.\n\n  - Setting up the compatibility hash requires the object database to be\n    initialized already, because we immediately read the loose object\n    map.\n\nThe first point is sensible, the second maybe a little less so. Ideally,\nit should be the responsibility of the object database itself to\ninitialize any data structures required for the compatibility hash. But\nthis would require further changes, so this is kept as-is for now.\n\nFurther note that this requires us to move handling of the environment\nvariables GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES into\nthe repository format, as well. This allows the caller more flexibility\naround whether or not those environment variables are being honored, as\nwe do do want to respect them in \"setup.c\", but not in \"repository.c\".\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c |  4 +---\n setup.c      | 45 +++++++++++++++++++++------------------------\n setup.h      | 10 ++++++++++\n 3 files changed, 32 insertions(+), 27 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex 61dfbb8be6..187dd471c4 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -291,13 +291,11 @@ int repo_init(struct repository *repo,\n \tif (read_repository_format_from_commondir(&format, repo->commondir))\n \t\tgoto error;\n \n-\tif (apply_repository_format(repo, &format, &err) < 0) {\n+\tif (apply_repository_format(repo, &format, 0, &err) < 0) {\n \t\twarning(\"%s\", err.buf);\n \t\tgoto error;\n \t}\n \n-\trepo->objects = odb_new(repo, NULL, NULL);\n-\n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n \ndiff --git a/setup.c b/setup.c\nindex 4a8d6230b1..513fc88749 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1752,12 +1752,22 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n \n int apply_repository_format(struct repository *repo,\n \t\t\t    const struct repository_format *format,\n+\t\t\t    enum apply_repository_format_flags flags,\n \t\t\t    struct strbuf *err)\n {\n+\tchar *object_directory = NULL, *alternate_object_directories = NULL;\n+\n \tif (verify_repository_format(format, err) < 0)\n \t\treturn -1;\n \n+\tif (flags & APPLY_REPOSITORY_FORMAT_HONOR_ENV) {\n+\t\tobject_directory = xstrdup_or_null(getenv(DB_ENVIRONMENT));\n+\t\talternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));\n+\t}\n+\n \trepo_set_hash_algo(repo, format->hash_algo);\n+\trepo->objects = odb_new(repo, object_directory,\n+\t\t\t\talternate_object_directories);\n \trepo_set_compat_hash_algo(repo, format->compat_hash_algo);\n \trepo_set_ref_storage_format(repo,\n \t\t\t\t    format->ref_storage_format,\n@@ -1773,6 +1783,8 @@ int apply_repository_format(struct repository *repo,\n \trepo->repository_format_precious_objects =\n \t\tformat->precious_objects;\n \n+\tfree(alternate_object_directories);\n+\tfree(object_directory);\n \treturn 0;\n }\n \n@@ -1785,7 +1797,8 @@ int apply_repository_format(struct repository *repo,\n  * If successful and fmt is not NULL, fill fmt with data.\n  */\n static void check_and_apply_repository_format(struct repository *repo,\n-\t\t\t\t\t      struct repository_format *fmt)\n+\t\t\t\t\t      struct repository_format *fmt,\n+\t\t\t\t\t      enum apply_repository_format_flags flags)\n {\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -1794,7 +1807,7 @@ static void check_and_apply_repository_format(struct repository *repo,\n \t\tfmt = &repo_fmt;\n \n \tcheck_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);\n-\tif (apply_repository_format(repo, fmt, &err) < 0)\n+\tif (apply_repository_format(repo, fmt, flags, &err) < 0)\n \t\tdie(\"%s\", err.buf);\n \tstartup_info->have_repository = 1;\n \n@@ -1874,15 +1887,9 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \t}\n \n \tif (is_git_directory(\".\")) {\n-\t\tstruct strvec to_free = STRVEC_INIT;\n-\n \t\tset_git_dir(repo, \".\", 0);\n-\t\trepo->objects = odb_new(repo,\n-\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\t\tcheck_and_apply_repository_format(repo, NULL);\n-\n-\t\tstrvec_clear(&to_free);\n+\t\tcheck_and_apply_repository_format(repo, NULL,\n+\t\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n \t\treturn path;\n \t}\n \n@@ -2034,8 +2041,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t    startup_info->have_repository ||\n \t    /* GIT_DIR_EXPLICIT */\n \t    getenv(GIT_DIR_ENVIRONMENT)) {\n-\t\tstruct strvec to_free = STRVEC_INIT;\n-\n \t\tif (!repo->gitdir) {\n \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\tif (!gitdir)\n@@ -2046,17 +2051,13 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n \n-\t\t\trepo->objects = odb_new(repo,\n-\t\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n+\t\t\tif (apply_repository_format(repo, &repo_fmt,\n+\t\t\t\t\t\t    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)\n \t\t\t\tdie(\"%s\", err.buf);\n \n \t\t\tclear_repository_format(&repo_fmt);\n \t\t\tstrbuf_release(&err);\n \t\t}\n-\n-\t\tstrvec_clear(&to_free);\n \t}\n \t/*\n \t * Since precompose_string_if_needed() needs to look at\n@@ -2805,7 +2806,6 @@ int init_db(struct repository *repo,\n \tint exist_ok = flags & INIT_DB_EXIST_OK;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n-\tstruct strvec to_free = STRVEC_INIT;\n \n \tif (real_git_dir) {\n \t\tstruct stat st;\n@@ -2826,16 +2826,14 @@ int init_db(struct repository *repo,\n \t}\n \tstartup_info->have_repository = 1;\n \n-\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\n \t/*\n \t * Check to see if the repository version is right.\n \t * Note that a newly created repository does not have\n \t * config file, so this will not fail.  What we are catching\n \t * is an attempt to reinitialize new repository with an old tool.\n \t */\n-\tcheck_and_apply_repository_format(repo, &repo_fmt);\n+\tcheck_and_apply_repository_format(repo, &repo_fmt,\n+\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n \n \trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n \n@@ -2892,7 +2890,6 @@ int init_db(struct repository *repo,\n \t}\n \n \tclear_repository_format(&repo_fmt);\n-\tstrvec_clear(&to_free);\n \tfree(original_git_dir);\n \treturn 0;\n }\ndiff --git a/setup.h b/setup.h\nindex 5ed92f53fa..821b55aca0 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);\n int verify_repository_format(const struct repository_format *format,\n \t\t\t     struct strbuf *err);\n \n+enum apply_repository_format_flags {\n+\t/*\n+\t * Honor environment variables when applying the repository format to\n+\t * the repository. For now, this only covers environment variables that\n+\t * relate to the object database.\n+\t */\n+\tAPPLY_REPOSITORY_FORMAT_HONOR_ENV = (1 << 0),\n+};\n+\n /*\n  * Apply the given repository format to the repo. This initializes extensions\n  * and basic data structures required for normal operation. Returns 0 on\n@@ -228,6 +237,7 @@ int verify_repository_format(const struct repository_format *format,\n  */\n int apply_repository_format(struct repository *repo,\n \t\t\t    const struct repository_format *format,\n+\t\t\t    enum apply_repository_format_flags flags,\n \t\t\t    struct strbuf *err);\n \n const char *get_template_dir(const char *option_template);\n\n-- \n2.54.0.771.g3ed373ac14.dirty\n\n"},{"id":"543841","messageId":"xmqqcxyozp0r.fsf@gitster.g","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-1-f130d2a7e8ae@pks.im","subject":"Re: [PATCH 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-21T16:49:24Z","receivedAt":"2026-05-21T16:49:27Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In subsequent commits we'll rework how we set up the repository. This is\n> a somewhat intricate and thus fragile sequence, there's many things that\n> can go subtly wrong, and there are lots of interesting interactions that\n> one can discover.\n>\n> One such discovered edge case was the interaction between git-init(1)\n> and the \"GIT_OBJECT_DIRECTORY\" enviroment variable. When set, the\n\n\"environment\"???\n\n> behaviour is that the object directory should be created at the path\n> that the variable points to. This behaviour is documented as such in\n> its man page:\n>\n>   If the object storage directory is specified via the\n>   GIT_OBJECT_DIRECTORY environment variable then the sha1 directories\n>   are created underneath; otherwise, the default $GIT_DIR/objects\n>   directory is used.\n>\n> Curiously enough though we don't seem to have any tests that exercise\n> this directly, and thus a subsequent commit inadvertently broke this\n> expectation.\n>\n> Plug this test gap.\n\nNice.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  t/t0001-init.sh | 10 ++++++++++\n>  1 file changed, 10 insertions(+)\n>\n> diff --git a/t/t0001-init.sh b/t/t0001-init.sh\n> index e4d32bb4d2..e89feca544 100755\n> --- a/t/t0001-init.sh\n> +++ b/t/t0001-init.sh\n> @@ -980,4 +980,14 @@ test_expect_success 're-init reads matching includeIf.onbranch' '\n>  \ttest_cmp expect err\n>  '\n>  \n> +test_expect_success 'init honors GIT_OBJECT_DIRECTORY' '\n> +\ttest_when_finished \"rm -rf init-objdir custom-odb\" &&\n> +\tmkdir custom-odb &&\n> +\tenv GIT_OBJECT_DIRECTORY=\"$(pwd)/custom-odb\" \\\n> +\t\tgit init init-objdir &&\n> +\ttest_path_is_missing init-objdir/.git/objects/pack &&\n> +\ttest_path_is_dir custom-odb/pack &&\n> +\ttest_path_is_dir custom-odb/info\n> +'\n> +\n>  test_done\n"},{"id":"543845","messageId":"741c2a26-7380-4d8e-aa91-fb237e9f10dc@app.fastmail.com","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-1-f130d2a7e8ae@pks.im","subject":"Re: [PATCH 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-05-21T17:51:59Z","receivedAt":"2026-05-21T17:52:21Z","isPatch":true,"body":"On Thu, May 21, 2026, at 09:42, Patrick Steinhardt wrote:\n> In subsequent commits we'll rework how we set up the repository. This is\n> a somewhat intricate and thus fragile sequence, there's many things that\n\nShould this be s/, there/; there/ ? Depends on if this is a list of\nthree items or if “This is” is a subclause that is supposed to point at\n“there's many”.\n\n> can go subtly wrong, and there are lots of interesting interactions that\n> one can discover.\n>\n> One such discovered edge case was the interaction between git-init(1)\n> and the \"GIT_OBJECT_DIRECTORY\" enviroment variable. When set, the\n> behaviour is that the object directory should be created at the path\n> that the variable points to. This behaviour is documented as such in\n> its man page:\n>\n>   If the object storage directory is specified via the\n>   GIT_OBJECT_DIRECTORY environment variable then the sha1 directories\n>   are created underneath; otherwise, the default $GIT_DIR/objects\n>   directory is used.\n>\n> Curiously enough though we don't seem to have any tests that exercise\n> this directly, and thus a subsequent commit inadvertently broke this\n> expectation.\n\nIsn’t it more that “the upcoming changes *would have* broken” them if\nnot for this change? This seems to refer to a an alternative commit\nhistory where this change does not exist?\n\n>\n> Plug this test gap.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>[snip]\n"},{"id":"543846","messageId":"xmqq4ik0zls3.fsf@gitster.g","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-8-f130d2a7e8ae@pks.im","subject":"Re: [PATCH 8/8] setup: construct object database in `apply_repository_format()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-21T17:59:24Z","receivedAt":"2026-05-21T17:59:28Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> With the preceding changes we now always construct the repository's\n> object database before applying the repository format. Remove this\n> duplication by constructing it in `apply_repository_format()` instead.\n>\n> Note that we create the object database _after_ having set up the\n> repository's hash algorithm, but _before_ setting the compat hash\n> algorithm. This is intentional:\n>\n>   - Constructing the object database may require knowledge of its\n>     intended object format.\n>\n>   - Setting up the compatibility hash requires the object database to be\n>     initialized already, because we immediately read the loose object\n>     map.\n>\n> The first point is sensible, the second maybe a little less so. Ideally,\n> it should be the responsibility of the object database itself to\n> initialize any data structures required for the compatibility hash. But\n> this would require further changes, so this is kept as-is for now.\n\nYeah, I guess it is a good place to stop, instead of solving the\nchicken-and-egg problem in one go.\n\n> Further note that this requires us to move handling of the environment\n> variables GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES into\n> the repository format, as well. This allows the caller more flexibility\n> around whether or not those environment variables are being honored, as\n> we do do want to respect them in \"setup.c\", but not in \"repository.c\".\n\nIt seems that we really really really want to do so ;-).  \"do do\nwant to\" -> \"do want to\" or even \"want to\", perhaps.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  repository.c |  4 +---\n>  setup.c      | 45 +++++++++++++++++++++------------------------\n>  setup.h      | 10 ++++++++++\n>  3 files changed, 32 insertions(+), 27 deletions(-)\n>\n> diff --git a/repository.c b/repository.c\n> index 61dfbb8be6..187dd471c4 100644\n> --- a/repository.c\n> +++ b/repository.c\n> @@ -291,13 +291,11 @@ int repo_init(struct repository *repo,\n>  \tif (read_repository_format_from_commondir(&format, repo->commondir))\n>  \t\tgoto error;\n>  \n> -\tif (apply_repository_format(repo, &format, &err) < 0) {\n> +\tif (apply_repository_format(repo, &format, 0, &err) < 0) {\n>  \t\twarning(\"%s\", err.buf);\n>  \t\tgoto error;\n>  \t}\n>  \n> -\trepo->objects = odb_new(repo, NULL, NULL);\n> -\n>  \tif (worktree)\n>  \t\trepo_set_worktree(repo, worktree);\n>  \n> diff --git a/setup.c b/setup.c\n> index 4a8d6230b1..513fc88749 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1752,12 +1752,22 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n>  \n>  int apply_repository_format(struct repository *repo,\n>  \t\t\t    const struct repository_format *format,\n> +\t\t\t    enum apply_repository_format_flags flags,\n>  \t\t\t    struct strbuf *err)\n>  {\n> +\tchar *object_directory = NULL, *alternate_object_directories = NULL;\n> +\n>  \tif (verify_repository_format(format, err) < 0)\n>  \t\treturn -1;\n>  \n> +\tif (flags & APPLY_REPOSITORY_FORMAT_HONOR_ENV) {\n> +\t\tobject_directory = xstrdup_or_null(getenv(DB_ENVIRONMENT));\n> +\t\talternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));\n> +\t}\n> +\n>  \trepo_set_hash_algo(repo, format->hash_algo);\n> +\trepo->objects = odb_new(repo, object_directory,\n> +\t\t\t\talternate_object_directories);\n>  \trepo_set_compat_hash_algo(repo, format->compat_hash_algo);\n>  \trepo_set_ref_storage_format(repo,\n>  \t\t\t\t    format->ref_storage_format,\n> @@ -1773,6 +1783,8 @@ int apply_repository_format(struct repository *repo,\n>  \trepo->repository_format_precious_objects =\n>  \t\tformat->precious_objects;\n>  \n> +\tfree(alternate_object_directories);\n> +\tfree(object_directory);\n>  \treturn 0;\n>  }\n>  \n> @@ -1785,7 +1797,8 @@ int apply_repository_format(struct repository *repo,\n>   * If successful and fmt is not NULL, fill fmt with data.\n>   */\n>  static void check_and_apply_repository_format(struct repository *repo,\n> -\t\t\t\t\t      struct repository_format *fmt)\n> +\t\t\t\t\t      struct repository_format *fmt,\n> +\t\t\t\t\t      enum apply_repository_format_flags flags)\n>  {\n>  \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n>  \tstruct strbuf err = STRBUF_INIT;\n> @@ -1794,7 +1807,7 @@ static void check_and_apply_repository_format(struct repository *repo,\n>  \t\tfmt = &repo_fmt;\n>  \n>  \tcheck_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);\n> -\tif (apply_repository_format(repo, fmt, &err) < 0)\n> +\tif (apply_repository_format(repo, fmt, flags, &err) < 0)\n>  \t\tdie(\"%s\", err.buf);\n>  \tstartup_info->have_repository = 1;\n>  \n> @@ -1874,15 +1887,9 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n>  \t}\n>  \n>  \tif (is_git_directory(\".\")) {\n> -\t\tstruct strvec to_free = STRVEC_INIT;\n> -\n>  \t\tset_git_dir(repo, \".\", 0);\n> -\t\trepo->objects = odb_new(repo,\n> -\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n> -\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n> -\t\tcheck_and_apply_repository_format(repo, NULL);\n> -\n> -\t\tstrvec_clear(&to_free);\n> +\t\tcheck_and_apply_repository_format(repo, NULL,\n> +\t\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n>  \t\treturn path;\n>  \t}\n>  \n> @@ -2034,8 +2041,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n>  \t    startup_info->have_repository ||\n>  \t    /* GIT_DIR_EXPLICIT */\n>  \t    getenv(GIT_DIR_ENVIRONMENT)) {\n> -\t\tstruct strvec to_free = STRVEC_INIT;\n> -\n>  \t\tif (!repo->gitdir) {\n>  \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n>  \t\t\tif (!gitdir)\n> @@ -2046,17 +2051,13 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n>  \t\tif (startup_info->have_repository) {\n>  \t\t\tstruct strbuf err = STRBUF_INIT;\n>  \n> -\t\t\trepo->objects = odb_new(repo,\n> -\t\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n> -\t\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n> -\t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n> +\t\t\tif (apply_repository_format(repo, &repo_fmt,\n> +\t\t\t\t\t\t    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)\n>  \t\t\t\tdie(\"%s\", err.buf);\n>  \n>  \t\t\tclear_repository_format(&repo_fmt);\n>  \t\t\tstrbuf_release(&err);\n>  \t\t}\n> -\n> -\t\tstrvec_clear(&to_free);\n>  \t}\n>  \t/*\n>  \t * Since precompose_string_if_needed() needs to look at\n> @@ -2805,7 +2806,6 @@ int init_db(struct repository *repo,\n>  \tint exist_ok = flags & INIT_DB_EXIST_OK;\n>  \tchar *original_git_dir = real_pathdup(git_dir, 1);\n>  \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n> -\tstruct strvec to_free = STRVEC_INIT;\n>  \n>  \tif (real_git_dir) {\n>  \t\tstruct stat st;\n> @@ -2826,16 +2826,14 @@ int init_db(struct repository *repo,\n>  \t}\n>  \tstartup_info->have_repository = 1;\n>  \n> -\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n> -\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n> -\n>  \t/*\n>  \t * Check to see if the repository version is right.\n>  \t * Note that a newly created repository does not have\n>  \t * config file, so this will not fail.  What we are catching\n>  \t * is an attempt to reinitialize new repository with an old tool.\n>  \t */\n> -\tcheck_and_apply_repository_format(repo, &repo_fmt);\n> +\tcheck_and_apply_repository_format(repo, &repo_fmt,\n> +\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n>  \n>  \trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n>  \n> @@ -2892,7 +2890,6 @@ int init_db(struct repository *repo,\n>  \t}\n>  \n>  \tclear_repository_format(&repo_fmt);\n> -\tstrvec_clear(&to_free);\n>  \tfree(original_git_dir);\n>  \treturn 0;\n>  }\n> diff --git a/setup.h b/setup.h\n> index 5ed92f53fa..821b55aca0 100644\n> --- a/setup.h\n> +++ b/setup.h\n> @@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);\n>  int verify_repository_format(const struct repository_format *format,\n>  \t\t\t     struct strbuf *err);\n>  \n> +enum apply_repository_format_flags {\n> +\t/*\n> +\t * Honor environment variables when applying the repository format to\n> +\t * the repository. For now, this only covers environment variables that\n> +\t * relate to the object database.\n> +\t */\n> +\tAPPLY_REPOSITORY_FORMAT_HONOR_ENV = (1 << 0),\n> +};\n> +\n>  /*\n>   * Apply the given repository format to the repo. This initializes extensions\n>   * and basic data structures required for normal operation. Returns 0 on\n> @@ -228,6 +237,7 @@ int verify_repository_format(const struct repository_format *format,\n>   */\n>  int apply_repository_format(struct repository *repo,\n>  \t\t\t    const struct repository_format *format,\n> +\t\t\t    enum apply_repository_format_flags flags,\n>  \t\t\t    struct strbuf *err);\n>  \n>  const char *get_template_dir(const char *option_template);\n"},{"id":"543865","messageId":"xmqqqzn4xp0c.fsf@gitster.g","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"Re: [PATCH 0/8] setup: centralize object database creation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-22T00:32:35Z","receivedAt":"2026-05-22T00:32:39Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)\n> with ps/setup-wo-the-repository at df69f40c34 (setup: stop using\n> `the_repository` in `init_db()`, 2026-05-19) merged into it.\n\nFWIW, this merge needs the following merge-fix squashed into it,\nfor the topic to build standalone.\n\ncommit ce350f62ceb26f3276ea3b7ad78b7f8cb4c35cf7\nAuthor: Junio C Hamano <gitster@pobox.com>\nDate:   Wed May 13 12:20:29 2026 +0900\n\n    merge-fix/ps/setup-wo-the-repository\n    \n    with  js/objects-larger-than-4gb-on-windows\n\ndiff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c\nindex 1f28ecf0f2..3fa534fbdf 100644\n--- a/t/helper/test-synthesize.c\n+++ b/t/helper/test-synthesize.c\n@@ -506,7 +506,7 @@ static int cmd__synthesize__pack(int argc, const char **argv,\n \t\tOPT_END()\n \t};\n \n-\tsetup_git_directory_gently(&non_git);\n+\tsetup_git_directory_gently(the_repository, &non_git);\n \trepo = the_repository;\n \talgo = unsafe_hash_algo(repo->hash_algo);\n \n"},{"id":"543885","messageId":"ag_xmQO_BSzRsoAv@pks.im","threadId":"65666","inReplyTo":"xmqqqzn4xp0c.fsf@gitster.g","subject":"Re: [PATCH 0/8] setup: centralize object database creation","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-22T06:03:05Z","receivedAt":"2026-05-22T06:03:13Z","isPatch":true,"body":"On Fri, May 22, 2026 at 09:32:35AM +0900, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)\n> > with ps/setup-wo-the-repository at df69f40c34 (setup: stop using\n> > `the_repository` in `init_db()`, 2026-05-19) merged into it.\n> \n> FWIW, this merge needs the following merge-fix squashed into it,\n> for the topic to build standalone.\n> \n> commit ce350f62ceb26f3276ea3b7ad78b7f8cb4c35cf7\n> Author: Junio C Hamano <gitster@pobox.com>\n> Date:   Wed May 13 12:20:29 2026 +0900\n> \n>     merge-fix/ps/setup-wo-the-repository\n>     \n>     with  js/objects-larger-than-4gb-on-windows\n> \n> diff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c\n> index 1f28ecf0f2..3fa534fbdf 100644\n> --- a/t/helper/test-synthesize.c\n> +++ b/t/helper/test-synthesize.c\n> @@ -506,7 +506,7 @@ static int cmd__synthesize__pack(int argc, const char **argv,\n>  \t\tOPT_END()\n>  \t};\n>  \n> -\tsetup_git_directory_gently(&non_git);\n> +\tsetup_git_directory_gently(the_repository, &non_git);\n>  \trepo = the_repository;\n>  \talgo = unsafe_hash_algo(repo->hash_algo);\n\nOh, right, I should have mentioned this. I do have the same fixup on top\nof the merge, thanks.\n\nPatrick\n"},{"id":"543886","messageId":"ag_xoMnclzss3N4o@pks.im","threadId":"65666","inReplyTo":"xmqq4ik0zls3.fsf@gitster.g","subject":"Re: [PATCH 8/8] setup: construct object database in `apply_repository_format()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-22T06:03:12Z","receivedAt":"2026-05-22T06:03:16Z","isPatch":true,"body":"On Fri, May 22, 2026 at 02:59:24AM +0900, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > With the preceding changes we now always construct the repository's\n> > object database before applying the repository format. Remove this\n> > duplication by constructing it in `apply_repository_format()` instead.\n> >\n> > Note that we create the object database _after_ having set up the\n> > repository's hash algorithm, but _before_ setting the compat hash\n> > algorithm. This is intentional:\n> >\n> >   - Constructing the object database may require knowledge of its\n> >     intended object format.\n> >\n> >   - Setting up the compatibility hash requires the object database to be\n> >     initialized already, because we immediately read the loose object\n> >     map.\n> >\n> > The first point is sensible, the second maybe a little less so. Ideally,\n> > it should be the responsibility of the object database itself to\n> > initialize any data structures required for the compatibility hash. But\n> > this would require further changes, so this is kept as-is for now.\n> \n> Yeah, I guess it is a good place to stop, instead of solving the\n> chicken-and-egg problem in one go.\n> \n> > Further note that this requires us to move handling of the environment\n> > variables GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES into\n> > the repository format, as well. This allows the caller more flexibility\n> > around whether or not those environment variables are being honored, as\n> > we do do want to respect them in \"setup.c\", but not in \"repository.c\".\n> \n> It seems that we really really really want to do so ;-).  \"do do\n> want to\" -> \"do want to\" or even \"want to\", perhaps.\n\nFixed locally, thanks! :)\n\nPatrick\n"},{"id":"543887","messageId":"ag_yUsOEO6AjT4Ky@pks.im","threadId":"65666","inReplyTo":"741c2a26-7380-4d8e-aa91-fb237e9f10dc@app.fastmail.com","subject":"Re: [PATCH 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-22T06:06:10Z","receivedAt":"2026-05-22T06:06:15Z","isPatch":true,"body":"On Thu, May 21, 2026 at 07:51:59PM +0200, Kristoffer Haugsbakk wrote:\n> On Thu, May 21, 2026, at 09:42, Patrick Steinhardt wrote:\n> > In subsequent commits we'll rework how we set up the repository. This is\n> > a somewhat intricate and thus fragile sequence, there's many things that\n> \n> Should this be s/, there/; there/ ? Depends on if this is a list of\n> three items or if “This is” is a subclause that is supposed to point at\n> “there's many”.\n\nThat reads a bit better.\n\n> > can go subtly wrong, and there are lots of interesting interactions that\n> > one can discover.\n> >\n> > One such discovered edge case was the interaction between git-init(1)\n> > and the \"GIT_OBJECT_DIRECTORY\" enviroment variable. When set, the\n> > behaviour is that the object directory should be created at the path\n> > that the variable points to. This behaviour is documented as such in\n> > its man page:\n> >\n> >   If the object storage directory is specified via the\n> >   GIT_OBJECT_DIRECTORY environment variable then the sha1 directories\n> >   are created underneath; otherwise, the default $GIT_DIR/objects\n> >   directory is used.\n> >\n> > Curiously enough though we don't seem to have any tests that exercise\n> > this directly, and thus a subsequent commit inadvertently broke this\n> > expectation.\n> \n> Isn’t it more that “the upcoming changes *would have* broken” them if\n> not for this change? This seems to refer to a an alternative commit\n> history where this change does not exist?\n\nGrammar is hard :) But yeah, this of course refers to an alternative\ncommit history I had at one point in time that did break this.\n\nFixed locally, will wait a bit before sending out the next version.\n\nPatrick\n"},{"id":"543904","messageId":"b6b3448b-ae21-49ad-9c5f-e1a7ab8dad70@app.fastmail.com","threadId":"65666","inReplyTo":"ag_yUsOEO6AjT4Ky@pks.im","subject":"Re: [PATCH 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-05-22T09:05:04Z","receivedAt":"2026-05-22T09:05:26Z","isPatch":true,"body":"On Fri, May 22, 2026, at 08:06, Patrick Steinhardt wrote:\n>>[snip]\n>\n> That reads a bit better.\n>\n>>>[snip]\n>>\n>> Isn’t it more that “the upcoming changes *would have* broken” them if\n>> not for this change? This seems to refer to a an alternative commit\n>> history where this change does not exist?\n>\n> Grammar is hard :) But yeah, this of course refers to an alternative\n> commit history I had at one point in time that did break this.\n>\n> Fixed locally, will wait a bit before sending out the next version.\n\nThank you for considering my small input, as always.\n\nCheers.\n"},{"id":"544685","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im","subject":"[PATCH v3 0/8] setup: centralize object database creation","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:24Z","receivedAt":"2026-06-04T07:46:41Z","isPatch":true,"body":"Hi,\n\nthis small patch series refactors the logic for how we discover and\nconfigure repositories. Most importantly, this involves the following\ntwo steps:\n\n  1. We unify the logic to apply the repository format, which is\n     currently open-coded across multiple sites. These sites have\n     already diverged, where some repository extensions are not\n     consistently applied.\n\n  2. We then centralize creation of the object database to happen at the\n     same time we apply the repository format.\n\nThe end result is that we apply the repository format exactly once, and\nthat's also the point in time where we can finalize the setup of the\nrepo's data structures as we know about all details of the repo at that\ntime. Ultimately, this makes it trivial to introduce the \"objectStorage\"\nextension, even though that's not part of this patch series.\n\nThe series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)\nwith ps/setup-wo-the-repository at df69f40c34 (setup: stop using\n`the_repository` in `init_db()`, 2026-05-19) merged into it.\n\nChanges in v3:\n  - Explain the move of `verify_repository_format()` better.\n  - Document that `apply_repository_format()` also verifies the format.\n  - Link to v2: https://patch.msgid.link/20260526-b4-pks-setup-centralize-odb-creation-v2-0-2fa5b385c13e@pks.im\n\nChanges in v2:\n  - Commit message improvements.\n  - Link to v1: https://patch.msgid.link/20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (8):\n      t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY\n      setup: drop `setup_git_env()`\n      setup: deduplicate logic to apply repository format\n      repository: stop initializing the object database in `repo_set_gitdir()`\n      setup: stop creating the object database in `setup_git_env()`\n      setup: stop initializing object database without repository\n      repository: stop reading loose object map twice on repo init\n      setup: construct object database in `apply_repository_format()`\n\n commit-graph.c  |   4 +-\n environment.h   |   8 +---\n refs.c          |   3 +-\n repository.c    |  40 +++++------------\n repository.h    |   3 --\n setup.c         | 130 +++++++++++++++++++++++++++++++-------------------------\n setup.h         |  20 +++++++++\n t/t0001-init.sh |  10 +++++\n 8 files changed, 118 insertions(+), 100 deletions(-)\n\nRange-diff versus v2:\n\n1:  50224c1a12 = 1:  a6f452b947 t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY\n2:  6d655e00e3 = 2:  905e618dc6 setup: drop `setup_git_env()`\n3:  2e7e9bb052 ! 3:  e11f16333d setup: deduplicate logic to apply repository format\n    @@ Commit message\n     \n         Introduce a new function `apply_repository_format()` that takes a repo\n         and applies a given format to it and adapt all callsites to use it.\n    -    While at it, rename `check_repository_format()` to clarify that it\n    -    doesn't only _check_ the format, but that it also applies it.\n    +    This function is also the new caller of `verify_repository_format()` so\n    +    that we can ensure that we never apply an invalid repository format.\n    +    The verification we have in `read_and_verify_repository_format()` is\n    +    thus redundant now and dropped.\n    +\n    +    Rename `read_and_verify_repository_format()` accordingly. While at it,\n    +    also rename `check_repository_format()` to clarify that it doesn't only\n    +    _check_ the format, but that it also applies it.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    @@ setup.h: void clear_repository_format(struct repository_format *format);\n     +/*\n     + * Apply the given repository format to the repo. This initializes extensions\n     + * and basic data structures required for normal operation. Returns 0 on\n    -+ * success, a negative error code otherwise.\n    ++ * success, a negative error code when the format is not valid as determined by\n    ++ * `verify_repository_format()`.\n     + */\n     +int apply_repository_format(struct repository *repo,\n     +\t\t\t    const struct repository_format *format,\n4:  81b92bca7f = 4:  b0d7c11fe6 repository: stop initializing the object database in `repo_set_gitdir()`\n5:  807fc56353 = 5:  d0af56fdae setup: stop creating the object database in `setup_git_env()`\n6:  96563ff99f = 6:  3e75c5b0a6 setup: stop initializing object database without repository\n7:  c14f45169c = 7:  50fa2fdb3c repository: stop reading loose object map twice on repo init\n8:  e67c6e66d6 = 8:  4dff9d1794 setup: construct object database in `apply_repository_format()`\n\n---\nbase-commit: 3398daa441965513c48744305d33bd36404547d6\nchange-id: 20260519-b4-pks-setup-centralize-odb-creation-3479c53fb11d\n\n"},{"id":"544686","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-1-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:25Z","receivedAt":"2026-06-04T07:46:42Z","isPatch":true,"body":"In subsequent commits we'll rework how we set up the repository. This is\na somewhat intricate and thus fragile sequence; there's many things that\ncan go subtly wrong, and there are lots of interesting interactions that\none can discover.\n\nOne such discovered edge case was the interaction between git-init(1)\nand the \"GIT_OBJECT_DIRECTORY\" environment variable. When set, the\nbehaviour is that the object directory should be created at the path\nthat the variable points to. This behaviour is documented as such in\nits man page:\n\n  If the object storage directory is specified via the\n  GIT_OBJECT_DIRECTORY environment variable then the sha1 directories\n  are created underneath; otherwise, the default $GIT_DIR/objects\n  directory is used.\n\nCuriously enough though we don't seem to have any tests that exercise\nthis directly, and thus a subsequent commit inadvertently would have\nbroken this expectation.\n\nPlug this test gap.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t0001-init.sh | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex e4d32bb4d2..e89feca544 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -980,4 +980,14 @@ test_expect_success 're-init reads matching includeIf.onbranch' '\n \ttest_cmp expect err\n '\n \n+test_expect_success 'init honors GIT_OBJECT_DIRECTORY' '\n+\ttest_when_finished \"rm -rf init-objdir custom-odb\" &&\n+\tmkdir custom-odb &&\n+\tenv GIT_OBJECT_DIRECTORY=\"$(pwd)/custom-odb\" \\\n+\t\tgit init init-objdir &&\n+\ttest_path_is_missing init-objdir/.git/objects/pack &&\n+\ttest_path_is_dir custom-odb/pack &&\n+\ttest_path_is_dir custom-odb/info\n+'\n+\n test_done\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544687","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-2-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 2/8] setup: drop `setup_git_env()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:26Z","receivedAt":"2026-06-04T07:46:45Z","isPatch":true,"body":"The `setup_git_env()` function is a trivial wrapper around\n`setup_git_env_internal()` and has a single call site only. Drop the\nfunction.\n\nWhile at it, drop stale documentation in \"environment.h\" that points to\nthis function, even though it hasn't been exposed to callers outside of\n\"setup.c\" since 43ad1047a9 (setup: stop using `the_repository` in\n`setup_git_env()`, 2026-03-27) anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.h | 8 +-------\n refs.c        | 3 ++-\n setup.c       | 7 +------\n 3 files changed, 4 insertions(+), 14 deletions(-)\n\ndiff --git a/environment.h b/environment.h\nindex 9eb97b3869..ccfcf37bfb 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -130,13 +130,6 @@ void repo_config_values_init(struct repo_config_values *cfg);\n  * `the_repository`. We should eventually get rid of these and make the\n  * dependency on a repository explicit:\n  *\n- *   - `setup_git_env()` ideally shouldn't exist as it modifies global state,\n- *     namely the environment. The current process shouldn't ever access that\n- *     state via envvars though, but should instead consult a `struct\n- *     repository`. When spawning new processes, we would ideally also pass a\n- *     `struct repository` and then set up the environment variables for the\n- *     child process, only.\n- *\n  *   - `have_git_dir()` should not have to exist at all. Instead, we should\n  *     decide on whether or not we have a `struct repository`.\n  *\n@@ -147,6 +140,7 @@ void repo_config_values_init(struct repo_config_values *cfg);\n  * Please do not add new global config variables here.\n  */\n # ifdef USE_THE_REPOSITORY_VARIABLE\n+\n /*\n  * Returns true iff we have a configured git repository (either via\n  * setup_git_directory, or in the environment via $GIT_DIR).\ndiff --git a/refs.c b/refs.c\nindex 0f3355d2ee..e7070eb743 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -126,7 +126,8 @@ struct ref_namespace_info ref_namespace[] = {\n \t\t * points to the content of another. Unlike the other\n \t\t * ref namespaces, this one can be changed by the\n \t\t * GIT_REPLACE_REF_BASE environment variable. This\n-\t\t * .namespace value will be overwritten in setup_git_env().\n+\t\t * .namespace value will be overwritten during repository\n+\t\t * setup.\n \t\t */\n \t\t.ref = \"refs/replace/\",\n \t\t.decoration = DECORATION_GRAFTED,\ndiff --git a/setup.c b/setup.c\nindex d723306dfe..252b443117 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1074,11 +1074,6 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tfetch_if_missing = 0;\n }\n \n-static void setup_git_env(struct repository *repo, const char *git_dir)\n-{\n-\tsetup_git_env_internal(repo, git_dir, false);\n-}\n-\n static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)\n {\n \txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n@@ -2023,7 +2018,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\tif (!gitdir)\n \t\t\t\tgitdir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\t\t\tsetup_git_env(repo, gitdir);\n+\t\t\tsetup_git_env_internal(repo, gitdir, false);\n \t\t}\n \t\tif (startup_info->have_repository) {\n \t\t\trepo_set_hash_algo(repo, repo_fmt.hash_algo);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544688","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-3-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 3/8] setup: deduplicate logic to apply repository format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:27Z","receivedAt":"2026-06-04T07:46:47Z","isPatch":true,"body":"After having discovered the repository format we then apply it to the\nrepository so that it knows to use the proper repository extensions. The\nlogic to apply the format is duplicated across three callsites, which\nmakes it rather painfull to add new extensions.\n\nIntroduce a new function `apply_repository_format()` that takes a repo\nand applies a given format to it and adapt all callsites to use it.\nThis function is also the new caller of `verify_repository_format()` so\nthat we can ensure that we never apply an invalid repository format.\nThe verification we have in `read_and_verify_repository_format()` is\nthus redundant now and dropped.\n\nRename `read_and_verify_repository_format()` accordingly. While at it,\nalso rename `check_repository_format()` to clarify that it doesn't only\n_check_ the format, but that it also applies it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 31 +++++++-------------\n setup.c      | 93 ++++++++++++++++++++++++++++++++----------------------------\n setup.h      | 10 +++++++\n 3 files changed, 71 insertions(+), 63 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex db57b8308b..58a13f7c4f 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -262,8 +262,8 @@ void repo_set_worktree(struct repository *repo, const char *path)\n \ttrace2_def_repo(repo);\n }\n \n-static int read_and_verify_repository_format(struct repository_format *format,\n-\t\t\t\t\t     const char *commondir)\n+static int read_repository_format_from_commondir(struct repository_format *format,\n+\t\t\t\t\t\t const char *commondir)\n {\n \tint ret = 0;\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -272,11 +272,6 @@ static int read_and_verify_repository_format(struct repository_format *format,\n \tread_repository_format(format, sb.buf);\n \tstrbuf_reset(&sb);\n \n-\tif (verify_repository_format(format, &sb) < 0) {\n-\t\twarning(\"%s\", sb.buf);\n-\t\tret = -1;\n-\t}\n-\n \tstrbuf_release(&sb);\n \treturn ret;\n }\n@@ -290,6 +285,8 @@ int repo_init(struct repository *repo,\n \t      const char *worktree)\n {\n \tstruct repository_format format = REPOSITORY_FORMAT_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n \tmemset(repo, 0, sizeof(*repo));\n \n \tinitialize_repository(repo);\n@@ -297,21 +294,13 @@ int repo_init(struct repository *repo,\n \tif (repo_init_gitdir(repo, gitdir))\n \t\tgoto error;\n \n-\tif (read_and_verify_repository_format(&format, repo->commondir))\n+\tif (read_repository_format_from_commondir(&format, repo->commondir))\n \t\tgoto error;\n \n-\trepo_set_hash_algo(repo, format.hash_algo);\n-\trepo_set_compat_hash_algo(repo, format.compat_hash_algo);\n-\trepo_set_ref_storage_format(repo, format.ref_storage_format,\n-\t\t\t\t    format.ref_storage_payload);\n-\trepo->repository_format_worktree_config = format.worktree_config;\n-\trepo->repository_format_relative_worktrees = format.relative_worktrees;\n-\trepo->repository_format_precious_objects = format.precious_objects;\n-\trepo->repository_format_submodule_path_cfg = format.submodule_path_cfg;\n-\n-\t/* take ownership of format.partial_clone */\n-\trepo->repository_format_partial_clone = format.partial_clone;\n-\tformat.partial_clone = NULL;\n+\tif (apply_repository_format(repo, &format, &err) < 0) {\n+\t\twarning(\"%s\", err.buf);\n+\t\tgoto error;\n+\t}\n \n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n@@ -320,10 +309,12 @@ int repo_init(struct repository *repo,\n \t\trepo_read_loose_object_map(repo);\n \n \tclear_repository_format(&format);\n+\tstrbuf_release(&err);\n \treturn 0;\n \n error:\n \tclear_repository_format(&format);\n+\tstrbuf_release(&err);\n \trepo_clear(repo);\n \treturn -1;\n }\ndiff --git a/setup.c b/setup.c\nindex 252b443117..c5015923f1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -750,8 +750,7 @@ static int check_repo_format(const char *var, const char *value,\n \treturn read_worktree_config(var, value, ctx, vdata);\n }\n \n-static int check_repository_format_gently(struct repository *repo,\n-\t\t\t\t\t  const char *gitdir,\n+static int check_repository_format_gently(const char *gitdir,\n \t\t\t\t\t  struct repository_format *candidate,\n \t\t\t\t\t  int *nongit_ok)\n {\n@@ -765,7 +764,7 @@ static int check_repository_format_gently(struct repository *repo,\n \tstrbuf_release(&sb);\n \n \t/*\n-\t * For historical use of check_repository_format() in git-init,\n+\t * For historical use of check_and_apply_repository_format() in git-init,\n \t * we treat a missing config as a silent \"ok\", even when nongit_ok\n \t * is unset.\n \t */\n@@ -782,8 +781,6 @@ static int check_repository_format_gently(struct repository *repo,\n \t\tdie(\"%s\", err.buf);\n \t}\n \n-\trepo->repository_format_precious_objects = candidate->precious_objects;\n-\n \tstring_list_clear(&candidate->unknown_extensions, 0);\n \tstring_list_clear(&candidate->v1_only_extensions, 0);\n \n@@ -1140,7 +1137,7 @@ static const char *setup_explicit_git_dir(struct repository *repo,\n \t\tdie(_(\"not a git repository: '%s'\"), gitdirenv);\n \t}\n \n-\tif (check_repository_format_gently(repo, gitdirenv, repo_fmt, nongit_ok)) {\n+\tif (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) {\n \t\tfree(gitfile);\n \t\treturn NULL;\n \t}\n@@ -1217,7 +1214,7 @@ static const char *setup_discovered_git_dir(struct repository *repo,\n \t\t\t\t\t    struct repository_format *repo_fmt,\n \t\t\t\t\t    int *nongit_ok)\n {\n-\tif (check_repository_format_gently(repo, gitdir, repo_fmt, nongit_ok))\n+\tif (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))\n \t\treturn NULL;\n \n \t/* --work-tree is set without --git-dir; use discovered one */\n@@ -1265,7 +1262,7 @@ static const char *setup_bare_git_dir(struct repository *repo,\n {\n \tint root_len;\n \n-\tif (check_repository_format_gently(repo, \".\", repo_fmt, nongit_ok))\n+\tif (check_repository_format_gently(\".\", repo_fmt, nongit_ok))\n \t\treturn NULL;\n \n \tsetenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, \"0\", 1);\n@@ -1757,6 +1754,32 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n \treturn result;\n }\n \n+int apply_repository_format(struct repository *repo,\n+\t\t\t    const struct repository_format *format,\n+\t\t\t    struct strbuf *err)\n+{\n+\tif (verify_repository_format(format, err) < 0)\n+\t\treturn -1;\n+\n+\trepo_set_hash_algo(repo, format->hash_algo);\n+\trepo_set_compat_hash_algo(repo, format->compat_hash_algo);\n+\trepo_set_ref_storage_format(repo,\n+\t\t\t\t    format->ref_storage_format,\n+\t\t\t\t    format->ref_storage_payload);\n+\trepo->repository_format_worktree_config =\n+\t\tformat->worktree_config;\n+\trepo->repository_format_submodule_path_cfg =\n+\t\tformat->submodule_path_cfg;\n+\trepo->repository_format_relative_worktrees =\n+\t\tformat->relative_worktrees;\n+\trepo->repository_format_partial_clone =\n+\t\txstrdup_or_null(format->partial_clone);\n+\trepo->repository_format_precious_objects =\n+\t\tformat->precious_objects;\n+\n+\treturn 0;\n+}\n+\n /*\n  * Check the repository format version in the path found in repo_get_git_dir(repo),\n  * and die if it is a version we don't understand. Generally one would\n@@ -1765,26 +1788,20 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n  *\n  * If successful and fmt is not NULL, fill fmt with data.\n  */\n-static void check_repository_format(struct repository *repo, struct repository_format *fmt)\n+static void check_and_apply_repository_format(struct repository *repo,\n+\t\t\t\t\t      struct repository_format *fmt)\n {\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n \tif (!fmt)\n \t\tfmt = &repo_fmt;\n-\tcheck_repository_format_gently(repo, repo_get_git_dir(repo), fmt, NULL);\n+\n+\tcheck_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);\n+\tif (apply_repository_format(repo, fmt, &err) < 0)\n+\t\tdie(\"%s\", err.buf);\n \tstartup_info->have_repository = 1;\n-\trepo_set_hash_algo(repo, fmt->hash_algo);\n-\trepo_set_compat_hash_algo(repo, fmt->compat_hash_algo);\n-\trepo_set_ref_storage_format(repo,\n-\t\t\t\t    fmt->ref_storage_format,\n-\t\t\t\t    fmt->ref_storage_payload);\n-\trepo->repository_format_worktree_config =\n-\t\tfmt->worktree_config;\n-\trepo->repository_format_submodule_path_cfg =\n-\t\tfmt->submodule_path_cfg;\n-\trepo->repository_format_relative_worktrees =\n-\t\tfmt->relative_worktrees;\n-\trepo->repository_format_partial_clone =\n-\t\txstrdup_or_null(fmt->partial_clone);\n+\n \tclear_repository_format(&repo_fmt);\n }\n \n@@ -1862,7 +1879,7 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \n \tif (is_git_directory(\".\")) {\n \t\tset_git_dir(repo, \".\", 0);\n-\t\tcheck_repository_format(repo, NULL);\n+\t\tcheck_and_apply_repository_format(repo, NULL);\n \t\treturn path;\n \t}\n \n@@ -2020,25 +2037,15 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\t\tgitdir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \t\t\tsetup_git_env_internal(repo, gitdir, false);\n \t\t}\n+\n \t\tif (startup_info->have_repository) {\n-\t\t\trepo_set_hash_algo(repo, repo_fmt.hash_algo);\n-\t\t\trepo_set_compat_hash_algo(repo,\n-\t\t\t\t\t\t  repo_fmt.compat_hash_algo);\n-\t\t\trepo_set_ref_storage_format(repo,\n-\t\t\t\t\t\t    repo_fmt.ref_storage_format,\n-\t\t\t\t\t\t    repo_fmt.ref_storage_payload);\n-\t\t\trepo->repository_format_worktree_config =\n-\t\t\t\trepo_fmt.worktree_config;\n-\t\t\trepo->repository_format_relative_worktrees =\n-\t\t\t\trepo_fmt.relative_worktrees;\n-\t\t\trepo->repository_format_submodule_path_cfg =\n-\t\t\t\trepo_fmt.submodule_path_cfg;\n-\t\t\t/* take ownership of repo_fmt.partial_clone */\n-\t\t\trepo->repository_format_partial_clone =\n-\t\t\t\trepo_fmt.partial_clone;\n-\t\t\trepo_fmt.partial_clone = NULL;\n-\t\t\trepo->repository_format_precious_objects =\n-\t\t\t\trepo_fmt.precious_objects;\n+\t\t\tstruct strbuf err = STRBUF_INIT;\n+\n+\t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n+\t\t\t\tdie(\"%s\", err.buf);\n+\n+\t\t\tclear_repository_format(&repo_fmt);\n+\t\t\tstrbuf_release(&err);\n \t\t}\n \t}\n \t/*\n@@ -2814,7 +2821,7 @@ int init_db(struct repository *repo,\n \t * config file, so this will not fail.  What we are catching\n \t * is an attempt to reinitialize new repository with an old tool.\n \t */\n-\tcheck_repository_format(repo, &repo_fmt);\n+\tcheck_and_apply_repository_format(repo, &repo_fmt);\n \n \trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n \ndiff --git a/setup.h b/setup.h\nindex 9409326fe4..efbb82fdbf 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -221,6 +221,16 @@ void clear_repository_format(struct repository_format *format);\n int verify_repository_format(const struct repository_format *format,\n \t\t\t     struct strbuf *err);\n \n+/*\n+ * Apply the given repository format to the repo. This initializes extensions\n+ * and basic data structures required for normal operation. Returns 0 on\n+ * success, a negative error code when the format is not valid as determined by\n+ * `verify_repository_format()`.\n+ */\n+int apply_repository_format(struct repository *repo,\n+\t\t\t    const struct repository_format *format,\n+\t\t\t    struct strbuf *err);\n+\n const char *get_template_dir(const char *option_template);\n \n #define INIT_DB_QUIET      (1 << 0)\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544689","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-4-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 4/8] repository: stop initializing the object database in `repo_set_gitdir()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:28Z","receivedAt":"2026-06-04T07:46:49Z","isPatch":true,"body":"The function `repo_set_gitdir()` obviously sets the Git directory for a\ngiven repository. Less obviously though, the function also configures a\ncouple of auxiliary settings.\n\nOne such thing is that we create the object database in this function.\nThis logic only happens conditionally though, as `set_git_dir()` may be\ncalled multiple times during repository setup, and we don't want to\ncreate the object database multiple times. This is somewhat tangled and\nhard to follow.\n\nRemove the logic from `repo_set_gitdir()` and instead initialize the\nobject database outside of it. This leads to some duplication right now,\nbut that duplication will be removed in a subsequent step where we will\nstart initializing the object database as part of applying the repo's\nformat.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 8 ++------\n repository.h | 3 ---\n setup.c      | 7 ++++---\n 3 files changed, 6 insertions(+), 12 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex 58a13f7c4f..2c2395105f 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -181,12 +181,6 @@ void repo_set_gitdir(struct repository *repo,\n \tfree(old_gitdir);\n \n \trepo_set_commondir(repo, o->commondir);\n-\n-\tif (!repo->objects)\n-\t\trepo->objects = odb_new(repo, o->object_dir, o->alternate_db);\n-\telse if (!o->skip_initializing_odb)\n-\t\tBUG(\"cannot reinitialize an already-initialized object directory\");\n-\n \trepo->disable_ref_updates = o->disable_ref_updates;\n \n \texpand_base_dir(&repo->graft_file, o->graft_file,\n@@ -302,6 +296,8 @@ int repo_init(struct repository *repo,\n \t\tgoto error;\n \t}\n \n+\trepo->objects = odb_new(repo, NULL, NULL);\n+\n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n \ndiff --git a/repository.h b/repository.h\nindex c3ec0f4b79..36e2db2633 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -221,12 +221,9 @@ const char *repo_get_work_tree(struct repository *repo);\n  */\n struct set_gitdir_args {\n \tconst char *commondir;\n-\tconst char *object_dir;\n \tconst char *graft_file;\n \tconst char *index_file;\n-\tconst char *alternate_db;\n \tbool disable_ref_updates;\n-\tbool skip_initializing_odb;\n };\n \n void repo_set_gitdir(struct repository *repo, const char *root,\ndiff --git a/setup.c b/setup.c\nindex c5015923f1..3bd3f6c592 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1045,17 +1045,18 @@ static void setup_git_env_internal(struct repository *repo,\n \tstruct strvec to_free = STRVEC_INIT;\n \n \targs.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);\n-\targs.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);\n \targs.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);\n \targs.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);\n-\targs.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);\n \tif (getenv(GIT_QUARANTINE_ENVIRONMENT))\n \t\targs.disable_ref_updates = true;\n-\targs.skip_initializing_odb = skip_initializing_odb;\n \n \trepo_set_gitdir(repo, git_dir, &args);\n \tstrvec_clear(&to_free);\n \n+\tif (!skip_initializing_odb)\n+\t\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n+\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tdisable_replace_refs();\n \treplace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544691","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-5-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 5/8] setup: stop creating the object database in `setup_git_env()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:29Z","receivedAt":"2026-06-04T07:46:52Z","isPatch":true,"body":"In the preceding commit we have stopped creating the object database in\n`repo_set_gitdir()`. But the logic is still somewhat confusing as we\nstill end up creating it conditionally in `setup_git_dir()`, which is\ncalled multiple times.\n\nDrop the conditional logic and instead create the object database in all\nplaces where we have discovered and configured a repository.\n\nThis leads to even more duplication than we already had in the preceding\ncommit, but an alert reader may notice that we now (almost) always call\n`odb_new()` directly before having called `apply_repository_format()`.\nThe only exception to this is `setup_git_directory_gently()`, where we\nalso call the function when _not_ applying the repository format. This\nwill be fixed in the next commit, and once that's done we can then unify\ncreation of the object database into `apply_repository_format()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n setup.c | 37 ++++++++++++++++++++++++++-----------\n 1 file changed, 26 insertions(+), 11 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 3bd3f6c592..0dc9fe4565 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1035,8 +1035,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)\n }\n \n static void setup_git_env_internal(struct repository *repo,\n-\t\t\t\t   const char *git_dir,\n-\t\t\t\t   bool skip_initializing_odb)\n+\t\t\t\t   const char *git_dir)\n {\n \tchar *git_replace_ref_base;\n \tconst char *shallow_file;\n@@ -1053,10 +1052,6 @@ static void setup_git_env_internal(struct repository *repo,\n \trepo_set_gitdir(repo, git_dir, &args);\n \tstrvec_clear(&to_free);\n \n-\tif (!skip_initializing_odb)\n-\t\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\n \tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n \t\tdisable_replace_refs();\n \treplace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);\n@@ -1072,10 +1067,10 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tfetch_if_missing = 0;\n }\n \n-static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)\n+static void set_git_dir_1(struct repository *repo, const char *path)\n {\n \txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n-\tsetup_git_env_internal(repo, path, skip_initializing_odb);\n+\tsetup_git_env_internal(repo, path);\n }\n \n static void update_relative_gitdir(const char *name UNUSED,\n@@ -1089,7 +1084,7 @@ static void update_relative_gitdir(const char *name UNUSED,\n \ttrace_printf_key(&trace_setup_key,\n \t\t\t \"setup: move $GIT_DIR to '%s'\",\n \t\t\t path);\n-\tset_git_dir_1(repo, path, true);\n+\tset_git_dir_1(repo, path);\n \tfree(path);\n }\n \n@@ -1102,7 +1097,7 @@ static void set_git_dir(struct repository *repo, const char *path, int make_real\n \t\tpath = realpath.buf;\n \t}\n \n-\tset_git_dir_1(repo, path, false);\n+\tset_git_dir_1(repo, path);\n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, update_relative_gitdir, repo);\n \n@@ -1879,8 +1874,15 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \t}\n \n \tif (is_git_directory(\".\")) {\n+\t\tstruct strvec to_free = STRVEC_INIT;\n+\n \t\tset_git_dir(repo, \".\", 0);\n+\t\trepo->objects = odb_new(repo,\n+\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n \t\tcheck_and_apply_repository_format(repo, NULL);\n+\n+\t\tstrvec_clear(&to_free);\n \t\treturn path;\n \t}\n \n@@ -2032,13 +2034,19 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t    startup_info->have_repository ||\n \t    /* GIT_DIR_EXPLICIT */\n \t    getenv(GIT_DIR_ENVIRONMENT)) {\n+\t\tstruct strvec to_free = STRVEC_INIT;\n+\n \t\tif (!repo->gitdir) {\n \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\tif (!gitdir)\n \t\t\t\tgitdir = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\t\t\tsetup_git_env_internal(repo, gitdir, false);\n+\t\t\tsetup_git_env_internal(repo, gitdir);\n \t\t}\n \n+\t\trepo->objects = odb_new(repo,\n+\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n+\n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n \n@@ -2048,6 +2056,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\tclear_repository_format(&repo_fmt);\n \t\t\tstrbuf_release(&err);\n \t\t}\n+\n+\t\tstrvec_clear(&to_free);\n \t}\n \t/*\n \t * Since precompose_string_if_needed() needs to look at\n@@ -2796,6 +2806,7 @@ int init_db(struct repository *repo,\n \tint exist_ok = flags & INIT_DB_EXIST_OK;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \n \tif (real_git_dir) {\n \t\tstruct stat st;\n@@ -2816,6 +2827,9 @@ int init_db(struct repository *repo,\n \t}\n \tstartup_info->have_repository = 1;\n \n+\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n+\n \t/*\n \t * Check to see if the repository version is right.\n \t * Note that a newly created repository does not have\n@@ -2879,6 +2893,7 @@ int init_db(struct repository *repo,\n \t}\n \n \tclear_repository_format(&repo_fmt);\n+\tstrvec_clear(&to_free);\n \tfree(original_git_dir);\n \treturn 0;\n }\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544692","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-6-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 6/8] setup: stop initializing object database without repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:30Z","receivedAt":"2026-06-04T07:46:54Z","isPatch":true,"body":"The function `setup_git_directory_gently()` is responsible for\ndiscovering and setting up a Git repository based on various environment\nvariables and the current working directory. The result is thus a fully\nusable Git repository.\n\nOne oddity of this function is that we may set up the object database\neven in the case where we don't have a repository, namely in the case\nwhere the `GIT_DIR_EXPLICIT` environment variable is set but points to a\nnon-existent repository. If so, we call `setup_git_env_internal()` with\nthe value of the environment variable so that the repository's Git\ndirectory is configured, even if it points to a non-existent directory.\n\nHistorically though, this function didn't only configure the repository,\nbut also initialized the object database. We retained this behaviour\nfrom a preceding commit, even though it really doesn't make much sense\nin the first place -- there is no repository, so we don't have an object\ndatabase either. There seemingly isn't much of a reason to construct the\nobject database, as we typically won't try to read objects when we don't\nhave an object database.\n\nThere's one exception though: git-index-pack(1) may run outside of a\nrepository, which can be used to perform consistency checks for a\npackfile. The code path is _almost_ working: we already know to call\n`parse_object_buffer()`, which can read objects without an object\ndatabase being available. And that works for all object types except for\ncommits, because `parse_commit_buffer()` calls `parse_commit_graph()`,\nand that function doesn't handle the case where we don't have an object\ndatabase.\n\nFix this instance to check for the object database instead of checking\nfor the Git directory having been initialized. With this fixed, we can\nnow stop constructing an object database completely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n commit-graph.c | 4 ++--\n setup.c        | 7 +++----\n 2 files changed, 5 insertions(+), 6 deletions(-)\n\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 9abe62bd5a..0820cf5fb8 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -740,13 +740,13 @@ static struct commit_graph *prepare_commit_graph(struct repository *r)\n \tstruct odb_source *source;\n \n \t/*\n-\t * Early return if there is no git dir or if the commit graph is\n+\t * Early return if there is no object database or if the commit graph is\n \t * disabled.\n \t *\n \t * This must come before the \"already attempted?\" check below, because\n \t * we want to disable even an already-loaded graph file.\n \t */\n-\tif (!r->gitdir || r->commit_graph_disabled)\n+\tif (!r->objects || r->commit_graph_disabled)\n \t\treturn NULL;\n \n \tif (r->objects->commit_graph_attempted)\ndiff --git a/setup.c b/setup.c\nindex 0dc9fe4565..4a8d6230b1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2043,13 +2043,12 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\t\tsetup_git_env_internal(repo, gitdir);\n \t\t}\n \n-\t\trepo->objects = odb_new(repo,\n-\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n \n+\t\t\trepo->objects = odb_new(repo,\n+\t\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n+\t\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n \t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n \t\t\t\tdie(\"%s\", err.buf);\n \n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544693","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-7-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 7/8] repository: stop reading loose object map twice on repo init","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:31Z","receivedAt":"2026-06-04T07:46:58Z","isPatch":true,"body":"When initializing a repository via `repo_init()` we end up reading the\nloose object map twice:\n\n  - `apply_repository_format()` calls `repo_set_compat_hash_algo()`,\n    which in turn calls `repo_read_loose_object_map()` if we have a\n    compatibility hash configured.\n\n  - `repo_init()` calls `repo_read_loose_object_map()` directly a second\n    time.\n\nDrop the second read of the loose object map in `repo_init()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 3 ---\n 1 file changed, 3 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex 2c2395105f..61dfbb8be6 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -301,9 +301,6 @@ int repo_init(struct repository *repo,\n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n \n-\tif (repo->compat_hash_algo)\n-\t\trepo_read_loose_object_map(repo);\n-\n \tclear_repository_format(&format);\n \tstrbuf_release(&err);\n \treturn 0;\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544694","messageId":"20260604-b4-pks-setup-centralize-odb-creation-v3-8-0691834f318a@pks.im","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"[PATCH v3 8/8] setup: construct object database in `apply_repository_format()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T07:46:32Z","receivedAt":"2026-06-04T07:47:00Z","isPatch":true,"body":"With the preceding changes we now always construct the repository's\nobject database before applying the repository format. Remove this\nduplication by constructing it in `apply_repository_format()` instead.\n\nNote that we create the object database _after_ having set up the\nrepository's hash algorithm, but _before_ setting the compat hash\nalgorithm. This is intentional:\n\n  - Constructing the object database may require knowledge of its\n    intended object format.\n\n  - Setting up the compatibility hash requires the object database to be\n    initialized already, because we immediately read the loose object\n    map.\n\nThe first point is sensible, the second maybe a little less so. Ideally,\nit should be the responsibility of the object database itself to\ninitialize any data structures required for the compatibility hash. But\nthis would require further changes, so this is kept as-is for now.\n\nFurther note that this requires us to move handling of the environment\nvariables GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES into\nthe repository format, as well. This allows the caller more flexibility\naround whether or not those environment variables are being honored, as\nwe want to respect them in \"setup.c\", but not in \"repository.c\".\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c |  4 +---\n setup.c      | 45 +++++++++++++++++++++------------------------\n setup.h      | 10 ++++++++++\n 3 files changed, 32 insertions(+), 27 deletions(-)\n\ndiff --git a/repository.c b/repository.c\nindex 61dfbb8be6..187dd471c4 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -291,13 +291,11 @@ int repo_init(struct repository *repo,\n \tif (read_repository_format_from_commondir(&format, repo->commondir))\n \t\tgoto error;\n \n-\tif (apply_repository_format(repo, &format, &err) < 0) {\n+\tif (apply_repository_format(repo, &format, 0, &err) < 0) {\n \t\twarning(\"%s\", err.buf);\n \t\tgoto error;\n \t}\n \n-\trepo->objects = odb_new(repo, NULL, NULL);\n-\n \tif (worktree)\n \t\trepo_set_worktree(repo, worktree);\n \ndiff --git a/setup.c b/setup.c\nindex 4a8d6230b1..513fc88749 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1752,12 +1752,22 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n \n int apply_repository_format(struct repository *repo,\n \t\t\t    const struct repository_format *format,\n+\t\t\t    enum apply_repository_format_flags flags,\n \t\t\t    struct strbuf *err)\n {\n+\tchar *object_directory = NULL, *alternate_object_directories = NULL;\n+\n \tif (verify_repository_format(format, err) < 0)\n \t\treturn -1;\n \n+\tif (flags & APPLY_REPOSITORY_FORMAT_HONOR_ENV) {\n+\t\tobject_directory = xstrdup_or_null(getenv(DB_ENVIRONMENT));\n+\t\talternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));\n+\t}\n+\n \trepo_set_hash_algo(repo, format->hash_algo);\n+\trepo->objects = odb_new(repo, object_directory,\n+\t\t\t\talternate_object_directories);\n \trepo_set_compat_hash_algo(repo, format->compat_hash_algo);\n \trepo_set_ref_storage_format(repo,\n \t\t\t\t    format->ref_storage_format,\n@@ -1773,6 +1783,8 @@ int apply_repository_format(struct repository *repo,\n \trepo->repository_format_precious_objects =\n \t\tformat->precious_objects;\n \n+\tfree(alternate_object_directories);\n+\tfree(object_directory);\n \treturn 0;\n }\n \n@@ -1785,7 +1797,8 @@ int apply_repository_format(struct repository *repo,\n  * If successful and fmt is not NULL, fill fmt with data.\n  */\n static void check_and_apply_repository_format(struct repository *repo,\n-\t\t\t\t\t      struct repository_format *fmt)\n+\t\t\t\t\t      struct repository_format *fmt,\n+\t\t\t\t\t      enum apply_repository_format_flags flags)\n {\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -1794,7 +1807,7 @@ static void check_and_apply_repository_format(struct repository *repo,\n \t\tfmt = &repo_fmt;\n \n \tcheck_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);\n-\tif (apply_repository_format(repo, fmt, &err) < 0)\n+\tif (apply_repository_format(repo, fmt, flags, &err) < 0)\n \t\tdie(\"%s\", err.buf);\n \tstartup_info->have_repository = 1;\n \n@@ -1874,15 +1887,9 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \t}\n \n \tif (is_git_directory(\".\")) {\n-\t\tstruct strvec to_free = STRVEC_INIT;\n-\n \t\tset_git_dir(repo, \".\", 0);\n-\t\trepo->objects = odb_new(repo,\n-\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\t\tcheck_and_apply_repository_format(repo, NULL);\n-\n-\t\tstrvec_clear(&to_free);\n+\t\tcheck_and_apply_repository_format(repo, NULL,\n+\t\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n \t\treturn path;\n \t}\n \n@@ -2034,8 +2041,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t    startup_info->have_repository ||\n \t    /* GIT_DIR_EXPLICIT */\n \t    getenv(GIT_DIR_ENVIRONMENT)) {\n-\t\tstruct strvec to_free = STRVEC_INIT;\n-\n \t\tif (!repo->gitdir) {\n \t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\tif (!gitdir)\n@@ -2046,17 +2051,13 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n \n-\t\t\trepo->objects = odb_new(repo,\n-\t\t\t\t\t\tgetenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\t\t\tif (apply_repository_format(repo, &repo_fmt, &err) < 0)\n+\t\t\tif (apply_repository_format(repo, &repo_fmt,\n+\t\t\t\t\t\t    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)\n \t\t\t\tdie(\"%s\", err.buf);\n \n \t\t\tclear_repository_format(&repo_fmt);\n \t\t\tstrbuf_release(&err);\n \t\t}\n-\n-\t\tstrvec_clear(&to_free);\n \t}\n \t/*\n \t * Since precompose_string_if_needed() needs to look at\n@@ -2805,7 +2806,6 @@ int init_db(struct repository *repo,\n \tint exist_ok = flags & INIT_DB_EXIST_OK;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n-\tstruct strvec to_free = STRVEC_INIT;\n \n \tif (real_git_dir) {\n \t\tstruct stat st;\n@@ -2826,16 +2826,14 @@ int init_db(struct repository *repo,\n \t}\n \tstartup_info->have_repository = 1;\n \n-\trepo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),\n-\t\t\t\tgetenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));\n-\n \t/*\n \t * Check to see if the repository version is right.\n \t * Note that a newly created repository does not have\n \t * config file, so this will not fail.  What we are catching\n \t * is an attempt to reinitialize new repository with an old tool.\n \t */\n-\tcheck_and_apply_repository_format(repo, &repo_fmt);\n+\tcheck_and_apply_repository_format(repo, &repo_fmt,\n+\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n \n \trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n \n@@ -2892,7 +2890,6 @@ int init_db(struct repository *repo,\n \t}\n \n \tclear_repository_format(&repo_fmt);\n-\tstrvec_clear(&to_free);\n \tfree(original_git_dir);\n \treturn 0;\n }\ndiff --git a/setup.h b/setup.h\nindex efbb82fdbf..19679fe78f 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);\n int verify_repository_format(const struct repository_format *format,\n \t\t\t     struct strbuf *err);\n \n+enum apply_repository_format_flags {\n+\t/*\n+\t * Honor environment variables when applying the repository format to\n+\t * the repository. For now, this only covers environment variables that\n+\t * relate to the object database.\n+\t */\n+\tAPPLY_REPOSITORY_FORMAT_HONOR_ENV = (1 << 0),\n+};\n+\n /*\n  * Apply the given repository format to the repo. This initializes extensions\n  * and basic data structures required for normal operation. Returns 0 on\n@@ -229,6 +238,7 @@ int verify_repository_format(const struct repository_format *format,\n  */\n int apply_repository_format(struct repository *repo,\n \t\t\t    const struct repository_format *format,\n+\t\t\t    enum apply_repository_format_flags flags,\n \t\t\t    struct strbuf *err);\n \n const char *get_template_dir(const char *option_template);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544791","messageId":"CAOLa=ZQwVbLsOcajaxQwtkTPm=4St7EiGEEyL6_B0o3Tt1v1pw@mail.gmail.com","threadId":"65666","inReplyTo":"20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im","subject":"Re: [PATCH v3 0/8] setup: centralize object database creation","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-05T14:16:00Z","receivedAt":"2026-06-05T14:16:03Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this small patch series refactors the logic for how we discover and\n> configure repositories. Most importantly, this involves the following\n> two steps:\n>\n>   1. We unify the logic to apply the repository format, which is\n>      currently open-coded across multiple sites. These sites have\n>      already diverged, where some repository extensions are not\n>      consistently applied.\n>\n>   2. We then centralize creation of the object database to happen at the\n>      same time we apply the repository format.\n>\n> The end result is that we apply the repository format exactly once, and\n> that's also the point in time where we can finalize the setup of the\n> repo's data structures as we know about all details of the repo at that\n> time. Ultimately, this makes it trivial to introduce the \"objectStorage\"\n> extension, even though that's not part of this patch series.\n>\n> The series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)\n> with ps/setup-wo-the-repository at df69f40c34 (setup: stop using\n> `the_repository` in `init_db()`, 2026-05-19) merged into it.\n>\n> Changes in v3:\n>   - Explain the move of `verify_repository_format()` better.\n>   - Document that `apply_repository_format()` also verifies the format.\n>   - Link to v2: https://patch.msgid.link/20260526-b4-pks-setup-centralize-odb-creation-v2-0-2fa5b385c13e@pks.im\n>\n> Changes in v2:\n>   - Commit message improvements.\n>   - Link to v1: https://patch.msgid.link/20260521-b4-pks-setup-centralize-odb-creation-v1-0-f130d2a7e8ae@pks.im\n>\n> Thanks!\n>\n> Patrick\n>\n> ---\n> Patrick Steinhardt (8):\n>       t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY\n>       setup: drop `setup_git_env()`\n>       setup: deduplicate logic to apply repository format\n>       repository: stop initializing the object database in `repo_set_gitdir()`\n>       setup: stop creating the object database in `setup_git_env()`\n>       setup: stop initializing object database without repository\n>       repository: stop reading loose object map twice on repo init\n>       setup: construct object database in `apply_repository_format()`\n>\n>  commit-graph.c  |   4 +-\n>  environment.h   |   8 +---\n>  refs.c          |   3 +-\n>  repository.c    |  40 +++++------------\n>  repository.h    |   3 --\n>  setup.c         | 130 +++++++++++++++++++++++++++++++-------------------------\n>  setup.h         |  20 +++++++++\n>  t/t0001-init.sh |  10 +++++\n>  8 files changed, 118 insertions(+), 100 deletions(-)\n>\n> Range-diff versus v2:\n>\n> 1:  50224c1a12 = 1:  a6f452b947 t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY\n> 2:  6d655e00e3 = 2:  905e618dc6 setup: drop `setup_git_env()`\n> 3:  2e7e9bb052 ! 3:  e11f16333d setup: deduplicate logic to apply repository format\n>     @@ Commit message\n>\n>          Introduce a new function `apply_repository_format()` that takes a repo\n>          and applies a given format to it and adapt all callsites to use it.\n>     -    While at it, rename `check_repository_format()` to clarify that it\n>     -    doesn't only _check_ the format, but that it also applies it.\n>     +    This function is also the new caller of `verify_repository_format()` so\n>     +    that we can ensure that we never apply an invalid repository format.\n>     +    The verification we have in `read_and_verify_repository_format()` is\n>     +    thus redundant now and dropped.\n>     +\n>     +    Rename `read_and_verify_repository_format()` accordingly. While at it,\n>     +    also rename `check_repository_format()` to clarify that it doesn't only\n>     +    _check_ the format, but that it also applies it.\n>\n>          Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>\n>     @@ setup.h: void clear_repository_format(struct repository_format *format);\n>      +/*\n>      + * Apply the given repository format to the repo. This initializes extensions\n>      + * and basic data structures required for normal operation. Returns 0 on\n>     -+ * success, a negative error code otherwise.\n>     ++ * success, a negative error code when the format is not valid as determined by\n>     ++ * `verify_repository_format()`.\n>      + */\n>      +int apply_repository_format(struct repository *repo,\n>      +\t\t\t    const struct repository_format *format,\n> 4:  81b92bca7f = 4:  b0d7c11fe6 repository: stop initializing the object database in `repo_set_gitdir()`\n> 5:  807fc56353 = 5:  d0af56fdae setup: stop creating the object database in `setup_git_env()`\n> 6:  96563ff99f = 6:  3e75c5b0a6 setup: stop initializing object database without repository\n> 7:  c14f45169c = 7:  50fa2fdb3c repository: stop reading loose object map twice on repo init\n> 8:  e67c6e66d6 = 8:  4dff9d1794 setup: construct object database in `apply_repository_format()`\n>\n\nThe range-diff looks good and as expected. Thanks!\n"},{"id":"544910","messageId":"xmqqzf15z16e.fsf@gitster.g","threadId":"65666","inReplyTo":"CAOLa=ZQwVbLsOcajaxQwtkTPm=4St7EiGEEyL6_B0o3Tt1v1pw@mail.gmail.com","subject":"Re: [PATCH v3 0/8] setup: centralize object database creation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-08T12:06:33Z","receivedAt":"2026-06-08T12:06:35Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n>> Hi,\n>>\n>> this small patch series refactors the logic for how we discover and\n>> configure repositories. Most importantly, this involves the following\n>> two steps:\n>>\n>>   1. We unify the logic to apply the repository format, which is\n>>      currently open-coded across multiple sites. These sites have\n>>      already diverged, where some repository extensions are not\n>>      consistently applied.\n>>\n>>   2. We then centralize creation of the object database to happen at the\n>>      same time we apply the repository format.\n>>\n>> The end result is that we apply the repository format exactly once, and\n>> that's also the point in time where we can finalize the setup of the\n>> repo's data structures as we know about all details of the repo at that\n>> time. Ultimately, this makes it trivial to introduce the \"objectStorage\"\n>> extension, even though that's not part of this patch series.\n>> ...\n>> 4:  81b92bca7f = 4:  b0d7c11fe6 repository: stop initializing the object database in `repo_set_gitdir()`\n>> 5:  807fc56353 = 5:  d0af56fdae setup: stop creating the object database in `setup_git_env()`\n>> 6:  96563ff99f = 6:  3e75c5b0a6 setup: stop initializing object database without repository\n>> 7:  c14f45169c = 7:  50fa2fdb3c repository: stop reading loose object map twice on repo init\n>> 8:  e67c6e66d6 = 8:  4dff9d1794 setup: construct object database in `apply_repository_format()`\n>>\n>\n> The range-diff looks good and as expected. Thanks!\n\nThanks, both of you.  Let me mark the topic for 'next', then.\n"}]}