{"thread":{"id":"64510","subject":"[PATCH 00/13] Centralize management of object database sources","startedAt":"2025-11-19T07:50:59Z","lastAt":"2025-11-21T08:12:55Z","messageCount":21,"participants":["Patrick Steinhardt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":13},"messages":[{"id":"530969","messageId":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":null,"subject":"[PATCH 00/13] Centralize management of object database sources","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:48Z","receivedAt":"2025-11-19T07:50:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\ncurrently, the creation of the object databases is handled both by\n\"setup.c\" and by \"odb.c\". While it's expected that \"setup.c\" is the one\nsetting up the object database itself, what's less so is that there is a\nshared responsibility for managing its sources:\n\n  - The primary object database source gets created in \"setup.c\".\n\n  - The temporary source used during ODB transactions is handled by\n    \"setup.c\" when changing the current working directory.\n\n  - Relative paths stored in object database sources get updated by\n    \"setup.c\" when changing the current working directory.\n\nThis means that the management of ODB sources is somewhat cluttered and\nthus hard to understand. Furthermore, it has the consequence that\n\"setup.c\" reaches into internals of the ODB that really shouldn't be any\nof its concern.\n\nThis patch series cleans that up and moves all handling of ODB sources\ninto \"odb.c\". This hopefully makes the logic easier to understand and it\nwill allow us to eventually handle the logic for different backends in a\nsingle central location.\n\nThe series is structured as follows:\n\n  - Patches 1 to 5 clean up some smaller nuisances in the vicinity.\n\n  - Patches 6 to 9 refactor a couple of callsites that play weird games\n    with the object database. These cause us to re-initialize the ODB\n    multiple times, which will not be allowed anymore at the end of this\n    series.\n\n  - Patches 10 to 13 move the logic that manages object sources from\n    \"setup.c\" into \"odb.c\".\n\nThis series is built on top of v2.52.0 with ps/object-source-loose at\n3e5e360888 (object-file: refactor writing objects via a stream,\n2025-11-03) merged into it.\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (13):\n      path: move `enter_repo()` into \"setup.c\"\n      setup: convert `set_git_dir()` to have file scope\n      odb: adopt logic to close object databases\n      odb: refactor `odb_clear()` to `odb_free()`\n      odb: move logic to disable ref updates into repo\n      oidset: introduce `oidset_equal()`\n      builtin/index-pack: fix deferred fsck outside repos\n      t/helper: stop setting up `the_repository` repeatedly\n      http-push: stop setting up `the_repository` for each reference\n      odb: handle initialization of sources in `odb_new()`\n      chdir-notify: add function to unregister listeners\n      odb: handle changing a repository's commondir\n      odb: handle recreation of quarantine directories\n\n builtin/clone.c            |   2 +-\n builtin/gc.c               |   2 +-\n builtin/index-pack.c       |  21 ++++-\n builtin/receive-pack.c     |   2 +-\n builtin/repack.c           |   2 +-\n builtin/upload-archive.c   |   2 +-\n builtin/upload-pack.c      |   2 +-\n chdir-notify.c             |  18 ++++\n chdir-notify.h             |   2 +\n fsck.c                     |   6 ++\n fsck.h                     |   7 ++\n http-backend.c             |   1 +\n http-push.c                |   5 +-\n midx-write.c               |   2 +-\n odb.c                      |  98 +++++++++++++++++----\n odb.h                      |  37 +++++---\n oidset.c                   |  16 ++++\n oidset.h                   |   9 +-\n packfile.c                 |  15 ----\n packfile.h                 |   1 -\n path.c                     | 100 ---------------------\n path.h                     |  15 ----\n refs.c                     |   2 +-\n repository.c               |  27 ++----\n repository.h               |  10 ++-\n run-command.c              |   2 +-\n scalar.c                   |   2 +-\n setup.c                    | 214 +++++++++++++++++++++++++++++++--------------\n setup.h                    |  39 ++++++++-\n t/helper/test-repository.c |  16 +---\n t/t5302-pack-index.sh      |  16 ++++\n 31 files changed, 414 insertions(+), 279 deletions(-)\n\n\n---\nbase-commit: 0ce8ad0c2b447fea8e7abd0236367a9f38ce92fe\nchange-id: 20251107-b4-pks-odb-creation-96c18fdab1d2\n\n"},{"id":"530970","messageId":"20251119-b4-pks-odb-creation-v1-1-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 01/13] path: move `enter_repo()` into \"setup.c\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:49Z","receivedAt":"2025-11-19T07:51:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `enter_repo()` is used to enter a repository at a given\npath. As such it sits way closer to setting up a repository than it does\nwith handling paths, but regardless of that it's located in \"path.c\"\ninstead of in \"setup.c\".\n\nMove the function into \"setup.c\".\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/receive-pack.c   |   2 +-\n builtin/upload-archive.c |   2 +-\n builtin/upload-pack.c    |   2 +-\n http-backend.c           |   1 +\n path.c                   | 100 -----------------------------------------------\n path.h                   |  15 -------\n setup.c                  |  81 ++++++++++++++++++++++++++++++++++++++\n setup.h                  |  38 ++++++++++++++++++\n 8 files changed, 123 insertions(+), 118 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c9288a9c7e..79a0fd4756 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -34,7 +34,6 @@\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n-#include \"path.h\"\n #include \"protocol.h\"\n #include \"commit-reach.h\"\n #include \"server-info.h\"\n@@ -42,6 +41,7 @@\n #include \"trace2.h\"\n #include \"worktree.h\"\n #include \"shallow.h\"\n+#include \"setup.h\"\n #include \"parse-options.h\"\n \n static const char * const receive_pack_usage[] = {\ndiff --git a/builtin/upload-archive.c b/builtin/upload-archive.c\nindex 97d7c9522f..25312bb2a5 100644\n--- a/builtin/upload-archive.c\n+++ b/builtin/upload-archive.c\n@@ -4,8 +4,8 @@\n #define USE_THE_REPOSITORY_VARIABLE\n #include \"builtin.h\"\n #include \"archive.h\"\n-#include \"path.h\"\n #include \"pkt-line.h\"\n+#include \"setup.h\"\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"strvec.h\"\ndiff --git a/builtin/upload-pack.c b/builtin/upload-pack.c\nindex c2bbc035ab..30498fafea 100644\n--- a/builtin/upload-pack.c\n+++ b/builtin/upload-pack.c\n@@ -5,11 +5,11 @@\n #include \"gettext.h\"\n #include \"pkt-line.h\"\n #include \"parse-options.h\"\n-#include \"path.h\"\n #include \"protocol.h\"\n #include \"replace-object.h\"\n #include \"upload-pack.h\"\n #include \"serve.h\"\n+#include \"setup.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 52f0483dd3..e9d1ef92bd 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -16,6 +16,7 @@\n #include \"run-command.h\"\n #include \"string-list.h\"\n #include \"url.h\"\n+#include \"setup.h\"\n #include \"strvec.h\"\n #include \"packfile.h\"\n #include \"odb.h\"\ndiff --git a/path.c b/path.c\nindex 7f56eaf993..d726537622 100644\n--- a/path.c\n+++ b/path.c\n@@ -738,106 +738,6 @@ char *interpolate_path(const char *path, int real_home)\n \treturn NULL;\n }\n \n-/*\n- * First, one directory to try is determined by the following algorithm.\n- *\n- * (0) If \"strict\" is given, the path is used as given and no DWIM is\n- *     done. Otherwise:\n- * (1) \"~/path\" to mean path under the running user's home directory;\n- * (2) \"~user/path\" to mean path under named user's home directory;\n- * (3) \"relative/path\" to mean cwd relative directory; or\n- * (4) \"/absolute/path\" to mean absolute directory.\n- *\n- * Unless \"strict\" is given, we check \"%s/.git\", \"%s\", \"%s.git/.git\", \"%s.git\"\n- * in this order. We select the first one that is a valid git repository, and\n- * chdir() to it. If none match, or we fail to chdir, we return NULL.\n- *\n- * If all goes well, we return the directory we used to chdir() (but\n- * before ~user is expanded), avoiding getcwd() resolving symbolic\n- * links.  User relative paths are also returned as they are given,\n- * except DWIM suffixing.\n- */\n-const char *enter_repo(const char *path, unsigned flags)\n-{\n-\tstatic struct strbuf validated_path = STRBUF_INIT;\n-\tstatic struct strbuf used_path = STRBUF_INIT;\n-\n-\tif (!path)\n-\t\treturn NULL;\n-\n-\tif (!(flags & ENTER_REPO_STRICT)) {\n-\t\tstatic const char *suffix[] = {\n-\t\t\t\"/.git\", \"\", \".git/.git\", \".git\", NULL,\n-\t\t};\n-\t\tconst char *gitfile;\n-\t\tint len = strlen(path);\n-\t\tint i;\n-\t\twhile ((1 < len) && (path[len-1] == '/'))\n-\t\t\tlen--;\n-\n-\t\t/*\n-\t\t * We can handle arbitrary-sized buffers, but this remains as a\n-\t\t * sanity check on untrusted input.\n-\t\t */\n-\t\tif (PATH_MAX <= len)\n-\t\t\treturn NULL;\n-\n-\t\tstrbuf_reset(&used_path);\n-\t\tstrbuf_reset(&validated_path);\n-\t\tstrbuf_add(&used_path, path, len);\n-\t\tstrbuf_add(&validated_path, path, len);\n-\n-\t\tif (used_path.buf[0] == '~') {\n-\t\t\tchar *newpath = interpolate_path(used_path.buf, 0);\n-\t\t\tif (!newpath)\n-\t\t\t\treturn NULL;\n-\t\t\tstrbuf_attach(&used_path, newpath, strlen(newpath),\n-\t\t\t\t      strlen(newpath));\n-\t\t}\n-\t\tfor (i = 0; suffix[i]; i++) {\n-\t\t\tstruct stat st;\n-\t\t\tsize_t baselen = used_path.len;\n-\t\t\tstrbuf_addstr(&used_path, suffix[i]);\n-\t\t\tif (!stat(used_path.buf, &st) &&\n-\t\t\t    (S_ISREG(st.st_mode) ||\n-\t\t\t    (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {\n-\t\t\t\tstrbuf_addstr(&validated_path, suffix[i]);\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tstrbuf_setlen(&used_path, baselen);\n-\t\t}\n-\t\tif (!suffix[i])\n-\t\t\treturn NULL;\n-\t\tgitfile = read_gitfile(used_path.buf);\n-\t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, used_path.buf);\n-\t\tif (gitfile) {\n-\t\t\tstrbuf_reset(&used_path);\n-\t\t\tstrbuf_addstr(&used_path, gitfile);\n-\t\t}\n-\t\tif (chdir(used_path.buf))\n-\t\t\treturn NULL;\n-\t\tpath = validated_path.buf;\n-\t}\n-\telse {\n-\t\tconst char *gitfile = read_gitfile(path);\n-\t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, path);\n-\t\tif (gitfile)\n-\t\t\tpath = gitfile;\n-\t\tif (chdir(path))\n-\t\t\treturn NULL;\n-\t}\n-\n-\tif (is_git_directory(\".\")) {\n-\t\tset_git_dir(\".\", 0);\n-\t\tcheck_repository_format(NULL);\n-\t\treturn path;\n-\t}\n-\n-\treturn NULL;\n-}\n-\n int calc_shared_perm(struct repository *repo,\n \t\t     int mode)\n {\ndiff --git a/path.h b/path.h\nindex e67348f253..0ec95a0b07 100644\n--- a/path.h\n+++ b/path.h\n@@ -146,21 +146,6 @@ int adjust_shared_perm(struct repository *repo, const char *path);\n \n char *interpolate_path(const char *path, int real_home);\n \n-/* The bits are as follows:\n- *\n- * - ENTER_REPO_STRICT: callers that require exact paths (as opposed\n- *   to allowing known suffixes like \".git\", \".git/.git\" to be\n- *   omitted) can set this bit.\n- *\n- * - ENTER_REPO_ANY_OWNER_OK: callers that are willing to run without\n- *   ownership check can set this bit.\n- */\n-enum {\n-\tENTER_REPO_STRICT = (1<<0),\n-\tENTER_REPO_ANY_OWNER_OK = (1<<1),\n-};\n-\n-const char *enter_repo(const char *path, unsigned flags);\n const char *remove_leading_path(const char *in, const char *prefix);\n const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);\n int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);\ndiff --git a/setup.c b/setup.c\nindex 7086741e6c..98c6fd8ee4 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1703,6 +1703,87 @@ void set_git_dir(const char *path, int make_realpath)\n \tstrbuf_release(&realpath);\n }\n \n+const char *enter_repo(const char *path, unsigned flags)\n+{\n+\tstatic struct strbuf validated_path = STRBUF_INIT;\n+\tstatic struct strbuf used_path = STRBUF_INIT;\n+\n+\tif (!path)\n+\t\treturn NULL;\n+\n+\tif (!(flags & ENTER_REPO_STRICT)) {\n+\t\tstatic const char *suffix[] = {\n+\t\t\t\"/.git\", \"\", \".git/.git\", \".git\", NULL,\n+\t\t};\n+\t\tconst char *gitfile;\n+\t\tint len = strlen(path);\n+\t\tint i;\n+\t\twhile ((1 < len) && (path[len-1] == '/'))\n+\t\t\tlen--;\n+\n+\t\t/*\n+\t\t * We can handle arbitrary-sized buffers, but this remains as a\n+\t\t * sanity check on untrusted input.\n+\t\t */\n+\t\tif (PATH_MAX <= len)\n+\t\t\treturn NULL;\n+\n+\t\tstrbuf_reset(&used_path);\n+\t\tstrbuf_reset(&validated_path);\n+\t\tstrbuf_add(&used_path, path, len);\n+\t\tstrbuf_add(&validated_path, path, len);\n+\n+\t\tif (used_path.buf[0] == '~') {\n+\t\t\tchar *newpath = interpolate_path(used_path.buf, 0);\n+\t\t\tif (!newpath)\n+\t\t\t\treturn NULL;\n+\t\t\tstrbuf_attach(&used_path, newpath, strlen(newpath),\n+\t\t\t\t      strlen(newpath));\n+\t\t}\n+\t\tfor (i = 0; suffix[i]; i++) {\n+\t\t\tstruct stat st;\n+\t\t\tsize_t baselen = used_path.len;\n+\t\t\tstrbuf_addstr(&used_path, suffix[i]);\n+\t\t\tif (!stat(used_path.buf, &st) &&\n+\t\t\t    (S_ISREG(st.st_mode) ||\n+\t\t\t    (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {\n+\t\t\t\tstrbuf_addstr(&validated_path, suffix[i]);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tstrbuf_setlen(&used_path, baselen);\n+\t\t}\n+\t\tif (!suffix[i])\n+\t\t\treturn NULL;\n+\t\tgitfile = read_gitfile(used_path.buf);\n+\t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n+\t\t\tdie_upon_dubious_ownership(gitfile, NULL, used_path.buf);\n+\t\tif (gitfile) {\n+\t\t\tstrbuf_reset(&used_path);\n+\t\t\tstrbuf_addstr(&used_path, gitfile);\n+\t\t}\n+\t\tif (chdir(used_path.buf))\n+\t\t\treturn NULL;\n+\t\tpath = validated_path.buf;\n+\t}\n+\telse {\n+\t\tconst char *gitfile = read_gitfile(path);\n+\t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n+\t\t\tdie_upon_dubious_ownership(gitfile, NULL, path);\n+\t\tif (gitfile)\n+\t\t\tpath = gitfile;\n+\t\tif (chdir(path))\n+\t\t\treturn NULL;\n+\t}\n+\n+\tif (is_git_directory(\".\")) {\n+\t\tset_git_dir(\".\", 0);\n+\t\tcheck_repository_format(NULL);\n+\t\treturn path;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n static int git_work_tree_initialized;\n \n /*\ndiff --git a/setup.h b/setup.h\nindex 8522fa8575..bfea199bcd 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -97,6 +97,44 @@ static inline int discover_git_directory(struct strbuf *commondir,\n void set_git_dir(const char *path, int make_realpath);\n void set_git_work_tree(const char *tree);\n \n+/* Flags that can be passed to `enter_repo()`. */\n+enum {\n+\t/*\n+\t * Callers that require exact paths (as opposed to allowing known\n+\t * suffixes like \".git\", \".git/.git\" to be omitted) can set this bit.\n+\t */\n+\tENTER_REPO_STRICT = (1<<0),\n+\n+\t/*\n+\t * Callers that are willing to run without ownership check can set this\n+\t * bit.\n+\t */\n+\tENTER_REPO_ANY_OWNER_OK = (1<<1),\n+};\n+\n+/*\n+ * Discover and enter a repository.\n+ *\n+ * First, one directory to try is determined by the following algorithm.\n+ *\n+ * (0) If \"strict\" is given, the path is used as given and no DWIM is\n+ *     done. Otherwise:\n+ * (1) \"~/path\" to mean path under the running user's home directory;\n+ * (2) \"~user/path\" to mean path under named user's home directory;\n+ * (3) \"relative/path\" to mean cwd relative directory; or\n+ * (4) \"/absolute/path\" to mean absolute directory.\n+ *\n+ * Unless \"strict\" is given, we check \"%s/.git\", \"%s\", \"%s.git/.git\", \"%s.git\"\n+ * in this order. We select the first one that is a valid git repository, and\n+ * chdir() to it. If none match, or we fail to chdir, we return NULL.\n+ *\n+ * If all goes well, we return the directory we used to chdir() (but\n+ * before ~user is expanded), avoiding getcwd() resolving symbolic\n+ * links.  User relative paths are also returned as they are given,\n+ * except DWIM suffixing.\n+ */\n+const char *enter_repo(const char *path, unsigned flags);\n+\n const char *setup_git_directory_gently(int *);\n const char *setup_git_directory(void);\n char *prefix_path(const char *prefix, int len, const char *path);\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530971","messageId":"20251119-b4-pks-odb-creation-v1-2-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 02/13] setup: convert `set_git_dir()` to have file scope","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:50Z","receivedAt":"2025-11-19T07:51:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We don't have any external callers of `set_git_dir()` anymore now that\n`enter_repo()` has been moved into \"setup.c\". Remove the declaration and\nmark the function as static.\n\nNote that this change requires us to move the implementation around so\nthat we can avoid adding any new forward declarations.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n setup.c | 80 ++++++++++++++++++++++++++++++++---------------------------------\n setup.h |  1 -\n 2 files changed, 40 insertions(+), 41 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 98c6fd8ee4..8bf52df716 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1002,6 +1002,46 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)\n \treturn error_code ? NULL : path;\n }\n \n+static void set_git_dir_1(const char *path)\n+{\n+\txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n+\tsetup_git_env(path);\n+}\n+\n+static void update_relative_gitdir(const char *name UNUSED,\n+\t\t\t\t   const char *old_cwd,\n+\t\t\t\t   const char *new_cwd,\n+\t\t\t\t   void *data UNUSED)\n+{\n+\tchar *path = reparent_relative_path(old_cwd, new_cwd,\n+\t\t\t\t\t    repo_get_git_dir(the_repository));\n+\tstruct tmp_objdir *tmp_objdir = tmp_objdir_unapply_primary_odb();\n+\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(path);\n+\tif (tmp_objdir)\n+\t\ttmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd);\n+\tfree(path);\n+}\n+\n+static void set_git_dir(const char *path, int make_realpath)\n+{\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (make_realpath) {\n+\t\tstrbuf_realpath(&realpath, path, 1);\n+\t\tpath = realpath.buf;\n+\t}\n+\n+\tset_git_dir_1(path);\n+\tif (!is_absolute_path(path))\n+\t\tchdir_notify_register(NULL, update_relative_gitdir, NULL);\n+\n+\tstrbuf_release(&realpath);\n+}\n+\n static const char *setup_explicit_git_dir(const char *gitdirenv,\n \t\t\t\t\t  struct strbuf *cwd,\n \t\t\t\t\t  struct repository_format *repo_fmt,\n@@ -1663,46 +1703,6 @@ void setup_git_env(const char *git_dir)\n \t\tfetch_if_missing = 0;\n }\n \n-static void set_git_dir_1(const char *path)\n-{\n-\txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n-\tsetup_git_env(path);\n-}\n-\n-static void update_relative_gitdir(const char *name UNUSED,\n-\t\t\t\t   const char *old_cwd,\n-\t\t\t\t   const char *new_cwd,\n-\t\t\t\t   void *data UNUSED)\n-{\n-\tchar *path = reparent_relative_path(old_cwd, new_cwd,\n-\t\t\t\t\t    repo_get_git_dir(the_repository));\n-\tstruct tmp_objdir *tmp_objdir = tmp_objdir_unapply_primary_odb();\n-\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(path);\n-\tif (tmp_objdir)\n-\t\ttmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd);\n-\tfree(path);\n-}\n-\n-void set_git_dir(const char *path, int make_realpath)\n-{\n-\tstruct strbuf realpath = STRBUF_INIT;\n-\n-\tif (make_realpath) {\n-\t\tstrbuf_realpath(&realpath, path, 1);\n-\t\tpath = realpath.buf;\n-\t}\n-\n-\tset_git_dir_1(path);\n-\tif (!is_absolute_path(path))\n-\t\tchdir_notify_register(NULL, update_relative_gitdir, NULL);\n-\n-\tstrbuf_release(&realpath);\n-}\n-\n const char *enter_repo(const char *path, unsigned flags)\n {\n \tstatic struct strbuf validated_path = STRBUF_INIT;\ndiff --git a/setup.h b/setup.h\nindex bfea199bcd..d55dcc6608 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -94,7 +94,6 @@ static inline int discover_git_directory(struct strbuf *commondir,\n \treturn 0;\n }\n \n-void set_git_dir(const char *path, int make_realpath);\n void set_git_work_tree(const char *tree);\n \n /* Flags that can be passed to `enter_repo()`. */\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530972","messageId":"20251119-b4-pks-odb-creation-v1-3-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 03/13] odb: adopt logic to close object databases","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:51Z","receivedAt":"2025-11-19T07:51:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The logic to close an object database is currently contained in the\npackfile subsystem. That choice is somewhat relatable, as most of the\nlogic really is to close resources associated with the packfile store\nitself. But we also end up handling object sources and commit graphs,\nwhich certainly is not related to packfiles.\n\nMove the function into the object database subsystem and rename it to\n`odb_close()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c  |  2 +-\n builtin/gc.c     |  2 +-\n builtin/repack.c |  2 +-\n midx-write.c     |  2 +-\n odb.c            | 18 +++++++++++++++++-\n odb.h            |  7 +++++++\n packfile.c       | 15 ---------------\n packfile.h       |  1 -\n run-command.c    |  2 +-\n scalar.c         |  2 +-\n 10 files changed, 30 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex c990f398ef..b19b302b06 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1617,7 +1617,7 @@ int cmd_clone(int argc,\n \ttransport_disconnect(transport);\n \n \tif (option_dissociate) {\n-\t\tclose_object_store(the_repository->objects);\n+\t\todb_close(the_repository->objects);\n \t\tdissociate_from_references();\n \t}\n \ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex d212cbb9b8..961fa343c4 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1048,7 +1048,7 @@ int cmd_gc(int argc,\n \treport_garbage = report_pack_garbage;\n \todb_reprepare(the_repository->objects);\n \tif (pack_garbage.nr > 0) {\n-\t\tclose_object_store(the_repository->objects);\n+\t\todb_close(the_repository->objects);\n \t\tclean_pack_garbage();\n \t}\n \ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex cfdb4c0920..d9012141f6 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -488,7 +488,7 @@ int cmd_repack(int argc,\n \n \tstring_list_sort(&names);\n \n-\tclose_object_store(repo->objects);\n+\todb_close(repo->objects);\n \n \t/*\n \t * Ok we have prepared all new packfiles.\ndiff --git a/midx-write.c b/midx-write.c\nindex c73010df6d..60497586fd 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -1459,7 +1459,7 @@ static int write_midx_internal(struct odb_source *source,\n \t}\n \n \tif (ctx.m || ctx.base_midx)\n-\t\tclose_object_store(ctx.repo->objects);\n+\t\todb_close(ctx.repo->objects);\n \n \tif (commit_lock_file(&lk) < 0)\n \t\tdie_errno(_(\"could not write multi-pack-index\"));\ndiff --git a/odb.c b/odb.c\nindex 3ec21ef24e..bcefa5cede 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -9,6 +9,7 @@\n #include \"khash.h\"\n #include \"lockfile.h\"\n #include \"loose.h\"\n+#include \"midx.h\"\n #include \"object-file-convert.h\"\n #include \"object-file.h\"\n #include \"odb.h\"\n@@ -1044,6 +1045,21 @@ struct object_database *odb_new(struct repository *repo)\n \treturn o;\n }\n \n+void odb_close(struct object_database *o)\n+{\n+\tstruct odb_source *source;\n+\n+\tpackfile_store_close(o->packfiles);\n+\n+\tfor (source = o->sources; source; source = source->next) {\n+\t\tif (source->midx)\n+\t\t\tclose_midx(source->midx);\n+\t\tsource->midx = NULL;\n+\t}\n+\n+\tclose_commit_graph(o);\n+}\n+\n static void odb_free_sources(struct object_database *o)\n {\n \twhile (o->sources) {\n@@ -1076,7 +1092,7 @@ void odb_clear(struct object_database *o)\n \t\tfree((char *) o->cached_objects[i].value.buf);\n \tFREE_AND_NULL(o->cached_objects);\n \n-\tclose_object_store(o);\n+\todb_close(o);\n \tpackfile_store_free(o->packfiles);\n \to->packfiles = NULL;\n \ndiff --git a/odb.h b/odb.h\nindex 9bb28008b1..71b4897c82 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -169,6 +169,13 @@ struct object_database {\n struct object_database *odb_new(struct repository *repo);\n void odb_clear(struct object_database *o);\n \n+/*\n+ * Close the object database and all of its sources so that any held resources\n+ * will be released. The database can still be used after closing it, in which\n+ * case these resources may be reallocated.\n+ */\n+void odb_close(struct object_database *o);\n+\n /*\n  * Clear caches, reload alternates and then reload object sources so that new\n  * objects may become accessible.\ndiff --git a/packfile.c b/packfile.c\nindex 40f733dd23..af71eaf7e3 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -359,21 +359,6 @@ void close_pack(struct packed_git *p)\n \toidset_clear(&p->bad_objects);\n }\n \n-void close_object_store(struct object_database *o)\n-{\n-\tstruct odb_source *source;\n-\n-\tpackfile_store_close(o->packfiles);\n-\n-\tfor (source = o->sources; source; source = source->next) {\n-\t\tif (source->midx)\n-\t\t\tclose_midx(source->midx);\n-\t\tsource->midx = NULL;\n-\t}\n-\n-\tclose_commit_graph(o);\n-}\n-\n void unlink_pack_path(const char *pack_name, int force_delete)\n {\n \tstatic const char *exts[] = {\".idx\", \".pack\", \".rev\", \".keep\", \".bitmap\", \".promisor\", \".mtimes\"};\ndiff --git a/packfile.h b/packfile.h\nindex 58fcc88e20..d9226a072a 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -279,7 +279,6 @@ struct object_database;\n unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);\n void close_pack_windows(struct packed_git *);\n void close_pack(struct packed_git *);\n-void close_object_store(struct object_database *o);\n void unuse_pack(struct pack_window **);\n void clear_delta_base_cache(void);\n struct packed_git *add_packed_git(struct repository *r, const char *path,\ndiff --git a/run-command.c b/run-command.c\nindex ed9575bd6a..e3e02475cc 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -743,7 +743,7 @@ int start_command(struct child_process *cmd)\n \tfflush(NULL);\n \n \tif (cmd->close_object_store)\n-\t\tclose_object_store(the_repository->objects);\n+\t\todb_close(the_repository->objects);\n \n #ifndef GIT_WINDOWS_NATIVE\n {\ndiff --git a/scalar.c b/scalar.c\nindex f754311627..2aeb191cc8 100644\n--- a/scalar.c\n+++ b/scalar.c\n@@ -931,7 +931,7 @@ static int cmd_delete(int argc, const char **argv)\n \tif (dir_inside_of(cwd, enlistment.buf) >= 0)\n \t\tres = error(_(\"refusing to delete current working directory\"));\n \telse {\n-\t\tclose_object_store(the_repository->objects);\n+\t\todb_close(the_repository->objects);\n \t\tres = delete_enlistment(&enlistment);\n \t}\n \tstrbuf_release(&enlistment);\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530973","messageId":"20251119-b4-pks-odb-creation-v1-4-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 04/13] odb: refactor `odb_clear()` to `odb_free()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:52Z","receivedAt":"2025-11-19T07:51:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `odb_clear()` releases all resources allocated to an object\ndatabase and ensures that all fields become zero'd out. Despite its\nnaming though it doesn't really clear the object database so that it\nbecomes ready for reuse afterwards again -- the caller would first have\nto reinitialize it, and that contradicts the terminology of \"clearing\"\nas we have defined it in our coding guidelines.\n\nThere isn't really only a reason to have \"clearing\" semantics, either.\nThere's only a single caller of `odb_clear()`, and that caller also ends\nup freeing the object database structure itself.\n\nRefactor the function to have \"freeing\" semantics instead, so that the\nstructure itself is also freed, which allows us to drop some useless\nboilerplate to zero out the structure's members.\n\nThis refactoring reveals that we're trying to close the commit graph\nmultiple times: once directly via `free_commit_graph()`, and once via\n`odb_close()`. Drop the former call.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c        | 19 ++++++++-----------\n odb.h        |  4 +++-\n repository.c |  4 ++--\n 3 files changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex bcefa5cede..29cf6496c5 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -1073,30 +1073,27 @@ static void odb_free_sources(struct object_database *o)\n \to->source_by_path = NULL;\n }\n \n-void odb_clear(struct object_database *o)\n+void odb_free(struct object_database *o)\n {\n-\tFREE_AND_NULL(o->alternate_db);\n+\tif (!o)\n+\t\treturn;\n+\n+\tfree(o->alternate_db);\n \n \toidmap_clear(&o->replace_map, 1);\n \tpthread_mutex_destroy(&o->replace_mutex);\n \n-\tfree_commit_graph(o->commit_graph);\n-\to->commit_graph = NULL;\n-\to->commit_graph_attempted = 0;\n-\n \todb_free_sources(o);\n-\to->sources_tail = NULL;\n-\to->loaded_alternates = 0;\n \n \tfor (size_t i = 0; i < o->cached_object_nr; i++)\n \t\tfree((char *) o->cached_objects[i].value.buf);\n-\tFREE_AND_NULL(o->cached_objects);\n+\tfree(o->cached_objects);\n \n \todb_close(o);\n \tpackfile_store_free(o->packfiles);\n-\to->packfiles = NULL;\n-\n \tstring_list_clear(&o->submodule_source_paths, 0);\n+\n+\tfree(o);\n }\n \n void odb_reprepare(struct object_database *o)\ndiff --git a/odb.h b/odb.h\nindex 71b4897c82..77b313b784 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -167,7 +167,9 @@ struct object_database {\n };\n \n struct object_database *odb_new(struct repository *repo);\n-void odb_clear(struct object_database *o);\n+\n+/* Free the object database and release all resources. */\n+void odb_free(struct object_database *o);\n \n /*\n  * Close the object database and all of its sources so that any held resources\ndiff --git a/repository.c b/repository.c\nindex 6aaa7ba008..3c8b3813b0 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -382,8 +382,8 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->worktree);\n \tFREE_AND_NULL(repo->submodule_prefix);\n \n-\todb_clear(repo->objects);\n-\tFREE_AND_NULL(repo->objects);\n+\todb_free(repo->objects);\n+\trepo->objects = NULL;\n \n \tparsed_object_pool_clear(repo->parsed_objects);\n \tFREE_AND_NULL(repo->parsed_objects);\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530974","messageId":"20251119-b4-pks-odb-creation-v1-5-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 05/13] odb: move logic to disable ref updates into repo","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:53Z","receivedAt":"2025-11-19T07:51:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Our object database sources have a field `disable_ref_updates`. This\nfield can obviously be set to disable reference updates, but it is\nsomewhat curious that this logic is hosted by the object database.\n\nThe reason for this is that it was primarily added to keep us from\naccidentally updating references while an ODB transaction is ongoing.\nAny objects part of the transaction have not yet been committed to disk,\nso new references that point to them might get corrupted in case we\nnever end up committing the transaction. As such, whenever we create a\nnew transaction we set up a new temporary ODB source and mark it as\ndisabling reference updates.\n\nThis has one (and only one?) upside: once we have committed the\ntransaction, the temporary source will be dropped and thus we clean up\nthe disabled reference updates automatically. But other than that, it's\nsomewhat misdesigned:\n\n  - We can have multiple ODB sources, but only the currently active\n    source inhibits reference updates.\n\n  - We're mixing concerns of the refbd with the ODB.\n\nArguably, the decision of whether we can update references or not should\nbe handled by the refdb. But that wouldn't be a great fit either, as\nthere can be one refdb per worktree. So we'd again have the same problem\nthat a \"global\" intent becomes localized to a specific instance.\n\nInstead, move the setting into the repository. While at it, convert it\ninto a boolean.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c        | 3 ++-\n odb.h        | 7 -------\n refs.c       | 2 +-\n repository.c | 2 +-\n repository.h | 9 ++++++++-\n setup.c      | 2 +-\n 6 files changed, 13 insertions(+), 12 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 29cf6496c5..ccc6e999e7 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -360,7 +360,7 @@ struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,\n \t * Disable ref updates while a temporary odb is active, since\n \t * the objects in the database may roll back.\n \t */\n-\tsource->disable_ref_updates = 1;\n+\todb->repo->disable_ref_updates = true;\n \tsource->will_destroy = will_destroy;\n \tsource->next = odb->sources;\n \todb->sources = source;\n@@ -387,6 +387,7 @@ void odb_restore_primary_source(struct object_database *odb,\n \tif (cur_source->next != restore_source)\n \t\tBUG(\"we expect the old primary object store to be the first alternate\");\n \n+\todb->repo->disable_ref_updates = false;\n \todb->sources = restore_source;\n \todb_source_free(cur_source);\n }\ndiff --git a/odb.h b/odb.h\nindex 77b313b784..99c4d48972 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -66,13 +66,6 @@ struct odb_source {\n \t */\n \tbool local;\n \n-\t/*\n-\t * This is a temporary object store created by the tmp_objdir\n-\t * facility. Disable ref updates since the objects in the store\n-\t * might be discarded on rollback.\n-\t */\n-\tint disable_ref_updates;\n-\n \t/*\n \t * This object store is ephemeral, so there is no need to fsync.\n \t */\ndiff --git a/refs.c b/refs.c\nindex 965381367e..6c7283d9eb 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2491,7 +2491,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction,\n \t\tbreak;\n \t}\n \n-\tif (refs->repo->objects->sources->disable_ref_updates) {\n+\tif (refs->repo->disable_ref_updates) {\n \t\tstrbuf_addstr(err,\n \t\t\t      _(\"ref updates forbidden inside quarantine environment\"));\n \t\treturn -1;\ndiff --git a/repository.c b/repository.c\nindex 3c8b3813b0..455c2d279f 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -179,7 +179,7 @@ void repo_set_gitdir(struct repository *repo,\n \t\trepo->objects->sources->path = objects_path;\n \t}\n \n-\trepo->objects->sources->disable_ref_updates = o->disable_ref_updates;\n+\trepo->disable_ref_updates = o->disable_ref_updates;\n \n \tfree(repo->objects->alternate_db);\n \trepo->objects->alternate_db = xstrdup_or_null(o->alternate_db);\ndiff --git a/repository.h b/repository.h\nindex 5808a5d610..614649413b 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -71,6 +71,13 @@ struct repository {\n \t */\n \tstruct ref_store *refs_private;\n \n+\t/*\n+\t * Disable ref updates. This is especially used in contexts where\n+\t * transactions may still be rolled back so that we don't start to\n+\t * reference objects that may vanish.\n+\t */\n+\tbool disable_ref_updates;\n+\n \t/*\n \t * A strmap of ref_stores, stored by submodule name, accessible via\n \t * `repo_get_submodule_ref_store()`.\n@@ -187,7 +194,7 @@ struct set_gitdir_args {\n \tconst char *graft_file;\n \tconst char *index_file;\n \tconst char *alternate_db;\n-\tint disable_ref_updates;\n+\tbool disable_ref_updates;\n };\n \n void repo_set_gitdir(struct repository *repo, const char *root,\ndiff --git a/setup.c b/setup.c\nindex 8bf52df716..a752e9fc84 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1682,7 +1682,7 @@ void setup_git_env(const char *git_dir)\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 = 1;\n+\t\targs.disable_ref_updates = true;\n \t}\n \n \trepo_set_gitdir(the_repository, git_dir, &args);\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530975","messageId":"20251119-b4-pks-odb-creation-v1-6-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 06/13] oidset: introduce `oidset_equal()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:54Z","receivedAt":"2025-11-19T07:51:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function that allows the caller to verify whether two\noidsets contain the exact same object IDs.\n\nNote that this change requires us to change `oidset_iter_init()` to\naccept a `const struct oidset`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n oidset.c | 16 ++++++++++++++++\n oidset.h |  9 +++++++--\n 2 files changed, 23 insertions(+), 2 deletions(-)\n\ndiff --git a/oidset.c b/oidset.c\nindex 8d36aef8dc..c8ff0b385c 100644\n--- a/oidset.c\n+++ b/oidset.c\n@@ -16,6 +16,22 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid)\n \treturn pos != kh_end(&set->set);\n }\n \n+bool oidset_equal(const struct oidset *a, const struct oidset *b)\n+{\n+\tstruct oidset_iter iter;\n+\tstruct object_id *a_oid;\n+\n+\tif (oidset_size(a) != oidset_size(b))\n+\t\treturn false;\n+\n+\toidset_iter_init(a, &iter);\n+\twhile ((a_oid = oidset_iter_next(&iter)))\n+\t\tif (!oidset_contains(b, a_oid))\n+\t\t\treturn false;\n+\n+\treturn true;\n+}\n+\n int oidset_insert(struct oidset *set, const struct object_id *oid)\n {\n \tint added;\ndiff --git a/oidset.h b/oidset.h\nindex 0106b6f278..e0f1a6ff4f 100644\n--- a/oidset.h\n+++ b/oidset.h\n@@ -38,6 +38,11 @@ void oidset_init(struct oidset *set, size_t initial_size);\n  */\n int oidset_contains(const struct oidset *set, const struct object_id *oid);\n \n+/**\n+ * Returns true iff `a` and `b` contain the exact same OIDs.\n+ */\n+bool oidset_equal(const struct oidset *a, const struct oidset *b);\n+\n /**\n  * Insert the oid into the set; a copy is made, so \"oid\" does not need\n  * to persist after this function is called.\n@@ -94,11 +99,11 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path,\n \t\t\t\t oidset_parse_tweak_fn fn, void *cbdata);\n \n struct oidset_iter {\n-\tkh_oid_set_t *set;\n+\tconst kh_oid_set_t *set;\n \tkhiter_t iter;\n };\n \n-static inline void oidset_iter_init(struct oidset *set,\n+static inline void oidset_iter_init(const struct oidset *set,\n \t\t\t\t    struct oidset_iter *iter)\n {\n \titer->set = &set->set;\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530976","messageId":"20251119-b4-pks-odb-creation-v1-7-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 07/13] builtin/index-pack: fix deferred fsck outside repos","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:55Z","receivedAt":"2025-11-19T07:51:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When asked to perform object consistency checks via the `--fsck-objects`\nflag we verify that each object part of the pack is valid. In general,\nthis check can even be performed outside of a Git repository: we don't\nneed an initialized object database as we simply read the object from\nthe packfile directly.\n\nBut there's one exception: a subset of the object checks may be deferred\nto a later point in time. For now, this only concerns \".gitmodules\" and\n\".gitattributes\" files: whenever we see a tree referencing these files\nwe queue them for a deferred check. This is done because we need to do\nsome extra checks for those files to ensure that they are well-formed,\nand these checks need to be done regardless of whether the corresponding\nblobs are part of the packfile or not.\n\nThis works inside a repository, but unfortunately the logic leads to a\nsegfault when running outside of one. This is because we eventually call\n`odb_read_object()`, which will crash because the object database has\nnot been initialized.\n\nThere's multiple options here:\n\n  - We could in theory create a purely in-memory database with only a\n    packfile store that contains the single packfile. We don't really\n    have the infrastructure for this yet though, and it would end up\n    being quite hacky.\n\n  - We could refuse to perform consistency checks outside of a\n    repository. But most of the checks work alright, so this would be a\n    regression.\n\n  - We can skip the finalizing consistency checks when running outside\n    of a repository. This is not as invasive as skipping all checks,\n    but it's not great to randomly skip a subset of tests, either.\n\nNone of these options really feel perfect. The first one would be the\nobvious choice if easily possible.\n\nThere's another option though: instead of skipping the final object\nchecks, we can die if there are any queued object checks. With this\nchange we now die exactly if and only if we would have previously\nsegfaulted. Like this we ensure that objects that _may_ fail the\nconsistency checks won't be silently skipped, and at the same time we\ngive users a much better error message.\n\nRefactor the code accordingly and add a test that would have triggered\nthe segfault. Note that we also move down the logic to add the packfile\nto the store. There is no point doing this any earlier than right before\nwe execute `fsck_finish()`, and it ensures that the logic to set up and\nperform the consistency check is self-contained.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/index-pack.c  | 21 ++++++++++++++++++---\n fsck.c                |  6 ++++++\n fsck.h                |  7 +++++++\n t/t5302-pack-index.sh | 16 ++++++++++++++++\n 4 files changed, 47 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 2b78ba7fe4..699fe678cd 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1640,7 +1640,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \trename_tmp_packfile(&final_index_name, curr_index_name, &index_name,\n \t\t\t    hash, \"idx\", 1);\n \n-\tif (do_fsck_object)\n+\tif (do_fsck_object && startup_info->have_repository)\n \t\tpackfile_store_load_pack(the_repository->objects->packfiles,\n \t\t\t\t\t final_index_name, 0);\n \n@@ -2110,8 +2110,23 @@ int cmd_index_pack(int argc,\n \telse\n \t\tclose(input_fd);\n \n-\tif (do_fsck_object && fsck_finish(&fsck_options))\n-\t\tdie(_(\"fsck error in pack objects\"));\n+\tif (do_fsck_object) {\n+\t\t/*\n+\t\t * We cannot perform queued consistency checks when running\n+\t\t * outside of a repository because those require us to read\n+\t\t * from the object database, which is uninitialized.\n+\t\t *\n+\t\t * TODO: we may eventually set up an in-memory object database,\n+\t\t * which would allow us to perform these queued checks.\n+\t\t */\n+\t\tif (!startup_info->have_repository &&\n+\t\t    fsck_has_queued_checks(&fsck_options))\n+\t\t\tdie(_(\"cannot perform queued object checks outside \"\n+\t\t\t      \"of a repository\"));\n+\n+\t\tif (fsck_finish(&fsck_options))\n+\t\t\tdie(_(\"fsck error in pack objects\"));\n+\t}\n \n \tfree(opts.anomaly);\n \tfree(objects);\ndiff --git a/fsck.c b/fsck.c\nindex 341e100d24..8e1565fe6d 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1350,6 +1350,12 @@ int fsck_finish(struct fsck_options *options)\n \treturn ret;\n }\n \n+bool fsck_has_queued_checks(struct fsck_options *options)\n+{\n+\treturn !oidset_equal(&options->gitmodules_found, &options->gitmodules_done) ||\n+\t       !oidset_equal(&options->gitattributes_found, &options->gitattributes_done);\n+}\n+\n void fsck_options_clear(struct fsck_options *options)\n {\n \tfree(options->msg_type);\ndiff --git a/fsck.h b/fsck.h\nindex cb6ef32f4f..336917c045 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -248,6 +248,13 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,\n  */\n int fsck_finish(struct fsck_options *options);\n \n+/*\n+ * Check whether there are any checks that have been queued up and that still\n+ * need to be run. Returns `false` iff `fsck_finish()` wouldn't perform any\n+ * actions, `true` otherwise.\n+ */\n+bool fsck_has_queued_checks(struct fsck_options *options);\n+\n /*\n  * Clear the fsck_options struct, freeing any allocated memory.\n  */\ndiff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh\nindex 413c99274c..9697448cb2 100755\n--- a/t/t5302-pack-index.sh\n+++ b/t/t5302-pack-index.sh\n@@ -293,4 +293,20 @@ test_expect_success 'too-large packs report the breach' '\n \tgrep \"maximum allowed size (20 bytes)\" err\n '\n \n+# git-index-pack(1) uses the default hash algorithm outside of the repository,\n+# and it has no way to tell it otherwise. So we can only run this test with the\n+# default hash algorithm, as it would otherwise fail to parse the tree.\n+test_expect_success DEFAULT_HASH_ALGORITHM 'index-pack --fsck-objects outside of a repo' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tprintf \"100644 blob $(test_oid 001)\\t.gitattributes\\n\" >tree &&\n+\t\tgit mktree --missing <tree >tree-oid &&\n+\t\tgit pack-objects <tree-oid pack &&\n+\t\ttest_must_fail nongit git index-pack --fsck-objects \"$(pwd)\"/pack-*.pack 2>err &&\n+\t\ttest_grep \"cannot perform queued object checks outside of a repository\" err\n+\t)\n+'\n+\n test_done\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530977","messageId":"20251119-b4-pks-odb-creation-v1-8-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 08/13] t/helper: stop setting up `the_repository` repeatedly","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:56Z","receivedAt":"2025-11-19T07:51:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The \"repository\" test helper sets up `the_repository` twice. In fact\nthough, we don't even have to set it up even once: all we need is to set\nup its hash algorithm, because we still depend on some subsystems that\naren't free of `the_repository`.\n\nRefactor the code accordingly. This prepares for a subsequent change,\nwhere setting up the repository repeatedly will lead to a `BUG()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/helper/test-repository.c | 16 ++--------------\n 1 file changed, 2 insertions(+), 14 deletions(-)\n\ndiff --git a/t/helper/test-repository.c b/t/helper/test-repository.c\nindex 63c37de33d..9ba94cdffa 100644\n--- a/t/helper/test-repository.c\n+++ b/t/helper/test-repository.c\n@@ -17,10 +17,6 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,\n \tstruct commit *c;\n \tstruct commit_list *parent;\n \n-\tsetup_git_env(gitdir);\n-\n-\trepo_clear(the_repository);\n-\n \tif (repo_init(&r, gitdir, worktree))\n \t\tdie(\"Couldn't init repo\");\n \n@@ -47,10 +43,6 @@ static void test_get_commit_tree_in_graph(const char *gitdir,\n \tstruct commit *c;\n \tstruct tree *tree;\n \n-\tsetup_git_env(gitdir);\n-\n-\trepo_clear(the_repository);\n-\n \tif (repo_init(&r, gitdir, worktree))\n \t\tdie(\"Couldn't init repo\");\n \n@@ -75,24 +67,20 @@ static void test_get_commit_tree_in_graph(const char *gitdir,\n \n int cmd__repository(int argc, const char **argv)\n {\n-\tint nongit_ok = 0;\n-\n-\tsetup_git_directory_gently(&nongit_ok);\n-\n \tif (argc < 2)\n \t\tdie(\"must have at least 2 arguments\");\n \tif (!strcmp(argv[1], \"parse_commit_in_graph\")) {\n \t\tstruct object_id oid;\n \t\tif (argc < 5)\n \t\t\tdie(\"not enough arguments\");\n-\t\tif (parse_oid_hex(argv[4], &oid, &argv[4]))\n+\t\tif (parse_oid_hex_any(argv[4], &oid, &argv[4]) == GIT_HASH_UNKNOWN)\n \t\t\tdie(\"cannot parse oid '%s'\", argv[4]);\n \t\ttest_parse_commit_in_graph(argv[2], argv[3], &oid);\n \t} else if (!strcmp(argv[1], \"get_commit_tree_in_graph\")) {\n \t\tstruct object_id oid;\n \t\tif (argc < 5)\n \t\t\tdie(\"not enough arguments\");\n-\t\tif (parse_oid_hex(argv[4], &oid, &argv[4]))\n+\t\tif (parse_oid_hex_any(argv[4], &oid, &argv[4]) == GIT_HASH_UNKNOWN)\n \t\t\tdie(\"cannot parse oid '%s'\", argv[4]);\n \t\ttest_get_commit_tree_in_graph(argv[2], argv[3], &oid);\n \t} else {\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530978","messageId":"20251119-b4-pks-odb-creation-v1-9-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 09/13] http-push: stop setting up `the_repository` for each reference","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:57Z","receivedAt":"2025-11-19T07:51:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When pushing references via HTTP we call `repo_init_revisions()` in a\nloop for each reference that we're about to push. As third argument we\npass the result of `setup_git_directory()`, which causes us to\nreinitialize the repository every single time.\n\nThis is an obvious waste of compute, as the repository that we're\nworking in will never change across any of the initializations. The only\nreason that we do this is to retrieve the directory of the repository.\nFurthermore, this is about to create issues in a subsequent commit,\nwhere reinitializing the repository will cause a `BUG()`.\n\nAddress this by storing the Git directory in a variable instead so that\nwe don't have to call the function repeatedly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http-push.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/http-push.c b/http-push.c\nindex a1c01e3b9b..a48ca23799 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1725,6 +1725,7 @@ int cmd_main(int argc, const char **argv)\n \tint i;\n \tint new_refs;\n \tstruct ref *ref, *local_refs = NULL;\n+\tconst char *gitdir;\n \n \tCALLOC_ARRAY(repo, 1);\n \n@@ -1787,7 +1788,7 @@ int cmd_main(int argc, const char **argv)\n \tif (delete_branch && rs.nr != 1)\n \t\tdie(\"You must specify only one branch name when deleting a remote branch\");\n \n-\tsetup_git_directory();\n+\tgitdir = setup_git_directory();\n \n \tmemset(remote_dir_exists, -1, 256);\n \n@@ -1941,7 +1942,7 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!push_all && !is_null_oid(&ref->old_oid))\n \t\t\tstrvec_pushf(&commit_argv, \"^%s\",\n \t\t\t\t     oid_to_hex(&ref->old_oid));\n-\t\trepo_init_revisions(the_repository, &revs, setup_git_directory());\n+\t\trepo_init_revisions(the_repository, &revs, gitdir);\n \t\tsetup_revisions_from_strvec(&commit_argv, &revs, NULL);\n \t\trevs.edge_hint = 0; /* just in case */\n \n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530979","messageId":"20251119-b4-pks-odb-creation-v1-10-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 10/13] odb: handle initialization of sources in `odb_new()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:58Z","receivedAt":"2025-11-19T07:51:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The logic to set up a new object database is currently distributed\nacross two functions in \"repository.c\":\n\n  - In `initialize_repository()` we initialize an empty object database.\n    This object database is not fully initialized and doesn't have any\n    sources attached to it.\n\n  - The primary object database source is then created in\n    `repo_set_gitdir()`.\n\nIdeally though, the logic should be entirely self-contained so that we\ncan iterate more readily on how exactly the sources themselves get set\nup.\n\nRefactor `odb_new()` to handle both allocation and setup of the object\ndatabase. This ensures that the object database is always initialized\nand ready for use, and it allows us to change how the sources get set up\neventually.\n\nNote that `repo_set_gitdir()` still reaches into the sources when the\nfunction gets called with an already-initialized object database. This\nwill be fixed in the next commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c        | 14 +++++++++++++-\n odb.h        | 15 ++++++++++++++-\n repository.c | 20 ++++++++------------\n 3 files changed, 35 insertions(+), 14 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex ccc6e999e7..88b40c81c0 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -1034,15 +1034,27 @@ int odb_write_object_stream(struct object_database *odb,\n \treturn odb_source_loose_write_stream(odb->sources, stream, len, oid);\n }\n \n-struct object_database *odb_new(struct repository *repo)\n+struct object_database *odb_new(struct repository *repo,\n+\t\t\t\tconst char *primary_source,\n+\t\t\t\tconst char *secondary_sources)\n {\n \tstruct object_database *o = xmalloc(sizeof(*o));\n+\tchar *to_free = NULL;\n \n \tmemset(o, 0, sizeof(*o));\n \to->repo = repo;\n \to->packfiles = packfile_store_new(o);\n \tpthread_mutex_init(&o->replace_mutex, NULL);\n \tstring_list_init_dup(&o->submodule_source_paths);\n+\n+\tif (!primary_source)\n+\t\tprimary_source = to_free = xstrfmt(\"%s/objects\", repo->commondir);\n+\to->sources = odb_source_new(o, primary_source, true);\n+\to->sources_tail = &o->sources->next;\n+\to->alternate_db = xstrdup_or_null(secondary_sources);\n+\n+\tfree(to_free);\n+\n \treturn o;\n }\n \ndiff --git a/odb.h b/odb.h\nindex 99c4d48972..41b3c03027 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -159,7 +159,20 @@ struct object_database {\n \tstruct string_list submodule_source_paths;\n };\n \n-struct object_database *odb_new(struct repository *repo);\n+/*\n+ * Create a new object database for the given repository.\n+ *\n+ * If the primary source parameter is set it will override the usual primary\n+ * object directory derived from the repository's common directory. The\n+ * alternate sources are expected to be a PATH_SEP-separated list of secondary\n+ * sources. Note that these alternate sources will be added in addition to, not\n+ * instead of, the alternates identified by the primary source.\n+ *\n+ * Returns the newly created object database.\n+ */\n+struct object_database *odb_new(struct repository *repo,\n+\t\t\t\tconst char *primary_source,\n+\t\t\t\tconst char *alternate_sources);\n \n /* Free the object database and release all resources. */\n void odb_free(struct object_database *o);\ndiff --git a/repository.c b/repository.c\nindex 455c2d279f..5975c8f341 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -52,7 +52,6 @@ static void set_default_hash_algo(struct repository *repo)\n \n void initialize_repository(struct repository *repo)\n {\n-\trepo->objects = odb_new(repo);\n \trepo->remote_state = remote_state_new();\n \trepo->parsed_objects = parsed_object_pool_new(repo);\n \tALLOC_ARRAY(repo->index, 1);\n@@ -160,29 +159,26 @@ void repo_set_gitdir(struct repository *repo,\n \t * until after xstrdup(root). Then we can free it.\n \t */\n \tchar *old_gitdir = repo->gitdir;\n-\tchar *objects_path = NULL;\n \n \trepo->gitdir = xstrdup(gitfile ? gitfile : root);\n \tfree(old_gitdir);\n \n \trepo_set_commondir(repo, o->commondir);\n-\texpand_base_dir(&objects_path, o->object_dir,\n-\t\t\trepo->commondir, \"objects\");\n-\n-\tif (!repo->objects->sources) {\n-\t\trepo->objects->sources = odb_source_new(repo->objects,\n-\t\t\t\t\t\t\tobjects_path, true);\n-\t\trepo->objects->sources_tail = &repo->objects->sources->next;\n-\t\tfree(objects_path);\n+\n+\tif (!repo->objects) {\n+\t\trepo->objects = odb_new(repo, o->object_dir, o->alternate_db);\n \t} else {\n+\t\tchar *objects_path = NULL;\n+\t\texpand_base_dir(&objects_path, o->object_dir,\n+\t\t\t\trepo->commondir, \"objects\");\n \t\tfree(repo->objects->sources->path);\n \t\trepo->objects->sources->path = objects_path;\n+\t\tfree(repo->objects->alternate_db);\n+\t\trepo->objects->alternate_db = xstrdup_or_null(o->alternate_db);\n \t}\n \n \trepo->disable_ref_updates = o->disable_ref_updates;\n \n-\tfree(repo->objects->alternate_db);\n-\trepo->objects->alternate_db = xstrdup_or_null(o->alternate_db);\n \texpand_base_dir(&repo->graft_file, o->graft_file,\n \t\t\trepo->commondir, \"info/grafts\");\n \texpand_base_dir(&repo->index_file, o->index_file,\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530980","messageId":"20251119-b4-pks-odb-creation-v1-11-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 11/13] chdir-notify: add function to unregister listeners","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:50:59Z","receivedAt":"2025-11-19T07:51:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we (obviously) have a way to register new listeners that get\ncalled whenever we chdir(3p), we don't have an equivalent that can be\nused to unregister such a listener again.\n\nAdd one, as it will be required in a subsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n chdir-notify.c | 18 ++++++++++++++++++\n chdir-notify.h |  2 ++\n 2 files changed, 20 insertions(+)\n\ndiff --git a/chdir-notify.c b/chdir-notify.c\nindex 0d7bc04607..f8bfe3cbef 100644\n--- a/chdir-notify.c\n+++ b/chdir-notify.c\n@@ -25,6 +25,24 @@ void chdir_notify_register(const char *name,\n \tlist_add_tail(&e->list, &chdir_notify_entries);\n }\n \n+void chdir_notify_unregister(const char *name, chdir_notify_callback cb,\n+\t\t\t     void *data)\n+{\n+\tstruct list_head *pos, *p;\n+\n+\tlist_for_each_safe(pos, p, &chdir_notify_entries) {\n+\t\tstruct chdir_notify_entry *e =\n+\t\t\tlist_entry(pos, struct chdir_notify_entry, list);\n+\n+\t\tif (e->cb != cb || e->data != data || !e->name != !name ||\n+\t\t    (e->name && strcmp(e->name, name)))\n+\t\t\tcontinue;\n+\n+\t\tlist_del(pos);\n+\t\tfree(e);\n+\t}\n+}\n+\n static void reparent_cb(const char *name,\n \t\t\tconst char *old_cwd,\n \t\t\tconst char *new_cwd,\ndiff --git a/chdir-notify.h b/chdir-notify.h\nindex 366e4c1ee9..81eb69d846 100644\n--- a/chdir-notify.h\n+++ b/chdir-notify.h\n@@ -41,6 +41,8 @@ typedef void (*chdir_notify_callback)(const char *name,\n \t\t\t\t      const char *new_cwd,\n \t\t\t\t      void *data);\n void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data);\n+void chdir_notify_unregister(const char *name, chdir_notify_callback cb,\n+\t\t\t     void *data);\n void chdir_notify_reparent(const char *name, char **path);\n \n /*\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530981","messageId":"20251119-b4-pks-odb-creation-v1-12-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 12/13] odb: handle changing a repository's commondir","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:51:00Z","receivedAt":"2025-11-19T07:51:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `repo_set_gitdir()` is called in two situations:\n\n  - To initialize the repository with its discovered location. As part\n    of this we also set up the new object database.\n\n  - To update the repository's discovered location in case the process\n    changes its working directory so that we update relative paths. This\n    means we also have to update any relative paths that are potentially\n    used in the object database.\n\nIn the context of the object database we ideally wouldn't ever have to\nworry about the second case: if all paths used by our object database\nsources were absolute, then we wouldn't have to update them. But\nunfortunately, the paths aren't only used to locate files owned by the\ngiven source, but we also use them for reporting purposes. One such\nexample is `repo_get_object_directory()`, where we cannot just change\nsemantics to always return absolute paths, as that is likely to break\ntooling out there.\n\nOne solution to this would be to have both a \"display path\" and an\n\"internal path\". This would allow us to use internal paths for all\ninternal matters, but continue to use the potentially-relative display\npaths so that we don't break compatibility. But converting the codebase\nto honor this split is quite a messy endeavour, and it wouldn't even\nhelp us with the goal to get rid of the need to update the display path\non chdir(3p).\n\nAnother solution would be to rework \"setup.c\" so that we never have to\nupdate paths in the first place. In that case, we'd only initialize the\nrepository once we have figured out final locations for all directories.\nThis would be a significant simplification of that subsystem indeed, but\nthe current logic is so messy that it would take significant investments\nto get there.\n\nMeanwhile though, while object sources may still use relative paths, the\nbest thing we can do is to handle the reparenting of the object source\npaths in the object database itself. This can be done by registering one\ncallback for each object database so that we get notified whenever the\ncurrent working directory changes, and we then perform the reparenting\nourselves.\n\nIdeally, this wouldn't even happen on the object database level, but\ninstead handled by each object database source. But we don't yet have\nproper pluggable object database sources, so this will need to be\nhandled at a later point in time.\n\nThe logic itself is rather simple:\n\n  - We register the callback when creating the object database.\n\n  - We unregister the callback when releasing it again.\n\n  - We split up `set_git_dir_1()` so that it becomes possible to skip\n    recreating the object database. This is required because the\n    function is called both when the current working directory changes,\n    but also when we set up the repository. Calling this function\n    without skipping creation of the ODB will result in a bug in case\n    it's already created.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c        | 37 +++++++++++++++++++++++---\n odb.h        |  4 ---\n repository.c | 13 +++-------\n repository.h |  1 +\n setup.c      | 84 ++++++++++++++++++++++++++++++++----------------------------\n 5 files changed, 83 insertions(+), 56 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 88b40c81c0..70665fb7f4 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n+#include \"chdir-notify.h\"\n #include \"commit-graph.h\"\n #include \"config.h\"\n #include \"dir.h\"\n@@ -142,9 +143,9 @@ static void read_info_alternates(struct object_database *odb,\n \t\t\t\t const char *relative_base,\n \t\t\t\t int depth);\n \n-struct odb_source *odb_source_new(struct object_database *odb,\n-\t\t\t\t  const char *path,\n-\t\t\t\t  bool local)\n+static struct odb_source *odb_source_new(struct object_database *odb,\n+\t\t\t\t\t const char *path,\n+\t\t\t\t\t bool local)\n {\n \tstruct odb_source *source;\n \n@@ -1034,6 +1035,32 @@ int odb_write_object_stream(struct object_database *odb,\n \treturn odb_source_loose_write_stream(odb->sources, stream, len, oid);\n }\n \n+static void odb_update_commondir(const char *name UNUSED,\n+\t\t\t\t const char *old_cwd,\n+\t\t\t\t const char *new_cwd,\n+\t\t\t\t void *cb_data)\n+{\n+\tstruct object_database *odb = cb_data;\n+\tstruct odb_source *source;\n+\n+\t/*\n+\t * In theory, we only have to do this for the primary object source, as\n+\t * alternates' paths are always resolved to an absolute path.\n+\t */\n+\tfor (source = odb->sources; source; source = source->next) {\n+\t\tchar *path;\n+\n+\t\tif (is_absolute_path(source->path))\n+\t\t\tcontinue;\n+\n+\t\tpath = reparent_relative_path(old_cwd, new_cwd,\n+\t\t\t\t\t      source->path);\n+\n+\t\tfree(source->path);\n+\t\tsource->path = path;\n+\t}\n+}\n+\n struct object_database *odb_new(struct repository *repo,\n \t\t\t\tconst char *primary_source,\n \t\t\t\tconst char *secondary_sources)\n@@ -1055,6 +1082,8 @@ struct object_database *odb_new(struct repository *repo,\n \n \tfree(to_free);\n \n+\tchdir_notify_register(NULL, odb_update_commondir, o);\n+\n \treturn o;\n }\n \n@@ -1106,6 +1135,8 @@ void odb_free(struct object_database *o)\n \tpackfile_store_free(o->packfiles);\n \tstring_list_clear(&o->submodule_source_paths, 0);\n \n+\tchdir_notify_unregister(NULL, odb_update_commondir, o);\n+\n \tfree(o);\n }\n \ndiff --git a/odb.h b/odb.h\nindex 41b3c03027..014cd9585a 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -78,10 +78,6 @@ struct odb_source {\n \tchar *path;\n };\n \n-struct odb_source *odb_source_new(struct object_database *odb,\n-\t\t\t\t  const char *path,\n-\t\t\t\t  bool local);\n-\n struct packed_git;\n struct packfile_store;\n struct cached_object_entry;\ndiff --git a/repository.c b/repository.c\nindex 5975c8f341..863f24411b 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -165,17 +165,10 @@ void repo_set_gitdir(struct repository *repo,\n \n \trepo_set_commondir(repo, o->commondir);\n \n-\tif (!repo->objects) {\n+\tif (!repo->objects)\n \t\trepo->objects = odb_new(repo, o->object_dir, o->alternate_db);\n-\t} else {\n-\t\tchar *objects_path = NULL;\n-\t\texpand_base_dir(&objects_path, o->object_dir,\n-\t\t\t\trepo->commondir, \"objects\");\n-\t\tfree(repo->objects->sources->path);\n-\t\trepo->objects->sources->path = objects_path;\n-\t\tfree(repo->objects->alternate_db);\n-\t\trepo->objects->alternate_db = xstrdup_or_null(o->alternate_db);\n-\t}\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 \ndiff --git a/repository.h b/repository.h\nindex 614649413b..6063c4b846 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -195,6 +195,7 @@ struct set_gitdir_args {\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 a752e9fc84..a625f9fbc8 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1002,10 +1002,51 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)\n \treturn error_code ? NULL : path;\n }\n \n-static void set_git_dir_1(const char *path)\n+static void setup_git_env_internal(const char *git_dir,\n+\t\t\t\t   bool skip_initializing_odb)\n+{\n+\tchar *git_replace_ref_base;\n+\tconst char *shallow_file;\n+\tconst char *replace_ref_base;\n+\tstruct set_gitdir_args args = { NULL };\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(the_repository, git_dir, &args);\n+\tstrvec_clear(&to_free);\n+\n+\tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n+\t\tdisable_replace_refs();\n+\treplace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);\n+\tgit_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base\n+\t\t\t\t\t\t\t  : \"refs/replace/\");\n+\tupdate_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);\n+\n+\tshallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);\n+\tif (shallow_file)\n+\t\tset_alternate_shallow_file(the_repository, shallow_file, 0);\n+\n+\tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n+\t\tfetch_if_missing = 0;\n+}\n+\n+void setup_git_env(const char *git_dir)\n+{\n+\tsetup_git_env_internal(git_dir, false);\n+}\n+\n+static void set_git_dir_1(const char *path, bool skip_initializing_odb)\n {\n \txsetenv(GIT_DIR_ENVIRONMENT, path, 1);\n-\tsetup_git_env(path);\n+\tsetup_git_env_internal(path, skip_initializing_odb);\n }\n \n static void update_relative_gitdir(const char *name UNUSED,\n@@ -1020,7 +1061,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(path);\n+\tset_git_dir_1(path, true);\n \tif (tmp_objdir)\n \t\ttmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd);\n \tfree(path);\n@@ -1035,7 +1076,7 @@ static void set_git_dir(const char *path, int make_realpath)\n \t\tpath = realpath.buf;\n \t}\n \n-\tset_git_dir_1(path);\n+\tset_git_dir_1(path, false);\n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, update_relative_gitdir, NULL);\n \n@@ -1668,41 +1709,6 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir,\n \treturn result;\n }\n \n-void setup_git_env(const char *git_dir)\n-{\n-\tchar *git_replace_ref_base;\n-\tconst char *shallow_file;\n-\tconst char *replace_ref_base;\n-\tstruct set_gitdir_args args = { NULL };\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-\t}\n-\n-\trepo_set_gitdir(the_repository, git_dir, &args);\n-\tstrvec_clear(&to_free);\n-\n-\tif (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))\n-\t\tdisable_replace_refs();\n-\treplace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);\n-\tgit_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base\n-\t\t\t\t\t\t\t  : \"refs/replace/\");\n-\tupdate_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);\n-\n-\tshallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);\n-\tif (shallow_file)\n-\t\tset_alternate_shallow_file(the_repository, shallow_file, 0);\n-\n-\tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n-}\n-\n const char *enter_repo(const char *path, unsigned flags)\n {\n \tstatic struct strbuf validated_path = STRBUF_INIT;\n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"530982","messageId":"20251119-b4-pks-odb-creation-v1-13-2b2ed2612cb6@pks.im","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-0-2b2ed2612cb6@pks.im","subject":"[PATCH 13/13] odb: handle recreation of quarantine directories","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-19T07:51:01Z","receivedAt":"2025-11-19T07:51:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the preceding commit we have moved the logic that reparents object\ndatabase sources on chdir(3p) from \"setup.c\" into \"odb.c\". Let's also do\nthe same for any temporary quarantine directories so that the complete\nreparenting logic is self-contained in \"odb.c\".\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c   | 7 +++++++\n setup.c | 5 -----\n 2 files changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 70665fb7f4..dc8f292f3d 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -24,6 +24,7 @@\n #include \"strbuf.h\"\n #include \"strvec.h\"\n #include \"submodule.h\"\n+#include \"tmp-objdir.h\"\n #include \"trace2.h\"\n #include \"write-or-die.h\"\n \n@@ -1041,8 +1042,11 @@ static void odb_update_commondir(const char *name UNUSED,\n \t\t\t\t void *cb_data)\n {\n \tstruct object_database *odb = cb_data;\n+\tstruct tmp_objdir *tmp_objdir;\n \tstruct odb_source *source;\n \n+\ttmp_objdir = tmp_objdir_unapply_primary_odb();\n+\n \t/*\n \t * In theory, we only have to do this for the primary object source, as\n \t * alternates' paths are always resolved to an absolute path.\n@@ -1059,6 +1063,9 @@ static void odb_update_commondir(const char *name UNUSED,\n \t\tfree(source->path);\n \t\tsource->path = path;\n \t}\n+\n+\tif (tmp_objdir)\n+\t\ttmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd);\n }\n \n struct object_database *odb_new(struct repository *repo,\ndiff --git a/setup.c b/setup.c\nindex a625f9fbc8..ae66188af3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -22,7 +22,6 @@\n #include \"chdir-notify.h\"\n #include \"path.h\"\n #include \"quote.h\"\n-#include \"tmp-objdir.h\"\n #include \"trace.h\"\n #include \"trace2.h\"\n #include \"worktree.h\"\n@@ -1056,14 +1055,10 @@ static void update_relative_gitdir(const char *name UNUSED,\n {\n \tchar *path = reparent_relative_path(old_cwd, new_cwd,\n \t\t\t\t\t    repo_get_git_dir(the_repository));\n-\tstruct tmp_objdir *tmp_objdir = tmp_objdir_unapply_primary_odb();\n-\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(path, true);\n-\tif (tmp_objdir)\n-\t\ttmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd);\n \tfree(path);\n }\n \n\n-- \n2.52.0.rc2.482.gaa765fefd0.dirty\n\n"},{"id":"531007","messageId":"xmqqcy5dbvay.fsf@gitster.g","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-5-2b2ed2612cb6@pks.im","subject":"Re: [PATCH 05/13] odb: move logic to disable ref updates into repo","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T20:51:17Z","receivedAt":"2025-11-19T20:51:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> ...\n> somewhat misdesigned:\n>\n>   - We can have multiple ODB sources, but only the currently active\n>     source inhibits reference updates.\n>\n>   - We're mixing concerns of the refbd with the ODB.\n\n\"refbd\" -> \"refdb\"?\n\n"},{"id":"531008","messageId":"xmqq8qg1buwv.fsf@gitster.g","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-6-2b2ed2612cb6@pks.im","subject":"Re: [PATCH 06/13] oidset: introduce `oidset_equal()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T20:59:44Z","receivedAt":"2025-11-19T20:59:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Introduce a new function that allows the caller to verify whether two\n> oidsets contain the exact same object IDs.\n>\n> Note that this change requires us to change `oidset_iter_init()` to\n> accept a `const struct oidset`.\n\nIterator shouldn't mutate the set it is iterating over, so\nthat sounds good.\n\n"},{"id":"531017","messageId":"xmqq1pltbtm0.fsf@gitster.g","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-7-2b2ed2612cb6@pks.im","subject":"Re: [PATCH 07/13] builtin/index-pack: fix deferred fsck outside repos","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T21:27:51Z","receivedAt":"2025-11-19T21:27:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> There's another option though: instead of skipping the final object\n> checks, we can die if there are any queued object checks. With this\n> change we now die exactly if and only if we would have previously\n> segfaulted. Like this we ensure that objects that _may_ fail the\n> consistency checks won't be silently skipped, and at the same time we\n> give users a much better error message.\n\nA packfile stream may not have the blob objects these tree entries\nrefer to, in which case index-pack cannot work outside a repository,\nbut I think that is fine.\n\n> @@ -2110,8 +2110,23 @@ int cmd_index_pack(int argc,\n>  \telse\n>  \t\tclose(input_fd);\n>  \n> -\tif (do_fsck_object && fsck_finish(&fsck_options))\n> -\t\tdie(_(\"fsck error in pack objects\"));\n> +\tif (do_fsck_object) {\n> +\t\t/*\n> +\t\t * We cannot perform queued consistency checks when running\n> +\t\t * outside of a repository because those require us to read\n> +\t\t * from the object database, which is uninitialized.\n> +\t\t *\n> +\t\t * TODO: we may eventually set up an in-memory object database,\n> +\t\t * which would allow us to perform these queued checks.\n> +\t\t */\n> +\t\tif (!startup_info->have_repository &&\n> +\t\t    fsck_has_queued_checks(&fsck_options))\n> +\t\t\tdie(_(\"cannot perform queued object checks outside \"\n> +\t\t\t      \"of a repository\"));\n> +\n> +\t\tif (fsck_finish(&fsck_options))\n> +\t\t\tdie(_(\"fsck error in pack objects\"));\n> +\t}\n\nOK.\n\n> +bool fsck_has_queued_checks(struct fsck_options *options)\n> +{\n> +\treturn !oidset_equal(&options->gitmodules_found, &options->gitmodules_done) ||\n> +\t       !oidset_equal(&options->gitattributes_found, &options->gitattributes_done);\n> +}\n\nSo, if we see a tree entry for these special blobs (and remember\nthem in the _found oid set) before we see the blobs, fsck_blob()\nwould notice that it is looking at the blob that is in these _found\nset, and throw it in _done set while checking the blob in-core.\n\nA packfile we generate has trees before blobs, so a self contained\npack stream should still be validatable outside a repository with\nthis code, but other people's reimplementations of Git may produce\na packfile that has a blob before a tree that refers to the blob.\nIn other words, we can validate a self contained pack stream outside\nrepository on a best-effort basis.  And that is perfectly fine.\n\n"},{"id":"531085","messageId":"xmqq34687414.fsf@gitster.g","threadId":"64510","inReplyTo":"20251119-b4-pks-odb-creation-v1-12-2b2ed2612cb6@pks.im","subject":"Re: [PATCH 12/13] odb: handle changing a repository's commondir","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-20T22:06:15Z","receivedAt":"2025-11-20T22:06:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> -static void set_git_dir_1(const char *path)\n> +static void setup_git_env_internal(const char *git_dir,\n> +\t\t\t\t   bool skip_initializing_odb)\n> +{\n\nHopefully we won't gain too many callers of this function, and ...\n\n> +static void set_git_dir_1(const char *path, bool skip_initializing_odb)\n>  {\n\n... this function, as ...\n\n> -\tset_git_dir_1(path);\n> +\tset_git_dir_1(path, true);\n> ...\n> -\tset_git_dir_1(path);\n> +\tset_git_dir_1(path, false);\n\n... it is almost impossible to tell from the call site which one is\nfor initializing the ODB (hint: \"true\" does initialize the ODB, oh,\nno it is the other way around, or is it correct?  now everybody is\nconfused).\n\nWe could do \"enum { INIT_DB, NO_INIT_DB }\" instead of bool and the\ncalling sites would become self-describing, but as long as we won't\nhave too many calling sites, the current code should be OK.\n\n"},{"id":"531127","messageId":"aSAZSyUzIMvn-IvR@pks.im","threadId":"64510","inReplyTo":"xmqqcy5dbvay.fsf@gitster.g","subject":"Re: [PATCH 05/13] odb: move logic to disable ref updates into repo","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-21T07:48:27Z","receivedAt":"2025-11-21T07:48:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Nov 19, 2025 at 12:51:17PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > ...\n> > somewhat misdesigned:\n> >\n> >   - We can have multiple ODB sources, but only the currently active\n> >     source inhibits reference updates.\n> >\n> >   - We're mixing concerns of the refbd with the ODB.\n> \n> \"refbd\" -> \"refdb\"?\n\nAh, yes. Fixed locally, thanks!\n\nPatrick\n"},{"id":"531128","messageId":"aSAZU4DlJ7CS7HLj@pks.im","threadId":"64510","inReplyTo":"xmqq1pltbtm0.fsf@gitster.g","subject":"Re: [PATCH 07/13] builtin/index-pack: fix deferred fsck outside repos","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-21T07:48:35Z","receivedAt":"2025-11-21T07:48:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Nov 19, 2025 at 01:27:51PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > +bool fsck_has_queued_checks(struct fsck_options *options)\n> > +{\n> > +\treturn !oidset_equal(&options->gitmodules_found, &options->gitmodules_done) ||\n> > +\t       !oidset_equal(&options->gitattributes_found, &options->gitattributes_done);\n> > +}\n> \n> So, if we see a tree entry for these special blobs (and remember\n> them in the _found oid set) before we see the blobs, fsck_blob()\n> would notice that it is looking at the blob that is in these _found\n> set, and throw it in _done set while checking the blob in-core.\n\nYup.\n\n> A packfile we generate has trees before blobs, so a self contained\n> pack stream should still be validatable outside a repository with\n> this code, but other people's reimplementations of Git may produce\n> a packfile that has a blob before a tree that refers to the blob.\n> In other words, we can validate a self contained pack stream outside\n> repository on a best-effort basis.  And that is perfectly fine.\n\nYeah, that was my reasoning, as well. Ideally we'd of course do better\nhere and be able to validate a fully self-contained packfile in all\ncases. But that's a bigger change that isn't easy to do now -- it will\nbecome easier though once we have proper pluggable object databases.\n\nMeanwhile, I guess having a proper error message is better than\ncrashing.\n\nThanks!\n\nPatrick\n"},{"id":"531129","messageId":"aSAfAJscjYHAg3Mc@pks.im","threadId":"64510","inReplyTo":"xmqq34687414.fsf@gitster.g","subject":"Re: [PATCH 12/13] odb: handle changing a repository's commondir","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-21T08:12:48Z","receivedAt":"2025-11-21T08:12:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Nov 20, 2025 at 02:06:15PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > -static void set_git_dir_1(const char *path)\n> > +static void setup_git_env_internal(const char *git_dir,\n> > +\t\t\t\t   bool skip_initializing_odb)\n> > +{\n> \n> Hopefully we won't gain too many callers of this function, and ...\n> \n> > +static void set_git_dir_1(const char *path, bool skip_initializing_odb)\n> >  {\n> \n> ... this function, as ...\n> \n> > -\tset_git_dir_1(path);\n> > +\tset_git_dir_1(path, true);\n> > ...\n> > -\tset_git_dir_1(path);\n> > +\tset_git_dir_1(path, false);\n> \n> ... it is almost impossible to tell from the call site which one is\n> for initializing the ODB (hint: \"true\" does initialize the ODB, oh,\n> no it is the other way around, or is it correct?  now everybody is\n> confused).\n> \n> We could do \"enum { INIT_DB, NO_INIT_DB }\" instead of bool and the\n> calling sites would become self-describing, but as long as we won't\n> have too many calling sites, the current code should be OK.\n\nQuite frankly, the whole \"setup.c\" file could use a makeover. I would\nclaim it's almost impossible to understand the different flows we have\nhere, and the setup of a repository (or `the_reposiory`) is awfully\ncomplex and non-obvious.\n\nSome ICs in my team might tackle this in the Git 2.53 release cycle,\nhopefully.\n\nPatrick\n"}]}