refs/files: lazy-load configuration to fix chicken-and-egg

When initializing the "files" reference backend we read the repository's config to parse "core.preferSymlinkRefs" and "core.logAllRefUpdates". This results in a chicken-and-egg problem though, because parsing the configuration may require us to have access to the reference store already when an "onbranch" condition exists. Luckily, all the configuration that we honor only relates to writing references. Consequently, we don't strictly need that configuration to be readily available at initialization time, and we can easiliy defer parsing it to a later point in time. Implement this fix and add tests that verify that we can indeed properly parse these config knobs via an "onbranch" condition. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jun 25, 2026 at 11:20 UTC f016ec17fbbde5ae7d208f0585bc02eb63416a53
2 files changed +54 -11
refs/files-backend.c
+33 -11
@@ -84,12 +84,21 @@ struct files_ref_store {
84 unsigned int store_flags;
85
86 char *gitcommondir;
87 - enum log_refs_config log_all_ref_updates;
88 - int prefer_symlink_refs;
89 -
87 struct ref_cache *loose;
91 -
88 struct ref_store *packed_ref_store;
89 +
90 + /*
91 + * Options used when writing references. These are parsed from the
92 + * config lazily on first use via `files_ref_store_write_options()` so
93 + * that we don't have to access the configuration when initializing the
94 + * ref store. Do not access these fields directly, but use the accessor
95 + * instead.
96 + */
97 + struct files_ref_store_write_options {
98 + enum log_refs_config log_all_ref_updates;
99 + int prefer_symlink_refs;
100 + bool initialized;
101 + } write_opts_lazy_loaded;
102 };
103
104 static void clear_loose_ref_cache(struct files_ref_store *refs)
@@ -121,17 +130,31 @@ static int files_ref_store_config(const char *var, const char *value,
130 const struct config_context *ctx UNUSED,
131 void *payload)
132 {
124 - struct files_ref_store *refs = payload;
133 + struct files_ref_store_write_options *opts = payload;
134
135 if (!strcmp(var, "core.prefersymlinkrefs")) {
127 - refs->prefer_symlink_refs = git_config_bool(var, value);
136 + opts->prefer_symlink_refs = git_config_bool(var, value);
137 } else if (!strcmp(var, "core.logallrefupdates")) {
129 - refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);
138 + opts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);
139 }
140
141 return 0;
142 }
143
144 +static const struct files_ref_store_write_options *files_ref_store_write_options(struct files_ref_store *refs)
145 +{
146 + struct files_ref_store_write_options *opts = &refs->write_opts_lazy_loaded;
147 +
148 + if (opts->initialized)
149 + return opts;
150 +
151 + opts->log_all_ref_updates = LOG_REFS_UNSET;
152 + repo_config(refs->base.repo, files_ref_store_config, opts);
153 +
154 + opts->initialized = true;
155 + return opts;
156 +}
157 +
158 /*
159 * Create a new submodule ref cache and add it to the internal
160 * set of caches.
@@ -156,9 +179,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
179 refs->packed_ref_store =
180 packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
181 refs->store_flags = opts->access_flags;
159 - refs->log_all_ref_updates = LOG_REFS_UNSET;
182
161 - repo_config(repo, files_ref_store_config, refs);
183 chdir_notify_register(NULL, files_ref_store_reparent, refs);
184
185 strbuf_release(&refdir);
@@ -1890,7 +1911,7 @@ static int log_ref_setup(struct files_ref_store *refs,
1911 const char *refname, int force_create,
1912 int *logfd, struct strbuf *err)
1913 {
1893 - enum log_refs_config log_refs_cfg = refs->log_all_ref_updates;
1914 + enum log_refs_config log_refs_cfg = files_ref_store_write_options(refs)->log_all_ref_updates;
1915 struct strbuf logfile_sb = STRBUF_INIT;
1916 char *logfile;
1917
@@ -3301,6 +3322,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
3322 {
3323 struct files_ref_store *refs =
3324 files_downcast(ref_store, 0, "ref_transaction_finish");
3325 + const struct files_ref_store_write_options *write_opts = files_ref_store_write_options(refs);
3326 size_t i;
3327 int ret = 0;
3328 struct strbuf sb = STRBUF_INIT;
@@ -3340,7 +3362,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
3362 * We try creating a symlink, if that succeeds we continue to the
3363 * next update. If not, we try and create a regular symref.
3364 */
3343 - if (update->new_target && refs->prefer_symlink_refs)
3365 + if (update->new_target && write_opts->prefer_symlink_refs)
3366 /*
3367 * By using the `NOT_CONSTANT()` trick, we can avoid
3368 * errors by `clang`'s `-Wunreachable` logic that would
t/t0600-reffiles-backend.sh
+21
@@ -519,4 +519,25 @@ test_expect_success 'symref transaction supports false symlink config' '
519 test_cmp expect actual
520 '
521
522 +test_expect_success SYMLINKS,!MINGW,!WITH_BREAKING_CHANGES 'core.preferSymlinkRefs can be set up via onbranch condition' '
523 + test_when_finished "git symbolic-ref -d TEST_SYMREF_HEAD" &&
524 + test_when_finished "rm -f .git/include" &&
525 + git update-ref refs/heads/new @ &&
526 + cat >.git/include <<-\EOF &&
527 + [core]
528 + preferSymlinkRefs = true
529 + EOF
530 + test_config includeIf.onbranch:"$(git branch --show-current)".path \
531 + "$(pwd)/.git/include" &&
532 + cat >stdin <<-EOF &&
533 + start
534 + symref-create TEST_SYMREF_HEAD refs/heads/new
535 + prepare
536 + commit
537 + EOF
538 + git update-ref --no-deref --stdin <stdin &&
539 + test_path_is_symlink .git/TEST_SYMREF_HEAD &&
540 + test "$(test_readlink .git/TEST_SYMREF_HEAD)" = refs/heads/new
541 +'
542 +
543 test_done