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

Same as with the "files" backend, the "reftable" backend also has a chicken-and-egg problem with "onbranch" conditions. Fix this issue the same as we did with the "files" backend by lazy-loading configuration. Now that both the "files" and the "reftable" backend handle this properly, add a generic test to t1400 that verifies that the user can configure "core.logAllRefUpdates" via an "onbranch" condition. This is mostly a nonsensical thing to do in the first place, but it serves as a good sanity check. Note that we had to move `should_write_log()` around so that it can access the new `reftable_be_write_options()` function. 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 79fa75d499d767540fae8d85ab62391cae6591f9
3 files changed +116 -61
refs/reftable-backend.c
+85 -61
@@ -141,10 +141,21 @@ struct reftable_ref_store {
141 */
142 struct strmap worktree_backends;
143 struct reftable_stack_options stack_options;
144 - struct reftable_write_options write_options;
144 +
145 + /*
146 + * Options used when writing to or compacting the reftable stacks.
147 + * These are parsed from the configuration lazily on first use via
148 + * `reftable_be_write_options()` so that we don't have to access the
149 + * configuration when initializing the ref store. Do not access these
150 + * fields directly, but use the accessor instead.
151 + */
152 + struct reftable_be_write_options {
153 + struct reftable_write_options opts;
154 + enum log_refs_config log_all_ref_updates;
155 + bool initialized;
156 + } write_opts_lazy_loaded;
157
158 unsigned int store_flags;
147 - enum log_refs_config log_all_ref_updates;
159 int err;
160 };
161
@@ -285,26 +296,6 @@ out:
296 return ret;
297 }
298
288 -static int should_write_log(struct reftable_ref_store *refs, const char *refname)
289 -{
290 - enum log_refs_config log_refs_cfg = refs->log_all_ref_updates;
291 - if (log_refs_cfg == LOG_REFS_UNSET)
292 - log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
293 -
294 - switch (log_refs_cfg) {
295 - case LOG_REFS_NONE:
296 - return refs_reflog_exists(&refs->base, refname);
297 - case LOG_REFS_ALWAYS:
298 - return 1;
299 - case LOG_REFS_NORMAL:
300 - if (should_autocreate_reflog(log_refs_cfg, refname))
301 - return 1;
302 - return refs_reflog_exists(&refs->base, refname);
303 - default:
304 - BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg);
305 - }
306 -}
307 -
299 static void fill_reftable_log_record(struct reftable_log_record *log, const struct ident_split *split)
300 {
301 const char *tz_begin;
@@ -336,38 +327,72 @@ static int reftable_be_config(const char *var, const char *value,
327 void *payload)
328 {
329 struct reftable_ref_store *refs = payload;
330 + struct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded;
331
332 if (!strcmp(var, "reftable.blocksize")) {
333 unsigned long block_size = git_config_ulong(var, value, ctx->kvi);
334 if (block_size > 16777215)
335 die("reftable block size cannot exceed 16MB");
344 - refs->write_options.block_size = block_size;
336 + opts->opts.block_size = block_size;
337 } else if (!strcmp(var, "reftable.restartinterval")) {
338 unsigned long restart_interval = git_config_ulong(var, value, ctx->kvi);
339 if (restart_interval > UINT16_MAX)
340 die("reftable block size cannot exceed %u", (unsigned)UINT16_MAX);
349 - refs->write_options.restart_interval = restart_interval;
341 + opts->opts.restart_interval = restart_interval;
342 } else if (!strcmp(var, "reftable.indexobjects")) {
351 - refs->write_options.skip_index_objects = !git_config_bool(var, value);
343 + opts->opts.skip_index_objects = !git_config_bool(var, value);
344 } else if (!strcmp(var, "reftable.geometricfactor")) {
345 unsigned long factor = git_config_ulong(var, value, ctx->kvi);
346 if (factor > UINT8_MAX)
347 die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX);
356 - refs->write_options.auto_compaction_factor = factor;
348 + opts->opts.auto_compaction_factor = factor;
349 } else if (!strcmp(var, "reftable.locktimeout")) {
350 int64_t lock_timeout = git_config_int64(var, value, ctx->kvi);
351 if (lock_timeout > LONG_MAX)
352 die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX);
353 if (lock_timeout < 0 && lock_timeout != -1)
354 die("reftable lock timeout does not support negative values other than -1");
363 - refs->write_options.lock_timeout_ms = lock_timeout;
355 + opts->opts.lock_timeout_ms = lock_timeout;
356 } else if (!strcmp(var, "core.logallrefupdates")) {
365 - refs->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);
357 + opts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value);
358 }
359
360 return 0;
361 }
362
363 +static const struct reftable_be_write_options *reftable_be_write_options(struct reftable_ref_store *refs)
364 +{
365 + struct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded;
366 + mode_t mask;
367 +
368 + if (opts->initialized)
369 + return opts;
370 +
371 + mask = umask(0);
372 + umask(mask);
373 +
374 + opts->opts.default_permissions = calc_shared_perm(refs->base.repo, 0666 & ~mask);
375 + opts->opts.disable_auto_compact =
376 + !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1);
377 + opts->opts.lock_timeout_ms = 100;
378 + opts->log_all_ref_updates = LOG_REFS_UNSET;
379 +
380 + repo_config(refs->base.repo, reftable_be_config, refs);
381 +
382 + /*
383 + * It is somewhat unfortunate that we have to mirror the default block
384 + * size of the reftable library here. But given that the write options
385 + * wouldn't be updated by the library here, and given that we require
386 + * the proper block size to trim reflog message so that they fit, we
387 + * must set up a proper value here.
388 + */
389 + if (!opts->opts.block_size)
390 + opts->opts.block_size = 4096;
391 +
392 + opts->initialized = true;
393 + return opts;
394 +}
395 +
396 static void reftable_be_reparent(const char *name UNUSED,
397 const char *old_cwd,
398 const char *new_cwd,
@@ -391,10 +416,6 @@ static struct ref_store *reftable_be_init(struct repository *repo,
416 struct strbuf refdir = STRBUF_INIT;
417 struct strbuf path = STRBUF_INIT;
418 bool is_worktree;
394 - mode_t mask;
395 -
396 - mask = umask(0);
397 - umask(mask);
419
420 refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,
421 &ref_common_dir);
@@ -413,23 +434,6 @@ static struct ref_store *reftable_be_init(struct repository *repo,
434 default:
435 BUG("unknown hash algorithm %d", repo->hash_algo->format_id);
436 }
416 - refs->write_options.default_permissions = calc_shared_perm(repo, 0666 & ~mask);
417 - refs->write_options.disable_auto_compact =
418 - !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1);
419 - refs->write_options.lock_timeout_ms = 100;
420 - refs->log_all_ref_updates = LOG_REFS_UNSET;
421 -
422 - repo_config(repo, reftable_be_config, refs);
423 -
424 - /*
425 - * It is somewhat unfortunate that we have to mirror the default block
426 - * size of the reftable library here. But given that the write options
427 - * wouldn't be updated by the library here, and given that we require
428 - * the proper block size to trim reflog message so that they fit, we
429 - * must set up a proper value here.
430 - */
431 - if (!refs->write_options.block_size)
432 - refs->write_options.block_size = 4096;
437
438 /*
439 * Set up the main reftable stack that is hosted in GIT_COMMON_DIR.
@@ -998,7 +1002,7 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
1002 struct reftable_addition *addition;
1003
1004 ret = reftable_stack_new_addition(&addition, be->stack,
1001 - &refs->write_options,
1005 + &reftable_be_write_options(refs)->opts,
1006 REFTABLE_STACK_NEW_ADDITION_RELOAD);
1007 if (ret) {
1008 if (ret == REFTABLE_LOCK_ERROR)
@@ -1437,6 +1441,26 @@ static int transaction_update_cmp(const void *a, const void *b)
1441 return strcmp(update_a->update->refname, update_b->update->refname);
1442 }
1443
1444 +static int should_write_log(struct reftable_ref_store *refs, const char *refname)
1445 +{
1446 + enum log_refs_config log_refs_cfg = reftable_be_write_options(refs)->log_all_ref_updates;
1447 + if (log_refs_cfg == LOG_REFS_UNSET)
1448 + log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
1449 +
1450 + switch (log_refs_cfg) {
1451 + case LOG_REFS_NONE:
1452 + return refs_reflog_exists(&refs->base, refname);
1453 + case LOG_REFS_ALWAYS:
1454 + return 1;
1455 + case LOG_REFS_NORMAL:
1456 + if (should_autocreate_reflog(log_refs_cfg, refname))
1457 + return 1;
1458 + return refs_reflog_exists(&refs->base, refname);
1459 + default:
1460 + BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg);
1461 + }
1462 +}
1463 +
1464 static int write_transaction_table(struct reftable_writer *writer, void *cb_data)
1465 {
1466 struct write_transaction_table_arg *arg = cb_data;
@@ -1571,7 +1595,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
1595 memcpy(log->value.update.old_hash,
1596 tx_update->current_oid.hash, GIT_MAX_RAWSZ);
1597 log->value.update.message =
1574 - xstrndup(u->msg, arg->refs->write_options.block_size / 2);
1598 + xstrndup(u->msg, reftable_be_write_options(arg->refs)->opts.block_size / 2);
1599 }
1600 }
1601
@@ -1687,9 +1711,9 @@ static int reftable_be_optimize(struct ref_store *ref_store,
1711 stack = refs->main_backend.stack;
1712
1713 if (opts->flags & REFS_OPTIMIZE_AUTO)
1690 - ret = reftable_stack_auto_compact(stack, &refs->write_options);
1714 + ret = reftable_stack_auto_compact(stack, &reftable_be_write_options(refs)->opts);
1715 else
1692 - ret = reftable_stack_compact_all(stack, &refs->write_options, NULL);
1716 + ret = reftable_stack_compact_all(stack, &reftable_be_write_options(refs)->opts, NULL);
1717 if (ret < 0) {
1718 ret = error(_("unable to compact stack: %s"),
1719 reftable_error_str(ret));
@@ -1723,7 +1747,7 @@ static int reftable_be_optimize_required(struct ref_store *ref_store,
1747 if (opts->flags & REFS_OPTIMIZE_AUTO)
1748 use_heuristics = true;
1749
1726 - return reftable_stack_compaction_required(stack, &refs->write_options,
1750 + return reftable_stack_compaction_required(stack, &reftable_be_write_options(refs)->opts,
1751 use_heuristics, required);
1752 }
1753
@@ -1843,7 +1867,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
1867 logs[logs_nr].refname = xstrdup(arg->newname);
1868 logs[logs_nr].update_index = deletion_ts;
1869 logs[logs_nr].value.update.message =
1846 - xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
1870 + xstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2);
1871 memcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ);
1872 logs_nr++;
1873
@@ -1882,7 +1906,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
1906 logs[logs_nr].refname = xstrdup(arg->newname);
1907 logs[logs_nr].update_index = creation_ts;
1908 logs[logs_nr].value.update.message =
1885 - xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
1909 + xstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2);
1910 memcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ);
1911 logs_nr++;
1912
@@ -1981,7 +2005,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,
2005 if (ret)
2006 goto done;
2007 ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
1984 - &refs->write_options,
2008 + &reftable_be_write_options(refs)->opts,
2009 REFTABLE_STACK_NEW_ADDITION_RELOAD);
2010
2011 done:
@@ -2012,7 +2036,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,
2036 if (ret)
2037 goto done;
2038 ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
2015 - &refs->write_options,
2039 + &reftable_be_write_options(refs)->opts,
2040 REFTABLE_STACK_NEW_ADDITION_RELOAD);
2041
2042 done:
@@ -2378,7 +2402,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,
2402 arg.stack = be->stack;
2403
2404 ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,
2381 - &refs->write_options,
2405 + &reftable_be_write_options(refs)->opts,
2406 REFTABLE_STACK_NEW_ADDITION_RELOAD);
2407
2408 done:
@@ -2451,7 +2475,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,
2475 arg.stack = be->stack;
2476
2477 ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,
2454 - &refs->write_options,
2478 + &reftable_be_write_options(refs)->opts,
2479 REFTABLE_STACK_NEW_ADDITION_RELOAD);
2480
2481 assert(ret != REFTABLE_API_ERROR);
@@ -2574,7 +2598,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
2598 goto done;
2599
2600 ret = reftable_stack_new_addition(&add, be->stack,
2577 - &refs->write_options,
2601 + &reftable_be_write_options(refs)->opts,
2602 REFTABLE_STACK_NEW_ADDITION_RELOAD);
2603 if (ret < 0)
2604 goto done;
t/t0613-reftable-write-options.sh
+19
@@ -278,4 +278,23 @@ test_expect_success 'object index can be disabled' '
278 )
279 '
280
281 +test_expect_success 'write options can be set up via onbranch condition' '
282 + test_config_global core.logAllRefUpdates false &&
283 + test_when_finished "rm -rf repo" &&
284 + init_repo &&
285 + (
286 + cd repo &&
287 + test_commit A &&
288 + test_commit B &&
289 + cat >.git/include <<-\EOF &&
290 + [reftable]
291 + blockSize = 123
292 + EOF
293 + git config includeIf.onbranch:master.path "$(pwd)/.git/include" &&
294 + git refs optimize &&
295 + test-tool dump-reftable -b .git/reftable/*.ref >stats &&
296 + test_grep "block_size: 123" stats
297 + )
298 +'
299 +
300 test_done
t/t1400-update-ref.sh
+12
@@ -178,6 +178,18 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always'
178 test_must_fail git reflog exists $outside
179 '
180
181 +test_expect_success 'core.logAllRefUpdates can be set up via onbranch condition' '
182 + test_when_finished "git update-ref -d $outside" &&
183 + test_when_finished "rm -f .git/include" &&
184 + cat >.git/include <<-\EOF &&
185 + [core]
186 + logAllRefUpdates = always
187 + EOF
188 + test_config includeIf.onbranch:main.path "$(pwd)/.git/include" &&
189 + git update-ref $outside $A &&
190 + git reflog exists $outside
191 +'
192 +
193 test_expect_success "create $m (by HEAD)" '
194 git update-ref HEAD $A &&
195 test $A = $(git show-ref -s --verify $m)