Subject: [PATCH v2 00/15] reftable: drop generic `reftable_table` interface

From: Patrick Steinhardt <[email-masked]>
Date: Wed, 14 Aug 2024 15:22:36 +0200

Hi,

this is the second version of my patch series that gets rid of the
generic `reftable_table` interface. It made it way harder to understand
the reftable code base and is not really required nowadays anymore where
we have generic re-seekable reftable iterators.

Changes compared to v1:

  - Fix some commit message typos.

  - Remove some braces while at it.

  - Drop the "-c" switch from the test-tool's help output.

  - Restore printing-related functionality, but move all of it into the
    test-helper. It has no reason to exist in the reftable library.

Thanks!

Patrick

Patrick Steinhardt (15):
  reftable/merged: expose functions to initialize iterators
  reftable/merged: rename `reftable_new_merged_table()`
  reftable/merged: stop using generic tables in the merged table
  reftable/stack: open-code reading refs
  reftable/iter: drop double-checking logic
  reftable/generic: move generic iterator code into iterator interface
  reftable/dump: drop unused `compact_stack()`
  t/helper: inline `reftable_dump_main()`
  t/helper: inline `reftable_reader_print_file()`
  t/helper: inline `reftable_stack_print_directory()`
  t/helper: inline `reftable_table_print()`
  t/helper: inline printing of reftable records
  t/helper: use `hash_to_hex_algop()` to print hashes
  t/helper: refactor to not use `struct reftable_table`
  reftable/generic: drop interface

 Makefile                         |   2 -
 reftable/dump.c                  | 111 ---------------
 reftable/generic.c               | 229 -------------------------------
 reftable/generic.h               |  37 -----
 reftable/iter.c                  | 126 ++++++++++++++---
 reftable/iter.h                  |  30 +++-
 reftable/merged.c                |  72 ++++------
 reftable/merged.h                |   4 +-
 reftable/reader.c                |  70 +---------
 reftable/reader.h                |   4 +
 reftable/record.c                | 127 -----------------
 reftable/record.h                |   1 -
 reftable/reftable-generic.h      |  47 -------
 reftable/reftable-merged.h       |  26 ++--
 reftable/reftable-reader.h       |   9 --
 reftable/reftable-record.h       |   8 --
 reftable/reftable-stack.h        |   3 -
 reftable/reftable-tests.h        |   1 -
 reftable/stack.c                 |  94 ++++++-------
 reftable/stack_test.c            |  29 ++--
 t/helper/test-reftable.c         | 189 ++++++++++++++++++++++++-
 t/unit-tests/t-reftable-merged.c |  17 +--
 22 files changed, 422 insertions(+), 814 deletions(-)
 delete mode 100644 reftable/dump.c
 delete mode 100644 reftable/generic.c
 delete mode 100644 reftable/generic.h
 delete mode 100644 reftable/reftable-generic.h

Range-diff against v1:
 1:  404d64effd =  1:  472c169b50 reftable/merged: expose functions to initialize iterators
 2:  511416fb73 =  2:  bc6f1cd8c1 reftable/merged: rename `reftable_new_merged_table()`
 3:  86e2f9f5dc =  3:  58e91ab4b3 reftable/merged: stop using generic tables in the merged table
 4:  d5d24e03bd =  4:  6ba3fcee41 reftable/stack: open-code reading refs
 5:  ab7538d0bb !  5:  cac08a934c reftable/iter: drop double-checking logic
    @@ Commit message
     
         The filtering ref iterator can be used to only yield refs which are not
         in a specific skip list. This iterator has an option to double-check the
    -    results it returns, which causes us to seek tho reference we are about
    +    results it returns, which causes us to seek the reference we are about
         to yield via a separate table such that we detect whether the reference
         that the first iterator has yielded actually exists.
     
 6:  14924604ce !  6:  103262dc79 reftable/generic: move generic iterator code into iterator interface
    @@ Commit message
         Move functions relating to the reftable iterator from "generic.c" into
         "iter.c". This prepares for the removal of the former subsystem.
     
    +    While at it, remove some unneeded braces to conform to our coding style.
    +
         Signed-off-by: Patrick Steinhardt <[email-masked]>
     
      ## reftable/generic.c ##
    @@ reftable/iter.c: void iterator_from_indexed_table_ref_iter(struct reftable_itera
     +
     +void reftable_iterator_destroy(struct reftable_iterator *it)
     +{
    -+	if (!it->ops) {
    ++	if (!it->ops)
     +		return;
    -+	}
     +	it->ops->close(it->iter_arg);
     +	it->ops = NULL;
     +	FREE_AND_NULL(it->iter_arg);
 -:  ---------- >  7:  4011fa65d8 reftable/dump: drop unused `compact_stack()`
 9:  4184f46f92 !  8:  ceaa296bfd reftable/dump: move code into "t/helper/test-reftable.c"
    @@ Metadata
     Author: Patrick Steinhardt <[email-masked]>
     
      ## Commit message ##
    -    reftable/dump: move code into "t/helper/test-reftable.c"
    +    t/helper: inline `reftable_dump_main()`
     
    -    The code in "reftable/dump.c" is only ever used by our test-tool to
    -    implement the "reftable" subcommand. It also feels very unlikely that it
    -    will ever be useful to any other potential user of the reftable library,
    -    which makes it a weird candidate to have in the library interface.
    +    The printing functionality part of `reftable/dump.c` is really only used
    +    by our "dump-reftable" test helper. It is certainly not generic logic
    +    that is useful to anybody outside of Git, and the format it generates is
    +    quite specific. Still, parts of it are used in our test suite and the
    +    output may be useful to take a peek into reftable stacks, tables and
    +    blocks. So while it does not make sense to expose this as part of the
    +    reftable library, it does make sense to keep it around.
     
    -    Inline the code into "t/helper/test-reftable.c".
    +    Inline the `reftable_dump_main()` function into the "dump-reftable" test
    +    helper. This clarifies that its format is subject to change and not part
    +    of our public interface. Furthermore, this allows us to iterate on the
    +    implementation in subsequent patches.
     
         Signed-off-by: Patrick Steinhardt <[email-masked]>
     
    @@ reftable/dump.c (deleted)
     -
     -static void print_help(void)
     -{
    --	printf("usage: dump [-cst] arg\n\n"
    +-	printf("usage: dump [-st] arg\n\n"
     -	       "options: \n"
    --	       "  -c compact\n"
     -	       "  -b dump blocks\n"
     -	       "  -t dump table\n"
     -	       "  -s dump stack\n"
    @@ reftable/dump.c (deleted)
     -{
     -	int err = 0;
     -	int opt_dump_blocks = 0;
    +-	int opt_dump_table = 0;
    +-	int opt_dump_stack = 0;
    +-	uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;
     -	const char *arg = NULL, *argv0 = argv[0];
     -
     -	for (; argc > 1; argv++, argc--)
    @@ reftable/dump.c (deleted)
     -			break;
     -		else if (!strcmp("-b", argv[1]))
     -			opt_dump_blocks = 1;
    +-		else if (!strcmp("-t", argv[1]))
    +-			opt_dump_table = 1;
    +-		else if (!strcmp("-6", argv[1]))
    +-			opt_hash_id = GIT_SHA256_FORMAT_ID;
    +-		else if (!strcmp("-s", argv[1]))
    +-			opt_dump_stack = 1;
     -		else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) {
     -			print_help();
     -			return 2;
    @@ reftable/dump.c (deleted)
     -
     -	arg = argv[1];
     -
    --	if (opt_dump_blocks)
    +-	if (opt_dump_blocks) {
     -		err = reftable_reader_print_blocks(arg);
    +-	} else if (opt_dump_table) {
    +-		err = reftable_reader_print_file(arg);
    +-	} else if (opt_dump_stack) {
    +-		err = reftable_stack_print_directory(arg, opt_hash_id);
    +-	}
     -
     -	if (err < 0) {
     -		fprintf(stderr, "%s: %s: %s\n", argv0, arg,
    @@ reftable/dump.c (deleted)
     -	return 0;
     -}
     
    + ## reftable/reftable-tests.h ##
    +@@ reftable/reftable-tests.h: int record_test_main(int argc, const char **argv);
    + int readwrite_test_main(int argc, const char **argv);
    + int stack_test_main(int argc, const char **argv);
    + int tree_test_main(int argc, const char **argv);
    +-int reftable_dump_main(int argc, char *const *argv);
    + 
    + #endif
    +
      ## t/helper/test-reftable.c ##
     @@
      #include "reftable/system.h"
     +#include "reftable/reftable-error.h"
     +#include "reftable/reftable-reader.h"
    ++#include "reftable/reftable-stack.h"
      #include "reftable/reftable-tests.h"
      #include "test-tool.h"
      
    @@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
      
     +static void print_help(void)
     +{
    -+	printf("usage: dump [-cst] arg\n\n"
    ++	printf("usage: dump [-st] arg\n\n"
     +	       "options: \n"
    -+	       "  -c compact\n"
     +	       "  -b dump blocks\n"
     +	       "  -t dump table\n"
     +	       "  -s dump stack\n"
    @@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
     -	return reftable_dump_main(argc, (char *const *)argv);
     +	int err = 0;
     +	int opt_dump_blocks = 0;
    ++	int opt_dump_table = 0;
    ++	int opt_dump_stack = 0;
    ++	uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;
     +	const char *arg = NULL, *argv0 = argv[0];
     +
     +	for (; argc > 1; argv++, argc--)
    @@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
     +			break;
     +		else if (!strcmp("-b", argv[1]))
     +			opt_dump_blocks = 1;
    ++		else if (!strcmp("-t", argv[1]))
    ++			opt_dump_table = 1;
    ++		else if (!strcmp("-6", argv[1]))
    ++			opt_hash_id = GIT_SHA256_FORMAT_ID;
    ++		else if (!strcmp("-s", argv[1]))
    ++			opt_dump_stack = 1;
     +		else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) {
     +			print_help();
     +			return 2;
    @@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
     +
     +	arg = argv[1];
     +
    -+	if (opt_dump_blocks)
    ++	if (opt_dump_blocks) {
     +		err = reftable_reader_print_blocks(arg);
    ++	} else if (opt_dump_table) {
    ++		err = reftable_reader_print_file(arg);
    ++	} else if (opt_dump_stack) {
    ++		err = reftable_stack_print_directory(arg, opt_hash_id);
    ++	}
     +
     +	if (err < 0) {
     +		fprintf(stderr, "%s: %s: %s\n", argv0, arg,
 -:  ---------- >  9:  a62e4612e9 t/helper: inline `reftable_reader_print_file()`
 -:  ---------- > 10:  7acfe4fecc t/helper: inline `reftable_stack_print_directory()`
 7:  0aa7186067 ! 11:  8bd53a1a65 reftable/dump: drop unused `compact_stack()`
    @@ Metadata
     Author: Patrick Steinhardt <[email-masked]>
     
      ## Commit message ##
    -    reftable/dump: drop unused `compact_stack()`
    +    t/helper: inline `reftable_table_print()`
     
    -    The `compact_stack()` function is exposed via `reftable_dump_main()`,
    -    which ultimately ends up being wired into "test-tool reftable". It is
    -    never used by our tests though, and nowadays we have wired up support
    -    for stack compaction into git-pack-refs(1).
    -
    -    Remove the code.
    +    Move `reftable_table_print()` into the "dump-reftable" helper. This
    +    follows the same reasoning as the preceding commit.
     
         Signed-off-by: Patrick Steinhardt <[email-masked]>
     
    - ## reftable/dump.c ##
    -@@ reftable/dump.c: license that can be found in the LICENSE file or at
    - #include <unistd.h>
    - #include <string.h>
    + ## reftable/generic.c ##
    +@@ reftable/generic.c: int reftable_table_read_ref(struct reftable_table *tab, const char *name,
    + 	return err;
    + }
      
    --static int compact_stack(const char *stackdir)
    --{
    --	struct reftable_stack *stack = NULL;
    --	struct reftable_write_options opts = { 0 };
    +-int reftable_table_print(struct reftable_table *tab) {
    +-	struct reftable_iterator it = { NULL };
    +-	struct reftable_ref_record ref = { NULL };
    +-	struct reftable_log_record log = { NULL };
    +-	uint32_t hash_id = reftable_table_hash_id(tab);
    +-	int err;
    +-
    +-	reftable_table_init_ref_iter(tab, &it);
     -
    --	int err = reftable_new_stack(&stack, stackdir, &opts);
    +-	err = reftable_iterator_seek_ref(&it, "");
     -	if (err < 0)
    --		goto done;
    +-		return err;
    +-
    +-	while (1) {
    +-		err = reftable_iterator_next_ref(&it, &ref);
    +-		if (err > 0) {
    +-			break;
    +-		}
    +-		if (err < 0) {
    +-			return err;
    +-		}
    +-		reftable_ref_record_print(&ref, hash_id);
    +-	}
    +-	reftable_iterator_destroy(&it);
    +-	reftable_ref_record_release(&ref);
    +-
    +-	reftable_table_init_log_iter(tab, &it);
     -
    --	err = reftable_stack_compact_all(stack, NULL);
    +-	err = reftable_iterator_seek_log(&it, "");
     -	if (err < 0)
    --		goto done;
    --done:
    --	if (stack) {
    --		reftable_stack_destroy(stack);
    +-		return err;
    +-
    +-	while (1) {
    +-		err = reftable_iterator_next_log(&it, &log);
    +-		if (err > 0) {
    +-			break;
    +-		}
    +-		if (err < 0) {
    +-			return err;
    +-		}
    +-		reftable_log_record_print(&log, hash_id);
     -	}
    --	return err;
    +-	reftable_iterator_destroy(&it);
    +-	reftable_log_record_release(&log);
    +-	return 0;
     -}
     -
    - static void print_help(void)
    + uint64_t reftable_table_max_update_index(struct reftable_table *tab)
    + {
    + 	return tab->ops->max_update_index(tab->table_arg);
    +
    + ## reftable/reftable-generic.h ##
    +@@ reftable/reftable-generic.h: uint64_t reftable_table_min_update_index(struct reftable_table *tab);
    + int reftable_table_read_ref(struct reftable_table *tab, const char *name,
    + 			    struct reftable_ref_record *ref);
    + 
    +-/* dump table contents onto stdout for debugging */
    +-int reftable_table_print(struct reftable_table *tab);
    +-
    + #endif
    +
    + ## t/helper/test-reftable.c ##
    +@@ t/helper/test-reftable.c: static void print_help(void)
    + 	       "\n");
    + }
    + 
    ++static int dump_table(struct reftable_table *tab)
    ++{
    ++	struct reftable_iterator it = { NULL };
    ++	struct reftable_ref_record ref = { NULL };
    ++	struct reftable_log_record log = { NULL };
    ++	uint32_t hash_id = reftable_table_hash_id(tab);
    ++	int err;
    ++
    ++	reftable_table_init_ref_iter(tab, &it);
    ++
    ++	err = reftable_iterator_seek_ref(&it, "");
    ++	if (err < 0)
    ++		return err;
    ++
    ++	while (1) {
    ++		err = reftable_iterator_next_ref(&it, &ref);
    ++		if (err > 0) {
    ++			break;
    ++		}
    ++		if (err < 0) {
    ++			return err;
    ++		}
    ++		reftable_ref_record_print(&ref, hash_id);
    ++	}
    ++	reftable_iterator_destroy(&it);
    ++	reftable_ref_record_release(&ref);
    ++
    ++	reftable_table_init_log_iter(tab, &it);
    ++
    ++	err = reftable_iterator_seek_log(&it, "");
    ++	if (err < 0)
    ++		return err;
    ++
    ++	while (1) {
    ++		err = reftable_iterator_next_log(&it, &log);
    ++		if (err > 0) {
    ++			break;
    ++		}
    ++		if (err < 0) {
    ++			return err;
    ++		}
    ++		reftable_log_record_print(&log, hash_id);
    ++	}
    ++	reftable_iterator_destroy(&it);
    ++	reftable_log_record_release(&log);
    ++	return 0;
    ++}
    ++
    + static int dump_stack(const char *stackdir, uint32_t hash_id)
      {
    - 	printf("usage: dump [-cst] arg\n\n"
    -@@ reftable/dump.c: int reftable_dump_main(int argc, char *const *argv)
    - 	int opt_dump_blocks = 0;
    - 	int opt_dump_table = 0;
    - 	int opt_dump_stack = 0;
    --	int opt_compact = 0;
    - 	uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;
    - 	const char *arg = NULL, *argv0 = argv[0];
    + 	struct reftable_stack *stack = NULL;
    +@@ t/helper/test-reftable.c: static int dump_stack(const char *stackdir, uint32_t hash_id)
      
    -@@ reftable/dump.c: int reftable_dump_main(int argc, char *const *argv)
    - 			opt_hash_id = GIT_SHA256_FORMAT_ID;
    - 		else if (!strcmp("-s", argv[1]))
    - 			opt_dump_stack = 1;
    --		else if (!strcmp("-c", argv[1]))
    --			opt_compact = 1;
    - 		else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) {
    - 			print_help();
    - 			return 2;
    -@@ reftable/dump.c: int reftable_dump_main(int argc, char *const *argv)
    - 		err = reftable_reader_print_file(arg);
    - 	} else if (opt_dump_stack) {
    - 		err = reftable_stack_print_directory(arg, opt_hash_id);
    --	} else if (opt_compact) {
    --		err = compact_stack(arg);
    - 	}
    + 	merged = reftable_stack_merged_table(stack);
    + 	reftable_table_from_merged_table(&table, merged);
    +-	err = reftable_table_print(&table);
    ++	err = dump_table(&table);
    + done:
    + 	if (stack)
    + 		reftable_stack_destroy(stack);
    +@@ t/helper/test-reftable.c: static int dump_reftable(const char *tablename)
    + 		goto done;
      
    - 	if (err < 0) {
    + 	reftable_table_from_reader(&tab, r);
    +-	err = reftable_table_print(&tab);
    ++	err = dump_table(&tab);
    + done:
    + 	reftable_reader_free(r);
    + 	return err;
 8:  1f211e514d ! 12:  c50aabbb80 reftable/dump: drop unused printing functionality
    @@ Metadata
     Author: Patrick Steinhardt <[email-masked]>
     
      ## Commit message ##
    -    reftable/dump: drop unused printing functionality
    +    t/helper: inline printing of reftable records
     
    -    We have a bunch of infrastructure wired up that allows us to print
    -    reftable records, tables and stacks. While this functionality is wired
    -    up via various "test-tool reftable" options, it is never used. It also
    -    feels kind of dubious whether any other eventual user of the reftable
    -    library should use it as it very much feels like a debugging aid rather
    -    than something sensible. The format itself is somewhat inscrutable and
    -    the infrastructure is non-extensible.
    -
    -    Drop this code. The only remaining function in this context is
    -    `reftable_reader_print_blocks()`, which we do use in our tests.
    +    Move printing of reftable records into the "dump-reftable" helper. This
    +    follows the same reasoning as the preceding commit.
     
         Signed-off-by: Patrick Steinhardt <[email-masked]>
     
    - ## reftable/dump.c ##
    -@@ reftable/dump.c: int reftable_dump_main(int argc, char *const *argv)
    - {
    - 	int err = 0;
    - 	int opt_dump_blocks = 0;
    --	int opt_dump_table = 0;
    --	int opt_dump_stack = 0;
    --	uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;
    - 	const char *arg = NULL, *argv0 = argv[0];
    - 
    - 	for (; argc > 1; argv++, argc--)
    -@@ reftable/dump.c: int reftable_dump_main(int argc, char *const *argv)
    - 			break;
    - 		else if (!strcmp("-b", argv[1]))
    - 			opt_dump_blocks = 1;
    --		else if (!strcmp("-t", argv[1]))
    --			opt_dump_table = 1;
    --		else if (!strcmp("-6", argv[1]))
    --			opt_hash_id = GIT_SHA256_FORMAT_ID;
    --		else if (!strcmp("-s", argv[1]))
    --			opt_dump_stack = 1;
    - 		else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) {
    - 			print_help();
    - 			return 2;
    -@@ reftable/dump.c: int reftable_dump_main(int argc, char *const *argv)
    - 
    - 	arg = argv[1];
    - 
    --	if (opt_dump_blocks) {
    -+	if (opt_dump_blocks)
    - 		err = reftable_reader_print_blocks(arg);
    --	} else if (opt_dump_table) {
    --		err = reftable_reader_print_file(arg);
    --	} else if (opt_dump_stack) {
    --		err = reftable_stack_print_directory(arg, opt_hash_id);
    --	}
    - 
    - 	if (err < 0) {
    - 		fprintf(stderr, "%s: %s: %s\n", argv0, arg,
    -
    - ## reftable/generic.c ##
    -@@ reftable/generic.c: int reftable_table_read_ref(struct reftable_table *tab, const char *name,
    - 	return err;
    - }
    - 
    --int reftable_table_print(struct reftable_table *tab) {
    --	struct reftable_iterator it = { NULL };
    --	struct reftable_ref_record ref = { NULL };
    --	struct reftable_log_record log = { NULL };
    --	uint32_t hash_id = reftable_table_hash_id(tab);
    --	int err;
    --
    --	reftable_table_init_ref_iter(tab, &it);
    --
    --	err = reftable_iterator_seek_ref(&it, "");
    --	if (err < 0)
    --		return err;
    --
    --	while (1) {
    --		err = reftable_iterator_next_ref(&it, &ref);
    --		if (err > 0) {
    --			break;
    --		}
    --		if (err < 0) {
    --			return err;
    --		}
    --		reftable_ref_record_print(&ref, hash_id);
    --	}
    --	reftable_iterator_destroy(&it);
    --	reftable_ref_record_release(&ref);
    --
    --	reftable_table_init_log_iter(tab, &it);
    --
    --	err = reftable_iterator_seek_log(&it, "");
    --	if (err < 0)
    --		return err;
    --
    --	while (1) {
    --		err = reftable_iterator_next_log(&it, &log);
    --		if (err > 0) {
    --			break;
    --		}
    --		if (err < 0) {
    --			return err;
    --		}
    --		reftable_log_record_print(&log, hash_id);
    --	}
    --	reftable_iterator_destroy(&it);
    --	reftable_log_record_release(&log);
    --	return 0;
    --}
    --
    - uint64_t reftable_table_max_update_index(struct reftable_table *tab)
    - {
    - 	return tab->ops->max_update_index(tab->table_arg);
    -
    - ## reftable/reader.c ##
    -@@ reftable/reader.c: void reftable_table_from_reader(struct reftable_table *tab,
    - 	tab->table_arg = reader;
    - }
    - 
    --
    --int reftable_reader_print_file(const char *tablename)
    --{
    --	struct reftable_block_source src = { NULL };
    --	int err = reftable_block_source_from_file(&src, tablename);
    --	struct reftable_reader *r = NULL;
    --	struct reftable_table tab = { NULL };
    --	if (err < 0)
    --		goto done;
    --
    --	err = reftable_new_reader(&r, &src, tablename);
    --	if (err < 0)
    --		goto done;
    --
    --	reftable_table_from_reader(&tab, r);
    --	err = reftable_table_print(&tab);
    --done:
    --	reftable_reader_free(r);
    --	return err;
    --}
    --
    - int reftable_reader_print_blocks(const char *tablename)
    - {
    - 	struct {
    -
      ## reftable/record.c ##
     @@ reftable/record.c: static void reftable_ref_record_copy_from(void *rec, const void *src_rec,
      	}
    @@ reftable/record.c: void reftable_record_init(struct reftable_record *rec, uint8_
     -}
     
      ## reftable/record.h ##
    -@@ reftable/record.h: struct reftable_record_vtable {
    - 	 * the same type.
    - 	 */
    - 	int (*cmp)(const void *a, const void *b);
    --
    --	/* Print on stdout, for debugging. */
    --	void (*print)(const void *rec, int hash_size);
    - };
    - 
    - /* returns true for recognized block types. Block start with the block type. */
     @@ reftable/record.h: void reftable_record_init(struct reftable_record *rec, uint8_t typ);
      /* see struct record_vtable */
      int reftable_record_cmp(struct reftable_record *a, struct reftable_record *b);
    @@ reftable/record.h: void reftable_record_init(struct reftable_record *rec, uint8_
      void reftable_record_copy_from(struct reftable_record *rec,
      			       struct reftable_record *src, int hash_size);
     
    - ## reftable/reftable-generic.h ##
    -@@ reftable/reftable-generic.h: uint64_t reftable_table_min_update_index(struct reftable_table *tab);
    - int reftable_table_read_ref(struct reftable_table *tab, const char *name,
    - 			    struct reftable_ref_record *ref);
    - 
    --/* dump table contents onto stdout for debugging */
    --int reftable_table_print(struct reftable_table *tab);
    --
    - #endif
    -
    - ## reftable/reftable-reader.h ##
    -@@ reftable/reftable-reader.h: uint64_t reftable_reader_min_update_index(struct reftable_reader *r);
    - void reftable_table_from_reader(struct reftable_table *tab,
    - 				struct reftable_reader *reader);
    - 
    --/* print table onto stdout for debugging. */
    --int reftable_reader_print_file(const char *tablename);
    - /* print blocks onto stdout for debugging. */
    - int reftable_reader_print_blocks(const char *tablename);
    - 
    -
      ## reftable/reftable-record.h ##
     @@ reftable/reftable-record.h: const unsigned char *reftable_ref_record_val2(const struct reftable_ref_record *
      /* returns whether 'ref' represents a deletion */
    @@ reftable/reftable-record.h: void reftable_log_record_release(struct reftable_log
     -
      #endif
     
    - ## reftable/reftable-stack.h ##
    -@@ reftable/reftable-stack.h: struct reftable_compaction_stats {
    - struct reftable_compaction_stats *
    - reftable_stack_compaction_stats(struct reftable_stack *st);
    - 
    --/* print the entire stack represented by the directory */
    --int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);
    --
    - #endif
    -
    - ## reftable/stack.c ##
    -@@ reftable/stack.c: int reftable_stack_clean(struct reftable_stack *st)
    - 	reftable_addition_destroy(add);
    - 	return err;
    + ## t/helper/test-reftable.c ##
    +@@ t/helper/test-reftable.c: static void print_help(void)
    + 	       "\n");
      }
    --
    --int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id)
    --{
    --	struct reftable_stack *stack = NULL;
    --	struct reftable_write_options opts = { .hash_id = hash_id };
    --	struct reftable_merged_table *merged = NULL;
    --	struct reftable_table table = { NULL };
    --
    --	int err = reftable_new_stack(&stack, stackdir, &opts);
    --	if (err < 0)
    --		goto done;
    --
    --	merged = reftable_stack_merged_table(stack);
    --	reftable_table_from_merged_table(&table, merged);
    --	err = reftable_table_print(&table);
    --done:
    --	if (stack)
    --		reftable_stack_destroy(stack);
    --	return err;
    --}
    -
    - ## reftable/stack_test.c ##
    -@@ reftable/stack_test.c: static void test_reftable_stack_add_one(void)
    - 	EXPECT(0 == strcmp("master", dest.value.symref));
    - 	EXPECT(st->readers_len > 0);
      
    --	printf("testing print functionality:\n");
    --	err = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);
    --	EXPECT_ERR(err);
    --
    --	err = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);
    --	EXPECT(err == REFTABLE_FORMAT_ERROR);
    --
    - #ifndef GIT_WINDOWS_NATIVE
    - 	strbuf_addstr(&scratch, dir);
    - 	strbuf_addstr(&scratch, "/tables.list");
    ++static char hexdigit(int c)
    ++{
    ++	if (c <= 9)
    ++		return '0' + c;
    ++	return 'a' + (c - 10);
    ++}
    ++
    ++static void hex_format(char *dest, const unsigned char *src, int hash_size)
    ++{
    ++	assert(hash_size > 0);
    ++	if (src) {
    ++		int i = 0;
    ++		for (i = 0; i < hash_size; i++) {
    ++			dest[2 * i] = hexdigit(src[i] >> 4);
    ++			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
    ++		}
    ++		dest[2 * hash_size] = 0;
    ++	}
    ++}
    ++
    + static int dump_table(struct reftable_table *tab)
    + {
    + 	struct reftable_iterator it = { NULL };
    + 	struct reftable_ref_record ref = { NULL };
    + 	struct reftable_log_record log = { NULL };
    + 	uint32_t hash_id = reftable_table_hash_id(tab);
    ++	int hash_len = hash_size(hash_id);
    + 	int err;
    + 
    + 	reftable_table_init_ref_iter(tab, &it);
    +@@ t/helper/test-reftable.c: static int dump_table(struct reftable_table *tab)
    + 		return err;
    + 
    + 	while (1) {
    ++		char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
    ++
    + 		err = reftable_iterator_next_ref(&it, &ref);
    +-		if (err > 0) {
    ++		if (err > 0)
    + 			break;
    +-		}
    +-		if (err < 0) {
    ++		if (err < 0)
    + 			return err;
    ++
    ++		printf("ref{%s(%" PRIu64 ") ", ref.refname, ref.update_index);
    ++		switch (ref.value_type) {
    ++		case REFTABLE_REF_SYMREF:
    ++			printf("=> %s", ref.value.symref);
    ++			break;
    ++		case REFTABLE_REF_VAL2:
    ++			hex_format(hex, ref.value.val2.value, hash_len);
    ++			printf("val 2 %s", hex);
    ++			hex_format(hex, ref.value.val2.target_value,
    ++				   hash_len);
    ++			printf("(T %s)", hex);
    ++			break;
    ++		case REFTABLE_REF_VAL1:
    ++			hex_format(hex, ref.value.val1, hash_len);
    ++			printf("val 1 %s", hex);
    ++			break;
    ++		case REFTABLE_REF_DELETION:
    ++			printf("delete");
    ++			break;
    + 		}
    +-		reftable_ref_record_print(&ref, hash_id);
    ++		printf("}\n");
    + 	}
    + 	reftable_iterator_destroy(&it);
    + 	reftable_ref_record_release(&ref);
    +@@ t/helper/test-reftable.c: static int dump_table(struct reftable_table *tab)
    + 		return err;
    + 
    + 	while (1) {
    ++		char hex[GIT_MAX_HEXSZ + 1] = { 0 };
    ++
    + 		err = reftable_iterator_next_log(&it, &log);
    +-		if (err > 0) {
    ++		if (err > 0)
    + 			break;
    +-		}
    +-		if (err < 0) {
    ++		if (err < 0)
    + 			return err;
    ++
    ++		switch (log.value_type) {
    ++		case REFTABLE_LOG_DELETION:
    ++			printf("log{%s(%" PRIu64 ") delete\n", log.refname,
    ++			       log.update_index);
    ++			break;
    ++		case REFTABLE_LOG_UPDATE:
    ++			printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
    ++			       log.refname, log.update_index,
    ++			       log.value.update.name ? log.value.update.name : "",
    ++			       log.value.update.email ? log.value.update.email : "",
    ++			       log.value.update.time,
    ++			       log.value.update.tz_offset);
    ++			hex_format(hex, log.value.update.old_hash, hash_len);
    ++			printf("%s => ", hex);
    ++			hex_format(hex, log.value.update.new_hash, hash_len);
    ++			printf("%s\n\n%s\n}\n", hex,
    ++			       log.value.update.message ? log.value.update.message : "");
    ++			break;
    + 		}
    +-		reftable_log_record_print(&log, hash_id);
    + 	}
    + 	reftable_iterator_destroy(&it);
    + 	reftable_log_record_release(&log);
 -:  ---------- > 13:  5498395872 t/helper: use `hash_to_hex_algop()` to print hashes
 -:  ---------- > 14:  5390be75c3 t/helper: refactor to not use `struct reftable_table`
10:  723495adf6 = 15:  5aeab8ee07 reftable/generic: drop interface
-- 
2.46.0.46.g406f326d27.dirty