{"thread":{"id":"65868","subject":"[PATCH v6 01/11] setup: inline `check_and_apply_repository_format()`","startedAt":"2026-06-25T09:20:13Z","lastAt":"2026-06-26T19:44:22Z","messageCount":17,"participants":["Patrick Steinhardt","Justin Tobler","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":6,"patchTotal":11},"messages":[{"id":"546386","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260610-b4-pks-refs-avoid-chdir-notify-reparent-v1-0-56c864b01c43@pks.im","subject":"[PATCH v6 00/11] refs: fix \"onbranch\" conditions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:19:58Z","receivedAt":"2026-06-25T09:20:12Z","isPatch":true,"body":"Hi,\n\noriginally, this patch series was a follow-up of the discussion at [1],\nwhere it converted the reference backends to always use absolute paths\ninternally so that we could drop the `chdir_notify_reparent()`\nmachinery. But this focus shifted as we discovered that this led to\nquite a sizeable performance regression.\n\nInstead, the series now focusses on fixing handling of the \"onbranch\"\nconditions. As part of the above work I discovered that we recurse when\ncreating the main reference database in case we have \"onbranch\"\nconditions, and that recursion caused us to construct an ad-hoc\nreference store that we essentially discarded. The leak wasn't ever\ncatched though because the store is kept alive by the `chdir_notify`\ninfrastructure.\n\nThis is a deeper-running issue though: the reference backends respect\nsome configuration guarded by \"onbranch\" conditions, but not all of\nthem. This issue is fixed by this series by lazy-loading all\nconfiguration so that we don't need to read any configuration when we\ninitialize the reference store. This fixes the recursion and makes us\nconsistently honor those \"onbranch\" conditions.\n\nThis series is built on top of 1ff279f340 (The 13th batch, 2026-06-09)\nwith ps/setup-centralize-odb-creation at 42b9d3dc9d (setup: construct\nobject database in `apply_repository_format()`, 2026-06-04) merged into\nit.\n\nChanges in v6:\n  - Drop redundant condition when setting the default for\n    \"core.logallrefupdates\".\n  - Leave breakcrumb for why we lazy-load write options for the \"files\"\n    backend.\n  - Fix commit message typo.\n  - Link to v5: https://patch.msgid.link/20260622-b4-pks-refs-avoid-chdir-notify-reparent-v5-0-018475013dbc@pks.im\n\nChanges in v5:\n  - Fix the \"onbranch\" recursion properly: instead of papering over the\n    issue, this series now refactors reference store initialization to\n    not read any configuration at all anymore. Instead, the config is\n    now parsed lazily. This fixes the recursion, but also makes us\n    respect configuration guarded by \"onbranch\" conditions properly.\n  - Link to v4: https://patch.msgid.link/20260619-b4-pks-refs-avoid-chdir-notify-reparent-v4-0-a6472be7acc4@pks.im\n\nChanges in v4:\n  - Fix the \"onbranch\" recursion at the root of the problem by\n    explicitly disabling the use of the ref store when parsing\n    configuration at ref store initialization time.\n  - Link to v3: https://patch.msgid.link/20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im\n\nChanges in v3:\n  - Reduce the scope of applying the GIT_REFERENCE_BACKEND environment\n    variable even further so that we really only do this when we end up\n    applying the reference format.\n  - Fix a commit message that still referred to the dropped last commit.\n  - Link to v2: https://patch.msgid.link/20260615-b4-pks-refs-avoid-chdir-notify-reparent-v2-0-f4854aa99859@pks.im\n\nChanges in v2:\n  - Drop the last patch. This seemingly destroys the whole purpose of\n    the patch series, but after Peff's hint that this is actually a\n    performance optimization I'm less inclined to drop the chdir_notify\n    infra. I still think that the remainder of the patches make sense\n    standalone, as they simplify \"setup.c\" and clean memory leaks. Going\n    forward I'd like to investigate the idea of introducing a `struct\n    fsroot` infrastructure that uses the platform-equivalent of openat\n    et al.\n  - Improve a couple of commit messages.\n  - Link to v1: https://patch.msgid.link/20260610-b4-pks-refs-avoid-chdir-notify-reparent-v1-0-56c864b01c43@pks.im\n\nThanks!\n\nPatrick\n\n[1]: <aifAVpxanV31KUpC@pks.im>\n\n---\nPatrick Steinhardt (11):\n      setup: inline `check_and_apply_repository_format()`\n      setup: stop applying repository format twice\n      setup: don't apply \"GIT_REFERENCE_BACKEND\" without a repository\n      refs: unregister reference stores from \"chdir_notify\"\n      chdir-notify: drop unused `chdir_notify_reparent()`\n      repository: free main reference database\n      refs: move parsing of \"core.logAllRefUpdates\" back into ref stores\n      refs/files: lazy-load configuration to fix chicken-and-egg\n      reftable: split up write options\n      refs/reftable: lazy-load configuration to fix chicken-and-egg\n      refs: protect against chicken-and-egg recursion\n\n builtin/checkout.c                  |   7 +-\n chdir-notify.c                      |  26 -----\n chdir-notify.h                      |   6 +-\n refs.c                              |  17 +++-\n refs.h                              |   9 ++\n refs/files-backend.c                |  76 ++++++++++++---\n refs/packed-backend.c               |  16 ++-\n refs/refs-internal.h                |   6 --\n refs/reftable-backend.c             | 177 ++++++++++++++++++++-------------\n reftable/reftable-stack.h           |  30 +++++-\n reftable/reftable-writer.h          |  17 +---\n reftable/stack.c                    | 100 ++++++++++++-------\n reftable/stack.h                    |   2 +-\n reftable/writer.c                   |  21 ++--\n reftable/writer.h                   |   1 +\n repo-settings.c                     |  16 ---\n repo-settings.h                     |   9 --\n repository.c                        |   5 +\n setup.c                             | 101 ++++++++-----------\n t/helper/test-reftable.c            |   2 +-\n t/t0600-reffiles-backend.sh         |  21 ++++\n t/t0613-reftable-write-options.sh   |  19 ++++\n t/t1400-update-ref.sh               |  12 +++\n t/unit-tests/lib-reftable.c         |   8 +-\n t/unit-tests/lib-reftable.h         |   2 +\n t/unit-tests/u-reftable-merged.c    |   9 +-\n t/unit-tests/u-reftable-readwrite.c |  38 ++++++--\n t/unit-tests/u-reftable-stack.c     | 189 ++++++++++++++++--------------------\n t/unit-tests/u-reftable-table.c     |   8 +-\n 29 files changed, 561 insertions(+), 389 deletions(-)\n\nRange-diff versus v5:\n\n 1:  27cb4688d9 =  1:  9281850ec4 setup: inline `check_and_apply_repository_format()`\n 2:  f0e9e95c25 =  2:  cdd6a01554 setup: stop applying repository format twice\n 3:  b3ecceae2e =  3:  748506f661 setup: don't apply \"GIT_REFERENCE_BACKEND\" without a repository\n 4:  8d70b43f8c =  4:  9ffc2e94c6 refs: unregister reference stores from \"chdir_notify\"\n 5:  190956a8f6 =  5:  080e7d175f chdir-notify: drop unused `chdir_notify_reparent()`\n 6:  504a0326c3 =  6:  5a2b973766 repository: free main reference database\n 7:  2f0ecfd9d9 !  7:  ecdbf37fde refs: move parsing of \"core.logAllRefUpdates\" back into ref stores\n    @@ setup.c: static int create_default_files(struct repository *repo,\n     +\n      \t\t/* allow template config file to override the default */\n     -\t\tif (repo_settings_get_log_all_ref_updates(repo) == LOG_REFS_UNSET)\n    -+\t\tif (repo_config_get_string_tmp(repo, \"core.logallrefupdates\", &value) ||\n    -+\t\t    refs_parse_log_all_ref_updates_config(value) == LOG_REFS_UNSET)\n    ++\t\tif (repo_config_get_string_tmp(repo, \"core.logallrefupdates\", &value))\n      \t\t\trepo_config_set(repo, \"core.logallrefupdates\", \"true\");\n     +\n      \t\tif (needs_work_tree_config(original_git_dir, work_tree))\n 8:  ea171d971d !  8:  618df32bb6 refs/files: lazy-load configuration to fix chicken-and-egg\n    @@ refs/files-backend.c: struct files_ref_store {\n     -\n      \tstruct ref_store *packed_ref_store;\n     +\n    ++\t/*\n    ++\t * Options used when writing references. These are parsed from the\n    ++\t * config lazily on first use via `files_ref_store_write_options()` so\n    ++\t * that we don't have to access the configuration when initializing the\n    ++\t * ref store. Do not access these fields directly, but use the accessor\n    ++\t * instead.\n    ++\t */\n     +\tstruct files_ref_store_write_options {\n     +\t\tenum log_refs_config log_all_ref_updates;\n     +\t\tint prefer_symlink_refs;\n 9:  e3bb0c90ef =  9:  13b14f9394 reftable: split up write options\n10:  74b389c2f1 ! 10:  ba12b6f164 refs/reftable: lazy-load configuration to fix chicken-and-egg\n    @@ Commit message\n         properly, add a generic test to t1400 that verifies that the user can\n         configure \"core.logAllRefUpdates\" via an \"onbranch\" condition. This is\n         mostly a nonsensical thing to do in the first place, but it serves as a\n    -    good sanity chekc.\n    +    good sanity check.\n     \n         Note that we had to move `should_write_log()` around so that it can\n         access the new `reftable_be_write_options()` function.\n11:  0cb5a85167 = 11:  85a675ada9 refs: protect against chicken-and-egg recursion\n\n---\nbase-commit: 255322df35357168daefec8523a3cdc849edd6c1\nchange-id: 20260609-b4-pks-refs-avoid-chdir-notify-reparent-a4eaf1edbcab\n\n"},{"id":"546385","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-1-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 01/11] setup: inline `check_and_apply_repository_format()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:19:59Z","receivedAt":"2026-06-25T09:20:13Z","isPatch":true,"body":"We have two callsites of `check_and_apply_repository_format()`. In a\nsubsequent commit we'll want to adapt one of those callsites to change\nthe order in which we read and apply the repository format, at which\npoint the helper function will not really be a good fit for us anymore.\n\nInline the function to both of the callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n setup.c | 47 ++++++++++++++++-------------------------------\n 1 file changed, 16 insertions(+), 31 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex b4652651df..a9db1f2c23 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1788,32 +1788,6 @@ int apply_repository_format(struct repository *repo,\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- * set_git_dir() before calling this, and use it only for \"are we in a valid\n- * repo?\".\n- *\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      enum apply_repository_format_flags flags)\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-\n-\tcheck_repository_format_gently(repo_get_git_dir(repo), fmt, NULL);\n-\tif (apply_repository_format(repo, fmt, flags, &err) < 0)\n-\t\tdie(\"%s\", err.buf);\n-\tstartup_info->have_repository = 1;\n-\n-\tclear_repository_format(&repo_fmt);\n-}\n-\n const char *enter_repo(struct repository *repo, const char *path, unsigned flags)\n {\n \tstatic struct strbuf validated_path = STRBUF_INIT;\n@@ -1887,9 +1861,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags\n \t}\n \n \tif (is_git_directory(\".\")) {\n+\t\tstruct repository_format fmt = REPOSITORY_FORMAT_INIT;\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\n \t\tset_git_dir(repo, \".\", 0);\n-\t\tcheck_and_apply_repository_format(repo, NULL,\n-\t\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n+\t\tcheck_repository_format_gently(\".\", &fmt, NULL);\n+\t\tif (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)\n+\t\t\tdie(\"%s\", err.buf);\n+\t\tstartup_info->have_repository = 1;\n+\n+\t\tclear_repository_format(&fmt);\n+\t\tstrbuf_release(&err);\n \t\treturn path;\n \t}\n \n@@ -2820,6 +2802,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 strbuf err = STRBUF_INIT;\n \n \tif (real_git_dir) {\n \t\tstruct stat st;\n@@ -2846,9 +2829,10 @@ 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_and_apply_repository_format(repo, &repo_fmt,\n-\t\t\t\t\t  APPLY_REPOSITORY_FORMAT_HONOR_ENV);\n-\n+\tcheck_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL);\n+\tif (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)\n+\t\tdie(\"%s\", err.buf);\n+\tstartup_info->have_repository = 1;\n \trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n \n \t/*\n@@ -2904,6 +2888,7 @@ int init_db(struct repository *repo,\n \t}\n \n \tclear_repository_format(&repo_fmt);\n+\tstrbuf_release(&err);\n \tfree(original_git_dir);\n \treturn 0;\n }\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546387","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-2-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 02/11] setup: stop applying repository format twice","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:00Z","receivedAt":"2026-06-25T09:20:14Z","isPatch":true,"body":"When discovering the repository in \"setup.c\" we apply the final\nrepository format multiple times:\n\n  - Once via `repository_format_configure()`, where we apply the hash\n    algorithm and ref storage format to both `struct repository_format`\n    and `struct repository`.\n\n  - And once via `apply_repository_format()`, where we apply these two\n    settings from `struct repository_format` to `struct repository`.\n\nWith the current flow both of these are in fact necessary. But this is\nonly because we call `repository_format_configure()` after we have\ncalled `apply_repository_format()`. Consequently, if we only changed the\nrepository format in `repository_format_configure()` it would never\npropagate to the repository.\n\nRefactor the code so that we first configure the repository format\nbefore applying it to the repository so that we can stop setting the\nhash and reference storage format multiple times.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n setup.c | 9 ++-------\n 1 file changed, 2 insertions(+), 7 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex a9db1f2c23..2748155964 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value,\n \treturn ret;\n }\n \n-static void repository_format_configure(struct repository *repo,\n-\t\t\t\t\tstruct repository_format *repo_fmt,\n+static void repository_format_configure(struct repository_format *repo_fmt,\n \t\t\t\t\tint hash, enum ref_storage_format ref_format)\n {\n \tstruct default_format_config cfg = {\n@@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo,\n \t} else if (cfg.hash != GIT_HASH_UNKNOWN) {\n \t\trepo_fmt->hash_algo = cfg.hash;\n \t}\n-\trepo_set_hash_algo(repo, repo_fmt->hash_algo);\n \n \tenv = getenv(\"GIT_DEFAULT_REF_FORMAT\");\n \tif (repo_fmt->version >= 0 &&\n@@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo,\n \n \t\tfree(backend);\n \t}\n-\n-\trepo_set_ref_storage_format(repo, repo_fmt->ref_storage_format,\n-\t\t\t\t    repo_fmt->ref_storage_payload);\n }\n \n int init_db(struct repository *repo,\n@@ -2830,10 +2825,10 @@ int init_db(struct repository *repo,\n \t * is an attempt to reinitialize new repository with an old tool.\n \t */\n \tcheck_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL);\n+\trepository_format_configure(&repo_fmt, hash, ref_storage_format);\n \tif (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)\n \t\tdie(\"%s\", err.buf);\n \tstartup_info->have_repository = 1;\n-\trepository_format_configure(repo, &repo_fmt, hash, ref_storage_format);\n \n \t/*\n \t * Ensure `core.hidedotfiles` is processed. This must happen after we\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546388","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-3-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 03/11] setup: don't apply \"GIT_REFERENCE_BACKEND\" without a repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:01Z","receivedAt":"2026-06-25T09:20:17Z","isPatch":true,"body":"When discovering a repository we eventually also apply the\n\"GIT_REFERENCE_BACKEND\" environment variable to the repository. There's\ntwo problems with that:\n\n  - We do this unconditionally, which is rather pointless: we really\n    only have to configure the repository when we have found one.\n\n  - We have already applied the repository format at that point in time,\n    so we need to manually reapply it.\n\nMove the logic around so that we only apply the environment variable\nwhen a repository was discovered. This also allows us to drop the\nexplcit call to `repo_set_ref_storage_format()` because we now adjust\nthe format before we apply it via `apply_repository_format()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n setup.c | 39 +++++++++++++++++++--------------------\n 1 file changed, 19 insertions(+), 20 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 2748155964..79125db565 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \tstatic struct strbuf cwd = STRBUF_INIT;\n \tstruct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;\n \tconst char *prefix = NULL;\n-\tconst char *ref_backend_uri;\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \n \t/*\n@@ -2032,6 +2031,25 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \n \t\tif (startup_info->have_repository) {\n \t\t\tstruct strbuf err = STRBUF_INIT;\n+\t\t\tconst char *ref_backend_uri;\n+\n+\t\t\t/*\n+\t\t\t * The env variable should override the repository config\n+\t\t\t * for 'extensions.refStorage'.\n+\t\t\t */\n+\t\t\tref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);\n+\t\t\tif (ref_backend_uri) {\n+\t\t\t\tchar *format;\n+\n+\t\t\t\tfree(repo_fmt.ref_storage_payload);\n+\n+\t\t\t\tparse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload);\n+\t\t\t\trepo_fmt.ref_storage_format = ref_storage_format_by_name(format);\n+\t\t\t\tif (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)\n+\t\t\t\t\tdie(_(\"unknown ref storage format: '%s'\"), format);\n+\n+\t\t\t\tfree(format);\n+\t\t\t}\n \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@@ -2057,25 +2075,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, \"\", 1);\n \t}\n \n-\t/*\n-\t * The env variable should override the repository config\n-\t * for 'extensions.refStorage'.\n-\t */\n-\tref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);\n-\tif (ref_backend_uri) {\n-\t\tchar *backend, *payload;\n-\t\tenum ref_storage_format format;\n-\n-\t\tparse_reference_uri(ref_backend_uri, &backend, &payload);\n-\t\tformat = ref_storage_format_by_name(backend);\n-\t\tif (format == REF_STORAGE_FORMAT_UNKNOWN)\n-\t\t\tdie(_(\"unknown ref storage format: '%s'\"), backend);\n-\t\trepo_set_ref_storage_format(repo, format, payload);\n-\n-\t\tfree(backend);\n-\t\tfree(payload);\n-\t}\n-\n \tsetup_original_cwd(repo);\n \n \tstrbuf_release(&dir);\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546389","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-4-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 04/11] refs: unregister reference stores from \"chdir_notify\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:02Z","receivedAt":"2026-06-25T09:20:20Z","isPatch":true,"body":"When creating reference stores we register them with the \"chdir_notify\"\nsubsystem. This is required because some of the paths we track may be\nrelative paths, so we have to reparent them in case the current working\ndirectory changes.\n\nBut while we register the reference stores, we never unregister them.\nThis can have multiple outcomes:\n\n  - For a repository's main reference database we essentially keep the\n    pointer alive. We never free that database, either, and our leak\n    checker doesn't notice because it's still registered.\n\n  - For submodule and worktree reference databases we do eventually free\n    them in `repo_clear()`, so we may keep pointers to free'd memory\n    registered. We never notice though as we don't tend to chdir around\n    in the middle of the process.\n\nWe never noticed either of these symptoms, but they are obviously bad.\n\nPartially fix those issues by unregistering the reference stores when\nreleasing them. The leak of the main reference database will be fixed in\na subsequent commit.\n\nNote that this requires us to use `chdir_notify_register()` instead of\n`chdir_notify_reparent()`, as there is no infrastructure to unregister the\nlatter.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/files-backend.c    | 22 +++++++++++++++++++---\n refs/packed-backend.c   | 16 +++++++++++++++-\n refs/reftable-backend.c | 16 +++++++++++++++-\n 3 files changed, 49 insertions(+), 5 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a4c7858787..296981584b 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)\n \t}\n }\n \n+static void files_ref_store_reparent(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 *payload)\n+{\n+\tstruct files_ref_store *refs = payload;\n+\tchar *tmp;\n+\n+\ttmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir);\n+\tfree(refs->base.gitdir);\n+\trefs->base.gitdir = tmp;\n+\n+\ttmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir);\n+\tfree(refs->gitcommondir);\n+\trefs->gitcommondir = tmp;\n+}\n+\n /*\n  * Create a new submodule ref cache and add it to the internal\n  * set of caches.\n@@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \n-\tchdir_notify_reparent(\"files-backend $GIT_DIR\", &refs->base.gitdir);\n-\tchdir_notify_reparent(\"files-backend $GIT_COMMONDIR\",\n-\t\t\t      &refs->gitcommondir);\n+\tchdir_notify_register(NULL, files_ref_store_reparent, refs);\n \n \tstrbuf_release(&refdir);\n \n@@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store)\n \tfree(refs->gitcommondir);\n \tref_store_release(refs->packed_ref_store);\n \tfree(refs->packed_ref_store);\n+\tchdir_notify_unregister(NULL, files_ref_store_reparent, refs);\n }\n \n static void files_reflog_path(struct files_ref_store *refs,\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 0acde48c45..499cb55dfa 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)\n \treturn snapshot->refs->base.repo->hash_algo->hexsz;\n }\n \n+static void packed_ref_store_reparent(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 *payload)\n+{\n+\tstruct packed_ref_store *refs = payload;\n+\tchar *tmp;\n+\n+\ttmp = reparent_relative_path(old_cwd, new_cwd, refs->path);\n+\tfree(refs->path);\n+\trefs->path = tmp;\n+}\n+\n /*\n  * Since packed-refs is only stored in the common dir, don't parse the\n  * payload and rely on the files-backend to set 'gitdir' correctly.\n@@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo,\n \n \tstrbuf_addf(&sb, \"%s/packed-refs\", gitdir);\n \trefs->path = strbuf_detach(&sb, NULL);\n-\tchdir_notify_reparent(\"packed-refs\", &refs->path);\n+\tchdir_notify_register(NULL, packed_ref_store_reparent, refs);\n \treturn ref_store;\n }\n \n@@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store)\n \tclear_snapshot(refs);\n \trollback_lock_file(&refs->lock);\n \tdelete_tempfile(&refs->tempfile);\n+\tchdir_notify_unregister(NULL, packed_ref_store_reparent, refs);\n \tfree(refs->path);\n }\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 4ae22922de..8c93070677 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value,\n \treturn 0;\n }\n \n+static void reftable_be_reparent(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 *payload)\n+{\n+\tstruct reftable_ref_store *refs = payload;\n+\tchar *tmp;\n+\n+\ttmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir);\n+\tfree(refs->base.gitdir);\n+\trefs->base.gitdir = tmp;\n+}\n+\n static struct ref_store *reftable_be_init(struct repository *repo,\n \t\t\t\t\t  const char *payload,\n \t\t\t\t\t  const char *gitdir,\n@@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \t\t\tgoto done;\n \t}\n \n-\tchdir_notify_reparent(\"reftables-backend $GIT_DIR\", &refs->base.gitdir);\n+\tchdir_notify_register(NULL, reftable_be_reparent, refs);\n \n done:\n \tassert(refs->err != REFTABLE_API_ERROR);\n@@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store)\n \t\tfree(be);\n \t}\n \tstrmap_clear(&refs->worktree_backends, 0);\n+\tchdir_notify_unregister(NULL, reftable_be_reparent, refs);\n }\n \n static int reftable_be_create_on_disk(struct ref_store *ref_store,\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546390","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-5-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 05/11] chdir-notify: drop unused `chdir_notify_reparent()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:03Z","receivedAt":"2026-06-25T09:20:22Z","isPatch":true,"body":"With the preceding commit we've removed all callers of\n`chdir_notify_reparent()`, so the function is unused now. Drop it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n chdir-notify.c | 26 --------------------------\n chdir-notify.h |  6 +-----\n 2 files changed, 1 insertion(+), 31 deletions(-)\n\ndiff --git a/chdir-notify.c b/chdir-notify.c\nindex f8bfe3cbef..1237a45e2e 100644\n--- a/chdir-notify.c\n+++ b/chdir-notify.c\n@@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb,\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,\n-\t\t\tvoid *data)\n-{\n-\tchar **path = data;\n-\tchar *tmp = *path;\n-\n-\tif (!tmp)\n-\t\treturn;\n-\n-\t*path = reparent_relative_path(old_cwd, new_cwd, tmp);\n-\tfree(tmp);\n-\n-\tif (name) {\n-\t\ttrace_printf_key(&trace_setup_key,\n-\t\t\t\t \"setup: reparent %s to '%s'\",\n-\t\t\t\t name, *path);\n-\t}\n-}\n-\n-void chdir_notify_reparent(const char *name, char **path)\n-{\n-\tchdir_notify_register(name, reparent_cb, path);\n-}\n-\n int chdir_notify(const char *new_cwd)\n {\n \tstruct strbuf old_cwd = STRBUF_INIT;\ndiff --git a/chdir-notify.h b/chdir-notify.h\nindex 81eb69d846..36b4114472 100644\n--- a/chdir-notify.h\n+++ b/chdir-notify.h\n@@ -19,10 +19,7 @@\n  *   chdir_notify_register(\"description\", foo, data);\n  *\n  * In practice most callers will want to move a relative path to the new root;\n- * they can use the reparent_relative_path() helper for that. If that's all\n- * you're doing, you can also use the convenience function:\n- *\n- *   chdir_notify_reparent(\"description\", &my_path);\n+ * they can use the reparent_relative_path() helper for that.\n  *\n  * Whenever a chdir event occurs, that will update my_path (if it's relative)\n  * to adjust for the new cwd by freeing any existing string and allocating a\n@@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name,\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\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546391","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-6-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 06/11] repository: free main reference database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:04Z","receivedAt":"2026-06-25T09:20:25Z","isPatch":true,"body":"While we release worktree and submodule reference databases when\nclearing a repository, we don't ever release the main reference\ndatabase. This memory leak went unnoticed because its pointer is\nkept alive by the \"chdir_notify\" subsystem.\n\nFix the memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n repository.c | 5 +++++\n 1 file changed, 5 insertions(+)\n\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..e2b5c6712b 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -421,6 +421,11 @@ void repo_clear(struct repository *repo)\n \t\tFREE_AND_NULL(repo->remote_state);\n \t}\n \n+\tif (repo->refs_private) {\n+\t\tref_store_release(repo->refs_private);\n+\t\tFREE_AND_NULL(repo->refs_private);\n+\t}\n+\n \tstrmap_for_each_entry(&repo->submodule_ref_stores, &iter, e)\n \t\tref_store_release(e->value);\n \tstrmap_clear(&repo->submodule_ref_stores, 1);\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546392","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-7-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 07/11] refs: move parsing of \"core.logAllRefUpdates\" back into ref stores","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:05Z","receivedAt":"2026-06-25T09:20:27Z","isPatch":true,"body":"In cc42c88945 (refs: extract out reflog config to generic layer,\n2026-05-04) we have refactored how we parse \"core.logAllRefUpdates\" so\nthat it happens in the generic layer. Unfortunately, this has worsened a\npreexisting issue where we may recurse when creating the reference store\nbecause of a chicken-and-egg problem between parsing the configuration\nand evaluating \"onbranch\" conditions.\n\nPrepare for a fix by essentially reverting that change so that we handle\nthis setting in the respective backends again. The backends are already\nparsing other configuration anyway, so by moving the logic back in there\nwe can ensure that all backend configuration is parsed the same way.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/checkout.c      |  7 +++++--\n refs.c                  | 10 +++++++++-\n refs.h                  |  9 +++++++++\n refs/files-backend.c    | 20 +++++++++++++++++---\n refs/refs-internal.h    |  6 ------\n refs/reftable-backend.c | 20 +++++++++++---------\n repo-settings.c         | 16 ----------------\n repo-settings.h         |  9 ---------\n setup.c                 |  6 +++++-\n 9 files changed, 56 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b78b3a1d16..aee84ca897 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -952,10 +952,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \tconst char *old_desc, *reflog_msg;\n \tif (opts->new_branch) {\n \t\tif (opts->new_orphan_branch) {\n-\t\t\tenum log_refs_config log_all_ref_updates =\n-\t\t\t\trepo_settings_get_log_all_ref_updates(the_repository);\n+\t\t\tenum log_refs_config log_all_ref_updates = LOG_REFS_UNSET;\n+\t\t\tconst char *value;\n \t\t\tchar *refname;\n \n+\t\t\tif (!repo_config_get_string_tmp(the_repository, \"core.logallrefupdates\", &value))\n+\t\t\t\tlog_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n+\n \t\t\trefname = mkpathdup(\"refs/heads/%s\", opts->new_orphan_branch);\n \t\t\tif (opts->new_branch_log &&\n \t\t\t    !should_autocreate_reflog(log_all_ref_updates, refname)) {\ndiff --git a/refs.c b/refs.c\nindex d3caa9a633..5b773b1c15 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1053,6 +1053,15 @@ static char *normalize_reflog_message(const char *msg)\n \treturn strbuf_detach(&sb, NULL);\n }\n \n+enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value)\n+{\n+\tif (value && !strcasecmp(value, \"always\"))\n+\t\treturn LOG_REFS_ALWAYS;\n+\telse if (git_config_bool(\"core.logallrefupdates\", value))\n+\t\treturn LOG_REFS_NORMAL;\n+\treturn LOG_REFS_NONE;\n+}\n+\n int should_autocreate_reflog(enum log_refs_config log_all_ref_updates,\n \t\t\t     const char *refname)\n {\n@@ -2327,7 +2336,6 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \tstruct ref_store *refs;\n \tstruct ref_store_init_options opts = {\n \t\t.access_flags = flags,\n-\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n \t};\n \n \tbe = find_ref_storage_backend(format);\ndiff --git a/refs.h b/refs.h\nindex 71d5c186d0..a381022c77 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -146,6 +146,15 @@ enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs,\n \n int refs_ref_exists(struct ref_store *refs, const char *refname);\n \n+enum log_refs_config {\n+\tLOG_REFS_UNSET = -1,\n+\tLOG_REFS_NONE = 0,\n+\tLOG_REFS_NORMAL,\n+\tLOG_REFS_ALWAYS\n+};\n+\n+enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value);\n+\n int should_autocreate_reflog(enum log_refs_config log_all_ref_updates,\n \t\t\t     const char *refname);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 296981584b..79fb6735e1 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -117,6 +117,21 @@ static void files_ref_store_reparent(const char *name UNUSED,\n \trefs->gitcommondir = tmp;\n }\n \n+static int files_ref_store_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *payload)\n+{\n+\tstruct files_ref_store *refs = payload;\n+\n+\tif (!strcmp(var, \"core.prefersymlinkrefs\")) {\n+\t\trefs->prefer_symlink_refs = git_config_bool(var, value);\n+\t} else if (!strcmp(var, \"core.logallrefupdates\")) {\n+\t\trefs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n+\t}\n+\n+\treturn 0;\n+}\n+\n /*\n  * Create a new submodule ref cache and add it to the internal\n  * set of caches.\n@@ -141,10 +156,9 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \trefs->packed_ref_store =\n \t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n \trefs->store_flags = opts->access_flags;\n-\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n-\n-\trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n+\trefs->log_all_ref_updates = LOG_REFS_UNSET;\n \n+\trepo_config(repo, files_ref_store_config, refs);\n \tchdir_notify_register(NULL, files_ref_store_reparent, refs);\n \n \tstrbuf_release(&refdir);\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex a08d58900e..c3ac7b556f 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -406,12 +406,6 @@ struct ref_store;\n struct ref_store_init_options {\n \t/* The kind of operations that the ref_store is allowed to perform. */\n \tunsigned int access_flags;\n-\n-\t/*\n-\t * Denotes under what conditions reflogs should be created when updating\n-\t * references.\n-\t */\n-\tenum log_refs_config log_all_ref_updates;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 8c93070677..5115a3f4ce 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -332,34 +332,36 @@ static void fill_reftable_log_record(struct reftable_log_record *log, const stru\n \n static int reftable_be_config(const char *var, const char *value,\n \t\t\t      const struct config_context *ctx,\n-\t\t\t      void *_opts)\n+\t\t\t      void *payload)\n {\n-\tstruct reftable_write_options *opts = _opts;\n+\tstruct reftable_ref_store *refs = payload;\n \n \tif (!strcmp(var, \"reftable.blocksize\")) {\n \t\tunsigned long block_size = git_config_ulong(var, value, ctx->kvi);\n \t\tif (block_size > 16777215)\n \t\t\tdie(\"reftable block size cannot exceed 16MB\");\n-\t\topts->block_size = block_size;\n+\t\trefs->write_options.block_size = block_size;\n \t} else if (!strcmp(var, \"reftable.restartinterval\")) {\n \t\tunsigned long restart_interval = git_config_ulong(var, value, ctx->kvi);\n \t\tif (restart_interval > UINT16_MAX)\n \t\t\tdie(\"reftable block size cannot exceed %u\", (unsigned)UINT16_MAX);\n-\t\topts->restart_interval = restart_interval;\n+\t\trefs->write_options.restart_interval = restart_interval;\n \t} else if (!strcmp(var, \"reftable.indexobjects\")) {\n-\t\topts->skip_index_objects = !git_config_bool(var, value);\n+\t\trefs->write_options.skip_index_objects = !git_config_bool(var, value);\n \t} else if (!strcmp(var, \"reftable.geometricfactor\")) {\n \t\tunsigned long factor = git_config_ulong(var, value, ctx->kvi);\n \t\tif (factor > UINT8_MAX)\n \t\t\tdie(\"reftable geometric factor cannot exceed %u\", (unsigned)UINT8_MAX);\n-\t\topts->auto_compaction_factor = factor;\n+\t\trefs->write_options.auto_compaction_factor = factor;\n \t} else if (!strcmp(var, \"reftable.locktimeout\")) {\n \t\tint64_t lock_timeout = git_config_int64(var, value, ctx->kvi);\n \t\tif (lock_timeout > LONG_MAX)\n \t\t\tdie(\"reftable lock timeout cannot exceed %\"PRIdMAX, (intmax_t)LONG_MAX);\n \t\tif (lock_timeout < 0 && lock_timeout != -1)\n \t\t\tdie(\"reftable lock timeout does not support negative values other than -1\");\n-\t\topts->lock_timeout_ms = lock_timeout;\n+\t\trefs->write_options.lock_timeout_ms = lock_timeout;\n+\t} else if (!strcmp(var, \"core.logallrefupdates\")) {\n+\t\trefs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n \t}\n \n \treturn 0;\n@@ -398,7 +400,6 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n@@ -415,8 +416,9 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \trefs->write_options.disable_auto_compact =\n \t\t!git_env_bool(\"GIT_TEST_REFTABLE_AUTOCOMPACTION\", 1);\n \trefs->write_options.lock_timeout_ms = 100;\n+\trefs->log_all_ref_updates = LOG_REFS_UNSET;\n \n-\trepo_config(repo, reftable_be_config, &refs->write_options);\n+\trepo_config(repo, reftable_be_config, refs);\n \n \t/*\n \t * It is somewhat unfortunate that we have to mirror the default block\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 208e09ff17..f3be3b8c5a 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -177,22 +177,6 @@ void repo_settings_set_big_file_threshold(struct repository *repo, unsigned long\n \trepo->settings.big_file_threshold = value;\n }\n \n-enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo)\n-{\n-\tconst char *value;\n-\n-\tif (!repo_config_get_string_tmp(repo, \"core.logallrefupdates\", &value)) {\n-\t\tif (value && !strcasecmp(value, \"always\"))\n-\t\t\treturn LOG_REFS_ALWAYS;\n-\t\telse if (git_config_bool(\"core.logallrefupdates\", value))\n-\t\t\treturn LOG_REFS_NORMAL;\n-\t\telse\n-\t\t\treturn LOG_REFS_NONE;\n-\t}\n-\n-\treturn LOG_REFS_UNSET;\n-}\n-\n int repo_settings_get_warn_ambiguous_refs(struct repository *repo)\n {\n \tprepare_repo_settings(repo);\ndiff --git a/repo-settings.h b/repo-settings.h\nindex cad9c3f0cc..e5253ead02 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -16,13 +16,6 @@ enum fetch_negotiation_setting {\n \tFETCH_NEGOTIATION_NOOP,\n };\n \n-enum log_refs_config {\n-\tLOG_REFS_UNSET = -1,\n-\tLOG_REFS_NONE = 0,\n-\tLOG_REFS_NORMAL,\n-\tLOG_REFS_ALWAYS\n-};\n-\n struct repo_settings {\n \tint initialized;\n \n@@ -86,8 +79,6 @@ struct repo_settings {\n void prepare_repo_settings(struct repository *r);\n void repo_settings_clear(struct repository *r);\n \n-/* Read the value for \"core.logAllRefUpdates\". */\n-enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo);\n /* Read the value for \"core.warnAmbiguousRefs\". */\n int repo_settings_get_warn_ambiguous_refs(struct repository *repo);\n /* Read the value for \"core.hooksPath\". */\ndiff --git a/setup.c b/setup.c\nindex 79125db565..592753457c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2584,10 +2584,14 @@ static int create_default_files(struct repository *repo,\n \tif (is_bare_repository())\n \t\trepo_config_set(repo, \"core.bare\", \"true\");\n \telse {\n+\t\tconst char *value;\n+\n \t\trepo_config_set(repo, \"core.bare\", \"false\");\n+\n \t\t/* allow template config file to override the default */\n-\t\tif (repo_settings_get_log_all_ref_updates(repo) == LOG_REFS_UNSET)\n+\t\tif (repo_config_get_string_tmp(repo, \"core.logallrefupdates\", &value))\n \t\t\trepo_config_set(repo, \"core.logallrefupdates\", \"true\");\n+\n \t\tif (needs_work_tree_config(original_git_dir, work_tree))\n \t\t\trepo_config_set(repo, \"core.worktree\", work_tree);\n \t}\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546393","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-8-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 08/11] refs/files: lazy-load configuration to fix chicken-and-egg","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:06Z","receivedAt":"2026-06-25T09:20:30Z","isPatch":true,"body":"When initializing the \"files\" reference backend we read the repository's\nconfig to parse \"core.preferSymlinkRefs\" and \"core.logAllRefUpdates\".\nThis results in a chicken-and-egg problem though, because parsing the\nconfiguration may require us to have access to the reference store\nalready when an \"onbranch\" condition exists.\n\nLuckily, all the configuration that we honor only relates to writing\nreferences. Consequently, we don't strictly need that configuration to\nbe readily available at initialization time, and we can easiliy defer\nparsing it to a later point in time.\n\nImplement this fix and add tests that verify that we can indeed properly\nparse these config knobs via an \"onbranch\" condition.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/files-backend.c        | 44 +++++++++++++++++++++++++++++++++-----------\n t/t0600-reffiles-backend.sh | 21 +++++++++++++++++++++\n 2 files changed, 54 insertions(+), 11 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 79fb6735e1..7ffe489f6a 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -84,12 +84,21 @@ struct files_ref_store {\n \tunsigned int store_flags;\n \n \tchar *gitcommondir;\n-\tenum log_refs_config log_all_ref_updates;\n-\tint prefer_symlink_refs;\n-\n \tstruct ref_cache *loose;\n-\n \tstruct ref_store *packed_ref_store;\n+\n+\t/*\n+\t * Options used when writing references. These are parsed from the\n+\t * config lazily on first use via `files_ref_store_write_options()` so\n+\t * that we don't have to access the configuration when initializing the\n+\t * ref store. Do not access these fields directly, but use the accessor\n+\t * instead.\n+\t */\n+\tstruct files_ref_store_write_options {\n+\t\tenum log_refs_config log_all_ref_updates;\n+\t\tint prefer_symlink_refs;\n+\t\tbool initialized;\n+\t} write_opts_lazy_loaded;\n };\n \n static void clear_loose_ref_cache(struct files_ref_store *refs)\n@@ -121,17 +130,31 @@ static int files_ref_store_config(const char *var, const char *value,\n \t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t  void *payload)\n {\n-\tstruct files_ref_store *refs = payload;\n+\tstruct files_ref_store_write_options *opts = payload;\n \n \tif (!strcmp(var, \"core.prefersymlinkrefs\")) {\n-\t\trefs->prefer_symlink_refs = git_config_bool(var, value);\n+\t\topts->prefer_symlink_refs = git_config_bool(var, value);\n \t} else if (!strcmp(var, \"core.logallrefupdates\")) {\n-\t\trefs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n+\t\topts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n \t}\n \n \treturn 0;\n }\n \n+static const struct files_ref_store_write_options *files_ref_store_write_options(struct files_ref_store *refs)\n+{\n+\tstruct files_ref_store_write_options *opts = &refs->write_opts_lazy_loaded;\n+\n+\tif (opts->initialized)\n+\t\treturn opts;\n+\n+\topts->log_all_ref_updates = LOG_REFS_UNSET;\n+\trepo_config(refs->base.repo, files_ref_store_config, opts);\n+\n+\topts->initialized = true;\n+\treturn opts;\n+}\n+\n /*\n  * Create a new submodule ref cache and add it to the internal\n  * set of caches.\n@@ -156,9 +179,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \trefs->packed_ref_store =\n \t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n \trefs->store_flags = opts->access_flags;\n-\trefs->log_all_ref_updates = LOG_REFS_UNSET;\n \n-\trepo_config(repo, files_ref_store_config, refs);\n \tchdir_notify_register(NULL, files_ref_store_reparent, refs);\n \n \tstrbuf_release(&refdir);\n@@ -1890,7 +1911,7 @@ static int log_ref_setup(struct files_ref_store *refs,\n \t\t\t const char *refname, int force_create,\n \t\t\t int *logfd, struct strbuf *err)\n {\n-\tenum log_refs_config log_refs_cfg = refs->log_all_ref_updates;\n+\tenum log_refs_config log_refs_cfg = files_ref_store_write_options(refs)->log_all_ref_updates;\n \tstruct strbuf logfile_sb = STRBUF_INIT;\n \tchar *logfile;\n \n@@ -3301,6 +3322,7 @@ static int files_transaction_finish(struct ref_store *ref_store,\n {\n \tstruct files_ref_store *refs =\n \t\tfiles_downcast(ref_store, 0, \"ref_transaction_finish\");\n+\tconst struct files_ref_store_write_options *write_opts = files_ref_store_write_options(refs);\n \tsize_t i;\n \tint ret = 0;\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -3340,7 +3362,7 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\t * We try creating a symlink, if that succeeds we continue to the\n \t\t * next update. If not, we try and create a regular symref.\n \t\t */\n-\t\tif (update->new_target && refs->prefer_symlink_refs)\n+\t\tif (update->new_target && write_opts->prefer_symlink_refs)\n \t\t\t/*\n \t\t\t * By using the `NOT_CONSTANT()` trick, we can avoid\n \t\t\t * errors by `clang`'s `-Wunreachable` logic that would\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 74bfa2e9ba..bbbf6fa422 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -519,4 +519,25 @@ test_expect_success 'symref transaction supports false symlink config' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success SYMLINKS,!MINGW,!WITH_BREAKING_CHANGES 'core.preferSymlinkRefs can be set up via onbranch condition' '\n+\ttest_when_finished \"git symbolic-ref -d TEST_SYMREF_HEAD\" &&\n+\ttest_when_finished \"rm -f .git/include\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\tcat >.git/include <<-\\EOF &&\n+\t[core]\n+\t\tpreferSymlinkRefs = true\n+\tEOF\n+\ttest_config includeIf.onbranch:\"$(git branch --show-current)\".path \\\n+\t\t\"$(pwd)/.git/include\" &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tsymref-create TEST_SYMREF_HEAD refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_symlink .git/TEST_SYMREF_HEAD &&\n+\ttest \"$(test_readlink .git/TEST_SYMREF_HEAD)\" = refs/heads/new\n+'\n+\n test_done\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546395","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-9-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 09/11] reftable: split up write options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:07Z","receivedAt":"2026-06-25T09:20:32Z","isPatch":true,"body":"When initializing the reftable stack the caller may optionally pass some\nwrite options. These write options mix up two different concerns though:\n\n  - Of course, they allow the caller to configure how new reftables are\n    being written.\n\n  - But they also allow the caller to configure the stack itself, like\n    its hash ID and the `on_reload` callback.\n\nThis is somewhat awkward, as it doesn't easily give the caller the\nflexibility to for example write multiple reftables with different\noptions. Furthermore, this requires us to eagerly parse relevant\nconfiguration when initializing the reftable backend.\n\nRefactor the code by splitting out those options that configure the\nstack itself. Creating a new stack will thus only require this limited\nset of options, whereas the caller is expected to pass write options to\nall functions that end up writing tables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/reftable-backend.c             |  29 +++---\n reftable/reftable-stack.h           |  30 +++++-\n reftable/reftable-writer.h          |  17 +---\n reftable/stack.c                    | 100 ++++++++++++-------\n reftable/stack.h                    |   2 +-\n reftable/writer.c                   |  21 ++--\n reftable/writer.h                   |   1 +\n t/helper/test-reftable.c            |   2 +-\n t/unit-tests/lib-reftable.c         |   8 +-\n t/unit-tests/lib-reftable.h         |   2 +\n t/unit-tests/u-reftable-merged.c    |   9 +-\n t/unit-tests/u-reftable-readwrite.c |  38 ++++++--\n t/unit-tests/u-reftable-stack.c     | 189 ++++++++++++++++--------------------\n t/unit-tests/u-reftable-table.c     |   8 +-\n 14 files changed, 258 insertions(+), 198 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 5115a3f4ce..608d71cf10 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -48,9 +48,9 @@ static void reftable_backend_on_reload(void *payload)\n \n static int reftable_backend_init(struct reftable_backend *be,\n \t\t\t\t const char *path,\n-\t\t\t\t const struct reftable_write_options *_opts)\n+\t\t\t\t const struct reftable_stack_options *_opts)\n {\n-\tstruct reftable_write_options opts = *_opts;\n+\tstruct reftable_stack_options opts = *_opts;\n \topts.on_reload = reftable_backend_on_reload;\n \topts.on_reload_payload = be;\n \treturn reftable_new_stack(&be->stack, path, &opts);\n@@ -140,6 +140,7 @@ struct reftable_ref_store {\n \t * is populated lazily when we try to resolve `worktrees/$worktree` refs.\n \t */\n \tstruct strmap worktree_backends;\n+\tstruct reftable_stack_options stack_options;\n \tstruct reftable_write_options write_options;\n \n \tunsigned int store_flags;\n@@ -190,7 +191,7 @@ static int backend_for_worktree(struct reftable_backend **out,\n \n \tCALLOC_ARRAY(*out, 1);\n \tstore->err = ret = reftable_backend_init(*out, worktree_dir.buf,\n-\t\t\t\t\t\t &store->write_options);\n+\t\t\t\t\t\t &store->stack_options);\n \tif (ret < 0) {\n \t\tfree(*out);\n \t\tgoto out;\n@@ -404,10 +405,10 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tswitch (repo->hash_algo->format_id) {\n \tcase GIT_SHA1_FORMAT_ID:\n-\t\trefs->write_options.hash_id = REFTABLE_HASH_SHA1;\n+\t\trefs->stack_options.hash_id = REFTABLE_HASH_SHA1;\n \t\tbreak;\n \tcase GIT_SHA256_FORMAT_ID:\n-\t\trefs->write_options.hash_id = REFTABLE_HASH_SHA256;\n+\t\trefs->stack_options.hash_id = REFTABLE_HASH_SHA256;\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"unknown hash algorithm %d\", repo->hash_algo->format_id);\n@@ -441,7 +442,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \t}\n \tstrbuf_addstr(&path, \"/reftable\");\n \trefs->err = reftable_backend_init(&refs->main_backend, path.buf,\n-\t\t\t\t\t  &refs->write_options);\n+\t\t\t\t\t  &refs->stack_options);\n \tif (refs->err)\n \t\tgoto done;\n \n@@ -457,7 +458,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \t\tstrbuf_addstr(&refdir, \"/reftable\");\n \n \t\trefs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf,\n-\t\t\t\t\t\t  &refs->write_options);\n+\t\t\t\t\t\t  &refs->stack_options);\n \t\tif (refs->err)\n \t\t\tgoto done;\n \t}\n@@ -997,6 +998,7 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,\n \t\tstruct reftable_addition *addition;\n \n \t\tret = reftable_stack_new_addition(&addition, be->stack,\n+\t\t\t\t\t\t  &refs->write_options,\n \t\t\t\t\t\t  REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \t\tif (ret) {\n \t\t\tif (ret == REFTABLE_LOCK_ERROR)\n@@ -1685,9 +1687,9 @@ static int reftable_be_optimize(struct ref_store *ref_store,\n \t\tstack = refs->main_backend.stack;\n \n \tif (opts->flags & REFS_OPTIMIZE_AUTO)\n-\t\tret = reftable_stack_auto_compact(stack);\n+\t\tret = reftable_stack_auto_compact(stack, &refs->write_options);\n \telse\n-\t\tret = reftable_stack_compact_all(stack, NULL);\n+\t\tret = reftable_stack_compact_all(stack, &refs->write_options, NULL);\n \tif (ret < 0) {\n \t\tret = error(_(\"unable to compact stack: %s\"),\n \t\t\t    reftable_error_str(ret));\n@@ -1721,8 +1723,8 @@ static int reftable_be_optimize_required(struct ref_store *ref_store,\n \tif (opts->flags & REFS_OPTIMIZE_AUTO)\n \t\tuse_heuristics = true;\n \n-\treturn reftable_stack_compaction_required(stack, use_heuristics,\n-\t\t\t\t\t\t  required);\n+\treturn reftable_stack_compaction_required(stack, &refs->write_options,\n+\t\t\t\t\t\t  use_heuristics, required);\n }\n \n struct write_create_symref_arg {\n@@ -1979,6 +1981,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,\n \tif (ret)\n \t\tgoto done;\n \tret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,\n+\t\t\t\t &refs->write_options,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n done:\n@@ -2009,6 +2012,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,\n \tif (ret)\n \t\tgoto done;\n \tret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,\n+\t\t\t\t &refs->write_options,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n done:\n@@ -2374,6 +2378,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,\n \targ.stack = be->stack;\n \n \tret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,\n+\t\t\t\t &refs->write_options,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n done:\n@@ -2446,6 +2451,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,\n \targ.stack = be->stack;\n \n \tret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,\n+\t\t\t\t &refs->write_options,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n \tassert(ret != REFTABLE_API_ERROR);\n@@ -2568,6 +2574,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,\n \t\tgoto done;\n \n \tret = reftable_stack_new_addition(&add, be->stack,\n+\t\t\t\t\t  &refs->write_options,\n \t\t\t\t\t  REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \tif (ret < 0)\n \t\tgoto done;\ndiff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h\nindex 5f7be573fa..11f9963f4f 100644\n--- a/reftable/reftable-stack.h\n+++ b/reftable/reftable-stack.h\n@@ -26,11 +26,29 @@\n  */\n struct reftable_stack;\n \n+/* Options related to opening a stack. */\n+struct reftable_stack_options {\n+\t/*\n+\t * 4-byte identifier (\"sha1\", \"s256\") of the hash. Defaults to SHA1 if\n+\t * unset.\n+\t */\n+\tenum reftable_hash hash_id;\n+\n+\t/*\n+\t * Callback function to execute whenever the stack is being reloaded.\n+\t * This can be used e.g. to discard cached information that relies on\n+\t * the old stack's data. The payload data will be passed as argument to\n+\t * the callback.\n+\t */\n+\tvoid (*on_reload)(void *payload);\n+\tvoid *on_reload_payload;\n+};\n+\n /* open a new reftable stack. The tables along with the table list will be\n  *  stored in 'dir'. Typically, this should be .git/reftables.\n  */\n int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n-\t\t       const struct reftable_write_options *opts);\n+\t\t       const struct reftable_stack_options *opts);\n \n /* returns the update_index at which a next table should be written. */\n uint64_t reftable_stack_next_update_index(struct reftable_stack *st);\n@@ -52,6 +70,7 @@ enum {\n  */\n int reftable_stack_new_addition(struct reftable_addition **dest,\n \t\t\t\tstruct reftable_stack *st,\n+\t\t\t\tconst struct reftable_write_options *opts,\n \t\t\t\tunsigned int flags);\n \n /* Adds a reftable to transaction. */\n@@ -77,7 +96,9 @@ void reftable_addition_destroy(struct reftable_addition *add);\n int reftable_stack_add(struct reftable_stack *st,\n \t\t       int (*write_table)(struct reftable_writer *wr,\n \t\t\t\t\t  void *write_arg),\n-\t\t       void *write_arg, unsigned flags);\n+\t\t       void *write_arg,\n+\t\t       const struct reftable_write_options *opts,\n+\t\t       unsigned flags);\n \n struct reftable_iterator;\n \n@@ -122,6 +143,7 @@ struct reftable_log_expiry_config {\n /* compacts all reftables into a giant table. Expire reflog entries if config is\n  * non-NULL */\n int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       const struct reftable_write_options *opts,\n \t\t\t       struct reftable_log_expiry_config *config);\n \n /*\n@@ -132,11 +154,13 @@ int reftable_stack_compact_all(struct reftable_stack *st,\n  * compacted to maintain geometric progression.\n  */\n int reftable_stack_compaction_required(struct reftable_stack *st,\n+\t\t\t\t       const struct reftable_write_options *opts,\n \t\t\t\t       bool use_heuristics,\n \t\t\t\t       bool *required);\n \n /* heuristically compact unbalanced table stack. */\n-int reftable_stack_auto_compact(struct reftable_stack *st);\n+int reftable_stack_auto_compact(struct reftable_stack *st,\n+\t\t\t\tconst struct reftable_write_options *opts);\n \n /* delete stale .ref tables. */\n int reftable_stack_clean(struct reftable_stack *st);\ndiff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h\nindex a66db415c8..6ff4ddfc60 100644\n--- a/reftable/reftable-writer.h\n+++ b/reftable/reftable-writer.h\n@@ -28,11 +28,6 @@ struct reftable_write_options {\n \t/* how often to write complete keys in each block. */\n \tuint16_t restart_interval;\n \n-\t/* 4-byte identifier (\"sha1\", \"s256\") of the hash.\n-\t * Defaults to SHA1 if unset\n-\t */\n-\tenum reftable_hash hash_id;\n-\n \t/* Default mode for creating files. If unset, use 0666 (+umask) */\n \tunsigned int default_permissions;\n \n@@ -60,15 +55,6 @@ struct reftable_write_options {\n \t * negative value will cause us to block indefinitely.\n \t */\n \tlong lock_timeout_ms;\n-\n-\t/*\n-\t * Callback function to execute whenever the stack is being reloaded.\n-\t * This can be used e.g. to discard cached information that relies on\n-\t * the old stack's data. The payload data will be passed as argument to\n-\t * the callback.\n-\t */\n-\tvoid (*on_reload)(void *payload);\n-\tvoid *on_reload_payload;\n };\n \n /* reftable_block_stats holds statistics for a single block type */\n@@ -114,7 +100,8 @@ struct reftable_writer;\n int reftable_writer_new(struct reftable_writer **out,\n \t\t\tssize_t (*writer_func)(void *, const void *, size_t),\n \t\t\tint (*flush_func)(void *),\n-\t\t\tvoid *writer_arg, const struct reftable_write_options *opts);\n+\t\t\tvoid *writer_arg, enum reftable_hash hash_id,\n+\t\t\tconst struct reftable_write_options *opts);\n \n /*\n  * Set the range of update indices for the records we will add. When writing a\ndiff --git a/reftable/stack.c b/reftable/stack.c\nindex 1fba96ddb3..ab12926708 100644\n--- a/reftable/stack.c\n+++ b/reftable/stack.c\n@@ -501,10 +501,10 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n }\n \n int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n-\t\t       const struct reftable_write_options *_opts)\n+\t\t       const struct reftable_stack_options *_opts)\n {\n \tstruct reftable_buf list_file_name = REFTABLE_BUF_INIT;\n-\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack_options opts = { 0 };\n \tstruct reftable_stack *p;\n \tint err;\n \n@@ -629,6 +629,7 @@ int reftable_stack_reload(struct reftable_stack *st)\n struct reftable_addition {\n \tstruct reftable_flock tables_list_lock;\n \tstruct reftable_stack *stack;\n+\tstruct reftable_write_options opts;\n \n \tchar **new_tables;\n \tsize_t new_tables_len, new_tables_cap;\n@@ -657,6 +658,7 @@ static void reftable_addition_close(struct reftable_addition *add)\n \n static int reftable_stack_init_addition(struct reftable_addition *add,\n \t\t\t\t\tstruct reftable_stack *st,\n+\t\t\t\t\tconst struct reftable_write_options *opts,\n \t\t\t\t\tunsigned int flags)\n {\n \tstruct reftable_buf lock_file_name = REFTABLE_BUF_INIT;\n@@ -664,15 +666,17 @@ static int reftable_stack_init_addition(struct reftable_addition *add,\n \n \tmemset(add, 0, sizeof(*add));\n \tadd->stack = st;\n+\tif (opts)\n+\t\tadd->opts = *opts;\n \n \terr = flock_acquire(&add->tables_list_lock, st->list_file,\n-\t\t\t    st->opts.lock_timeout_ms);\n+\t\t\t    add->opts.lock_timeout_ms);\n \tif (err < 0)\n \t\tgoto done;\n \n-\tif (st->opts.default_permissions) {\n+\tif (add->opts.default_permissions) {\n \t\tif (chmod(add->tables_list_lock.path,\n-\t\t\t  st->opts.default_permissions) < 0) {\n+\t\t\t  add->opts.default_permissions) < 0) {\n \t\t\terr = REFTABLE_IO_ERROR;\n \t\t\tgoto done;\n \t\t}\n@@ -702,12 +706,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add,\n static int stack_try_add(struct reftable_stack *st,\n \t\t\t int (*write_table)(struct reftable_writer *wr,\n \t\t\t\t\t    void *arg),\n-\t\t\t void *arg, unsigned flags)\n+\t\t\t void *arg,\n+\t\t\t const struct reftable_write_options *opts,\n+\t\t\t unsigned flags)\n {\n \tstruct reftable_addition add;\n \tint err;\n \n-\terr = reftable_stack_init_addition(&add, st, flags);\n+\terr = reftable_stack_init_addition(&add, st, opts, flags);\n \tif (err < 0)\n \t\tgoto done;\n \n@@ -723,9 +729,11 @@ static int stack_try_add(struct reftable_stack *st,\n \n int reftable_stack_add(struct reftable_stack *st,\n \t\t       int (*write)(struct reftable_writer *wr, void *arg),\n-\t\t       void *arg, unsigned flags)\n+\t\t       void *arg,\n+\t\t       const struct reftable_write_options *opts,\n+\t\t       unsigned flags)\n {\n-\tint err = stack_try_add(st, write, arg, flags);\n+\tint err = stack_try_add(st, write, arg, opts, flags);\n \tif (err < 0) {\n \t\tif (err == REFTABLE_OUTDATED_ERROR) {\n \t\t\t/* Ignore error return, we want to propagate\n@@ -810,7 +818,7 @@ int reftable_addition_commit(struct reftable_addition *add)\n \tif (err)\n \t\tgoto done;\n \n-\tif (!add->stack->opts.disable_auto_compact) {\n+\tif (!add->opts.disable_auto_compact) {\n \t\t/*\n \t\t * Auto-compact the stack to keep the number of tables in\n \t\t * control. It is possible that a concurrent writer is already\n@@ -820,7 +828,7 @@ int reftable_addition_commit(struct reftable_addition *add)\n \t\t * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`.\n \t\t * Both of these errors are benign, so we simply ignore them.\n \t\t */\n-\t\terr = reftable_stack_auto_compact(add->stack);\n+\t\terr = reftable_stack_auto_compact(add->stack, &add->opts);\n \t\tif (err < 0 && err != REFTABLE_LOCK_ERROR &&\n \t\t    err != REFTABLE_OUTDATED_ERROR)\n \t\t\tgoto done;\n@@ -834,6 +842,7 @@ int reftable_addition_commit(struct reftable_addition *add)\n \n int reftable_stack_new_addition(struct reftable_addition **dest,\n \t\t\t\tstruct reftable_stack *st,\n+\t\t\t\tconst struct reftable_write_options *opts,\n \t\t\t\tunsigned int flags)\n {\n \tint err;\n@@ -842,7 +851,7 @@ int reftable_stack_new_addition(struct reftable_addition **dest,\n \tif (!*dest)\n \t\treturn REFTABLE_OUT_OF_MEMORY_ERROR;\n \n-\terr = reftable_stack_init_addition(*dest, st, flags);\n+\terr = reftable_stack_init_addition(*dest, st, opts, flags);\n \tif (err) {\n \t\treftable_free(*dest);\n \t\t*dest = NULL;\n@@ -862,7 +871,7 @@ int reftable_addition_add(struct reftable_addition *add,\n \tstruct reftable_writer *wr = NULL;\n \tstruct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;\n \tstruct fd_writer writer = {\n-\t\t.opts = &add->stack->opts,\n+\t\t.opts = &add->opts,\n \t};\n \tint err = 0;\n \n@@ -883,9 +892,9 @@ int reftable_addition_add(struct reftable_addition *add,\n \terr = tmpfile_from_pattern(&tab_file, temp_tab_file_name.buf);\n \tif (err < 0)\n \t\tgoto done;\n-\tif (add->stack->opts.default_permissions) {\n+\tif (add->opts.default_permissions) {\n \t\tif (chmod(tab_file.path,\n-\t\t\t  add->stack->opts.default_permissions)) {\n+\t\t\t  add->opts.default_permissions)) {\n \t\t\terr = REFTABLE_IO_ERROR;\n \t\t\tgoto done;\n \t\t}\n@@ -893,7 +902,7 @@ int reftable_addition_add(struct reftable_addition *add,\n \n \twriter.fd = tab_file.fd;\n \terr = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,\n-\t\t\t\t  &writer, &add->stack->opts);\n+\t\t\t\t  &writer, add->stack->opts.hash_id, &add->opts);\n \tif (err < 0)\n \t\tgoto done;\n \n@@ -1066,13 +1075,14 @@ static int stack_write_compact(struct reftable_stack *st,\n static int stack_compact_locked(struct reftable_stack *st,\n \t\t\t\tsize_t first, size_t last,\n \t\t\t\tstruct reftable_log_expiry_config *config,\n+\t\t\t\tconst struct reftable_write_options *opts,\n \t\t\t\tstruct reftable_tmpfile *tab_file_out)\n {\n \tstruct reftable_buf next_name = REFTABLE_BUF_INIT;\n \tstruct reftable_buf tab_file_path = REFTABLE_BUF_INIT;\n \tstruct reftable_writer *wr = NULL;\n \tstruct fd_writer writer=  {\n-\t\t.opts = &st->opts,\n+\t\t.opts = opts,\n \t};\n \tstruct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;\n \tint err = 0;\n@@ -1094,15 +1104,15 @@ static int stack_compact_locked(struct reftable_stack *st,\n \tif (err < 0)\n \t\tgoto done;\n \n-\tif (st->opts.default_permissions &&\n-\t    chmod(tab_file.path, st->opts.default_permissions) < 0) {\n+\tif (opts->default_permissions &&\n+\t    chmod(tab_file.path, opts->default_permissions) < 0) {\n \t\terr = REFTABLE_IO_ERROR;\n \t\tgoto done;\n \t}\n \n \twriter.fd = tab_file.fd;\n \terr = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,\n-\t\t\t\t  &writer, &st->opts);\n+\t\t\t\t  &writer, st->opts.hash_id, opts);\n \tif (err < 0)\n \t\tgoto done;\n \n@@ -1150,6 +1160,7 @@ enum stack_compact_range_flags {\n static int stack_compact_range(struct reftable_stack *st,\n \t\t\t       size_t first, size_t last,\n \t\t\t       struct reftable_log_expiry_config *expiry,\n+\t\t\t       const struct reftable_write_options *opts,\n \t\t\t       unsigned int flags)\n {\n \tstruct reftable_buf tables_list_buf = REFTABLE_BUF_INIT;\n@@ -1175,7 +1186,7 @@ static int stack_compact_range(struct reftable_stack *st,\n \t * Hold the lock so that we can read \"tables.list\" and lock all tables\n \t * which are part of the user-specified range.\n \t */\n-\terr = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);\n+\terr = flock_acquire(&tables_list_lock, st->list_file, opts->lock_timeout_ms);\n \tif (err < 0)\n \t\tgoto done;\n \n@@ -1274,7 +1285,7 @@ static int stack_compact_range(struct reftable_stack *st,\n \t * these tables may end up with an empty new table in case tombstones\n \t * end up cancelling out all refs in that range.\n \t */\n-\terr = stack_compact_locked(st, first, last, expiry, &new_table);\n+\terr = stack_compact_locked(st, first, last, expiry, opts, &new_table);\n \tif (err < 0) {\n \t\tif (err != REFTABLE_EMPTY_TABLE_ERROR)\n \t\t\tgoto done;\n@@ -1286,13 +1297,13 @@ static int stack_compact_range(struct reftable_stack *st,\n \t * \"tables.list\". We'll then replace the compacted range of tables with\n \t * the new table.\n \t */\n-\terr = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);\n+\terr = flock_acquire(&tables_list_lock, st->list_file, opts->lock_timeout_ms);\n \tif (err < 0)\n \t\tgoto done;\n \n-\tif (st->opts.default_permissions) {\n+\tif (opts->default_permissions) {\n \t\tif (chmod(tables_list_lock.path,\n-\t\t\t  st->opts.default_permissions) < 0) {\n+\t\t\t  opts->default_permissions) < 0) {\n \t\t\terr = REFTABLE_IO_ERROR;\n \t\t\tgoto done;\n \t\t}\n@@ -1513,10 +1524,16 @@ static int stack_compact_range(struct reftable_stack *st,\n }\n \n int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       const struct reftable_write_options *opts,\n \t\t\t       struct reftable_log_expiry_config *config)\n {\n+\tstruct reftable_write_options opts_default = { 0 };\n \tsize_t last = st->merged->tables_len ? st->merged->tables_len - 1 : 0;\n-\treturn stack_compact_range(st, 0, last, config, 0);\n+\n+\tif (!opts)\n+\t\topts = &opts_default;\n+\n+\treturn stack_compact_range(st, 0, last, config, opts, 0);\n }\n \n static int segment_size(struct segment *s)\n@@ -1601,6 +1618,7 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n,\n }\n \n static int stack_segments_for_compaction(struct reftable_stack *st,\n+\t\t\t\t\t const struct reftable_write_options *opts,\n \t\t\t\t\t struct segment *seg)\n {\n \tint version = (st->opts.hash_id == REFTABLE_HASH_SHA1) ? 1 : 2;\n@@ -1615,13 +1633,14 @@ static int stack_segments_for_compaction(struct reftable_stack *st,\n \t\tsizes[i] = st->tables[i]->size - overhead;\n \n \t*seg = suggest_compaction_segment(sizes, st->merged->tables_len,\n-\t\t\t\t\t  st->opts.auto_compaction_factor);\n+\t\t\t\t\t  opts->auto_compaction_factor);\n \treftable_free(sizes);\n \n \treturn 0;\n }\n \n static int update_segment_if_compaction_required(struct reftable_stack *st,\n+\t\t\t\t\t\t const struct reftable_write_options *opts,\n \t\t\t\t\t\t struct segment *seg,\n \t\t\t\t\t\t bool use_geometric,\n \t\t\t\t\t\t bool *required)\n@@ -1638,7 +1657,7 @@ static int update_segment_if_compaction_required(struct reftable_stack *st,\n \t\treturn 0;\n \t}\n \n-\terr = stack_segments_for_compaction(st, seg);\n+\terr = stack_segments_for_compaction(st, opts, seg);\n \tif (err)\n \t\treturn err;\n \n@@ -1647,27 +1666,40 @@ static int update_segment_if_compaction_required(struct reftable_stack *st,\n }\n \n int reftable_stack_compaction_required(struct reftable_stack *st,\n+\t\t\t\t       const struct reftable_write_options *opts,\n \t\t\t\t       bool use_heuristics,\n \t\t\t\t       bool *required)\n {\n+\tstruct reftable_write_options opts_default = { 0 };\n \tstruct segment seg;\n-\treturn update_segment_if_compaction_required(st, &seg, use_heuristics,\n-\t\t\t\t\t\t     required);\n+\n+\tif (!opts)\n+\t\topts = &opts_default;\n+\n+\treturn update_segment_if_compaction_required(st, opts, &seg,\n+\t\t\t\t\t\t     use_heuristics, required);\n }\n \n-int reftable_stack_auto_compact(struct reftable_stack *st)\n+int reftable_stack_auto_compact(struct reftable_stack *st,\n+\t\t\t\tconst struct reftable_write_options *opts)\n {\n+\tstruct reftable_write_options opts_default = { 0 };\n \tstruct segment seg;\n \tbool required;\n \tint err;\n \n-\terr = update_segment_if_compaction_required(st, &seg, true, &required);\n+\tif (!opts)\n+\t\topts = &opts_default;\n+\n+\terr = update_segment_if_compaction_required(st, opts, &seg, true,\n+\t\t\t\t\t\t    &required);\n \tif (err)\n \t\treturn err;\n \n \tif (required)\n \t\treturn stack_compact_range(st, seg.start, seg.end - 1,\n-\t\t\t\t\t   NULL, STACK_COMPACT_RANGE_BEST_EFFORT);\n+\t\t\t\t\t   NULL, opts,\n+\t\t\t\t\t   STACK_COMPACT_RANGE_BEST_EFFORT);\n \n \treturn 0;\n }\n@@ -1807,7 +1839,7 @@ static int reftable_stack_clean_locked(struct reftable_stack *st)\n int reftable_stack_clean(struct reftable_stack *st)\n {\n \tstruct reftable_addition *add = NULL;\n-\tint err = reftable_stack_new_addition(&add, st, 0);\n+\tint err = reftable_stack_new_addition(&add, st, NULL, 0);\n \tif (err < 0) {\n \t\tgoto done;\n \t}\ndiff --git a/reftable/stack.h b/reftable/stack.h\nindex bc28f2998a..f7901e6c6f 100644\n--- a/reftable/stack.h\n+++ b/reftable/stack.h\n@@ -20,7 +20,7 @@ struct reftable_stack {\n \n \tchar *reftable_dir;\n \n-\tstruct reftable_write_options opts;\n+\tstruct reftable_stack_options opts;\n \n \tstruct reftable_table **tables;\n \tsize_t tables_len;\ndiff --git a/reftable/writer.c b/reftable/writer.c\nindex 0133b64975..f850e9d599 100644\n--- a/reftable/writer.c\n+++ b/reftable/writer.c\n@@ -80,9 +80,6 @@ static void options_set_defaults(struct reftable_write_options *opts)\n \t\topts->restart_interval = 16;\n \t}\n \n-\tif (opts->hash_id == 0) {\n-\t\topts->hash_id = REFTABLE_HASH_SHA1;\n-\t}\n \tif (opts->block_size == 0) {\n \t\topts->block_size = DEFAULT_BLOCK_SIZE;\n \t}\n@@ -90,7 +87,7 @@ static void options_set_defaults(struct reftable_write_options *opts)\n \n static int writer_version(struct reftable_writer *w)\n {\n-\treturn (w->opts.hash_id == 0 || w->opts.hash_id == REFTABLE_HASH_SHA1) ?\n+\treturn (w->hash_id == 0 || w->hash_id == REFTABLE_HASH_SHA1) ?\n \t\t\t     1 :\n \t\t\t     2;\n }\n@@ -107,7 +104,7 @@ static int writer_write_header(struct reftable_writer *w, uint8_t *dest)\n \tif (writer_version(w) == 2) {\n \t\tuint32_t hash_id;\n \n-\t\tswitch (w->opts.hash_id) {\n+\t\tswitch (w->hash_id) {\n \t\tcase REFTABLE_HASH_SHA1:\n \t\t\thash_id = REFTABLE_FORMAT_ID_SHA1;\n \t\t\tbreak;\n@@ -134,7 +131,7 @@ static int writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)\n \treftable_buf_reset(&w->last_key);\n \tret = block_writer_init(&w->block_writer_data, typ, w->block,\n \t\t\t\tw->opts.block_size, block_start,\n-\t\t\t\thash_size(w->opts.hash_id));\n+\t\t\t\thash_size(w->hash_id));\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -147,7 +144,9 @@ static int writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)\n int reftable_writer_new(struct reftable_writer **out,\n \t\t\tssize_t (*writer_func)(void *, const void *, size_t),\n \t\t\tint (*flush_func)(void *),\n-\t\t\tvoid *writer_arg, const struct reftable_write_options *_opts)\n+\t\t\tvoid *writer_arg,\n+\t\t\tenum reftable_hash hash_id,\n+\t\t\tconst struct reftable_write_options *_opts)\n {\n \tstruct reftable_write_options opts = {0};\n \tstruct reftable_writer *wp;\n@@ -162,6 +161,9 @@ int reftable_writer_new(struct reftable_writer **out,\n \tif (opts.block_size >= (1 << 24))\n \t\treturn REFTABLE_API_ERROR;\n \n+\tif (!hash_id)\n+\t\thash_id = REFTABLE_HASH_SHA1;\n+\n \treftable_buf_init(&wp->block_writer_data.last_key);\n \treftable_buf_init(&wp->last_key);\n \treftable_buf_init(&wp->scratch);\n@@ -173,6 +175,7 @@ int reftable_writer_new(struct reftable_writer **out,\n \twp->write = writer_func;\n \twp->write_arg = writer_arg;\n \twp->opts = opts;\n+\twp->hash_id = hash_id;\n \twp->flush = flush_func;\n \twriter_reinit_block_writer(wp, REFTABLE_BLOCK_TYPE_REF);\n \n@@ -367,7 +370,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,\n \tif (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) {\n \t\treftable_buf_reset(&w->scratch);\n \t\terr = reftable_buf_add(&w->scratch, (char *)reftable_ref_record_val1(ref),\n-\t\t\t\t       hash_size(w->opts.hash_id));\n+\t\t\t\t       hash_size(w->hash_id));\n \t\tif (err < 0)\n \t\t\tgoto out;\n \n@@ -379,7 +382,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,\n \tif (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) {\n \t\treftable_buf_reset(&w->scratch);\n \t\terr = reftable_buf_add(&w->scratch, reftable_ref_record_val2(ref),\n-\t\t\t\t       hash_size(w->opts.hash_id));\n+\t\t\t\t       hash_size(w->hash_id));\n \t\tif (err < 0)\n \t\t\tgoto out;\n \ndiff --git a/reftable/writer.h b/reftable/writer.h\nindex 9f53610b27..c08fc413e1 100644\n--- a/reftable/writer.h\n+++ b/reftable/writer.h\n@@ -27,6 +27,7 @@ struct reftable_writer {\n \tuint64_t next;\n \tuint64_t min_update_index, max_update_index;\n \tstruct reftable_write_options opts;\n+\tenum reftable_hash hash_id;\n \n \t/* memory buffer for writing */\n \tuint8_t *block;\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex b16c0722c8..fc49fafc34 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -174,7 +174,7 @@ static int dump_table(struct reftable_merged_table *mt)\n static int dump_stack(const char *stackdir, uint32_t hash_id)\n {\n \tstruct reftable_stack *stack = NULL;\n-\tstruct reftable_write_options opts = { .hash_id = hash_id };\n+\tstruct reftable_stack_options opts = { .hash_id = hash_id };\n \tstruct reftable_merged_table *merged = NULL;\n \n \tint err = reftable_new_stack(&stack, stackdir, &opts);\ndiff --git a/t/unit-tests/lib-reftable.c b/t/unit-tests/lib-reftable.c\nindex fdb5b11a20..19a3ac8b80 100644\n--- a/t/unit-tests/lib-reftable.c\n+++ b/t/unit-tests/lib-reftable.c\n@@ -25,11 +25,12 @@ static int strbuf_writer_flush(void *arg UNUSED)\n }\n \n struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,\n+\t\t\t\t\t\t enum reftable_hash hash_id,\n \t\t\t\t\t\t struct reftable_write_options *opts)\n {\n \tstruct reftable_writer *writer;\n \tint ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,\n-\t\t\t\t      buf, opts);\n+\t\t\t\t      buf, hash_id, opts);\n \tcl_assert(!ret);\n \treturn writer;\n }\n@@ -39,6 +40,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf,\n \t\t\t     size_t nrefs,\n \t\t\t     struct reftable_log_record *logs,\n \t\t\t     size_t nlogs,\n+\t\t\t     enum reftable_hash hash_id,\n \t\t\t     struct reftable_write_options *_opts)\n {\n \tstruct reftable_write_options opts = { 0 };\n@@ -66,7 +68,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf,\n \t\t\tmin = ui;\n \t}\n \n-\twriter = cl_reftable_strbuf_writer(buf, &opts);\n+\twriter = cl_reftable_strbuf_writer(buf, hash_id, &opts);\n \tret = reftable_writer_set_limits(writer, min, max);\n \tcl_assert(!ret);\n \n@@ -88,7 +90,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf,\n \t\tsize_t off = i * (opts.block_size ? opts.block_size\n \t\t\t\t\t\t  : DEFAULT_BLOCK_SIZE);\n \t\tif (!off)\n-\t\t\toff = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);\n+\t\t\toff = header_size(hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);\n \t\tcl_assert(buf->buf[off] == 'r');\n \t}\n \ndiff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\nindex d7e6d3136f..caf443d147 100644\n--- a/t/unit-tests/lib-reftable.h\n+++ b/t/unit-tests/lib-reftable.h\n@@ -10,6 +10,7 @@ struct reftable_buf;\n void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);\n \n struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,\n+\t\t\t\t\t\t enum reftable_hash hash_id,\n \t\t\t\t\t\t struct reftable_write_options *opts);\n \n void cl_reftable_write_to_buf(struct reftable_buf *buf,\n@@ -17,4 +18,5 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf,\n \t\t\t     size_t nrecords,\n \t\t\t     struct reftable_log_record *logs,\n \t\t\t     size_t nlogs,\n+\t\t\t     enum reftable_hash hash_id,\n \t\t\t     struct reftable_write_options *opts);\ndiff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c\nindex 54cb7fc2a7..21232c1e4f 100644\n--- a/t/unit-tests/u-reftable-merged.c\n+++ b/t/unit-tests/u-reftable-merged.c\n@@ -34,7 +34,8 @@ merged_table_from_records(struct reftable_ref_record **refs,\n \tcl_assert(*source != NULL);\n \n \tfor (size_t i = 0; i < n; i++) {\n-\t\tcl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);\n+\t\tcl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0,\n+\t\t\t\t\t REFTABLE_HASH_SHA1, &opts);\n \t\tblock_source_from_buf(&(*source)[i], &buf[i]);\n \n \t\terr = reftable_table_new(&(*tables)[i], &(*source)[i],\n@@ -357,7 +358,8 @@ merged_table_from_log_records(struct reftable_log_record **logs,\n \tcl_assert(*source != NULL);\n \n \tfor (size_t i = 0; i < n; i++) {\n-\t\tcl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts);\n+\t\tcl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i],\n+\t\t\t\t\t REFTABLE_HASH_SHA1, &opts);\n \t\tblock_source_from_buf(&(*source)[i], &buf[i]);\n \n \t\terr = reftable_table_new(&(*tables)[i], &(*source)[i],\n@@ -487,7 +489,8 @@ void test_reftable_merged__default_write_opts(void)\n {\n \tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1, &opts);\n \tstruct reftable_ref_record rec = {\n \t\t.refname = (char *) \"master\",\n \t\t.update_index = 1,\ndiff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c\nindex 4d8c4be5f1..5794b460c6 100644\n--- a/t/unit-tests/u-reftable-readwrite.c\n+++ b/t/unit-tests/u-reftable-readwrite.c\n@@ -48,7 +48,6 @@ static void write_table(char ***names, struct reftable_buf *buf, int N,\n {\n \tstruct reftable_write_options opts = {\n \t\t.block_size = block_size,\n-\t\t.hash_id = hash_id,\n \t};\n \tstruct reftable_ref_record *refs;\n \tstruct reftable_log_record *logs;\n@@ -78,7 +77,7 @@ static void write_table(char ***names, struct reftable_buf *buf, int N,\n \t\tlogs[i].value.update.message = (char *) \"message\";\n \t}\n \n-\tcl_reftable_write_to_buf(buf, refs, N, logs, N, &opts);\n+\tcl_reftable_write_to_buf(buf, refs, N, logs, N, hash_id, &opts);\n \n \treftable_free(refs);\n \treftable_free(logs);\n@@ -103,6 +102,7 @@ void test_reftable_readwrite__log_buffer_size(void)\n \t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n \t\t\t\t   } } };\n \tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n \t\t\t\t\t\t\t      &opts);\n \n \t/* This tests buffer extension for log compression. Must use a random\n@@ -143,6 +143,7 @@ void test_reftable_readwrite__log_overflow(void)\n \t\t},\n \t};\n \tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n \t\t\t\t\t\t\t      &opts);\n \n \tmemset(msg, 'x', sizeof(msg) - 1);\n@@ -157,6 +158,7 @@ void test_reftable_readwrite__log_write_limits(void)\n \tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n \tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n \t\t\t\t\t\t\t      &opts);\n \tstruct reftable_log_record log = {\n \t\t.refname = (char *)\"refs/head/master\",\n@@ -202,7 +204,9 @@ void test_reftable_readwrite__log_write_read(void)\n \tstruct reftable_table *table;\n \tstruct reftable_block_source source = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n+\t\t\t\t\t\t\t      &opts);\n \tconst struct reftable_stats *stats = NULL;\n \tint N = 2, i;\n \tchar **names;\n@@ -299,6 +303,7 @@ void test_reftable_readwrite__log_zlib_corruption(void)\n \tstruct reftable_block_source source = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n \tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n \t\t\t\t\t\t\t      &opts);\n \tconst struct reftable_stats *stats = NULL;\n \tchar message[100] = { 0 };\n@@ -531,6 +536,7 @@ static void t_table_refs_for(int indexed)\n \tstruct reftable_block_source source = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n \tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n \t\t\t\t\t\t\t      &opts);\n \tstruct reftable_iterator it = { 0 };\n \tint N = 50, j, i;\n@@ -622,7 +628,9 @@ void test_reftable_readwrite__write_empty_table(void)\n {\n \tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n+\t\t\t\t\t\t\t      &opts);\n \tstruct reftable_block_source source = { 0 };\n \tstruct reftable_table *table = NULL;\n \tstruct reftable_ref_record rec = { 0 };\n@@ -660,7 +668,9 @@ void test_reftable_readwrite__write_object_id_min_length(void)\n \t\t.block_size = 75,\n \t};\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n+\t\t\t\t\t\t\t      &opts);\n \tstruct reftable_ref_record ref = {\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n@@ -691,7 +701,9 @@ void test_reftable_readwrite__write_object_id_length(void)\n \t\t.block_size = 75,\n \t};\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n+\t\t\t\t\t\t\t      &opts);\n \tstruct reftable_ref_record ref = {\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n@@ -721,7 +733,9 @@ void test_reftable_readwrite__write_empty_key(void)\n {\n \tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n+\t\t\t\t\t\t\t      &opts);\n \tstruct reftable_ref_record ref = {\n \t\t.refname = (char *) \"\",\n \t\t.update_index = 1,\n@@ -740,7 +754,9 @@ void test_reftable_readwrite__write_key_order(void)\n {\n \tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf,\n+\t\t\t\t\t\t\t      REFTABLE_HASH_SHA1,\n+\t\t\t\t\t\t\t      &opts);\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n \t\t\t.refname = (char *) \"b\",\n@@ -787,7 +803,8 @@ void test_reftable_readwrite__write_multiple_indices(void)\n \tint i;\n \tint err;\n \n-\twriter = cl_reftable_strbuf_writer(&writer_buf, &opts);\n+\twriter = cl_reftable_strbuf_writer(&writer_buf, REFTABLE_HASH_SHA1,\n+\t\t\t\t\t   &opts);\n \treftable_writer_set_limits(writer, 1, 1);\n \tfor (i = 0; i < 100; i++) {\n \t\tstruct reftable_ref_record ref = {\n@@ -861,7 +878,8 @@ void test_reftable_readwrite__write_multi_level_index(void)\n \tstruct reftable_table *table;\n \tint err;\n \n-\twriter = cl_reftable_strbuf_writer(&writer_buf, &opts);\n+\twriter = cl_reftable_strbuf_writer(&writer_buf, REFTABLE_HASH_SHA1,\n+\t\t\t\t\t   &opts);\n \treftable_writer_set_limits(writer, 1, 1);\n \tfor (size_t i = 0; i < 200; i++) {\n \t\tstruct reftable_ref_record ref = {\ndiff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c\nindex b8110cdeee..e6c1635940 100644\n--- a/t/unit-tests/u-reftable-stack.c\n+++ b/t/unit-tests/u-reftable-stack.c\n@@ -111,10 +111,9 @@ static int write_test_ref(struct reftable_writer *wr, void *arg)\n static void write_n_ref_tables(struct reftable_stack *st,\n \t\t\t       size_t n)\n {\n-\tint disable_auto_compact;\n-\n-\tdisable_auto_compact = st->opts.disable_auto_compact;\n-\tst->opts.disable_auto_compact = 1;\n+\tstruct reftable_write_options opts = {\n+\t\t.disable_auto_compact = 1,\n+\t};\n \n \tfor (size_t i = 0; i < n; i++) {\n \t\tstruct reftable_ref_record ref = {\n@@ -128,10 +127,8 @@ static void write_n_ref_tables(struct reftable_stack *st,\n \t\tcl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);\n \n \t\tcl_assert_equal_i(reftable_stack_add(st,\n-\t\t\t\t\t\t     &write_test_ref, &ref, 0), 0);\n+\t\t\t\t\t\t     &write_test_ref, &ref, &opts, 0), 0);\n \t}\n-\n-\tst->opts.disable_auto_compact = disable_auto_compact;\n }\n \n struct write_log_arg {\n@@ -168,10 +165,10 @@ void test_reftable_stack__add_one(void)\n \tstruct stat stat_result = { 0 };\n \tint err;\n \n-\terr = reftable_new_stack(&st, dir, &opts);\n+\terr = reftable_new_stack(&st, dir, NULL);\n \tcl_assert(!err);\n \n-\terr = reftable_stack_add(st, write_test_ref, &ref, 0);\n+\terr = reftable_stack_add(st, write_test_ref, &ref, &opts, 0);\n \tcl_assert(!err);\n \n \terr = reftable_stack_read_ref(st, ref.refname, &dest);\n@@ -210,7 +207,6 @@ void test_reftable_stack__add_one(void)\n \n void test_reftable_stack__uptodate(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st1 = NULL;\n \tstruct reftable_stack *st2 = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n@@ -232,15 +228,15 @@ void test_reftable_stack__uptodate(void)\n \t/* simulate multi-process access to the same stack\n \t   by creating two stacks for the same directory.\n \t */\n-\tcl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);\n-\tcl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_add(st1, write_test_ref,\n-\t\t\t\t\t     &ref1, 0), 0);\n+\t\t\t\t\t     &ref1, NULL, 0), 0);\n \tcl_assert_equal_i(reftable_stack_add(st2, write_test_ref,\n-\t\t\t\t\t     &ref2, 0), REFTABLE_OUTDATED_ERROR);\n+\t\t\t\t\t     &ref2, NULL, 0), REFTABLE_OUTDATED_ERROR);\n \tcl_assert_equal_i(reftable_stack_reload(st2), 0);\n \tcl_assert_equal_i(reftable_stack_add(st2, write_test_ref,\n-\t\t\t\t\t     &ref2, 0), 0);\n+\t\t\t\t\t     &ref2, NULL, 0), 0);\n \treftable_stack_destroy(st1);\n \treftable_stack_destroy(st2);\n \tclear_dir(dir);\n@@ -249,7 +245,6 @@ void test_reftable_stack__uptodate(void)\n void test_reftable_stack__transaction_api(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tstruct reftable_addition *add = NULL;\n \n@@ -261,11 +256,11 @@ void test_reftable_stack__transaction_api(void)\n \t};\n \tstruct reftable_ref_record dest = { 0 };\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \treftable_addition_destroy(add);\n \n-\tcl_assert_equal_i(reftable_stack_new_addition(&add, st, 0), 0);\n+\tcl_assert_equal_i(reftable_stack_new_addition(&add, st, NULL, 0), 0);\n \tcl_assert_equal_i(reftable_addition_add(add, write_test_ref,\n \t\t\t\t\t\t&ref), 0);\n \tcl_assert_equal_i(reftable_addition_commit(add), 0);\n@@ -306,7 +301,7 @@ void test_reftable_stack__transaction_with_reload(void)\n \n \tcl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);\n-\tcl_assert_equal_i(reftable_stack_new_addition(&add, st1, 0), 0);\n+\tcl_assert_equal_i(reftable_stack_new_addition(&add, st1, NULL, 0), 0);\n \tcl_assert_equal_i(reftable_addition_add(add, write_test_ref,\n \t\t\t\t\t\t&refs[0]), 0);\n \tcl_assert_equal_i(reftable_addition_commit(add), 0);\n@@ -317,9 +312,9 @@ void test_reftable_stack__transaction_with_reload(void)\n \t * create the addition and lock the stack by default, but allow the\n \t * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.\n \t */\n-\tcl_assert_equal_i(reftable_stack_new_addition(&add, st2, 0),\n+\tcl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0),\n \t\t\t\t\t\t      REFTABLE_OUTDATED_ERROR);\n-\tcl_assert_equal_i(reftable_stack_new_addition(&add, st2,\n+\tcl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL,\n \t\t\t\t\t\t      REFTABLE_STACK_NEW_ADDITION_RELOAD), 0);\n \tcl_assert_equal_i(reftable_addition_add(add, write_test_ref,\n \t\t\t\t\t\t&refs[1]), 0);\n@@ -342,12 +337,11 @@ void test_reftable_stack__transaction_with_reload(void)\n void test_reftable_stack__transaction_api_performs_auto_compaction(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = {0};\n \tstruct reftable_addition *add = NULL;\n \tstruct reftable_stack *st = NULL;\n \tsize_t n = 20;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tfor (size_t i = 0; i <= n; i++) {\n \t\tstruct reftable_ref_record ref = {\n@@ -356,6 +350,9 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void)\n \t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tchar name[100];\n+\t\tstruct reftable_write_options write_opts = {\n+\t\t\t.disable_auto_compact = (i != n),\n+\t\t};\n \n \t\tsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n \t\tref.refname = name;\n@@ -365,10 +362,8 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void)\n \t\t * we can ensure that we indeed honor this setting and have\n \t\t * better control over when exactly auto compaction runs.\n \t\t */\n-\t\tst->opts.disable_auto_compact = i != n;\n-\n \t\tcl_assert_equal_i(reftable_stack_new_addition(&add,\n-\t\t\t\t\t\t\t      st, 0), 0);\n+\t\t\t\t\t\t\t      st, &write_opts, 0), 0);\n \t\tcl_assert_equal_i(reftable_addition_add(add,\n \t\t\t\t\t\t\twrite_test_ref, &ref), 0);\n \t\tcl_assert_equal_i(reftable_addition_commit(add), 0);\n@@ -398,15 +393,14 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n \t};\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st;\n \tstruct reftable_buf table_path = REFTABLE_BUF_INIT;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tint err;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t     &ref, 0), 0);\n+\t\t\t\t\t     &ref, NULL, 0), 0);\n \tcl_assert_equal_i(st->merged->tables_len, 1);\n \tcl_assert_equal_i(st->stats.attempts, 0);\n \tcl_assert_equal_i(st->stats.failures, 0);\n@@ -424,7 +418,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)\n \twrite_file_buf(table_path.buf, \"\", 0);\n \n \tref.update_index = 2;\n-\terr = reftable_stack_add(st, write_test_ref, &ref, 0);\n+\terr = reftable_stack_add(st, write_test_ref, &ref, NULL, 0);\n \tcl_assert(!err);\n \tcl_assert_equal_i(st->merged->tables_len, 2);\n \tcl_assert_equal_i(st->stats.attempts, 1);\n@@ -443,7 +437,6 @@ static int write_error(struct reftable_writer *wr UNUSED, void *arg)\n void test_reftable_stack__update_index_check(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tstruct reftable_ref_record ref1 = {\n \t\t.refname = (char *) \"name1\",\n@@ -458,11 +451,11 @@ void test_reftable_stack__update_index_check(void)\n \t\t.value.symref = (char *) \"master\",\n \t};\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t     &ref1, 0), 0);\n+\t\t\t\t\t     &ref1, NULL, 0), 0);\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t     &ref2, 0), REFTABLE_API_ERROR);\n+\t\t\t\t\t     &ref2, NULL, 0), REFTABLE_API_ERROR);\n \treftable_stack_destroy(st);\n \tclear_dir(dir);\n }\n@@ -470,14 +463,13 @@ void test_reftable_stack__update_index_check(void)\n void test_reftable_stack__lock_failure(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tint i;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_error,\n-\t\t\t\t\t\t     &i, 0), i);\n+\t\t\t\t\t\t     &i, NULL, 0), i);\n \n \treftable_stack_destroy(st);\n \tclear_dir(dir);\n@@ -499,7 +491,7 @@ void test_reftable_stack__add(void)\n \tsize_t i, N = ARRAY_SIZE(refs);\n \tint err = 0;\n \n-\terr = reftable_new_stack(&st, dir, &opts);\n+\terr = reftable_new_stack(&st, dir, NULL);\n \tcl_assert(!err);\n \n \tfor (i = 0; i < N; i++) {\n@@ -521,7 +513,7 @@ void test_reftable_stack__add(void)\n \n \tfor (i = 0; i < N; i++)\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t\t     &refs[i], 0), 0);\n+\t\t\t\t\t\t     &refs[i], &opts, 0), 0);\n \n \tfor (i = 0; i < N; i++) {\n \t\tstruct write_log_arg arg = {\n@@ -529,10 +521,10 @@ void test_reftable_stack__add(void)\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t};\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t\t     &arg, 0), 0);\n+\t\t\t\t\t\t     &arg, &opts, 0), 0);\n \t}\n \n-\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st, &opts, NULL), 0);\n \n \tfor (i = 0; i < N; i++) {\n \t\tstruct reftable_ref_record dest = { 0 };\n@@ -584,7 +576,6 @@ void test_reftable_stack__add(void)\n \n void test_reftable_stack__iterator(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_ref_record refs[10] = { 0 };\n@@ -593,7 +584,7 @@ void test_reftable_stack__iterator(void)\n \tsize_t N = ARRAY_SIZE(refs), i;\n \tint err;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tfor (i = 0; i < N; i++) {\n \t\trefs[i].refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n@@ -613,7 +604,7 @@ void test_reftable_stack__iterator(void)\n \n \tfor (i = 0; i < N; i++)\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t\t     &refs[i], 0), 0);\n+\t\t\t\t\t\t     &refs[i], NULL, 0), 0);\n \n \tfor (i = 0; i < N; i++) {\n \t\tstruct write_log_arg arg = {\n@@ -622,7 +613,7 @@ void test_reftable_stack__iterator(void)\n \t\t};\n \n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t\t     &arg, 0), 0);\n+\t\t\t\t\t\t     &arg, NULL, 0), 0);\n \t}\n \n \treftable_stack_init_ref_iterator(st, &it);\n@@ -669,9 +660,6 @@ void test_reftable_stack__iterator(void)\n \n void test_reftable_stack__log_normalize(void)\n {\n-\tstruct reftable_write_options opts = {\n-\t\t0,\n-\t};\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_log_record input = {\n@@ -693,15 +681,15 @@ void test_reftable_stack__log_normalize(void)\n \t\t.update_index = 1,\n \t};\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tinput.value.update.message = (char *) \"one\\ntwo\";\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t     &arg, 0), REFTABLE_API_ERROR);\n+\t\t\t\t\t     &arg, NULL, 0), REFTABLE_API_ERROR);\n \n \tinput.value.update.message = (char *) \"one\";\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t     &arg, 0), 0);\n+\t\t\t\t\t     &arg, NULL, 0), 0);\n \tcl_assert_equal_i(reftable_stack_read_log(st, input.refname,\n \t\t\t\t\t\t  &dest), 0);\n \tcl_assert_equal_s(dest.value.update.message, \"one\\n\");\n@@ -709,7 +697,7 @@ void test_reftable_stack__log_normalize(void)\n \tinput.value.update.message = (char *) \"two\\n\";\n \targ.update_index = 2;\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t     &arg, 0), 0);\n+\t\t\t\t\t     &arg, NULL, 0), 0);\n \tcl_assert_equal_i(reftable_stack_read_log(st, input.refname,\n \t\t\t\t\t\t  &dest), 0);\n \tcl_assert_equal_s(dest.value.update.message, \"two\\n\");\n@@ -723,7 +711,6 @@ void test_reftable_stack__log_normalize(void)\n void test_reftable_stack__tombstone(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tstruct reftable_ref_record refs[2] = { 0 };\n \tstruct reftable_log_record logs[2] = { 0 };\n@@ -731,7 +718,7 @@ void test_reftable_stack__tombstone(void)\n \tstruct reftable_ref_record dest = { 0 };\n \tstruct reftable_log_record log_dest = { 0 };\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \t/* even entries add the refs, odd entries delete them. */\n \tfor (i = 0; i < N; i++) {\n@@ -760,7 +747,7 @@ void test_reftable_stack__tombstone(void)\n \t}\n \tfor (i = 0; i < N; i++)\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t\t     &refs[i], 0), 0);\n+\t\t\t\t\t\t     &refs[i], NULL, 0), 0);\n \n \tfor (i = 0; i < N; i++) {\n \t\tstruct write_log_arg arg = {\n@@ -768,7 +755,7 @@ void test_reftable_stack__tombstone(void)\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t};\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t\t     &arg, 0), 0);\n+\t\t\t\t\t\t     &arg, NULL, 0), 0);\n \t}\n \n \tcl_assert_equal_i(reftable_stack_read_ref(st, \"branch\",\n@@ -779,7 +766,7 @@ void test_reftable_stack__tombstone(void)\n \t\t\t\t\t\t  &log_dest), 1);\n \treftable_log_record_release(&log_dest);\n \n-\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_read_ref(st, \"branch\",\n \t\t\t\t\t\t  &dest), 1);\n \tcl_assert_equal_i(reftable_stack_read_log(st, \"branch\",\n@@ -799,7 +786,6 @@ void test_reftable_stack__tombstone(void)\n void test_reftable_stack__hash_id(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \n \tstruct reftable_ref_record ref = {\n@@ -808,15 +794,14 @@ void test_reftable_stack__hash_id(void)\n \t\t.value.symref = (char *) \"target\",\n \t\t.update_index = 1,\n \t};\n-\tstruct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };\n+\tstruct reftable_stack_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };\n \tstruct reftable_stack *st32 = NULL;\n-\tstruct reftable_write_options opts_default = { 0 };\n \tstruct reftable_stack *st_default = NULL;\n \tstruct reftable_ref_record dest = { 0 };\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t     &ref, 0), 0);\n+\t\t\t\t\t     &ref, NULL, 0), 0);\n \n \t/* can't read it with the wrong hash ID. */\n \tcl_assert_equal_i(reftable_new_stack(&st32, dir,\n@@ -824,7 +809,7 @@ void test_reftable_stack__hash_id(void)\n \n \t/* check that we can read it back with default opts too. */\n \tcl_assert_equal_i(reftable_new_stack(&st_default, dir,\n-\t\t\t\t\t     &opts_default), 0);\n+\t\t\t\t\t     NULL), 0);\n \tcl_assert_equal_i(reftable_stack_read_ref(st_default, \"master\",\n \t\t\t\t\t\t  &dest), 0);\n \tcl_assert(reftable_ref_record_equal(&ref, &dest,\n@@ -855,7 +840,6 @@ void test_reftable_stack__suggest_compaction_segment_nothing(void)\n void test_reftable_stack__reflog_expire(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tstruct reftable_log_record logs[20] = { 0 };\n \tsize_t i, N = ARRAY_SIZE(logs) - 1;\n@@ -864,7 +848,7 @@ void test_reftable_stack__reflog_expire(void)\n \t};\n \tstruct reftable_log_record log = { 0 };\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tfor (i = 1; i <= N; i++) {\n \t\tchar buf[256];\n@@ -885,18 +869,18 @@ void test_reftable_stack__reflog_expire(void)\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t};\n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n-\t\t\t\t\t\t     &arg, 0), 0);\n+\t\t\t\t\t\t     &arg, NULL, 0), 0);\n \t}\n \n-\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);\n-\tcl_assert_equal_i(reftable_stack_compact_all(st, &expiry), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0);\n \tcl_assert_equal_i(reftable_stack_read_log(st, logs[9].refname,\n \t\t\t\t\t\t  &log), 1);\n \tcl_assert_equal_i(reftable_stack_read_log(st, logs[11].refname,\n \t\t\t\t\t\t  &log), 0);\n \n \texpiry.min_update_index = 15;\n-\tcl_assert_equal_i(reftable_stack_compact_all(st, &expiry), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0);\n \tcl_assert_equal_i(reftable_stack_read_log(st, logs[14].refname,\n \t\t\t\t\t\t  &log), 1);\n \tcl_assert_equal_i(reftable_stack_read_log(st, logs[16].refname,\n@@ -918,15 +902,14 @@ static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)\n \n void test_reftable_stack__empty_add(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_stack *st2 = NULL;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_add(st, write_nothing,\n-\t\t\t\t\t     NULL, 0), 0);\n-\tcl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);\n+\t\t\t\t\t     NULL, NULL, 0), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);\n \tclear_dir(dir);\n \treftable_stack_destroy(st);\n \treftable_stack_destroy(st2);\n@@ -952,7 +935,7 @@ void test_reftable_stack__auto_compaction(void)\n \tsize_t i, N = 100;\n \tint err;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tfor (i = 0; i < N; i++) {\n \t\tchar name[100];\n@@ -964,10 +947,10 @@ void test_reftable_stack__auto_compaction(void)\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n \n-\t\terr = reftable_stack_add(st, write_test_ref, &ref, 0);\n+\t\terr = reftable_stack_add(st, write_test_ref, &ref, &opts, 0);\n \t\tcl_assert(!err);\n \n-\t\terr = reftable_stack_auto_compact(st);\n+\t\terr = reftable_stack_auto_compact(st, &opts);\n \t\tcl_assert(!err);\n \t\tcl_assert(i < 2 || st->merged->tables_len < 2 * fastlogN(i, 2));\n \t}\n@@ -989,7 +972,7 @@ void test_reftable_stack__auto_compaction_factor(void)\n \tsize_t N = 100;\n \tint err;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tfor (size_t i = 0; i < N; i++) {\n \t\tchar name[20];\n@@ -1000,7 +983,7 @@ void test_reftable_stack__auto_compaction_factor(void)\n \t\t};\n \t\txsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n \n-\t\terr = reftable_stack_add(st, &write_test_ref, &ref, 0);\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref, &opts, 0);\n \t\tcl_assert(!err);\n \n \t\tcl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5));\n@@ -1020,7 +1003,7 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void)\n \tchar *dir = get_tmp_dir(__LINE__);\n \tint err;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \twrite_n_ref_tables(st, 5);\n \tcl_assert_equal_i(st->merged->tables_len, 5);\n@@ -1042,7 +1025,7 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void)\n \t * would in theory compact all tables, due to the preexisting lock we\n \t * only compact the newest two tables.\n \t */\n-\terr = reftable_stack_auto_compact(st);\n+\terr = reftable_stack_auto_compact(st, &opts);\n \tcl_assert(!err);\n \tcl_assert_equal_i(st->stats.failures, 0);\n \tcl_assert_equal_i(st->merged->tables_len, 4);\n@@ -1054,12 +1037,11 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void)\n \n void test_reftable_stack__add_performs_auto_compaction(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tsize_t i, n = 20;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \tfor (i = 0; i <= n; i++) {\n \t\tstruct reftable_ref_record ref = {\n@@ -1067,6 +1049,9 @@ void test_reftable_stack__add_performs_auto_compaction(void)\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value.symref = (char *) \"master\",\n \t\t};\n+\t\tstruct reftable_write_options write_opts = {\n+\t\t\t.disable_auto_compact = (i != n),\n+\t\t};\n \t\tbool required = false;\n \t\tchar buf[128];\n \n@@ -1075,20 +1060,18 @@ void test_reftable_stack__add_performs_auto_compaction(void)\n \t\t * we can ensure that we indeed honor this setting and have\n \t\t * better control over when exactly auto compaction runs.\n \t\t */\n-\t\tst->opts.disable_auto_compact = i != n;\n-\n \t\tsnprintf(buf, sizeof(buf), \"branch-%04\"PRIuMAX, (uintmax_t)i);\n \t\tref.refname = buf;\n \n \t\tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n-\t\t\t\t\t\t     &ref, 0), 0);\n+\t\t\t\t\t\t     &ref, &write_opts, 0), 0);\n \n \t\t/*\n \t\t * The stack length should grow continuously for all runs where\n \t\t * auto compaction is disabled. When enabled, we should merge\n \t\t * all tables in the stack.\n \t\t */\n-\t\tcl_assert_equal_i(reftable_stack_compaction_required(st, true, &required), 0);\n+\t\tcl_assert_equal_i(reftable_stack_compaction_required(st, NULL, true, &required), 0);\n \t\tif (i != n) {\n \t\t\tcl_assert_equal_i(st->merged->tables_len, i + 1);\n \t\t\tif (i < 1)\n@@ -1115,7 +1098,7 @@ void test_reftable_stack__compaction_with_locked_tables(void)\n \tchar *dir = get_tmp_dir(__LINE__);\n \tint err;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \twrite_n_ref_tables(st, 3);\n \tcl_assert_equal_i(st->merged->tables_len, 3);\n@@ -1131,7 +1114,7 @@ void test_reftable_stack__compaction_with_locked_tables(void)\n \t * Compaction is expected to fail given that we were not able to\n \t * compact all tables.\n \t */\n-\terr = reftable_stack_compact_all(st, NULL);\n+\terr = reftable_stack_compact_all(st, &opts, NULL);\n \tcl_assert_equal_i(err, REFTABLE_LOCK_ERROR);\n \tcl_assert_equal_i(st->stats.failures, 1);\n \tcl_assert_equal_i(st->merged->tables_len, 3);\n@@ -1143,15 +1126,14 @@ void test_reftable_stack__compaction_with_locked_tables(void)\n \n void test_reftable_stack__compaction_concurrent(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \n-\tcl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);\n \twrite_n_ref_tables(st1, 3);\n \n-\tcl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);\n-\tcl_assert_equal_i(reftable_stack_compact_all(st1, NULL), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0);\n \n \treftable_stack_destroy(st1);\n \treftable_stack_destroy(st2);\n@@ -1171,20 +1153,19 @@ static void unclean_stack_close(struct reftable_stack *st)\n \n void test_reftable_stack__compaction_concurrent_clean(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \n-\tcl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);\n \twrite_n_ref_tables(st1, 3);\n \n-\tcl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);\n-\tcl_assert_equal_i(reftable_stack_compact_all(st1, NULL), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);\n+\tcl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0);\n \n \tunclean_stack_close(st1);\n \tunclean_stack_close(st2);\n \n-\tcl_assert_equal_i(reftable_new_stack(&st3, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st3, dir, NULL), 0);\n \tcl_assert_equal_i(reftable_stack_clean(st3), 0);\n \tcl_assert_equal_i(count_dir_entries(dir), 2);\n \n@@ -1197,7 +1178,6 @@ void test_reftable_stack__compaction_concurrent_clean(void)\n \n void test_reftable_stack__read_across_reload(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n \tstruct reftable_ref_record rec = { 0 };\n \tstruct reftable_iterator it = { 0 };\n@@ -1205,17 +1185,17 @@ void test_reftable_stack__read_across_reload(void)\n \tint err;\n \n \t/* Create a first stack and set up an iterator for it. */\n-\tcl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);\n \twrite_n_ref_tables(st1, 2);\n \tcl_assert_equal_i(st1->merged->tables_len, 2);\n \treftable_stack_init_ref_iterator(st1, &it);\n \tcl_assert_equal_i(reftable_iterator_seek_ref(&it, \"\"), 0);\n \n \t/* Set up a second stack for the same directory and compact it. */\n-\terr = reftable_new_stack(&st2, dir, &opts);\n+\terr = reftable_new_stack(&st2, dir, NULL);\n \tcl_assert(!err);\n \tcl_assert_equal_i(st2->merged->tables_len, 2);\n-\terr = reftable_stack_compact_all(st2, NULL);\n+\terr = reftable_stack_compact_all(st2, NULL, NULL);\n \tcl_assert(!err);\n \tcl_assert_equal_i(st2->merged->tables_len, 1);\n \n@@ -1244,7 +1224,6 @@ void test_reftable_stack__read_across_reload(void)\n \n void test_reftable_stack__reload_with_missing_table(void)\n {\n-\tstruct reftable_write_options opts = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tstruct reftable_ref_record rec = { 0 };\n \tstruct reftable_iterator it = { 0 };\n@@ -1253,7 +1232,7 @@ void test_reftable_stack__reload_with_missing_table(void)\n \tint err;\n \n \t/* Create a first stack and set up an iterator for it. */\n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \twrite_n_ref_tables(st, 2);\n \tcl_assert_equal_i(st->merged->tables_len, 2);\n \treftable_stack_init_ref_iterator(st, &it);\n@@ -1320,11 +1299,11 @@ void test_reftable_stack__invalid_limit_updates(void)\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_stack *st = NULL;\n \n-\tcl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);\n+\tcl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);\n \n \treftable_addition_destroy(add);\n \n-\tcl_assert_equal_i(reftable_stack_new_addition(&add, st, 0), 0);\n+\tcl_assert_equal_i(reftable_stack_new_addition(&add, st, &opts, 0), 0);\n \n \t/*\n \t * write_limits_after_ref also updates the update indexes after adding\ndiff --git a/t/unit-tests/u-reftable-table.c b/t/unit-tests/u-reftable-table.c\nindex 14fae8b199..fae478ee04 100644\n--- a/t/unit-tests/u-reftable-table.c\n+++ b/t/unit-tests/u-reftable-table.c\n@@ -22,7 +22,8 @@ void test_reftable_table__seek_once(void)\n \tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n \tint ret;\n \n-\tcl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);\n+\tcl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0,\n+\t\t\t\t REFTABLE_HASH_SHA1, NULL);\n \tblock_source_from_buf(&source, &buf);\n \n \tret = reftable_table_new(&table, &source, \"name\");\n@@ -64,7 +65,7 @@ void test_reftable_table__reseek(void)\n \tint ret;\n \n \tcl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records),\n-\t\t\t\t NULL, 0, NULL);\n+\t\t\t\t NULL, 0, REFTABLE_HASH_SHA1, NULL);\n \tblock_source_from_buf(&source, &buf);\n \n \tret = reftable_table_new(&table, &source, \"name\");\n@@ -147,7 +148,8 @@ void test_reftable_table__block_iterator(void)\n \t\t\t\t\t     (uintmax_t) i);\n \t}\n \n-\tcl_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, NULL);\n+\tcl_reftable_write_to_buf(&buf, records, nrecords, NULL, 0,\n+\t\t\t\t REFTABLE_HASH_SHA1, NULL);\n \tblock_source_from_buf(&source, &buf);\n \n \tret = reftable_table_new(&table, &source, \"name\");\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546394","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-10-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 10/11] refs/reftable: lazy-load configuration to fix chicken-and-egg","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:08Z","receivedAt":"2026-06-25T09:20:35Z","isPatch":true,"body":"Same as with the \"files\" backend, the \"reftable\" backend also has a\nchicken-and-egg problem with \"onbranch\" conditions. Fix this issue the\nsame as we did with the \"files\" backend by lazy-loading configuration.\n\nNow that both the \"files\" and the \"reftable\" backend handle this\nproperly, add a generic test to t1400 that verifies that the user can\nconfigure \"core.logAllRefUpdates\" via an \"onbranch\" condition. This is\nmostly a nonsensical thing to do in the first place, but it serves as a\ngood sanity check.\n\nNote that we had to move `should_write_log()` around so that it can\naccess the new `reftable_be_write_options()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/reftable-backend.c           | 146 ++++++++++++++++++++++----------------\n t/t0613-reftable-write-options.sh |  19 +++++\n t/t1400-update-ref.sh             |  12 ++++\n 3 files changed, 116 insertions(+), 61 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 608d71cf10..d74131a5ae 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -141,10 +141,21 @@ struct reftable_ref_store {\n \t */\n \tstruct strmap worktree_backends;\n \tstruct reftable_stack_options stack_options;\n-\tstruct reftable_write_options write_options;\n+\n+\t/*\n+\t * Options used when writing to or compacting the reftable stacks.\n+\t * These are parsed from the configuration lazily on first use via\n+\t * `reftable_be_write_options()` so that we don't have to access the\n+\t * configuration when initializing the ref store. Do not access these\n+\t * fields directly, but use the accessor instead.\n+\t */\n+\tstruct reftable_be_write_options {\n+\t\tstruct reftable_write_options opts;\n+\t\tenum log_refs_config log_all_ref_updates;\n+\t\tbool initialized;\n+\t} write_opts_lazy_loaded;\n \n \tunsigned int store_flags;\n-\tenum log_refs_config log_all_ref_updates;\n \tint err;\n };\n \n@@ -285,26 +296,6 @@ static int backend_for(struct reftable_backend **out,\n \treturn ret;\n }\n \n-static int should_write_log(struct reftable_ref_store *refs, const char *refname)\n-{\n-\tenum log_refs_config log_refs_cfg = refs->log_all_ref_updates;\n-\tif (log_refs_cfg == LOG_REFS_UNSET)\n-\t\tlog_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;\n-\n-\tswitch (log_refs_cfg) {\n-\tcase LOG_REFS_NONE:\n-\t\treturn refs_reflog_exists(&refs->base, refname);\n-\tcase LOG_REFS_ALWAYS:\n-\t\treturn 1;\n-\tcase LOG_REFS_NORMAL:\n-\t\tif (should_autocreate_reflog(log_refs_cfg, refname))\n-\t\t\treturn 1;\n-\t\treturn refs_reflog_exists(&refs->base, refname);\n-\tdefault:\n-\t\tBUG(\"unhandled core.logAllRefUpdates value %d\", log_refs_cfg);\n-\t}\n-}\n-\n static void fill_reftable_log_record(struct reftable_log_record *log, const struct ident_split *split)\n {\n \tconst char *tz_begin;\n@@ -336,38 +327,72 @@ static int reftable_be_config(const char *var, const char *value,\n \t\t\t      void *payload)\n {\n \tstruct reftable_ref_store *refs = payload;\n+\tstruct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded;\n \n \tif (!strcmp(var, \"reftable.blocksize\")) {\n \t\tunsigned long block_size = git_config_ulong(var, value, ctx->kvi);\n \t\tif (block_size > 16777215)\n \t\t\tdie(\"reftable block size cannot exceed 16MB\");\n-\t\trefs->write_options.block_size = block_size;\n+\t\topts->opts.block_size = block_size;\n \t} else if (!strcmp(var, \"reftable.restartinterval\")) {\n \t\tunsigned long restart_interval = git_config_ulong(var, value, ctx->kvi);\n \t\tif (restart_interval > UINT16_MAX)\n \t\t\tdie(\"reftable block size cannot exceed %u\", (unsigned)UINT16_MAX);\n-\t\trefs->write_options.restart_interval = restart_interval;\n+\t\topts->opts.restart_interval = restart_interval;\n \t} else if (!strcmp(var, \"reftable.indexobjects\")) {\n-\t\trefs->write_options.skip_index_objects = !git_config_bool(var, value);\n+\t\topts->opts.skip_index_objects = !git_config_bool(var, value);\n \t} else if (!strcmp(var, \"reftable.geometricfactor\")) {\n \t\tunsigned long factor = git_config_ulong(var, value, ctx->kvi);\n \t\tif (factor > UINT8_MAX)\n \t\t\tdie(\"reftable geometric factor cannot exceed %u\", (unsigned)UINT8_MAX);\n-\t\trefs->write_options.auto_compaction_factor = factor;\n+\t\topts->opts.auto_compaction_factor = factor;\n \t} else if (!strcmp(var, \"reftable.locktimeout\")) {\n \t\tint64_t lock_timeout = git_config_int64(var, value, ctx->kvi);\n \t\tif (lock_timeout > LONG_MAX)\n \t\t\tdie(\"reftable lock timeout cannot exceed %\"PRIdMAX, (intmax_t)LONG_MAX);\n \t\tif (lock_timeout < 0 && lock_timeout != -1)\n \t\t\tdie(\"reftable lock timeout does not support negative values other than -1\");\n-\t\trefs->write_options.lock_timeout_ms = lock_timeout;\n+\t\topts->opts.lock_timeout_ms = lock_timeout;\n \t} else if (!strcmp(var, \"core.logallrefupdates\")) {\n-\t\trefs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n+\t\topts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);\n \t}\n \n \treturn 0;\n }\n \n+static const struct reftable_be_write_options *reftable_be_write_options(struct reftable_ref_store *refs)\n+{\n+\tstruct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded;\n+\tmode_t mask;\n+\n+\tif (opts->initialized)\n+\t\treturn opts;\n+\n+\tmask = umask(0);\n+\tumask(mask);\n+\n+\topts->opts.default_permissions = calc_shared_perm(refs->base.repo, 0666 & ~mask);\n+\topts->opts.disable_auto_compact =\n+\t\t!git_env_bool(\"GIT_TEST_REFTABLE_AUTOCOMPACTION\", 1);\n+\topts->opts.lock_timeout_ms = 100;\n+\topts->log_all_ref_updates = LOG_REFS_UNSET;\n+\n+\trepo_config(refs->base.repo, reftable_be_config, refs);\n+\n+\t/*\n+\t * It is somewhat unfortunate that we have to mirror the default block\n+\t * size of the reftable library here. But given that the write options\n+\t * wouldn't be updated by the library here, and given that we require\n+\t * the proper block size to trim reflog message so that they fit, we\n+\t * must set up a proper value here.\n+\t */\n+\tif (!opts->opts.block_size)\n+\t\topts->opts.block_size = 4096;\n+\n+\topts->initialized = true;\n+\treturn opts;\n+}\n+\n static void reftable_be_reparent(const char *name UNUSED,\n \t\t\t\t const char *old_cwd,\n \t\t\t\t const char *new_cwd,\n@@ -391,10 +416,6 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \tstruct strbuf refdir = STRBUF_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n \tbool is_worktree;\n-\tmode_t mask;\n-\n-\tmask = umask(0);\n-\tumask(mask);\n \n \trefs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,\n \t\t\t\t\t &ref_common_dir);\n@@ -413,23 +434,6 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \tdefault:\n \t\tBUG(\"unknown hash algorithm %d\", repo->hash_algo->format_id);\n \t}\n-\trefs->write_options.default_permissions = calc_shared_perm(repo, 0666 & ~mask);\n-\trefs->write_options.disable_auto_compact =\n-\t\t!git_env_bool(\"GIT_TEST_REFTABLE_AUTOCOMPACTION\", 1);\n-\trefs->write_options.lock_timeout_ms = 100;\n-\trefs->log_all_ref_updates = LOG_REFS_UNSET;\n-\n-\trepo_config(repo, reftable_be_config, refs);\n-\n-\t/*\n-\t * It is somewhat unfortunate that we have to mirror the default block\n-\t * size of the reftable library here. But given that the write options\n-\t * wouldn't be updated by the library here, and given that we require\n-\t * the proper block size to trim reflog message so that they fit, we\n-\t * must set up a proper value here.\n-\t */\n-\tif (!refs->write_options.block_size)\n-\t\trefs->write_options.block_size = 4096;\n \n \t/*\n \t * Set up the main reftable stack that is hosted in GIT_COMMON_DIR.\n@@ -998,7 +1002,7 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,\n \t\tstruct reftable_addition *addition;\n \n \t\tret = reftable_stack_new_addition(&addition, be->stack,\n-\t\t\t\t\t\t  &refs->write_options,\n+\t\t\t\t\t\t  &reftable_be_write_options(refs)->opts,\n \t\t\t\t\t\t  REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \t\tif (ret) {\n \t\t\tif (ret == REFTABLE_LOCK_ERROR)\n@@ -1437,6 +1441,26 @@ static int transaction_update_cmp(const void *a, const void *b)\n \treturn strcmp(update_a->update->refname, update_b->update->refname);\n }\n \n+static int should_write_log(struct reftable_ref_store *refs, const char *refname)\n+{\n+\tenum log_refs_config log_refs_cfg = reftable_be_write_options(refs)->log_all_ref_updates;\n+\tif (log_refs_cfg == LOG_REFS_UNSET)\n+\t\tlog_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;\n+\n+\tswitch (log_refs_cfg) {\n+\tcase LOG_REFS_NONE:\n+\t\treturn refs_reflog_exists(&refs->base, refname);\n+\tcase LOG_REFS_ALWAYS:\n+\t\treturn 1;\n+\tcase LOG_REFS_NORMAL:\n+\t\tif (should_autocreate_reflog(log_refs_cfg, refname))\n+\t\t\treturn 1;\n+\t\treturn refs_reflog_exists(&refs->base, refname);\n+\tdefault:\n+\t\tBUG(\"unhandled core.logAllRefUpdates value %d\", log_refs_cfg);\n+\t}\n+}\n+\n static int write_transaction_table(struct reftable_writer *writer, void *cb_data)\n {\n \tstruct write_transaction_table_arg *arg = cb_data;\n@@ -1571,7 +1595,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t\tmemcpy(log->value.update.old_hash,\n \t\t\t\t       tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n \t\t\t\tlog->value.update.message =\n-\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\t\t\txstrndup(u->msg, reftable_be_write_options(arg->refs)->opts.block_size / 2);\n \t\t\t}\n \t\t}\n \n@@ -1687,9 +1711,9 @@ static int reftable_be_optimize(struct ref_store *ref_store,\n \t\tstack = refs->main_backend.stack;\n \n \tif (opts->flags & REFS_OPTIMIZE_AUTO)\n-\t\tret = reftable_stack_auto_compact(stack, &refs->write_options);\n+\t\tret = reftable_stack_auto_compact(stack, &reftable_be_write_options(refs)->opts);\n \telse\n-\t\tret = reftable_stack_compact_all(stack, &refs->write_options, NULL);\n+\t\tret = reftable_stack_compact_all(stack, &reftable_be_write_options(refs)->opts, NULL);\n \tif (ret < 0) {\n \t\tret = error(_(\"unable to compact stack: %s\"),\n \t\t\t    reftable_error_str(ret));\n@@ -1723,7 +1747,7 @@ static int reftable_be_optimize_required(struct ref_store *ref_store,\n \tif (opts->flags & REFS_OPTIMIZE_AUTO)\n \t\tuse_heuristics = true;\n \n-\treturn reftable_stack_compaction_required(stack, &refs->write_options,\n+\treturn reftable_stack_compaction_required(stack, &reftable_be_write_options(refs)->opts,\n \t\t\t\t\t\t  use_heuristics, required);\n }\n \n@@ -1843,7 +1867,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs[logs_nr].update_index = deletion_ts;\n \t\tlogs[logs_nr].value.update.message =\n-\t\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n+\t\t\txstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2);\n \t\tmemcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ);\n \t\tlogs_nr++;\n \n@@ -1882,7 +1906,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tlogs[logs_nr].refname = xstrdup(arg->newname);\n \tlogs[logs_nr].update_index = creation_ts;\n \tlogs[logs_nr].value.update.message =\n-\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n+\t\txstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2);\n \tmemcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ);\n \tlogs_nr++;\n \n@@ -1981,7 +2005,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,\n \tif (ret)\n \t\tgoto done;\n \tret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,\n-\t\t\t\t &refs->write_options,\n+\t\t\t\t &reftable_be_write_options(refs)->opts,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n done:\n@@ -2012,7 +2036,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,\n \tif (ret)\n \t\tgoto done;\n \tret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,\n-\t\t\t\t &refs->write_options,\n+\t\t\t\t &reftable_be_write_options(refs)->opts,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n done:\n@@ -2378,7 +2402,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,\n \targ.stack = be->stack;\n \n \tret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,\n-\t\t\t\t &refs->write_options,\n+\t\t\t\t &reftable_be_write_options(refs)->opts,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n done:\n@@ -2451,7 +2475,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,\n \targ.stack = be->stack;\n \n \tret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,\n-\t\t\t\t &refs->write_options,\n+\t\t\t\t &reftable_be_write_options(refs)->opts,\n \t\t\t\t REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \n \tassert(ret != REFTABLE_API_ERROR);\n@@ -2574,7 +2598,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,\n \t\tgoto done;\n \n \tret = reftable_stack_new_addition(&add, be->stack,\n-\t\t\t\t\t  &refs->write_options,\n+\t\t\t\t\t  &reftable_be_write_options(refs)->opts,\n \t\t\t\t\t  REFTABLE_STACK_NEW_ADDITION_RELOAD);\n \tif (ret < 0)\n \t\tgoto done;\ndiff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh\nindex 26b716c75f..a65960d048 100755\n--- a/t/t0613-reftable-write-options.sh\n+++ b/t/t0613-reftable-write-options.sh\n@@ -278,4 +278,23 @@ test_expect_success 'object index can be disabled' '\n \t)\n '\n \n+test_expect_success 'write options can be set up via onbranch condition' '\n+\ttest_config_global core.logAllRefUpdates false &&\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tinit_repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit A &&\n+\t\ttest_commit B &&\n+\t\tcat >.git/include <<-\\EOF &&\n+\t\t[reftable]\n+\t\t\tblockSize = 123\n+\t\tEOF\n+\t\tgit config includeIf.onbranch:master.path \"$(pwd)/.git/include\" &&\n+\t\tgit refs optimize &&\n+\t\ttest-tool dump-reftable -b .git/reftable/*.ref >stats &&\n+\t\ttest_grep \"block_size: 123\" stats\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 1015f335e3..b8c3be6631 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -178,6 +178,18 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always'\n \ttest_must_fail git reflog exists $outside\n '\n \n+test_expect_success 'core.logAllRefUpdates can be set up via onbranch condition' '\n+\ttest_when_finished \"git update-ref -d $outside\" &&\n+\ttest_when_finished \"rm -f .git/include\" &&\n+\tcat >.git/include <<-\\EOF &&\n+\t[core]\n+\t\tlogAllRefUpdates = always\n+\tEOF\n+\ttest_config includeIf.onbranch:main.path \"$(pwd)/.git/include\" &&\n+\tgit update-ref $outside $A &&\n+\tgit reflog exists $outside\n+'\n+\n test_expect_success \"create $m (by HEAD)\" '\n \tgit update-ref HEAD $A &&\n \ttest $A = $(git show-ref -s --verify $m)\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546396","messageId":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-11-41fbca3cf5e3@pks.im","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"[PATCH v6 11/11] refs: protect against chicken-and-egg recursion","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-25T09:20:09Z","receivedAt":"2026-06-25T09:20:37Z","isPatch":true,"body":"In the preceding commits we have fixed recursion when creating the\nreference backends due to a chicken-and-egg situation with \"onbranch\"\nconditions. Unfortunately, this issue has existed for a while, and we\ndidn't really have a good mechanism to detect this recursion.\n\nImprove the status quo by detecting the recursion when creating the main\nreference store.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 5b773b1c15..1d24637891 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2359,15 +2359,22 @@ void ref_store_release(struct ref_store *ref_store)\n \n struct ref_store *get_main_ref_store(struct repository *r)\n {\n+\tstatic bool initializing;\n+\n \tif (r->refs_private)\n \t\treturn r->refs_private;\n \n \tif (!r->gitdir)\n \t\tBUG(\"attempting to get main_ref_store outside of repository\");\n+\tif (initializing)\n+\t\tBUG(\"initialization of main ref store is recursing\");\n \n+\tinitializing = true;\n \tr->refs_private = ref_store_init(r, r->ref_storage_format,\n \t\t\t\t\t r->gitdir, REF_STORE_ALL_CAPS);\n \tr->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);\n+\tinitializing = false;\n+\n \treturn r->refs_private;\n }\n \n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546419","messageId":"aj1DuUzusBUqmF_C@denethor","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"Re: [PATCH v6 00/11] refs: fix \"onbranch\" conditions","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-25T15:06:47Z","receivedAt":"2026-06-25T15:06:49Z","isPatch":true,"body":"On 26/06/25 11:19AM, Patrick Steinhardt wrote:\n> Changes in v6:\n>   - Drop redundant condition when setting the default for\n>     \"core.logallrefupdates\".\n>   - Leave breakcrumb for why we lazy-load write options for the \"files\"\n>     backend.\n>   - Fix commit message typo.\n\nThanks. This version of the series looks good to me.\n\n-Justin\n"},{"id":"546433","messageId":"xmqqse6ae45i.fsf@gitster.g","threadId":"65868","inReplyTo":"aj1DuUzusBUqmF_C@denethor","subject":"Re: [PATCH v6 00/11] refs: fix \"onbranch\" conditions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-25T20:50:01Z","receivedAt":"2026-06-25T20:50:07Z","isPatch":true,"body":"Justin Tobler <jltobler@gmail.com> writes:\n\n> On 26/06/25 11:19AM, Patrick Steinhardt wrote:\n>> Changes in v6:\n>>   - Drop redundant condition when setting the default for\n>>     \"core.logallrefupdates\".\n>>   - Leave breakcrumb for why we lazy-load write options for the \"files\"\n>>     backend.\n>>   - Fix commit message typo.\n>\n> Thanks. This version of the series looks good to me.\n>\n> -Justin\n\nThanks, both.  Let's call it ready for 'next' then.\n"},{"id":"546439","messageId":"20260626050608.GA3138423@coredump.intra.peff.net","threadId":"65868","inReplyTo":"20260625-b4-pks-refs-avoid-chdir-notify-reparent-v6-0-41fbca3cf5e3@pks.im","subject":"Re: [PATCH v6 00/11] refs: fix \"onbranch\" conditions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-06-26T05:06:08Z","receivedAt":"2026-06-26T05:06:17Z","isPatch":true,"body":"On Thu, Jun 25, 2026 at 11:19:58AM +0200, Patrick Steinhardt wrote:\n\n>   - Fix the \"onbranch\" recursion properly: instead of papering over the\n>     issue, this series now refactors reference store initialization to\n>     not read any configuration at all anymore. Instead, the config is\n>     now parsed lazily. This fixes the recursion, but also makes us\n>     respect configuration guarded by \"onbranch\" conditions properly.\n\nSorry, I was offline and missed reviewing some of the intermediate\nstages.\n\nThe approach you take in v6 looks good to me, and I'm glad the result\ndoes not look too painful to maintain. If we ever add a config option\nthat is absolutely required for initializing the reading side of the\nbackends, we'll be back to our chicken-and-egg problem. But I won't be\nsurprised if that never happens, and if it does, we can decide on the\nuser-visible interaction with onbranch includes then (and whatever the\nresult is, document it).\n\nThanks for taking the time to re-work all of this.\n\n-Peff\n"},{"id":"546481","messageId":"xmqqmrwh9vl4.fsf@gitster.g","threadId":"65868","inReplyTo":"xmqqse6ae45i.fsf@gitster.g","subject":"Re: [PATCH v6 00/11] refs: fix \"onbranch\" conditions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-26T15:20:55Z","receivedAt":"2026-06-26T15:20:58Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Justin Tobler <jltobler@gmail.com> writes:\n>\n>> On 26/06/25 11:19AM, Patrick Steinhardt wrote:\n>>> Changes in v6:\n>>>   - Drop redundant condition when setting the default for\n>>>     \"core.logallrefupdates\".\n>>>   - Leave breakcrumb for why we lazy-load write options for the \"files\"\n>>>     backend.\n>>>   - Fix commit message typo.\n>>\n>> Thanks. This version of the series looks good to me.\n>>\n>> -Justin\n>\n> Thanks, both.  Let's call it ready for 'next' then.\n\nAh, before I forget, as the focus of the topic shifted dramatically\nbetween v4 and v5, I think we should rename it to something like\n'ps/refs-onbranch-fixes' to reflect the fact that is no longer is\nabout chdir-notify-parent but to fix \"onbranch\" chicken-and-egg\nsituation.\n"},{"id":"546512","messageId":"aj7WfJqsPYzJ4nLf@pks.im","threadId":"65868","inReplyTo":"xmqqmrwh9vl4.fsf@gitster.g","subject":"Re: [PATCH v6 00/11] refs: fix \"onbranch\" conditions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-26T19:43:56Z","receivedAt":"2026-06-26T19:44:22Z","isPatch":true,"body":"On Fri, Jun 26, 2026 at 08:20:55AM -0700, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Justin Tobler <jltobler@gmail.com> writes:\n> >\n> >> On 26/06/25 11:19AM, Patrick Steinhardt wrote:\n> >>> Changes in v6:\n> >>>   - Drop redundant condition when setting the default for\n> >>>     \"core.logallrefupdates\".\n> >>>   - Leave breakcrumb for why we lazy-load write options for the \"files\"\n> >>>     backend.\n> >>>   - Fix commit message typo.\n> >>\n> >> Thanks. This version of the series looks good to me.\n> >>\n> >> -Justin\n> >\n> > Thanks, both.  Let's call it ready for 'next' then.\n> \n> Ah, before I forget, as the focus of the topic shifted dramatically\n> between v4 and v5, I think we should rename it to something like\n> 'ps/refs-onbranch-fixes' to reflect the fact that is no longer is\n> about chdir-notify-parent but to fix \"onbranch\" chicken-and-egg\n> situation.\n\nAgreed. I also changed the subject of the cover letter starting with v5\nto reflect this, so updating the branch name to match seems sensible to\nme.\n\nThanks!\n\nPatrick\n"}]}