git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2 2/5] refs/reftable: add fsck check for checking the table name

From
Karthik Nayak <karthik.188@gmail.com>
Date
Sep 9, 2025, 08:42 UTC
Message-ID
<CAOLa=ZQoOX9y2_KwnAcyS-dVeQm1QnFdZocomt05BkxL9xDFuA@mail.gmail.com>
In-Reply-To
<aLf3MaKHZSQfnBlT@pks.im>
Patrick Steinhardt <ps@pks.im> writes:
Show 31 quoted lines
> On Tue, Sep 02, 2025 at 09:05:22AM +0200, Karthik Nayak wrote:
>> The `git refs verify` command is used to run fsck checks on the
>> reference backends. This command is also invoked when users run 'git
>> fsck'. While the files-backend has some fsck checks added, the reftable
>> backend lacks such checks. Let's add the required infrastructure and a
>> check to test for the table names in the 'tables.list' of reftables.
>>
>> For the infrastructure, since the reftable library is treated as an
>> independent library we should ensure that the library code works
>> independently without knowledge about Git's internals. To do this,
>> add both 'reftable/fsck.c' and 'reftable/reftable-fsck.h'. Which
>> provide an entry point 'reftable_fsck_check' for running fsck checks
>> over a provided reftable stack. The callee provides the function with
>> callbacks to handle issue and information reporting.
>>
>> Add glue code in 'refs/reftable-backend.c' which calls the reftable
>> library to perform the fsck checks. Here we also map the reftable errors
>> to Git' fsck errors.
>>
>> Introduce a check to validate table names for a given reftable stack.
>> Also add 'badReftableTableName' as a corresponding error within Git. Add
>> a test to check for this behavior.
>>
>> While here, remove a unused header `#include "../lockfile.h"` from
>> 'refs/reftable-backend.c'.
>
> It's quite a bunch of changes overall that could've been reasonably
> split up into multiple commits. E.g. one to introduce the reftable-side
> logic, one to start calling it in Git, and one to drop the superfluous
> header.
>

I'm always hesitant to have small commits for some reason. Thanks for calling out, I'll split it up.

Sidenote: I used `git history split` for this, and it was just perfect.
Show 13 quoted lines
>> diff --git a/Makefile b/Makefile
>> index e11340c1ae..f2ddcc8d7c 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -2733,6 +2733,7 @@ REFTABLE_OBJS += reftable/error.o
>>  REFTABLE_OBJS += reftable/block.o
>>  REFTABLE_OBJS += reftable/blocksource.o
>>  REFTABLE_OBJS += reftable/iter.o
>> +REFTABLE_OBJS += reftable/fsck.o
>
> "f" is before "i" in the alphabet I'm accustomed to :) So let's retain
> lexicographic ordering here.
>

This ordering was already broken, but that's no reason to break it more. Let' me fix it.

Show 23 quoted lines
>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>> index 8dae1e1112..c38c6422f8 100644
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -2675,11 +2676,55 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
>>  	return ret;
>>  }
>>
>> -static int reftable_be_fsck(struct ref_store *ref_store UNUSED,
>> -			    struct fsck_options *o UNUSED,
>> +static void reftable_fsck_verbose_handler(const char *msg, void *cb_data)
>> +{
>> +	struct fsck_options *o = cb_data;
>> +
>> +	if (o->verbose)
>> +		fprintf_ln(stderr, "%s", _(msg));
>> +}
>
> Is this `_()` marker correct here? There isn't really any reasonable way
> for somebody to translate a variable with unknown contents. So shouldn't
> it only be the caller of `reftable_fsck_verbose_handler()` that should
> mark the string as translatable?
>

True, but this is a callback function called from within the reftable library. I guess for now I can leave out the translation and we can think about the best way to fix that later.

We need a more generic way to translate output strings originating from the reftable library.

Show 30 quoted lines
>> +static int reftable_fsck_error_handler(struct reftable_fsck_info *info,
>> +				       void *cb_data)
>> +{
>> +	struct fsck_ref_report report = { .path = info->path };
>> +	struct fsck_options *o = cb_data;
>> +	enum fsck_msg_id msg_id;
>> +
>> +	switch (info->error) {
>> +	case REFTABLE_FSCK_ERROR_TABLE_NAME:
>> +		msg_id = FSCK_MSG_BAD_REFTABLE_TABLE_NAME;
>> +		break;
>> +	default:
>> +		BUG("unknown fsck error: %d", info->error);
>> +	}
>> +
>> +	return fsck_report_ref(o, &report, msg_id, "%s", info->msg);
>> +}
>
> I think this function will become a bit unwieldy over time. We might
> instead want to have an array that maps from reftable-specific to
> fsck-specific error code:
>
>     static const fsck_msg_id[] = {
>         [REFTABLE_FSCK_ERROR_TABLE_NAME] = FSCK_MSG_BAD_REFTABLE_TABLE_NAME,
>     };
>
> So in that case, all we'd have to do is to perform bounds checking in
> the above function. And maybe verify that the developer didn't forget to
> fill in a new msg ID by checking that the derived message ID is non-zero.
>
Yeah that sounds like a really good improvement, let me add that.
Show 17 quoted lines
>> +static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o,
>>  			    struct worktree *wt UNUSED)
>>  {
>> -	return 0;
>> +	struct reftable_ref_store *refs;
>> +	struct strmap_entry *entry;
>> +	struct hashmap_iter iter;
>> +	int ret = 0;
>> +
>> +	refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
>> +
>> +	if (o->verbose)
>> +		fprintf_ln(stderr, _("Checking references consistency"));
>
> This line is duplicate across both backends, right? Maybe it's something
> that we can do in the generic logic?
>
Yeah, we can. Will do.
Show 64 quoted lines
>> +	ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler,
>> +				  reftable_fsck_verbose_handler, o);
>> +
>> +	strmap_for_each_entry(&refs->worktree_backends, &iter, entry) {
>> +		struct reftable_backend *b = (struct reftable_backend *)entry->value;
>> +		ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler,
>> +					  reftable_fsck_verbose_handler, o);
>> +	}
>> +
>> +	return ret;
>>  }
>>
>>  struct ref_storage_be refs_be_reftable = {
>
> Looks good.
>
>> diff --git a/reftable/fsck.c b/reftable/fsck.c
>> new file mode 100644
>> index 0000000000..4282b1413e
>> --- /dev/null
>> +++ b/reftable/fsck.c
>> @@ -0,0 +1,53 @@
>> +#include "basics.h"
>> +#include "reftable-fsck.h"
>> +#include "stack.h"
>> +
>> +int reftable_fsck_check(struct reftable_stack *stack,
>> +			reftable_fsck_report_fn report_fn,
>> +			reftable_fsck_verbose_fn verbose_fn,
>> +			void *cb_data)
>> +{
>> +
>> +	char **names = NULL;
>> +	uint64_t min, max;
>> +	int err = 0;
>> +
>> +	if (stack == NULL)
>> +		goto out;
>> +
>> +	err = read_lines(stack->list_file, &names);
>> +	if (err < 0)
>> +		goto out;
>> +
>> +	verbose_fn("Checking reftable table names", cb_data);
>> +
>> +	for (size_t i = 0; names[i]; i++) {
>> +		struct reftable_fsck_info info = {
>> +			.error = REFTABLE_FSCK_ERROR_TABLE_NAME,
>> +			.path = names[i],
>> +		};
>> +		uint32_t rnd;
>> +		/*
>> +		 * We want to match the tail '.ref'. One extra byte to ensure
>> +		 * that there is no unexpected extra character and one byte for
>> +		 * the null terminator added by sscanf.
>> +		 */
>> +		char tail[6];
>> +
>> +		if (sscanf(names[i], "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x%5s",
>> +			   &min, &max, &rnd, tail) != 4) {
>> +			info.msg = "invalid reftable table name";
>
> This here is where the string should be translated.
>

But we don't have translation capabilities within the reftable lib no? Or am I mistaken?

Show 7 quoted lines
>> +			err = report_fn(&info, cb_data);
>> +			continue;
>> +		}
>
> I think sscanf is quite frowned-upon in the Git codebase. Maybe we
> should manually parse through the string instead?
>

That would be cumbersome. This isn't user input data, so I thought this would be okay. But let me do the change.

Show 5 quoted lines
> Furthermore, I think we should move every single check into a separate
> function, similar to how the files backend does it. This ensures that
> checks are self-contained and that it's way easier to add new checks
> over time.
>
I think Shejialuo also mentioned this, let me do that.
> Another angle: did you verify that reftables written by JGit follow this
> format?
>
No I haven't.
Show 23 quoted lines
>> +		if (strcmp(tail, ".ref")) {
>> +			info.msg = "invalid reftable table extension";
>
> Same here, this should be translated.
>
>> diff --git a/reftable/reftable-fsck.h b/reftable/reftable-fsck.h
>> new file mode 100644
>> index 0000000000..4cf0053234
>> --- /dev/null
>> +++ b/reftable/reftable-fsck.h
>> @@ -0,0 +1,38 @@
>> +#ifndef REFTABLE_FSCK_H
>> +#define REFTABLE_FSCK_H
>> +
>> +#include "reftable-stack.h"
>> +
>> +enum reftable_fsck_error {
>> +	/* Invalid table name */
>> +	REFTABLE_FSCK_ERROR_TABLE_NAME = -1,
>> +};
>
> Wouldn't it be more natural to give these positive numbers?
>

Yes, that would be better and also fits in with the array suggstion you made earlier.

Show 10 quoted lines
>> +/* Represents an individual error encountered during the FSCK checks. */
>> +struct reftable_fsck_info {
>> +	enum reftable_fsck_error error;
>> +	const char *msg;
>> +	const char *path;
>> +};
>
> I wonder whether it should be the reftable library that decides on the
> severity of each generated finding.
>
I think that'd make sense. Let me add that in.
Show 20 quoted lines
>> +typedef int reftable_fsck_report_fn(struct reftable_fsck_info *info,
>> +				    void *cb_data);
>> +typedef void reftable_fsck_verbose_fn(const char *msg, void *cb_data);
>> +
>> +/*
>> + * Given a reftable stack, perform FSCK check on the stack.
>
> s/FSCK check/consistency checks/
>
>> + *
>> + * If an issue is encountered, the issue is reported to the callee via the
>> + * provided 'report_fn'. If the issue is non-recoverable the flow will not
>> + * continue. If it is recoverable, the flow will continue and further issues
>> + * will be reported as identified.
>> + *
>> + * The 'verbose_fn' will be invoked to provide verbose information about
>> + * the progress and state of the FSCK checks.
>
> Same here.
>
Thanks, changed both.
Show 17 quoted lines
>> diff --git a/t/t0614-reftable-fsck.sh b/t/t0614-reftable-fsck.sh
>> new file mode 100755
>> index 0000000000..81d30df2d7
>> --- /dev/null
>> +++ b/t/t0614-reftable-fsck.sh
>> @@ -0,0 +1,58 @@
>> +#!/bin/sh
>> +
>> +test_description='Test reftable backend consistency check'
>> +
>> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
>> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
>
> Tests shouldn't define these variables, but should dynamically figure
> out what the default branch name is as required, e.g. by using
> git-symbolic-ref(1).
>
Yeah, makes sense. Will change it.
Show 19 quoted lines
>> +GIT_TEST_DEFAULT_REF_FORMAT=reftable
>> +export GIT_TEST_DEFAULT_REF_FORMAT
>> +
>> +. ./test-lib.sh
>> +
>> +test_expect_success 'table name should be checked' '
>> +	test_when_finished "rm -rf repo" &&
>> +	git init repo &&
>> +	(
>> +		cd repo &&
>> +		git commit --allow-empty -m initial &&
>> +
>> +		git refs verify 2>err &&
>> +		test_must_be_empty err &&
>> +
>> +		TABLE_NAME=$(cat .git/reftable/tables.list | head -n1) &&
>
> You can drop the cat(1) invocation and directly say `head -n1 file`.
>
Indeed, thanks!
Show 7 quoted lines
>> +		sed "1s/^/extra/" .git/reftable/tables.list >.git/reftable/tables.list.tmp &&
>> +		mv .git/reftable/tables.list.tmp .git/reftable/tables.list &&
>> +		mv .git/reftable/${TABLE_NAME} .git/reftable/extra${TABLE_NAME} &&
>
> No need for the curly braces around TABLE_NAME here and further down. It
> would be nice to quote these strings though.
>

Understandable. I always prefer using them, since they make it much easier to read, even without the ambiguity issue.

Show 23 quoted lines
>> +
>> +		test_must_fail git refs verify 2>err &&
>> +		cat >expect <<-EOF &&
>> +		error: extra${TABLE_NAME}: badReftableTableName: invalid reftable table name
>> +		EOF
>> +		test_cmp expect err
>> +	)
>> +'
>> +
>> +test_expect_success 'table name should be checked' '
>> +	test_when_finished "rm -rf repo" &&
>> +	git init repo &&
>> +	(
>> +		cd repo &&
>> +		git commit --allow-empty -m initial &&
>> +
>> +		git refs verify 2>err &&
>> +		test_must_be_empty err &&
>> +
>> +		TABLE_NAME=$(cat .git/reftable/tables.list | head -n1) &&
>
> Same here wrt the extra invocation of cat(1).
>
Will change! Thanks.
> Patrick
Previous: Karthik NayakNext: Karthik Nayak
Message 9 of 18 in “refs/reftable: add fsck checks”
  1. 0/5 refs/reftable: add fsck checksKarthik Nayak, Sep 2, 2025
  2. 1/5 fsck: order 'fsck_msg_type' alphabeticallyKarthik Nayak, Sep 2, 2025
  3. Junio C HamanoSep 2, 2025
  4. Karthik NayakSep 8, 2025
  5. 2/5 refs/reftable: add fsck check for checking the table nameKarthik Nayak, Sep 2, 2025
  6. Patrick SteinhardtSep 3, 2025
  7. shejialuoSep 3, 2025
  8. Karthik NayakSep 9, 2025
  9. Karthik NayakSep 9, 2025
  10. 3/5 refs/reftable: add fsck check for number of tablesKarthik Nayak, Sep 2, 2025
  11. Patrick SteinhardtSep 3, 2025
  12. Karthik NayakSep 15, 2025
  13. 4/5 refs/reftable: add fsck check for trailing newlineKarthik Nayak, Sep 2, 2025
  14. Junio C HamanoSep 2, 2025
  15. Patrick SteinhardtSep 3, 2025
  16. 5/5 refs/reftable: add fsck check for incorrect update indexKarthik Nayak, Sep 2, 2025
  17. Junio C HamanoSep 2, 2025
  18. Karthik NayakSep 18, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.