Skip to contentSkip to conversation

reftable library

PATCH Superseded Han-Wen Nienhuys via GitGitGadget 18 participants 251 messages Sep 16, 2020 – Jul 22, 2021 Page 1 of 3

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover reftable library

This splits the giant commit from
https://github.com/gitgitgadget/git/pull/539 into a series of smaller commits, which build and have unittests.

The final commit should also be split up, but I want to wait until we have consensus that the bottom commits look good.

Han-Wen Nienhuys (12):
reftable: add LICENSE
reftable: define the public API
reftable: add a barebones unittest framework
reftable: utility functions
reftable: (de)serialization for the polymorphic record type.
reftable: reading/writing blocks
reftable: a generic binary tree implementation
reftable: write reftable files
reftable: read reftable files
reftable: file level tests
reftable: rest of library
reftable: "test-tool dump-reftable" command.

Johannes Schindelin (1):
vcxproj: adjust for the reftable changes

 Makefile                                   |   46 +-
 config.mak.uname                           |    2 +-
 contrib/buildsystems/Generators/Vcxproj.pm |   11 +-
 reftable/.gitattributes                    |    1 +
 reftable/LICENSE                           |   31 +
 reftable/VERSION                           |    1 +
 reftable/basics.c                          |  131 +++
 reftable/basics.h                          |   48 +
 reftable/block.c                           |  443 +++++++
 reftable/block.h                           |  129 +++
 reftable/block_test.c                      |  158 +++
 reftable/blocksource.c                     |  148 +++
 reftable/blocksource.h                     |   22 +
 reftable/compat.c                          |  110 ++
 reftable/compat.h                          |   48 +
 reftable/constants.h                       |   21 +
 reftable/dump.c                            |  212 ++++
 reftable/iter.c                            |  242 ++++
 reftable/iter.h                            |   72 ++
 reftable/merged.c                          |  358 ++++++
 reftable/merged.h                          |   39 +
 reftable/merged_test.c                     |  331 ++++++
 reftable/pq.c                              |  115 ++
 reftable/pq.h                              |   34 +
 reftable/publicbasics.c                    |  100 ++
 reftable/reader.c                          |  733 ++++++++++++
 reftable/reader.h                          |   78 ++
 reftable/record.c                          | 1114 ++++++++++++++++++
 reftable/record.h                          |  143 +++
 reftable/record_test.c                     |  410 +++++++
 reftable/refname.c                         |  209 ++++
 reftable/refname.h                         |   38 +
 reftable/refname_test.c                    |  100 ++
 reftable/reftable-tests.h                  |   22 +
 reftable/reftable.c                        |  104 ++
 reftable/reftable.h                        |  585 ++++++++++
 reftable/reftable_test.c                   |  585 ++++++++++
 reftable/stack.c                           | 1224 ++++++++++++++++++++
 reftable/stack.h                           |   50 +
 reftable/stack_test.c                      |  787 +++++++++++++
 reftable/strbuf.c                          |  142 +++
 reftable/strbuf.h                          |   80 ++
 reftable/strbuf_test.c                     |   37 +
 reftable/system.h                          |   51 +
 reftable/test_framework.c                  |   68 ++
 reftable/test_framework.h                  |   60 +
 reftable/tree.c                            |   63 +
 reftable/tree.h                            |   34 +
 reftable/tree_test.c                       |   62 +
 reftable/update.sh                         |   22 +
 reftable/writer.c                          |  664 +++++++++++
 reftable/writer.h                          |   60 +
 reftable/zlib-compat.c                     |   92 ++
 t/helper/test-reftable.c                   |   20 +
 t/helper/test-tool.c                       |    2 +
 t/helper/test-tool.h                       |    2 +
 56 files changed, 10489 insertions(+), 5 deletions(-)
 create mode 100644 reftable/.gitattributes
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/blocksource.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/compat.c
 create mode 100644 reftable/compat.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/publicbasics.c
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100644 reftable/strbuf.c
 create mode 100644 reftable/strbuf.h
 create mode 100644 reftable/strbuf_test.c
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
 create mode 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100644 t/helper/test-reftable.c

base-commit: 54e85e7af1ac9e9a92888060d6811ae767fea1bc
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-847%2Fhanwen%2Flibreftable-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-847/hanwen/libreftable-v1
Pull-Request: https://github.com/git/git/pull/847

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/13 reftable: add LICENSE

From: Han-Wen Nienhuys <[email protected]>

TODO: relicense?

---
 reftable/LICENSE | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 reftable/LICENSE
reftable/LICENSE new file +31 -0
new file mode 100644
index 0000000000..402e0f9356
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/13 reftable: define the public API

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 reftable/reftable.h | 585 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 585 insertions(+)
 create mode 100644 reftable/reftable.h
reftable/reftable.h new file +585 -0
new file mode 100644
index 0000000000..5f8ebf5540
@@ -0,0 +1,585 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+void reftable_set_alloc(void *(*malloc)(size_t),
+			void *(*realloc)(void *, size_t), void (*free)(void *));
+
+/****************************************************************
+ Basic data types
+
+ Reftables store the state of each ref in struct reftable_ref_record, and they
+ store a sequence of reflog updates in struct reftable_log_record.
+ ****************************************************************/
+
+/* reftable_ref_record holds a ref database entry target_value */
+struct reftable_ref_record {
+	char *refname; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	uint8_t *value; /* SHA1, or NULL. malloced. */
+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
+
+/* prints a reftable_ref_record onto stdout */
+void reftable_ref_record_print(struct reftable_ref_record *ref,
+			       uint32_t hash_id);
+
+/* frees and nulls all pointer values. */
+void reftable_ref_record_clear(struct reftable_ref_record *ref);
+
+/* returns whether two reftable_ref_records are the same */
+int reftable_ref_record_equal(struct reftable_ref_record *a,
+			      struct reftable_ref_record *b, int hash_size);
+
+/* reftable_log_record holds a reflog entry */
+struct reftable_log_record {
+	char *refname;
+	uint64_t update_index; /* logical timestamp of a transactional update.
+				*/
+	uint8_t *new_hash;
+	uint8_t *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+int reftable_log_record_is_deletion(const struct reftable_log_record *log);
+
+/* frees and nulls all pointer values. */
+void reftable_log_record_clear(struct reftable_log_record *log);
+
+/* returns whether two records are equal. */
+int reftable_log_record_equal(struct reftable_log_record *a,
+			      struct reftable_log_record *b, int hash_size);
+
+/* dumps a reftable_log_record on stdout, for debugging/testing. */
+void reftable_log_record_print(struct reftable_log_record *log,
+			       uint32_t hash_id);
+
+/****************************************************************
+ Error handling
+
+ Error are signaled with negative integer return values. 0 means success.
+ ****************************************************************/
+
+/* different types of errors */
+enum reftable_error {
+	/* Unexpected file system behavior */
+	REFTABLE_IO_ERROR = -2,
+
+	/* Format inconsistency on reading data
+	 */
+	REFTABLE_FORMAT_ERROR = -3,
+
+	/* File does not exist. Returned from block_source_from_file(),  because
+	   it needs special handling in stack.
+	*/
+	REFTABLE_NOT_EXIST_ERROR = -4,
+
+	/* Trying to write out-of-date data. */
+	REFTABLE_LOCK_ERROR = -5,
+
+	/* Misuse of the API:
+	   - on writing a record with NULL refname.
+	   - on writing a reftable_ref_record outside the table limits
+	   - on writing a ref or log record before the stack's next_update_index
+	   - on writing a log record with multiline message with
+	   exact_log_message unset
+	   - on reading a reftable_ref_record from log iterator, or vice versa.
+	*/
+	REFTABLE_API_ERROR = -6,
+
+	/* Decompression error */
+	REFTABLE_ZLIB_ERROR = -7,
+
+	/* Wrote a table without blocks. */
+	REFTABLE_EMPTY_TABLE_ERROR = -8,
+
+	/* Dir/file conflict. */
+	REFTABLE_NAME_CONFLICT = -9,
+
+	/* Illegal ref name. */
+	REFTABLE_REFNAME_ERROR = -10,
+};
+
+/* convert the numeric error code to a string. The string should not be
+ * deallocated. */
+const char *reftable_error_str(int err);
+
+/*
+ * Convert the numeric error code to an equivalent errno code.
+ */
+int reftable_error_to_errno(int err);
+
+/****************************************************************
+ Writing
+
+ Writing single reftables
+ ****************************************************************/
+
+/* reftable_write_options sets options for writing a single reftable. */
+struct reftable_write_options {
+	/* boolean: do not pad out blocks to block size. */
+	int unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* boolean: do not generate a SHA1 => ref index. */
+	int skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+
+	/* 4-byte identifier ("sha1", "s256") of the hash.
+	 * Defaults to SHA1 if unset
+	 */
+	uint32_t hash_id;
+
+	/* boolean: do not check ref names for validity or dir/file conflicts.
+	 */
+	int skip_name_check;
+
+	/* boolean: copy log messages exactly. If unset, check that the message
+	 *   is a single line, and add '\n' if missing.
+	 */
+	int exact_log_message;
+};
+
+/* reftable_block_stats holds statistics for a single block type */
+struct reftable_block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct reftable_stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct reftable_block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct reftable_block_stats obj_stats;
+	/* stats for index blocks */
+	struct reftable_block_stats idx_stats;
+	/* stats for log blocks */
+	struct reftable_block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* reftable_new_writer creates a new writer */
+struct reftable_writer *
+reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
+		    void *writer_arg, struct reftable_write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int reftable_fd_write(void *fdp, const void *data, size_t size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
+ */
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+				uint64_t max);
+
+/* adds a reftable_ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned.
+
+   It is an error to write a ref record after a log record.
+ */
+int reftable_writer_add_ref(struct reftable_writer *w,
+			    struct reftable_ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int reftable_writer_add_refs(struct reftable_writer *w,
+			     struct reftable_ref_record *refs, int n);
+
+/* adds a reftable_log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int reftable_writer_add_log(struct reftable_writer *w,
+			    struct reftable_log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int reftable_writer_add_logs(struct reftable_writer *w,
+			     struct reftable_log_record *logs, int n);
+
+/* reftable_writer_close finalizes the reftable. The writer is retained so
+ * statistics can be inspected. */
+int reftable_writer_close(struct reftable_writer *w);
+
+/* writer_stats returns the statistics on the reftable being written.
+
+   This struct becomes invalid when the writer is freed.
+ */
+const struct reftable_stats *writer_stats(struct reftable_writer *w);
+
+/* reftable_writer_free deallocates memory for the writer */
+void reftable_writer_free(struct reftable_writer *w);
+
+/****************************************************************
+ * ITERATING
+ ****************************************************************/
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct reftable_iterator {
+	struct reftable_iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_ref(struct reftable_iterator *it,
+			       struct reftable_ref_record *ref);
+
+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_log(struct reftable_iterator *it,
+			       struct reftable_log_record *log);
+
+/* releases resources associated with an iterator. */
+void reftable_iterator_destroy(struct reftable_iterator *it);
+
+/****************************************************************
+ Reading single tables
+
+ The follow routines are for reading single files. For an application-level
+ interface, skip ahead to struct reftable_merged_table and struct
+ reftable_stack.
+ ****************************************************************/
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct reftable_block_source {
+	struct reftable_block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct reftable_block {
+	uint8_t *data;
+	int len;
+	struct reftable_block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct reftable_block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct reftable_block *dest,
+			  uint64_t off, uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct reftable_block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int reftable_block_source_from_file(struct reftable_block_source *block_src,
+				    const char *name);
+
+/* The reader struct is a handle to an open reftable file. */
+struct reftable_reader;
+
+/* reftable_new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp. The name is used for creating a stack. Typically, it is the
+ * basename of the file. The block source `src` is owned by the reader, and is
+ * closed on calling reftable_reader_destroy().
+ */
+int reftable_new_reader(struct reftable_reader **pp,
+			struct reftable_block_source *src, const char *name);
+
+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted
+   in the table.  To seek to the start of the table, use name = "".
+
+   example:
+
+   struct reftable_reader *r = NULL;
+   int err = reftable_new_reader(&r, &src, "filename");
+   if (err < 0) { ... }
+   struct reftable_iterator it  = {0};
+   err = reftable_reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct reftable_ref_record ref  = {0};
+   while (1) {
+     err = reftable_iterator_next_ref(&it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   reftable_iterator_destroy(&it);
+   reftable_ref_record_clear(&ref);
+ */
+int reftable_reader_seek_ref(struct reftable_reader *r,
+			     struct reftable_iterator *it, const char *name);
+
+/* returns the hash ID used in this table. */
+uint32_t reftable_reader_hash_id(struct reftable_reader *r);
+
+/* seek to logs for the given name, older than update_index. To seek to the
+   start of the table, use name = "".
+ */
+int reftable_reader_seek_log_at(struct reftable_reader *r,
+				struct reftable_iterator *it, const char *name,
+				uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reftable_reader_seek_log(struct reftable_reader *r,
+			     struct reftable_iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reftable_reader_free(struct reftable_reader *);
+
+/* return an iterator for the refs pointing to `oid`. */
+int reftable_reader_refs_for(struct reftable_reader *r,
+			     struct reftable_iterator *it, uint8_t *oid);
+
+/* return the max_update_index for a table */
+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);
+
+/****************************************************************
+ Merged tables
+
+ A ref database kept in a sequence of table files. The merged_table presents a
+ unified view to reading (seeking, iterating) a sequence of immutable tables.
+ ****************************************************************/
+
+/* A merged table is implements seeking/iterating over a stack of tables. */
+struct reftable_merged_table;
+
+/* A generic reftable; see below. */
+struct reftable_table;
+
+/* reftable_new_merged_table creates a new merged table. It takes ownership of
+   the stack array.
+*/
+int reftable_new_merged_table(struct reftable_merged_table **dest,
+			      struct reftable_table *stack, int n,
+			      uint32_t hash_id);
+
+/* returns an iterator positioned just before 'name' */
+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
+				   struct reftable_iterator *it,
+				   const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
+				      struct reftable_iterator *it,
+				      const char *name, uint64_t update_index);
+
+/* like reftable_merged_table_seek_log_at but look for the newest entry. */
+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
+				   struct reftable_iterator *it,
+				   const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
+
+/* releases memory for the merged_table */
+void reftable_merged_table_free(struct reftable_merged_table *m);
+
+/* return the hash ID of the merged table. */
+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);
+
+/****************************************************************
+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.
+ ****************************************************************/
+
+struct reftable_table {
+	struct reftable_table_vtable *ops;
+	void *table_arg;
+};
+
+int reftable_table_seek_ref(struct reftable_table *tab,
+			    struct reftable_iterator *it, const char *name);
+
+void reftable_table_from_reader(struct reftable_table *tab,
+				struct reftable_reader *reader);
+
+/* returns the hash ID from a generic reftable_table */
+uint32_t reftable_table_hash_id(struct reftable_table *tab);
+
+/* create a generic table from reftable_merged_table */
+void reftable_table_from_merged_table(struct reftable_table *tab,
+				      struct reftable_merged_table *table);
+
+/* returns the max update_index covered by this table. */
+uint64_t reftable_table_max_update_index(struct reftable_table *tab);
+
+/* returns the min update_index covered by this table. */
+uint64_t reftable_table_min_update_index(struct reftable_table *tab);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_table_read_ref(struct reftable_table *tab, const char *name,
+			    struct reftable_ref_record *ref);
+
+/****************************************************************
+ Mutable ref database
+
+ The stack presents an interface to a mutable sequence of reftables.
+ ****************************************************************/
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct reftable_stack;
+
+/* open a new reftable stack. The tables along with the table list will be
+   stored in 'dir'. Typically, this should be .git/reftables.
+*/
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
+		       struct reftable_write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
+
+/* holds a transaction to add tables at the top of a stack. */
+struct reftable_addition;
+
+/*
+  returns a new transaction to add reftables to the given stack. As a side
+  effect, the ref database is locked.
+*/
+int reftable_stack_new_addition(struct reftable_addition **dest,
+				struct reftable_stack *st);
+
+/* Adds a reftable to transaction. */
+int reftable_addition_add(struct reftable_addition *add,
+			  int (*write_table)(struct reftable_writer *wr,
+					     void *arg),
+			  void *arg);
+
+/* Commits the transaction, releasing the lock. */
+int reftable_addition_commit(struct reftable_addition *add);
+
+/* Release all non-committed data from the transaction, and deallocate the
+   transaction. Releases the lock if held. */
+void reftable_addition_destroy(struct reftable_addition *add);
+
+/* add a new table to the stack. The write_table function must call
+   reftable_writer_set_limits, add refs and return an error value. */
+int reftable_stack_add(struct reftable_stack *st,
+		       int (*write_table)(struct reftable_writer *wr,
+					  void *write_arg),
+		       void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct reftable_merged_table *
+reftable_stack_merged_table(struct reftable_stack *st);
+
+/* frees all resources associated with the stack. */
+void reftable_stack_destroy(struct reftable_stack *st);
+
+/* Reloads the stack if necessary. This is very cheap to run if the stack was up
+ * to date */
+int reftable_stack_reload(struct reftable_stack *st);
+
+/* Policy for expiring reflog entries. */
+struct reftable_log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int reftable_stack_compact_all(struct reftable_stack *st,
+			       struct reftable_log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int reftable_stack_auto_compact(struct reftable_stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
+			    struct reftable_ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
+			    struct reftable_log_record *log);
+
+/* statistics on past compactions. */
+struct reftable_compaction_stats {
+	uint64_t bytes; /* total number of bytes written */
+	uint64_t entries_written; /* total number of entries written, including
+				     failures. */
+	int attempts; /* how often we tried to compact */
+	int failures; /* failures happen on concurrent updates */
+};
+
+/* return statistics for compaction up till now. */
+struct reftable_compaction_stats *
+reftable_stack_compaction_stats(struct reftable_stack *st);
+
+#endif
Signature · 1 line
gitgitgadget
#3 lore raw

PATCH 3/13 vcxproj: adjust for the reftable changes

In reply to Han-Wen Nienhuys via GitGitGadget

From: Johannes Schindelin <[email protected]>

This allows Git to be compiled via Visual Studio again after integrating the hn/reftable branch.

---
 config.mak.uname                           |  2 +-
 contrib/buildsystems/Generators/Vcxproj.pm | 11 ++++++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)
config.mak.uname +1 -1
index c7eba69e54..ae4e25a1a4 100644
@@ -709,7 +709,7 @@ vcxproj:
 	# Make .vcxproj files and add them
 	unset QUIET_GEN QUIET_BUILT_IN; \
 	perl contrib/buildsystems/generate -g Vcxproj
-	git add -f git.sln {*,*/lib,t/helper/*}/*.vcxproj
+	git add -f git.sln {*,*/lib,*/libreftable,t/helper/*}/*.vcxproj
 
 	# Generate the LinkOrCopyBuiltins.targets and LinkOrCopyRemoteHttp.targets file
 	(echo '<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">' && \
contrib/buildsystems/Generators/Vcxproj.pm +10 -1
index d2584450ba..1a25789d28 100644
@@ -77,7 +77,7 @@ sub createProject {
     my $libs_release = "\n    ";
     my $libs_debug = "\n    ";
     if (!$static_library) {
-      $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
+      $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
       $libs_debug = $libs_release;
       $libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
       $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g;
@@ -232,6 +232,7 @@ sub createProject {
 EOM
     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
       my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
+      my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"};
       my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};
 
       print F << "EOM";
@@ -241,6 +242,14 @@ sub createProject {
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
 EOM
+      if (!($name =~ /xdiff|libreftable/)) {
+        print F << "EOM";
+    <ProjectReference Include="$cdup\\reftable\\libreftable\\libreftable.vcxproj">
+      <Project>$uuid_libreftable</Project>
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+    </ProjectReference>
+EOM
+      }
       if (!($name =~ 'xdiff')) {
         print F << "EOM";
     <ProjectReference Include="$cdup\\xdiff\\lib\\xdiff_lib.vcxproj">
Signature · 1 line
gitgitgadget
#4 lore raw

PATCH 4/13 reftable: add a barebones unittest framework

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 reftable/test_framework.c | 68 +++++++++++++++++++++++++++++++++++++++
 reftable/test_framework.h | 60 ++++++++++++++++++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
reftable/test_framework.c new file +68 -0
new file mode 100644
index 0000000000..f304a2773a
@@ -0,0 +1,68 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test_framework.h"
+
+#include "system.h"
+#include "basics.h"
+
+struct test_case **test_cases;
+int test_case_len;
+int test_case_cap;
+
+struct test_case *new_test_case(const char *name, void (*testfunc)(void))
+{
+	struct test_case *tc = reftable_malloc(sizeof(struct test_case));
+	tc->name = name;
+	tc->testfunc = testfunc;
+	return tc;
+}
+
+struct test_case *add_test_case(const char *name, void (*testfunc)(void))
+{
+	struct test_case *tc = new_test_case(name, testfunc);
+	if (test_case_len == test_case_cap) {
+		test_case_cap = 2 * test_case_cap + 1;
+		test_cases = reftable_realloc(
+			test_cases, sizeof(struct test_case) * test_case_cap);
+	}
+
+	test_cases[test_case_len++] = tc;
+	return tc;
+}
+
+int test_main(int argc, const char *argv[])
+{
+	const char *filter = NULL;
+	int i = 0;
+	if (argc > 1) {
+		filter = argv[1];
+	}
+
+	for (i = 0; i < test_case_len; i++) {
+		const char *name = test_cases[i]->name;
+		if (filter == NULL || strstr(name, filter) != NULL) {
+			printf("case %s\n", name);
+			test_cases[i]->testfunc();
+		} else {
+			printf("skip %s\n", name);
+		}
+
+		reftable_free(test_cases[i]);
+	}
+	reftable_free(test_cases);
+	test_cases = 0;
+	test_case_len = 0;
+	test_case_cap = 0;
+	return 0;
+}
+
+void set_test_hash(uint8_t *p, int i)
+{
+	memset(p, (uint8_t)i, hash_size(SHA1_ID));
+}
reftable/test_framework.h new file +60 -0

60 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#5 lore raw

PATCH 5/13 reftable: utility functions

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

---
 Makefile                  |  26 ++++++-
 reftable/basics.c         | 131 +++++++++++++++++++++++++++++++++
 reftable/basics.h         |  48 +++++++++++++
 reftable/blocksource.c    | 148 ++++++++++++++++++++++++++++++++++++++
 reftable/blocksource.h    |  22 ++++++
 reftable/compat.c         | 110 ++++++++++++++++++++++++++++
 reftable/compat.h         |  48 +++++++++++++
 reftable/publicbasics.c   | 100 ++++++++++++++++++++++++++
 reftable/reftable-tests.h |  22 ++++++
 reftable/strbuf.c         | 142 ++++++++++++++++++++++++++++++++++++
 reftable/strbuf.h         |  80 +++++++++++++++++++++
 reftable/strbuf_test.c    |  37 ++++++++++
 reftable/system.h         |  51 +++++++++++++
 t/helper/test-reftable.c  |   8 +++
 t/helper/test-tool.c      |   1 +
 t/helper/test-tool.h      |   1 +
 16 files changed, 972 insertions(+), 3 deletions(-)
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/blocksource.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/compat.c
 create mode 100644 reftable/compat.h
 create mode 100644 reftable/publicbasics.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/strbuf.c
 create mode 100644 reftable/strbuf.h
 create mode 100644 reftable/strbuf_test.c
 create mode 100644 reftable/system.h
 create mode 100644 t/helper/test-reftable.c
Makefile +23 -3
index 86e5411f39..66158525a9 100644
@@ -720,6 +720,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o
 TEST_BUILTINS_OBJS += test-read-graph.o
 TEST_BUILTINS_OBJS += test-read-midx.o
 TEST_BUILTINS_OBJS += test-ref-store.o
+TEST_BUILTINS_OBJS += test-reftable.o
 TEST_BUILTINS_OBJS += test-regex.o
 TEST_BUILTINS_OBJS += test-repository.o
 TEST_BUILTINS_OBJS += test-revision-walking.o
@@ -799,6 +800,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
+REFTABLE_LIB = reftable/libreftable.a
+REFTABLE_TEST_LIB = reftable/libreftable_test.a
 
 GENERATED_H += config-list.h
 GENERATED_H += command-list.h
@@ -1160,7 +1163,7 @@ THIRD_PARTY_SOURCES += compat/regex/%
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2341,10 +2344,21 @@ XDIFF_OBJS += xdiff/xpatience.o
 XDIFF_OBJS += xdiff/xprepare.o
 XDIFF_OBJS += xdiff/xutils.o
 
+REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/blocksource.o
+REFTABLE_OBJS += reftable/publicbasics.o
+REFTABLE_OBJS += reftable/compat.o
+REFTABLE_OBJS += reftable/strbuf.o
+
+REFTABLE_TEST_OBJS += reftable/strbuf_test.o
+REFTABLE_TEST_OBJS += reftable/test_framework.o
+
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 	$(XDIFF_OBJS) \
 	$(FUZZ_OBJS) \
+	$(REFTABLE_OBJS) \
+	$(REFTABLE_TEST_OBJS) \
 	common-main.o \
 	git.o
 ifndef NO_CURL
@@ -2474,6 +2488,12 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+$(REFTABLE_LIB): $(REFTABLE_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -2752,7 +2772,7 @@ perf: all
 
 t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: t/helper/test-tool$X
@@ -3080,7 +3100,7 @@ cocciclean:
 clean: profile-clean coverage-clean cocciclean
 	$(RM) *.res
 	$(RM) $(OBJECTS)
-	$(RM) $(LIB_FILE) $(XDIFF_LIB)
+	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
reftable/basics.c new file +131 -0

131 lines not on this page · raw message

reftable/basics.h new file +48 -0

48 lines not on this page · raw message

reftable/blocksource.c new file +148 -0

148 lines not on this page · raw message

reftable/blocksource.h new file +22 -0
new file mode 100644
index 0000000000..3faf83fa9d
@@ -0,0 +1,22 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "strbuf.h"
+
+struct reftable_block_source;
+
+/* Create an in-memory block source for reading reftables */
+void block_source_from_strbuf(struct reftable_block_source *bs,
+			      struct strbuf *buf);
+
+struct reftable_block_source malloc_block_source(void);
+
+#endif
reftable/compat.c new file +110 -0

110 lines not on this page · raw message

reftable/compat.h new file +48 -0

48 lines not on this page · raw message

reftable/publicbasics.c new file +100 -0

100 lines not on this page · raw message

reftable/reftable-tests.h new file +22 -0
new file mode 100644
index 0000000000..e38471888f
@@ -0,0 +1,22 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_TESTS_H
+#define REFTABLE_TESTS_H
+
+int block_test_main(int argc, const char **argv);
+int merged_test_main(int argc, const char **argv);
+int record_test_main(int argc, const char **argv);
+int refname_test_main(int argc, const char **argv);
+int reftable_test_main(int argc, const char **argv);
+int strbuf_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
reftable/strbuf.c new file +142 -0

142 lines not on this page · raw message

reftable/strbuf.h new file +80 -0

80 lines not on this page · raw message

reftable/strbuf_test.c new file +37 -0
new file mode 100644
index 0000000000..39f561c81a
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "strbuf.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "test_framework.h"
+#include "reftable-tests.h"
+
+static void test_strbuf(void)
+{
+	struct strbuf s = STRBUF_INIT;
+	struct strbuf t = STRBUF_INIT;
+
+	strbuf_addstr(&s, "abc");
+	assert(0 == strcmp("abc", s.buf));
+
+	strbuf_addstr(&t, "pqr");
+	strbuf_addbuf(&s, &t);
+	assert(0 == strcmp("abcpqr", s.buf));
+
+	strbuf_release(&s);
+	strbuf_release(&t);
+}
+
+int strbuf_test_main(int argc, const char *argv[])
+{
+	add_test_case("test_strbuf", &test_strbuf);
+	return test_main(argc, argv);
+}
reftable/system.h new file +51 -0

51 lines not on this page · raw message

t/helper/test-reftable.c new file +8 -0
new file mode 100644
index 0000000000..7d50aa6bcc
@@ -0,0 +1,8 @@
+#include "reftable/reftable-tests.h"
+#include "test-tool.h"
+
+int cmd__reftable(int argc, const char **argv)
+{
+	strbuf_test_main(argc, argv);
+	return 0;
+}
t/helper/test-tool.c +1 -0
index 590b2efca7..10366b7b76 100644
@@ -52,6 +52,7 @@ static struct test_cmd cmds[] = {
 	{ "read-graph", cmd__read_graph },
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
+	{ "reftable", cmd__reftable },
 	{ "regex", cmd__regex },
 	{ "repository", cmd__repository },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index ddc8e990e9..d52ba2f5e5 100644
@@ -41,6 +41,7 @@ int cmd__read_cache(int argc, const char **argv);
 int cmd__read_graph(int argc, const char **argv);
 int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
+int cmd__reftable(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
 int cmd__repository(int argc, const char **argv);
 int cmd__revision_walking(int argc, const char **argv);
Signature · 1 line
gitgitgadget
#6 lore raw

PATCH 6/13 reftable: (de)serialization for the polymorphic record type.

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile                 |    2 +
 reftable/constants.h     |   21 +
 reftable/record.c        | 1114 ++++++++++++++++++++++++++++++++++++++
 reftable/record.h        |  143 +++++
 reftable/record_test.c   |  410 ++++++++++++++
 t/helper/test-reftable.c |    1 +
 6 files changed, 1691 insertions(+)
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
Makefile +2 -0
index 66158525a9..0fb0048c34 100644
@@ -2348,8 +2348,10 @@ REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
+REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
 
+REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 
reftable/constants.h new file +21 -0
new file mode 100644
index 0000000000..5eee72c4c1
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
reftable/record.c new file +1114 -0

1,114 lines not on this page · raw message

reftable/record.h new file +143 -0

143 lines not on this page · raw message

reftable/record_test.c new file +410 -0

410 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 7d50aa6bcc..9341272089 100644
@@ -3,6 +3,7 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	record_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#7 lore raw

Re: [PATCH 06/13] reftable: (de)serialization for the polymorphic record type.

Quoted 4 lines · Han-Wen Nienhuys via GitGitGadget
reftable/record.c new file +4 -0
+static void reftable_log_record_key(const void *r, struct strbuf *dest)
+{
+	const struct reftable_log_record *rec =
+		(const struct reftable_log_record *)r;

reftable/record.c line 580

Quoted 10 lines
reftable/record.c new file +10 -0
+	int len = strlen(rec->refname);
+	uint8_t i64[8];
+	uint64_t ts = 0;
+	strbuf_reset(dest);
+	strbuf_add(dest, (uint8_t *)rec->refname, len + 1);
+
+	ts = (~ts) - rec->update_index;
+	put_be64(&i64[0], ts);
+	strbuf_add(dest, i64, sizeof(i64));
+}

We seem to be getting

reftable/record.c: In function 'reftable_log_record_key':
reftable/record.c:578:2: error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]

  put_be64(&i64[0], ts);
    ^
        CC reftable/refname.o

when this series is merged to 'seen'.

cf. e.g. https://travis-ci.org/github/git/git/jobs/728655368

Thanks.

#8 lore raw

Re: [PATCH 06/13] reftable: (de)serialization for the polymorphic record type.

Quoted 17 lines · Junio C Hamano
Quoted 4 lines
reftable/record.c new file +4 -0
+static void reftable_log_record_key(const void *r, struct strbuf *dest)
+{
+     const struct reftable_log_record *rec =
+             (const struct reftable_log_record *)r;

reftable/record.c line 580

Quoted 10 lines
reftable/record.c new file +10 -0
+     int len = strlen(rec->refname);
+     uint8_t i64[8];
+     uint64_t ts = 0;
+     strbuf_reset(dest);
+     strbuf_add(dest, (uint8_t *)rec->refname, len + 1);
+
+     ts = (~ts) - rec->update_index;
+     put_be64(&i64[0], ts);
+     strbuf_add(dest, i64, sizeof(i64));
+}

We seem to be getting

Quoted 7 lines

reftable/record.c: In function 'reftable_log_record_key':
reftable/record.c:578:2: error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]

  put_be64(&i64[0], ts);
    ^
        CC reftable/refname.o

when this series is merged to 'seen'.

Thanks for bringing this up. I did see this before, but I've been unable to reproduce this locally, and I forgot about it.

The problem is actually triggered by the Git-provided put_be64() (which appears unused in the Git source code). The definition

  #define put_be64(p, v) do { *(uint64_t *)(p) = htonll(v); } while (0)

is trying to reinterpret a char* as a uint64_t* , which is illegal in strict aliasing rules? I originally had

+void put_be64(uint8_t *out, uint64_t v)
+{
+ int i = sizeof(uint64_t);
+ while (i--) {
+ out[i] = (uint8_t)(v & 0xff);
+ v >>= 8;
+ }
+}

in my reftable library, which is portable. Is there a reason for the magic with htonll and friends?

Signature · 7 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--
Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#9 lore raw

Re: [PATCH 06/13] reftable: (de)serialization for the polymorphic record type.

Quoted 7 lines · Han-Wen Nienhuys

The problem is actually triggered by the Git-provided put_be64() (which appears unused in the Git source code). The definition

  #define put_be64(p, v) do { *(uint64_t *)(p) = htonll(v); } while (0)

is trying to reinterpret a char* as a uint64_t* , which is illegal in strict aliasing rules?

Ah, thanks for finding that. I think this resolves the mystery I had around aliasing warnings with put_be32() in f39ad38410 (fast-export: use local array to store anonymized oid, 2020-06-25). I got confused because when I looked at the implementation of put_be32(), I stupidly looked at the NO_ALIGNED_LOADS fallback code, which indeed does not alias. But the one we use on x86 does.

I thought this would be OK because C allows aliasing through "char *" pointers. But I think it may only go the other way. I.e., you can type-pun a uint64_t as a char, but not the other way around. But then why doesn't basically every use of put_be32() and put_be64() complain?

Quoted 11 lines

I originally had

(quoted diff) +8 -0
+void put_be64(uint8_t *out, uint64_t v)
+{
+       int i = sizeof(uint64_t);
+        while (i--) {
+               out[i] = (uint8_t)(v & 0xff);
+               v >>= 8;
+       }
+}
Quoted 2 lines

in my reftable library, which is portable. Is there a reason for the magic with htonll and friends?

Presumably it was thought to be faster. This comes originally from the block-sha1 code in 660231aa97 (block-sha1: support for architectures with memory alignment restrictions, 2009-08-12). I don't know how it compares in practice, and especially these days.

Our fallback routines are similar to an unrolled version of what you wrote above.

-Peff

#10 lore raw

Re: [PATCH 06/13] reftable: (de)serialization for the polymorphic record type.

Quoted 19 lines · Jeff King
Quoted 11 lines

I originally had

(quoted diff) +8 -0
+void put_be64(uint8_t *out, uint64_t v)
+{
+       int i = sizeof(uint64_t);
+        while (i--) {
+               out[i] = (uint8_t)(v & 0xff);
+               v >>= 8;
+       }
+}
Quoted 2 lines

in my reftable library, which is portable. Is there a reason for the magic with htonll and friends?

Presumably it was thought to be faster. This comes originally from the block-sha1 code in 660231aa97 (block-sha1: support for architectures with memory alignment restrictions, 2009-08-12). I don't know how it compares in practice, and especially these days.

Quoted 2 lines

Our fallback routines are similar to an unrolled version of what you wrote above.

We should be able to measure it pretty easily, since block-sha1 uses a lot of get_be32/put_be32. I generated a 4GB random file, built with BLK_SHA1=Yes and -O2, and timed:

t/helper/test-tool sha1 <foo.rand

Then I did the same, but building with -DNO_UNALIGNED_LOADS. The latter actually ran faster, by a small margin. Here are the hyperfine results:

[stock]
Time (mean ± σ): 6.638 s ± 0.081 s [User: 6.269 s, System: 0.368 s]
Range (min … max): 6.550 s … 6.841 s 10 runs

[-DNO_UNALIGNED_LOADS]
Time (mean ± σ): 6.418 s ± 0.015 s [User: 6.058 s, System: 0.360 s]
Range (min … max): 6.394 s … 6.447 s 10 runs

For casual use as in reftables I doubt the difference is even measurable. But this result implies that perhaps we ought to just be using the fallback version all the time.

-Peff

#11 lore raw

Re: [PATCH 06/13] reftable: (de)serialization for the polymorphic record type.

Quoted 7 lines · Jeff King

Then I did the same, but building with -DNO_UNALIGNED_LOADS. The latter actually ran faster, by a small margin. Here are the hyperfine results:

[stock]
Time (mean ± σ): 6.638 s ± 0.081 s [User: 6.269 s, System: 0.368 s]
Range (min … max): 6.550 s … 6.841 s 10 runs

Quoted 7 lines

[-DNO_UNALIGNED_LOADS]
Time (mean ± σ): 6.418 s ± 0.015 s [User: 6.058 s, System: 0.360 s]
Range (min … max): 6.394 s … 6.447 s 10 runs

For casual use as in reftables I doubt the difference is even measurable. But this result implies that perhaps we ought to just be using the fallback version all the time.

I like that one. One less configurable knob that makes us execute different codepaths is one less thing to be worried about.

#12 lore raw

PATCH 7/13 reftable: reading/writing blocks

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

Includes a code snippet copied from zlib

---
 Makefile                 |   3 +
 reftable/.gitattributes  |   1 +
 reftable/block.c         | 443 +++++++++++++++++++++++++++++++++++++++
 reftable/block.h         | 129 ++++++++++++
 reftable/block_test.c    | 158 ++++++++++++++
 reftable/zlib-compat.c   |  92 ++++++++
 t/helper/test-reftable.c |   1 +
 7 files changed, 827 insertions(+)
 create mode 100644 reftable/.gitattributes
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/zlib-compat.c
Makefile +3 -0
index 0fb0048c34..a897818a7e 100644
@@ -2345,12 +2345,15 @@ XDIFF_OBJS += xdiff/xprepare.o
 XDIFF_OBJS += xdiff/xutils.o
 
 REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
+REFTABLE_OBJS += reftable/zlib-compat.o
 
+REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
reftable/.gitattributes new file +1 -0
new file mode 100644
index 0000000000..f44451a379
@@ -0,0 +1 @@
+/zlib-compat.c	whitespace=-indent-with-non-tab,-trailing-space
reftable/block.c new file +443 -0

443 lines not on this page · raw message

reftable/block.h new file +129 -0

129 lines not on this page · raw message

reftable/block_test.c new file +158 -0

158 lines not on this page · raw message

reftable/zlib-compat.c new file +92 -0

92 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 9341272089..81a9bd5667 100644
@@ -3,6 +3,7 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	block_test_main(argc, argv);
 	record_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
 	return 0;
Signature · 1 line
gitgitgadget
#13 lore raw

PATCH 8/13 reftable: a generic binary tree implementation

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This is necessary for building a OID => ref map on write

---
 Makefile                 |  2 ++
 reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++
 reftable/tree.h          | 34 ++++++++++++++++++++++
 reftable/tree_test.c     | 62 +++++++++++++++++++++++++++++++++++++++
 t/helper/test-reftable.c |  1 +
 5 files changed, 162 insertions(+)
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
Makefile +2 -0
index a897818a7e..b96c235087 100644
@@ -2351,12 +2351,14 @@ REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
+REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
+REFTABLE_TEST_OBJS += reftable/tree_test.o
 
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
reftable/tree.c new file +63 -0

63 lines not on this page · raw message

reftable/tree.h new file +34 -0
new file mode 100644
index 0000000000..954512e9a3
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+/* tree_node is a generic binary search tree. */
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+/* looks for `key` in `rootp` using `compare` as comparison function. If insert
+   is set, insert the key if it's not found. Else, return NULL.
+*/
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+
+/* performs an infix walk of the tree. */
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+
+/*
+  deallocates the tree nodes recursively. Keys should be deallocated separately
+  by walking over the tree. */
+void tree_free(struct tree_node *t);
+
+#endif
reftable/tree_test.c new file +62 -0

62 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 81a9bd5667..9c4e0f42dc 100644
@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)
 	block_test_main(argc, argv);
 	record_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#14 lore raw

PATCH 9/13 reftable: write reftable files

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile          |   1 +
 reftable/writer.c | 664 ++++++++++++++++++++++++++++++++++++++++++++++
 reftable/writer.h |  60 +++++
 3 files changed, 725 insertions(+)
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
Makefile +1 -0
index b96c235087..a200633945 100644
@@ -2352,6 +2352,7 @@ REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
 REFTABLE_OBJS += reftable/tree.o
+REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
reftable/writer.c new file +664 -0

664 lines not on this page · raw message

reftable/writer.h new file +60 -0

60 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#15 lore raw

PATCH 10/13 reftable: read reftable files

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile            |   3 +
 reftable/iter.c     | 242 +++++++++++++++
 reftable/iter.h     |  72 +++++
 reftable/reader.c   | 733 ++++++++++++++++++++++++++++++++++++++++++++
 reftable/reader.h   |  78 +++++
 reftable/reftable.c | 104 +++++++
 6 files changed, 1232 insertions(+)
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/reftable.c
Makefile +3 -0
index a200633945..275fc4227c 100644
@@ -2349,7 +2349,10 @@ REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
+REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/reader.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/reftable.o
 REFTABLE_OBJS += reftable/strbuf.o
 REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
reftable/iter.c new file +242 -0

242 lines not on this page · raw message

reftable/iter.h new file +72 -0

72 lines not on this page · raw message

reftable/reader.c new file +733 -0

733 lines not on this page · raw message

reftable/reader.h new file +78 -0

78 lines not on this page · raw message

reftable/reftable.c new file +104 -0

104 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#16 lore raw

PATCH 11/13 reftable: file level tests

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile                 |   1 +
 reftable/reftable_test.c | 585 +++++++++++++++++++++++++++++++++++++++
 t/helper/test-reftable.c |   1 +
 3 files changed, 587 insertions(+)
 create mode 100644 reftable/reftable_test.c
Makefile +1 -0
index 275fc4227c..22dd31e7ff 100644
@@ -2360,6 +2360,7 @@ REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
+REFTABLE_TEST_OBJS += reftable/reftable_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/tree_test.o
reftable/reftable_test.c new file +585 -0

585 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 9c4e0f42dc..2273fdfe39 100644
@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)
 {
 	block_test_main(argc, argv);
 	record_test_main(argc, argv);
+	reftable_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
 	tree_test_main(argc, argv);
 	return 0;
Signature · 1 line
gitgitgadget
#17 lore raw

PATCH 12/13 reftable: rest of library

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This will be further split up once preceding commits have passed review.

---
 Makefile                 |    8 +-
 reftable/VERSION         |    1 +
 reftable/dump.c          |  212 +++++++
 reftable/merged.c        |  358 +++++++++++
 reftable/merged.h        |   39 ++
 reftable/merged_test.c   |  331 +++++++++++
 reftable/pq.c            |  115 ++++
 reftable/pq.h            |   34 ++
 reftable/refname.c       |  209 +++++++
 reftable/refname.h       |   38 ++
 reftable/refname_test.c  |  100 ++++
 reftable/stack.c         | 1224 ++++++++++++++++++++++++++++++++++++++
 reftable/stack.h         |   50 ++
 reftable/stack_test.c    |  787 ++++++++++++++++++++++++
 reftable/update.sh       |   22 +
 t/helper/test-reftable.c |    3 +
 16 files changed, 3530 insertions(+), 1 deletion(-)
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100755 reftable/update.sh
Makefile +7 -1
index 22dd31e7ff..6652259baf 100644
@@ -2347,12 +2347,16 @@ XDIFF_OBJS += xdiff/xutils.o
 REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
-REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/merged.o
+REFTABLE_OBJS += reftable/pq.o
+REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/reader.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/refname.o
 REFTABLE_OBJS += reftable/reftable.o
+REFTABLE_OBJS += reftable/stack.o
 REFTABLE_OBJS += reftable/strbuf.o
 REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
@@ -2360,7 +2364,9 @@ REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
+REFTABLE_TEST_OBJS += reftable/refname_test.o
 REFTABLE_TEST_OBJS += reftable/reftable_test.o
+REFTABLE_TEST_OBJS += reftable/stack_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/tree_test.o
reftable/VERSION new file +1 -0
new file mode 100644
index 0000000000..7553c395eb
@@ -0,0 +1 @@
+b8ec0f74c74cb6752eb2033ad8e755a9c19aad15 C: add missing header
reftable/dump.c new file +212 -0

212 lines not on this page · raw message

reftable/merged.c new file +358 -0

358 lines not on this page · raw message

reftable/merged.h new file +39 -0
new file mode 100644
index 0000000000..66a1d71830
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct reftable_merged_table {
+	struct reftable_table *stack;
+	size_t stack_len;
+	uint32_t hash_id;
+	int suppress_deletions;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct reftable_iterator *stack;
+	uint32_t hash_id;
+	size_t stack_len;
+	uint8_t typ;
+	int suppress_deletions;
+	struct merged_iter_pqueue pq;
+};
+
+void merged_table_clear(struct reftable_merged_table *mt);
+int merged_table_seek_record(struct reftable_merged_table *mt,
+			     struct reftable_iterator *it,
+			     struct reftable_record *rec);
+
+#endif
reftable/merged_test.c new file +331 -0

331 lines not on this page · raw message

reftable/pq.c new file +115 -0

115 lines not on this page · raw message

reftable/pq.h new file +34 -0
new file mode 100644
index 0000000000..918ac3990d
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	int index;
+	struct reftable_record rec;
+};
+
+int pq_less(struct pq_entry a, struct pq_entry b);
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	size_t len;
+	size_t cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
reftable/refname.c new file +209 -0

209 lines not on this page · raw message

reftable/refname.h new file +38 -0
new file mode 100644
index 0000000000..13bf1e6861
@@ -0,0 +1,38 @@
+/*
+  Copyright 2020 Google LLC
+
+  Use of this source code is governed by a BSD-style
+  license that can be found in the LICENSE file or at
+  https://developers.google.com/open-source/licenses/bsd
+*/
+#ifndef REFNAME_H
+#define REFNAME_H
+
+#include "reftable.h"
+
+struct modification {
+	struct reftable_table tab;
+
+	char **add;
+	size_t add_len;
+
+	char **del;
+	size_t del_len;
+};
+
+// -1 = error, 0 = found, 1 = not found
+int modification_has_ref(struct modification *mod, const char *name);
+
+// -1 = error, 0 = found, 1 = not found.
+int modification_has_ref_with_prefix(struct modification *mod,
+				     const char *prefix);
+
+// 0 = OK.
+int validate_refname(const char *name);
+
+int validate_ref_record_addition(struct reftable_table tab,
+				 struct reftable_ref_record *recs, size_t sz);
+
+int modification_validate(struct modification *mod);
+
+#endif
reftable/refname_test.c new file +100 -0

100 lines not on this page · raw message

reftable/stack.c new file +1224 -0

1,224 lines not on this page · raw message

reftable/stack.h new file +50 -0

50 lines not on this page · raw message

reftable/stack_test.c new file +787 -0

263 lines not on this page · raw message

reftable/update.sh new file +22 -0
new file mode 100755
index 0000000000..4dadd875f4

Lines of this file are not shown. View the raw message.

t/helper/test-reftable.c +3 -0
index 2273fdfe39..def8883439 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
gitgitgadget

Part of the diff is not shown here. View the raw message.

#18 lore raw

PATCH 13/13 reftable: "test-tool dump-reftable" command.

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This command dumps individual tables or a stack of of tables.

---
 Makefile                 | 2 ++
 t/helper/test-reftable.c | 5 +++++
 t/helper/test-tool.c     | 1 +
 t/helper/test-tool.h     | 1 +
 4 files changed, 9 insertions(+)
Makefile +2 -0
index 6652259baf..ee82ff2915 100644
@@ -2363,6 +2363,8 @@ REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
+REFTABLE_TEST_OBJS += reftable/dump.o
+REFTABLE_TEST_OBJS += reftable/merged_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/refname_test.o
 REFTABLE_TEST_OBJS += reftable/reftable_test.o
t/helper/test-reftable.c +5 -0
index def8883439..aff4fbccda 100644
@@ -13,3 +13,8 @@ int cmd__reftable(int argc, const char **argv)
 	tree_test_main(argc, argv);
 	return 0;
 }
+
+int cmd__dump_reftable(int argc, const char **argv)
+{
+	return reftable_dump_main(argc, (char *const *)argv);
+}
t/helper/test-tool.c +1 -0
index 10366b7b76..9e689f9d2b 100644
@@ -53,6 +53,7 @@ static struct test_cmd cmds[] = {
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
 	{ "reftable", cmd__reftable },
+	{ "dump-reftable", cmd__dump_reftable },
 	{ "regex", cmd__regex },
 	{ "repository", cmd__repository },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index d52ba2f5e5..bf833e01d4 100644
@@ -17,6 +17,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);
 int cmd__dump_fsmonitor(int argc, const char **argv);
 int cmd__dump_split_index(int argc, const char **argv);
 int cmd__dump_untracked_cache(int argc, const char **argv);
+int cmd__dump_reftable(int argc, const char **argv);
 int cmd__example_decorate(int argc, const char **argv);
 int cmd__genrandom(int argc, const char **argv);
 int cmd__genzeros(int argc, const char **argv);
Signature · 1 line
gitgitgadget
#19 lore raw

v2 cover reftable library

In reply to Han-Wen Nienhuys via GitGitGadget

This splits the giant commit from
https://github.com/gitgitgadget/git/pull/539 into a series of smaller commits, which build and have unittests.

The final commit should also be split up, but I want to wait until we have consensus that the bottom commits look good.

Han-Wen Nienhuys (12):
reftable: add LICENSE
reftable: define the public API
reftable: add a barebones unittest framework
reftable: utility functions
reftable: (de)serialization for the polymorphic record type.
reftable: reading/writing blocks
reftable: a generic binary tree implementation
reftable: write reftable files
reftable: read reftable files
reftable: file level tests
reftable: rest of library
reftable: "test-tool dump-reftable" command.

Johannes Schindelin (1):
vcxproj: adjust for the reftable changes

 Makefile                                   |   46 +-
 config.mak.uname                           |    2 +-
 contrib/buildsystems/Generators/Vcxproj.pm |   11 +-
 reftable/.gitattributes                    |    1 +
 reftable/LICENSE                           |   31 +
 reftable/VERSION                           |    1 +
 reftable/basics.c                          |  131 +++
 reftable/basics.h                          |   48 +
 reftable/block.c                           |  441 +++++++
 reftable/block.h                           |  129 ++
 reftable/block_test.c                      |  158 +++
 reftable/blocksource.c                     |  148 +++
 reftable/blocksource.h                     |   22 +
 reftable/compat.c                          |  110 ++
 reftable/compat.h                          |   48 +
 reftable/constants.h                       |   21 +
 reftable/dump.c                            |  212 ++++
 reftable/iter.c                            |  242 ++++
 reftable/iter.h                            |   72 ++
 reftable/merged.c                          |  358 ++++++
 reftable/merged.h                          |   36 +
 reftable/merged_test.c                     |  331 ++++++
 reftable/pq.c                              |  115 ++
 reftable/pq.h                              |   32 +
 reftable/publicbasics.c                    |  100 ++
 reftable/reader.c                          |  733 ++++++++++++
 reftable/reader.h                          |   78 ++
 reftable/record.c                          | 1116 ++++++++++++++++++
 reftable/record.h                          |  137 +++
 reftable/record_test.c                     |  410 +++++++
 reftable/refname.c                         |  209 ++++
 reftable/refname.h                         |   28 +
 reftable/refname_test.c                    |  100 ++
 reftable/reftable-tests.h                  |   22 +
 reftable/reftable.c                        |  104 ++
 reftable/reftable.h                        |  585 +++++++++
 reftable/reftable_test.c                   |  585 +++++++++
 reftable/stack.c                           | 1240 ++++++++++++++++++++
 reftable/stack.h                           |   40 +
 reftable/stack_test.c                      |  788 +++++++++++++
 reftable/strbuf.c                          |  142 +++
 reftable/strbuf.h                          |   80 ++
 reftable/strbuf_test.c                     |   37 +
 reftable/system.h                          |   51 +
 reftable/test_framework.c                  |   68 ++
 reftable/test_framework.h                  |   59 +
 reftable/tree.c                            |   63 +
 reftable/tree.h                            |   34 +
 reftable/tree_test.c                       |   62 +
 reftable/update.sh                         |   22 +
 reftable/writer.c                          |  673 +++++++++++
 reftable/writer.h                          |   51 +
 reftable/zlib-compat.c                     |   92 ++
 t/helper/test-reftable.c                   |   20 +
 t/helper/test-tool.c                       |    2 +
 t/helper/test-tool.h                       |    2 +
 56 files changed, 10474 insertions(+), 5 deletions(-)
 create mode 100644 reftable/.gitattributes
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/blocksource.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/compat.c
 create mode 100644 reftable/compat.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/publicbasics.c
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100644 reftable/strbuf.c
 create mode 100644 reftable/strbuf.h
 create mode 100644 reftable/strbuf_test.c
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
 create mode 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100644 t/helper/test-reftable.c

base-commit: 306ee63a703ad67c54ba1209dc11dd9ea500dc1f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-847%2Fhanwen%2Flibreftable-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-847/hanwen/libreftable-v2
Pull-Request: https://github.com/git/git/pull/847

Range-diff vs v1:

  1:  f697eac228 =  1:  6228103b4a reftable: add LICENSE
  2:  e2dbc84833 =  2:  5d1b946ab5 reftable: define the public API
  3:  0f6efc065c =  3:  01a669a731 vcxproj: adjust for the reftable changes
  4:  7bf59d491d !  4:  b94c5f5c61 reftable: add a barebones unittest framework
     @@ reftable/test_framework.c (new)
      +#include "system.h"
      +#include "basics.h"
      +
     -+struct test_case **test_cases;
     -+int test_case_len;
     -+int test_case_cap;
     ++static struct test_case **test_cases;
     ++static int test_case_len;
     ++static int test_case_cap;
      +
     -+struct test_case *new_test_case(const char *name, void (*testfunc)(void))
     ++static struct test_case *new_test_case(const char *name, void (*testfunc)(void))
      +{
      +	struct test_case *tc = reftable_malloc(sizeof(struct test_case));
      +	tc->name = name;
     @@ reftable/test_framework.c (new)
      +		reftable_free(test_cases[i]);
      +	}
      +	reftable_free(test_cases);
     -+	test_cases = 0;
     ++	test_cases = NULL;
      +	test_case_len = 0;
      +	test_case_cap = 0;
      +	return 0;
     @@ reftable/test_framework.h (new)
      +	void (*testfunc)(void);
      +};
      +
     -+struct test_case *new_test_case(const char *name, void (*testfunc)(void));
      +struct test_case *add_test_case(const char *name, void (*testfunc)(void));
      +int test_main(int argc, const char *argv[]);
      +
  5:  570a8c4bca !  5:  4190da597e reftable: utility functions
     @@ reftable/blocksource.c (new)
      +	return ((struct strbuf *)b)->len;
      +}
      +
     -+struct reftable_block_source_vtable strbuf_vtable = {
     ++static struct reftable_block_source_vtable strbuf_vtable = {
      +	.size = &strbuf_size,
      +	.read_block = &strbuf_read_block,
      +	.return_block = &strbuf_return_block,
     @@ reftable/blocksource.c (new)
      +	reftable_free(dest->data);
      +}
      +
     -+struct reftable_block_source_vtable malloc_vtable = {
     ++static struct reftable_block_source_vtable malloc_vtable = {
      +	.return_block = &malloc_return_block,
      +};
      +
     -+struct reftable_block_source malloc_block_source_instance = {
     ++static struct reftable_block_source malloc_block_source_instance = {
      +	.ops = &malloc_vtable,
      +};
      +
     @@ reftable/blocksource.c (new)
      +	return size;
      +}
      +
     -+struct reftable_block_source_vtable file_vtable = {
     ++static struct reftable_block_source_vtable file_vtable = {
      +	.size = &file_size,
      +	.read_block = &file_read_block,
      +	.return_block = &file_return_block,
     @@ reftable/publicbasics.c (new)
      +	}
      +}
      +
     -+void *(*reftable_malloc_ptr)(size_t sz) = &malloc;
     -+void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;
     -+void (*reftable_free_ptr)(void *) = &free;
     ++static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;
     ++static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;
     ++static void (*reftable_free_ptr)(void *) = &free;
      +
      +void *reftable_malloc(size_t sz)
      +{
  6:  791f69c000 !  6:  8eb944ea9b reftable: (de)serialization for the polymorphic record type.
     @@ reftable/record.c (new)
      +		(const struct reftable_ref_record *)p);
      +}
      +
     -+struct reftable_record_vtable reftable_ref_record_vtable = {
     ++static struct reftable_record_vtable reftable_ref_record_vtable = {
      +	.key = &reftable_ref_record_key,
      +	.type = BLOCK_TYPE_REF,
      +	.copy_from = &reftable_ref_record_copy_from,
     @@ reftable/record.c (new)
      +	return 0;
      +}
      +
     -+struct reftable_record_vtable reftable_obj_record_vtable = {
     ++static struct reftable_record_vtable reftable_obj_record_vtable = {
      +	.key = &reftable_obj_record_key,
      +	.type = BLOCK_TYPE_OBJ,
      +	.copy_from = &reftable_obj_record_copy_from,
     @@ reftable/record.c (new)
      +		(const struct reftable_log_record *)p);
      +}
      +
     -+struct reftable_record_vtable reftable_log_record_vtable = {
     ++static struct reftable_record_vtable reftable_log_record_vtable = {
      +	.key = &reftable_log_record_key,
      +	.type = BLOCK_TYPE_LOG,
      +	.copy_from = &reftable_log_record_copy_from,
     @@ reftable/record.c (new)
      +	return rec;
      +}
      +
     -+void *reftable_record_yield(struct reftable_record *rec)
     ++/* clear out the record, yielding the reftable_record data that was
     ++ * encapsulated. */
     ++static void *reftable_record_yield(struct reftable_record *rec)
      +{
      +	void *p = rec->data;
      +	rec->data = NULL;
     @@ reftable/record.c (new)
      +	return start.len - in.len;
      +}
      +
     -+struct reftable_record_vtable reftable_index_record_vtable = {
     ++static struct reftable_record_vtable reftable_index_record_vtable = {
      +	.key = &reftable_index_record_key,
      +	.type = BLOCK_TYPE_INDEX,
      +	.copy_from = &reftable_index_record_copy_from,
     @@ reftable/record.h (new)
      +/* creates a malloced record of the given type. Dispose with record_destroy */
      +struct reftable_record reftable_new_record(uint8_t typ);
      +
     -+extern struct reftable_record_vtable reftable_ref_record_vtable;
     -+
      +/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
      +   number of bytes written. */
      +int reftable_encode_key(int *is_restart, struct string_view dest,
     @@ reftable/record.h (new)
      +/* zeroes out the embedded record */
      +void reftable_record_clear(struct reftable_record *rec);
      +
     -+/* clear out the record, yielding the reftable_record data that was
     -+ * encapsulated. */
     -+void *reftable_record_yield(struct reftable_record *rec);
     -+
      +/* clear and deallocate embedded record, and zero `rec`. */
      +void reftable_record_destroy(struct reftable_record *rec);
      +
     @@ reftable/record_test.c (new)
      +	int i = 0;
      +
      +	for (i = 0; i <= 3; i++) {
     -+		struct reftable_ref_record in = { 0 };
     ++		struct reftable_ref_record in = { NULL };
      +		struct reftable_ref_record out = {
      +			.refname = xstrdup("old name"),
      +			.value = reftable_calloc(SHA1_SIZE),
      +			.target_value = reftable_calloc(SHA1_SIZE),
      +			.target = xstrdup("old value"),
      +		};
     -+		struct reftable_record rec_out = { 0 };
     ++		struct reftable_record rec_out = { NULL };
      +		struct strbuf key = STRBUF_INIT;
     -+		struct reftable_record rec = { 0 };
     ++		struct reftable_record rec = { NULL };
      +		uint8_t buffer[1024] = { 0 };
      +		struct string_view dest = {
      +			.buf = buffer,
     @@ reftable/record_test.c (new)
      +	set_test_hash(in[0].new_hash, 1);
      +	set_test_hash(in[0].old_hash, 2);
      +	for (int i = 0; i < ARRAY_SIZE(in); i++) {
     -+		struct reftable_record rec = { 0 };
     ++		struct reftable_record rec = { NULL };
      +		struct strbuf key = STRBUF_INIT;
      +		uint8_t buffer[1024] = { 0 };
      +		struct string_view dest = {
     @@ reftable/record_test.c (new)
      +			.email = xstrdup("old@email"),
      +			.message = xstrdup("old message"),
      +		};
     -+		struct reftable_record rec_out = { 0 };
     ++		struct reftable_record rec_out = { NULL };
      +		int n, m, valtype;
      +
      +		reftable_record_from_log(&rec, &in[i]);
     @@ reftable/record_test.c (new)
      +			.buf = buffer,
      +			.len = sizeof(buffer),
      +		};
     -+		struct reftable_record rec = { 0 };
     ++		struct reftable_record rec = { NULL };
      +		struct strbuf key = STRBUF_INIT;
     -+		struct reftable_obj_record out = { 0 };
     -+		struct reftable_record rec_out = { 0 };
     ++		struct reftable_obj_record out = { NULL };
     ++		struct reftable_record rec_out = { NULL };
      +		int n, m;
      +		uint8_t extra;
      +
     @@ reftable/record_test.c (new)
      +		.len = sizeof(buffer),
      +	};
      +	struct strbuf key = STRBUF_INIT;
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	struct reftable_index_record out = { .last_key = STRBUF_INIT };
      +	struct reftable_record out_rec = { NULL };
      +	int n, m;
  7:  1ba8e3eb30 !  7:  757dd30fe2 reftable: reading/writing blocks
     @@ reftable/block.c (new)
      +	abort();
      +}
      +
     -+int block_writer_register_restart(struct block_writer *w, int n, int restart,
     -+				  struct strbuf *key);
     ++static int block_writer_register_restart(struct block_writer *w, int n,
     ++					 int is_restart, struct strbuf *key)
     ++{
     ++	int rlen = w->restart_len;
     ++	if (rlen >= MAX_RESTARTS) {
     ++		is_restart = 0;
     ++	}
     ++
     ++	if (is_restart) {
     ++		rlen++;
     ++	}
     ++	if (2 + 3 * rlen + n > w->block_size - w->next)
     ++		return -1;
     ++	if (is_restart) {
     ++		if (w->restart_len == w->restart_cap) {
     ++			w->restart_cap = w->restart_cap * 2 + 1;
     ++			w->restarts = reftable_realloc(
     ++				w->restarts, sizeof(uint32_t) * w->restart_cap);
     ++		}
     ++
     ++		w->restarts[w->restart_len++] = w->next;
     ++	}
     ++
     ++	w->next += n;
     ++
     ++	strbuf_reset(&w->last_key);
     ++	strbuf_addbuf(&w->last_key, key);
     ++	w->entries++;
     ++	return 0;
     ++}
      +
      +void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,
      +		       uint32_t block_size, uint32_t header_off, int hash_size)
     @@ reftable/block.c (new)
      +	return -1;
      +}
      +
     -+int block_writer_register_restart(struct block_writer *w, int n, int is_restart,
     -+				  struct strbuf *key)
     -+{
     -+	int rlen = w->restart_len;
     -+	if (rlen >= MAX_RESTARTS) {
     -+		is_restart = 0;
     -+	}
     -+
     -+	if (is_restart) {
     -+		rlen++;
     -+	}
     -+	if (2 + 3 * rlen + n > w->block_size - w->next)
     -+		return -1;
     -+	if (is_restart) {
     -+		if (w->restart_len == w->restart_cap) {
     -+			w->restart_cap = w->restart_cap * 2 + 1;
     -+			w->restarts = reftable_realloc(
     -+				w->restarts, sizeof(uint32_t) * w->restart_cap);
     -+		}
     -+
     -+		w->restarts[w->restart_len++] = w->next;
     -+	}
     -+
     -+	w->next += n;
     -+
     -+	strbuf_reset(&w->last_key);
     -+	strbuf_addbuf(&w->last_key, key);
     -+	w->entries++;
     -+	return 0;
     -+}
      +
      +int block_writer_finish(struct block_writer *w)
      +{
     @@ reftable/block_test.c (new)
      +	char *names[30];
      +	const int N = ARRAY_SIZE(names);
      +	const int block_size = 1024;
     -+	struct reftable_block block = { 0 };
     ++	struct reftable_block block = { NULL };
      +	struct block_writer bw = {
      +		.last_key = STRBUF_INIT,
      +	};
     -+	struct reftable_ref_record ref = { 0 };
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
     ++	struct reftable_record rec = { NULL };
      +	int i = 0;
      +	int n;
      +	struct block_reader br = { 0 };
  8:  17fb8d050d !  8:  e30a7e0281 reftable: a generic binary tree implementation
     @@ reftable/tree_test.c (new)
      +{
      +	struct tree_node *root = NULL;
      +
     -+	void *values[11] = { 0 };
     -+	struct tree_node *nodes[11] = { 0 };
     ++	void *values[11] = { NULL };
     ++	struct tree_node *nodes[11] = { NULL };
      +	int i = 1;
     -+	struct curry c = { 0 };
     ++	struct curry c = { NULL };
      +	do {
      +		nodes[i] = tree_search(values + i, &root, &test_compare, 1);
      +		i = (i * 7) % 11;
  9:  4e40fc3b40 !  9:  68aee16e60 reftable: write reftable files
     @@ reftable/writer.c (new)
      +#include "reftable.h"
      +#include "tree.h"
      +
     ++/* finishes a block, and writes it to storage */
     ++static int writer_flush_block(struct reftable_writer *w);
     ++
     ++/* deallocates memory related to the index */
     ++static void writer_clear_index(struct reftable_writer *w);
     ++
     ++/* finishes writing a 'r' (refs) or 'g' (reflogs) section */
     ++static int writer_finish_public_section(struct reftable_writer *w);
     ++
      +static struct reftable_block_stats *
      +writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ)
      +{
     @@ reftable/writer.c (new)
      +int reftable_writer_add_ref(struct reftable_writer *w,
      +			    struct reftable_ref_record *ref)
      +{
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	struct reftable_ref_record copy = *ref;
      +	int err = 0;
      +
     @@ reftable/writer.c (new)
      +int reftable_writer_add_log(struct reftable_writer *w,
      +			    struct reftable_log_record *log)
      +{
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	char *input_log_message = log->message;
      +	struct strbuf cleaned_message = STRBUF_INIT;
      +	int err;
     @@ reftable/writer.c (new)
      +		w->index_len = 0;
      +		w->index_cap = 0;
      +		for (i = 0; i < idx_len; i++) {
     -+			struct reftable_record rec = { 0 };
     ++			struct reftable_record rec = { NULL };
      +			reftable_record_from_index(&rec, idx + i);
      +			if (block_writer_add(w->block_writer, &rec) == 0) {
      +				continue;
     @@ reftable/writer.c (new)
      +		.offsets = entry->offsets,
      +		.offset_len = entry->offset_len,
      +	};
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	if (arg->err < 0)
      +		goto done;
      +
     @@ reftable/writer.c (new)
      +static int writer_dump_object_index(struct reftable_writer *w)
      +{
      +	struct write_record_arg closure = { .w = w };
     -+	struct common_prefix_arg common = { 0 };
     ++	struct common_prefix_arg common = { NULL };
      +	if (w->obj_index_tree != NULL) {
      +		infix_walk(w->obj_index_tree, &update_common, &common);
      +	}
     @@ reftable/writer.c (new)
      +	return writer_finish_section(w);
      +}
      +
     -+int writer_finish_public_section(struct reftable_writer *w)
     ++static int writer_finish_public_section(struct reftable_writer *w)
      +{
      +	uint8_t typ = 0;
      +	int err = 0;
     @@ reftable/writer.c (new)
      +	return err;
      +}
      +
     -+void writer_clear_index(struct reftable_writer *w)
     ++static void writer_clear_index(struct reftable_writer *w)
      +{
      +	int i = 0;
      +	for (i = 0; i < w->index_len; i++) {
     @@ reftable/writer.c (new)
      +	w->index_cap = 0;
      +}
      +
     -+const int debug = 0;
     ++static const int debug = 0;
      +
      +static int writer_flush_nonempty_block(struct reftable_writer *w)
      +{
     @@ reftable/writer.c (new)
      +	return 0;
      +}
      +
     -+int writer_flush_block(struct reftable_writer *w)
     ++static int writer_flush_block(struct reftable_writer *w)
      +{
      +	if (w->block_writer == NULL)
      +		return 0;
     @@ reftable/writer.h (new)
      +	struct reftable_stats stats;
      +};
      +
     -+/* finishes a block, and writes it to storage */
     -+int writer_flush_block(struct reftable_writer *w);
     -+
     -+/* deallocates memory related to the index */
     -+void writer_clear_index(struct reftable_writer *w);
     -+
     -+/* finishes writing a 'r' (refs) or 'g' (reflogs) section */
     -+int writer_finish_public_section(struct reftable_writer *w);
     -+
      +#endif
 10:  25259b683b ! 10:  c196de7f06 reftable: read reftable files
     @@ reftable/iter.c (new)
      +{
      +}
      +
     -+struct reftable_iterator_vtable empty_vtable = {
     ++static struct reftable_iterator_vtable empty_vtable = {
      +	.next = &empty_iterator_next,
      +	.close = &empty_iterator_close,
      +};
     @@ reftable/iter.c (new)
      +	return err;
      +}
      +
     -+struct reftable_iterator_vtable filtering_ref_iterator_vtable = {
     ++static struct reftable_iterator_vtable filtering_ref_iterator_vtable = {
      +	.next = &filtering_ref_iterator_next,
      +	.close = &filtering_ref_iterator_close,
      +};
     @@ reftable/iter.c (new)
      +	return err;
      +}
      +
     -+struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {
     ++static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {
      +	.next = &indexed_table_ref_iter_next,
      +	.close = &indexed_table_ref_iter_close,
      +};
     @@ reftable/reader.c (new)
      +	block_iter_close(&ti->bi);
      +}
      +
     -+struct reftable_iterator_vtable table_iter_vtable = {
     ++static struct reftable_iterator_vtable table_iter_vtable = {
      +	.next = &table_iter_next_void,
      +	.close = &table_iter_close,
      +};
 11:  3448fcf828 ! 11:  bf6b929b86 reftable: file level tests
     @@ reftable/reftable_test.c (new)
      +{
      +	struct strbuf buf = STRBUF_INIT;
      +	struct reftable_block_source source = { NULL };
     -+	struct reftable_block out = { 0 };
     ++	struct reftable_block out = { NULL };
      +	int n;
      +	uint8_t in[] = "hello";
      +	strbuf_add(&buf, in, sizeof(in));
     @@ reftable/reftable_test.c (new)
      +	};
      +	struct reftable_writer *w =
      +		reftable_new_writer(&strbuf_add_void, buf, &opts);
     -+	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
      +	int i = 0, n;
     -+	struct reftable_log_record log = { 0 };
     ++	struct reftable_log_record log = { NULL };
      +	const struct reftable_stats *stats = NULL;
      +	*names = reftable_calloc(sizeof(char *) * (N + 1));
      +	reftable_writer_set_limits(w, update_index, update_index);
     @@ reftable/reftable_test.c (new)
      +	struct reftable_write_options opts = {
      +		.block_size = 256,
      +	};
     -+	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
      +	int i = 0;
     -+	struct reftable_log_record log = { 0 };
     ++	struct reftable_log_record log = { NULL };
      +	int n;
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_reader rd = { 0 };
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_reader rd = { NULL };
     ++	struct reftable_block_source source = { NULL };
      +	struct strbuf buf = STRBUF_INIT;
      +	struct reftable_writer *w =
      +		reftable_new_writer(&strbuf_add_void, &buf, &opts);
     @@ reftable/reftable_test.c (new)
      +	reftable_writer_set_limits(w, 0, N);
      +	for (i = 0; i < N; i++) {
      +		char name[256];
     -+		struct reftable_ref_record ref = { 0 };
     ++		struct reftable_ref_record ref = { NULL };
      +		snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
      +		names[i] = xstrdup(name);
      +		ref.refname = name;
     @@ reftable/reftable_test.c (new)
      +	}
      +	for (i = 0; i < N; i++) {
      +		uint8_t hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     -+		struct reftable_log_record log = { 0 };
     ++		struct reftable_log_record log = { NULL };
      +		set_test_hash(hash1, i);
      +		set_test_hash(hash2, i + 1);
      +
     @@ reftable/reftable_test.c (new)
      +	char **names;
      +	struct strbuf buf = STRBUF_INIT;
      +	int N = 50;
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_block_source source = { 0 };
     -+	struct reftable_reader rd = { 0 };
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_block_source source = { NULL };
     ++	struct reftable_reader rd = { NULL };
      +	int err = 0;
      +	int j = 0;
      +
     @@ reftable/reftable_test.c (new)
      +	assert_err(err);
      +
      +	while (1) {
     -+		struct reftable_ref_record ref = { 0 };
     ++		struct reftable_ref_record ref = { NULL };
      +		int r = reftable_iterator_next_ref(&it, &ref);
      +		assert(r >= 0);
      +		if (r > 0) {
     @@ reftable/reftable_test.c (new)
      +	char **names;
      +	struct strbuf buf = STRBUF_INIT;
      +	int N = 50;
     -+	struct reftable_reader rd = { 0 };
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_reader rd = { NULL };
     ++	struct reftable_block_source source = { NULL };
      +	int err;
      +	int i;
     -+	struct reftable_log_record log = { 0 };
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_log_record log = { NULL };
     ++	struct reftable_iterator it = { NULL };
      +
      +	write_table(&names, &buf, N, 256, SHA1_ID);
      +
     @@ reftable/reftable_test.c (new)
      +	char **names;
      +	struct strbuf buf = STRBUF_INIT;
      +	int N = 50;
     -+	struct reftable_reader rd = { 0 };
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_reader rd = { NULL };
     ++	struct reftable_block_source source = { NULL };
      +	int err;
      +	int i = 0;
      +
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_iterator it = { NULL };
      +	struct strbuf pastLast = STRBUF_INIT;
     -+	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
      +
      +	write_table(&names, &buf, N, 256, hash_id);
      +
     @@ reftable/reftable_test.c (new)
      +
      +	err = reftable_reader_seek_ref(&rd, &it, pastLast.buf);
      +	if (err == 0) {
     -+		struct reftable_ref_record ref = { 0 };
     ++		struct reftable_ref_record ref = { NULL };
      +		int err = reftable_iterator_next_ref(&it, &ref);
      +		assert(err > 0);
      +	} else {
     @@ reftable/reftable_test.c (new)
      +	struct reftable_write_options opts = {
      +		.block_size = 256,
      +	};
     -+	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
      +	int i = 0;
      +	int n;
      +	int err;
      +	struct reftable_reader rd;
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_block_source source = { NULL };
      +
      +	struct strbuf buf = STRBUF_INIT;
      +	struct reftable_writer *w =
      +		reftable_new_writer(&strbuf_add_void, &buf, &opts);
      +
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_iterator it = { NULL };
      +	int j;
      +
      +	set_test_hash(want_hash, 4);
     @@ reftable/reftable_test.c (new)
      +		char name[100];
      +		uint8_t hash1[SHA1_SIZE];
      +		uint8_t hash2[SHA1_SIZE];
     -+		struct reftable_ref_record ref = { 0 };
     ++		struct reftable_ref_record ref = { NULL };
      +
      +		memset(hash, i, sizeof(hash));
      +		memset(fill, 'x', 50);
     @@ reftable/reftable_test.c (new)
      +	struct strbuf buf = STRBUF_INIT;
      +	struct reftable_writer *w =
      +		reftable_new_writer(&strbuf_add_void, &buf, &opts);
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_block_source source = { NULL };
      +	struct reftable_reader *rd = NULL;
     -+	struct reftable_ref_record rec = { 0 };
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record rec = { NULL };
     ++	struct reftable_iterator it = { NULL };
      +	int err;
      +
      +	reftable_writer_set_limits(w, 1, 1);
 12:  64d98e60b2 ! 12:  4e38db7f48 reftable: rest of library
     @@ Makefile: REFTABLE_OBJS += reftable/zlib-compat.o
      
       ## reftable/VERSION (new) ##
      @@
     -+b8ec0f74c74cb6752eb2033ad8e755a9c19aad15 C: add missing header
     ++7134eb9f8171a9759800f4187f9e6dde997335e7 C: NULL iso 0 for init
      
       ## reftable/dump.c (new) ##
      @@
     @@ reftable/dump.c (new)
      +
      +static int dump_table(const char *tablename)
      +{
     -+	struct reftable_block_source src = { 0 };
     ++	struct reftable_block_source src = { NULL };
      +	int err = reftable_block_source_from_file(&src, tablename);
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_ref_record ref = { 0 };
     -+	struct reftable_log_record log = { 0 };
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_ref_record ref = { NULL };
     ++	struct reftable_log_record log = { NULL };
      +	struct reftable_reader *r = NULL;
      +
      +	if (err < 0)
     @@ reftable/dump.c (new)
      +{
      +	struct reftable_stack *stack = NULL;
      +	struct reftable_write_options cfg = {};
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_ref_record ref = { 0 };
     -+	struct reftable_log_record log = { 0 };
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_ref_record ref = { NULL };
     ++	struct reftable_log_record log = { NULL };
      +	struct reftable_merged_table *merged = NULL;
      +
      +	int err = reftable_new_stack(&stack, stackdir, cfg);
     @@ reftable/merged.c (new)
      +	return merged_iter_next(mi, rec);
      +}
      +
     -+struct reftable_iterator_vtable merged_iter_vtable = {
     ++static struct reftable_iterator_vtable merged_iter_vtable = {
      +	.next = &merged_iter_next_void,
      +	.close = &merged_iter_close,
      +};
     @@ reftable/merged.c (new)
      +	return mt->min;
      +}
      +
     -+int merged_table_seek_record(struct reftable_merged_table *mt,
     -+			     struct reftable_iterator *it,
     -+			     struct reftable_record *rec)
     ++static int merged_table_seek_record(struct reftable_merged_table *mt,
     ++				    struct reftable_iterator *it,
     ++				    struct reftable_record *rec)
      +{
      +	struct reftable_iterator *iters = reftable_calloc(
      +		sizeof(struct reftable_iterator) * mt->stack_len);
     @@ reftable/merged.c (new)
      +	struct reftable_ref_record ref = {
      +		.refname = (char *)name,
      +	};
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	reftable_record_from_ref(&rec, &ref);
      +	return merged_table_seek_record(mt, it, &rec);
      +}
     @@ reftable/merged.c (new)
      +		.refname = (char *)name,
      +		.update_index = update_index,
      +	};
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	reftable_record_from_log(&rec, &log);
      +	return merged_table_seek_record(mt, it, &rec);
      +}
     @@ reftable/merged.h (new)
      +};
      +
      +void merged_table_clear(struct reftable_merged_table *mt);
     -+int merged_table_seek_record(struct reftable_merged_table *mt,
     -+			     struct reftable_iterator *it,
     -+			     struct reftable_record *rec);
      +
      +#endif
      
     @@ reftable/merged_test.c (new)
      +
      +static void test_pq(void)
      +{
     -+	char *names[54] = { 0 };
     ++	char *names[54] = { NULL };
      +	int N = ARRAY_SIZE(names) - 1;
      +
     -+	struct merged_iter_pqueue pq = { 0 };
     ++	struct merged_iter_pqueue pq = { NULL };
      +	const char *last = NULL;
      +
      +	int i = 0;
     @@ reftable/merged_test.c (new)
      +	struct reftable_merged_table *mt =
      +		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);
      +	int i;
     -+	struct reftable_ref_record ref = { 0 };
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
     ++	struct reftable_iterator it = { NULL };
      +	int err = reftable_merged_table_seek_ref(mt, &it, "a");
      +	assert_err(err);
      +
     @@ reftable/merged_test.c (new)
      +	struct reftable_merged_table *mt =
      +		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
      +
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_iterator it = { NULL };
      +	int err = reftable_merged_table_seek_ref(mt, &it, "a");
      +	struct reftable_ref_record *out = NULL;
      +	size_t len = 0;
     @@ reftable/merged_test.c (new)
      +
      +	assert_err(err);
      +	while (len < 100) { /* cap loops/recursion. */
     -+		struct reftable_ref_record ref = { 0 };
     ++		struct reftable_ref_record ref = { NULL };
      +		int err = reftable_iterator_next_ref(&it, &ref);
      +		if (err > 0) {
      +			break;
     @@ reftable/merged_test.c (new)
      +		.update_index = 1,
      +	};
      +	int err;
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_block_source source = { NULL };
      +	struct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);
      +	uint32_t hash_id;
      +	struct reftable_reader *rd = NULL;
     @@ reftable/pq.c (new)
      +#include "system.h"
      +#include "basics.h"
      +
     -+int pq_less(struct pq_entry a, struct pq_entry b)
     ++static int pq_less(struct pq_entry a, struct pq_entry b)
      +{
      +	struct strbuf ak = STRBUF_INIT;
      +	struct strbuf bk = STRBUF_INIT;
     @@ reftable/pq.h (new)
      +	struct reftable_record rec;
      +};
      +
     -+int pq_less(struct pq_entry a, struct pq_entry b);
     -+
      +struct merged_iter_pqueue {
      +	struct pq_entry *heap;
      +	size_t len;
     @@ reftable/refname.c (new)
      +	return strcmp(f_arg->names[k], f_arg->want) >= 0;
      +}
      +
     -+int modification_has_ref(struct modification *mod, const char *name)
     ++static int modification_has_ref(struct modification *mod, const char *name)
      +{
     -+	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_ref_record ref = { NULL };
      +	int err = 0;
      +
      +	if (mod->add_len > 0) {
     @@ reftable/refname.c (new)
      +	mod->del_len = 0;
      +}
      +
     -+int modification_has_ref_with_prefix(struct modification *mod,
     -+				     const char *prefix)
     ++static int modification_has_ref_with_prefix(struct modification *mod,
     ++					    const char *prefix)
      +{
      +	struct reftable_iterator it = { NULL };
      +	struct reftable_ref_record ref = { NULL };
     @@ reftable/refname.c (new)
      +	return err;
      +}
      +
     -+int validate_refname(const char *name)
     ++static int validate_refname(const char *name)
      +{
      +	while (1) {
      +		char *next = strchr(name, '/');
     @@ reftable/refname.h (new)
      +	size_t del_len;
      +};
      +
     -+// -1 = error, 0 = found, 1 = not found
     -+int modification_has_ref(struct modification *mod, const char *name);
     -+
     -+// -1 = error, 0 = found, 1 = not found.
     -+int modification_has_ref_with_prefix(struct modification *mod,
     -+				     const char *prefix);
     -+
     -+// 0 = OK.
     -+int validate_refname(const char *name);
     -+
      +int validate_ref_record_addition(struct reftable_table tab,
      +				 struct reftable_ref_record *recs, size_t sz);
      +
     @@ reftable/refname_test.c (new)
      +	};
      +	int err;
      +	int i;
     -+	struct reftable_block_source source = { 0 };
     ++	struct reftable_block_source source = { NULL };
      +	struct reftable_reader *rd = NULL;
      +	struct reftable_table tab = { NULL };
      +	struct testcase cases[] = {
     @@ reftable/refname_test.c (new)
      +	return test_main(argc, argv);
      +}
      
     + ## reftable/reftable.c ##
     +@@ reftable/reftable.c: int reftable_table_seek_ref(struct reftable_table *tab,
     + 	struct reftable_ref_record ref = {
     + 		.refname = (char *)name,
     + 	};
     +-	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
     + 	reftable_record_from_ref(&rec, &ref);
     + 	return tab->ops->seek_record(tab->table_arg, it, &rec);
     + }
     +@@ reftable/reftable.c: void reftable_table_from_reader(struct reftable_table *tab,
     + int reftable_table_read_ref(struct reftable_table *tab, const char *name,
     + 			    struct reftable_ref_record *ref)
     + {
     +-	struct reftable_iterator it = { 0 };
     ++	struct reftable_iterator it = { NULL };
     + 	int err = reftable_table_seek_ref(tab, &it, name);
     + 	if (err)
     + 		goto done;
     +
       ## reftable/stack.c (new) ##
      @@
      +/*
     @@ reftable/stack.c (new)
      +#include "reftable.h"
      +#include "writer.h"
      +
     ++static int stack_try_add(struct reftable_stack *st,
     ++			 int (*write_table)(struct reftable_writer *wr,
     ++					    void *arg),
     ++			 void *arg);
     ++static int stack_write_compact(struct reftable_stack *st,
     ++			       struct reftable_writer *wr, int first, int last,
     ++			       struct reftable_log_expiry_config *config);
     ++static int stack_check_addition(struct reftable_stack *st,
     ++				const char *new_tab_name);
     ++static void reftable_addition_close(struct reftable_addition *add);
     ++static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     ++					     int reuse_open);
     ++
      +int reftable_new_stack(struct reftable_stack **dest, const char *dir,
      +		       struct reftable_write_options config)
      +{
     @@ reftable/stack.c (new)
      +		}
      +
      +		if (rd == NULL) {
     -+			struct reftable_block_source src = { 0 };
     ++			struct reftable_block_source src = { NULL };
      +			struct strbuf table_path = STRBUF_INIT;
      +			strbuf_addstr(&table_path, st->reftable_dir);
      +			strbuf_addstr(&table_path, "/");
     @@ reftable/stack.c (new)
      +	return udiff;
      +}
      +
     -+int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, int reuse_open)
     ++static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     ++					     int reuse_open)
      +{
      +	struct timeval deadline = { 0 };
      +	int err = gettimeofday(&deadline, NULL);
     @@ reftable/stack.c (new)
      +	return err;
      +}
      +
     -+void reftable_addition_close(struct reftable_addition *add)
     ++static void reftable_addition_close(struct reftable_addition *add)
      +{
      +	int i = 0;
      +	struct strbuf nm = STRBUF_INIT;
     @@ reftable/stack.c (new)
      +	return err;
      +}
      +
     -+int stack_try_add(struct reftable_stack *st,
     -+		  int (*write_table)(struct reftable_writer *wr, void *arg),
     -+		  void *arg)
     ++static int stack_try_add(struct reftable_stack *st,
     ++			 int (*write_table)(struct reftable_writer *wr,
     ++					    void *arg),
     ++			 void *arg)
      +{
      +	struct reftable_addition add = REFTABLE_ADDITION_INIT;
      +	int err = reftable_stack_init_addition(&add, st);
     @@ reftable/stack.c (new)
      +	return err;
      +}
      +
     -+int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr,
     -+			int first, int last,
     -+			struct reftable_log_expiry_config *config)
     ++static int stack_write_compact(struct reftable_stack *st,
     ++			       struct reftable_writer *wr, int first, int last,
     ++			       struct reftable_log_expiry_config *config)
      +{
      +	int subtabs_len = last - first + 1;
      +	struct reftable_table *subtabs = reftable_calloc(
      +		sizeof(struct reftable_table) * (last - first + 1));
      +	struct reftable_merged_table *mt = NULL;
      +	int err = 0;
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_ref_record ref = { 0 };
     -+	struct reftable_log_record log = { 0 };
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_ref_record ref = { NULL };
     ++	struct reftable_log_record log = { NULL };
      +
      +	uint64_t entries = 0;
      +
     @@ reftable/stack.c (new)
      +int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
      +			    struct reftable_log_record *log)
      +{
     -+	struct reftable_iterator it = { 0 };
     ++	struct reftable_iterator it = { NULL };
      +	struct reftable_merged_table *mt = reftable_stack_merged_table(st);
      +	int err = reftable_merged_table_seek_log(mt, &it, refname);
      +	if (err)
     @@ reftable/stack.c (new)
      +	return err;
      +}
      +
     -+int stack_check_addition(struct reftable_stack *st, const char *new_tab_name)
     ++static int stack_check_addition(struct reftable_stack *st,
     ++				const char *new_tab_name)
      +{
      +	int err = 0;
     -+	struct reftable_block_source src = { 0 };
     ++	struct reftable_block_source src = { NULL };
      +	struct reftable_reader *rd = NULL;
      +	struct reftable_table tab = { NULL };
      +	struct reftable_ref_record *refs = NULL;
     @@ reftable/stack.c (new)
      +		goto done;
      +
      +	while (1) {
     -+		struct reftable_ref_record ref = { 0 };
     ++		struct reftable_ref_record ref = { NULL };
      +		err = reftable_iterator_next_ref(&it, &ref);
      +		if (err > 0) {
      +			break;
     @@ reftable/stack.h (new)
      +};
      +
      +int read_lines(const char *filename, char ***lines);
     -+int stack_try_add(struct reftable_stack *st,
     -+		  int (*write_table)(struct reftable_writer *wr, void *arg),
     -+		  void *arg);
     -+int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr,
     -+			int first, int last,
     -+			struct reftable_log_expiry_config *config);
     -+int fastlog2(uint64_t sz);
     -+int stack_check_addition(struct reftable_stack *st, const char *new_tab_name);
     -+void reftable_addition_close(struct reftable_addition *add);
     -+int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     -+				      int reuse_open);
      +
      +struct segment {
      +	int start, end;
     @@ reftable/stack.h (new)
      +	uint64_t bytes;
      +};
      +
     ++int fastlog2(uint64_t sz);
      +struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
      +struct segment suggest_compaction_segment(uint64_t *sizes, int n);
      +
     @@ reftable/stack_test.c (new)
      +		.update_index = 1,
      +		.target = "master",
      +	};
     -+	struct reftable_ref_record dest = { 0 };
     ++	struct reftable_ref_record dest = { NULL };
      +
      +	assert(mkdtemp(dir));
      +
     @@ reftable/stack_test.c (new)
      +		.update_index = 1,
      +		.target = "master",
      +	};
     -+	struct reftable_ref_record dest = { 0 };
     ++	struct reftable_ref_record dest = { NULL };
      +
      +	assert(mkdtemp(dir));
      +
     @@ reftable/stack_test.c (new)
      +	};
      +	struct reftable_stack *st = NULL;
      +	char dir[256] = "/tmp/stack_test.XXXXXX";
     -+	struct reftable_ref_record refs[2] = { { 0 } };
     -+	struct reftable_log_record logs[2] = { { 0 } };
     ++	struct reftable_ref_record refs[2] = { { NULL } };
     ++	struct reftable_log_record logs[2] = { { NULL } };
      +	int N = ARRAY_SIZE(refs);
      +
      +	assert(mkdtemp(dir));
     @@ reftable/stack_test.c (new)
      +	assert_err(err);
      +
      +	for (i = 0; i < N; i++) {
     -+		struct reftable_ref_record dest = { 0 };
     ++		struct reftable_ref_record dest = { NULL };
     ++
      +		int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
      +		assert_err(err);
      +		assert(reftable_ref_record_equal(&dest, refs + i, SHA1_SIZE));
     @@ reftable/stack_test.c (new)
      +	}
      +
      +	for (i = 0; i < N; i++) {
     -+		struct reftable_log_record dest = { 0 };
     ++		struct reftable_log_record dest = { NULL };
      +		int err = reftable_stack_read_log(st, refs[i].refname, &dest);
      +		assert_err(err);
      +		assert(reftable_log_record_equal(&dest, logs + i, SHA1_SIZE));
     @@ reftable/stack_test.c (new)
      +	struct reftable_write_options cfg = { 0 };
      +	struct reftable_stack *st = NULL;
      +	int err;
     -+	struct reftable_ref_record refs[2] = { { 0 } };
     -+	struct reftable_log_record logs[2] = { { 0 } };
     ++	struct reftable_ref_record refs[2] = { { NULL } };
     ++	struct reftable_log_record logs[2] = { { NULL } };
      +	int N = ARRAY_SIZE(refs);
     -+	struct reftable_ref_record dest = { 0 };
     -+	struct reftable_log_record log_dest = { 0 };
     ++	struct reftable_ref_record dest = { NULL };
     ++	struct reftable_log_record log_dest = { NULL };
      +
      +	assert(mkdtemp(dir));
      +
     @@ reftable/stack_test.c (new)
      +	struct reftable_stack *st32 = NULL;
      +	struct reftable_write_options cfg_default = { 0 };
      +	struct reftable_stack *st_default = NULL;
     -+	struct reftable_ref_record dest = { 0 };
     ++	struct reftable_ref_record dest = { NULL };
      +
      +	assert(mkdtemp(dir));
      +	err = reftable_new_stack(&st, dir, cfg);
     @@ reftable/stack_test.c (new)
      +	char dir[256] = "/tmp/stack.test_reflog_expire.XXXXXX";
      +	struct reftable_write_options cfg = { 0 };
      +	struct reftable_stack *st = NULL;
     -+	struct reftable_log_record logs[20] = { { 0 } };
     ++	struct reftable_log_record logs[20] = { { NULL } };
      +	int N = ARRAY_SIZE(logs) - 1;
      +	int i = 0;
      +	int err;
      +	struct reftable_log_expiry_config expiry = {
      +		.time = 10,
      +	};
     -+	struct reftable_log_record log = { 0 };
     ++	struct reftable_log_record log = { NULL };
      +
      +	assert(mkdtemp(dir));
      +

13: b8729fee9e ! 13: c535f838d6 reftable: "test-tool dump-reftable" command.
@@ Makefile: REFTABLE_OBJS += reftable/writer.o
REFTABLE_TEST_OBJS += reftable/refname_test.o REFTABLE_TEST_OBJS += reftable/reftable_test.o

+ ## reftable/iter.c ##
+@@ reftable/iter.c: void reftable_iterator_destroy(struct reftable_iterator *it)
+ int reftable_iterator_next_ref(struct reftable_iterator *it,
+ struct reftable_ref_record *ref)
+ {
+- struct reftable_record rec = { 0 };
++ struct reftable_record rec = { NULL };
+ reftable_record_from_ref(&rec, ref);
+ return iterator_next(it, &rec);
+ }
+@@ reftable/iter.c: int reftable_iterator_next_ref(struct reftable_iterator *it,
+ int reftable_iterator_next_log(struct reftable_iterator *it,
+ struct reftable_log_record *log)
+ {
+- struct reftable_record rec = { 0 };
++ struct reftable_record rec = { NULL };
+ reftable_record_from_log(&rec, log);
+ return iterator_next(it, &rec);
+ }
+@@ reftable/iter.c: static int filtering_ref_iterator_next(void *iter_arg,
+ }
+
+ if (fri->double_check) {
+- struct reftable_iterator it = { 0 };
++ struct reftable_iterator it = { NULL };
+
+ err = reftable_table_seek_ref(&fri->tab, &it,
+ ref->refname);
+
+ ## reftable/reader.c ##
+@@ reftable/reader.c: static int parse_footer(struct reftable_reader *r, uint8_t *footer,
+ int init_reader(struct reftable_reader *r, struct reftable_block_source *source,
+ const char *name)
+ {
+- struct reftable_block footer = { 0 };
+- struct reftable_block header = { 0 };
++ struct reftable_block footer = { NULL };
++ struct reftable_block header = { NULL };
+ int err = 0;
+
+ memset(r, 0, sizeof(struct reftable_reader));
+@@ reftable/reader.c: int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
+ {
+ int32_t guess_block_size = r->block_size ? r->block_size :
+ DEFAULT_BLOCK_SIZE;
+- struct reftable_block block = { 0 };
++ struct reftable_block block = { NULL };
+ uint8_t block_typ = 0;
+ int err = 0;
+ uint32_t header_off = next_off ? 0 : header_size(r->version);
+@@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
+ struct reftable_record *rec)
+ {
+ struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
+- struct reftable_record want_index_rec = { 0 };
++ struct reftable_record want_index_rec = { NULL };
+ struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
+- struct reftable_record index_result_rec = { 0 };
++ struct reftable_record index_result_rec = { NULL };
+ struct table_iter index_iter = TABLE_ITER_INIT;
+ struct table_iter next = TABLE_ITER_INIT;
+ int err = 0;
+@@ reftable/reader.c: int reftable_reader_seek_ref(struct reftable_reader *r,
+ struct reftable_ref_record ref = {
+ .refname = (char *)name,
+ };
+- struct reftable_record rec = { 0 };
++ struct reftable_record rec = { NULL };
+ reftable_record_from_ref(&rec, &ref);
+ return reader_seek(r, it, &rec);
+ }
+@@ reftable/reader.c: int reftable_reader_seek_log_at(struct reftable_reader *r,
+ .refname = (char *)name,
+ .update_index = update_index,
+ };
+- struct reftable_record rec = { 0 };
++ struct reftable_record rec = { NULL };
+ reftable_record_from_log(&rec, &log);
+ return reader_seek(r, it, &rec);
+ }
+@@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
+ .hash_prefix = oid,
+ .hash_prefix_len = r->object_id_len,
+ };
+- struct reftable_record want_rec = { 0 };
+- struct reftable_iterator oit = { 0 };
+- struct reftable_obj_record got = { 0 };
+- struct reftable_record got_rec = { 0 };
++ struct reftable_record want_rec = { NULL };
++ struct reftable_iterator oit = { NULL };
++ struct reftable_obj_record got = { NULL };
++ struct reftable_record got_rec = { NULL };
+ int err = 0;
+ struct indexed_table_ref_iter *itr = NULL;
+
+
## t/helper/test-reftable.c ##
@@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
tree_test_main(argc, argv);

Signature · 1 line
gitgitgadget
#20 lore raw

PATCH v2 1/13 reftable: add LICENSE

From: Han-Wen Nienhuys <[email protected]>

TODO: relicense?

---
 reftable/LICENSE | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 reftable/LICENSE
reftable/LICENSE new file +31 -0
new file mode 100644
index 0000000000..402e0f9356
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Signature · 1 line
gitgitgadget
#21 lore raw

Re: [PATCH v2 01/13] reftable: add LICENSE

Hi,

Quoted 1 line · Han-Wen Nienhuys

TODO: relicense?

This line presumably doesn't want to be part of permanent history. :)

Instead, it can say a little about rationale. For example, does this make the code easier for other projects like libgit2 to share?

Quoted 5 lines
---
 reftable/LICENSE | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 reftable/LICENSE

Should there be a reftable/README as well?

[...]

reftable/LICENSE line 4

Quoted 7 lines
reftable/LICENSE new file +4 -0
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.

nit: Google seems to prefer a slightly more spartan form of this license text:
https://opensource.google/docs/releasing/licenses/#bsd-license-file

Nits aside, except for the TODO line, this patch looks reasonable to me.

Thanks,
Jonathan

#22 lore raw

PATCH v2 2/13 reftable: define the public API

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 reftable/reftable.h | 585 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 585 insertions(+)
 create mode 100644 reftable/reftable.h
reftable/reftable.h new file +585 -0
new file mode 100644
index 0000000000..5f8ebf5540
@@ -0,0 +1,585 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+void reftable_set_alloc(void *(*malloc)(size_t),
+			void *(*realloc)(void *, size_t), void (*free)(void *));
+
+/****************************************************************
+ Basic data types
+
+ Reftables store the state of each ref in struct reftable_ref_record, and they
+ store a sequence of reflog updates in struct reftable_log_record.
+ ****************************************************************/
+
+/* reftable_ref_record holds a ref database entry target_value */
+struct reftable_ref_record {
+	char *refname; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	uint8_t *value; /* SHA1, or NULL. malloced. */
+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
+
+/* prints a reftable_ref_record onto stdout */
+void reftable_ref_record_print(struct reftable_ref_record *ref,
+			       uint32_t hash_id);
+
+/* frees and nulls all pointer values. */
+void reftable_ref_record_clear(struct reftable_ref_record *ref);
+
+/* returns whether two reftable_ref_records are the same */
+int reftable_ref_record_equal(struct reftable_ref_record *a,
+			      struct reftable_ref_record *b, int hash_size);
+
+/* reftable_log_record holds a reflog entry */
+struct reftable_log_record {
+	char *refname;
+	uint64_t update_index; /* logical timestamp of a transactional update.
+				*/
+	uint8_t *new_hash;
+	uint8_t *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+int reftable_log_record_is_deletion(const struct reftable_log_record *log);
+
+/* frees and nulls all pointer values. */
+void reftable_log_record_clear(struct reftable_log_record *log);
+
+/* returns whether two records are equal. */
+int reftable_log_record_equal(struct reftable_log_record *a,
+			      struct reftable_log_record *b, int hash_size);
+
+/* dumps a reftable_log_record on stdout, for debugging/testing. */
+void reftable_log_record_print(struct reftable_log_record *log,
+			       uint32_t hash_id);
+
+/****************************************************************
+ Error handling
+
+ Error are signaled with negative integer return values. 0 means success.
+ ****************************************************************/
+
+/* different types of errors */
+enum reftable_error {
+	/* Unexpected file system behavior */
+	REFTABLE_IO_ERROR = -2,
+
+	/* Format inconsistency on reading data
+	 */
+	REFTABLE_FORMAT_ERROR = -3,
+
+	/* File does not exist. Returned from block_source_from_file(),  because
+	   it needs special handling in stack.
+	*/
+	REFTABLE_NOT_EXIST_ERROR = -4,
+
+	/* Trying to write out-of-date data. */
+	REFTABLE_LOCK_ERROR = -5,
+
+	/* Misuse of the API:
+	   - on writing a record with NULL refname.
+	   - on writing a reftable_ref_record outside the table limits
+	   - on writing a ref or log record before the stack's next_update_index
+	   - on writing a log record with multiline message with
+	   exact_log_message unset
+	   - on reading a reftable_ref_record from log iterator, or vice versa.
+	*/
+	REFTABLE_API_ERROR = -6,
+
+	/* Decompression error */
+	REFTABLE_ZLIB_ERROR = -7,
+
+	/* Wrote a table without blocks. */
+	REFTABLE_EMPTY_TABLE_ERROR = -8,
+
+	/* Dir/file conflict. */
+	REFTABLE_NAME_CONFLICT = -9,
+
+	/* Illegal ref name. */
+	REFTABLE_REFNAME_ERROR = -10,
+};
+
+/* convert the numeric error code to a string. The string should not be
+ * deallocated. */
+const char *reftable_error_str(int err);
+
+/*
+ * Convert the numeric error code to an equivalent errno code.
+ */
+int reftable_error_to_errno(int err);
+
+/****************************************************************
+ Writing
+
+ Writing single reftables
+ ****************************************************************/
+
+/* reftable_write_options sets options for writing a single reftable. */
+struct reftable_write_options {
+	/* boolean: do not pad out blocks to block size. */
+	int unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* boolean: do not generate a SHA1 => ref index. */
+	int skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+
+	/* 4-byte identifier ("sha1", "s256") of the hash.
+	 * Defaults to SHA1 if unset
+	 */
+	uint32_t hash_id;
+
+	/* boolean: do not check ref names for validity or dir/file conflicts.
+	 */
+	int skip_name_check;
+
+	/* boolean: copy log messages exactly. If unset, check that the message
+	 *   is a single line, and add '\n' if missing.
+	 */
+	int exact_log_message;
+};
+
+/* reftable_block_stats holds statistics for a single block type */
+struct reftable_block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct reftable_stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct reftable_block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct reftable_block_stats obj_stats;
+	/* stats for index blocks */
+	struct reftable_block_stats idx_stats;
+	/* stats for log blocks */
+	struct reftable_block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* reftable_new_writer creates a new writer */
+struct reftable_writer *
+reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
+		    void *writer_arg, struct reftable_write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int reftable_fd_write(void *fdp, const void *data, size_t size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
+ */
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+				uint64_t max);
+
+/* adds a reftable_ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned.
+
+   It is an error to write a ref record after a log record.
+ */
+int reftable_writer_add_ref(struct reftable_writer *w,
+			    struct reftable_ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int reftable_writer_add_refs(struct reftable_writer *w,
+			     struct reftable_ref_record *refs, int n);
+
+/* adds a reftable_log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int reftable_writer_add_log(struct reftable_writer *w,
+			    struct reftable_log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int reftable_writer_add_logs(struct reftable_writer *w,
+			     struct reftable_log_record *logs, int n);
+
+/* reftable_writer_close finalizes the reftable. The writer is retained so
+ * statistics can be inspected. */
+int reftable_writer_close(struct reftable_writer *w);
+
+/* writer_stats returns the statistics on the reftable being written.
+
+   This struct becomes invalid when the writer is freed.
+ */
+const struct reftable_stats *writer_stats(struct reftable_writer *w);
+
+/* reftable_writer_free deallocates memory for the writer */
+void reftable_writer_free(struct reftable_writer *w);
+
+/****************************************************************
+ * ITERATING
+ ****************************************************************/
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct reftable_iterator {
+	struct reftable_iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_ref(struct reftable_iterator *it,
+			       struct reftable_ref_record *ref);
+
+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_log(struct reftable_iterator *it,
+			       struct reftable_log_record *log);
+
+/* releases resources associated with an iterator. */
+void reftable_iterator_destroy(struct reftable_iterator *it);
+
+/****************************************************************
+ Reading single tables
+
+ The follow routines are for reading single files. For an application-level
+ interface, skip ahead to struct reftable_merged_table and struct
+ reftable_stack.
+ ****************************************************************/
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct reftable_block_source {
+	struct reftable_block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct reftable_block {
+	uint8_t *data;
+	int len;
+	struct reftable_block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct reftable_block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct reftable_block *dest,
+			  uint64_t off, uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct reftable_block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int reftable_block_source_from_file(struct reftable_block_source *block_src,
+				    const char *name);
+
+/* The reader struct is a handle to an open reftable file. */
+struct reftable_reader;
+
+/* reftable_new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp. The name is used for creating a stack. Typically, it is the
+ * basename of the file. The block source `src` is owned by the reader, and is
+ * closed on calling reftable_reader_destroy().
+ */
+int reftable_new_reader(struct reftable_reader **pp,
+			struct reftable_block_source *src, const char *name);
+
+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted
+   in the table.  To seek to the start of the table, use name = "".
+
+   example:
+
+   struct reftable_reader *r = NULL;
+   int err = reftable_new_reader(&r, &src, "filename");
+   if (err < 0) { ... }
+   struct reftable_iterator it  = {0};
+   err = reftable_reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct reftable_ref_record ref  = {0};
+   while (1) {
+     err = reftable_iterator_next_ref(&it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   reftable_iterator_destroy(&it);
+   reftable_ref_record_clear(&ref);
+ */
+int reftable_reader_seek_ref(struct reftable_reader *r,
+			     struct reftable_iterator *it, const char *name);
+
+/* returns the hash ID used in this table. */
+uint32_t reftable_reader_hash_id(struct reftable_reader *r);
+
+/* seek to logs for the given name, older than update_index. To seek to the
+   start of the table, use name = "".
+ */
+int reftable_reader_seek_log_at(struct reftable_reader *r,
+				struct reftable_iterator *it, const char *name,
+				uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reftable_reader_seek_log(struct reftable_reader *r,
+			     struct reftable_iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reftable_reader_free(struct reftable_reader *);
+
+/* return an iterator for the refs pointing to `oid`. */
+int reftable_reader_refs_for(struct reftable_reader *r,
+			     struct reftable_iterator *it, uint8_t *oid);
+
+/* return the max_update_index for a table */
+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);
+
+/****************************************************************
+ Merged tables
+
+ A ref database kept in a sequence of table files. The merged_table presents a
+ unified view to reading (seeking, iterating) a sequence of immutable tables.
+ ****************************************************************/
+
+/* A merged table is implements seeking/iterating over a stack of tables. */
+struct reftable_merged_table;
+
+/* A generic reftable; see below. */
+struct reftable_table;
+
+/* reftable_new_merged_table creates a new merged table. It takes ownership of
+   the stack array.
+*/
+int reftable_new_merged_table(struct reftable_merged_table **dest,
+			      struct reftable_table *stack, int n,
+			      uint32_t hash_id);
+
+/* returns an iterator positioned just before 'name' */
+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
+				   struct reftable_iterator *it,
+				   const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
+				      struct reftable_iterator *it,
+				      const char *name, uint64_t update_index);
+
+/* like reftable_merged_table_seek_log_at but look for the newest entry. */
+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
+				   struct reftable_iterator *it,
+				   const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
+
+/* releases memory for the merged_table */
+void reftable_merged_table_free(struct reftable_merged_table *m);
+
+/* return the hash ID of the merged table. */
+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);
+
+/****************************************************************
+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.
+ ****************************************************************/
+
+struct reftable_table {
+	struct reftable_table_vtable *ops;
+	void *table_arg;
+};
+
+int reftable_table_seek_ref(struct reftable_table *tab,
+			    struct reftable_iterator *it, const char *name);
+
+void reftable_table_from_reader(struct reftable_table *tab,
+				struct reftable_reader *reader);
+
+/* returns the hash ID from a generic reftable_table */
+uint32_t reftable_table_hash_id(struct reftable_table *tab);
+
+/* create a generic table from reftable_merged_table */
+void reftable_table_from_merged_table(struct reftable_table *tab,
+				      struct reftable_merged_table *table);
+
+/* returns the max update_index covered by this table. */
+uint64_t reftable_table_max_update_index(struct reftable_table *tab);
+
+/* returns the min update_index covered by this table. */
+uint64_t reftable_table_min_update_index(struct reftable_table *tab);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_table_read_ref(struct reftable_table *tab, const char *name,
+			    struct reftable_ref_record *ref);
+
+/****************************************************************
+ Mutable ref database
+
+ The stack presents an interface to a mutable sequence of reftables.
+ ****************************************************************/
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct reftable_stack;
+
+/* open a new reftable stack. The tables along with the table list will be
+   stored in 'dir'. Typically, this should be .git/reftables.
+*/
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
+		       struct reftable_write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
+
+/* holds a transaction to add tables at the top of a stack. */
+struct reftable_addition;
+
+/*
+  returns a new transaction to add reftables to the given stack. As a side
+  effect, the ref database is locked.
+*/
+int reftable_stack_new_addition(struct reftable_addition **dest,
+				struct reftable_stack *st);
+
+/* Adds a reftable to transaction. */
+int reftable_addition_add(struct reftable_addition *add,
+			  int (*write_table)(struct reftable_writer *wr,
+					     void *arg),
+			  void *arg);
+
+/* Commits the transaction, releasing the lock. */
+int reftable_addition_commit(struct reftable_addition *add);
+
+/* Release all non-committed data from the transaction, and deallocate the
+   transaction. Releases the lock if held. */
+void reftable_addition_destroy(struct reftable_addition *add);
+
+/* add a new table to the stack. The write_table function must call
+   reftable_writer_set_limits, add refs and return an error value. */
+int reftable_stack_add(struct reftable_stack *st,
+		       int (*write_table)(struct reftable_writer *wr,
+					  void *write_arg),
+		       void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct reftable_merged_table *
+reftable_stack_merged_table(struct reftable_stack *st);
+
+/* frees all resources associated with the stack. */
+void reftable_stack_destroy(struct reftable_stack *st);
+
+/* Reloads the stack if necessary. This is very cheap to run if the stack was up
+ * to date */
+int reftable_stack_reload(struct reftable_stack *st);
+
+/* Policy for expiring reflog entries. */
+struct reftable_log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int reftable_stack_compact_all(struct reftable_stack *st,
+			       struct reftable_log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int reftable_stack_auto_compact(struct reftable_stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
+			    struct reftable_ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
+			    struct reftable_log_record *log);
+
+/* statistics on past compactions. */
+struct reftable_compaction_stats {
+	uint64_t bytes; /* total number of bytes written */
+	uint64_t entries_written; /* total number of entries written, including
+				     failures. */
+	int attempts; /* how often we tried to compact */
+	int failures; /* failures happen on concurrent updates */
+};
+
+/* return statistics for compaction up till now. */
+struct reftable_compaction_stats *
+reftable_stack_compaction_stats(struct reftable_stack *st);
+
+#endif
Signature · 1 line
gitgitgadget
#23 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

Hi,

Quoted 3 lines · Han-Wen Nienhuys
 reftable/reftable.h | 585 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 585 insertions(+)
 create mode 100644 reftable/reftable.h

Adding a header in a separate patch from the implementation doesn't match the usual practice. Can we add declarations in the same patch as the functions being declared instead?

We could still introduce the header early in its own patch if we want, but it would be a skeleton of a header that gets filled out by later patches.

[...]

Quoted 3 lines
reftable/reftable.h new file +0 -0
@@ -0,0 +1,585 @@

[...]

reftable/reftable.h line 13

Quoted 5 lines
reftable/reftable.h new file +5 -0
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include <stdint.h>
+#include <stddef.h>

Git tends to prefer to rely on the caller to have #include-ed git-compat-util.h instead. That helps avoid having to worry about portability gotchas around what feature test macros need to have been set before the first #include of a standard header.

reftable/reftable.h line 23

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+void reftable_set_alloc(void *(*malloc)(size_t),
+			void *(*realloc)(void *, size_t), void (*free)(void *));
+
+/****************************************************************
+ Basic data types
+
+ Reftables store the state of each ref in struct reftable_ref_record, and they
+ store a sequence of reflog updates in struct reftable_log_record.
+ ****************************************************************/

nit: Git style for comments would be

/*
* Basic data types
*
* Reftables store the state of each [etc]
*/

In other words:

- no lines of stars
- "/*" with no text after it as first line
- " *" leading subsequent lines
- " */" as last line

reftable/reftable.h line 29

Quoted 6 lines
reftable/reftable.h new file +6 -0
+
+/* reftable_ref_record holds a ref database entry target_value */
+struct reftable_ref_record {
+	char *refname; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */

Likewise: Git doesn't have this style of multi-line comment, but this could be e.g. a single-line comment before

	/* logical timestamp at which this value was written */
	uint64_t update_index;

reftable/reftable.h line 30

Quoted 1 line
reftable/reftable.h new file +1 -0
+	uint8_t *value; /* SHA1, or NULL. malloced. */

What does "NULL. malloced" mean? Is the point that the reftable_ref_record owns the memory? (We don't do as good a job of documenting that as we should. Often a non-const pointer is owned, but in this example it's probably better to be explicit that this struct owns the memory pointed to by all its fields in the comment describing the struct as a whole.)

It would be more idiomatic for this to be a 'struct object_id' inline in the reftable_ref_record.

reftable/reftable.h line 36

Quoted 6 lines
reftable/reftable.h new file +6 -0
+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);

Nice, this looks pleasant to use.

reftable/reftable.h line 40

Quoted 4 lines
reftable/reftable.h new file +4 -0
+
+/* prints a reftable_ref_record onto stdout */
+void reftable_ref_record_print(struct reftable_ref_record *ref,
+			       uint32_t hash_id);

Probably worth being explicit about the purpose: is this for debugging?

What is a hash_id?

reftable/reftable.h line 50

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+/* frees and nulls all pointer values. */
+void reftable_ref_record_clear(struct reftable_ref_record *ref);
+
+/* returns whether two reftable_ref_records are the same */
+int reftable_ref_record_equal(struct reftable_ref_record *a,
+			      struct reftable_ref_record *b, int hash_size);
+
+/* reftable_log_record holds a reflog entry */
+struct reftable_log_record {

Should some of this documentation mention the reftable spec? E.g. an opening comment at the beginning of the header. That might help avoid having to duplicate too much explanation when terminology matches.

reftable/reftable.h line 53

Quoted 3 lines
reftable/reftable.h new file +3 -0
+	char *refname;
+	uint64_t update_index; /* logical timestamp of a transactional update.
+				*/

multiline comment (I won't mention the rest).

reftable/reftable.h line 63

Quoted 10 lines
reftable/reftable.h new file +10 -0
+	uint8_t *new_hash;
+	uint8_t *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */

These are a bit subtle --- should the comment include a little more explanation (e.g. about how this is mostly for tools like "git stash")?

Quoted 11 lines
reftable/reftable.h new file +11 -0
+int reftable_log_record_is_deletion(const struct reftable_log_record *log);
+
+/* frees and nulls all pointer values. */
+void reftable_log_record_clear(struct reftable_log_record *log);
+
+/* returns whether two records are equal. */
+int reftable_log_record_equal(struct reftable_log_record *a,
+			      struct reftable_log_record *b, int hash_size);
+
+/* dumps a reftable_log_record on stdout, for debugging/testing. */
+void reftable_log_record_print(struct reftable_log_record *log,

reftable/reftable.h line 84

Quoted 10 lines
reftable/reftable.h new file +10 -0
+			       uint32_t hash_id);
+
+/****************************************************************
+ Error handling
+
+ Error are signaled with negative integer return values. 0 means success.
+ ****************************************************************/
+
+/* different types of errors */
+enum reftable_error {

Reminds me of zlib and liblzma. I like it.

Quoted 14 lines
reftable/reftable.h new file +14 -0
+	/* Unexpected file system behavior */
+	REFTABLE_IO_ERROR = -2,
+
+	/* Format inconsistency on reading data
+	 */
+	REFTABLE_FORMAT_ERROR = -3,
+
+	/* File does not exist. Returned from block_source_from_file(),  because
+	   it needs special handling in stack.
+	*/
+	REFTABLE_NOT_EXIST_ERROR = -4,
+
+	/* Trying to write out-of-date data. */
+	REFTABLE_LOCK_ERROR = -5,

reftable/reftable.h line 108

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+	/* Misuse of the API:
+	   - on writing a record with NULL refname.
+	   - on writing a reftable_ref_record outside the table limits
+	   - on writing a ref or log record before the stack's next_update_index
+	   - on writing a log record with multiline message with
+	   exact_log_message unset
+	   - on reading a reftable_ref_record from log iterator, or vice versa.
+	*/
+	REFTABLE_API_ERROR = -6,

Should these call BUG()? Or is it useful for some callers to be able to recover from these errors?

reftable/reftable.h line 116

Quoted 8 lines
reftable/reftable.h new file +8 -0
+
+	/* Decompression error */
+	REFTABLE_ZLIB_ERROR = -7,
+
+	/* Wrote a table without blocks. */
+	REFTABLE_EMPTY_TABLE_ERROR = -8,
+
+	/* Dir/file conflict. */

This is when adding a ref?

reftable/reftable.h line 125

Quoted 9 lines
reftable/reftable.h new file +9 -0
+	REFTABLE_NAME_CONFLICT = -9,
+
+	/* Illegal ref name. */
+	REFTABLE_REFNAME_ERROR = -10,
+};
+
+/* convert the numeric error code to a string. The string should not be
+ * deallocated. */
+const char *reftable_error_str(int err);

In the ZLIB_ERROR case this would produce a frustratingly vague message. I think I don't mind, but it might be worth a NEEDSWORK comment to revisit in the future.

reftable/reftable.h line 130

Quoted 5 lines
reftable/reftable.h new file +5 -0
+
+/*
+ * Convert the numeric error code to an equivalent errno code.
+ */
+int reftable_error_to_errno(int err);

What is the intended use of this function?

[...]

reftable/reftable.h line 168

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* reftable_block_stats holds statistics for a single block type */
+struct reftable_block_stats {

Are these for use in tracing, debugging, or some other purpose?

[...]

reftable/reftable.h line 207

Quoted 4 lines
reftable/reftable.h new file +4 -0
+/* reftable_new_writer creates a new writer */
+struct reftable_writer *
+reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
+		    void *writer_arg, struct reftable_write_options *opts);

What is a writer? What is a writer_func? Is this a handle that allows me to determine where I write my reftable? (Sounds reasonable but the comment could be more explicit.)

[...]

reftable/reftable.h line 210

Quoted 3 lines
reftable/reftable.h new file +3 -0
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int reftable_fd_write(void *fdp, const void *data, size_t size);

What does this do for interrupted writes?

reftable/reftable.h line 221

Quoted 11 lines
reftable/reftable.h new file +11 -0
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
+ */
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+				uint64_t max);

What happens if I write to a reftable_writer without setting limits first?

reftable/reftable.h line 230

Quoted 9 lines
reftable/reftable.h new file +9 -0
+
+/* adds a reftable_ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned.
+
+   It is an error to write a ref record after a log record.
+ */
+int reftable_writer_add_ref(struct reftable_writer *w,
+			    struct reftable_ref_record *ref);

optional: since it's called a writer, _write_ref() feels like a slightly more natural verb (likewise for the others).

[...]

reftable/reftable.h line 241

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/* adds a reftable_log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int reftable_writer_add_log(struct reftable_writer *w,
+			    struct reftable_log_record *log);

If I want to write multiple log records with the same update_index to represent a multi-ref transaction, do I have to do anything careful?

reftable/reftable.h line 250

Quoted 9 lines
reftable/reftable.h new file +9 -0
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int reftable_writer_add_logs(struct reftable_writer *w,
+			     struct reftable_log_record *logs, int n);
+
+/* reftable_writer_close finalizes the reftable. The writer is retained so
+ * statistics can be inspected. */
+int reftable_writer_close(struct reftable_writer *w);

Nice.

reftable/reftable.h line 256

Quoted 6 lines
reftable/reftable.h new file +6 -0
+
+/* writer_stats returns the statistics on the reftable being written.
+
+   This struct becomes invalid when the writer is freed.
+ */
+const struct reftable_stats *writer_stats(struct reftable_writer *w);

Also nice.

[...]

reftable/reftable.h line 271

Quoted 7 lines
reftable/reftable.h new file +7 -0
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct reftable_iterator {
+	struct reftable_iterator_vtable *ops;
+	void *iter_arg;
+};

Even small structs like this one tend not to be passed by value in the Git codebase. The only counterexample I found with a quick search (builtin/stash.c::save_untracked_files) appears to be a typo.

[...]

reftable/reftable.h line 311

Quoted 8 lines
reftable/reftable.h new file +8 -0
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct reftable_block {
+	uint8_t *data;
+	int len;
+	struct reftable_block_source source;
+};

Is this something that could use mem-pool.h?

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/* block_source_vtable are the operations that make up block_source */
+struct reftable_block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+

reftable/reftable.h line 327

Quoted 10 lines
reftable/reftable.h new file +10 -0
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct reftable_block *dest,
+			  uint64_t off, uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct reftable_block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};

This is abstracting file input? I wonder if some name emphasizing the 'read' operation like block_reader might be clearer.

Do I pass in the 'struct block_source *' as the source arg? If so, why are these declared as void *?

Is the reason this manages the buffer instead of requiring a caller-supplied buffer to support zero-copy?

[...]

reftable/reftable.h line 334

Quoted 7 lines
reftable/reftable.h new file +7 -0
+
+/* opens a file on the file system as a block_source */
+int reftable_block_source_from_file(struct reftable_block_source *block_src,
+				    const char *name);
+
+/* The reader struct is a handle to an open reftable file. */
+struct reftable_reader;

... ah, this is why the underlying file handle isn't called a reader. Hmm.

[...]

reftable/reftable.h line 345

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted
+   in the table.  To seek to the start of the table, use name = "".

nit: s/iterator where/iterator pointing where/

Quoted 12 lines
reftable/reftable.h new file +12 -0
+
+   example:
+
+   struct reftable_reader *r = NULL;
+   int err = reftable_new_reader(&r, &src, "filename");
+   if (err < 0) { ... }
+   struct reftable_iterator it  = {0};
+   err = reftable_reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct reftable_ref_record ref  = {0};
+   while (1) {
+     err = reftable_iterator_next_ref(&it, &ref);

reftable/reftable.h line 367

Quoted 10 lines
reftable/reftable.h new file +10 -0
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   reftable_iterator_destroy(&it);
+   reftable_ref_record_clear(&ref);

Nice.

reftable/reftable.h line 373

Quoted 6 lines

+ */

reftable/reftable.h new file +5 -0
+int reftable_reader_seek_ref(struct reftable_reader *r,
+			     struct reftable_iterator *it, const char *name);
+
+/* returns the hash ID used in this table. */
+uint32_t reftable_reader_hash_id(struct reftable_reader *r);

What is a hash id?

[...]

Quoted 4 lines

+ Merged tables
+
+ A ref database kept in a sequence of table files. The merged_table presents a
+ unified view to reading (seeking, iterating) a sequence of immutable tables.

Nice as well.

[...]

reftable/reftable.h line 436

Quoted 3 lines
reftable/reftable.h new file +3 -0
+/* returns the max update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);

nit: return type should be on the same line as the function name

	uint64_t reftable_merged_table_max_update_index(
					struct reftable_merged_table *mt);

[...]

Quoted 3 lines

+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.

Are there callers/helpers that don't know whether they want one or the other?

[...]

Quoted 2 lines

+ * a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack

Nice.

[...]

reftable/reftable.h line 503

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* holds a transaction to add tables at the top of a stack. */
+struct reftable_addition;

When would I add multiple tables at once to the top? Is this used during compacting, for example?

[...]

reftable/reftable.h line 560

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* heuristically compact unbalanced table stack. */
+int reftable_stack_auto_compact(struct reftable_stack *st);

When do I call this? What are the semantics? (By this I don't mean what heuristic does it use, but what expectation should I have as a caller?)

reftable/reftable.h line 570

Quoted 4 lines
reftable/reftable.h new file +4 -0
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
+			    struct reftable_log_record *log);

nit: single log *record*?

reftable/reftable.h line 583

Quoted 12 lines
reftable/reftable.h new file +12 -0
+/* statistics on past compactions. */
+struct reftable_compaction_stats {
+	uint64_t bytes; /* total number of bytes written */
+	uint64_t entries_written; /* total number of entries written, including
+				     failures. */
+	int attempts; /* how often we tried to compact */
+	int failures; /* failures happen on concurrent updates */
+};
+
+/* return statistics for compaction up till now. */
+struct reftable_compaction_stats *
+reftable_stack_compaction_stats(struct reftable_stack *st);

All in all, I like this API. Thanks for putting it together thoughtfully.

The API builds up in layers (blocks, reftables, merged reftables, etc), suggesting a natural division for the patch series. I think the rest of the series follows that --- let's see.

Thanks,
Jonathan

#24 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

Quoted 9 lines · Jonathan Nieder

Hi,

Quoted 3 lines · Han-Wen Nienhuys
 reftable/reftable.h | 585 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 585 insertions(+)
 create mode 100644 reftable/reftable.h
Quoted 7 lines

Adding a header in a separate patch from the implementation doesn't match the usual practice. Can we add declarations in the same patch as the functions being declared instead?

We could still introduce the header early in its own patch if we want, but it would be a skeleton of a header that gets filled out by later patches.

To poke a little deeper into what I think Jonathan is trying to say:

When we reviewed this for review club, the general feeling was that A) this commit structure is still pretty difficult to review, and B) we want to make sure you aren't spending a ton of time reorganizing the code only to find that people still don't like the structure (as may have happened with the current iteration).

One point I think Jonathan is making is that each commit should stand
alone: compile, be tested, and do something succinct. Another point (or maybe the same point from a different angle) is that a series of commits should tell a progressive story. That is, something like

add infrastructure to support reftable library
reftable: read/write blocks
reftable: write files
reftable: read files
reftable: generate binary tree from file
...

is much more compelling (and easier to review) than

reftable: LICENSE
reftable: headers
reftable: tests
...

Towards the latter end of your series it seems like you started to take that approach; but some commits, like this one (reftable: define the public API) are not quite so. That is, this commit is hard to review without the context of the rest of the series: I read it, I say, "well WHY do we need these functions?", and then I cannot continue my review of this patch until I've completed my review of the rest of the series.

Of course, taking a completed project - like your initial reftable submission - and then chopping it up into a cute story of commits is a pain in the ***. Doing it twice - or more - is just aggravating. So I wonder whether we can bikeshed what story would look nice before you even pick up your 'git rebase -i'? Doing that bikeshedding here on list means that we also have a chance for someone to interrupt and say, "no, that organization doesn't make sense" - or even for someone to say "Emily, there is no need to reorganize these commits, go sit down" ;)

Quoted 6 lines

All in all, I like this API. Thanks for putting it together thoughtfully.

The API builds up in layers (blocks, reftables, merged reftables, etc), suggesting a natural division for the patch series. I think the rest of the series follows that --- let's see.

Or maybe what I said runs contrary to what Jonathan was actually saying. Please, correct me :)

- Emily

#25 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

Quoted 7 lines · Emily Shaffer

Of course, taking a completed project - like your initial reftable submission - and then chopping it up into a cute story of commits is a pain in the ***. Doing it twice - or more - is just aggravating. So I wonder whether we can bikeshed what story would look nice before you even pick up your 'git rebase -i'? Doing that bikeshedding here on list means that we also have a chance for someone to interrupt and say, "no, that organization doesn't make sense" - or even for someone to say

It would be great if you could bikeshed about this while I'm on holiday :)

That said, I think it's worthwhile to first look at the commit that uses the library to implement a ref backend, because that will show better how the reftable library fits with the way that Git likes to consume its data.

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#26 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

In reply to Emily Shaffer
Quoted 19 lines · Emily Shaffer
Quoted 9 lines · Jonathan Nieder

Hi,

Quoted 3 lines · Han-Wen Nienhuys
 reftable/reftable.h | 585 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 585 insertions(+)
 create mode 100644 reftable/reftable.h
Quoted 7 lines

Adding a header in a separate patch from the implementation doesn't match the usual practice. Can we add declarations in the same patch as the functions being declared instead?

We could still introduce the header early in its own patch if we want, but it would be a skeleton of a header that gets filled out by later patches.

Quoted 7 lines

To poke a little deeper into what I think Jonathan is trying to say:

Of course, taking a completed project - like your initial reftable submission - and then chopping it up into a cute story of commits is a pain in the ***. Doing it twice - or more - is just aggravating. So I wonder whether we can bikeshed what story would look nice before you even pick up your 'git rebase -i'? Doing that bikeshedding here on list

Looks like this didn't happen. I went with Jonathan's suggestion, and chopped the public API header in smaller bits. PTAL.

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#27 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

In reply to Jonathan Nieder
Quoted 3 lines · Jonathan Nieder

Adding a header in a separate patch from the implementation doesn't match the usual practice. Can we add declarations in the same patch as the functions being declared instead?

We could, but it would considerably complicate work on this patch series, as the commit boundary then doesn't fall on file boundaries anymore. Would you be open to having multiple headers for the public interface? eg. reftable-record.h, reftable-reader.h etc. ?

Quoted 10 lines

reftable/reftable.h line 108

Quoted 9 lines
reftable/reftable.h new file +9 -0
+     /* Misuse of the API:
+        - on writing a record with NULL refname.
+        - on writing a reftable_ref_record outside the table limits
+        - on writing a ref or log record before the stack's next_update_index
+        - on writing a log record with multiline message with
+        exact_log_message unset
+        - on reading a reftable_ref_record from log iterator, or vice versa.
+     */
+     REFTABLE_API_ERROR = -6,
Quoted 2 lines

Should these call BUG()? Or is it useful for some callers to be able to recover from these errors?

Since this was written as a standalone library, it's up to the caller to decide what should be done. It also simplifies testing: you can verify that incorrect API usage returns a certain error code, but a BUG() that prints an error or crashes the program is much harder to verify.

Quoted 3 lines
Quoted 1 line

+ /* Dir/file conflict. */

This is when adding a ref?

yes.

Quoted 7 lines

reftable/reftable.h line 130

Quoted 5 lines
reftable/reftable.h new file +5 -0
+
+/*
+ * Convert the numeric error code to an equivalent errno code.
+ */
+int reftable_error_to_errno(int err);

What is the intended use of this function?

The read_raw_ref method in the ref backend API uses errno values as out-of-band communication mechanism.

Quoted 5 lines
Quoted 5 lines
reftable/reftable.h new file +5 -0
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
Quoted 8 lines

reftable/reftable.h line 221

Quoted 5 lines
reftable/reftable.h new file +5 -0
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
+ */
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+                             uint64_t max);

What happens if I write to a reftable_writer without setting limits first?

you get an REFTABLE_API_ERROR.

Quoted 19 lines
Quoted 5 lines
reftable/reftable.h new file +5 -0
+/* block_source_vtable are the operations that make up block_source */
+struct reftable_block_source_vtable {
+     /* returns the size of a block source */
+     uint64_t (*size)(void *source);
+

reftable/reftable.h line 327

Quoted 10 lines
reftable/reftable.h new file +10 -0
+     /* reads a segment from the block source. It is an error to read
+        beyond the end of the block */
+     int (*read_block)(void *source, struct reftable_block *dest,
+                       uint64_t off, uint32_t size);
+     /* mark the block as read; may return the data back to malloc */
+     void (*return_block)(void *source, struct reftable_block *blockp);
+
+     /* release all resources associated with the block source */
+     void (*close)(void *source);
+};

This is abstracting file input? I wonder if some name emphasizing the 'read' operation like block_reader might be clearer.

Quoted 2 lines

Do I pass in the 'struct block_source *' as the source arg? If so, why are these declared as void *?

you pass in block_source->arg. Should struct implementations of polymorphic types carry a pointer to their own vtable instead?

Quoted 2 lines

Is the reason this manages the buffer instead of requiring a caller-supplied buffer to support zero-copy?

Log blocks are compressed, so the caller doesn't know the correct size to supply. By letting the block source handle the management, we can swap out the block read from the file for a block managed by malloc on decompressing a log block.

Quoted 4 lines

reftable/reftable.h line 373

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* returns the hash ID used in this table. */
+uint32_t reftable_reader_hash_id(struct reftable_reader *r);

What is a hash id?

The hash identifier, eg. "sha1" for SHA1 and "s256" for SHA-256.

Quoted 7 lines

[...]

Quoted 3 lines

+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.

Are there callers/helpers that don't know whether they want one or the other?

The setup with per-worktree refs means that there are two reftable stacks in a .git repo. In order to iterate over the entire ref space, you have to merge two merged tables, but the stack itslef merges a set of simple reftables.

Quoted 6 lines

[...]

reftable/reftable.h line 503

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* holds a transaction to add tables at the top of a stack. */
+struct reftable_addition;

When would I add multiple tables at once to the top? Is this used during compacting, for example?

No. You'd usually add only one table, but at the same time, it seemed kind of arbitrary to allow adding only one table. FWIW, I added this because libgit2 wants to be able to lock a ref database: creating the reftable_addition institutes the lock.

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#28 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

Quoted 10 lines · Han-Wen Nienhuys
Quoted 3 lines · Jonathan Nieder

Adding a header in a separate patch from the implementation doesn't match the usual practice. Can we add declarations in the same patch as the functions being declared instead?

We could, but it would considerably complicate work on this patch series, as the commit boundary then doesn't fall on file boundaries anymore. Would you be open to having multiple headers for the public interface? eg. reftable-record.h, reftable-reader.h etc. ?

I'd be more than open to it: I think that would make for a clearer API.

For example, a caller like refs.c wouldn't have any need to use reftable-block.h because that's a lower-level detail that operates behind the scenes when operating on a stack of reftables. So I think this would be a good step toward addressing the feedback Jonathan gave about the set of functions exposed by the API being overwhelming.

Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
reftable/reftable.h new file +9 -0
+     /* Misuse of the API:
+        - on writing a record with NULL refname.
+        - on writing a reftable_ref_record outside the table limits
+        - on writing a ref or log record before the stack's next_update_index
+        - on writing a log record with multiline message with
+        exact_log_message unset
+        - on reading a reftable_ref_record from log iterator, or vice versa.
+     */
+     REFTABLE_API_ERROR = -6,
Quoted 5 lines
Quoted 2 lines

Should these call BUG()? Or is it useful for some callers to be able to recover from these errors?

Since this was written as a standalone library, it's up to the caller to decide what should be done.

I'm not strongly opinionated about this, but just a quick note: this implies that the library would need to make sure it is producing a valid state in error cases.

Does the API documentation describe what state a handle is in after an error --- e.g., what operations are permitted after that?

Quoted 10 lines
Quoted 7 lines

reftable/reftable.h line 130

Quoted 5 lines
reftable/reftable.h new file +5 -0
+
+/*
+ * Convert the numeric error code to an equivalent errno code.
+ */
+int reftable_error_to_errno(int err);

What is the intended use of this function?

The read_raw_ref method in the ref backend API uses errno values as out-of-band communication mechanism.

Could we change that? It sounds error-prone.

[...]

Quoted 14 lines
Quoted 5 lines
Quoted 5 lines
reftable/reftable.h new file +5 -0
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
Quoted 8 lines

reftable/reftable.h line 221

Quoted 5 lines
reftable/reftable.h new file +5 -0
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
+ */
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+                             uint64_t max);

What happens if I write to a reftable_writer without setting limits first?

Quoted 1 line

you get an REFTABLE_API_ERROR.

(To be clear, with all of these questions, I am asking not only for my own curiosity but so that the documentation, naming, etc can be cleared up to help the next person who wonders the same thing.)

[...]

Quoted 5 lines
Quoted 2 lines

Do I pass in the 'struct block_source *' as the source arg? If so, why are these declared as void *?

you pass in block_source->arg. Should struct implementations of polymorphic types carry a pointer to their own vtable instead?

I think a pointer to block_source would make it more self-explanatory, yes.

(Aside: this is a difference between how Go and e.g. Java and C++ implement polymorphism. In Go, an interface object contains a vtable and a pointer to the object. In Java and C++, the object contains a vptr.)

Quoted 7 lines
Quoted 2 lines

Is the reason this manages the buffer instead of requiring a caller-supplied buffer to support zero-copy?

Log blocks are compressed, so the caller doesn't know the correct size to supply. By letting the block source handle the management, we can swap out the block read from the file for a block managed by malloc on decompressing a log block.

Hm, my naive assumption would have been that we'd use different buffers for the compressed and uncompressed data.

Quoted 11 lines
Quoted 6 lines
Quoted 3 lines

+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.

Are there callers/helpers that don't know whether they want one or the other?

The setup with per-worktree refs means that there are two reftable stacks in a .git repo. In order to iterate over the entire ref space, you have to merge two merged tables, but the stack itslef merges a set of simple reftables.

I see. That's subtle, so it seems worth documenting for the next person reading this documentation and wondering why.

Thanks,
Jonathan

#29 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

Quoted 6 lines · Jonathan Nieder
Quoted 4 lines

We could, but it would considerably complicate work on this patch series, as the commit boundary then doesn't fall on file boundaries anymore. Would you be open to having multiple headers for the public interface? eg. reftable-record.h, reftable-reader.h etc. ?

I'd be more than open to it: I think that would make for a clearer API.

Done.

Quoted 9 lines
Quoted 5 lines
Quoted 2 lines

Should these call BUG()? Or is it useful for some callers to be able to recover from these errors?

Since this was written as a standalone library, it's up to the caller to decide what should be done.

I'm not strongly opinionated about this, but just a quick note: this implies that the library would need to make sure it is producing a valid state in error cases.

This is what it already does. I tried to clarify this in some places.

Quoted 2 lines

Does the API documentation describe what state a handle is in after an error --- e.g., what operations are permitted after that?

API_ERROR should not result in state changes.

Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
Quoted 1 line

+int reftable_error_to_errno(int err);

What is the intended use of this function?

The read_raw_ref method in the ref backend API uses errno values as out-of-band communication mechanism.

Could we change that? It sounds error-prone.

I moved it into refs/reftable-backend.c; I'd rather not also have to clean up the files backend as part of this work, though.

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Do I pass in the 'struct block_source *' as the source arg? If so, why are these declared as void *?

you pass in block_source->arg. Should struct implementations of polymorphic types carry a pointer to their own vtable instead?

I think a pointer to block_source would make it more self-explanatory, yes.

This has a number of downsides, though. It means I have to introduce separate structs strbuf_blocksource and file_blocksource, along with functions to create and deallocate them, so it makes the whole thing more unwieldy to use.

Quoted 10 lines
Quoted 7 lines
Quoted 2 lines

Is the reason this manages the buffer instead of requiring a caller-supplied buffer to support zero-copy?

Log blocks are compressed, so the caller doesn't know the correct size to supply. By letting the block source handle the management, we can swap out the block read from the file for a block managed by malloc on decompressing a log block.

Hm, my naive assumption would have been that we'd use different buffers for the compressed and uncompressed data.

Maybe you can comment on how to solve this differently when we get to the implementation of block reading/writing in the commit series.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
Quoted 3 lines

+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.

Are there callers/helpers that don't know whether they want one or the other?

Quoted 7 lines
Quoted 4 lines

The setup with per-worktree refs means that there are two reftable stacks in a .git repo. In order to iterate over the entire ref space, you have to merge two merged tables, but the stack itslef merges a set of simple reftables.

I see. That's subtle, so it seems worth documenting for the next person reading this documentation and wondering why.

Done.

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#30 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

In reply to Han-Wen Nienhuys via GitGitGadget

My initial impression is that this public API is much larger than I would expect from reading the reftable spec. Let me look at it in detail...

Quoted 6 lines
reftable/reftable.h new file +6 -0
+/****************************************************************
+ Basic data types
+
+ Reftables store the state of each ref in struct reftable_ref_record, and they
+ store a sequence of reflog updates in struct reftable_log_record.
+ ****************************************************************/

reftable/reftable.h line 33

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+/* reftable_ref_record holds a ref database entry target_value */
+struct reftable_ref_record {
+	char *refname; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	uint8_t *value; /* SHA1, or NULL. malloced. */
+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};

A typical user would just want to do refname->hash, refname->log, or possibly hash->refname lookups, so a ref record would be meant for low-level users. I can't think of a typical use case, but even if such existed, I wouldn't expect this API - in particular, the refname is not written as such on disk (it is composed from a prefix and a suffix, if I remember correctly), so I would expect a function that one can call to write the refname, but not for it to appear in a data structure.

reftable/reftable.h line 36

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* returns whether 'ref' represents a deletion */
+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);

This looks unorthogonal - looking at the spec, the "value type" can represent a deletion, one object name (value of the ref), two object names (value of the ref + peeled target), or symbolic reference. If we're willing to allocate memory for a ref record, I think we can afford the extra 4 bytes (maybe 8) and use a tagged union instead.

reftable/reftable.h line 40

Quoted 3 lines
reftable/reftable.h new file +3 -0
+/* prints a reftable_ref_record onto stdout */
+void reftable_ref_record_print(struct reftable_ref_record *ref,
+			       uint32_t hash_id);

Not sure if this belongs here.

reftable/reftable.h line 43

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* frees and nulls all pointer values. */
+void reftable_ref_record_clear(struct reftable_ref_record *ref);

Usually we call this "release" in Git.

reftable/reftable.h line 47

Quoted 3 lines
reftable/reftable.h new file +3 -0
+/* returns whether two reftable_ref_records are the same */
+int reftable_ref_record_equal(struct reftable_ref_record *a,
+			      struct reftable_ref_record *b, int hash_size);

Not sure what this will be used for.

reftable/reftable.h line 61

Quoted 13 lines
reftable/reftable.h new file +13 -0
+/* reftable_log_record holds a reflog entry */
+struct reftable_log_record {
+	char *refname;
+	uint64_t update_index; /* logical timestamp of a transactional update.
+				*/
+	uint8_t *new_hash;
+	uint8_t *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};

Mostly same comments as above.

[snip]

reftable/reftable.h line 81

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/****************************************************************
+ Error handling
+
+ Error are signaled with negative integer return values. 0 means success.
+ ****************************************************************/

I didn't look at this section deeply, but this looks OK at first glance.

reftable/reftable.h line 136

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/****************************************************************
+ Writing
+
+ Writing single reftables
+ ****************************************************************/

OK - writing a single reftable makes sense.

Quoted 18 lines
reftable/reftable.h new file +18 -0
+/* reftable_write_options sets options for writing a single reftable. */
+struct reftable_write_options {
+	/* boolean: do not pad out blocks to block size. */
+	int unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* boolean: do not generate a SHA1 => ref index. */
+	int skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+
+	/* 4-byte identifier ("sha1", "s256") of the hash.
+	 * Defaults to SHA1 if unset
+	 */
+	uint32_t hash_id;

reftable/reftable.h line 165

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+	/* boolean: do not check ref names for validity or dir/file conflicts.
+	 */
+	int skip_name_check;
+
+	/* boolean: copy log messages exactly. If unset, check that the message
+	 *   is a single line, and add '\n' if missing.
+	 */
+	int exact_log_message;
+};

I'm not sure we need so many options, but nothing jumps out to me off-hand. Some minor things - "hash_id" should probably be an enum and the booleans should be "unsigned variable_name : 1".

[snip statistics]

Statistics look useful.

reftable/reftable.h line 210

Quoted 7 lines
reftable/reftable.h new file +7 -0
+/* reftable_new_writer creates a new writer */
+struct reftable_writer *
+reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
+		    void *writer_arg, struct reftable_write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int reftable_fd_write(void *fdp, const void *data, size_t size);

Not sure if we'll need to write to things other than an fd, but if we do, this makes sense.

reftable/reftable.h line 221

Quoted 10 lines
reftable/reftable.h new file +10 -0
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
+ */
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+				uint64_t max);

This seems to be here because we want to write the file in a single pass, and the update index maximum and minimum appear in the header. If we were allowed to seek while writing, could the update index maximum and minimum be deduced instead? That might be a compelling reason to only support FDs, and require that the FDs be seekable.

reftable/reftable.h line 235

Quoted 13 lines
reftable/reftable.h new file +13 -0
+/* adds a reftable_ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned.
+
+   It is an error to write a ref record after a log record.
+ */
+int reftable_writer_add_ref(struct reftable_writer *w,
+			    struct reftable_ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int reftable_writer_add_refs(struct reftable_writer *w,
+			     struct reftable_ref_record *refs, int n);

Since refs is an array of objects and not an array of pointers, these two functions could be combined - the first is the same as calling the second with n=1.

Also, ascending order of what?

The user will also need to know where to get the update index from - I presume that this will be the maximum update index of any record with the given refname + 1.

[snip similar functions for log]

reftable/reftable.h line 259

Quoted 12 lines
reftable/reftable.h new file +12 -0
+/* reftable_writer_close finalizes the reftable. The writer is retained so
+ * statistics can be inspected. */
+int reftable_writer_close(struct reftable_writer *w);
+
+/* writer_stats returns the statistics on the reftable being written.
+
+   This struct becomes invalid when the writer is freed.
+ */
+const struct reftable_stats *writer_stats(struct reftable_writer *w);
+
+/* reftable_writer_free deallocates memory for the writer */
+void reftable_writer_free(struct reftable_writer *w);

OK.

Quoted 3 lines

+/****************************************************************
+ * ITERATING
+ ****************************************************************/

[snip]

reftable/reftable.h line 283

Quoted 11 lines
reftable/reftable.h new file +11 -0
+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_ref(struct reftable_iterator *it,
+			       struct reftable_ref_record *ref);
+
+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_log(struct reftable_iterator *it,
+			       struct reftable_log_record *log);

From my recollection, in Git, we typically use foreach functions with a callback that is invoked once for each result. I think that's preferable to this approach.

reftable/reftable.h line 294

Quoted 7 lines
reftable/reftable.h new file +7 -0
+/****************************************************************
+ Reading single tables
+
+ The follow routines are for reading single files. For an application-level
+ interface, skip ahead to struct reftable_merged_table and struct
+ reftable_stack.
+ ****************************************************************/

I think that this whole section could be skipped - if we really wanted to read a single table, we could just use the merged-table interface with one table.

[snip]

reftable/reftable.h line 410

Quoted 12 lines
reftable/reftable.h new file +12 -0
+/****************************************************************
+ Merged tables
+
+ A ref database kept in a sequence of table files. The merged_table presents a
+ unified view to reading (seeking, iterating) a sequence of immutable tables.
+ ****************************************************************/
+
+/* A merged table is implements seeking/iterating over a stack of tables. */
+struct reftable_merged_table;
+
+/* A generic reftable; see below. */
+struct reftable_table;

Why would we need to see an individual reftable? Could we just represent a merged table as a virtual concatenation of blocks (as if all the blocks were in the same file)?

reftable/reftable.h line 417

Quoted 6 lines
reftable/reftable.h new file +6 -0
+/* reftable_new_merged_table creates a new merged table. It takes ownership of
+   the stack array.
+*/
+int reftable_new_merged_table(struct reftable_merged_table **dest,
+			      struct reftable_table *stack, int n,
+			      uint32_t hash_id);

I presume this would be used for things like compacting a few reftables together? In which case, I would expect this function to just take a list of filenames.

Quoted 4 lines
reftable/reftable.h new file +4 -0
+/* returns an iterator positioned just before 'name' */
+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
+				   struct reftable_iterator *it,
+				   const char *name);

reftable/reftable.h line 432

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+/* returns an iterator for log entry, at given update_index */
+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
+				      struct reftable_iterator *it,
+				      const char *name, uint64_t update_index);
+
+/* like reftable_merged_table_seek_log_at but look for the newest entry. */
+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
+				   struct reftable_iterator *it,
+				   const char *name);

Why do iterators need to be precisely positioned if this merged table is immutable?

reftable/reftable.h line 446

Quoted 13 lines
reftable/reftable.h new file +13 -0
+/* returns the max update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t
+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
+
+/* releases memory for the merged_table */
+void reftable_merged_table_free(struct reftable_merged_table *m);
+
+/* return the hash ID of the merged table. */
+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);

OK - makes sense.

reftable/reftable.h line 452

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/****************************************************************
+ Generic tables
+
+ A unified API for reading tables, either merged tables, or single readers.
+ ****************************************************************/

Again, we could just use the merged table API.

[snip]

reftable/reftable.h line 481

Quoted 4 lines
reftable/reftable.h new file +4 -0
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_table_read_ref(struct reftable_table *tab, const char *name,
+			    struct reftable_ref_record *ref);

I presume this returns the most up-to-date record in the (possibly merged) table? So we can just read the hash off the record to know what this ref points to.

reftable/reftable.h line 487

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/****************************************************************
+ Mutable ref database
+
+ The stack presents an interface to a mutable sequence of reftables.
+ ****************************************************************/

So I would expect ref mutations to be done by opening the existing reftables as a merged table, open a reftable writer, write all the changes that need to be written, and update the tables.list file...

reftable/reftable.h line 500

Quoted 12 lines
reftable/reftable.h new file +12 -0
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct reftable_stack;
+
+/* open a new reftable stack. The tables along with the table list will be
+   stored in 'dir'. Typically, this should be .git/reftables.
+*/
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
+		       struct reftable_write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);

...so I don't see why we need a stack.

Quoted 12 lines
reftable/reftable.h new file +12 -0
+/* holds a transaction to add tables at the top of a stack. */
+struct reftable_addition;
+
+/*
+  returns a new transaction to add reftables to the given stack. As a side
+  effect, the ref database is locked.
+*/
+int reftable_stack_new_addition(struct reftable_addition **dest,
+				struct reftable_stack *st);
+
+/* Adds a reftable to transaction. */
+int reftable_addition_add(struct reftable_addition *add,

reftable/reftable.h line 523

Quoted 10 lines
reftable/reftable.h new file +10 -0
+			  int (*write_table)(struct reftable_writer *wr,
+					     void *arg),
+			  void *arg);
+
+/* Commits the transaction, releasing the lock. */
+int reftable_addition_commit(struct reftable_addition *add);
+
+/* Release all non-committed data from the transaction, and deallocate the
+   transaction. Releases the lock if held. */
+void reftable_addition_destroy(struct reftable_addition *add);

We do need a transaction to write the new tables.list and then atomically update the repository with it, but I don't think we need one just to add a reftable to the stack.

[snip rest of stack functions]

reftable/reftable.h line 557

Quoted 13 lines
reftable/reftable.h new file +13 -0
+/* Policy for expiring reflog entries. */
+struct reftable_log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int reftable_stack_compact_all(struct reftable_stack *st,
+			       struct reftable_log_expiry_config *config);

Ah, the stack is used for compacting as well. I don't think compacting belongs here though - it should be its own thing that can make use of the merged-table reader and the single-table writer.

[snip]

reftable/reftable.h line 570

Quoted 9 lines
reftable/reftable.h new file +9 -0
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
+			    struct reftable_ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
+			    struct reftable_log_record *log);

Other things that belong to the merged table reader instead of the stack.

Some things that I think are missing:

- foreach all latest record of all refs (in any order) (I see some functions above that can set the position of the iterator, but I don't think that the iterator skips over irrelevant refs? So we can't use it for iterating over all refs if we don't care about the history of refs)
- foreach all log entries of one ref (or all refs)

In summary, I would have expected a mechanism to read multiple (possibly one) reftable and perform queries on it, a mechanism to write a single reftable, and some functions to update tables.list.

#31 lore raw

Re: [PATCH v2 02/13] reftable: define the public API

Hi Jonathan,

thanks for a detailed look at the API. I'm replying to some queries here, but I won't have time for another 2 weeks to revisit the code.

Quoted 2 lines · Jonathan Tan

A typical user would just want to do refname->hash, refname->log, or possibly hash->refname lookups, so a ref record would be meant for low-level

You are forgetting refname -> symref target and refname -> peeled tag.

Quoted 5 lines

users. I can't think of a typical use case, but even if such existed, I wouldn't expect this API - in particular, the refname is not written as such on disk (it is composed from a prefix and a suffix, if I remember correctly), so I would expect a function that one can call to write the refname, but not for it to appear in a data structure.

It works with records, because it is more practical for the low level code, but also because it obviates defining many functions for the different types of lookups and seeks.

Quoted 8 lines

reftable/reftable.h line 36

Quoted 2 lines
reftable/reftable.h new file +2 -0
+/* returns whether 'ref' represents a deletion */
+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);

This looks unorthogonal - looking at the spec, the "value type" can represent a deletion, one object name (value of the ref), two object names (value of the ref + peeled target), or symbolic reference. If we're willing to allocate memory for a ref record, I think we can afford the extra 4 bytes (maybe 8) and use a tagged union instead.

I guess you want something like

  struct {
     enum TYPE  { REF_DEL, REF_1VAL, REF_2VAL, REF_SYM } record_type;
     char *ref_name;
     uint64_t update_index;
     union  {
          uint_8 *val1;
          struct { uint8_t *val1, *val2; } val2;
          char *sym;
     };
  };

I suspect it makes initialization more verbose, but yes, I could do that.

Quoted 5 lines

reftable/reftable.h line 47

Quoted 3 lines
reftable/reftable.h new file +3 -0
+/* returns whether two reftable_ref_records are the same */
+int reftable_ref_record_equal(struct reftable_ref_record *a,
+                           struct reftable_ref_record *b, int hash_size);

Not sure what this will be used for.

This is useful for testing.

Quoted 7 lines
Quoted 7 lines
reftable/reftable.h new file +7 -0
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
+
+   For transactional updates, typically min==max. When converting an existing
+   ref database into a single reftable, this would be a range of update-index
+   timestamps.
Quoted 8 lines

reftable/reftable.h line 221

Quoted 3 lines

+ */

reftable/reftable.h new file +2 -0
+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
+                             uint64_t max);

This seems to be here because we want to write the file in a single pass, and the update index maximum and minimum appear in the header. If we were allowed to seek while writing, could the update index maximum and minimum be deduced instead?

No. The ref record update_index is delta encoded against the min update_index, so you have to know it upfront.

If I could redesign the format, I'd leave out the max update_index from the header (because it can be determined afterwards.), but it's too late now.

Quoted 19 lines
Quoted 4 lines
reftable/reftable.h new file +3 -0
+/* adds a reftable_ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned.

reftable/reftable.h line 235

Quoted 10 lines
reftable/reftable.h new file +10 -0
+
+   It is an error to write a ref record after a log record.
+ */
+int reftable_writer_add_ref(struct reftable_writer *w,
+                         struct reftable_ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int reftable_writer_add_refs(struct reftable_writer *w,
+                          struct reftable_ref_record *refs, int n);

Since refs is an array of objects and not an array of pointers, these two functions could be combined - the first is the same as calling the second with n=1.

Quoted 1 line

Also, ascending order of what?

Of record keys, ie. refname in case of ref records.

Quoted 3 lines

The user will also need to know where to get the update index from - I presume that this will be the maximum update index of any record with the given refname + 1.

there is a reftable_stack_next_update_index() for that.

Quoted 7 lines
Quoted 7 lines
reftable/reftable.h new file +7 -0
+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_ref(struct reftable_iterator *it,
+                            struct reftable_ref_record *ref);
+
+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
Quoted 8 lines

reftable/reftable.h line 283

Quoted 4 lines
reftable/reftable.h new file +4 -0
+   end of iteration.
+*/
+int reftable_iterator_next_log(struct reftable_iterator *it,
+                            struct reftable_log_record *log);

From my recollection, in Git, we typically use foreach functions with a callback that is invoked once for each result. I think that's preferable to this approach.

The iterator interface has the advantage that it captures reading both individual entries (read_raw_ref) and iteration. It's also a natural structure, because internally the iteration state has to be stored (iterating merged tables stores the iterators in a priority queue.)

Quoted 16 lines

[snip]

reftable/reftable.h line 410

Quoted 12 lines
reftable/reftable.h new file +12 -0
+/****************************************************************
+ Merged tables
+
+ A ref database kept in a sequence of table files. The merged_table presents a
+ unified view to reading (seeking, iterating) a sequence of immutable tables.
+ ****************************************************************/
+
+/* A merged table is implements seeking/iterating over a stack of tables. */
+struct reftable_merged_table;
+
+/* A generic reftable; see below. */
+struct reftable_table;

Why would we need to see an individual reftable?

It could be useful diagnostics and troubleshooting. For example, if you are debugging or trying to troubleshoot/fsck a repo, it might be interesting to read a single file from .git/reftable/

Philosophically, since we have to expose the functionality to write a single table, it seems reasonable to read single tables for symmetry as well.

Quoted 3 lines

Could we just represent
a merged table as a virtual concatenation of blocks (as if all the blocks were in the same file)?

No. The format doesn't work like that.

Quoted 10 lines

reftable/reftable.h line 417

Quoted 6 lines
reftable/reftable.h new file +6 -0
+/* reftable_new_merged_table creates a new merged table. It takes ownership of
+   the stack array.
+*/
+int reftable_new_merged_table(struct reftable_merged_table **dest,
+                           struct reftable_table *stack, int n,
+                           uint32_t hash_id);

I presume this would be used for things like compacting a few reftables together? In which case, I would expect this function to just take a list of filenames.

The merging of tables has nothing to do with the filesystem, and could be done with reftables represented as in memory structure. This is also how the unittests work.

Quoted 9 lines
Quoted 9 lines
reftable/reftable.h new file +9 -0
+/* returns an iterator positioned just before 'name' */
+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
+                                struct reftable_iterator *it,
+                                const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
+                                   struct reftable_iterator *it,
+                                   const char *name, uint64_t update_index);
Quoted 8 lines

reftable/reftable.h line 432

Quoted 5 lines
reftable/reftable.h new file +5 -0
+
+/* like reftable_merged_table_seek_log_at but look for the newest entry. */
+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
+                                struct reftable_iterator *it,
+                                const char *name);

Why do iterators need to be precisely positioned if this merged table is immutable?

I don't understand the question. How would you read a single ref from a merged table without a seek function?

Quoted 8 lines

reftable/reftable.h line 481

Quoted 4 lines
reftable/reftable.h new file +4 -0
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int reftable_table_read_ref(struct reftable_table *tab, const char *name,
+                         struct reftable_ref_record *ref);

I presume this returns the most up-to-date record in the (possibly merged) table? So we can just read the hash off the record to know what this ref points to.

correct.

Quoted 9 lines

reftable/reftable.h line 487

Quoted 5 lines
reftable/reftable.h new file +5 -0
+/****************************************************************
+ Mutable ref database
+
+ The stack presents an interface to a mutable sequence of reftables.
+ ****************************************************************/

So I would expect ref mutations to be done by opening the existing reftables as a merged table, open a reftable writer, write all the changes that need to be written, and update the tables.list file...

Roughly, yes, but there are other considerations: you have to check for concurrent updates (by the time you finish reading tables.list, a compaction may have deleted some of the tables referenced, so you have to retry). If there are any failures, the transaction fails and you have to clean up already written tables. If the write succeeds, but the stack becomes unbalanced, you have to do a compaction. If you have to reload data, you want to avoid closing and opening the same file.

If you are curious what the stack does, have a look at stack.c.

Also, this is for separation of logic. The merged table is composed of reftables that have no particular type of storage, but support the read interface. The stack is what ties the merged table to a posix-like file system, and provides mutations.

Quoted 18 lines
Quoted 18 lines
reftable/reftable.h new file +18 -0
+/* holds a transaction to add tables at the top of a stack. */
+struct reftable_addition;
+
+/*
+  returns a new transaction to add reftables to the given stack. As a side
+  effect, the ref database is locked.
+*/
+int reftable_stack_new_addition(struct reftable_addition **dest,
+                             struct reftable_stack *st);
+
+/* Adds a reftable to transaction. */
+int reftable_addition_add(struct reftable_addition *add,
+                       int (*write_table)(struct reftable_writer *wr,
+                                          void *arg),
+                       void *arg);
+
+/* Commits the transaction, releasing the lock. */
+int reftable_addition_commit(struct reftable_addition *add);
Quoted 8 lines

reftable/reftable.h line 523

Quoted 4 lines
reftable/reftable.h new file +4 -0
+
+/* Release all non-committed data from the transaction, and deallocate the
+   transaction. Releases the lock if held. */
+void reftable_addition_destroy(struct reftable_addition *add);

We do need a transaction to write the new tables.list and then atomically update the repository with it, but I don't think we need one just to add a reftable to the stack.

see above.

Quoted 8 lines

reftable/reftable.h line 557

Quoted 4 lines
reftable/reftable.h new file +4 -0
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int reftable_stack_compact_all(struct reftable_stack *st,
+                            struct reftable_log_expiry_config *config);

Ah, the stack is used for compacting as well. I don't think compacting belongs here though - it should be its own thing that can make use of the merged-table reader and the single-table writer.

Why should it be like that, and where should compaction live?

Quoted 3 lines

Some things that I think are missing:

- foreach all latest record of all refs (in any order) (I see some

Ref records are keyed by name, so the iteration produces each ref only once.

Quoted 2 lines

functions above that can set the position of the iterator, but I don't think that the iterator skips over irrelevant refs?

What makes you think that?

Quoted 3 lines

So we can't
use it for iterating over all refs if we don't care about the history of refs)

I think you might be confusing the refs with reflogs here. The history of a ref is kept in the reflog. This is why functions that want to seek to a log record need an update_index timestamp.

Quoted 1 line

- foreach all log entries of one ref (or all refs)

Before reviewing the API header here, I think it might be useful to also look at the commit in the other series that uses the API to implement the git ref backend. It shows how the foreach functions can be implemented using the iterator interface.

Quoted 3 lines

In summary, I would have expected a mechanism to read multiple (possibly one) reftable and perform queries on it, a mechanism to write a single reftable, and some functions to update tables.list.

I understand your expectation, but I think that expectation glosses over important details of how it actually works.

Signature · 7 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--
Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#32 lore raw

PATCH v2 3/13 vcxproj: adjust for the reftable changes

In reply to Han-Wen Nienhuys via GitGitGadget

From: Johannes Schindelin <[email protected]>

This allows Git to be compiled via Visual Studio again after integrating the hn/reftable branch.

---
 config.mak.uname                           |  2 +-
 contrib/buildsystems/Generators/Vcxproj.pm | 11 ++++++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)
config.mak.uname +1 -1
index c7eba69e54..ae4e25a1a4 100644
@@ -709,7 +709,7 @@ vcxproj:
 	# Make .vcxproj files and add them
 	unset QUIET_GEN QUIET_BUILT_IN; \
 	perl contrib/buildsystems/generate -g Vcxproj
-	git add -f git.sln {*,*/lib,t/helper/*}/*.vcxproj
+	git add -f git.sln {*,*/lib,*/libreftable,t/helper/*}/*.vcxproj
 
 	# Generate the LinkOrCopyBuiltins.targets and LinkOrCopyRemoteHttp.targets file
 	(echo '<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">' && \
contrib/buildsystems/Generators/Vcxproj.pm +10 -1
index d2584450ba..1a25789d28 100644
@@ -77,7 +77,7 @@ sub createProject {
     my $libs_release = "\n    ";
     my $libs_debug = "\n    ";
     if (!$static_library) {
-      $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
+      $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
       $libs_debug = $libs_release;
       $libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
       $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g;
@@ -232,6 +232,7 @@ sub createProject {
 EOM
     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
       my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
+      my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"};
       my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};
 
       print F << "EOM";
@@ -241,6 +242,14 @@ sub createProject {
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
 EOM
+      if (!($name =~ /xdiff|libreftable/)) {
+        print F << "EOM";
+    <ProjectReference Include="$cdup\\reftable\\libreftable\\libreftable.vcxproj">
+      <Project>$uuid_libreftable</Project>
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+    </ProjectReference>
+EOM
+      }
       if (!($name =~ 'xdiff')) {
         print F << "EOM";
     <ProjectReference Include="$cdup\\xdiff\\lib\\xdiff_lib.vcxproj">
Signature · 1 line
gitgitgadget
#33 lore raw

Re: [PATCH v2 03/13] vcxproj: adjust for the reftable changes

Quoted 4 lines · Han-Wen Nienhuys

From: Johannes Schindelin <[email protected]>

This allows Git to be compiled via Visual Studio again after integrating the hn/reftable branch.

nit: This branch name is no longer meaningful to the primary audience for the commit message (people discovering this commit in git history later).

Quoted 5 lines
---
 config.mak.uname                           |  2 +-
 contrib/buildsystems/Generators/Vcxproj.pm | 11 ++++++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)

Can this be squashed into or put immediately after patch 5 which introduces the Makefile?

Thanks,
Jonathan

#34 lore raw

Re: [PATCH v2 03/13] vcxproj: adjust for the reftable changes

Hi Jonathan,

Quoted 11 lines · Jonathan Nieder
Quoted 4 lines · Han-Wen Nienhuys

From: Johannes Schindelin <[email protected]>

This allows Git to be compiled via Visual Studio again after integrating the hn/reftable branch.

nit: This branch name is no longer meaningful to the primary audience for the commit message (people discovering this commit in git history later).

Quoted 8 lines
Quoted 5 lines
---
 config.mak.uname                           |  2 +-
 contrib/buildsystems/Generators/Vcxproj.pm | 11 ++++++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)

Can this be squashed into or put immediately after patch 5 which introduces the Makefile?

I never intended this to be a stand-alone patch. So yes, I would be very much in favor of squashing these changes into the appropriate commit.

Ciao,
Dscho

#35 lore raw

PATCH v2 4/13 reftable: add a barebones unittest framework

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 reftable/test_framework.c | 68 +++++++++++++++++++++++++++++++++++++++
 reftable/test_framework.h | 59 +++++++++++++++++++++++++++++++++
 2 files changed, 127 insertions(+)
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
reftable/test_framework.c new file +68 -0
new file mode 100644
index 0000000000..8d718f2f06
@@ -0,0 +1,68 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test_framework.h"
+
+#include "system.h"
+#include "basics.h"
+
+static struct test_case **test_cases;
+static int test_case_len;
+static int test_case_cap;
+
+static struct test_case *new_test_case(const char *name, void (*testfunc)(void))
+{
+	struct test_case *tc = reftable_malloc(sizeof(struct test_case));
+	tc->name = name;
+	tc->testfunc = testfunc;
+	return tc;
+}
+
+struct test_case *add_test_case(const char *name, void (*testfunc)(void))
+{
+	struct test_case *tc = new_test_case(name, testfunc);
+	if (test_case_len == test_case_cap) {
+		test_case_cap = 2 * test_case_cap + 1;
+		test_cases = reftable_realloc(
+			test_cases, sizeof(struct test_case) * test_case_cap);
+	}
+
+	test_cases[test_case_len++] = tc;
+	return tc;
+}
+
+int test_main(int argc, const char *argv[])
+{
+	const char *filter = NULL;
+	int i = 0;
+	if (argc > 1) {
+		filter = argv[1];
+	}
+
+	for (i = 0; i < test_case_len; i++) {
+		const char *name = test_cases[i]->name;
+		if (filter == NULL || strstr(name, filter) != NULL) {
+			printf("case %s\n", name);
+			test_cases[i]->testfunc();
+		} else {
+			printf("skip %s\n", name);
+		}
+
+		reftable_free(test_cases[i]);
+	}
+	reftable_free(test_cases);
+	test_cases = NULL;
+	test_case_len = 0;
+	test_case_cap = 0;
+	return 0;
+}
+
+void set_test_hash(uint8_t *p, int i)
+{
+	memset(p, (uint8_t)i, hash_size(SHA1_ID));
+}
reftable/test_framework.h new file +59 -0

59 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#36 lore raw

Re: [PATCH v2 04/13] reftable: add a barebones unittest framework

Hi,

Quoted 7 lines · Han-Wen Nienhuys
---
 reftable/test_framework.c | 68 +++++++++++++++++++++++++++++++++++++++
 reftable/test_framework.h | 59 +++++++++++++++++++++++++++++++++
 2 files changed, 127 insertions(+)
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h

Hm, can the commit message say a little about the motivation for this test framework and what it allows?

For example, does it allow unit tests to write output in TAP format? How does it compare to existing frameworks like
https://www.eyrie.org/~eagle/software/c-tap-harness/? How does this approach compare to what Git's existing unit-style tests with helpers in t/helper/ driven by scripts in t/ do?

Thanks,
Jonathan

#37 lore raw

Re: [PATCH v2 04/13] reftable: add a barebones unittest framework

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 6 lines

From: Han-Wen Nienhuys <[email protected]>

---
 reftable/test_framework.c | 68 +++++++++++++++++++++++++++++++++++++++
 reftable/test_framework.h | 59 +++++++++++++++++++++++++++++++++

Even if reftable will be a project maintained by the Git project but independent enough to be used by other projects, I don't think it's worth creating a separate test framework. E.g. as far as I can tell, when we import sha1dc, we don't import any tests, so I don't think they need to import tests either - they can run the tests in the Git repo themselves if they need to.

#38 lore raw

Re: [PATCH v2 04/13] reftable: add a barebones unittest framework

Quoted 7 lines · Jonathan Tan
Quoted 6 lines

From: Han-Wen Nienhuys <[email protected]>

---
 reftable/test_framework.c | 68 +++++++++++++++++++++++++++++++++++++++
 reftable/test_framework.h | 59 +++++++++++++++++++++++++++++++++
Quoted 6 lines

Even if reftable will be a project maintained by the Git project but independent enough to be used by other projects, I don't think it's worth creating a separate test framework. E.g. as far as I can tell, when we import sha1dc, we don't import any tests, so I don't think they need to import tests either - they can run the tests in the Git repo themselves if they need to.

I agree with Jonathan here, I think we should stick with the existing test framework.

#39 lore raw

PATCH v2 5/13 reftable: utility functions

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

---
 Makefile                  |  26 ++++++-
 reftable/basics.c         | 131 +++++++++++++++++++++++++++++++++
 reftable/basics.h         |  48 +++++++++++++
 reftable/blocksource.c    | 148 ++++++++++++++++++++++++++++++++++++++
 reftable/blocksource.h    |  22 ++++++
 reftable/compat.c         | 110 ++++++++++++++++++++++++++++
 reftable/compat.h         |  48 +++++++++++++
 reftable/publicbasics.c   | 100 ++++++++++++++++++++++++++
 reftable/reftable-tests.h |  22 ++++++
 reftable/strbuf.c         | 142 ++++++++++++++++++++++++++++++++++++
 reftable/strbuf.h         |  80 +++++++++++++++++++++
 reftable/strbuf_test.c    |  37 ++++++++++
 reftable/system.h         |  51 +++++++++++++
 t/helper/test-reftable.c  |   8 +++
 t/helper/test-tool.c      |   1 +
 t/helper/test-tool.h      |   1 +
 16 files changed, 972 insertions(+), 3 deletions(-)
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/blocksource.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/compat.c
 create mode 100644 reftable/compat.h
 create mode 100644 reftable/publicbasics.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/strbuf.c
 create mode 100644 reftable/strbuf.h
 create mode 100644 reftable/strbuf_test.c
 create mode 100644 reftable/system.h
 create mode 100644 t/helper/test-reftable.c
Makefile +23 -3
index de53954590..e40d55cd87 100644
@@ -727,6 +727,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o
 TEST_BUILTINS_OBJS += test-read-graph.o
 TEST_BUILTINS_OBJS += test-read-midx.o
 TEST_BUILTINS_OBJS += test-ref-store.o
+TEST_BUILTINS_OBJS += test-reftable.o
 TEST_BUILTINS_OBJS += test-regex.o
 TEST_BUILTINS_OBJS += test-repository.o
 TEST_BUILTINS_OBJS += test-revision-walking.o
@@ -806,6 +807,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
+REFTABLE_LIB = reftable/libreftable.a
+REFTABLE_TEST_LIB = reftable/libreftable_test.a
 
 GENERATED_H += config-list.h
 GENERATED_H += command-list.h
@@ -1167,7 +1170,7 @@ THIRD_PARTY_SOURCES += compat/regex/%
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2369,10 +2372,21 @@ XDIFF_OBJS += xdiff/xpatience.o
 XDIFF_OBJS += xdiff/xprepare.o
 XDIFF_OBJS += xdiff/xutils.o
 
+REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/blocksource.o
+REFTABLE_OBJS += reftable/publicbasics.o
+REFTABLE_OBJS += reftable/compat.o
+REFTABLE_OBJS += reftable/strbuf.o
+
+REFTABLE_TEST_OBJS += reftable/strbuf_test.o
+REFTABLE_TEST_OBJS += reftable/test_framework.o
+
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 	$(XDIFF_OBJS) \
 	$(FUZZ_OBJS) \
+	$(REFTABLE_OBJS) \
+	$(REFTABLE_TEST_OBJS) \
 	common-main.o \
 	git.o
 ifndef NO_CURL
@@ -2524,6 +2538,12 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+$(REFTABLE_LIB): $(REFTABLE_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -2802,7 +2822,7 @@ perf: all
 
 t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: t/helper/test-tool$X
@@ -3133,7 +3153,7 @@ cocciclean:
 clean: profile-clean coverage-clean cocciclean
 	$(RM) *.res
 	$(RM) $(OBJECTS)
-	$(RM) $(LIB_FILE) $(XDIFF_LIB)
+	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
reftable/basics.c new file +131 -0

131 lines not on this page · raw message

reftable/basics.h new file +48 -0

48 lines not on this page · raw message

reftable/blocksource.c new file +148 -0

148 lines not on this page · raw message

reftable/blocksource.h new file +22 -0
new file mode 100644
index 0000000000..3faf83fa9d
@@ -0,0 +1,22 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "strbuf.h"
+
+struct reftable_block_source;
+
+/* Create an in-memory block source for reading reftables */
+void block_source_from_strbuf(struct reftable_block_source *bs,
+			      struct strbuf *buf);
+
+struct reftable_block_source malloc_block_source(void);
+
+#endif
reftable/compat.c new file +110 -0

110 lines not on this page · raw message

reftable/compat.h new file +48 -0

48 lines not on this page · raw message

reftable/publicbasics.c new file +100 -0

100 lines not on this page · raw message

reftable/reftable-tests.h new file +22 -0
new file mode 100644
index 0000000000..e38471888f
@@ -0,0 +1,22 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_TESTS_H
+#define REFTABLE_TESTS_H
+
+int block_test_main(int argc, const char **argv);
+int merged_test_main(int argc, const char **argv);
+int record_test_main(int argc, const char **argv);
+int refname_test_main(int argc, const char **argv);
+int reftable_test_main(int argc, const char **argv);
+int strbuf_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
reftable/strbuf.c new file +142 -0

142 lines not on this page · raw message

reftable/strbuf.h new file +80 -0

80 lines not on this page · raw message

reftable/strbuf_test.c new file +37 -0
new file mode 100644
index 0000000000..39f561c81a
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "strbuf.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "test_framework.h"
+#include "reftable-tests.h"
+
+static void test_strbuf(void)
+{
+	struct strbuf s = STRBUF_INIT;
+	struct strbuf t = STRBUF_INIT;
+
+	strbuf_addstr(&s, "abc");
+	assert(0 == strcmp("abc", s.buf));
+
+	strbuf_addstr(&t, "pqr");
+	strbuf_addbuf(&s, &t);
+	assert(0 == strcmp("abcpqr", s.buf));
+
+	strbuf_release(&s);
+	strbuf_release(&t);
+}
+
+int strbuf_test_main(int argc, const char *argv[])
+{
+	add_test_case("test_strbuf", &test_strbuf);
+	return test_main(argc, argv);
+}
reftable/system.h new file +51 -0

51 lines not on this page · raw message

t/helper/test-reftable.c new file +8 -0
new file mode 100644
index 0000000000..7d50aa6bcc
@@ -0,0 +1,8 @@
+#include "reftable/reftable-tests.h"
+#include "test-tool.h"
+
+int cmd__reftable(int argc, const char **argv)
+{
+	strbuf_test_main(argc, argv);
+	return 0;
+}
t/helper/test-tool.c +1 -0
index a0d3966b29..c7ab5bc4a6 100644
@@ -53,6 +53,7 @@ static struct test_cmd cmds[] = {
 	{ "read-graph", cmd__read_graph },
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
+	{ "reftable", cmd__reftable },
 	{ "regex", cmd__regex },
 	{ "repository", cmd__repository },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index 07034d3f38..fa2b11ace6 100644
@@ -42,6 +42,7 @@ int cmd__read_cache(int argc, const char **argv);
 int cmd__read_graph(int argc, const char **argv);
 int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
+int cmd__reftable(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
 int cmd__repository(int argc, const char **argv);
 int cmd__revision_walking(int argc, const char **argv);
Signature · 1 line
gitgitgadget
#40 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

reftable/basics.c line 9

Quoted 12 lines · Han-Wen Nienhuys
reftable/basics.c new file +9 -0
@@ -0,0 +1,131 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"

Git's source files start with #include-ing "git-compat-util.h"; how do we approach that here?

E.g. would we pass -include on the command line, or could we #include some kind of compat-util.h that #include-s "git-compat-util.h" in Git and does the appropriate steps for enabling feature test macros and including system headers when used outside?

[...]

reftable/basics.c line 30

Quoted 1 line
reftable/basics.c new file +1 -0
+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)

How does this compare to stdlib's bsearch?

[...]

reftable/basics.c line 71

Quoted 12 lines
reftable/basics.c new file +12 -0
+void free_names(char **a)
+{
+	char **p = a;
+	if (p == NULL) {
+		return;
+	}
+	while (*p) {
+		reftable_free(*p);
+		p++;
+	}
+	reftable_free(a);
+}

Are there other callers that need custom free?

[...]

reftable/basics.c line 82

Quoted 10 lines
reftable/basics.c new file +10 -0
+int names_length(char **names)
+{
+	int len = 0;
+	char **p = names;
+	while (*p) {
+		p++;
+		len++;
+	}
+	return len;
+}

The rest are probably easier to evaluate when I look at the callers, and it's time for me to go to sleep now. I'll try to find some time soon to pick the review back up.

Thanks for a thoughtfully put together library so far.

Jonathan

#41 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 4 lines · Jonathan Nieder

[...]

Quoted 1 line

+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)

How does this compare to stdlib's bsearch?

bsearch gives you back NULL if it doesn't find an exact match.

Quoted 4 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+     reftable_free(a);
+}

Are there other callers that need custom free?

The libgit2 folks requested the ability to set memory allocation routines, hence reftable_free().

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#42 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

(+cc: Patrick Steinhardt from libgit2)
Hi,

Han-Wen Nienhuys wrote[1]:

Quoted 10 lines · Jonathan Nieder
Quoted 6 lines

Review

Quoted 2 lines · Han-Wen Nienhuys
(quoted diff) +2 -0
+     reftable_free(a);
+}

Are there other callers that need custom free?

The libgit2 folks requested the ability to set memory allocation routines, hence reftable_free().

Thanks. Patrick or Han-Wen, can you say a little more about this use case? That would help with making sure we are making an API that meets its needs.

For example, is a custom allocator something that would be set globally or something attached to a handle? If the former, would code that uses xmalloc and free and gets #define-d away when used in libgit2 work? If the latter, what does the handle look like?

Thanks,
Jonathan

[1] context: https://lore.kernel.org/git/[email protected]/T/#r2337ddc80848c80f6c9699d904ab64d4297d2c54

#43 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 15 lines · Jonathan Nieder

(+cc: Patrick Steinhardt from libgit2)
Hi,

Han-Wen Nienhuys wrote[1]:

Quoted 10 lines · Jonathan Nieder
Quoted 6 lines

Review

Quoted 2 lines · Han-Wen Nienhuys
(quoted diff) +2 -0
+     reftable_free(a);
+}

Are there other callers that need custom free?

The libgit2 folks requested the ability to set memory allocation routines, hence reftable_free().

Quoted 8 lines

Thanks. Patrick or Han-Wen, can you say a little more about this use case? That would help with making sure we are making an API that meets its needs.

For example, is a custom allocator something that would be set globally or something attached to a handle? If the former, would code that uses xmalloc and free and gets #define-d away when used in libgit2 work? If the latter, what does the handle look like?

We have global pluggable allocators in libgit2 which can be set up before calling git_libgit2_init(). The user of libgit2 can fill a git_allocator structure with a set of funtcion pointers, most importantly with implementations of free and malloc. Those then get used across all of libgit2 for all subsequent allocations.

In order to be as thorough as possible, we thus also need to replace these function pointers for libgit2's dependencies. As registration of the allocator happens at runtime, we need to also be able to replace function pointers of dependencies at runtime. E.g. for OpenSSL, it provides an API CRYPTO_set_mem_functions(malloc, realloc, free) which we call on global initialization of the libgit2 library.

So, to answer your questions:

- The allocator is global and cannot be changed after initialization of libgit2.

- It is pluggable, users can set up their own allocators by filling a structure with function pointers for free, malloc, realloc etc.

- Due to the pluggable nature, we need to be able to set up those pointers at runtime. We can provide a set of static wrappers though which then call into the pluggable functions, so defines would probably work for us, too.

I hope that answers all of your questions.

Patrick

#44 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 16 lines · Patrick Steinhardt

In order to be as thorough as possible, we thus also need to replace these function pointers for libgit2's dependencies. As registration of the allocator happens at runtime, we need to also be able to replace function pointers of dependencies at runtime. E.g. for OpenSSL, it provides an API CRYPTO_set_mem_functions(malloc, realloc, free) which we call on global initialization of the libgit2 library.

So, to answer your questions:

- The allocator is global and cannot be changed after initialization of libgit2.

- It is pluggable, users can set up their own allocators by filling a structure with function pointers for free, malloc, realloc etc.

Quoted 6 lines

- Due to the pluggable nature, we need to be able to set up those pointers at runtime. We can provide a set of static wrappers though which then call into the pluggable functions, so defines would probably work for us, too.

I hope that answers all of your questions.

Thanks! Yes, that's very helpful.

Jonathan

#45 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

In reply to Patrick Steinhardt

Hi Patrick,

Quoted 25 lines · Patrick Steinhardt
Quoted 15 lines · Jonathan Nieder

(+cc: Patrick Steinhardt from libgit2)
Hi,

Han-Wen Nienhuys wrote[1]:

Quoted 10 lines · Jonathan Nieder
Quoted 6 lines

Review

Quoted 2 lines · Han-Wen Nienhuys
(quoted diff) +2 -0
+     reftable_free(a);
+}

Are there other callers that need custom free?

The libgit2 folks requested the ability to set memory allocation routines, hence reftable_free().

Quoted 8 lines

Thanks. Patrick or Han-Wen, can you say a little more about this use case? That would help with making sure we are making an API that meets its needs.

For example, is a custom allocator something that would be set globally or something attached to a handle? If the former, would code that uses xmalloc and free and gets #define-d away when used in libgit2 work? If the latter, what does the handle look like?

Quoted 5 lines

We have global pluggable allocators in libgit2 which can be set up before calling git_libgit2_init(). The user of libgit2 can fill a git_allocator structure with a set of funtcion pointers, most importantly with implementations of free and malloc. Those then get used across all of libgit2 for all subsequent allocations.

I did not find out how those are used in the deps/ part of libgit2's source code. For example, I see a couple of instances where malloc() is used in ntlmclient and in pcre.

The thing I was looking for would have been something like

	#define malloc(size) git__malloc(size)
	...

This would also have been what I imagined to be the best strategy to integrate the reftable code once it is properly embedded in libgit.a (and of course using libgit.a's API as much as it can).

Somewhat related: I was wondering whether it would make sense for git.git to rename strbuf to git_buf? Would that make it easier to exchange code between the two projects? Or would it just be unnecessary churn?

Ciao,
Dscho

#46 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 3 lines · Johannes Schindelin

Somewhat related: I was wondering whether it would make sense for git.git to rename strbuf to git_buf? Would that make it easier to exchange code between the two projects? Or would it just be unnecessary churn?

To us, "git_buf" is just as descriptive as "buf" and does not say anything about the nature of 'buf' (other than apparently it was invented and widely used here). "git_strbuf" I can understand, but why should we?

#47 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Junio,

Quoted 10 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin

Somewhat related: I was wondering whether it would make sense for git.git to rename strbuf to git_buf? Would that make it easier to exchange code between the two projects? Or would it just be unnecessary churn?

To us, "git_buf" is just as descriptive as "buf" and does not say anything about the nature of 'buf' (other than apparently it was invented and widely used here). "git_strbuf" I can understand, but why should we?

If it makes code sharing between git.git and libgit2 easier, why shouldn't we ;-)

Obviously, if it doesn't make life easier, we shouldn't bother.

Ciao,
Dscho

#48 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 10 lines · Johannes Schindelin

Hi Junio,

Quoted 6 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin

Somewhat related: I was wondering whether it would make sense for git.git to rename strbuf to git_buf? Would that make it easier to exchange code between the two projects? Or would it just be unnecessary churn?

Quoted 7 lines
Quoted 4 lines

To us, "git_buf" is just as descriptive as "buf" and does not say anything about the nature of 'buf' (other than apparently it was invented and widely used here). "git_strbuf" I can understand, but why should we?

If it makes code sharing between git.git and libgit2 easier, why shouldn't we ;-)

I see no reasonably explanation why libgit2 is not the one who uses "#define strbuf git_buf" to make "sharing easier", though.

#49 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Junio,

Quoted 17 lines · Junio C Hamano
Quoted 15 lines · Johannes Schindelin

Hi Junio,

Quoted 10 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin

Somewhat related: I was wondering whether it would make sense for git.git to rename strbuf to git_buf? Would that make it easier to exchange code between the two projects? Or would it just be unnecessary churn?

To us, "git_buf" is just as descriptive as "buf" and does not say anything about the nature of 'buf' (other than apparently it was invented and widely used here). "git_strbuf" I can understand, but why should we?

Quoted 5 lines
Quoted 2 lines

If it makes code sharing between git.git and libgit2 easier, why shouldn't we ;-)

I see no reasonably explanation why libgit2 is not the one who uses "#define strbuf git_buf" to make "sharing easier", though.

That's a fair point, thanks for bringing it up!

Ciao,
Dscho

#50 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

In reply to Junio C Hamano
Quoted 17 lines · Junio C Hamano
Quoted 15 lines · Johannes Schindelin

Hi Junio,

Quoted 10 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin

Somewhat related: I was wondering whether it would make sense for git.git to rename strbuf to git_buf? Would that make it easier to exchange code between the two projects? Or would it just be unnecessary churn?

To us, "git_buf" is just as descriptive as "buf" and does not say anything about the nature of 'buf' (other than apparently it was invented and widely used here). "git_strbuf" I can understand, but why should we?

Quoted 5 lines
Quoted 2 lines

If it makes code sharing between git.git and libgit2 easier, why shouldn't we ;-)

I see no reasonably explanation why libgit2 is not the one who uses "#define strbuf git_buf" to make "sharing easier", though.

It probably wouldn't help much anyway. We already have our own buffer type which we can't easily replace with yours as it's part of the public interface. If the need arises, providing a compatibility interface for it shouldn't be too hard.

Patrick

#51 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

In reply to Han-Wen Nienhuys via GitGitGadget

Hi Han-Wen,

Quoted 6 lines · Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

My position on this idea to duplicate functionality in order to somehow pretend that the reftable code is independent of Git's source code has not changed.

Be that as it may, the CI build failures impacted my notifications' signal/noise ratio too much, so here goes (Junio, I would be delighted if you could apply this on top of your branch):

-- snipsnap --
From 1698c3450b5e47927c2e8fe35cad2634963bad89 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Wed, 30 Sep 2020 16:53:53 +0200
Subject: [PATCH] fixup??? reftable: utility functions

Let's not forget our CMake configuration.

---
 contrib/buildsystems/CMakeLists.txt | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)
contrib/buildsystems/CMakeLists.txt +12 -2
index 1bd53c4ad51..284bf4402d7 100644
@@ -591,6 +591,12 @@ parse_makefile_for_sources(libxdiff_SOURCES "XDIFF_OBJS")
 list(TRANSFORM libxdiff_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
 add_library(xdiff STATIC ${libxdiff_SOURCES})
 
+#reftable
+parse_makefile_for_sources(reftable_SOURCES "REFTABLE_OBJS")
+
+list(TRANSFORM reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+add_library(reftable STATIC ${reftable_SOURCES})
+
 if(WIN32)
 	if(NOT MSVC)#use windres when compiling with gcc and clang
 		add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res
@@ -613,7 +619,7 @@ endif()
 #link all required libraries to common-main
 add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)
 
-target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})
+target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})
 if(Intl_FOUND)
 	target_link_libraries(common-main ${Intl_LIBRARIES})
 endif()
@@ -848,11 +854,15 @@ if(BUILD_TESTING)
 add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)
 target_link_libraries(test-fake-ssh common-main)
 
+#reftable-tests
+parse_makefile_for_sources(test-reftable_SOURCES "REFTABLE_TEST_OBJS")
+list(TRANSFORM test-reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+
 #test-tool
 parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
 
 list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/")
-add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})
+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})
 target_link_libraries(test-tool common-main)
 
 set_target_properties(test-fake-ssh test-tool

--
2.28.0.windows.1.18.g5300e52e185

#52 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 8 lines · Johannes Schindelin
Quoted 4 lines

This commit provides basic utility classes for the reftable library.

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

My position on this idea to duplicate functionality in order to somehow pretend that the reftable code is independent of Git's source code has not changed.

The above may be sufficient between you and Han-Wen, and a few selected others who still remember, but please do not forget that we are collaborating in the open. It would help those who are learning open source interactions by watching from sidelines, if you spelled out what your position is or at least left a pointer to a previous discussion.

FWIW, I think it is a mistake to try to make this directory so that it can be lifted out of our code base and used independently, as it has to create unnecessary friction to the code when used here. It is not like other code that we are not their primary intended audience and we simply "borrow" from them (e.g. xdiff/ & sha1dc/).

The previous paragraph agrees with my guess of your position, but perhaps you have something else in mind. I dunno.

Quoted 3 lines

Be that as it may, the CI build failures impacted my notifications' signal/noise ratio too much, so here goes (Junio, I would be delighted if you could apply this on top of your branch):

Quite honestly, this close to the first preview release for 2.29, I'd rather drop this round from 'seen' and expect an updated topic, than piling fixup on top of fixups.

Thanks.

#53 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Junio,

Quoted 11 lines · Junio C Hamano
Quoted 8 lines · Johannes Schindelin
Quoted 4 lines

This commit provides basic utility classes for the reftable library.

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

My position on this idea to duplicate functionality in order to somehow pretend that the reftable code is independent of Git's source code has not changed.

Quoted 6 lines

The above may be sufficient between you and Han-Wen, and a few selected others who still remember, but please do not forget that we are collaborating in the open. It would help those who are learning open source interactions by watching from sidelines, if you spelled out what your position is or at least left a pointer to a previous discussion.

You're right, sorry.

Quoted 8 lines

FWIW, I think it is a mistake to try to make this directory so that it can be lifted out of our code base and used independently, as it has to create unnecessary friction to the code when used here. It is not like other code that we are not their primary intended audience and we simply "borrow" from them (e.g. xdiff/ & sha1dc/).

The previous paragraph agrees with my guess of your position, but perhaps you have something else in mind. I dunno.

Yes, you were spot on.

I am rather strongly opposed to introducing duplicated functionality. Rather than spending time on finding half a dozen links to conversations in the past, let me try to make the case afresh:

I have a very concrete example of the undesirable consequences: the commit-graph breakage Stolee and I debugged yesterday, where a bug hid in in_merge_bases_many() for two years, hidden by the fact that there was _duplicate logic_ in get_reachable_subset().

Pretty much _all_ of the code in reftable/ is _very_ new, has not seen any real-world testing, and as such cannot really be trusted, in particular the code that duplicates functionality already present in libgit.a. Note that I do not claim that Han-Wen cannot be trusted: to the contrary, I trust him very much (we have a history going all the way back to Lilypond, the musical typesetter). The code, however, cannot be trusted, and needs to be reviewed and tested in practice.

The simple fact is that even the thorough review that the commit-graph patches received did not prevent the min_generation/max_generation bug in in_merge_bases_many() from entering the code base, and if we had not had _another_ function doing very, very similar things, that bug would most likely not have lingered for _this_ long.

Likewise for the reimplementation of the convenient functionality like strbuf. This kind of duplication (in the case of struct strbuf, quite _literally_: there are now confusingly _two_ strbuf.h files that even try to implement the _exact same_ API) is _prone_ to lead to stale, or long undiscovered, bugs in one of the duplicated implementations.

Which means that we're likely to introduce bugs with those new functions introduced in reftable/. For the reftable functionality, that has to be accepted. For functions that do the same as equivalents in libgit.a, we do not have to accept it.

In any case, duplicated functionality is a maintenance burden, and especially in this case, an unnecessary one at that: unlike xdiff or compat/regex/, the reftable functionality is co-dependent with libgit.a. Yes, you can implement libreftable as a stand-alone library (including duplicated functionality from libgit.a, as I pointed out), but you _cannot use it independently of Git_.

The reftable concerns _Git_ refs.

This is not a compressor that could come in handy in a messenger app, or a diff engine that could potentially help with a semantic merging tool.

It is code whose entire purpose is to manage boatloads of Git refs efficiently. Therefore, I see no convincing reason to insist on maintaining this code outside of git/git.

It would make more sense to maintain parse-options or strbuf or t/test-lib.sh outside of git/git (because at least that functionality is independent enough of Git to be potentially useful to other projects) and we don't. We maintain them inside git/git, and for good reason: it makes development and maintenance a hell of a lot easier.

And once we agree that reftable is intended to be so integral a part of Git that it should absolutely have its home inside git/git, all of that duplicated functionality (with almost totally untested implementations, at least when it comes to "battle testing in production") can totally evaporate and does not need to worry us any more.

And then we can start to spend reviewers' time on the actual code revolving around the actual reftables rather than having to bother with reviewing, say, a close, but non-identical strbuf that might even have "borrowed" parts of the strbuf code and put it under a more permissive license (I don't know, and I don't want to know, that's part of the reason why I don't want to review the reftable patches in their current form).

In other words, we can then finally start to actually review the reftable code on its merits of teaching Git a valuable new feature.

Quoted 7 lines
Quoted 3 lines

Be that as it may, the CI build failures impacted my notifications' signal/noise ratio too much, so here goes (Junio, I would be delighted if you could apply this on top of your branch):

Quite honestly, this close to the first preview release for 2.29, I'd rather drop this round from 'seen' and expect an updated topic, than piling fixup on top of fixups.

Sounds good to me.

Ciao,
Dscho

#54 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 17 lines

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

---
 Makefile                  |  26 ++++++-
 reftable/basics.c         | 131 +++++++++++++++++++++++++++++++++
 reftable/basics.h         |  48 +++++++++++++
 reftable/blocksource.c    | 148 ++++++++++++++++++++++++++++++++++++++
 reftable/blocksource.h    |  22 ++++++
 reftable/compat.c         | 110 ++++++++++++++++++++++++++++
 reftable/compat.h         |  48 +++++++++++++
 reftable/publicbasics.c   | 100 ++++++++++++++++++++++++++
Quoted 8 lines
 reftable/reftable-tests.h |  22 ++++++
 reftable/strbuf.c         | 142 ++++++++++++++++++++++++++++++++++++
 reftable/strbuf.h         |  80 +++++++++++++++++++++
 reftable/strbuf_test.c    |  37 ++++++++++
 reftable/system.h         |  51 +++++++++++++
 t/helper/test-reftable.c  |   8 +++
 t/helper/test-tool.c      |   1 +
 t/helper/test-tool.h      |   1 +

I think duplicating things like strbuf is an unnecessary burden if Git is to maintain this library. Something like "reftable will only import git-compat-util.h and strbuf.h, and any project that wants to use reftable must make sure that these functions and data structures are available" would be more plausible.

#55 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 5 lines · Jonathan Tan
Quoted 5 lines

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Quoted 8 lines
Quoted 2 lines

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

I think duplicating things like strbuf is an unnecessary burden if Git is to maintain this library. Something like "reftable will only import git-compat-util.h and strbuf.h, and any project that wants to use reftable must make sure that these functions and data structures are available" would be more plausible.

Sure, but how do we ensure that the directory won't take on dependencies beyond these headers? I am worried that I will be involved in a tedious back & forth process to keep updates going into libgit2 and/or also have to keep maintaining github.com/google/reftable.

FWIW, the duplication is really tiny: according to

 $ wc $(grep -l REFTABLE_STANDALONE *[ch])

it's just 431 lines of code.

--
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays. --
Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg
Geschäftsführer: Paul Manicle, Halimah DeLaine Prado

#56 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Han-Wen,

Quoted 21 lines · Han-Wen Nienhuys
Quoted 5 lines · Jonathan Tan
Quoted 5 lines

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Quoted 8 lines
Quoted 2 lines

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

I think duplicating things like strbuf is an unnecessary burden if Git is to maintain this library. Something like "reftable will only import git-compat-util.h and strbuf.h, and any project that wants to use reftable must make sure that these functions and data structures are available" would be more plausible.

Sure, but how do we ensure that the directory won't take on dependencies beyond these headers? I am worried that I will be involved in a tedious back & forth process to keep updates going into libgit2 and/or also have to keep maintaining github.com/google/reftable.

Quoted 5 lines

FWIW, the duplication is really tiny: according to

 $ wc $(grep -l REFTABLE_STANDALONE *[ch])

it's just 431 lines of code.

The merge_bases_many() function has only 33 lines of code, partially duplicating get_reachable_subset(). Yet, it had a bug in it for two years that was not found.

How much worse will the situation be with your 431 lines of code.

Even more so when you consider the fact that you intend to shove the same duplication down libgit2's throat. It's "triplicating" code.

So I find the argument you made above quite unconvincing.

Ciao,
Dscho

#57 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi,

Quoted 8 lines · Johannes Schindelin

The merge_bases_many() function has only 33 lines of code, partially duplicating get_reachable_subset(). Yet, it had a bug in it for two years that was not found.

How much worse will the situation be with your 431 lines of code.

Even more so when you consider the fact that you intend to shove the same duplication down libgit2's throat. It's "triplicating" code.

Careful: you seem to be making a bunch of assumptions here (for example, around Han-Wen having some intent around shoving things down libgit2's throat), and you seem to be focusing on the person instead of the contribution.

Would you mind restating your point in a way that is a little easier to process, for example by focusing on the effect that this patch would have on you as a Git developer?

Thanks,
Jonathan

Quoted 1 line

So I find the argument you made above quite unconvincing.

#58 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Jonathan,

Quoted 11 lines · Jonathan Nieder
Quoted 8 lines · Johannes Schindelin

The merge_bases_many() function has only 33 lines of code, partially duplicating get_reachable_subset(). Yet, it had a bug in it for two years that was not found.

How much worse will the situation be with your 431 lines of code.

Even more so when you consider the fact that you intend to shove the same duplication down libgit2's throat. It's "triplicating" code.

Quoted 8 lines

Careful: you seem to be making a bunch of assumptions here (for example, around Han-Wen having some intent around shoving things down libgit2's throat), and you seem to be focusing on the person instead of the contribution.

Would you mind restating your point in a way that is a little easier to process, for example by focusing on the effect that this patch would have on you as a Git developer?

I really hope that Han-Wen did not take this as a personal attack.

In case he did, I'll gladly try to re-phrase my point: It does not matter how much code is duplicated. The fact that we run into bugs even in unintentionally duplicated code is reason enough to not lightly accept this design decision as being set in stone.

Which is why I, you, Emily and Josh pointed that out, and it is the same reason why Peff and Junio had made the same point in the past.

I was quite unpleasantly surprised to see that all of those objections seemed to be insufficient, hence my rather forceful reply.

As to the libgit2 angle: libgit2 also has strbuf (which it calls git_buf IIRC), and I am certain that it also has those other helper functions à la put_be32(). So yes, the triplication I mentioned is a very real, undesirable feature of this patch series.

To be honest, I would much rather spend my time reviewing patches that added reftable support using as much of libgit.a's functionality as possible rather than repeating the same point over and over again: that it makes very little sense _not_ to use that functionality. Of course, at this stage I understand that my feedback is not very welcome, so I will try to refrain from commenting on this patch series further (I do reserve the right to send patches that fix CI failures, just like I've done quite a few times up to this point).

Ciao,
Dscho

#59 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 2 lines · Johannes Schindelin

Of course, at
this stage I understand that my feedback is not very welcome,

Do you mean because you don't feel that your feedback has been sufficiently acted upon, or is there something else you are alluding to?

Puzzled,
Jonathan

#60 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Jonathan,

Quoted 7 lines · Jonathan Nieder
Quoted 2 lines · Johannes Schindelin

Of course, at this stage I understand that my feedback is not very welcome,

Do you mean because you don't feel that your feedback has been sufficiently acted upon, or is there something else you are alluding to?

My feedback has not been acted upon, and my arguments have been dismissed (or there were attempts to dismiss them, I should say, that did not convince me). At least I saw that when gitster repeated some of my concerns in #git-devel, there were attempts to address them. While I feel bad about leaving the burden on gitster's shoulders, at least I am glad that those concerns are being addressed.

Ciao,
Dscho

#61 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

In reply to Johannes Schindelin
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines

FWIW, the duplication is really tiny: according to

 $ wc $(grep -l REFTABLE_STANDALONE *[ch])

it's just 431 lines of code.

The merge_bases_many() function has only 33 lines of code, partially duplicating get_reachable_subset(). Yet, it had a bug in it for two years that was not found.

It does not affect the current discussion, but what you are giving is a revisionist view of the world. The latter function came MUCH later to do a bit more than the former. The bug was caused by the fact that those that added the latter neglected the responsibility of maintaining the former to the same degree when new feature like commit-graph were added to the latter.

The root cause was that the latter one did not share the code with the former one when it was introduced. That does make it appear similar to the situation we have at hand with duplicated utility functions.

Quoted 9 lines

How much worse will the situation be with your 431 lines of code.

Even more so when you consider the fact that you intend to shove the same duplication down libgit2's throat. It's "triplicating" code.

So I find the argument you made above quite unconvincing.

Ciao,
Dscho

#62 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Hi Junio,

Quoted 12 lines · Junio C Hamano
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines

FWIW, the duplication is really tiny: according to

 $ wc $(grep -l REFTABLE_STANDALONE *[ch])

it's just 431 lines of code.

The merge_bases_many() function has only 33 lines of code, partially duplicating get_reachable_subset(). Yet, it had a bug in it for two years that was not found.

Quoted 6 lines

It does not affect the current discussion, but what you are giving is a revisionist view of the world. The latter function came MUCH later to do a bit more than the former. The bug was caused by the fact that those that added the latter neglected the responsibility of maintaining the former to the same degree when new feature like commit-graph were added to the latter.

You're right, I mixed up the direction: by introducing new code, the old code became under-tested and a bug creeped in that was not detected for two years.

Quoted 4 lines

The root cause was that the latter one did not share the code with the former one when it was introduced. That does make it appear similar to the situation we have at hand with duplicated utility functions.

Indeed, and even if this is just one concrete example, experience (and teachers and instructors and mentors) repeat the lesson that code duplication should be avoided because it _does_ cause problems.

Thanks for setting the record straight,
Dscho

Quoted 11 lines
Quoted 10 lines

How much worse will the situation be with your 431 lines of code.

Even more so when you consider the fact that you intend to shove the same duplication down libgit2's throat. It's "triplicating" code.

So I find the argument you made above quite unconvincing.

Ciao,
Dscho

#63 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

In reply to Han-Wen Nienhuys
Quoted 21 lines · Han-Wen Nienhuys
Quoted 5 lines · Jonathan Tan
Quoted 5 lines

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Quoted 8 lines
Quoted 2 lines

Since the reftable library must compile standalone, there may be some overlap with git-core utility functions.

I think duplicating things like strbuf is an unnecessary burden if Git is to maintain this library. Something like "reftable will only import git-compat-util.h and strbuf.h, and any project that wants to use reftable must make sure that these functions and data structures are available" would be more plausible.

Sure, but how do we ensure that the directory won't take on dependencies beyond these headers? I am worried that I will be involved in a tedious back & forth process to keep updates going into libgit2 and/or also have to keep maintaining github.com/google/reftable.

Quoted 5 lines

FWIW, the duplication is really tiny: according to

 $ wc $(grep -l REFTABLE_STANDALONE *[ch])

it's just 431 lines of code.

Why import the dead code at all? If I add this to your update script:

# Not a general solution, but works for the specific code here.
perl -0777 -pi -e 's[(#ifndef REFTABLE_STANDALONE\n.*?\n#else\n).*?(?=\n#endif)][$1/* Removed upstream code for git.git import */]gs' reftable/system.h perl -0777 -pi -e 's[(?<=#ifdef REFTABLE_STANDALONE\n).*?(?=\n#endif)][/* Removed upstream code for git.git import */]gs' reftable/strbuf.c reftable/compat.h perl -0777 -pi -e 's[(?<=#ifdef REFTABLE_STANDALONE\n).*?(?=\n#else)][/* Removed upstream code for git.git import */]gs' reftable/compat.c reftable/strbuf.h

It's now 157 lines instead of 431.

I think doing that with a tiny bit more complexity in the update.sh script is a much lower maintenance burden.

It's not just about the number of lines, but things coming up in grep, and now unique code really stands out (e.g. strbuf_add_void, should probably be just added to the main strbuf.h if it's needed...), and of course the cost of attention of eyeballing an already big series on the ML & the churn every time we have updates.

Overall I'm all for having this carved out in its own directory at a cost of a bit more maintenance burden if it can be shared with libgit2 & be a standalone library.

I am concerned that it seems this code can't be maintained in git.git by anyone not willing to sign a contract with Google. I sent a tiny PR for a typo fix at [1] and got directed to sign [2] before someone at Google could look at it. I see brian raised this before in [3] but there wasn't a reply to that point.

Is there some summary of how this part of integrating it is supposed to work going forward?

At first glance that seems like a recipe for perma-forking this pretty much from day one, i.e.:

A. Upstream changes happen
B. We get a patch to the bundled library to this ML ==> Google employees maintaining A can't even look at B ==> Patch B can't be integrated into A

1. https://github.com/google/reftable/pull/2
2. https://cla.developers.google.com/about/google-individual
3. https://lore.kernel.org/git/[email protected]/

#64 lore raw

Re: [PATCH v2 05/13] reftable: utility functions

Quoted 5 lines · Ævar Arnfjörð Bjarmason

It's not just about the number of lines, but things coming up in grep, and now unique code really stands out (e.g. strbuf_add_void, should probably be just added to the main strbuf.h if it's needed...), and of course the cost of attention of eyeballing an already big series on the ML & the churn every time we have updates.

These are all valid concerns.

Quoted 3 lines

Overall I'm all for having this carved out in its own directory at a cost of a bit more maintenance burden if it can be shared with libgit2 & be a standalone library.

Do you mean by "this" the reftable stuff as a whole, or only the duplicated support library part? If the latter is split out of what we import as our reftable/ directory, and stored in a separate directory that we do not have to import (because we have strbuf and other stuff) but other people may choose to use (because they may not have strbuf and other stuff), that may work. Also, if the contents of reftable/ directory wants a pluggable allocator, the main code can be written to call reftable_malloc (or whatever), with a thin shim to interface with us (i.e. reftable_malloc() would be implemented as a thin wrapper around xmalloc() for us) which is stored in a separate directory just for us to interface with the main reftable library. For libgit2, there will be a separate directory that uses a different implementation of reftable_malloc() that would let them plug their preferred allocator. An arrangement like that might work.

I do not offhand know if that kind of overhead is worth the trouble or if there are better ways, though.

Quoted 2 lines

I am concerned that it seems this code can't be maintained in git.git by anyone not willing to sign a contract with Google.

It can be maintained in git.git; the trouble comes when they want to update us.

I however suspect that, as the primary intended audience, it is hard to imagine that the reftable library as a standalone project will be successful without going through the usual reviews and testing in git.git. So even though there exists github.com/google/reftable repository, its contents may not matter very much in practice, unless they come here and beg for the change we make ourselves anyway. Perhaps I am being naive. I dunno.

Quoted 7 lines

I sent a tiny PR for
a typo fix at [1] and got directed to sign [2] before someone at Google could look at it. I see brian raised this before in [3] but there wasn't a reply to that point.

Is there some summary of how this part of integrating it is supposed to work going forward?

Yeah, thanks for raising a good point. We definitely need to figure this part out.

Quoted 11 lines

At first glance that seems like a recipe for perma-forking this pretty much from day one, i.e.:

A. Upstream changes happen
B. We get a patch to the bundled library to this ML ==> Google employees maintaining A can't even look at B ==> Patch B can't be integrated into A

1. https://github.com/google/reftable/pull/2
2. https://cla.developers.google.com/about/google-individual
3. https://lore.kernel.org/git/[email protected]/

#65 lore raw

PATCH v2 6/13 reftable: (de)serialization for the polymorphic record type.

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile                 |    2 +
 reftable/constants.h     |   21 +
 reftable/record.c        | 1116 ++++++++++++++++++++++++++++++++++++++
 reftable/record.h        |  137 +++++
 reftable/record_test.c   |  410 ++++++++++++++
 t/helper/test-reftable.c |    1 +
 6 files changed, 1687 insertions(+)
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
Makefile +2 -0
index e40d55cd87..b5e43d79ed 100644
@@ -2376,8 +2376,10 @@ REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
+REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
 
+REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 
reftable/constants.h new file +21 -0
new file mode 100644
index 0000000000..5eee72c4c1
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
reftable/record.c new file +1116 -0

1,116 lines not on this page · raw message

reftable/record.h new file +137 -0

137 lines not on this page · raw message

reftable/record_test.c new file +410 -0

410 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 7d50aa6bcc..9341272089 100644
@@ -3,6 +3,7 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	record_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#66 lore raw

Re: [PATCH v2 06/13] reftable: (de)serialization for the polymorphic record type.

reftable/record.c line 837

Quoted 3 lines · Han-Wen Nienhuys via GitGitGadget
reftable/record.c new file +3 -0
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+	struct reftable_record rec = { NULL };

I can see a lot of "sparse" inspired work went into this, but we would want to take the previous discussion into account, as nothing has changed since then.

https://lore.kernel.org/git/[email protected]/

https://lore.kernel.org/git/[email protected]/

Unfortunately we didn't reach a clear consensus back then; I thought we saw a fix to make sparse silent when we use the "= { 0 }" idiom in recent versions of sparse, but the above threads do not have any mention of it, so either I am misremembering or there were other discussions on the same topic where it was also mentioned.

Review

Quoted 1 line
(quoted diff) +1 -0
+	switch (typ) {

Need blank before this line.

Thanks.

#67 lore raw

Re: [PATCH v2 06/13] reftable: (de)serialization for the polymorphic record type.

Quoted 10 lines · Junio C Hamano

reftable/record.c line 837

Quoted 3 lines · Han-Wen Nienhuys via GitGitGadget
reftable/record.c new file +3 -0
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+	struct reftable_record rec = { NULL };

I can see a lot of "sparse" inspired work went into this, but we would want to take the previous discussion into account, as nothing has changed since then.

Quoted 3 lines

https://lore.kernel.org/git/[email protected]/

https://lore.kernel.org/git/[email protected]/

yep, sparse is equally happy with = { 0 }; here - at least any sparse version after 'v0.6.1-246-g41f651b4'. ;-) So, as I said yesterday, the last official release v0.6.2 should be fine, and I _think_ Debian Testing has a suitable version. (or you could build from source).

Quoted 5 lines

Unfortunately we didn't reach a clear consensus back then; I thought we saw a fix to make sparse silent when we use the "= { 0 }" idiom in recent versions of sparse, but the above threads do not have any mention of it, so either I am misremembering or there were other discussions on the same topic where it was also mentioned.

See commit 1c96642326 (sparse: allow '{ 0 }' to be used without warnings, 22-05-2020). We could actually remove that now, since '-Wno-universal-initializer' is now the default (but it doesn't hurt).

ATB,
Ramsay Jones

#68 lore raw

PATCH v2 7/13 reftable: reading/writing blocks

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

Includes a code snippet copied from zlib

---
 Makefile                 |   3 +
 reftable/.gitattributes  |   1 +
 reftable/block.c         | 441 +++++++++++++++++++++++++++++++++++++++
 reftable/block.h         | 129 ++++++++++++
 reftable/block_test.c    | 158 ++++++++++++++
 reftable/zlib-compat.c   |  92 ++++++++
 t/helper/test-reftable.c |   1 +
 7 files changed, 825 insertions(+)
 create mode 100644 reftable/.gitattributes
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/zlib-compat.c
Makefile +3 -0
index b5e43d79ed..eda92b00ef 100644
@@ -2373,12 +2373,15 @@ XDIFF_OBJS += xdiff/xprepare.o
 XDIFF_OBJS += xdiff/xutils.o
 
 REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
+REFTABLE_OBJS += reftable/zlib-compat.o
 
+REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
reftable/.gitattributes new file +1 -0
new file mode 100644
index 0000000000..f44451a379
@@ -0,0 +1 @@
+/zlib-compat.c	whitespace=-indent-with-non-tab,-trailing-space
reftable/block.c new file +441 -0

441 lines not on this page · raw message

reftable/block.h new file +129 -0

129 lines not on this page · raw message

reftable/block_test.c new file +158 -0

158 lines not on this page · raw message

reftable/zlib-compat.c new file +92 -0

92 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 9341272089..81a9bd5667 100644
@@ -3,6 +3,7 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	block_test_main(argc, argv);
 	record_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
 	return 0;
Signature · 1 line
gitgitgadget
#69 lore raw

PATCH v2 8/13 reftable: a generic binary tree implementation

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This is necessary for building a OID => ref map on write

---
 Makefile                 |  2 ++
 reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++
 reftable/tree.h          | 34 ++++++++++++++++++++++
 reftable/tree_test.c     | 62 +++++++++++++++++++++++++++++++++++++++
 t/helper/test-reftable.c |  1 +
 5 files changed, 162 insertions(+)
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
Makefile +2 -0
index eda92b00ef..870e540f22 100644
@@ -2379,12 +2379,14 @@ REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
+REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
+REFTABLE_TEST_OBJS += reftable/tree_test.o
 
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
reftable/tree.c new file +63 -0

63 lines not on this page · raw message

reftable/tree.h new file +34 -0
new file mode 100644
index 0000000000..954512e9a3
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+/* tree_node is a generic binary search tree. */
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+/* looks for `key` in `rootp` using `compare` as comparison function. If insert
+   is set, insert the key if it's not found. Else, return NULL.
+*/
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+
+/* performs an infix walk of the tree. */
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+
+/*
+  deallocates the tree nodes recursively. Keys should be deallocated separately
+  by walking over the tree. */
+void tree_free(struct tree_node *t);
+
+#endif
reftable/tree_test.c new file +62 -0

62 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 81a9bd5667..9c4e0f42dc 100644
@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)
 	block_test_main(argc, argv);
 	record_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#70 lore raw

PATCH v2 9/13 reftable: write reftable files

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile          |   1 +
 reftable/writer.c | 673 ++++++++++++++++++++++++++++++++++++++++++++++
 reftable/writer.h |  51 ++++
 3 files changed, 725 insertions(+)
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
Makefile +1 -0
index 870e540f22..c1dd046155 100644
@@ -2380,6 +2380,7 @@ REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/strbuf.o
 REFTABLE_OBJS += reftable/tree.o
+REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
reftable/writer.c new file +673 -0

673 lines not on this page · raw message

reftable/writer.h new file +51 -0

51 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#71 lore raw

PATCH v2 10/13 reftable: read reftable files

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile            |   3 +
 reftable/iter.c     | 242 +++++++++++++++
 reftable/iter.h     |  72 +++++
 reftable/reader.c   | 733 ++++++++++++++++++++++++++++++++++++++++++++
 reftable/reader.h   |  78 +++++
 reftable/reftable.c | 104 +++++++
 6 files changed, 1232 insertions(+)
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/reftable.c
Makefile +3 -0
index c1dd046155..8b09a56d91 100644
@@ -2377,7 +2377,10 @@ REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
+REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/reader.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/reftable.o
 REFTABLE_OBJS += reftable/strbuf.o
 REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
reftable/iter.c new file +242 -0

242 lines not on this page · raw message

reftable/iter.h new file +72 -0

72 lines not on this page · raw message

reftable/reader.c new file +733 -0

733 lines not on this page · raw message

reftable/reader.h new file +78 -0

78 lines not on this page · raw message

reftable/reftable.c new file +104 -0

104 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#72 lore raw

PATCH v2 11/13 reftable: file level tests

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile                 |   1 +
 reftable/reftable_test.c | 585 +++++++++++++++++++++++++++++++++++++++
 t/helper/test-reftable.c |   1 +
 3 files changed, 587 insertions(+)
 create mode 100644 reftable/reftable_test.c
Makefile +1 -0
index 8b09a56d91..17d5fba0a0 100644
@@ -2388,6 +2388,7 @@ REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
+REFTABLE_TEST_OBJS += reftable/reftable_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/tree_test.o
reftable/reftable_test.c new file +585 -0

585 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 9c4e0f42dc..2273fdfe39 100644
@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)
 {
 	block_test_main(argc, argv);
 	record_test_main(argc, argv);
+	reftable_test_main(argc, argv);
 	strbuf_test_main(argc, argv);
 	tree_test_main(argc, argv);
 	return 0;
Signature · 1 line
gitgitgadget
#73 lore raw

PATCH v2 12/13 reftable: rest of library

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This will be further split up once preceding commits have passed review.

---
 Makefile                 |    8 +-
 reftable/VERSION         |    1 +
 reftable/dump.c          |  212 +++++++
 reftable/merged.c        |  358 +++++++++++
 reftable/merged.h        |   36 ++
 reftable/merged_test.c   |  331 ++++++++++
 reftable/pq.c            |  115 ++++
 reftable/pq.h            |   32 +
 reftable/refname.c       |  209 +++++++
 reftable/refname.h       |   28 +
 reftable/refname_test.c  |  100 +++
 reftable/reftable.c      |    4 +-
 reftable/stack.c         | 1240 ++++++++++++++++++++++++++++++++++++++
 reftable/stack.h         |   40 ++
 reftable/stack_test.c    |  788 ++++++++++++++++++++++++
 reftable/update.sh       |   22 +
 t/helper/test-reftable.c |    3 +
 17 files changed, 3524 insertions(+), 3 deletions(-)
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100755 reftable/update.sh
Makefile +7 -1
index 17d5fba0a0..05021f79a1 100644
@@ -2375,12 +2375,16 @@ XDIFF_OBJS += xdiff/xutils.o
 REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
-REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/compat.o
 REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/merged.o
+REFTABLE_OBJS += reftable/pq.o
+REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/reader.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/refname.o
 REFTABLE_OBJS += reftable/reftable.o
+REFTABLE_OBJS += reftable/stack.o
 REFTABLE_OBJS += reftable/strbuf.o
 REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
@@ -2388,7 +2392,9 @@ REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
+REFTABLE_TEST_OBJS += reftable/refname_test.o
 REFTABLE_TEST_OBJS += reftable/reftable_test.o
+REFTABLE_TEST_OBJS += reftable/stack_test.o
 REFTABLE_TEST_OBJS += reftable/strbuf_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/tree_test.o
reftable/VERSION new file +1 -0
new file mode 100644
index 0000000000..cf8ae4958f
@@ -0,0 +1 @@
+7134eb9f8171a9759800f4187f9e6dde997335e7 C: NULL iso 0 for init
reftable/dump.c new file +212 -0

212 lines not on this page · raw message

reftable/merged.c new file +358 -0

358 lines not on this page · raw message

reftable/merged.h new file +36 -0
new file mode 100644
index 0000000000..76c29928c3
@@ -0,0 +1,36 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct reftable_merged_table {
+	struct reftable_table *stack;
+	size_t stack_len;
+	uint32_t hash_id;
+	int suppress_deletions;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct reftable_iterator *stack;
+	uint32_t hash_id;
+	size_t stack_len;
+	uint8_t typ;
+	int suppress_deletions;
+	struct merged_iter_pqueue pq;
+};
+
+void merged_table_clear(struct reftable_merged_table *mt);
+
+#endif
reftable/merged_test.c new file +331 -0

331 lines not on this page · raw message

reftable/pq.c new file +115 -0

115 lines not on this page · raw message

reftable/pq.h new file +32 -0
new file mode 100644
index 0000000000..9c2c0e0bfe
@@ -0,0 +1,32 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	int index;
+	struct reftable_record rec;
+};
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	size_t len;
+	size_t cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
reftable/refname.c new file +209 -0

209 lines not on this page · raw message

reftable/refname.h new file +28 -0
new file mode 100644
index 0000000000..fcd645a99f
@@ -0,0 +1,28 @@
+/*
+  Copyright 2020 Google LLC
+
+  Use of this source code is governed by a BSD-style
+  license that can be found in the LICENSE file or at
+  https://developers.google.com/open-source/licenses/bsd
+*/
+#ifndef REFNAME_H
+#define REFNAME_H
+
+#include "reftable.h"
+
+struct modification {
+	struct reftable_table tab;
+
+	char **add;
+	size_t add_len;
+
+	char **del;
+	size_t del_len;
+};
+
+int validate_ref_record_addition(struct reftable_table tab,
+				 struct reftable_ref_record *recs, size_t sz);
+
+int modification_validate(struct modification *mod);
+
+#endif
reftable/refname_test.c new file +100 -0

100 lines not on this page · raw message

reftable/reftable.c +2 -2
index 1425aef6f2..0f3bf0291e 100644
@@ -44,7 +44,7 @@ int reftable_table_seek_ref(struct reftable_table *tab,
 	struct reftable_ref_record ref = {
 		.refname = (char *)name,
 	};
-	struct reftable_record rec = { 0 };
+	struct reftable_record rec = { NULL };
 	reftable_record_from_ref(&rec, &ref);
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
@@ -60,7 +60,7 @@ void reftable_table_from_reader(struct reftable_table *tab,
 int reftable_table_read_ref(struct reftable_table *tab, const char *name,
 			    struct reftable_ref_record *ref)
 {
-	struct reftable_iterator it = { 0 };
+	struct reftable_iterator it = { NULL };
 	int err = reftable_table_seek_ref(tab, &it, name);
 	if (err)
 		goto done;
reftable/stack.c new file +1240 -0

1,240 lines not on this page · raw message

reftable/stack.h new file +40 -0
new file mode 100644
index 0000000000..4e9557f08d
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef STACK_H
+#define STACK_H
+
+#include "reftable.h"
+#include "system.h"
+
+struct reftable_stack {
+	char *list_file;
+	char *reftable_dir;
+	int disable_auto_compact;
+
+	struct reftable_write_options config;
+
+	struct reftable_reader **readers;
+	size_t readers_len;
+	struct reftable_merged_table *merged;
+	struct reftable_compaction_stats stats;
+};
+
+int read_lines(const char *filename, char ***lines);
+
+struct segment {
+	int start, end;
+	int log;
+	uint64_t bytes;
+};
+
+int fastlog2(uint64_t sz);
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
+
+#endif
reftable/stack_test.c new file +788 -0

256 lines not on this page · raw message

reftable/update.sh new file +22 -0
new file mode 100755
index 0000000000..4dadd875f4

Lines of this file are not shown. View the raw message.

t/helper/test-reftable.c +3 -0
index 2273fdfe39..def8883439 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
gitgitgadget

Part of the diff is not shown here. View the raw message.

#74 lore raw

Re: [PATCH v2 12/13] reftable: rest of library

Hi Han-Wen,

Quoted 3 lines · Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This will be further split up once preceding commits have passed review.

Before you further split it up, I encourage you to include these patches without which the CI builds will continue to fail (Junio, could I ask you to either cherry-pick them from https://github.com/git-for-windows/git's
shears/seen branch, or apply them from the mbox?):

-- snipsnap --
From e485e006f34922439f2e971a1c5c38b8ca56c011 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Wed, 30 Sep 2020 14:46:59 +0200
Subject: [PATCH 1/3] fixup??? reftable: rest of library

	struct abc x = {}

is a GNUism.

---
 reftable/dump.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
reftable/dump.c +2 -2
index ed09f2e2f94..233f0434e39 100644
@@ -75,7 +75,7 @@ static int dump_table(const char *tablename)
 static int compact_stack(const char *stackdir)
 {
 	struct reftable_stack *stack = NULL;
-	struct reftable_write_options cfg = {};
+	struct reftable_write_options cfg = { 0 };
 
 	int err = reftable_new_stack(&stack, stackdir, cfg);
 	if (err < 0)
@@ -94,7 +94,7 @@ static int compact_stack(const char *stackdir)
 static int dump_stack(const char *stackdir)
 {
 	struct reftable_stack *stack = NULL;
-	struct reftable_write_options cfg = {};
+	struct reftable_write_options cfg = { 0 };
 	struct reftable_iterator it = { NULL };
 	struct reftable_ref_record ref = { NULL };
 	struct reftable_log_record log = { NULL };

--
2.28.0.windows.1.18.g5300e52e185

From d5faa818a1bc00016e310d27602551127db620fb Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Wed, 30 Sep 2020 14:55:28 +0200
Subject: [PATCH 2/3] fixup??? reftable: rest of library

0-sized arrays are actually not portable.

---
 reftable/stack_test.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
reftable/stack_test.c +2 -2
index 534463829cb..e863cc3c0a2 100644
@@ -572,11 +572,11 @@ static void test_sizes_to_segments(void)
 
 static void test_sizes_to_segments_empty(void)
 {
-	uint64_t sizes[0];
+	uint64_t sizes[1];
 
 	int seglen = 0;
 	struct segment *segs =
-		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
+		sizes_to_segments(&seglen, sizes, 0);
 	assert(seglen == 0);
 	reftable_free(segs);
 }

--
2.28.0.windows.1.18.g5300e52e185

From d446e1a7354c676d60114b50ba96a6ea083441ae Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Wed, 30 Sep 2020 15:19:28 +0200
Subject: [PATCH 3/3] fixup??? reftable: rest of library

Avoid using getopt(): it might be POSIX, but Git's audience is much larger than POSIX. MSVC, for example, does not support getopt().

---
 reftable/dump.c | 32 +++++++++++++-------------------
 1 file changed, 13 insertions(+), 19 deletions(-)
reftable/dump.c +13 -19
index 233f0434e39..b63c9fe9e81 100644
@@ -160,40 +160,34 @@ static void print_help(void)
 int reftable_dump_main(int argc, char *const *argv)
 {
 	int err = 0;
-	int opt;
 	int opt_dump_table = 0;
 	int opt_dump_stack = 0;
 	int opt_compact = 0;
-	const char *arg = NULL;
-	while ((opt = getopt(argc, argv, "2chts")) != -1) {
-		switch (opt) {
-		case '2':
-			hash_id = 0x73323536;
+	const char *arg = NULL, *argv0 = argv[0];
+
+	for (; argc > 1; argv++, argc--)
+		if (*argv[1] != '-')
 			break;
-		case 't':
+		else if (!strcmp("-2", argv[1]))
+			hash_id = 0x73323536;
+		else if (!strcmp("-t", argv[1]))
 			opt_dump_table = 1;
-			break;
-		case 's':
+		else if (!strcmp("-s", argv[1]))
 			opt_dump_stack = 1;
-			break;
-		case 'c':
+		else if (!strcmp("-c", argv[1]))
 			opt_compact = 1;
-			break;
-		case '?':
-		case 'h':
+		else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) {
 			print_help();
 			return 2;
-			break;
 		}
-	}
 
-	if (argv[optind] == NULL) {
+	if (argc != 2) {
 		fprintf(stderr, "need argument\n");
 		print_help();
 		return 2;
 	}
 
-	arg = argv[optind];
+	arg = argv[1];
 
 	if (opt_dump_table) {
 		err = dump_table(arg);
@@ -204,7 +198,7 @@ int reftable_dump_main(int argc, char *const *argv)
 	}
 
 	if (err < 0) {
-		fprintf(stderr, "%s: %s: %s\n", argv[0], arg,
+		fprintf(stderr, "%s: %s: %s\n", argv0, arg,
 			reftable_error_str(err));
 		return 1;
 	}

--
2.28.0.windows.1.18.g5300e52e185

#75 lore raw

Re: [PATCH v2 12/13] reftable: rest of library

Quoted 1 line · Johannes Schindelin

Subject: [PATCH 1/3] fixup??? reftable: rest of library

This is unambiguously good.

Quoted 6 lines

Subject: [PATCH 2/3] fixup??? reftable: rest of library

0-sized arrays are actually not portable.
...
static void test_sizes_to_segments_empty(void)
{

reftable/stack_test.c static void test_sizes_to_segments(void) · line 581

Quoted 9 lines
reftable/stack_test.c +2 -2
-	uint64_t sizes[0];
+	uint64_t sizes[1];
 
 	int seglen = 0;
 	struct segment *segs =
-		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
+		sizes_to_segments(&seglen, sizes, 0);
 	assert(seglen == 0);
 	reftable_free(segs);

Question to Han-Wen.

It is unclear what this test wants to test. Do we even need sizes[] array if we know we are passing a hardcoded 0? IOW, I would understand if the test were

	sizes_to_segments(&seglen, NULL, 0);

to ensure that sizes_to_segments do not even attempt to look at sizes[] array when the number of elements is 0.

Quoted 4 lines

Subject: [PATCH 3/3] fixup??? reftable: rest of library

Avoid using getopt(): it might be POSIX, but Git's audience is much larger than POSIX. MSVC, for example, does not support getopt().

Either that, or we could use parse-options(). I do not care either way, as this seems to be purely for debugging?

#76 lore raw

Re: [PATCH v2 12/13] reftable: rest of library

Hi Junio,

Quoted 9 lines · Junio C Hamano
Quoted 4 lines · Johannes Schindelin

Subject: [PATCH 3/3] fixup??? reftable: rest of library

Avoid using getopt(): it might be POSIX, but Git's audience is much larger than POSIX. MSVC, for example, does not support getopt().

Either that, or we could use parse-options(). I do not care either way, as this seems to be purely for debugging?

I actually wanted to use parse_options(), but due to the way reftable/ insists on being completely separate from libgit.a, it is not really trivial, as #include "parse-options.h" fails (because git-compat-util.h was not included, and IIRC including _that_ resulted in more compile errors, at which stage I opted for a more minimal solution).

So yeah, I would strongly expect reftable/ to become considerably simpler once it starts using libgit.a's goodies.

Ciao,
Dscho

#77 lore raw

PATCH v2 13/13 reftable: "test-tool dump-reftable" command.

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This command dumps individual tables or a stack of of tables.

---
 Makefile                 |  2 ++
 reftable/iter.c          |  6 +++---
 reftable/reader.c        | 22 +++++++++++-----------
 t/helper/test-reftable.c |  5 +++++
 t/helper/test-tool.c     |  1 +
 t/helper/test-tool.h     |  1 +
 6 files changed, 23 insertions(+), 14 deletions(-)
Makefile +2 -0
index 05021f79a1..5f92e5cfc2 100644
@@ -2391,6 +2391,8 @@ REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/block_test.o
+REFTABLE_TEST_OBJS += reftable/dump.o
+REFTABLE_TEST_OBJS += reftable/merged_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/refname_test.o
 REFTABLE_TEST_OBJS += reftable/reftable_test.o
reftable/iter.c +3 -3
index 2cff447323..80bc65e07d 100644
@@ -59,7 +59,7 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
 int reftable_iterator_next_ref(struct reftable_iterator *it,
 			       struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { 0 };
+	struct reftable_record rec = { NULL };
 	reftable_record_from_ref(&rec, ref);
 	return iterator_next(it, &rec);
 }
@@ -67,7 +67,7 @@ int reftable_iterator_next_ref(struct reftable_iterator *it,
 int reftable_iterator_next_log(struct reftable_iterator *it,
 			       struct reftable_log_record *log)
 {
-	struct reftable_record rec = { 0 };
+	struct reftable_record rec = { NULL };
 	reftable_record_from_log(&rec, log);
 	return iterator_next(it, &rec);
 }
@@ -95,7 +95,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
 		}
 
 		if (fri->double_check) {
-			struct reftable_iterator it = { 0 };
+			struct reftable_iterator it = { NULL };
 
 			err = reftable_table_seek_ref(&fri->tab, &it,
 						      ref->refname);
reftable/reader.c +11 -11
index c7f56b5fdc..8d3f63b0d8 100644
@@ -161,8 +161,8 @@ static int parse_footer(struct reftable_reader *r, uint8_t *footer,
 int init_reader(struct reftable_reader *r, struct reftable_block_source *source,
 		const char *name)
 {
-	struct reftable_block footer = { 0 };
-	struct reftable_block header = { 0 };
+	struct reftable_block footer = { NULL };
+	struct reftable_block header = { NULL };
 	int err = 0;
 
 	memset(r, 0, sizeof(struct reftable_reader));
@@ -270,7 +270,7 @@ int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
 {
 	int32_t guess_block_size = r->block_size ? r->block_size :
 							 DEFAULT_BLOCK_SIZE;
-	struct reftable_block block = { 0 };
+	struct reftable_block block = { NULL };
 	uint8_t block_typ = 0;
 	int err = 0;
 	uint32_t header_off = next_off ? 0 : header_size(r->version);
@@ -477,9 +477,9 @@ static int reader_seek_indexed(struct reftable_reader *r,
 			       struct reftable_record *rec)
 {
 	struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-	struct reftable_record want_index_rec = { 0 };
+	struct reftable_record want_index_rec = { NULL };
 	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-	struct reftable_record index_result_rec = { 0 };
+	struct reftable_record index_result_rec = { NULL };
 	struct table_iter index_iter = TABLE_ITER_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
 	int err = 0;
@@ -584,7 +584,7 @@ int reftable_reader_seek_ref(struct reftable_reader *r,
 	struct reftable_ref_record ref = {
 		.refname = (char *)name,
 	};
-	struct reftable_record rec = { 0 };
+	struct reftable_record rec = { NULL };
 	reftable_record_from_ref(&rec, &ref);
 	return reader_seek(r, it, &rec);
 }
@@ -597,7 +597,7 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
 		.refname = (char *)name,
 		.update_index = update_index,
 	};
-	struct reftable_record rec = { 0 };
+	struct reftable_record rec = { NULL };
 	reftable_record_from_log(&rec, &log);
 	return reader_seek(r, it, &rec);
 }
@@ -644,10 +644,10 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 		.hash_prefix = oid,
 		.hash_prefix_len = r->object_id_len,
 	};
-	struct reftable_record want_rec = { 0 };
-	struct reftable_iterator oit = { 0 };
-	struct reftable_obj_record got = { 0 };
-	struct reftable_record got_rec = { 0 };
+	struct reftable_record want_rec = { NULL };
+	struct reftable_iterator oit = { NULL };
+	struct reftable_obj_record got = { NULL };
+	struct reftable_record got_rec = { NULL };
 	int err = 0;
 	struct indexed_table_ref_iter *itr = NULL;
 
t/helper/test-reftable.c +5 -0
index def8883439..aff4fbccda 100644
@@ -13,3 +13,8 @@ int cmd__reftable(int argc, const char **argv)
 	tree_test_main(argc, argv);
 	return 0;
 }
+
+int cmd__dump_reftable(int argc, const char **argv)
+{
+	return reftable_dump_main(argc, (char *const *)argv);
+}
t/helper/test-tool.c +1 -0
index c7ab5bc4a6..ce27041ab2 100644
@@ -54,6 +54,7 @@ static struct test_cmd cmds[] = {
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
 	{ "reftable", cmd__reftable },
+	{ "dump-reftable", cmd__dump_reftable },
 	{ "regex", cmd__regex },
 	{ "repository", cmd__repository },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index fa2b11ace6..8b5790d93f 100644
@@ -17,6 +17,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);
 int cmd__dump_fsmonitor(int argc, const char **argv);
 int cmd__dump_split_index(int argc, const char **argv);
 int cmd__dump_untracked_cache(int argc, const char **argv);
+int cmd__dump_reftable(int argc, const char **argv);
 int cmd__example_decorate(int argc, const char **argv);
 int cmd__genrandom(int argc, const char **argv);
 int cmd__genzeros(int argc, const char **argv);
Signature · 1 line
gitgitgadget
#78 lore raw

v3 cover reftable library

In reply to Han-Wen Nienhuys via GitGitGadget

This splits the giant commit from
https://github.com/gitgitgadget/git/pull/539 into a series of smaller commits, which build and have unittests.

The final commit should also be split up, but I want to wait until we have consensus that the bottom commits look good.

version 26 Nov 2020:

* split public API header into multiple headers
* minor adaptions in response to review feedback
* filter out code for standalone compile
* get rid of test framework

Han-Wen Nienhuys (15):
move sleep_millisec to git-compat-util.h
init-db: set the_repository->hash_algo early on
reftable: add LICENSE
reftable: add error related functionality
reftable: utility functions
reftable: add blocksource, an abstraction for random access reads
reftable: (de)serialization for the polymorphic record type.
reftable: reading/writing blocks
reftable: a generic binary tree implementation
reftable: write reftable files
reftable: read reftable files
reftable: reftable file level tests
reftable: rest of library
Reftable support for git-core
Add "test-tool dump-reftable" command.

SZEDER Gábor (1):
git-prompt: prepare for reftable refs backend

 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   46 +-
 builtin/clone.c                               |    5 +-
 builtin/init-db.c                             |   56 +-
 builtin/worktree.c                            |   27 +-
 cache.h                                       |    9 +-
 config.mak.uname                              |    2 +-
 contrib/buildsystems/Generators/Vcxproj.pm    |   11 +-
 contrib/completion/git-prompt.sh              |    7 +-
 git-compat-util.h                             |    2 +
 refs.c                                        |   27 +-
 refs.h                                        |    3 +
 refs/refs-internal.h                          |    1 +
 refs/reftable-backend.c                       | 1418 +++++++++++++++++
 reftable/.gitattributes                       |    1 +
 reftable/LICENSE                              |   31 +
 reftable/VERSION                              |    1 +
 reftable/basics.c                             |  144 ++
 reftable/basics.h                             |   56 +
 reftable/basics_test.c                        |   56 +
 reftable/block.c                              |  440 +++++
 reftable/block.h                              |  129 ++
 reftable/block_test.c                         |  119 ++
 reftable/blocksource.c                        |  148 ++
 reftable/blocksource.h                        |   22 +
 reftable/compat.h                             |   48 +
 reftable/constants.h                          |   21 +
 reftable/dump.c                               |  219 +++
 reftable/error.c                              |   39 +
 reftable/iter.c                               |  242 +++
 reftable/iter.h                               |   75 +
 reftable/merged.c                             |  366 +++++
 reftable/merged.h                             |   35 +
 reftable/merged_test.c                        |  333 ++++
 reftable/pq.c                                 |  115 ++
 reftable/pq.h                                 |   32 +
 reftable/publicbasics.c                       |   58 +
 reftable/reader.c                             |  733 +++++++++
 reftable/reader.h                             |   75 +
 reftable/record.c                             | 1116 +++++++++++++
 reftable/record.h                             |  139 ++
 reftable/record_test.c                        |  404 +++++
 reftable/refname.c                            |  209 +++
 reftable/refname.h                            |   29 +
 reftable/refname_test.c                       |  100 ++
 reftable/reftable-blocksource.h               |   50 +
 reftable/reftable-error.h                     |   62 +
 reftable/reftable-generic.h                   |   49 +
 reftable/reftable-iterator.h                  |   37 +
 reftable/reftable-malloc.h                    |   20 +
 reftable/reftable-merged.h                    |   68 +
 reftable/reftable-reader.h                    |   89 ++
 reftable/reftable-record.h                    |   73 +
 reftable/reftable-stack.h                     |  116 ++
 reftable/reftable-tests.h                     |   22 +
 reftable/reftable-writer.h                    |  149 ++
 reftable/reftable.c                           |   98 ++
 reftable/reftable_test.c                      |  577 +++++++
 reftable/stack.c                              | 1247 +++++++++++++++
 reftable/stack.h                              |   41 +
 reftable/stack_test.c                         |  781 +++++++++
 reftable/system.h                             |   32 +
 reftable/test_framework.c                     |   23 +
 reftable/test_framework.h                     |   48 +
 reftable/tree.c                               |   63 +
 reftable/tree.h                               |   34 +
 reftable/tree_test.c                          |   61 +
 reftable/writer.c                             |  676 ++++++++
 reftable/writer.h                             |   50 +
 reftable/zlib-compat.c                        |   92 ++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 setup.c                                       |    9 +-
 t/helper/test-reftable.c                      |   20 +
 t/helper/test-tool.c                          |    4 +-
 t/helper/test-tool.h                          |    2 +
 t/t0031-reftable.sh                           |  199 +++
 t/t0032-reftable-unittest.sh                  |   14 +
 t/t1409-avoid-packing-refs.sh                 |    6 +
 t/t1450-fsck.sh                               |    6 +
 t/t3210-pack-refs.sh                          |    6 +
 t/test-lib.sh                                 |    5 +
 82 files changed, 11951 insertions(+), 39 deletions(-)
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/.gitattributes
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/basics_test.c
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/blocksource.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/compat.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/error.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/publicbasics.c
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-blocksource.h
 create mode 100644 reftable/reftable-error.h
 create mode 100644 reftable/reftable-generic.h
 create mode 100644 reftable/reftable-iterator.h
 create mode 100644 reftable/reftable-malloc.h
 create mode 100644 reftable/reftable-merged.h
 create mode 100644 reftable/reftable-reader.h
 create mode 100644 reftable/reftable-record.h
 create mode 100644 reftable/reftable-stack.h
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/reftable-writer.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100644 t/helper/test-reftable.c
 create mode 100755 t/t0031-reftable.sh
 create mode 100755 t/t0032-reftable-unittest.sh

base-commit: faefdd61ec7c7f6f3c8c9907891465ac9a2a1475
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-847%2Fhanwen%2Flibreftable-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-847/hanwen/libreftable-v3
Pull-Request: https://github.com/git/git/pull/847

Range-diff vs v2:

  -:  ---------- >  1:  030998a732 move sleep_millisec to git-compat-util.h
  -:  ---------- >  2:  cf667653df init-db: set the_repository->hash_algo early on
  1:  6228103b4a =  3:  91c3ac2449 reftable: add LICENSE
  2:  5d1b946ab5 !  4:  2aa30f536f reftable: define the public API
     @@ Metadata
      Author: Han-Wen Nienhuys <[email protected]>
      
       ## Commit message ##
     -    reftable: define the public API
     +    reftable: add error related functionality
     +
     +    The reftable/ directory is structured as a library, so it cannot
     +    crash on misuse. Instead, it returns an error codes.
     +
     +    In addition, the error code can be used to signal conditions from lower levels
     +    of the library to be handled by higher levels of the library. For example, a
     +    transaction might legitimately write an empty reftable file, but in that case,
     +    we'd want to shortcut the transaction overhead.
      
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
     - ## reftable/reftable.h (new) ##
     + ## reftable/error.c (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/reftable.h (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#ifndef REFTABLE_H
     -+#define REFTABLE_H
     -+
     -+#include <stdint.h>
     -+#include <stddef.h>
     -+
     -+void reftable_set_alloc(void *(*malloc)(size_t),
     -+			void *(*realloc)(void *, size_t), void (*free)(void *));
     -+
     -+/****************************************************************
     -+ Basic data types
     -+
     -+ Reftables store the state of each ref in struct reftable_ref_record, and they
     -+ store a sequence of reflog updates in struct reftable_log_record.
     -+ ****************************************************************/
     -+
     -+/* reftable_ref_record holds a ref database entry target_value */
     -+struct reftable_ref_record {
     -+	char *refname; /* Name of the ref, malloced. */
     -+	uint64_t update_index; /* Logical timestamp at which this value is
     -+				  written */
     -+	uint8_t *value; /* SHA1, or NULL. malloced. */
     -+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
     -+	char *target; /* symref, or NULL. malloced. */
     -+};
     -+
     -+/* returns whether 'ref' represents a deletion */
     -+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
     -+
     -+/* prints a reftable_ref_record onto stdout */
     -+void reftable_ref_record_print(struct reftable_ref_record *ref,
     -+			       uint32_t hash_id);
     -+
     -+/* frees and nulls all pointer values. */
     -+void reftable_ref_record_clear(struct reftable_ref_record *ref);
     -+
     -+/* returns whether two reftable_ref_records are the same */
     -+int reftable_ref_record_equal(struct reftable_ref_record *a,
     -+			      struct reftable_ref_record *b, int hash_size);
     -+
     -+/* reftable_log_record holds a reflog entry */
     -+struct reftable_log_record {
     -+	char *refname;
     -+	uint64_t update_index; /* logical timestamp of a transactional update.
     -+				*/
     -+	uint8_t *new_hash;
     -+	uint8_t *old_hash;
     -+	char *name;
     -+	char *email;
     -+	uint64_t time;
     -+	int16_t tz_offset;
     -+	char *message;
     -+};
     -+
     -+/* returns whether 'ref' represents the deletion of a log record. */
     -+int reftable_log_record_is_deletion(const struct reftable_log_record *log);
     -+
     -+/* frees and nulls all pointer values. */
     -+void reftable_log_record_clear(struct reftable_log_record *log);
     -+
     -+/* returns whether two records are equal. */
     -+int reftable_log_record_equal(struct reftable_log_record *a,
     -+			      struct reftable_log_record *b, int hash_size);
     -+
     -+/* dumps a reftable_log_record on stdout, for debugging/testing. */
     -+void reftable_log_record_print(struct reftable_log_record *log,
     -+			       uint32_t hash_id);
     ++#include "reftable-error.h"
     ++
     ++#include <stdio.h>
     ++
     ++const char *reftable_error_str(int err)
     ++{
     ++	static char buf[250];
     ++	switch (err) {
     ++	case REFTABLE_IO_ERROR:
     ++		return "I/O error";
     ++	case REFTABLE_FORMAT_ERROR:
     ++		return "corrupt reftable file";
     ++	case REFTABLE_NOT_EXIST_ERROR:
     ++		return "file does not exist";
     ++	case REFTABLE_LOCK_ERROR:
     ++		return "data is outdated";
     ++	case REFTABLE_API_ERROR:
     ++		return "misuse of the reftable API";
     ++	case REFTABLE_ZLIB_ERROR:
     ++		return "zlib failure";
     ++	case REFTABLE_NAME_CONFLICT:
     ++		return "file/directory conflict";
     ++	case REFTABLE_REFNAME_ERROR:
     ++		return "invalid refname";
     ++	case -1:
     ++		return "general error";
     ++	default:
     ++		snprintf(buf, sizeof(buf), "unknown error code %d", err);
     ++		return buf;
     ++	}
     ++}
     +
     + ## reftable/reftable-error.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+/****************************************************************
     -+ Error handling
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
      +
     -+ Error are signaled with negative integer return values. 0 means success.
     -+ ****************************************************************/
     ++#ifndef REFTABLE_ERROR_H
     ++#define REFTABLE_ERROR_H
      +
     -+/* different types of errors */
     ++/*
     ++ Errors in reftable calls are signaled with negative integer return values. 0
     ++ means success.
     ++*/
      +enum reftable_error {
      +	/* Unexpected file system behavior */
      +	REFTABLE_IO_ERROR = -2,
     @@ reftable/reftable.h (new)
      +	   - on writing a log record with multiline message with
      +	   exact_log_message unset
      +	   - on reading a reftable_ref_record from log iterator, or vice versa.
     ++
     ++	  When a call misuses the API, the internal state of the library is kept
     ++	  unchanged.
      +	*/
      +	REFTABLE_API_ERROR = -6,
      +
     @@ reftable/reftable.h (new)
      + * deallocated. */
      +const char *reftable_error_str(int err);
      +
     -+/*
     -+ * Convert the numeric error code to an equivalent errno code.
     -+ */
     -+int reftable_error_to_errno(int err);
     -+
     -+/****************************************************************
     -+ Writing
     -+
     -+ Writing single reftables
     -+ ****************************************************************/
     -+
     -+/* reftable_write_options sets options for writing a single reftable. */
     -+struct reftable_write_options {
     -+	/* boolean: do not pad out blocks to block size. */
     -+	int unpadded;
     -+
     -+	/* the blocksize. Should be less than 2^24. */
     -+	uint32_t block_size;
     -+
     -+	/* boolean: do not generate a SHA1 => ref index. */
     -+	int skip_index_objects;
     -+
     -+	/* how often to write complete keys in each block. */
     -+	int restart_interval;
     -+
     -+	/* 4-byte identifier ("sha1", "s256") of the hash.
     -+	 * Defaults to SHA1 if unset
     -+	 */
     -+	uint32_t hash_id;
     -+
     -+	/* boolean: do not check ref names for validity or dir/file conflicts.
     -+	 */
     -+	int skip_name_check;
     -+
     -+	/* boolean: copy log messages exactly. If unset, check that the message
     -+	 *   is a single line, and add '\n' if missing.
     -+	 */
     -+	int exact_log_message;
     -+};
     -+
     -+/* reftable_block_stats holds statistics for a single block type */
     -+struct reftable_block_stats {
     -+	/* total number of entries written */
     -+	int entries;
     -+	/* total number of key restarts */
     -+	int restarts;
     -+	/* total number of blocks */
     -+	int blocks;
     -+	/* total number of index blocks */
     -+	int index_blocks;
     -+	/* depth of the index */
     -+	int max_index_level;
     -+
     -+	/* offset of the first block for this type */
     -+	uint64_t offset;
     -+	/* offset of the top level index block for this type, or 0 if not
     -+	 * present */
     -+	uint64_t index_offset;
     -+};
     -+
     -+/* stats holds overall statistics for a single reftable */
     -+struct reftable_stats {
     -+	/* total number of blocks written. */
     -+	int blocks;
     -+	/* stats for ref data */
     -+	struct reftable_block_stats ref_stats;
     -+	/* stats for the SHA1 to ref map. */
     -+	struct reftable_block_stats obj_stats;
     -+	/* stats for index blocks */
     -+	struct reftable_block_stats idx_stats;
     -+	/* stats for log blocks */
     -+	struct reftable_block_stats log_stats;
     -+
     -+	/* disambiguation length of shortened object IDs. */
     -+	int object_id_len;
     -+};
     -+
     -+/* reftable_new_writer creates a new writer */
     -+struct reftable_writer *
     -+reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
     -+		    void *writer_arg, struct reftable_write_options *opts);
     -+
     -+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
     -+int reftable_fd_write(void *fdp, const void *data, size_t size);
     -+
     -+/* Set the range of update indices for the records we will add.  When
     -+   writing a table into a stack, the min should be at least
     -+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
     -+
     -+   For transactional updates, typically min==max. When converting an existing
     -+   ref database into a single reftable, this would be a range of update-index
     -+   timestamps.
     -+ */
     -+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
     -+				uint64_t max);
     -+
     -+/* adds a reftable_ref_record. Must be called in ascending
     -+   order. The update_index must be within the limits set by
     -+   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned.
     -+
     -+   It is an error to write a ref record after a log record.
     -+ */
     -+int reftable_writer_add_ref(struct reftable_writer *w,
     -+			    struct reftable_ref_record *ref);
     -+
     -+/* Convenience function to add multiple refs. Will sort the refs by
     -+   name before adding. */
     -+int reftable_writer_add_refs(struct reftable_writer *w,
     -+			     struct reftable_ref_record *refs, int n);
     -+
     -+/* adds a reftable_log_record. Must be called in ascending order (with more
     -+   recent log entries first.)
     -+ */
     -+int reftable_writer_add_log(struct reftable_writer *w,
     -+			    struct reftable_log_record *log);
     -+
     -+/* Convenience function to add multiple logs. Will sort the records by
     -+   key before adding. */
     -+int reftable_writer_add_logs(struct reftable_writer *w,
     -+			     struct reftable_log_record *logs, int n);
     -+
     -+/* reftable_writer_close finalizes the reftable. The writer is retained so
     -+ * statistics can be inspected. */
     -+int reftable_writer_close(struct reftable_writer *w);
     -+
     -+/* writer_stats returns the statistics on the reftable being written.
     -+
     -+   This struct becomes invalid when the writer is freed.
     -+ */
     -+const struct reftable_stats *writer_stats(struct reftable_writer *w);
     -+
     -+/* reftable_writer_free deallocates memory for the writer */
     -+void reftable_writer_free(struct reftable_writer *w);
     -+
     -+/****************************************************************
     -+ * ITERATING
     -+ ****************************************************************/
     -+
     -+/* iterator is the generic interface for walking over data stored in a
     -+   reftable. It is generally passed around by value.
     -+*/
     -+struct reftable_iterator {
     -+	struct reftable_iterator_vtable *ops;
     -+	void *iter_arg;
     -+};
     -+
     -+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
     -+   end of iteration.
     -+*/
     -+int reftable_iterator_next_ref(struct reftable_iterator *it,
     -+			       struct reftable_ref_record *ref);
     -+
     -+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
     -+   end of iteration.
     -+*/
     -+int reftable_iterator_next_log(struct reftable_iterator *it,
     -+			       struct reftable_log_record *log);
     -+
     -+/* releases resources associated with an iterator. */
     -+void reftable_iterator_destroy(struct reftable_iterator *it);
     -+
     -+/****************************************************************
     -+ Reading single tables
     -+
     -+ The follow routines are for reading single files. For an application-level
     -+ interface, skip ahead to struct reftable_merged_table and struct
     -+ reftable_stack.
     -+ ****************************************************************/
     -+
     -+/* block_source is a generic wrapper for a seekable readable file.
     -+   It is generally passed around by value.
     -+ */
     -+struct reftable_block_source {
     -+	struct reftable_block_source_vtable *ops;
     -+	void *arg;
     -+};
     -+
     -+/* a contiguous segment of bytes. It keeps track of its generating block_source
     -+   so it can return itself into the pool.
     -+*/
     -+struct reftable_block {
     -+	uint8_t *data;
     -+	int len;
     -+	struct reftable_block_source source;
     -+};
     -+
     -+/* block_source_vtable are the operations that make up block_source */
     -+struct reftable_block_source_vtable {
     -+	/* returns the size of a block source */
     -+	uint64_t (*size)(void *source);
     -+
     -+	/* reads a segment from the block source. It is an error to read
     -+	   beyond the end of the block */
     -+	int (*read_block)(void *source, struct reftable_block *dest,
     -+			  uint64_t off, uint32_t size);
     -+	/* mark the block as read; may return the data back to malloc */
     -+	void (*return_block)(void *source, struct reftable_block *blockp);
     -+
     -+	/* release all resources associated with the block source */
     -+	void (*close)(void *source);
     -+};
     -+
     -+/* opens a file on the file system as a block_source */
     -+int reftable_block_source_from_file(struct reftable_block_source *block_src,
     -+				    const char *name);
     -+
     -+/* The reader struct is a handle to an open reftable file. */
     -+struct reftable_reader;
     -+
     -+/* reftable_new_reader opens a reftable for reading. If successful, returns 0
     -+ * code and sets pp. The name is used for creating a stack. Typically, it is the
     -+ * basename of the file. The block source `src` is owned by the reader, and is
     -+ * closed on calling reftable_reader_destroy().
     -+ */
     -+int reftable_new_reader(struct reftable_reader **pp,
     -+			struct reftable_block_source *src, const char *name);
     -+
     -+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted
     -+   in the table.  To seek to the start of the table, use name = "".
     -+
     -+   example:
     -+
     -+   struct reftable_reader *r = NULL;
     -+   int err = reftable_new_reader(&r, &src, "filename");
     -+   if (err < 0) { ... }
     -+   struct reftable_iterator it  = {0};
     -+   err = reftable_reader_seek_ref(r, &it, "refs/heads/master");
     -+   if (err < 0) { ... }
     -+   struct reftable_ref_record ref  = {0};
     -+   while (1) {
     -+     err = reftable_iterator_next_ref(&it, &ref);
     -+     if (err > 0) {
     -+       break;
     -+     }
     -+     if (err < 0) {
     -+       ..error handling..
     -+     }
     -+     ..found..
     -+   }
     -+   reftable_iterator_destroy(&it);
     -+   reftable_ref_record_clear(&ref);
     -+ */
     -+int reftable_reader_seek_ref(struct reftable_reader *r,
     -+			     struct reftable_iterator *it, const char *name);
     -+
     -+/* returns the hash ID used in this table. */
     -+uint32_t reftable_reader_hash_id(struct reftable_reader *r);
     -+
     -+/* seek to logs for the given name, older than update_index. To seek to the
     -+   start of the table, use name = "".
     -+ */
     -+int reftable_reader_seek_log_at(struct reftable_reader *r,
     -+				struct reftable_iterator *it, const char *name,
     -+				uint64_t update_index);
     -+
     -+/* seek to newest log entry for given name. */
     -+int reftable_reader_seek_log(struct reftable_reader *r,
     -+			     struct reftable_iterator *it, const char *name);
     -+
     -+/* closes and deallocates a reader. */
     -+void reftable_reader_free(struct reftable_reader *);
     -+
     -+/* return an iterator for the refs pointing to `oid`. */
     -+int reftable_reader_refs_for(struct reftable_reader *r,
     -+			     struct reftable_iterator *it, uint8_t *oid);
     -+
     -+/* return the max_update_index for a table */
     -+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);
     -+
     -+/* return the min_update_index for a table */
     -+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);
     -+
     -+/****************************************************************
     -+ Merged tables
     -+
     -+ A ref database kept in a sequence of table files. The merged_table presents a
     -+ unified view to reading (seeking, iterating) a sequence of immutable tables.
     -+ ****************************************************************/
     -+
     -+/* A merged table is implements seeking/iterating over a stack of tables. */
     -+struct reftable_merged_table;
     -+
     -+/* A generic reftable; see below. */
     -+struct reftable_table;
     -+
     -+/* reftable_new_merged_table creates a new merged table. It takes ownership of
     -+   the stack array.
     -+*/
     -+int reftable_new_merged_table(struct reftable_merged_table **dest,
     -+			      struct reftable_table *stack, int n,
     -+			      uint32_t hash_id);
     -+
     -+/* returns an iterator positioned just before 'name' */
     -+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
     -+				   struct reftable_iterator *it,
     -+				   const char *name);
     -+
     -+/* returns an iterator for log entry, at given update_index */
     -+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
     -+				      struct reftable_iterator *it,
     -+				      const char *name, uint64_t update_index);
     -+
     -+/* like reftable_merged_table_seek_log_at but look for the newest entry. */
     -+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
     -+				   struct reftable_iterator *it,
     -+				   const char *name);
     -+
     -+/* returns the max update_index covered by this merged table. */
     -+uint64_t
     -+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
     -+
     -+/* returns the min update_index covered by this merged table. */
     -+uint64_t
     -+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
     -+
     -+/* releases memory for the merged_table */
     -+void reftable_merged_table_free(struct reftable_merged_table *m);
     -+
     -+/* return the hash ID of the merged table. */
     -+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);
     -+
     -+/****************************************************************
     -+ Generic tables
     -+
     -+ A unified API for reading tables, either merged tables, or single readers.
     -+ ****************************************************************/
     -+
     -+struct reftable_table {
     -+	struct reftable_table_vtable *ops;
     -+	void *table_arg;
     -+};
     -+
     -+int reftable_table_seek_ref(struct reftable_table *tab,
     -+			    struct reftable_iterator *it, const char *name);
     -+
     -+void reftable_table_from_reader(struct reftable_table *tab,
     -+				struct reftable_reader *reader);
     -+
     -+/* returns the hash ID from a generic reftable_table */
     -+uint32_t reftable_table_hash_id(struct reftable_table *tab);
     -+
     -+/* create a generic table from reftable_merged_table */
     -+void reftable_table_from_merged_table(struct reftable_table *tab,
     -+				      struct reftable_merged_table *table);
     -+
     -+/* returns the max update_index covered by this table. */
     -+uint64_t reftable_table_max_update_index(struct reftable_table *tab);
     -+
     -+/* returns the min update_index covered by this table. */
     -+uint64_t reftable_table_min_update_index(struct reftable_table *tab);
     -+
     -+/* convenience function to read a single ref. Returns < 0 for error, 0
     -+   for success, and 1 if ref not found. */
     -+int reftable_table_read_ref(struct reftable_table *tab, const char *name,
     -+			    struct reftable_ref_record *ref);
     -+
     -+/****************************************************************
     -+ Mutable ref database
     -+
     -+ The stack presents an interface to a mutable sequence of reftables.
     -+ ****************************************************************/
     -+
     -+/* a stack is a stack of reftables, which can be mutated by pushing a table to
     -+ * the top of the stack */
     -+struct reftable_stack;
     -+
     -+/* open a new reftable stack. The tables along with the table list will be
     -+   stored in 'dir'. Typically, this should be .git/reftables.
     -+*/
     -+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
     -+		       struct reftable_write_options config);
     -+
     -+/* returns the update_index at which a next table should be written. */
     -+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
     -+
     -+/* holds a transaction to add tables at the top of a stack. */
     -+struct reftable_addition;
     -+
     -+/*
     -+  returns a new transaction to add reftables to the given stack. As a side
     -+  effect, the ref database is locked.
     -+*/
     -+int reftable_stack_new_addition(struct reftable_addition **dest,
     -+				struct reftable_stack *st);
     -+
     -+/* Adds a reftable to transaction. */
     -+int reftable_addition_add(struct reftable_addition *add,
     -+			  int (*write_table)(struct reftable_writer *wr,
     -+					     void *arg),
     -+			  void *arg);
     -+
     -+/* Commits the transaction, releasing the lock. */
     -+int reftable_addition_commit(struct reftable_addition *add);
     -+
     -+/* Release all non-committed data from the transaction, and deallocate the
     -+   transaction. Releases the lock if held. */
     -+void reftable_addition_destroy(struct reftable_addition *add);
     -+
     -+/* add a new table to the stack. The write_table function must call
     -+   reftable_writer_set_limits, add refs and return an error value. */
     -+int reftable_stack_add(struct reftable_stack *st,
     -+		       int (*write_table)(struct reftable_writer *wr,
     -+					  void *write_arg),
     -+		       void *write_arg);
     -+
     -+/* returns the merged_table for seeking. This table is valid until the
     -+   next write or reload, and should not be closed or deleted.
     -+*/
     -+struct reftable_merged_table *
     -+reftable_stack_merged_table(struct reftable_stack *st);
     -+
     -+/* frees all resources associated with the stack. */
     -+void reftable_stack_destroy(struct reftable_stack *st);
     -+
     -+/* Reloads the stack if necessary. This is very cheap to run if the stack was up
     -+ * to date */
     -+int reftable_stack_reload(struct reftable_stack *st);
     -+
     -+/* Policy for expiring reflog entries. */
     -+struct reftable_log_expiry_config {
     -+	/* Drop entries older than this timestamp */
     -+	uint64_t time;
     -+
     -+	/* Drop older entries */
     -+	uint64_t min_update_index;
     -+};
     -+
     -+/* compacts all reftables into a giant table. Expire reflog entries if config is
     -+ * non-NULL */
     -+int reftable_stack_compact_all(struct reftable_stack *st,
     -+			       struct reftable_log_expiry_config *config);
     -+
     -+/* heuristically compact unbalanced table stack. */
     -+int reftable_stack_auto_compact(struct reftable_stack *st);
     -+
     -+/* convenience function to read a single ref. Returns < 0 for error, 0
     -+   for success, and 1 if ref not found. */
     -+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
     -+			    struct reftable_ref_record *ref);
     -+
     -+/* convenience function to read a single log. Returns < 0 for error, 0
     -+   for success, and 1 if ref not found. */
     -+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
     -+			    struct reftable_log_record *log);
     -+
     -+/* statistics on past compactions. */
     -+struct reftable_compaction_stats {
     -+	uint64_t bytes; /* total number of bytes written */
     -+	uint64_t entries_written; /* total number of entries written, including
     -+				     failures. */
     -+	int attempts; /* how often we tried to compact */
     -+	int failures; /* failures happen on concurrent updates */
     -+};
     -+
     -+/* return statistics for compaction up till now. */
     -+struct reftable_compaction_stats *
     -+reftable_stack_compaction_stats(struct reftable_stack *st);
     -+
      +#endif
  3:  01a669a731 <  -:  ---------- vcxproj: adjust for the reftable changes
  5:  4190da597e !  5:  987d1d186f reftable: utility functions
     @@ Commit message
      
          This commit provides basic utility classes for the reftable library.
      
     -    Since the reftable library must compile standalone, there may be some overlap
     -    with git-core utility functions.
     -
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
     @@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)
      +REFTABLE_LIB = reftable/libreftable.a
      +REFTABLE_TEST_LIB = reftable/libreftable_test.a
       
     - GENERATED_H += config-list.h
       GENERATED_H += command-list.h
     + GENERATED_H += config-list.h
      @@ Makefile: THIRD_PARTY_SOURCES += compat/regex/%
       THIRD_PARTY_SOURCES += sha1collisiondetection/%
       THIRD_PARTY_SOURCES += sha1dc/%
     @@ Makefile: XDIFF_OBJS += xdiff/xpatience.o
       XDIFF_OBJS += xdiff/xutils.o
       
      +REFTABLE_OBJS += reftable/basics.o
     -+REFTABLE_OBJS += reftable/blocksource.o
     ++REFTABLE_OBJS += reftable/error.o
      +REFTABLE_OBJS += reftable/publicbasics.o
     -+REFTABLE_OBJS += reftable/compat.o
     -+REFTABLE_OBJS += reftable/strbuf.o
      +
     -+REFTABLE_TEST_OBJS += reftable/strbuf_test.o
      +REFTABLE_TEST_OBJS += reftable/test_framework.o
     ++REFTABLE_TEST_OBJS += reftable/basics_test.o
      +
       TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
       OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
     @@ reftable/basics.c (new)
      +	}
      +
      +	return *a == *b;
     ++}
     ++
     ++int common_prefix_size(struct strbuf *a, struct strbuf *b)
     ++{
     ++	int p = 0;
     ++	while (p < a->len && p < b->len) {
     ++		if (a->buf[p] != b->buf[p]) {
     ++			break;
     ++		}
     ++		p++;
     ++	}
     ++
     ++	return p;
      +}
      
       ## reftable/basics.h (new) ##
     @@ reftable/basics.h (new)
      +#ifndef BASICS_H
      +#define BASICS_H
      +
     ++/*
     ++ * miscellaneous utilities that are not provided by Git.
     ++ */
     ++
      +#include "system.h"
      +
      +/* Bigendian en/decoding of integers */
     @@ reftable/basics.h (new)
      +void reftable_free(void *p);
      +void *reftable_calloc(size_t sz);
      +
     ++/* Find the longest shared prefix size of `a` and `b` */
     ++struct strbuf;
     ++int common_prefix_size(struct strbuf *a, struct strbuf *b);
     ++
      +#endif
      
     - ## reftable/blocksource.c (new) ##
     + ## reftable/basics_test.c (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/blocksource.c (new)
      +#include "system.h"
      +
      +#include "basics.h"
     -+#include "blocksource.h"
     -+#include "strbuf.h"
     -+#include "reftable.h"
     -+
     -+static void strbuf_return_block(void *b, struct reftable_block *dest)
     -+{
     -+	memset(dest->data, 0xff, dest->len);
     -+	reftable_free(dest->data);
     -+}
     -+
     -+static void strbuf_close(void *b)
     -+{
     -+}
     -+
     -+static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,
     -+			     uint32_t size)
     -+{
     -+	struct strbuf *b = (struct strbuf *)v;
     -+	assert(off + size <= b->len);
     -+	dest->data = reftable_calloc(size);
     -+	memcpy(dest->data, b->buf + off, size);
     -+	dest->len = size;
     -+	return size;
     -+}
     -+
     -+static uint64_t strbuf_size(void *b)
     -+{
     -+	return ((struct strbuf *)b)->len;
     -+}
     -+
     -+static struct reftable_block_source_vtable strbuf_vtable = {
     -+	.size = &strbuf_size,
     -+	.read_block = &strbuf_read_block,
     -+	.return_block = &strbuf_return_block,
     -+	.close = &strbuf_close,
     -+};
     -+
     -+void block_source_from_strbuf(struct reftable_block_source *bs,
     -+			      struct strbuf *buf)
     -+{
     -+	assert(bs->ops == NULL);
     -+	bs->ops = &strbuf_vtable;
     -+	bs->arg = buf;
     -+}
     -+
     -+static void malloc_return_block(void *b, struct reftable_block *dest)
     -+{
     -+	memset(dest->data, 0xff, dest->len);
     -+	reftable_free(dest->data);
     -+}
     -+
     -+static struct reftable_block_source_vtable malloc_vtable = {
     -+	.return_block = &malloc_return_block,
     -+};
     -+
     -+static struct reftable_block_source malloc_block_source_instance = {
     -+	.ops = &malloc_vtable,
     -+};
     -+
     -+struct reftable_block_source malloc_block_source(void)
     -+{
     -+	return malloc_block_source_instance;
     -+}
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
      +
     -+struct file_block_source {
     -+	int fd;
     -+	uint64_t size;
     ++struct binsearch_args {
     ++	int key;
     ++	int *arr;
      +};
      +
     -+static uint64_t file_size(void *b)
     ++static int binsearch_func(size_t i, void *void_args)
      +{
     -+	return ((struct file_block_source *)b)->size;
     -+}
     ++	struct binsearch_args *args = (struct binsearch_args *)void_args;
      +
     -+static void file_return_block(void *b, struct reftable_block *dest)
     -+{
     -+	memset(dest->data, 0xff, dest->len);
     -+	reftable_free(dest->data);
     ++	return args->key < args->arr[i];
      +}
      +
     -+static void file_close(void *b)
     -+{
     -+	int fd = ((struct file_block_source *)b)->fd;
     -+	if (fd > 0) {
     -+		close(fd);
     -+		((struct file_block_source *)b)->fd = 0;
     -+	}
     -+
     -+	reftable_free(b);
     -+}
     -+
     -+static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,
     -+			   uint32_t size)
     -+{
     -+	struct file_block_source *b = (struct file_block_source *)v;
     -+	assert(off + size <= b->size);
     -+	dest->data = reftable_malloc(size);
     -+	if (pread(b->fd, dest->data, size, off) != size)
     -+		return -1;
     -+	dest->len = size;
     -+	return size;
     -+}
     -+
     -+static struct reftable_block_source_vtable file_vtable = {
     -+	.size = &file_size,
     -+	.read_block = &file_read_block,
     -+	.return_block = &file_return_block,
     -+	.close = &file_close,
     -+};
     -+
     -+int reftable_block_source_from_file(struct reftable_block_source *bs,
     -+				    const char *name)
     ++static void test_binsearch(void)
      +{
     -+	struct stat st = { 0 };
     -+	int err = 0;
     -+	int fd = open(name, O_RDONLY);
     -+	struct file_block_source *p = NULL;
     -+	if (fd < 0) {
     -+		if (errno == ENOENT) {
     -+			return REFTABLE_NOT_EXIST_ERROR;
     -+		}
     -+		return -1;
     -+	}
     -+
     -+	err = fstat(fd, &st);
     -+	if (err < 0)
     -+		return -1;
     -+
     -+	p = reftable_calloc(sizeof(struct file_block_source));
     -+	p->size = st.st_size;
     -+	p->fd = fd;
     -+
     -+	assert(bs->ops == NULL);
     -+	bs->ops = &file_vtable;
     -+	bs->arg = p;
     -+	return 0;
     -+}
     -
     - ## reftable/blocksource.h (new) ##
     -@@
     -+/*
     -+Copyright 2020 Google LLC
     -+
     -+Use of this source code is governed by a BSD-style
     -+license that can be found in the LICENSE file or at
     -+https://developers.google.com/open-source/licenses/bsd
     -+*/
     -+
     -+#ifndef BLOCKSOURCE_H
     -+#define BLOCKSOURCE_H
     -+
     -+#include "strbuf.h"
     -+
     -+struct reftable_block_source;
     -+
     -+/* Create an in-memory block source for reading reftables */
     -+void block_source_from_strbuf(struct reftable_block_source *bs,
     -+			      struct strbuf *buf);
     ++	int arr[] = { 2, 4, 6, 8, 10 };
     ++	size_t sz = ARRAY_SIZE(arr);
     ++	struct binsearch_args args = {
     ++		.arr = arr,
     ++	};
      +
     -+struct reftable_block_source malloc_block_source(void);
     -+
     -+#endif
     -
     - ## reftable/compat.c (new) ##
     -@@
     -+/*
     -+Copyright 2020 Google LLC
     -+
     -+Use of this source code is governed by a BSD-style
     -+license that can be found in the LICENSE file or at
     -+https://developers.google.com/open-source/licenses/bsd
     -+
     -+*/
     -+
     -+/* compat.c - compatibility functions for standalone compilation */
     -+
     -+#include "system.h"
     -+#include "basics.h"
     -+
     -+#ifdef REFTABLE_STANDALONE
     -+
     -+#include <dirent.h>
     -+
     -+void put_be32(void *p, uint32_t i)
     -+{
     -+	uint8_t *out = (uint8_t *)p;
     -+
     -+	out[0] = (uint8_t)((i >> 24) & 0xff);
     -+	out[1] = (uint8_t)((i >> 16) & 0xff);
     -+	out[2] = (uint8_t)((i >> 8) & 0xff);
     -+	out[3] = (uint8_t)((i)&0xff);
     -+}
     -+
     -+uint32_t get_be32(uint8_t *in)
     -+{
     -+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     -+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
     -+}
     -+
     -+void put_be64(void *p, uint64_t v)
     -+{
     -+	uint8_t *out = (uint8_t *)p;
     -+	int i = sizeof(uint64_t);
     -+	while (i--) {
     -+		out[i] = (uint8_t)(v & 0xff);
     -+		v >>= 8;
     -+	}
     -+}
     -+
     -+uint64_t get_be64(void *out)
     -+{
     -+	uint8_t *bytes = (uint8_t *)out;
     -+	uint64_t v = 0;
      +	int i = 0;
     -+	for (i = 0; i < sizeof(uint64_t); i++) {
     -+		v = (v << 8) | (uint8_t)(bytes[i] & 0xff);
     -+	}
     -+	return v;
     -+}
     -+
     -+uint16_t get_be16(uint8_t *in)
     -+{
     -+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
     -+}
     -+
     -+char *xstrdup(const char *s)
     -+{
     -+	int l = strlen(s);
     -+	char *dest = (char *)reftable_malloc(l + 1);
     -+	strncpy(dest, s, l + 1);
     -+	return dest;
     -+}
     -+
     -+void sleep_millisec(int millisecs)
     -+{
     -+	usleep(millisecs * 1000);
     -+}
     -+
     -+void reftable_clear_dir(const char *dirname)
     -+{
     -+	DIR *dir = opendir(dirname);
     -+	struct dirent *ent = NULL;
     -+	assert(dir);
     -+	while ((ent = readdir(dir)) != NULL) {
     -+		unlinkat(dirfd(dir), ent->d_name, 0);
     ++	for (i = 1; i < 11; i++) {
     ++		int res;
     ++		args.key = i;
     ++		res = binsearch(sz, &binsearch_func, &args);
     ++
     ++		if (res < sz) {
     ++			EXPECT(args.key < arr[res]);
     ++			if (res > 0) {
     ++				EXPECT(args.key >= arr[res - 1]);
     ++			}
     ++		} else {
     ++			EXPECT(args.key == 10 || args.key == 11);
     ++		}
      +	}
     -+	closedir(dir);
     -+	rmdir(dirname);
      +}
      +
     -+#else
     -+
     -+#include "../dir.h"
     -+
     -+void reftable_clear_dir(const char *dirname)
     ++int basics_test_main(int argc, const char *argv[])
      +{
     -+	struct strbuf path = STRBUF_INIT;
     -+	strbuf_addstr(&path, dirname);
     -+	remove_dir_recursively(&path, 0);
     -+	strbuf_release(&path);
     -+}
     -+
     -+#endif
     -+
     -+int hash_size(uint32_t id)
     -+{
     -+	switch (id) {
     -+	case 0:
     -+	case SHA1_ID:
     -+		return SHA1_SIZE;
     -+	case SHA256_ID:
     -+		return SHA256_SIZE;
     -+	}
     -+	abort();
     ++	test_binsearch();
     ++	return 0;
      +}
      
       ## reftable/compat.h (new) ##
     @@ reftable/publicbasics.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "reftable.h"
     ++#include "reftable-malloc.h"
      +
      +#include "basics.h"
      +#include "system.h"
      +
     -+const char *reftable_error_str(int err)
     -+{
     -+	static char buf[250];
     -+	switch (err) {
     -+	case REFTABLE_IO_ERROR:
     -+		return "I/O error";
     -+	case REFTABLE_FORMAT_ERROR:
     -+		return "corrupt reftable file";
     -+	case REFTABLE_NOT_EXIST_ERROR:
     -+		return "file does not exist";
     -+	case REFTABLE_LOCK_ERROR:
     -+		return "data is outdated";
     -+	case REFTABLE_API_ERROR:
     -+		return "misuse of the reftable API";
     -+	case REFTABLE_ZLIB_ERROR:
     -+		return "zlib failure";
     -+	case REFTABLE_NAME_CONFLICT:
     -+		return "file/directory conflict";
     -+	case REFTABLE_REFNAME_ERROR:
     -+		return "invalid refname";
     -+	case -1:
     -+		return "general error";
     -+	default:
     -+		snprintf(buf, sizeof(buf), "unknown error code %d", err);
     -+		return buf;
     -+	}
     -+}
     -+
     -+int reftable_error_to_errno(int err)
     -+{
     -+	switch (err) {
     -+	case REFTABLE_IO_ERROR:
     -+		return EIO;
     -+	case REFTABLE_FORMAT_ERROR:
     -+		return EFAULT;
     -+	case REFTABLE_NOT_EXIST_ERROR:
     -+		return ENOENT;
     -+	case REFTABLE_LOCK_ERROR:
     -+		return EBUSY;
     -+	case REFTABLE_API_ERROR:
     -+		return EINVAL;
     -+	case REFTABLE_ZLIB_ERROR:
     -+		return EDOM;
     -+	default:
     -+		return ERANGE;
     -+	}
     -+}
     -+
      +static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;
      +static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;
      +static void (*reftable_free_ptr)(void *) = &free;
     @@ reftable/publicbasics.c (new)
      +	reftable_free_ptr = free;
      +}
      +
     -+int reftable_fd_write(void *arg, const void *data, size_t sz)
     ++int hash_size(uint32_t id)
      +{
     -+	int *fdp = (int *)arg;
     -+	return write(*fdp, data, sz);
     ++	switch (id) {
     ++	case 0:
     ++	case SHA1_ID:
     ++		return SHA1_SIZE;
     ++	case SHA256_ID:
     ++		return SHA256_SIZE;
     ++	}
     ++	abort();
      +}
      
     - ## reftable/reftable-tests.h (new) ##
     + ## reftable/reftable-malloc.h (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/reftable-tests.h (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#ifndef REFTABLE_TESTS_H
     -+#define REFTABLE_TESTS_H
     ++#ifndef REFTABLE_H
     ++#define REFTABLE_H
      +
     -+int block_test_main(int argc, const char **argv);
     -+int merged_test_main(int argc, const char **argv);
     -+int record_test_main(int argc, const char **argv);
     -+int refname_test_main(int argc, const char **argv);
     -+int reftable_test_main(int argc, const char **argv);
     -+int strbuf_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);
     ++#include <stddef.h>
     ++
     ++/*
     ++  Overrides the functions to use for memory management.
     ++ */
     ++void reftable_set_alloc(void *(*malloc)(size_t),
     ++			void *(*realloc)(void *, size_t), void (*free)(void *));
      +
      +#endif
      
     - ## reftable/strbuf.c (new) ##
     + ## reftable/reftable-tests.h (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/strbuf.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "strbuf.h"
     -+
     -+#ifdef REFTABLE_STANDALONE
     -+
     -+void strbuf_init(struct strbuf *s, size_t alloc)
     -+{
     -+	struct strbuf empty = STRBUF_INIT;
     -+	*s = empty;
     -+}
     -+
     -+void strbuf_grow(struct strbuf *s, size_t extra)
     -+{
     -+	size_t newcap = s->len + extra + 1;
     -+	if (newcap > s->cap) {
     -+		s->buf = reftable_realloc(s->buf, newcap);
     -+		s->cap = newcap;
     -+	}
     -+}
     -+
     -+static void strbuf_resize(struct strbuf *s, int l)
     -+{
     -+	int zl = l + 1; /* one uint8_t for 0 termination. */
     -+	assert(s->canary == STRBUF_CANARY);
     -+	if (s->cap < zl) {
     -+		int c = s->cap * 2;
     -+		if (c < zl) {
     -+			c = zl;
     -+		}
     -+		s->cap = c;
     -+		s->buf = reftable_realloc(s->buf, s->cap);
     -+	}
     -+	s->len = l;
     -+	s->buf[l] = 0;
     -+}
     -+
     -+void strbuf_setlen(struct strbuf *s, size_t l)
     -+{
     -+	assert(s->cap >= l + 1);
     -+	s->len = l;
     -+	s->buf[l] = 0;
     -+}
     -+
     -+void strbuf_reset(struct strbuf *s)
     -+{
     -+	strbuf_resize(s, 0);
     -+}
     -+
     -+void strbuf_addstr(struct strbuf *d, const char *s)
     -+{
     -+	int l1 = d->len;
     -+	int l2 = strlen(s);
     -+	assert(d->canary == STRBUF_CANARY);
     -+
     -+	strbuf_resize(d, l2 + l1);
     -+	memcpy(d->buf + l1, s, l2);
     -+}
     -+
     -+void strbuf_addbuf(struct strbuf *s, struct strbuf *a)
     -+{
     -+	int end = s->len;
     -+	assert(s->canary == STRBUF_CANARY);
     -+	strbuf_resize(s, s->len + a->len);
     -+	memcpy(s->buf + end, a->buf, a->len);
     -+}
     -+
     -+char *strbuf_detach(struct strbuf *s, size_t *sz)
     -+{
     -+	char *p = NULL;
     -+	p = (char *)s->buf;
     -+	if (sz)
     -+		*sz = s->len;
     -+	s->buf = NULL;
     -+	s->cap = 0;
     -+	s->len = 0;
     -+	return p;
     -+}
     -+
     -+void strbuf_release(struct strbuf *s)
     -+{
     -+	assert(s->canary == STRBUF_CANARY);
     -+	s->cap = 0;
     -+	s->len = 0;
     -+	reftable_free(s->buf);
     -+	s->buf = NULL;
     -+}
     -+
     -+int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
     -+{
     -+	int min = a->len < b->len ? a->len : b->len;
     -+	int res = memcmp(a->buf, b->buf, min);
     -+	assert(a->canary == STRBUF_CANARY);
     -+	assert(b->canary == STRBUF_CANARY);
     -+	if (res != 0)
     -+		return res;
     -+	if (a->len < b->len)
     -+		return -1;
     -+	else if (a->len > b->len)
     -+		return 1;
     -+	else
     -+		return 0;
     -+}
     ++#ifndef REFTABLE_TESTS_H
     ++#define REFTABLE_TESTS_H
      +
     -+int strbuf_add(struct strbuf *b, const void *data, size_t sz)
     -+{
     -+	assert(b->canary == STRBUF_CANARY);
     -+	strbuf_grow(b, sz);
     -+	memcpy(b->buf + b->len, data, sz);
     -+	b->len += sz;
     -+	b->buf[b->len] = 0;
     -+	return sz;
     -+}
     ++int basics_test_main(int argc, const char **argv);
     ++int block_test_main(int argc, const char **argv);
     ++int merged_test_main(int argc, const char **argv);
     ++int record_test_main(int argc, const char **argv);
     ++int refname_test_main(int argc, const char **argv);
     ++int reftable_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
     -+
     -+int strbuf_add_void(void *b, const void *data, size_t sz)
     -+{
     -+	strbuf_add((struct strbuf *)b, data, sz);
     -+	return sz;
     -+}
     -+
     -+int common_prefix_size(struct strbuf *a, struct strbuf *b)
     -+{
     -+	int p = 0;
     -+	while (p < a->len && p < b->len) {
     -+		if (a->buf[p] != b->buf[p]) {
     -+			break;
     -+		}
     -+		p++;
     -+	}
     -+
     -+	return p;
     -+}
     -+
     -+struct strbuf reftable_empty_strbuf = STRBUF_INIT;
      
     - ## reftable/strbuf.h (new) ##
     + ## reftable/system.h (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/strbuf.h (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#ifndef SLICE_H
     -+#define SLICE_H
     -+
     -+#ifdef REFTABLE_STANDALONE
     -+
     -+#include "basics.h"
     -+
     -+/*
     -+  Provides a bounds-checked, growable byte ranges. To use, initialize as "strbuf
     -+  x = STRBUF_INIT;"
     -+ */
     -+struct strbuf {
     -+	size_t len;
     -+	size_t cap;
     -+	char *buf;
     -+
     -+	/* Used to enforce initialization with STRBUF_INIT */
     -+	uint8_t canary;
     -+};
     -+
     -+#define STRBUF_CANARY 0x42
     -+#define STRBUF_INIT                       \
     -+	{                                 \
     -+		0, 0, NULL, STRBUF_CANARY \
     -+	}
     -+
     -+void strbuf_addstr(struct strbuf *dest, const char *src);
     -+
     -+/* Deallocate and clear strbuf */
     -+void strbuf_release(struct strbuf *strbuf);
     -+
     -+/* Set strbuf to 0 length, but retain buffer. */
     -+void strbuf_reset(struct strbuf *strbuf);
     -+
     -+/* Initializes a strbuf. Accepts a strbuf with random garbage. */
     -+void strbuf_init(struct strbuf *strbuf, size_t alloc);
     -+
     -+/* Return `buf`, clearing out `s`. Optionally return len (not cap) in `sz`.  */
     -+char *strbuf_detach(struct strbuf *s, size_t *sz);
     -+
     -+/* Set length of the slace to `l`, but don't reallocated. */
     -+void strbuf_setlen(struct strbuf *s, size_t l);
     -+
     -+/* Ensure `l` bytes beyond current length are available */
     -+void strbuf_grow(struct strbuf *s, size_t l);
     -+
     -+/* Signed comparison */
     -+int strbuf_cmp(const struct strbuf *a, const struct strbuf *b);
     -+
     -+/* Append `data` to the `dest` strbuf.  */
     -+int strbuf_add(struct strbuf *dest, const void *data, size_t sz);
     ++#ifndef SYSTEM_H
     ++#define SYSTEM_H
      +
     -+/* Append `add` to `dest. */
     -+void strbuf_addbuf(struct strbuf *dest, struct strbuf *add);
     ++#include "git-compat-util.h"
     ++#include "strbuf.h"
      +
     -+#else
     ++#include <zlib.h>
      +
     -+#include "../git-compat-util.h"
     -+#include "../strbuf.h"
     ++struct strbuf;
     ++/* In git, this is declared in dir.h */
     ++int remove_dir_recursively(struct strbuf *path, int flags);
      +
     -+#endif
     -+
     -+extern struct strbuf reftable_empty_strbuf;
     ++#define SHA1_ID 0x73686131
     ++#define SHA256_ID 0x73323536
     ++#define SHA1_SIZE 20
     ++#define SHA256_SIZE 32
      +
     -+/* Like strbuf_add, but suitable for passing to reftable_new_writer
     ++/* This is uncompress2, which is only available in zlib as of 2017.
      + */
     -+int strbuf_add_void(void *b, const void *data, size_t sz);
     -+
     -+/* Find the longest shared prefix size of `a` and `b` */
     -+int common_prefix_size(struct strbuf *a, struct strbuf *b);
     ++int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
     ++			       const Bytef *source, uLong *sourceLen);
     ++int hash_size(uint32_t id);
      +
      +#endif
      
     - ## reftable/strbuf_test.c (new) ##
     + ## reftable/test_framework.c (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/strbuf_test.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "strbuf.h"
     -+
      +#include "system.h"
     ++#include "test_framework.h"
      +
      +#include "basics.h"
     -+#include "test_framework.h"
     -+#include "reftable-tests.h"
      +
     -+static void test_strbuf(void)
     ++void set_test_hash(uint8_t *p, int i)
      +{
     -+	struct strbuf s = STRBUF_INIT;
     -+	struct strbuf t = STRBUF_INIT;
     -+
     -+	strbuf_addstr(&s, "abc");
     -+	assert(0 == strcmp("abc", s.buf));
     -+
     -+	strbuf_addstr(&t, "pqr");
     -+	strbuf_addbuf(&s, &t);
     -+	assert(0 == strcmp("abcpqr", s.buf));
     -+
     -+	strbuf_release(&s);
     -+	strbuf_release(&t);
     ++	memset(p, (uint8_t)i, hash_size(SHA1_ID));
      +}
      +
     -+int strbuf_test_main(int argc, const char *argv[])
     ++int strbuf_add_void(void *b, const void *data, size_t sz)
      +{
     -+	add_test_case("test_strbuf", &test_strbuf);
     -+	return test_main(argc, argv);
     ++	strbuf_add((struct strbuf *)b, data, sz);
     ++	return sz;
      +}
      
     - ## reftable/system.h (new) ##
     + ## reftable/test_framework.h (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/system.h (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#ifndef SYSTEM_H
     -+#define SYSTEM_H
     -+
     -+#ifndef REFTABLE_STANDALONE
     -+
     -+#include "git-compat-util.h"
     -+#include "cache.h"
     -+#include <zlib.h>
     -+
     -+#else
     -+
     -+#include <assert.h>
     -+#include <errno.h>
     -+#include <fcntl.h>
     -+#include <inttypes.h>
     -+#include <stdint.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <sys/stat.h>
     -+#include <sys/time.h>
     -+#include <sys/types.h>
     -+#include <unistd.h>
     -+#include <zlib.h>
     ++#ifndef TEST_FRAMEWORK_H
     ++#define TEST_FRAMEWORK_H
      +
     -+#include "compat.h"
     ++#include "system.h"
     ++#include "reftable-error.h"
     ++
     ++#define EXPECT_ERR(c)                                                  \
     ++	if (c != 0) {                                                  \
     ++		fflush(stderr);                                        \
     ++		fflush(stdout);                                        \
     ++		fprintf(stderr, "%s: %d: error == %d (%s), want 0\n",  \
     ++			__FILE__, __LINE__, c, reftable_error_str(c)); \
     ++		abort();                                               \
     ++	}
      +
     -+#endif /* REFTABLE_STANDALONE */
     ++#define EXPECT_STREQ(a, b)                                               \
     ++	if (strcmp(a, b)) {                                              \
     ++		fflush(stderr);                                          \
     ++		fflush(stdout);                                          \
     ++		fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \
     ++			__LINE__, #a, a, #b, b);                         \
     ++		abort();                                                 \
     ++	}
      +
     -+void reftable_clear_dir(const char *dirname);
     ++#define EXPECT(c)                                                          \
     ++	if (!(c)) {                                                        \
     ++		fflush(stderr);                                            \
     ++		fflush(stdout);                                            \
     ++		fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \
     ++			__LINE__, #c);                                     \
     ++		abort();                                                   \
     ++	}
      +
     -+#define SHA1_ID 0x73686131
     -+#define SHA256_ID 0x73323536
     -+#define SHA1_SIZE 20
     -+#define SHA256_SIZE 32
     ++void set_test_hash(uint8_t *p, int i);
      +
     -+/* This is uncompress2, which is only available in zlib as of 2017.
     ++/* Like strbuf_add, but suitable for passing to reftable_new_writer
      + */
     -+int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
     -+			       const Bytef *source, uLong *sourceLen);
     -+int hash_size(uint32_t id);
     ++int strbuf_add_void(void *b, const void *data, size_t sz);
      +
      +#endif
      
     @@ t/helper/test-reftable.c (new)
      +
      +int cmd__reftable(int argc, const char **argv)
      +{
     -+	strbuf_test_main(argc, argv);
     ++	basics_test_main(argc, argv);
     ++
      +	return 0;
      +}
      
       ## t/helper/test-tool.c ##
      @@ t/helper/test-tool.c: static struct test_cmd cmds[] = {
     + 	{ "path-utils", cmd__path_utils },
     + 	{ "pkt-line", cmd__pkt_line },
     + 	{ "prio-queue", cmd__prio_queue },
     +-	{ "proc-receive", cmd__proc_receive},
     ++	{ "proc-receive", cmd__proc_receive },
     + 	{ "progress", cmd__progress },
     + 	{ "reach", cmd__reach },
     + 	{ "read-cache", cmd__read_cache },
       	{ "read-graph", cmd__read_graph },
       	{ "read-midx", cmd__read_midx },
       	{ "ref-store", cmd__ref_store },
     @@ t/helper/test-tool.h: int cmd__read_cache(int argc, const char **argv);
       int cmd__regex(int argc, const char **argv);
       int cmd__repository(int argc, const char **argv);
       int cmd__revision_walking(int argc, const char **argv);
     +
     + ## t/t0032-reftable-unittest.sh (new) ##
     +@@
     ++#!/bin/sh
     ++#
     ++# Copyright (c) 2020 Google LLC
     ++#
     ++
     ++test_description='reftable unittests'
     ++
     ++. ./test-lib.sh
     ++
     ++test_expect_success 'unittests' '
     ++       test-tool reftable
     ++'
     ++
     ++test_done
  4:  b94c5f5c61 !  6:  f71e82b995 reftable: add a barebones unittest framework
     @@ Metadata
      Author: Han-Wen Nienhuys <[email protected]>
      
       ## Commit message ##
     -    reftable: add a barebones unittest framework
     +    reftable: add blocksource, an abstraction for random access reads
     +
     +    The reftable format is usually used with files for storage. However, we abstract
     +    away this using the blocksource data structure. This has two advantages:
     +
     +    * log blocks are zlib compressed, and handling them is simplified if we can
     +      discard byte segments from within the block layer.
     +
     +    * for unittests, it is useful to read and write in-memory. The blocksource
     +      allows us to abstract the data away from on-disk files.
      
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
     - ## reftable/test_framework.c (new) ##
     + ## Makefile ##
     +@@ Makefile: XDIFF_OBJS += xdiff/xutils.o
     + 
     + REFTABLE_OBJS += reftable/basics.o
     + REFTABLE_OBJS += reftable/error.o
     ++REFTABLE_OBJS += reftable/blocksource.o
     + REFTABLE_OBJS += reftable/publicbasics.o
     + 
     + REFTABLE_TEST_OBJS += reftable/test_framework.o
     +
     + ## reftable/blocksource.c (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/test_framework.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "test_framework.h"
     -+
      +#include "system.h"
     ++
      +#include "basics.h"
     ++#include "blocksource.h"
     ++#include "reftable-blocksource.h"
     ++#include "reftable-error.h"
      +
     -+static struct test_case **test_cases;
     -+static int test_case_len;
     -+static int test_case_cap;
     ++static void strbuf_return_block(void *b, struct reftable_block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	reftable_free(dest->data);
     ++}
      +
     -+static struct test_case *new_test_case(const char *name, void (*testfunc)(void))
     ++static void strbuf_close(void *b)
      +{
     -+	struct test_case *tc = reftable_malloc(sizeof(struct test_case));
     -+	tc->name = name;
     -+	tc->testfunc = testfunc;
     -+	return tc;
      +}
      +
     -+struct test_case *add_test_case(const char *name, void (*testfunc)(void))
     ++static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,
     ++			     uint32_t size)
      +{
     -+	struct test_case *tc = new_test_case(name, testfunc);
     -+	if (test_case_len == test_case_cap) {
     -+		test_case_cap = 2 * test_case_cap + 1;
     -+		test_cases = reftable_realloc(
     -+			test_cases, sizeof(struct test_case) * test_case_cap);
     -+	}
     ++	struct strbuf *b = (struct strbuf *)v;
     ++	assert(off + size <= b->len);
     ++	dest->data = reftable_calloc(size);
     ++	memcpy(dest->data, b->buf + off, size);
     ++	dest->len = size;
     ++	return size;
     ++}
      +
     -+	test_cases[test_case_len++] = tc;
     -+	return tc;
     ++static uint64_t strbuf_size(void *b)
     ++{
     ++	return ((struct strbuf *)b)->len;
      +}
      +
     -+int test_main(int argc, const char *argv[])
     ++static struct reftable_block_source_vtable strbuf_vtable = {
     ++	.size = &strbuf_size,
     ++	.read_block = &strbuf_read_block,
     ++	.return_block = &strbuf_return_block,
     ++	.close = &strbuf_close,
     ++};
     ++
     ++void block_source_from_strbuf(struct reftable_block_source *bs,
     ++			      struct strbuf *buf)
      +{
     -+	const char *filter = NULL;
     -+	int i = 0;
     -+	if (argc > 1) {
     -+		filter = argv[1];
     -+	}
     ++	assert(bs->ops == NULL);
     ++	bs->ops = &strbuf_vtable;
     ++	bs->arg = buf;
     ++}
      +
     -+	for (i = 0; i < test_case_len; i++) {
     -+		const char *name = test_cases[i]->name;
     -+		if (filter == NULL || strstr(name, filter) != NULL) {
     -+			printf("case %s\n", name);
     -+			test_cases[i]->testfunc();
     -+		} else {
     -+			printf("skip %s\n", name);
     -+		}
     ++static void malloc_return_block(void *b, struct reftable_block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	reftable_free(dest->data);
     ++}
     ++
     ++static struct reftable_block_source_vtable malloc_vtable = {
     ++	.return_block = &malloc_return_block,
     ++};
     ++
     ++static struct reftable_block_source malloc_block_source_instance = {
     ++	.ops = &malloc_vtable,
     ++};
      +
     -+		reftable_free(test_cases[i]);
     ++struct reftable_block_source malloc_block_source(void)
     ++{
     ++	return malloc_block_source_instance;
     ++}
     ++
     ++struct file_block_source {
     ++	int fd;
     ++	uint64_t size;
     ++};
     ++
     ++static uint64_t file_size(void *b)
     ++{
     ++	return ((struct file_block_source *)b)->size;
     ++}
     ++
     ++static void file_return_block(void *b, struct reftable_block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	reftable_free(dest->data);
     ++}
     ++
     ++static void file_close(void *b)
     ++{
     ++	int fd = ((struct file_block_source *)b)->fd;
     ++	if (fd > 0) {
     ++		close(fd);
     ++		((struct file_block_source *)b)->fd = 0;
      +	}
     -+	reftable_free(test_cases);
     -+	test_cases = NULL;
     -+	test_case_len = 0;
     -+	test_case_cap = 0;
     -+	return 0;
     ++
     ++	reftable_free(b);
      +}
      +
     -+void set_test_hash(uint8_t *p, int i)
     ++static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,
     ++			   uint32_t size)
      +{
     -+	memset(p, (uint8_t)i, hash_size(SHA1_ID));
     ++	struct file_block_source *b = (struct file_block_source *)v;
     ++	assert(off + size <= b->size);
     ++	dest->data = reftable_malloc(size);
     ++	if (pread(b->fd, dest->data, size, off) != size)
     ++		return -1;
     ++	dest->len = size;
     ++	return size;
     ++}
     ++
     ++static struct reftable_block_source_vtable file_vtable = {
     ++	.size = &file_size,
     ++	.read_block = &file_read_block,
     ++	.return_block = &file_return_block,
     ++	.close = &file_close,
     ++};
     ++
     ++int reftable_block_source_from_file(struct reftable_block_source *bs,
     ++				    const char *name)
     ++{
     ++	struct stat st = { 0 };
     ++	int err = 0;
     ++	int fd = open(name, O_RDONLY);
     ++	struct file_block_source *p = NULL;
     ++	if (fd < 0) {
     ++		if (errno == ENOENT) {
     ++			return REFTABLE_NOT_EXIST_ERROR;
     ++		}
     ++		return -1;
     ++	}
     ++
     ++	err = fstat(fd, &st);
     ++	if (err < 0)
     ++		return -1;
     ++
     ++	p = reftable_calloc(sizeof(struct file_block_source));
     ++	p->size = st.st_size;
     ++	p->fd = fd;
     ++
     ++	assert(bs->ops == NULL);
     ++	bs->ops = &file_vtable;
     ++	bs->arg = p;
     ++	return 0;
      +}
      
     - ## reftable/test_framework.h (new) ##
     + ## reftable/blocksource.h (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/test_framework.h (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#ifndef TEST_FRAMEWORK_H
     -+#define TEST_FRAMEWORK_H
     ++#ifndef BLOCKSOURCE_H
     ++#define BLOCKSOURCE_H
      +
      +#include "system.h"
      +
     -+#ifdef NDEBUG
     -+#undef NDEBUG
     -+#endif
     ++struct reftable_block_source;
     ++
     ++/* Create an in-memory block source for reading reftables */
     ++void block_source_from_strbuf(struct reftable_block_source *bs,
     ++			      struct strbuf *buf);
     ++
     ++struct reftable_block_source malloc_block_source(void);
      +
     -+#ifdef assert
     -+#undef assert
      +#endif
     +
     + ## reftable/reftable-blocksource.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+#define assert_err(c)                                                  \
     -+	if (c != 0) {                                                  \
     -+		fflush(stderr);                                        \
     -+		fflush(stdout);                                        \
     -+		fprintf(stderr, "%s: %d: error == %d (%s), want 0\n",  \
     -+			__FILE__, __LINE__, c, reftable_error_str(c)); \
     -+		abort();                                               \
     -+	}
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
      +
     -+#define assert_streq(a, b)                                               \
     -+	if (strcmp(a, b)) {                                              \
     -+		fflush(stderr);                                          \
     -+		fflush(stdout);                                          \
     -+		fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \
     -+			__LINE__, #a, a, #b, b);                         \
     -+		abort();                                                 \
     -+	}
     ++#ifndef REFTABLE_BLOCKSOURCE_H
     ++#define REFTABLE_BLOCKSOURCE_H
      +
     -+#define assert(c)                                                          \
     -+	if (!(c)) {                                                        \
     -+		fflush(stderr);                                            \
     -+		fflush(stdout);                                            \
     -+		fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \
     -+			__LINE__, #c);                                     \
     -+		abort();                                                   \
     -+	}
     ++#include <stdint.h>
     ++
     ++/* block_source is a generic wrapper for a seekable readable file.
     ++ */
     ++struct reftable_block_source {
     ++	struct reftable_block_source_vtable *ops;
     ++	void *arg;
     ++};
      +
     -+struct test_case {
     -+	const char *name;
     -+	void (*testfunc)(void);
     ++/* a contiguous segment of bytes. It keeps track of its generating block_source
     ++   so it can return itself into the pool.
     ++*/
     ++struct reftable_block {
     ++	uint8_t *data;
     ++	int len;
     ++	struct reftable_block_source source;
      +};
      +
     -+struct test_case *add_test_case(const char *name, void (*testfunc)(void));
     -+int test_main(int argc, const char *argv[]);
     ++/* block_source_vtable are the operations that make up block_source */
     ++struct reftable_block_source_vtable {
     ++	/* returns the size of a block source */
     ++	uint64_t (*size)(void *source);
     ++
     ++	/* reads a segment from the block source. It is an error to read
     ++	   beyond the end of the block */
     ++	int (*read_block)(void *source, struct reftable_block *dest,
     ++			  uint64_t off, uint32_t size);
     ++	/* mark the block as read; may return the data back to malloc */
     ++	void (*return_block)(void *source, struct reftable_block *blockp);
     ++
     ++	/* release all resources associated with the block source */
     ++	void (*close)(void *source);
     ++};
      +
     -+void set_test_hash(uint8_t *p, int i);
     ++/* opens a file on the file system as a block_source */
     ++int reftable_block_source_from_file(struct reftable_block_source *block_src,
     ++				    const char *name);
      +
      +#endif
  6:  8eb944ea9b !  7:  bbe2ed7173 reftable: (de)serialization for the polymorphic record type.
     @@ Metadata
       ## Commit message ##
          reftable: (de)serialization for the polymorphic record type.
      
     +    The reftable format is structured as a sequence of blocks, and each block
     +    contains a sequence of prefix-compressed key-value records. There are 4 types of
     +    records, and they have similarities in how they must be handled. This is
     +    achieved by introducing a polymorphic 'record' type that encapsulates ref, log,
     +    index and object records.
     +
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
      @@ Makefile: REFTABLE_OBJS += reftable/basics.o
     + REFTABLE_OBJS += reftable/error.o
       REFTABLE_OBJS += reftable/blocksource.o
       REFTABLE_OBJS += reftable/publicbasics.o
     - REFTABLE_OBJS += reftable/compat.o
      +REFTABLE_OBJS += reftable/record.o
     - REFTABLE_OBJS += reftable/strbuf.o
       
      +REFTABLE_TEST_OBJS += reftable/record_test.o
     - REFTABLE_TEST_OBJS += reftable/strbuf_test.o
       REFTABLE_TEST_OBJS += reftable/test_framework.o
     + REFTABLE_TEST_OBJS += reftable/basics_test.o
       
      
       ## reftable/constants.h (new) ##
     @@ reftable/record.c (new)
      +
      +#include "system.h"
      +#include "constants.h"
     -+#include "reftable.h"
     ++#include "reftable-error.h"
      +#include "basics.h"
      +
      +int get_var_int(uint64_t *dest, struct string_view *in)
     @@ reftable/record.c (new)
      +
      +	/* This is simple and correct, but we could probably reuse the hash
      +	   fields. */
     -+	reftable_ref_record_clear(ref);
     ++	reftable_ref_record_release(ref);
      +	if (src->refname != NULL) {
      +		ref->refname = xstrdup(src->refname);
      +	}
     @@ reftable/record.c (new)
      +	printf("}\n");
      +}
      +
     -+static void reftable_ref_record_clear_void(void *rec)
     ++static void reftable_ref_record_release_void(void *rec)
      +{
     -+	reftable_ref_record_clear((struct reftable_ref_record *)rec);
     ++	reftable_ref_record_release((struct reftable_ref_record *)rec);
      +}
      +
     -+void reftable_ref_record_clear(struct reftable_ref_record *ref)
     ++void reftable_ref_record_release(struct reftable_ref_record *ref)
      +{
      +	reftable_free(ref->refname);
      +	reftable_free(ref->target);
     @@ reftable/record.c (new)
      +	.val_type = &reftable_ref_record_val_type,
      +	.encode = &reftable_ref_record_encode,
      +	.decode = &reftable_ref_record_decode,
     -+	.clear = &reftable_ref_record_clear_void,
     ++	.release = &reftable_ref_record_release_void,
      +	.is_deletion = &reftable_ref_record_is_deletion_void,
      +};
      +
     @@ reftable/record.c (new)
      +	strbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len);
      +}
      +
     -+static void reftable_obj_record_clear(void *rec)
     ++static void reftable_obj_record_release(void *rec)
      +{
      +	struct reftable_obj_record *obj = (struct reftable_obj_record *)rec;
      +	FREE_AND_NULL(obj->hash_prefix);
     @@ reftable/record.c (new)
      +		(const struct reftable_obj_record *)src_rec;
      +	int olen;
      +
     -+	reftable_obj_record_clear(obj);
     ++	reftable_obj_record_release(obj);
      +	*obj = *src;
      +	obj->hash_prefix = reftable_malloc(obj->hash_prefix_len);
      +	memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
     @@ reftable/record.c (new)
      +	.val_type = &reftable_obj_record_val_type,
      +	.encode = &reftable_obj_record_encode,
      +	.decode = &reftable_obj_record_decode,
     -+	.clear = &reftable_obj_record_clear,
     ++	.release = &reftable_obj_record_release,
      +	.is_deletion = not_a_deletion,
      +};
      +
     @@ reftable/record.c (new)
      +	const struct reftable_log_record *src =
      +		(const struct reftable_log_record *)src_rec;
      +
     -+	reftable_log_record_clear(dst);
     ++	reftable_log_record_release(dst);
      +	*dst = *src;
      +	if (dst->refname != NULL) {
      +		dst->refname = xstrdup(dst->refname);
     @@ reftable/record.c (new)
      +	}
      +}
      +
     -+static void reftable_log_record_clear_void(void *rec)
     ++static void reftable_log_record_release_void(void *rec)
      +{
      +	struct reftable_log_record *r = (struct reftable_log_record *)rec;
     -+	reftable_log_record_clear(r);
     ++	reftable_log_record_release(r);
      +}
      +
     -+void reftable_log_record_clear(struct reftable_log_record *r)
     ++void reftable_log_record_release(struct reftable_log_record *r)
      +{
      +	reftable_free(r->refname);
      +	reftable_free(r->new_hash);
     @@ reftable/record.c (new)
      +	.val_type = &reftable_log_record_val_type,
      +	.encode = &reftable_log_record_encode,
      +	.decode = &reftable_log_record_decode,
     -+	.clear = &reftable_log_record_clear_void,
     ++	.release = &reftable_log_record_release_void,
      +	.is_deletion = &reftable_log_record_is_deletion_void,
      +};
      +
     @@ reftable/record.c (new)
      +
      +void reftable_record_destroy(struct reftable_record *rec)
      +{
     -+	reftable_record_clear(rec);
     ++	reftable_record_release(rec);
      +	reftable_free(reftable_record_yield(rec));
      +}
      +
     @@ reftable/record.c (new)
      +	dst->offset = src->offset;
      +}
      +
     -+static void reftable_index_record_clear(void *rec)
     ++static void reftable_index_record_release(void *rec)
      +{
      +	struct reftable_index_record *idx = (struct reftable_index_record *)rec;
      +	strbuf_release(&idx->last_key);
     @@ reftable/record.c (new)
      +	.val_type = &reftable_index_record_val_type,
      +	.encode = &reftable_index_record_encode,
      +	.decode = &reftable_index_record_decode,
     -+	.clear = &reftable_index_record_clear,
     ++	.release = &reftable_index_record_release,
      +	.is_deletion = &not_a_deletion,
      +};
      +
     @@ reftable/record.c (new)
      +	return rec->ops->decode(rec->data, key, extra, src, hash_size);
      +}
      +
     -+void reftable_record_clear(struct reftable_record *rec)
     ++void reftable_record_release(struct reftable_record *rec)
      +{
     -+	rec->ops->clear(rec->data);
     ++	rec->ops->release(rec->data);
      +}
      +
      +int reftable_record_is_deletion(struct reftable_record *rec)
     @@ reftable/record.h (new)
      +#ifndef RECORD_H
      +#define RECORD_H
      +
     -+#include "reftable.h"
     -+#include "strbuf.h"
      +#include "system.h"
      +
     ++#include <stdint.h>
     ++
     ++#include "reftable-record.h"
     ++
      +/*
      +  A substring of existing string data. This structure takes no responsibility
      +  for the lifetime of the data it points to.
     @@ reftable/record.h (new)
      +		      struct string_view src, int hash_size);
      +
      +	/* deallocate and null the record. */
     -+	void (*clear)(void *rec);
     ++	void (*release)(void *rec);
      +
      +	/* is this a tombstone? */
      +	int (*is_deletion)(const void *rec);
     @@ reftable/record.h (new)
      +int reftable_record_is_deletion(struct reftable_record *rec);
      +
      +/* zeroes out the embedded record */
     -+void reftable_record_clear(struct reftable_record *rec);
     ++void reftable_record_release(struct reftable_record *rec);
      +
      +/* clear and deallocate embedded record, and zero `rec`. */
      +void reftable_record_destroy(struct reftable_record *rec);
     @@ reftable/record_test.c (new)
      +#include "system.h"
      +#include "basics.h"
      +#include "constants.h"
     -+#include "reftable.h"
      +#include "test_framework.h"
      +#include "reftable-tests.h"
      +
     @@ reftable/record_test.c (new)
      +	reftable_record_copy_from(&copy, rec, SHA1_SIZE);
      +	switch (reftable_record_type(&copy)) {
      +	case BLOCK_TYPE_REF:
     -+		assert(reftable_ref_record_equal(reftable_record_as_ref(&copy),
     ++		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
      +						 reftable_record_as_ref(rec),
      +						 SHA1_SIZE));
      +		break;
      +	case BLOCK_TYPE_LOG:
     -+		assert(reftable_log_record_equal(reftable_record_as_log(&copy),
     ++		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
      +						 reftable_record_as_log(rec),
      +						 SHA1_SIZE));
      +		break;
     @@ reftable/record_test.c (new)
      +		int n = put_var_int(&out, in);
      +		uint64_t got = 0;
      +
     -+		assert(n > 0);
     ++		EXPECT(n > 0);
      +		out.len = n;
      +		n = get_var_int(&got, &out);
     -+		assert(n > 0);
     ++		EXPECT(n > 0);
      +
     -+		assert(got == in);
     ++		EXPECT(got == in);
      +	}
      +}
      +
     @@ reftable/record_test.c (new)
      +		struct strbuf b = STRBUF_INIT;
      +		strbuf_addstr(&a, cases[i].a);
      +		strbuf_addstr(&b, cases[i].b);
     -+		assert(common_prefix_size(&a, &b) == cases[i].want);
     ++		EXPECT(common_prefix_size(&a, &b) == cases[i].want);
      +
      +		strbuf_release(&a);
      +		strbuf_release(&b);
     @@ reftable/record_test.c (new)
      +		reftable_record_from_ref(&rec, &in);
      +		test_copy(&rec);
      +
     -+		assert(reftable_record_val_type(&rec) == i);
     ++		EXPECT(reftable_record_val_type(&rec) == i);
      +
      +		reftable_record_key(&rec, &key);
      +		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     -+		assert(n > 0);
     ++		EXPECT(n > 0);
      +
      +		/* decode into a non-zero reftable_record to test for leaks. */
      +
      +		reftable_record_from_ref(&rec_out, &out);
      +		m = reftable_record_decode(&rec_out, key, i, dest, SHA1_SIZE);
     -+		assert(n == m);
     ++		EXPECT(n == m);
      +
     -+		assert((out.value != NULL) == (in.value != NULL));
     -+		assert((out.target_value != NULL) == (in.target_value != NULL));
     -+		assert((out.target != NULL) == (in.target != NULL));
     -+		reftable_record_clear(&rec_out);
     ++		EXPECT((out.value != NULL) == (in.value != NULL));
     ++		EXPECT((out.target_value != NULL) == (in.target_value != NULL));
     ++		EXPECT((out.target != NULL) == (in.target != NULL));
     ++		reftable_record_release(&rec_out);
      +
      +		strbuf_release(&key);
     -+		reftable_ref_record_clear(&in);
     ++		reftable_ref_record_release(&in);
      +	}
      +}
      +
     @@ reftable/record_test.c (new)
      +		}
      +	};
      +
     -+	assert(!reftable_log_record_equal(&in[0], &in[1], SHA1_SIZE));
     ++	EXPECT(!reftable_log_record_equal(&in[0], &in[1], SHA1_SIZE));
      +	in[1].update_index = in[0].update_index;
     -+	assert(reftable_log_record_equal(&in[0], &in[1], SHA1_SIZE));
     -+	reftable_log_record_clear(&in[0]);
     -+	reftable_log_record_clear(&in[1]);
     ++	EXPECT(reftable_log_record_equal(&in[0], &in[1], SHA1_SIZE));
     ++	reftable_log_record_release(&in[0]);
     ++	reftable_log_record_release(&in[1]);
      +}
      +
      +static void test_reftable_log_record_roundtrip(void)
     @@ reftable/record_test.c (new)
      +		reftable_record_key(&rec, &key);
      +
      +		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     -+		assert(n >= 0);
     ++		EXPECT(n >= 0);
      +		reftable_record_from_log(&rec_out, &out);
      +		valtype = reftable_record_val_type(&rec);
      +		m = reftable_record_decode(&rec_out, key, valtype, dest,
      +					   SHA1_SIZE);
     -+		assert(n == m);
     ++		EXPECT(n == m);
      +
     -+		assert(reftable_log_record_equal(&in[i], &out, SHA1_SIZE));
     -+		reftable_log_record_clear(&in[i]);
     ++		EXPECT(reftable_log_record_equal(&in[i], &out, SHA1_SIZE));
     ++		reftable_log_record_release(&in[i]);
      +		strbuf_release(&key);
     -+		reftable_record_clear(&rec_out);
     ++		reftable_record_release(&rec_out);
      +	}
      +}
      +
     @@ reftable/record_test.c (new)
      +	uint32_t out;
      +	put_be24(dest, in);
      +	out = get_be24(dest);
     -+	assert(in == out);
     ++	EXPECT(in == out);
      +}
      +
      +static void test_key_roundtrip(void)
     @@ reftable/record_test.c (new)
      +	strbuf_addstr(&key, "refs/tags/bla");
      +	extra = 6;
      +	n = reftable_encode_key(&restart, dest, last_key, key, extra);
     -+	assert(!restart);
     -+	assert(n > 0);
     ++	EXPECT(!restart);
     ++	EXPECT(n > 0);
      +
      +	m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);
     -+	assert(n == m);
     -+	assert(0 == strbuf_cmp(&key, &roundtrip));
     -+	assert(rt_extra == extra);
     ++	EXPECT(n == m);
     ++	EXPECT(0 == strbuf_cmp(&key, &roundtrip));
     ++	EXPECT(rt_extra == extra);
      +
      +	strbuf_release(&last_key);
      +	strbuf_release(&key);
     @@ reftable/record_test.c (new)
      +		test_copy(&rec);
      +		reftable_record_key(&rec, &key);
      +		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     -+		assert(n > 0);
     ++		EXPECT(n > 0);
      +		extra = reftable_record_val_type(&rec);
      +		reftable_record_from_obj(&rec_out, &out);
      +		m = reftable_record_decode(&rec_out, key, extra, dest,
      +					   SHA1_SIZE);
     -+		assert(n == m);
     ++		EXPECT(n == m);
      +
     -+		assert(in.hash_prefix_len == out.hash_prefix_len);
     -+		assert(in.offset_len == out.offset_len);
     ++		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
     ++		EXPECT(in.offset_len == out.offset_len);
      +
     -+		assert(!memcmp(in.hash_prefix, out.hash_prefix,
     ++		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
      +			       in.hash_prefix_len));
     -+		assert(0 == memcmp(in.offsets, out.offsets,
     ++		EXPECT(0 == memcmp(in.offsets, out.offsets,
      +				   sizeof(uint64_t) * in.offset_len));
      +		strbuf_release(&key);
     -+		reftable_record_clear(&rec_out);
     ++		reftable_record_release(&rec_out);
      +	}
      +}
      +
     @@ reftable/record_test.c (new)
      +	reftable_record_key(&rec, &key);
      +	test_copy(&rec);
      +
     -+	assert(0 == strbuf_cmp(&key, &in.last_key));
     ++	EXPECT(0 == strbuf_cmp(&key, &in.last_key));
      +	n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     -+	assert(n > 0);
     ++	EXPECT(n > 0);
      +
      +	extra = reftable_record_val_type(&rec);
      +	reftable_record_from_index(&out_rec, &out);
      +	m = reftable_record_decode(&out_rec, key, extra, dest, SHA1_SIZE);
     -+	assert(m == n);
     ++	EXPECT(m == n);
      +
     -+	assert(in.offset == out.offset);
     ++	EXPECT(in.offset == out.offset);
      +
     -+	reftable_record_clear(&out_rec);
     ++	reftable_record_release(&out_rec);
      +	strbuf_release(&key);
      +	strbuf_release(&in.last_key);
      +}
      +
      +int record_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("test_reftable_log_record_equal",
     -+		      &test_reftable_log_record_equal);
     -+	add_test_case("test_reftable_log_record_roundtrip",
     -+		      &test_reftable_log_record_roundtrip);
     -+	add_test_case("test_reftable_ref_record_roundtrip",
     -+		      &test_reftable_ref_record_roundtrip);
     -+	add_test_case("test_varint_roundtrip", &test_varint_roundtrip);
     -+	add_test_case("test_key_roundtrip", &test_key_roundtrip);
     -+	add_test_case("test_common_prefix", &test_common_prefix);
     -+	add_test_case("test_reftable_obj_record_roundtrip",
     -+		      &test_reftable_obj_record_roundtrip);
     -+	add_test_case("test_reftable_index_record_roundtrip",
     -+		      &test_reftable_index_record_roundtrip);
     -+	add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
     -+	return test_main(argc, argv);
     ++	test_reftable_log_record_equal();
     ++	test_reftable_log_record_roundtrip();
     ++	test_reftable_ref_record_roundtrip();
     ++	test_varint_roundtrip();
     ++	test_key_roundtrip();
     ++	test_common_prefix();
     ++	test_reftable_obj_record_roundtrip();
     ++	test_reftable_index_record_roundtrip();
     ++	test_u24_roundtrip();
     ++	return 0;
      +}
      
     + ## reftable/reftable-record.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef REFTABLE_RECORD_H
     ++#define REFTABLE_RECORD_H
     ++
     ++#include <stdint.h>
     ++
     ++/*
     ++ Basic data types
     ++
     ++ Reftables store the state of each ref in struct reftable_ref_record, and they
     ++ store a sequence of reflog updates in struct reftable_log_record.
     ++*/
     ++
     ++/* reftable_ref_record holds a ref database entry target_value */
     ++struct reftable_ref_record {
     ++	char *refname; /* Name of the ref, malloced. */
     ++	uint64_t update_index; /* Logical timestamp at which this value is
     ++				  written */
     ++	uint8_t *value; /* SHA1, or NULL. malloced. */
     ++	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
     ++	char *target; /* symref, or NULL. malloced. */
     ++};
     ++
     ++/* returns whether 'ref' represents a deletion */
     ++int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
     ++
     ++/* prints a reftable_ref_record onto stdout */
     ++void reftable_ref_record_print(struct reftable_ref_record *ref,
     ++			       uint32_t hash_id);
     ++
     ++/* frees and nulls all pointer values. */
     ++void reftable_ref_record_release(struct reftable_ref_record *ref);
     ++
     ++/* returns whether two reftable_ref_records are the same */
     ++int reftable_ref_record_equal(struct reftable_ref_record *a,
     ++			      struct reftable_ref_record *b, int hash_size);
     ++
     ++/* reftable_log_record holds a reflog entry */
     ++struct reftable_log_record {
     ++	char *refname;
     ++	uint64_t update_index; /* logical timestamp of a transactional update.
     ++				*/
     ++	uint8_t *new_hash;
     ++	uint8_t *old_hash;
     ++	char *name;
     ++	char *email;
     ++	uint64_t time;
     ++	int16_t tz_offset;
     ++	char *message;
     ++};
     ++
     ++/* returns whether 'ref' represents the deletion of a log record. */
     ++int reftable_log_record_is_deletion(const struct reftable_log_record *log);
     ++
     ++/* frees and nulls all pointer values. */
     ++void reftable_log_record_release(struct reftable_log_record *log);
     ++
     ++/* returns whether two records are equal. Useful for testing. */
     ++int reftable_log_record_equal(struct reftable_log_record *a,
     ++			      struct reftable_log_record *b, int hash_size);
     ++
     ++/* dumps a reftable_log_record on stdout, for debugging/testing. */
     ++void reftable_log_record_print(struct reftable_log_record *log,
     ++			       uint32_t hash_id);
     ++
     ++#endif
     +
       ## t/helper/test-reftable.c ##
      @@
     - 
       int cmd__reftable(int argc, const char **argv)
       {
     + 	basics_test_main(argc, argv);
     +-
      +	record_test_main(argc, argv);
     - 	strbuf_test_main(argc, argv);
       	return 0;
       }
  7:  757dd30fe2 !  8:  a358b052d5 reftable: reading/writing blocks
     @@ Metadata
       ## Commit message ##
          reftable: reading/writing blocks
      
     +    The reftable format is structured as a sequence of block. Within a block,
     +    records are prefix compressed, with an index of offsets for fully expand keys to
     +    enable binary search within blocks.
     +
     +    This commit provides the logic to read and write these blocks.
     +
          Includes a code snippet copied from zlib
      
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
     -@@ Makefile: XDIFF_OBJS += xdiff/xprepare.o
     - XDIFF_OBJS += xdiff/xutils.o
     +@@ Makefile: XDIFF_OBJS += xdiff/xutils.o
       
       REFTABLE_OBJS += reftable/basics.o
     + REFTABLE_OBJS += reftable/error.o
      +REFTABLE_OBJS += reftable/block.o
       REFTABLE_OBJS += reftable/blocksource.o
       REFTABLE_OBJS += reftable/publicbasics.o
     - REFTABLE_OBJS += reftable/compat.o
       REFTABLE_OBJS += reftable/record.o
     - REFTABLE_OBJS += reftable/strbuf.o
      +REFTABLE_OBJS += reftable/zlib-compat.o
       
      +REFTABLE_TEST_OBJS += reftable/block_test.o
       REFTABLE_TEST_OBJS += reftable/record_test.o
     - REFTABLE_TEST_OBJS += reftable/strbuf_test.o
       REFTABLE_TEST_OBJS += reftable/test_framework.o
     + REFTABLE_TEST_OBJS += reftable/basics_test.o
      
       ## reftable/.gitattributes (new) ##
      @@
     @@ reftable/block.c (new)
      +#include "blocksource.h"
      +#include "constants.h"
      +#include "record.h"
     -+#include "reftable.h"
     ++#include "reftable-error.h"
      +#include "system.h"
      +#include "zlib.h"
      +
     @@ reftable/block.c (new)
      +	return -1;
      +}
      +
     -+
      +int block_writer_finish(struct block_writer *w)
      +{
      +	int i = 0;
     @@ reftable/block.c (new)
      +	return err;
      +}
      +
     -+void block_writer_clear(struct block_writer *bw)
     ++void block_writer_release(struct block_writer *bw)
      +{
      +	FREE_AND_NULL(bw->restarts);
      +	strbuf_release(&bw->last_key);
     @@ reftable/block.h (new)
      +
      +#include "basics.h"
      +#include "record.h"
     -+#include "reftable.h"
     ++#include "reftable-blocksource.h"
      +
      +/*
      +  Writes reftable blocks. The block_writer is reused across blocks to minimize
     @@ reftable/block.h (new)
      +int block_writer_finish(struct block_writer *w);
      +
      +/* clears out internally allocated block_writer members. */
     -+void block_writer_clear(struct block_writer *bw);
     ++void block_writer_release(struct block_writer *bw);
      +
      +/* Read a block. */
      +struct block_reader {
     @@ reftable/block_test.c (new)
      +#include "basics.h"
      +#include "constants.h"
      +#include "record.h"
     -+#include "reftable.h"
      +#include "test_framework.h"
      +#include "reftable-tests.h"
      +
     -+struct binsearch_args {
     -+	int key;
     -+	int *arr;
     -+};
     -+
     -+static int binsearch_func(size_t i, void *void_args)
     -+{
     -+	struct binsearch_args *args = (struct binsearch_args *)void_args;
     -+
     -+	return args->key < args->arr[i];
     -+}
     -+
     -+static void test_binsearch(void)
     -+{
     -+	int arr[] = { 2, 4, 6, 8, 10 };
     -+	size_t sz = ARRAY_SIZE(arr);
     -+	struct binsearch_args args = {
     -+		.arr = arr,
     -+	};
     -+
     -+	int i = 0;
     -+	for (i = 1; i < 11; i++) {
     -+		int res;
     -+		args.key = i;
     -+		res = binsearch(sz, &binsearch_func, &args);
     -+
     -+		if (res < sz) {
     -+			assert(args.key < arr[res]);
     -+			if (res > 0) {
     -+				assert(args.key >= arr[res - 1]);
     -+			}
     -+		} else {
     -+			assert(args.key == 10 || args.key == 11);
     -+		}
     -+	}
     -+}
     -+
      +static void test_block_read_write(void)
      +{
      +	const int header_off = 21; /* random */
     @@ reftable/block_test.c (new)
      +		n = block_writer_add(&bw, &rec);
      +		ref.refname = NULL;
      +		ref.value = NULL;
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +	}
      +
      +	n = block_writer_finish(&bw);
     -+	assert(n > 0);
     ++	EXPECT(n > 0);
      +
     -+	block_writer_clear(&bw);
     ++	block_writer_release(&bw);
      +
      +	block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
      +
     @@ reftable/block_test.c (new)
      +
      +	while (1) {
      +		int r = block_iter_next(&it, &rec);
     -+		assert(r >= 0);
     ++		EXPECT(r >= 0);
      +		if (r > 0) {
      +			break;
      +		}
     -+		assert_streq(names[j], ref.refname);
     ++		EXPECT_STREQ(names[j], ref.refname);
      +		j++;
      +	}
      +
     -+	reftable_record_clear(&rec);
     ++	reftable_record_release(&rec);
      +	block_iter_close(&it);
      +
      +	for (i = 0; i < N; i++) {
     @@ reftable/block_test.c (new)
      +		strbuf_addstr(&want, names[i]);
      +
      +		n = block_reader_seek(&br, &it, &want);
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +
      +		n = block_iter_next(&it, &rec);
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +
     -+		assert_streq(names[i], ref.refname);
     ++		EXPECT_STREQ(names[i], ref.refname);
      +
      +		want.len--;
      +		n = block_reader_seek(&br, &it, &want);
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +
      +		n = block_iter_next(&it, &rec);
     -+		assert(n == 0);
     -+		assert_streq(names[10 * (i / 10)], ref.refname);
     ++		EXPECT(n == 0);
     ++		EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
      +
      +		block_iter_close(&it);
      +	}
      +
     -+	reftable_record_clear(&rec);
     ++	reftable_record_release(&rec);
      +	reftable_block_done(&br.block);
      +	strbuf_release(&want);
      +	for (i = 0; i < N; i++) {
     @@ reftable/block_test.c (new)
      +
      +int block_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("binsearch", &test_binsearch);
     -+	add_test_case("block_read_write", &test_block_read_write);
     -+	return test_main(argc, argv);
     ++	test_block_read_write();
     ++	return 0;
      +}
      
       ## reftable/zlib-compat.c (new) ##
     @@ reftable/zlib-compat.c (new)
      
       ## t/helper/test-reftable.c ##
      @@
     - 
       int cmd__reftable(int argc, const char **argv)
       {
     + 	basics_test_main(argc, argv);
      +	block_test_main(argc, argv);
       	record_test_main(argc, argv);
     - 	strbuf_test_main(argc, argv);
       	return 0;
     + }
  8:  e30a7e0281 !  9:  24afac9c91 reftable: a generic binary tree implementation
     @@ Metadata
       ## Commit message ##
          reftable: a generic binary tree implementation
      
     -    This is necessary for building a OID => ref map on write
     +    The reftable format includes support for an (OID => ref) map. This map can speed
     +    up visibility and reachability checks. In particular, various operations along
     +    the fetch/push path within Gerrit have ben sped up by using this structure.
     +
     +    The map is constructed with help of a binary tree. Object IDs are hashes, so
     +    they are uniformly distributed. Hence, the tree does not attempt forced
     +    rebalancing.
      
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
     -@@ Makefile: REFTABLE_OBJS += reftable/publicbasics.o
     - REFTABLE_OBJS += reftable/compat.o
     +@@ Makefile: REFTABLE_OBJS += reftable/block.o
     + REFTABLE_OBJS += reftable/blocksource.o
     + REFTABLE_OBJS += reftable/publicbasics.o
       REFTABLE_OBJS += reftable/record.o
     - REFTABLE_OBJS += reftable/strbuf.o
      +REFTABLE_OBJS += reftable/tree.o
       REFTABLE_OBJS += reftable/zlib-compat.o
       
     ++REFTABLE_TEST_OBJS += reftable/basics_test.o
       REFTABLE_TEST_OBJS += reftable/block_test.o
       REFTABLE_TEST_OBJS += reftable/record_test.o
     - REFTABLE_TEST_OBJS += reftable/strbuf_test.o
       REFTABLE_TEST_OBJS += reftable/test_framework.o
     +-REFTABLE_TEST_OBJS += reftable/basics_test.o
      +REFTABLE_TEST_OBJS += reftable/tree_test.o
       
       TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
     @@ reftable/tree_test.c (new)
      +
      +#include "basics.h"
      +#include "record.h"
     -+#include "reftable.h"
      +#include "test_framework.h"
      +#include "reftable-tests.h"
      +
     @@ reftable/tree_test.c (new)
      +
      +int tree_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("test_tree", &test_tree);
     -+	return test_main(argc, argv);
     ++	test_tree();
     ++	return 0;
      +}
      
       ## t/helper/test-reftable.c ##
      @@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
     + 	basics_test_main(argc, argv);
       	block_test_main(argc, argv);
       	record_test_main(argc, argv);
     - 	strbuf_test_main(argc, argv);
      +	tree_test_main(argc, argv);
       	return 0;
       }
  9:  68aee16e60 ! 10:  5f0b7c5592 reftable: write reftable files
     @@ Commit message
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
     -@@ Makefile: REFTABLE_OBJS += reftable/compat.o
     +@@ Makefile: REFTABLE_OBJS += reftable/blocksource.o
     + REFTABLE_OBJS += reftable/publicbasics.o
       REFTABLE_OBJS += reftable/record.o
     - REFTABLE_OBJS += reftable/strbuf.o
       REFTABLE_OBJS += reftable/tree.o
      +REFTABLE_OBJS += reftable/writer.o
       REFTABLE_OBJS += reftable/zlib-compat.o
       
     - REFTABLE_TEST_OBJS += reftable/block_test.o
     + REFTABLE_TEST_OBJS += reftable/basics_test.o
     +
     + ## reftable/reftable-writer.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef REFTABLE_WRITER_H
     ++#define REFTABLE_WRITER_H
     ++
     ++#include <stdint.h>
     ++
     ++#include "reftable-record.h"
     ++
     ++/*
     ++ Writing single reftables
     ++*/
     ++
     ++/* reftable_write_options sets options for writing a single reftable. */
     ++struct reftable_write_options {
     ++	/* boolean: do not pad out blocks to block size. */
     ++	unsigned unpadded : 1;
     ++
     ++	/* the blocksize. Should be less than 2^24. */
     ++	uint32_t block_size;
     ++
     ++	/* boolean: do not generate a SHA1 => ref index. */
     ++	unsigned skip_index_objects : 1;
     ++
     ++	/* how often to write complete keys in each block. */
     ++	int restart_interval;
     ++
     ++	/* 4-byte identifier ("sha1", "s256") of the hash.
     ++	 * Defaults to SHA1 if unset
     ++	 */
     ++	uint32_t hash_id;
     ++
     ++	/* boolean: do not check ref names for validity or dir/file conflicts.
     ++	 */
     ++	unsigned skip_name_check : 1;
     ++
     ++	/* boolean: copy log messages exactly. If unset, check that the message
     ++	 *   is a single line, and add '\n' if missing.
     ++	 */
     ++	unsigned exact_log_message : 1;
     ++};
     ++
     ++/* reftable_block_stats holds statistics for a single block type */
     ++struct reftable_block_stats {
     ++	/* total number of entries written */
     ++	int entries;
     ++	/* total number of key restarts */
     ++	int restarts;
     ++	/* total number of blocks */
     ++	int blocks;
     ++	/* total number of index blocks */
     ++	int index_blocks;
     ++	/* depth of the index */
     ++	int max_index_level;
     ++
     ++	/* offset of the first block for this type */
     ++	uint64_t offset;
     ++	/* offset of the top level index block for this type, or 0 if not
     ++	 * present */
     ++	uint64_t index_offset;
     ++};
     ++
     ++/* stats holds overall statistics for a single reftable */
     ++struct reftable_stats {
     ++	/* total number of blocks written. */
     ++	int blocks;
     ++	/* stats for ref data */
     ++	struct reftable_block_stats ref_stats;
     ++	/* stats for the SHA1 to ref map. */
     ++	struct reftable_block_stats obj_stats;
     ++	/* stats for index blocks */
     ++	struct reftable_block_stats idx_stats;
     ++	/* stats for log blocks */
     ++	struct reftable_block_stats log_stats;
     ++
     ++	/* disambiguation length of shortened object IDs. */
     ++	int object_id_len;
     ++};
     ++
     ++/* reftable_new_writer creates a new writer */
     ++struct reftable_writer *
     ++reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
     ++		    void *writer_arg, struct reftable_write_options *opts);
     ++
     ++/* Set the range of update indices for the records we will add. When writing a
     ++   table into a stack, the min should be at least
     ++   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
     ++
     ++   For transactional updates to a stack, typically min==max, and the
     ++   update_index can be obtained by inspeciting the stack. When converting an
     ++   existing ref database into a single reftable, this would be a range of
     ++   update-index timestamps.
     ++ */
     ++void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
     ++				uint64_t max);
     ++
     ++/*
     ++  Add a reftable_ref_record. The record should have names that come after
     ++  already added records.
     ++
     ++  The update_index must be within the limits set by
     ++  reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an
     ++  REFTABLE_API_ERROR error to write a ref record after a log record.
     ++*/
     ++int reftable_writer_add_ref(struct reftable_writer *w,
     ++			    struct reftable_ref_record *ref);
     ++
     ++/*
     ++  Convenience function to add multiple reftable_ref_records; the function sorts
     ++  the records before adding them, reordering the records array passed in.
     ++*/
     ++int reftable_writer_add_refs(struct reftable_writer *w,
     ++			     struct reftable_ref_record *refs, int n);
     ++
     ++/*
     ++  adds reftable_log_records. Log records are keyed by (refname, decreasing
     ++  update_index). The key for the record added must come after the already added
     ++  log records.
     ++*/
     ++int reftable_writer_add_log(struct reftable_writer *w,
     ++			    struct reftable_log_record *log);
     ++
     ++/*
     ++  Convenience function to add multiple reftable_log_records; the function sorts
     ++  the records before adding them, reordering records array passed in.
     ++*/
     ++int reftable_writer_add_logs(struct reftable_writer *w,
     ++			     struct reftable_log_record *logs, int n);
     ++
     ++/* reftable_writer_close finalizes the reftable. The writer is retained so
     ++ * statistics can be inspected. */
     ++int reftable_writer_close(struct reftable_writer *w);
     ++
     ++/* writer_stats returns the statistics on the reftable being written.
     ++
     ++   This struct becomes invalid when the writer is freed.
     ++ */
     ++const struct reftable_stats *writer_stats(struct reftable_writer *w);
     ++
     ++/* reftable_writer_free deallocates memory for the writer */
     ++void reftable_writer_free(struct reftable_writer *w);
     ++
     ++#endif
      
       ## reftable/writer.c (new) ##
      @@
     @@ reftable/writer.c (new)
      +#include "block.h"
      +#include "constants.h"
      +#include "record.h"
     -+#include "reftable.h"
      +#include "tree.h"
     ++#include "reftable-error.h"
      +
      +/* finishes a block, and writes it to storage */
      +static int writer_flush_block(struct reftable_writer *w);
     @@ reftable/writer.c (new)
      +	w->block_writer->restart_interval = w->opts.restart_interval;
      +}
      +
     ++static struct strbuf reftable_empty_strbuf = STRBUF_INIT;
     ++
      +struct reftable_writer *
      +reftable_new_writer(int (*writer_func)(void *, const void *, size_t),
      +		    void *writer_arg, struct reftable_write_options *opts)
     @@ reftable/writer.c (new)
      +	int err = 0;
      +	int i = 0;
      +	QSORT(logs, n, reftable_log_record_compare_key);
     ++
      +	for (i = 0; err == 0 && i < n; i++) {
      +		err = reftable_writer_add_log(w, &logs[i]);
      +	}
     @@ reftable/writer.c (new)
      +
      +done:
      +	/* free up memory. */
     -+	block_writer_clear(&w->block_writer_data);
     ++	block_writer_release(&w->block_writer_data);
      +	writer_clear_index(w);
      +	strbuf_release(&w->last_key);
      +	return err;
     @@ reftable/writer.h (new)
      +
      +#include "basics.h"
      +#include "block.h"
     -+#include "reftable.h"
     -+#include "strbuf.h"
      +#include "tree.h"
     ++#include "reftable-writer.h"
      +
      +struct reftable_writer {
      +	int (*write)(void *, const void *, size_t);
 10:  c196de7f06 ! 11:  9aa2caade8 reftable: read reftable files
     @@ Metadata
       ## Commit message ##
          reftable: read reftable files
      
     +    This supports reading a single reftable file.
     +
     +    The commit introduces an abstract iterator type, which captures the usecases
     +    both of reading individual refs, and iterating over a segment of the ref
     +    namespace.
     +
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
     -@@ Makefile: REFTABLE_OBJS += reftable/block.o
     +@@ Makefile: REFTABLE_OBJS += reftable/basics.o
     + REFTABLE_OBJS += reftable/error.o
     + REFTABLE_OBJS += reftable/block.o
       REFTABLE_OBJS += reftable/blocksource.o
     - REFTABLE_OBJS += reftable/publicbasics.o
     - REFTABLE_OBJS += reftable/compat.o
      +REFTABLE_OBJS += reftable/iter.o
     + REFTABLE_OBJS += reftable/publicbasics.o
      +REFTABLE_OBJS += reftable/reader.o
       REFTABLE_OBJS += reftable/record.o
      +REFTABLE_OBJS += reftable/reftable.o
     - REFTABLE_OBJS += reftable/strbuf.o
       REFTABLE_OBJS += reftable/tree.o
       REFTABLE_OBJS += reftable/writer.o
     + REFTABLE_OBJS += reftable/zlib-compat.o
      
       ## reftable/iter.c (new) ##
      @@
     @@ reftable/iter.c (new)
      +#include "block.h"
      +#include "constants.h"
      +#include "reader.h"
     -+#include "reftable.h"
     ++#include "reftable-error.h"
      +
      +int iterator_is_null(struct reftable_iterator *it)
      +{
     @@ reftable/iter.c (new)
      +int reftable_iterator_next_ref(struct reftable_iterator *it,
      +			       struct reftable_ref_record *ref)
      +{
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	reftable_record_from_ref(&rec, ref);
      +	return iterator_next(it, &rec);
      +}
     @@ reftable/iter.c (new)
      +int reftable_iterator_next_log(struct reftable_iterator *it,
      +			       struct reftable_log_record *log)
      +{
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	reftable_record_from_log(&rec, log);
      +	return iterator_next(it, &rec);
      +}
     @@ reftable/iter.c (new)
      +		}
      +
      +		if (fri->double_check) {
     -+			struct reftable_iterator it = { 0 };
     ++			struct reftable_iterator it = { NULL };
      +
      +			err = reftable_table_seek_ref(&fri->tab, &it,
      +						      ref->refname);
     @@ reftable/iter.c (new)
      +		}
      +	}
      +
     -+	reftable_ref_record_clear(ref);
     ++	reftable_ref_record_release(ref);
      +	return err;
      +}
      +
     @@ reftable/iter.h (new)
      +#ifndef ITER_H
      +#define ITER_H
      +
     ++#include "system.h"
      +#include "block.h"
      +#include "record.h"
     -+#include "strbuf.h"
     ++
     ++#include "reftable-iterator.h"
     ++#include "reftable-generic.h"
      +
      +struct reftable_iterator_vtable {
      +	int (*next)(void *iter_arg, struct reftable_record *rec);
     @@ reftable/reader.c (new)
      +#include "constants.h"
      +#include "iter.h"
      +#include "record.h"
     -+#include "reftable.h"
     ++#include "reftable-error.h"
      +#include "tree.h"
      +
      +uint64_t block_source_size(struct reftable_block_source *source)
     @@ reftable/reader.c (new)
      +int init_reader(struct reftable_reader *r, struct reftable_block_source *source,
      +		const char *name)
      +{
     -+	struct reftable_block footer = { 0 };
     -+	struct reftable_block header = { 0 };
     ++	struct reftable_block footer = { NULL };
     ++	struct reftable_block header = { NULL };
      +	int err = 0;
      +
      +	memset(r, 0, sizeof(struct reftable_reader));
     @@ reftable/reader.c (new)
      +{
      +	int32_t guess_block_size = r->block_size ? r->block_size :
      +							 DEFAULT_BLOCK_SIZE;
     -+	struct reftable_block block = { 0 };
     ++	struct reftable_block block = { NULL };
      +	uint8_t block_typ = 0;
      +	int err = 0;
      +	uint32_t header_off = next_off ? 0 : header_size(r->version);
     @@ reftable/reader.c (new)
      +			       struct reftable_record *rec)
      +{
      +	struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
     -+	struct reftable_record want_index_rec = { 0 };
     ++	struct reftable_record want_index_rec = { NULL };
      +	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
     -+	struct reftable_record index_result_rec = { 0 };
     ++	struct reftable_record index_result_rec = { NULL };
      +	struct table_iter index_iter = TABLE_ITER_INIT;
      +	struct table_iter next = TABLE_ITER_INIT;
      +	int err = 0;
     @@ reftable/reader.c (new)
      +done:
      +	block_iter_close(&next.bi);
      +	table_iter_close(&index_iter);
     -+	reftable_record_clear(&want_index_rec);
     -+	reftable_record_clear(&index_result_rec);
     ++	reftable_record_release(&want_index_rec);
     ++	reftable_record_release(&index_result_rec);
      +	return err;
      +}
      +
     @@ reftable/reader.c (new)
      +	struct reftable_ref_record ref = {
      +		.refname = (char *)name,
      +	};
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	reftable_record_from_ref(&rec, &ref);
      +	return reader_seek(r, it, &rec);
      +}
     @@ reftable/reader.c (new)
      +		.refname = (char *)name,
      +		.update_index = update_index,
      +	};
     -+	struct reftable_record rec = { 0 };
     ++	struct reftable_record rec = { NULL };
      +	reftable_record_from_log(&rec, &log);
      +	return reader_seek(r, it, &rec);
      +}
     @@ reftable/reader.c (new)
      +		.hash_prefix = oid,
      +		.hash_prefix_len = r->object_id_len,
      +	};
     -+	struct reftable_record want_rec = { 0 };
     -+	struct reftable_iterator oit = { 0 };
     -+	struct reftable_obj_record got = { 0 };
     -+	struct reftable_record got_rec = { 0 };
     ++	struct reftable_record want_rec = { NULL };
     ++	struct reftable_iterator oit = { NULL };
     ++	struct reftable_obj_record got = { NULL };
     ++	struct reftable_record got_rec = { NULL };
      +	int err = 0;
      +	struct indexed_table_ref_iter *itr = NULL;
      +
     @@ reftable/reader.c (new)
      +
      +done:
      +	reftable_iterator_destroy(&oit);
     -+	reftable_record_clear(&got_rec);
     ++	reftable_record_release(&got_rec);
      +	return err;
      +}
      +
     @@ reftable/reader.h (new)
      +
      +#include "block.h"
      +#include "record.h"
     -+#include "reftable.h"
     ++#include "reftable-iterator.h"
     ++#include "reftable-reader.h"
      +
      +uint64_t block_source_size(struct reftable_block_source *source);
      +
     @@ reftable/reader.h (new)
      +	uint64_t (*max_update_index)(void *tab);
      +};
      +
     -+int reftable_table_seek_record(struct reftable_table *tab,
     -+			       struct reftable_iterator *it,
     -+			       struct reftable_record *rec);
     -+
      +#endif
      
     - ## reftable/reftable.c (new) ##
     + ## reftable/reftable-iterator.h (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/reftable.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "reftable.h"
     -+#include "record.h"
     -+#include "reader.h"
     ++#ifndef REFTABLE_ITERATOR_H
     ++#define REFTABLE_ITERATOR_H
      +
     -+static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,
     -+				     struct reftable_record *rec)
     -+{
     -+	return reader_seek((struct reftable_reader *)tab, it, rec);
     -+}
     ++#include "reftable-record.h"
      +
     -+static uint32_t reftable_reader_hash_id_void(void *tab)
     -+{
     -+	return reftable_reader_hash_id((struct reftable_reader *)tab);
     -+}
     ++/* iterator is the generic interface for walking over data stored in a
     ++   reftable.
     ++*/
     ++struct reftable_iterator {
     ++	struct reftable_iterator_vtable *ops;
     ++	void *iter_arg;
     ++};
      +
     -+static uint64_t reftable_reader_min_update_index_void(void *tab)
     -+{
     -+	return reftable_reader_min_update_index((struct reftable_reader *)tab);
     -+}
     ++/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
     ++   end of iteration.
     ++*/
     ++int reftable_iterator_next_ref(struct reftable_iterator *it,
     ++			       struct reftable_ref_record *ref);
      +
     -+static uint64_t reftable_reader_max_update_index_void(void *tab)
     -+{
     -+	return reftable_reader_max_update_index((struct reftable_reader *)tab);
     -+}
     ++/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
     ++   end of iteration.
     ++*/
     ++int reftable_iterator_next_log(struct reftable_iterator *it,
     ++			       struct reftable_log_record *log);
      +
     -+static struct reftable_table_vtable reader_vtable = {
     -+	.seek_record = reftable_reader_seek_void,
     -+	.hash_id = reftable_reader_hash_id_void,
     -+	.min_update_index = reftable_reader_min_update_index_void,
     -+	.max_update_index = reftable_reader_max_update_index_void,
     -+};
     ++/* releases resources associated with an iterator. */
     ++void reftable_iterator_destroy(struct reftable_iterator *it);
      +
     -+int reftable_table_seek_ref(struct reftable_table *tab,
     -+			    struct reftable_iterator *it, const char *name)
     -+{
     -+	struct reftable_ref_record ref = {
     -+		.refname = (char *)name,
     -+	};
     -+	struct reftable_record rec = { 0 };
     -+	reftable_record_from_ref(&rec, &ref);
     -+	return tab->ops->seek_record(tab->table_arg, it, &rec);
     -+}
     ++#endif
     +
     + ## reftable/reftable-reader.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+void reftable_table_from_reader(struct reftable_table *tab,
     -+				struct reftable_reader *reader)
     -+{
     -+	assert(tab->ops == NULL);
     -+	tab->ops = &reader_vtable;
     -+	tab->table_arg = reader;
     -+}
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
      +
     -+int reftable_table_read_ref(struct reftable_table *tab, const char *name,
     -+			    struct reftable_ref_record *ref)
     -+{
     -+	struct reftable_iterator it = { 0 };
     -+	int err = reftable_table_seek_ref(tab, &it, name);
     -+	if (err)
     -+		goto done;
     ++#ifndef REFTABLE_READER_H
     ++#define REFTABLE_READER_H
      +
     -+	err = reftable_iterator_next_ref(&it, ref);
     -+	if (err)
     -+		goto done;
     ++#include "reftable-iterator.h"
     ++#include "reftable-blocksource.h"
      +
     -+	if (strcmp(ref->refname, name) ||
     -+	    reftable_ref_record_is_deletion(ref)) {
     -+		reftable_ref_record_clear(ref);
     -+		err = 1;
     -+		goto done;
     -+	}
     ++/*
     ++ Reading single tables
      +
     -+done:
     -+	reftable_iterator_destroy(&it);
     -+	return err;
     -+}
     ++ The follow routines are for reading single files. For an application-level
     ++ interface, skip ahead to struct reftable_merged_table and struct
     ++ reftable_stack.
     ++*/
      +
     -+int reftable_table_seek_record(struct reftable_table *tab,
     -+			       struct reftable_iterator *it,
     -+			       struct reftable_record *rec)
     -+{
     -+	return tab->ops->seek_record(tab->table_arg, it, rec);
     -+}
     ++/* The reader struct is a handle to an open reftable file. */
     ++struct reftable_reader;
      +
     -+uint64_t reftable_table_max_update_index(struct reftable_table *tab)
     -+{
     -+	return tab->ops->max_update_index(tab->table_arg);
     -+}
     ++/* reftable_new_reader opens a reftable for reading. If successful, returns 0
     ++ * code and sets pp. The name is used for creating a stack. Typically, it is the
     ++ * basename of the file. The block source `src` is owned by the reader, and is
     ++ * closed on calling reftable_reader_destroy().
     ++ */
     ++int reftable_new_reader(struct reftable_reader **pp,
     ++			struct reftable_block_source *src, const char *name);
     ++
     ++/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted
     ++   in the table.  To seek to the start of the table, use name = "".
     ++
     ++   example:
     ++
     ++   struct reftable_reader *r = NULL;
     ++   int err = reftable_new_reader(&r, &src, "filename");
     ++   if (err < 0) { ... }
     ++   struct reftable_iterator it  = {0};
     ++   err = reftable_reader_seek_ref(r, &it, "refs/heads/master");
     ++   if (err < 0) { ... }
     ++   struct reftable_ref_record ref  = {0};
     ++   while (1) {
     ++     err = reftable_iterator_next_ref(&it, &ref);
     ++     if (err > 0) {
     ++       break;
     ++     }
     ++     if (err < 0) {
     ++       ..error handling..
     ++     }
     ++     ..found..
     ++   }
     ++   reftable_iterator_destroy(&it);
     ++   reftable_ref_record_release(&ref);
     ++ */
     ++int reftable_reader_seek_ref(struct reftable_reader *r,
     ++			     struct reftable_iterator *it, const char *name);
      +
     -+uint64_t reftable_table_min_update_index(struct reftable_table *tab)
     -+{
     -+	return tab->ops->min_update_index(tab->table_arg);
     -+}
     ++/* returns the hash ID used in this table. */
     ++uint32_t reftable_reader_hash_id(struct reftable_reader *r);
      +
     -+uint32_t reftable_table_hash_id(struct reftable_table *tab)
     -+{
     -+	return tab->ops->hash_id(tab->table_arg);
     -+}
     ++/* seek to logs for the given name, older than update_index. To seek to the
     ++   start of the table, use name = "".
     ++ */
     ++int reftable_reader_seek_log_at(struct reftable_reader *r,
     ++				struct reftable_iterator *it, const char *name,
     ++				uint64_t update_index);
     ++
     ++/* seek to newest log entry for given name. */
     ++int reftable_reader_seek_log(struct reftable_reader *r,
     ++			     struct reftable_iterator *it, const char *name);
     ++
     ++/* closes and deallocates a reader. */
     ++void reftable_reader_free(struct reftable_reader *);
     ++
     ++/* return an iterator for the refs pointing to `oid`. */
     ++int reftable_reader_refs_for(struct reftable_reader *r,
     ++			     struct reftable_iterator *it, uint8_t *oid);
     ++
     ++/* return the max_update_index for a table */
     ++uint64_t reftable_reader_max_update_index(struct reftable_reader *r);
     ++
     ++/* return the min_update_index for a table */
     ++uint64_t reftable_reader_min_update_index(struct reftable_reader *r);
     ++
     ++#endif
 11:  bf6b929b86 ! 12:  581a4200d6 reftable: file level tests
     @@ Metadata
      Author: Han-Wen Nienhuys <[email protected]>
      
       ## Commit message ##
     -    reftable: file level tests
     +    reftable: reftable file level tests
     +
     +    With support for reading and writing files in place, we can construct files (in
     +    memory) and attempt to read them back.
     +
     +    Because some sections of the format are optional (eg. indices, log entries), we
     +    have to exercise this code using multiple sizes of input data
      
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
      @@ Makefile: REFTABLE_OBJS += reftable/zlib-compat.o
     - 
     + REFTABLE_TEST_OBJS += reftable/basics_test.o
       REFTABLE_TEST_OBJS += reftable/block_test.o
       REFTABLE_TEST_OBJS += reftable/record_test.o
      +REFTABLE_TEST_OBJS += reftable/reftable_test.o
     - REFTABLE_TEST_OBJS += reftable/strbuf_test.o
       REFTABLE_TEST_OBJS += reftable/test_framework.o
       REFTABLE_TEST_OBJS += reftable/tree_test.o
     + 
      
       ## reftable/reftable_test.c (new) ##
      @@
     @@ reftable/reftable_test.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "reftable.h"
     -+
      +#include "system.h"
      +
      +#include "basics.h"
     @@ reftable/reftable_test.c (new)
      +#include "record.h"
      +#include "test_framework.h"
      +#include "reftable-tests.h"
     ++#include "reftable-stack.h"
      +
      +static const int update_index = 5;
      +
     @@ reftable/reftable_test.c (new)
      +	uint8_t in[] = "hello";
      +	strbuf_add(&buf, in, sizeof(in));
      +	block_source_from_strbuf(&source, &buf);
     -+	assert(block_source_size(&source) == 6);
     ++	EXPECT(block_source_size(&source) == 6);
      +	n = block_source_read_block(&source, &out, 0, sizeof(in));
     -+	assert(n == sizeof(in));
     -+	assert(!memcmp(in, out.data, n));
     ++	EXPECT(n == sizeof(in));
     ++	EXPECT(!memcmp(in, out.data, n));
      +	reftable_block_done(&out);
      +
      +	n = block_source_read_block(&source, &out, 1, 2);
     -+	assert(n == 2);
     -+	assert(!memcmp(out.data, "el", 2));
     ++	EXPECT(n == 2);
     ++	EXPECT(!memcmp(out.data, "el", 2));
      +
      +	reftable_block_done(&out);
      +	block_source_close(&source);
     @@ reftable/reftable_test.c (new)
      +		(*names)[i] = xstrdup(name);
      +
      +		n = reftable_writer_add_ref(w, &ref);
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +	}
      +
      +	for (i = 0; i < N; i++) {
     @@ reftable/reftable_test.c (new)
      +		log.message = "message";
      +
      +		n = reftable_writer_add_log(w, &log);
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +	}
      +
      +	n = reftable_writer_close(w);
     -+	assert(n == 0);
     ++	EXPECT(n == 0);
      +
      +	stats = writer_stats(w);
      +	for (i = 0; i < stats->ref_stats.blocks; i++) {
     @@ reftable/reftable_test.c (new)
      +		if (off == 0) {
      +			off = header_size((hash_id == SHA256_ID) ? 2 : 1);
      +		}
     -+		assert(buf->buf[off] == 'r');
     ++		EXPECT(buf->buf[off] == 'r');
      +	}
      +
     -+	assert(stats->log_stats.blocks > 0);
     ++	EXPECT(stats->log_stats.blocks > 0);
      +	reftable_writer_free(w);
      +}
      +
     @@ reftable/reftable_test.c (new)
      +	log.new_hash = hash2;
      +	reftable_writer_set_limits(w, update_index, update_index);
      +	err = reftable_writer_add_log(w, &log);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	err = reftable_writer_close(w);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	reftable_writer_free(w);
      +	strbuf_release(&buf);
      +}
     @@ reftable/reftable_test.c (new)
      +		ref.update_index = i;
      +
      +		err = reftable_writer_add_ref(w, &ref);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +	for (i = 0; i < N; i++) {
      +		uint8_t hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     @@ reftable/reftable_test.c (new)
      +		log.new_hash = hash2;
      +
      +		err = reftable_writer_add_log(w, &log);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +
      +	n = reftable_writer_close(w);
     -+	assert(n == 0);
     ++	EXPECT(n == 0);
      +
      +	stats = writer_stats(w);
     -+	assert(stats->log_stats.blocks > 0);
     ++	EXPECT(stats->log_stats.blocks > 0);
      +	reftable_writer_free(w);
      +	w = NULL;
      +
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = init_reader(&rd, &source, "file.log");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_reader_seek_ref(&rd, &it, names[N - 1]);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_iterator_next_ref(&it, &ref);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	/* end of iteration. */
      +	err = reftable_iterator_next_ref(&it, &ref);
     -+	assert(0 < err);
     ++	EXPECT(0 < err);
      +
      +	reftable_iterator_destroy(&it);
     -+	reftable_ref_record_clear(&ref);
     ++	reftable_ref_record_release(&ref);
      +
      +	err = reftable_reader_seek_log(&rd, &it, "");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	i = 0;
      +	while (1) {
     @@ reftable/reftable_test.c (new)
      +			break;
      +		}
      +
     -+		assert_err(err);
     -+		assert_streq(names[i], log.refname);
     -+		assert(i == log.update_index);
     ++		EXPECT_ERR(err);
     ++		EXPECT_STREQ(names[i], log.refname);
     ++		EXPECT(i == log.update_index);
      +		i++;
     -+		reftable_log_record_clear(&log);
     ++		reftable_log_record_release(&log);
      +	}
      +
     -+	assert(i == N);
     ++	EXPECT(i == N);
      +	reftable_iterator_destroy(&it);
      +
      +	/* cleanup. */
     @@ reftable/reftable_test.c (new)
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = init_reader(&rd, &source, "file.ref");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_reader_seek_ref(&rd, &it, "");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	while (1) {
      +		struct reftable_ref_record ref = { NULL };
      +		int r = reftable_iterator_next_ref(&it, &ref);
     -+		assert(r >= 0);
     ++		EXPECT(r >= 0);
      +		if (r > 0) {
      +			break;
      +		}
     -+		assert(0 == strcmp(names[j], ref.refname));
     -+		assert(update_index == ref.update_index);
     ++		EXPECT(0 == strcmp(names[j], ref.refname));
     ++		EXPECT(update_index == ref.update_index);
      +
      +		j++;
     -+		reftable_ref_record_clear(&ref);
     ++		reftable_ref_record_release(&ref);
      +	}
     -+	assert(j == N);
     ++	EXPECT(j == N);
      +	reftable_iterator_destroy(&it);
      +	strbuf_release(&buf);
      +	free_names(names);
     @@ reftable/reftable_test.c (new)
      +	struct strbuf buf = STRBUF_INIT;
      +	int N = 1;
      +	write_table(&names, &buf, N, 4096, SHA1_ID);
     -+	assert(buf.len < 200);
     ++	EXPECT(buf.len < 200);
      +	strbuf_release(&buf);
      +	free_names(names);
      +}
     @@ reftable/reftable_test.c (new)
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = init_reader(&rd, &source, "file.ref");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_reader_seek_ref(&rd, &it, names[0]);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_iterator_next_log(&it, &log);
     -+	assert(err == REFTABLE_API_ERROR);
     ++	EXPECT(err == REFTABLE_API_ERROR);
      +
      +	strbuf_release(&buf);
      +	for (i = 0; i < N; i++) {
     @@ reftable/reftable_test.c (new)
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = init_reader(&rd, &source, "file.ref");
     -+	assert_err(err);
     -+	assert(hash_id == reftable_reader_hash_id(&rd));
     ++	EXPECT_ERR(err);
     ++	EXPECT(hash_id == reftable_reader_hash_id(&rd));
      +
      +	if (!index) {
      +		rd.ref_offsets.index_offset = 0;
      +	} else {
     -+		assert(rd.ref_offsets.index_offset > 0);
     ++		EXPECT(rd.ref_offsets.index_offset > 0);
      +	}
      +
      +	for (i = 1; i < N; i++) {
      +		int err = reftable_reader_seek_ref(&rd, &it, names[i]);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +		err = reftable_iterator_next_ref(&it, &ref);
     -+		assert_err(err);
     -+		assert(0 == strcmp(names[i], ref.refname));
     -+		assert(i == ref.value[0]);
     ++		EXPECT_ERR(err);
     ++		EXPECT(0 == strcmp(names[i], ref.refname));
     ++		EXPECT(i == ref.value[0]);
      +
     -+		reftable_ref_record_clear(&ref);
     ++		reftable_ref_record_release(&ref);
      +		reftable_iterator_destroy(&it);
      +	}
      +
     @@ reftable/reftable_test.c (new)
      +	if (err == 0) {
      +		struct reftable_ref_record ref = { NULL };
      +		int err = reftable_iterator_next_ref(&it, &ref);
     -+		assert(err > 0);
     ++		EXPECT(err > 0);
      +	} else {
     -+		assert(err > 0);
     ++		EXPECT(err > 0);
      +	}
      +
      +	strbuf_release(&pastLast);
     @@ reftable/reftable_test.c (new)
      +		 */
      +		/* blocks. */
      +		n = reftable_writer_add_ref(w, &ref);
     -+		assert(n == 0);
     ++		EXPECT(n == 0);
      +
      +		if (!memcmp(hash1, want_hash, SHA1_SIZE) ||
      +		    !memcmp(hash2, want_hash, SHA1_SIZE)) {
     @@ reftable/reftable_test.c (new)
      +	}
      +
      +	n = reftable_writer_close(w);
     -+	assert(n == 0);
     ++	EXPECT(n == 0);
      +
      +	reftable_writer_free(w);
      +	w = NULL;
     @@ reftable/reftable_test.c (new)
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = init_reader(&rd, &source, "file.ref");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	if (!indexed) {
      +		rd.obj_offsets.is_present = 0;
      +	}
      +
      +	err = reftable_reader_seek_ref(&rd, &it, "");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	reftable_iterator_destroy(&it);
      +
      +	err = reftable_reader_refs_for(&rd, &it, want_hash);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	j = 0;
      +	while (1) {
      +		int err = reftable_iterator_next_ref(&it, &ref);
     -+		assert(err >= 0);
     ++		EXPECT(err >= 0);
      +		if (err > 0) {
      +			break;
      +		}
      +
     -+		assert(j < want_names_len);
     -+		assert(0 == strcmp(ref.refname, want_names[j]));
     ++		EXPECT(j < want_names_len);
     ++		EXPECT(0 == strcmp(ref.refname, want_names[j]));
      +		j++;
     -+		reftable_ref_record_clear(&ref);
     ++		reftable_ref_record_release(&ref);
      +	}
     -+	assert(j == want_names_len);
     ++	EXPECT(j == want_names_len);
      +
      +	strbuf_release(&buf);
      +	free_names(want_names);
     @@ reftable/reftable_test.c (new)
      +	reftable_writer_set_limits(w, 1, 1);
      +
      +	err = reftable_writer_close(w);
     -+	assert(err == REFTABLE_EMPTY_TABLE_ERROR);
     ++	EXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);
      +	reftable_writer_free(w);
      +
     -+	assert(buf.len == header_size(1) + footer_size(1));
     ++	EXPECT(buf.len == header_size(1) + footer_size(1));
      +
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = reftable_new_reader(&rd, &source, "filename");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_reader_seek_ref(rd, &it, "");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_iterator_next_ref(&it, &rec);
     -+	assert(err > 0);
     ++	EXPECT(err > 0);
      +
      +	reftable_iterator_destroy(&it);
      +	reftable_reader_free(rd);
     @@ reftable/reftable_test.c (new)
      +
      +int reftable_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("test_log_write_read", test_log_write_read);
     -+	add_test_case("test_table_read_write_seek_linear_sha256",
     -+		      &test_table_read_write_seek_linear_sha256);
     -+	add_test_case("test_log_buffer_size", test_log_buffer_size);
     -+	add_test_case("test_table_write_small_table",
     -+		      &test_table_write_small_table);
     -+	add_test_case("test_buffer", &test_buffer);
     -+	add_test_case("test_table_read_api", &test_table_read_api);
     -+	add_test_case("test_table_read_write_sequential",
     -+		      &test_table_read_write_sequential);
     -+	add_test_case("test_table_read_write_seek_linear",
     -+		      &test_table_read_write_seek_linear);
     -+	add_test_case("test_table_read_write_seek_index",
     -+		      &test_table_read_write_seek_index);
     -+	add_test_case("test_table_read_write_refs_for_no_index",
     -+		      &test_table_refs_for_no_index);
     -+	add_test_case("test_table_read_write_refs_for_obj_index",
     -+		      &test_table_refs_for_obj_index);
     -+	add_test_case("test_table_empty", &test_table_empty);
     -+	return test_main(argc, argv);
     ++	test_log_write_read();
     ++	test_table_read_write_seek_linear_sha256();
     ++	test_log_buffer_size();
     ++	test_table_write_small_table();
     ++	test_buffer();
     ++	test_table_read_api();
     ++	test_table_read_write_sequential();
     ++	test_table_read_write_seek_linear();
     ++	test_table_read_write_seek_index();
     ++	test_table_refs_for_no_index();
     ++	test_table_refs_for_obj_index();
     ++	test_table_empty();
     ++	return 0;
      +}
      
       ## t/helper/test-reftable.c ##
      @@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
     - {
     + 	basics_test_main(argc, argv);
       	block_test_main(argc, argv);
       	record_test_main(argc, argv);
      +	reftable_test_main(argc, argv);
     - 	strbuf_test_main(argc, argv);
       	tree_test_main(argc, argv);
       	return 0;
     + }
 12:  4e38db7f48 ! 13:  a26fb180ad reftable: rest of library
     @@ Metadata
       ## Commit message ##
          reftable: rest of library
      
     -    This will be further split up once preceding commits have passed review.
     -
          Signed-off-by: Han-Wen Nienhuys <[email protected]>
      
       ## Makefile ##
     -@@ Makefile: XDIFF_OBJS += xdiff/xutils.o
     - REFTABLE_OBJS += reftable/basics.o
     +@@ Makefile: REFTABLE_OBJS += reftable/error.o
       REFTABLE_OBJS += reftable/block.o
       REFTABLE_OBJS += reftable/blocksource.o
     --REFTABLE_OBJS += reftable/publicbasics.o
     - REFTABLE_OBJS += reftable/compat.o
       REFTABLE_OBJS += reftable/iter.o
      +REFTABLE_OBJS += reftable/merged.o
      +REFTABLE_OBJS += reftable/pq.o
     -+REFTABLE_OBJS += reftable/publicbasics.o
     + REFTABLE_OBJS += reftable/publicbasics.o
       REFTABLE_OBJS += reftable/reader.o
       REFTABLE_OBJS += reftable/record.o
      +REFTABLE_OBJS += reftable/refname.o
       REFTABLE_OBJS += reftable/reftable.o
      +REFTABLE_OBJS += reftable/stack.o
     - REFTABLE_OBJS += reftable/strbuf.o
       REFTABLE_OBJS += reftable/tree.o
       REFTABLE_OBJS += reftable/writer.o
     -@@ Makefile: REFTABLE_OBJS += reftable/zlib-compat.o
     - 
     - REFTABLE_TEST_OBJS += reftable/block_test.o
     - REFTABLE_TEST_OBJS += reftable/record_test.o
     -+REFTABLE_TEST_OBJS += reftable/refname_test.o
     - REFTABLE_TEST_OBJS += reftable/reftable_test.o
     -+REFTABLE_TEST_OBJS += reftable/stack_test.o
     - REFTABLE_TEST_OBJS += reftable/strbuf_test.o
     - REFTABLE_TEST_OBJS += reftable/test_framework.o
     - REFTABLE_TEST_OBJS += reftable/tree_test.o
     + REFTABLE_OBJS += reftable/zlib-compat.o
      
       ## reftable/VERSION (new) ##
      @@
     -+7134eb9f8171a9759800f4187f9e6dde997335e7 C: NULL iso 0 for init
     ++b8ec0f74c74cb6752eb2033ad8e755a9c19aad15 C: add missing header
      
       ## reftable/dump.c (new) ##
      @@
     @@ reftable/dump.c (new)
      +
      +static int dump_table(const char *tablename)
      +{
     -+	struct reftable_block_source src = { NULL };
     ++	struct reftable_block_source src = { 0 };
      +	int err = reftable_block_source_from_file(&src, tablename);
     -+	struct reftable_iterator it = { NULL };
     -+	struct reftable_ref_record ref = { NULL };
     -+	struct reftable_log_record log = { NULL };
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_log_record log = { 0 };
      +	struct reftable_reader *r = NULL;
      +
      +	if (err < 0)
     @@ reftable/dump.c (new)
      +{
      +	struct reftable_stack *stack = NULL;
      +	struct reftable_write_options cfg = {};
     -+	struct reftable_iterator it = { NULL };
     -+	struct reftable_ref_record ref = { NULL };
     -+	struct reftable_log_record log = { NULL };
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_log_record log = { 0 };
      +	struct reftable_merged_table *merged = NULL;
      +
      +	int err = reftable_new_stack(&stack, stackdir, cfg);
     @@ reftable/merged.c (new)
      +#include "pq.h"
      +#include "reader.h"
      +#include "record.h"
     -+#include "reftable.h"
     ++#include "reftable-merged.h"
     ++#include "reftable-error.h"
      +#include "system.h"
      +
      +static int merged_iter_init(struct merged_iter *mi)
     @@ reftable/merged.c (new)
      +{
      +	struct merged_iter *mi = (struct merged_iter *)p;
      +	int i = 0;
     -+	merged_iter_pqueue_clear(&mi->pq);
     ++	merged_iter_pqueue_release(&mi->pq);
      +	for (i = 0; i < mi->stack_len; i++) {
      +		reftable_iterator_destroy(&mi->stack[i]);
      +	}
     @@ reftable/merged.c (new)
      +}
      +
      +/* clears the list of subtable, without affecting the readers themselves. */
     -+void merged_table_clear(struct reftable_merged_table *mt)
     ++void merged_table_release(struct reftable_merged_table *mt)
      +{
      +	FREE_AND_NULL(mt->stack);
      +	mt->stack_len = 0;
     @@ reftable/merged.c (new)
      +	if (mt == NULL) {
      +		return;
      +	}
     -+	merged_table_clear(mt);
     ++	merged_table_release(mt);
      +	reftable_free(mt);
      +}
      +
     @@ reftable/merged.c (new)
      +	return mt->min;
      +}
      +
     ++static int reftable_table_seek_record(struct reftable_table *tab,
     ++				      struct reftable_iterator *it,
     ++				      struct reftable_record *rec)
     ++{
     ++	return tab->ops->seek_record(tab->table_arg, it, rec);
     ++}
     ++
      +static int merged_table_seek_record(struct reftable_merged_table *mt,
      +				    struct reftable_iterator *it,
      +				    struct reftable_record *rec)
     @@ reftable/merged.h (new)
      +#define MERGED_H
      +
      +#include "pq.h"
     -+#include "reftable.h"
      +
      +struct reftable_merged_table {
      +	struct reftable_table *stack;
     @@ reftable/merged.h (new)
      +	struct merged_iter_pqueue pq;
      +};
      +
     -+void merged_table_clear(struct reftable_merged_table *mt);
     ++void merged_table_release(struct reftable_merged_table *mt);
      +
      +#endif
      
     @@ reftable/merged_test.c (new)
      +#include "pq.h"
      +#include "reader.h"
      +#include "record.h"
     -+#include "reftable.h"
      +#include "test_framework.h"
     ++#include "reftable-merged.h"
      +#include "reftable-tests.h"
     ++#include "reftable-generic.h"
     ++#include "reftable-stack.h"
      +
      +static void test_pq(void)
      +{
     @@ reftable/merged_test.c (new)
      +		reftable_free(names[i]);
      +	}
      +
     -+	merged_iter_pqueue_clear(&pq);
     ++	merged_iter_pqueue_release(&pq);
      +}
      +
      +static void write_test_table(struct strbuf *buf,
     @@ reftable/merged_test.c (new)
      +	}
      +
      +	err = reftable_writer_close(w);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	reftable_writer_free(w);
      +}
     @@ reftable/merged_test.c (new)
      +
      +		err = reftable_new_reader(&(*readers)[i], &(*source)[i],
      +					  "name");
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +		reftable_table_from_reader(&tabs[i], (*readers)[i]);
      +	}
      +
      +	err = reftable_new_merged_table(&mt, tabs, n, SHA1_ID);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	return mt;
      +}
      +
     @@ reftable/merged_test.c (new)
      +	struct reftable_ref_record ref = { NULL };
      +	struct reftable_iterator it = { NULL };
      +	int err = reftable_merged_table_seek_ref(mt, &it, "a");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_iterator_next_ref(&it, &ref);
     -+	assert_err(err);
     -+	assert(ref.update_index == 2);
     -+	reftable_ref_record_clear(&ref);
     ++	EXPECT_ERR(err);
     ++	EXPECT(ref.update_index == 2);
     ++	reftable_ref_record_release(&ref);
      +	reftable_iterator_destroy(&it);
      +	readers_destroy(readers, 2);
      +	reftable_merged_table_free(mt);
     @@ reftable/merged_test.c (new)
      +	size_t cap = 0;
      +	int i = 0;
      +
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	while (len < 100) { /* cap loops/recursion. */
      +		struct reftable_ref_record ref = { NULL };
      +		int err = reftable_iterator_next_ref(&it, &ref);
     @@ reftable/merged_test.c (new)
      +		assert(reftable_ref_record_equal(&want[i], &out[i], SHA1_SIZE));
      +	}
      +	for (i = 0; i < len; i++) {
     -+		reftable_ref_record_clear(&out[i]);
     ++		reftable_ref_record_release(&out[i]);
      +	}
      +	reftable_free(out);
      +
     @@ reftable/merged_test.c (new)
      +	reftable_writer_set_limits(w, 1, 1);
      +
      +	err = reftable_writer_add_ref(w, &rec);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_writer_close(w);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	reftable_writer_free(w);
      +
      +	block_source_from_strbuf(&source, &buf);
      +
      +	err = reftable_new_reader(&rd, &source, "filename");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	hash_id = reftable_reader_hash_id(rd);
      +	assert(hash_id == SHA1_ID);
      +
      +	reftable_table_from_reader(&tab[0], rd);
      +	err = reftable_new_merged_table(&merged, tab, 1, SHA1_ID);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	reftable_reader_free(rd);
      +	reftable_merged_table_free(merged);
     @@ reftable/merged_test.c (new)
      +
      +int merged_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("test_merged_between", &test_merged_between);
     -+	add_test_case("test_pq", &test_pq);
     -+	add_test_case("test_merged", &test_merged);
     -+	add_test_case("test_default_write_opts", &test_default_write_opts);
     -+	return test_main(argc, argv);
     ++	test_merged_between();
     ++	test_pq();
     ++	test_merged();
     ++	test_default_write_opts();
     ++	return 0;
      +}
      
       ## reftable/pq.c (new) ##
     @@ reftable/pq.c (new)
      +
      +#include "pq.h"
      +
     -+#include "reftable.h"
     ++#include "reftable-record.h"
      +#include "system.h"
      +#include "basics.h"
      +
     @@ reftable/pq.c (new)
      +	}
      +}
      +
     -+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
     ++void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
      +{
      +	int i = 0;
      +	for (i = 0; i < pq->len; i++) {
     @@ reftable/pq.h (new)
      +void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
      +struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
      +void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
     -+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
     ++void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);
      +
      +#endif
      
     @@ reftable/refname.c (new)
      +*/
      +
      +#include "system.h"
     -+#include "reftable.h"
     ++#include "reftable-error.h"
      +#include "basics.h"
      +#include "refname.h"
     -+#include "strbuf.h"
     ++#include "reftable-iterator.h"
      +
      +struct find_arg {
      +	char **names;
     @@ reftable/refname.c (new)
      +	}
      +
      +	err = reftable_table_read_ref(&mod->tab, name, &ref);
     -+	reftable_ref_record_clear(&ref);
     ++	reftable_ref_record_release(&ref);
      +	return err;
      +}
      +
     -+static void modification_clear(struct modification *mod)
     ++static void modification_release(struct modification *mod)
      +{
      +	/* don't delete the strings themselves; they're owned by ref records.
      +	 */
     @@ reftable/refname.c (new)
      +	}
      +
      +done:
     -+	reftable_ref_record_clear(&ref);
     ++	reftable_ref_record_release(&ref);
      +	reftable_iterator_destroy(&it);
      +	return err;
      +}
     @@ reftable/refname.c (new)
      +	}
      +
      +	err = modification_validate(&mod);
     -+	modification_clear(&mod);
     ++	modification_release(&mod);
      +	return err;
      +}
      +
     @@ reftable/refname.h (new)
      +#ifndef REFNAME_H
      +#define REFNAME_H
      +
     -+#include "reftable.h"
     ++#include "reftable-record.h"
     ++#include "reftable-generic.h"
      +
      +struct modification {
      +	struct reftable_table tab;
     @@ reftable/refname_test.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "reftable.h"
     -+
      +#include "basics.h"
      +#include "block.h"
      +#include "blocksource.h"
     @@ reftable/refname_test.c (new)
      +#include "reader.h"
      +#include "record.h"
      +#include "refname.h"
     ++#include "reftable-error.h"
     ++#include "reftable-writer.h"
      +#include "system.h"
      +
      +#include "test_framework.h"
     @@ reftable/refname_test.c (new)
      +	reftable_writer_set_limits(w, 1, 1);
      +
      +	err = reftable_writer_add_ref(w, &rec);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_writer_close(w);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	reftable_writer_free(w);
      +
      +	block_source_from_strbuf(&source, &buf);
      +	err = reftable_new_reader(&rd, &source, "filename");
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	reftable_table_from_reader(&tab, rd);
      +
     @@ reftable/refname_test.c (new)
      +		}
      +
      +		err = modification_validate(&mod);
     -+		assert(err == cases[i].error_code);
     ++		EXPECT(err == cases[i].error_code);
      +	}
      +
      +	reftable_reader_free(rd);
     @@ reftable/refname_test.c (new)
      +
      +int refname_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("test_conflict", &test_conflict);
     -+	return test_main(argc, argv);
     ++	test_conflict();
     ++	return 0;
      +}
      
     - ## reftable/reftable.c ##
     -@@ reftable/reftable.c: int reftable_table_seek_ref(struct reftable_table *tab,
     - 	struct reftable_ref_record ref = {
     - 		.refname = (char *)name,
     - 	};
     --	struct reftable_record rec = { 0 };
     + ## reftable/reftable-generic.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef REFTABLE_GENERIC_H
     ++#define REFTABLE_GENERIC_H
     ++
     ++#include "reftable-iterator.h"
     ++#include "reftable-reader.h"
     ++#include "reftable-merged.h"
     ++
     ++/*
     ++ Provides a unified API for reading tables, either merged tables, or single
     ++ readers.
     ++*/
     ++struct reftable_table {
     ++	struct reftable_table_vtable *ops;
     ++	void *table_arg;
     ++};
     ++
     ++int reftable_table_seek_ref(struct reftable_table *tab,
     ++			    struct reftable_iterator *it, const char *name);
     ++
     ++void reftable_table_from_reader(struct reftable_table *tab,
     ++				struct reftable_reader *reader);
     ++
     ++/* returns the hash ID from a generic reftable_table */
     ++uint32_t reftable_table_hash_id(struct reftable_table *tab);
     ++
     ++/* create a generic table from reftable_merged_table */
     ++void reftable_table_from_merged_table(struct reftable_table *tab,
     ++				      struct reftable_merged_table *table);
     ++
     ++/* returns the max update_index covered by this table. */
     ++uint64_t reftable_table_max_update_index(struct reftable_table *tab);
     ++
     ++/* returns the min update_index covered by this table. */
     ++uint64_t reftable_table_min_update_index(struct reftable_table *tab);
     ++
     ++/* convenience function to read a single ref. Returns < 0 for error, 0
     ++   for success, and 1 if ref not found. */
     ++int reftable_table_read_ref(struct reftable_table *tab, const char *name,
     ++			    struct reftable_ref_record *ref);
     ++
     ++#endif
     +
     + ## reftable/reftable-merged.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef REFTABLE_MERGED_H
     ++#define REFTABLE_MERGED_H
     ++
     ++#include "reftable-iterator.h"
     ++
     ++/*
     ++ Merged tables
     ++
     ++ A ref database kept in a sequence of table files. The merged_table presents a
     ++ unified view to reading (seeking, iterating) a sequence of immutable tables.
     ++
     ++ The merged tables are on purpose kept disconnected from their actual storage
     ++ (eg. files on disk), because it is useful to merge tables aren't files. For
     ++ example, the per-workspace and global ref namespace can be implemented as a
     ++ merged table of two stacks of file-backed reftables.
     ++*/
     ++
     ++/* A merged table is implements seeking/iterating over a stack of tables. */
     ++struct reftable_merged_table;
     ++
     ++/* A generic reftable; see below. */
     ++struct reftable_table;
     ++
     ++/* reftable_new_merged_table creates a new merged table. It takes ownership of
     ++   the stack array.
     ++*/
     ++int reftable_new_merged_table(struct reftable_merged_table **dest,
     ++			      struct reftable_table *stack, int n,
     ++			      uint32_t hash_id);
     ++
     ++/* returns an iterator positioned just before 'name' */
     ++int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
     ++				   struct reftable_iterator *it,
     ++				   const char *name);
     ++
     ++/* returns an iterator for log entry, at given update_index */
     ++int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
     ++				      struct reftable_iterator *it,
     ++				      const char *name, uint64_t update_index);
     ++
     ++/* like reftable_merged_table_seek_log_at but look for the newest entry. */
     ++int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
     ++				   struct reftable_iterator *it,
     ++				   const char *name);
     ++
     ++/* returns the max update_index covered by this merged table. */
     ++uint64_t
     ++reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
     ++
     ++/* returns the min update_index covered by this merged table. */
     ++uint64_t
     ++reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
     ++
     ++/* releases memory for the merged_table */
     ++void reftable_merged_table_free(struct reftable_merged_table *m);
     ++
     ++/* return the hash ID of the merged table. */
     ++uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);
     ++
     ++#endif
     +
     + ## reftable/reftable-stack.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef REFTABLE_STACK_H
     ++#define REFTABLE_STACK_H
     ++
     ++#include "reftable-writer.h"
     ++
     ++/*
     ++  The stack presents an interface to a mutable sequence of reftables.
     ++
     ++  A stack can be mutated by pushing a table to the top of the stack.
     ++
     ++  The reftable_stack automatically compacts files on disk to ensure good
     ++  amortized performance.
     ++*/
     ++struct reftable_stack;
     ++
     ++/* open a new reftable stack. The tables along with the table list will be
     ++   stored in 'dir'. Typically, this should be .git/reftables.
     ++*/
     ++int reftable_new_stack(struct reftable_stack **dest, const char *dir,
     ++		       struct reftable_write_options config);
     ++
     ++/* returns the update_index at which a next table should be written. */
     ++uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
     ++
     ++/* holds a transaction to add tables at the top of a stack. */
     ++struct reftable_addition;
     ++
     ++/*
     ++  returns a new transaction to add reftables to the given stack. As a side
     ++  effect, the ref database is locked.
     ++*/
     ++int reftable_stack_new_addition(struct reftable_addition **dest,
     ++				struct reftable_stack *st);
     ++
     ++/* Adds a reftable to transaction. */
     ++int reftable_addition_add(struct reftable_addition *add,
     ++			  int (*write_table)(struct reftable_writer *wr,
     ++					     void *arg),
     ++			  void *arg);
     ++
     ++/* Commits the transaction, releasing the lock. */
     ++int reftable_addition_commit(struct reftable_addition *add);
     ++
     ++/* Release all non-committed data from the transaction, and deallocate the
     ++   transaction. Releases the lock if held. */
     ++void reftable_addition_destroy(struct reftable_addition *add);
     ++
     ++/* add a new table to the stack. The write_table function must call
     ++   reftable_writer_set_limits, add refs and return an error value. */
     ++int reftable_stack_add(struct reftable_stack *st,
     ++		       int (*write_table)(struct reftable_writer *wr,
     ++					  void *write_arg),
     ++		       void *write_arg);
     ++
     ++/* returns the merged_table for seeking. This table is valid until the
     ++   next write or reload, and should not be closed or deleted.
     ++*/
     ++struct reftable_merged_table *
     ++reftable_stack_merged_table(struct reftable_stack *st);
     ++
     ++/* frees all resources associated with the stack. */
     ++void reftable_stack_destroy(struct reftable_stack *st);
     ++
     ++/* Reloads the stack if necessary. This is very cheap to run if the stack was up
     ++ * to date */
     ++int reftable_stack_reload(struct reftable_stack *st);
     ++
     ++/* Policy for expiring reflog entries. */
     ++struct reftable_log_expiry_config {
     ++	/* Drop entries older than this timestamp */
     ++	uint64_t time;
     ++
     ++	/* Drop older entries */
     ++	uint64_t min_update_index;
     ++};
     ++
     ++/* compacts all reftables into a giant table. Expire reflog entries if config is
     ++ * non-NULL */
     ++int reftable_stack_compact_all(struct reftable_stack *st,
     ++			       struct reftable_log_expiry_config *config);
     ++
     ++/* heuristically compact unbalanced table stack. */
     ++int reftable_stack_auto_compact(struct reftable_stack *st);
     ++
     ++/* convenience function to read a single ref. Returns < 0 for error, 0
     ++   for success, and 1 if ref not found. */
     ++int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
     ++			    struct reftable_ref_record *ref);
     ++
     ++/* convenience function to read a single log. Returns < 0 for error, 0
     ++   for success, and 1 if ref not found. */
     ++int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
     ++			    struct reftable_log_record *log);
     ++
     ++/* statistics on past compactions. */
     ++struct reftable_compaction_stats {
     ++	uint64_t bytes; /* total number of bytes written */
     ++	uint64_t entries_written; /* total number of entries written, including
     ++				     failures. */
     ++	int attempts; /* how often we tried to compact */
     ++	int failures; /* failures happen on concurrent updates */
     ++};
     ++
     ++/* return statistics for compaction up till now. */
     ++struct reftable_compaction_stats *
     ++reftable_stack_compaction_stats(struct reftable_stack *st);
     ++
     ++#endif
     +
     + ## reftable/reftable.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "record.h"
     ++#include "reader.h"
     ++#include "reftable-iterator.h"
     ++#include "reftable-generic.h"
     ++
     ++static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,
     ++				     struct reftable_record *rec)
     ++{
     ++	return reader_seek((struct reftable_reader *)tab, it, rec);
     ++}
     ++
     ++static uint32_t reftable_reader_hash_id_void(void *tab)
     ++{
     ++	return reftable_reader_hash_id((struct reftable_reader *)tab);
     ++}
     ++
     ++static uint64_t reftable_reader_min_update_index_void(void *tab)
     ++{
     ++	return reftable_reader_min_update_index((struct reftable_reader *)tab);
     ++}
     ++
     ++static uint64_t reftable_reader_max_update_index_void(void *tab)
     ++{
     ++	return reftable_reader_max_update_index((struct reftable_reader *)tab);
     ++}
     ++
     ++static struct reftable_table_vtable reader_vtable = {
     ++	.seek_record = reftable_reader_seek_void,
     ++	.hash_id = reftable_reader_hash_id_void,
     ++	.min_update_index = reftable_reader_min_update_index_void,
     ++	.max_update_index = reftable_reader_max_update_index_void,
     ++};
     ++
     ++int reftable_table_seek_ref(struct reftable_table *tab,
     ++			    struct reftable_iterator *it, const char *name)
     ++{
     ++	struct reftable_ref_record ref = {
     ++		.refname = (char *)name,
     ++	};
      +	struct reftable_record rec = { NULL };
     - 	reftable_record_from_ref(&rec, &ref);
     - 	return tab->ops->seek_record(tab->table_arg, it, &rec);
     - }
     -@@ reftable/reftable.c: void reftable_table_from_reader(struct reftable_table *tab,
     - int reftable_table_read_ref(struct reftable_table *tab, const char *name,
     - 			    struct reftable_ref_record *ref)
     - {
     --	struct reftable_iterator it = { 0 };
     ++	reftable_record_from_ref(&rec, &ref);
     ++	return tab->ops->seek_record(tab->table_arg, it, &rec);
     ++}
     ++
     ++void reftable_table_from_reader(struct reftable_table *tab,
     ++				struct reftable_reader *reader)
     ++{
     ++	assert(tab->ops == NULL);
     ++	tab->ops = &reader_vtable;
     ++	tab->table_arg = reader;
     ++}
     ++
     ++int reftable_table_read_ref(struct reftable_table *tab, const char *name,
     ++			    struct reftable_ref_record *ref)
     ++{
      +	struct reftable_iterator it = { NULL };
     - 	int err = reftable_table_seek_ref(tab, &it, name);
     - 	if (err)
     - 		goto done;
     ++	int err = reftable_table_seek_ref(tab, &it, name);
     ++	if (err)
     ++		goto done;
     ++
     ++	err = reftable_iterator_next_ref(&it, ref);
     ++	if (err)
     ++		goto done;
     ++
     ++	if (strcmp(ref->refname, name) ||
     ++	    reftable_ref_record_is_deletion(ref)) {
     ++		reftable_ref_record_release(ref);
     ++		err = 1;
     ++		goto done;
     ++	}
     ++
     ++done:
     ++	reftable_iterator_destroy(&it);
     ++	return err;
     ++}
     ++
     ++uint64_t reftable_table_max_update_index(struct reftable_table *tab)
     ++{
     ++	return tab->ops->max_update_index(tab->table_arg);
     ++}
     ++
     ++uint64_t reftable_table_min_update_index(struct reftable_table *tab)
     ++{
     ++	return tab->ops->min_update_index(tab->table_arg);
     ++}
     ++
     ++uint32_t reftable_table_hash_id(struct reftable_table *tab)
     ++{
     ++	return tab->ops->hash_id(tab->table_arg);
     ++}
      
       ## reftable/stack.c (new) ##
      @@
     @@ reftable/stack.c (new)
      +#include "merged.h"
      +#include "reader.h"
      +#include "refname.h"
     -+#include "reftable.h"
     ++#include "reftable-error.h"
     ++#include "reftable-record.h"
      +#include "writer.h"
      +
      +static int stack_try_add(struct reftable_stack *st,
     @@ reftable/stack.c (new)
      +static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
      +					     int reuse_open);
      +
     ++static int reftable_fd_write(void *arg, const void *data, size_t sz)
     ++{
     ++	int *fdp = (int *)arg;
     ++	return write(*fdp, data, sz);
     ++}
     ++
      +int reftable_new_stack(struct reftable_stack **dest, const char *dir,
      +		       struct reftable_write_options config)
      +{
     @@ reftable/stack.c (new)
      +	new_tables = NULL;
      +	st->readers_len = new_readers_len;
      +	if (st->merged != NULL) {
     -+		merged_table_clear(st->merged);
     ++		merged_table_release(st->merged);
      +		reftable_merged_table_free(st->merged);
      +	}
      +	if (st->readers != NULL) {
     @@ reftable/stack.c (new)
      +done:
      +	reftable_iterator_destroy(&it);
      +	if (mt != NULL) {
     -+		merged_table_clear(mt);
     ++		merged_table_release(mt);
      +		reftable_merged_table_free(mt);
      +	}
     -+	reftable_ref_record_clear(&ref);
     -+	reftable_log_record_clear(&log);
     ++	reftable_ref_record_release(&ref);
     ++	reftable_log_record_release(&log);
      +	st->stats.entries_written += entries;
      +	return err;
      +}
     @@ reftable/stack.c (new)
      +
      +done:
      +	if (err) {
     -+		reftable_log_record_clear(log);
     ++		reftable_log_record_release(log);
      +	}
      +	reftable_iterator_destroy(&it);
      +	return err;
     @@ reftable/stack.c (new)
      +
      +done:
      +	for (i = 0; i < len; i++) {
     -+		reftable_ref_record_clear(&refs[i]);
     ++		reftable_ref_record_release(&refs[i]);
      +	}
      +
      +	free(refs);
     @@ reftable/stack.h (new)
      +#ifndef STACK_H
      +#define STACK_H
      +
     -+#include "reftable.h"
      +#include "system.h"
     ++#include "reftable-writer.h"
     ++#include "reftable-stack.h"
      +
      +struct reftable_stack {
      +	char *list_file;
     @@ reftable/stack_test.c (new)
      +#include "basics.h"
      +#include "constants.h"
      +#include "record.h"
     -+#include "reftable.h"
      +#include "test_framework.h"
      +#include "reftable-tests.h"
      +
      +#include <sys/types.h>
      +#include <dirent.h>
      +
     ++static void clear_dir(const char *dirname)
     ++{
     ++	struct strbuf path = STRBUF_INIT;
     ++	strbuf_addstr(&path, dirname);
     ++	remove_dir_recursively(&path, 0);
     ++	strbuf_release(&path);
     ++}
     ++
      +static void test_read_file(void)
      +{
      +	char fn[256] = "/tmp/stack.test_read_file.XXXXXX";
     @@ reftable/stack_test.c (new)
      +	char *want[] = { "line1", "line2", "line3" };
      +	int i = 0;
      +
     -+	assert(fd > 0);
     ++	EXPECT(fd > 0);
      +	n = write(fd, out, strlen(out));
     -+	assert(n == strlen(out));
     ++	EXPECT(n == strlen(out));
      +	err = close(fd);
     -+	assert(err >= 0);
     ++	EXPECT(err >= 0);
      +
      +	err = read_lines(fn, &names);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	for (i = 0; names[i] != NULL; i++) {
     -+		assert(0 == strcmp(want[i], names[i]));
     ++		EXPECT(0 == strcmp(want[i], names[i]));
      +	}
      +	free_names(names);
      +	remove(fn);
     @@ reftable/stack_test.c (new)
      +	char **names = NULL;
      +	parse_names(buf, strlen(buf), &names);
      +
     -+	assert(NULL != names[0]);
     -+	assert(0 == strcmp(names[0], "line"));
     -+	assert(NULL == names[1]);
     ++	EXPECT(NULL != names[0]);
     ++	EXPECT(0 == strcmp(names[0], "line"));
     ++	EXPECT(NULL == names[1]);
      +	free_names(names);
      +}
      +
     @@ reftable/stack_test.c (new)
      +	char *b[] = { "a", "b", "d", NULL };
      +	char *c[] = { "a", "b", NULL };
      +
     -+	assert(names_equal(a, a));
     -+	assert(!names_equal(a, b));
     -+	assert(!names_equal(a, c));
     ++	EXPECT(names_equal(a, a));
     ++	EXPECT(!names_equal(a, b));
     ++	EXPECT(!names_equal(a, c));
      +}
      +
      +static int write_test_ref(struct reftable_writer *wr, void *arg)
     @@ reftable/stack_test.c (new)
      +	};
      +	struct reftable_ref_record dest = { NULL };
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st, &write_test_ref, &ref);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_read_ref(st, ref.refname, &dest);
     -+	assert_err(err);
     -+	assert(0 == strcmp("master", dest.target));
     ++	EXPECT_ERR(err);
     ++	EXPECT(0 == strcmp("master", dest.target));
      +
     -+	reftable_ref_record_clear(&dest);
     ++	reftable_ref_record_release(&dest);
      +	reftable_stack_destroy(st);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_uptodate(void)
     @@ reftable/stack_test.c (new)
      +		.target = "master",
      +	};
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st1, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_new_stack(&st2, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st1, &write_test_ref, &ref1);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st2, &write_test_ref, &ref2);
     -+	assert(err == REFTABLE_LOCK_ERROR);
     ++	EXPECT(err == REFTABLE_LOCK_ERROR);
      +
      +	err = reftable_stack_reload(st2);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st2, &write_test_ref, &ref2);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	reftable_stack_destroy(st1);
      +	reftable_stack_destroy(st2);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_transaction_api(void)
     @@ reftable/stack_test.c (new)
      +	};
      +	struct reftable_ref_record dest = { NULL };
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	reftable_addition_destroy(add);
      +
      +	err = reftable_stack_new_addition(&add, st);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_addition_add(add, &write_test_ref, &ref);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_addition_commit(add);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	reftable_addition_destroy(add);
      +
      +	err = reftable_stack_read_ref(st, ref.refname, &dest);
     -+	assert_err(err);
     -+	assert(0 == strcmp("master", dest.target));
     ++	EXPECT_ERR(err);
     ++	EXPECT(0 == strcmp("master", dest.target));
      +
     -+	reftable_ref_record_clear(&dest);
     ++	reftable_ref_record_release(&dest);
      +	reftable_stack_destroy(st);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_validate_refname(void)
     @@ reftable/stack_test.c (new)
      +	};
      +	char *additions[] = { "a", "a/b/c" };
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st, &write_test_ref, &ref);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	for (i = 0; i < ARRAY_SIZE(additions); i++) {
      +		struct reftable_ref_record ref = {
     @@ reftable/stack_test.c (new)
      +		};
      +
      +		err = reftable_stack_add(st, &write_test_ref, &ref);
     -+		assert(err == REFTABLE_NAME_CONFLICT);
     ++		EXPECT(err == REFTABLE_NAME_CONFLICT);
      +	}
      +
      +	reftable_stack_destroy(st);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static int write_error(struct reftable_writer *wr, void *arg)
     @@ reftable/stack_test.c (new)
      +		.update_index = 1,
      +		.target = "master",
      +	};
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st, &write_test_ref, &ref1);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st, &write_test_ref, &ref2);
     -+	assert(err == REFTABLE_API_ERROR);
     ++	EXPECT(err == REFTABLE_API_ERROR);
      +	reftable_stack_destroy(st);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_lock_failure(void)
     @@ reftable/stack_test.c (new)
      +	struct reftable_write_options cfg = { 0 };
      +	struct reftable_stack *st = NULL;
      +	int err, i;
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
      +		err = reftable_stack_add(st, &write_error, &i);
     -+		assert(err == i);
     ++		EXPECT(err == i);
      +	}
      +
      +	reftable_stack_destroy(st);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_add(void)
     @@ reftable/stack_test.c (new)
      +	struct reftable_log_record logs[2] = { { NULL } };
      +	int N = ARRAY_SIZE(refs);
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	st->disable_auto_compact = 1;
      +
      +	for (i = 0; i < N; i++) {
     @@ reftable/stack_test.c (new)
      +
      +	for (i = 0; i < N; i++) {
      +		int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +
      +	for (i = 0; i < N; i++) {
     @@ reftable/stack_test.c (new)
      +			.update_index = reftable_stack_next_update_index(st),
      +		};
      +		int err = reftable_stack_add(st, &write_test_log, &arg);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +
      +	err = reftable_stack_compact_all(st, NULL);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	for (i = 0; i < N; i++) {
      +		struct reftable_ref_record dest = { NULL };
      +
      +		int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
     -+		assert_err(err);
     -+		assert(reftable_ref_record_equal(&dest, refs + i, SHA1_SIZE));
     -+		reftable_ref_record_clear(&dest);
     ++		EXPECT_ERR(err);
     ++		EXPECT(reftable_ref_record_equal(&dest, refs + i, SHA1_SIZE));
     ++		reftable_ref_record_release(&dest);
      +	}
      +
      +	for (i = 0; i < N; i++) {
      +		struct reftable_log_record dest = { NULL };
      +		int err = reftable_stack_read_log(st, refs[i].refname, &dest);
     -+		assert_err(err);
     -+		assert(reftable_log_record_equal(&dest, logs + i, SHA1_SIZE));
     -+		reftable_log_record_clear(&dest);
     ++		EXPECT_ERR(err);
     ++		EXPECT(reftable_log_record_equal(&dest, logs + i, SHA1_SIZE));
     ++		reftable_log_record_release(&dest);
      +	}
      +
      +	/* cleanup */
      +	reftable_stack_destroy(st);
      +	for (i = 0; i < N; i++) {
     -+		reftable_ref_record_clear(&refs[i]);
     -+		reftable_log_record_clear(&logs[i]);
     ++		reftable_ref_record_release(&refs[i]);
     ++		reftable_log_record_release(&logs[i]);
      +	}
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_log_normalize(void)
     @@ reftable/stack_test.c (new)
      +		.update_index = 1,
      +	};
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	input.message = "one\ntwo";
      +	err = reftable_stack_add(st, &write_test_log, &arg);
     -+	assert(err == REFTABLE_API_ERROR);
     ++	EXPECT(err == REFTABLE_API_ERROR);
      +
      +	input.message = "one";
      +	err = reftable_stack_add(st, &write_test_log, &arg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_read_log(st, input.refname, &dest);
     -+	assert_err(err);
     -+	assert(0 == strcmp(dest.message, "one\n"));
     ++	EXPECT_ERR(err);
     ++	EXPECT(0 == strcmp(dest.message, "one\n"));
      +
      +	input.message = "two\n";
      +	arg.update_index = 2;
      +	err = reftable_stack_add(st, &write_test_log, &arg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +	err = reftable_stack_read_log(st, input.refname, &dest);
     -+	assert_err(err);
     -+	assert(0 == strcmp(dest.message, "two\n"));
     ++	EXPECT_ERR(err);
     ++	EXPECT(0 == strcmp(dest.message, "two\n"));
      +
      +	/* cleanup */
      +	reftable_stack_destroy(st);
     -+	reftable_log_record_clear(&dest);
     -+	reftable_clear_dir(dir);
     ++	reftable_log_record_release(&dest);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_tombstone(void)
     @@ reftable/stack_test.c (new)
      +	struct reftable_ref_record dest = { NULL };
      +	struct reftable_log_record log_dest = { NULL };
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	for (i = 0; i < N; i++) {
      +		const char *buf = "branch";
     @@ reftable/stack_test.c (new)
      +	}
      +	for (i = 0; i < N; i++) {
      +		int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +	for (i = 0; i < N; i++) {
      +		struct write_log_arg arg = {
     @@ reftable/stack_test.c (new)
      +			.update_index = reftable_stack_next_update_index(st),
      +		};
      +		int err = reftable_stack_add(st, &write_test_log, &arg);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +
      +	err = reftable_stack_read_ref(st, "branch", &dest);
     -+	assert(err == 1);
     -+	reftable_ref_record_clear(&dest);
     ++	EXPECT(err == 1);
     ++	reftable_ref_record_release(&dest);
      +
      +	err = reftable_stack_read_log(st, "branch", &log_dest);
     -+	assert(err == 1);
     -+	reftable_log_record_clear(&log_dest);
     ++	EXPECT(err == 1);
     ++	reftable_log_record_release(&log_dest);
      +
      +	err = reftable_stack_compact_all(st, NULL);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_read_ref(st, "branch", &dest);
     -+	assert(err == 1);
     ++	EXPECT(err == 1);
      +
      +	err = reftable_stack_read_log(st, "branch", &log_dest);
     -+	assert(err == 1);
     -+	reftable_ref_record_clear(&dest);
     -+	reftable_log_record_clear(&log_dest);
     ++	EXPECT(err == 1);
     ++	reftable_ref_record_release(&dest);
     ++	reftable_log_record_release(&log_dest);
      +
      +	/* cleanup */
      +	reftable_stack_destroy(st);
      +	for (i = 0; i < N; i++) {
     -+		reftable_ref_record_clear(&refs[i]);
     -+		reftable_log_record_clear(&logs[i]);
     ++		reftable_ref_record_release(&refs[i]);
     ++		reftable_log_record_release(&logs[i]);
      +	}
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_hash_id(void)
     @@ reftable/stack_test.c (new)
      +	struct reftable_stack *st_default = NULL;
      +	struct reftable_ref_record dest = { NULL };
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st, &write_test_ref, &ref);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	/* can't read it with the wrong hash ID. */
      +	err = reftable_new_stack(&st32, dir, cfg32);
     -+	assert(err == REFTABLE_FORMAT_ERROR);
     ++	EXPECT(err == REFTABLE_FORMAT_ERROR);
      +
      +	/* check that we can read it back with default config too. */
      +	err = reftable_new_stack(&st_default, dir, cfg_default);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_read_ref(st_default, "master", &dest);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
     -+	assert(!strcmp(dest.target, ref.target));
     -+	reftable_ref_record_clear(&dest);
     ++	EXPECT(!strcmp(dest.target, ref.target));
     ++	reftable_ref_record_release(&dest);
      +	reftable_stack_destroy(st);
      +	reftable_stack_destroy(st_default);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +static void test_log2(void)
      +{
     -+	assert(1 == fastlog2(3));
     -+	assert(2 == fastlog2(4));
     -+	assert(2 == fastlog2(5));
     ++	EXPECT(1 == fastlog2(3));
     ++	EXPECT(2 == fastlog2(4));
     ++	EXPECT(2 == fastlog2(5));
      +}
      +
      +static void test_sizes_to_segments(void)
     @@ reftable/stack_test.c (new)
      +	int seglen = 0;
      +	struct segment *segs =
      +		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
     -+	assert(segs[2].log == 3);
     -+	assert(segs[2].start == 5);
     -+	assert(segs[2].end == 6);
     ++	EXPECT(segs[2].log == 3);
     ++	EXPECT(segs[2].start == 5);
     ++	EXPECT(segs[2].end == 6);
      +
     -+	assert(segs[1].log == 2);
     -+	assert(segs[1].start == 2);
     -+	assert(segs[1].end == 5);
     ++	EXPECT(segs[1].log == 2);
     ++	EXPECT(segs[1].start == 2);
     ++	EXPECT(segs[1].end == 5);
      +	reftable_free(segs);
      +}
      +
     @@ reftable/stack_test.c (new)
      +	int seglen = 0;
      +	struct segment *segs =
      +		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
     -+	assert(seglen == 0);
     ++	EXPECT(seglen == 0);
      +	reftable_free(segs);
      +}
      +
     @@ reftable/stack_test.c (new)
      +	int seglen = 0;
      +	struct segment *segs =
      +		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
     -+	assert(seglen == 1);
     -+	assert(segs[0].start == 0);
     -+	assert(segs[0].end == 2);
     ++	EXPECT(seglen == 1);
     ++	EXPECT(segs[0].start == 0);
     ++	EXPECT(segs[0].end == 2);
      +	reftable_free(segs);
      +}
      +
     @@ reftable/stack_test.c (new)
      +	/* .................0    1    2  3   4  5  6 */
      +	struct segment min =
      +		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
     -+	assert(min.start == 2);
     -+	assert(min.end == 7);
     ++	EXPECT(min.start == 2);
     ++	EXPECT(min.end == 7);
      +}
      +
      +static void test_suggest_compaction_segment_nothing(void)
     @@ reftable/stack_test.c (new)
      +	uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
      +	struct segment result =
      +		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
     -+	assert(result.start == result.end);
     ++	EXPECT(result.start == result.end);
      +}
      +
      +static void test_reflog_expire(void)
     @@ reftable/stack_test.c (new)
      +	};
      +	struct reftable_log_record log = { NULL };
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	for (i = 1; i <= N; i++) {
      +		char buf[256];
     @@ reftable/stack_test.c (new)
      +			.update_index = reftable_stack_next_update_index(st),
      +		};
      +		int err = reftable_stack_add(st, &write_test_log, &arg);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +	}
      +
      +	err = reftable_stack_compact_all(st, NULL);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_compact_all(st, &expiry);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_read_log(st, logs[9].refname, &log);
     -+	assert(err == 1);
     ++	EXPECT(err == 1);
      +
      +	err = reftable_stack_read_log(st, logs[11].refname, &log);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	expiry.min_update_index = 15;
      +	err = reftable_stack_compact_all(st, &expiry);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_read_log(st, logs[14].refname, &log);
     -+	assert(err == 1);
     ++	EXPECT(err == 1);
      +
      +	err = reftable_stack_read_log(st, logs[16].refname, &log);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	/* cleanup */
      +	reftable_stack_destroy(st);
      +	for (i = 0; i <= N; i++) {
     -+		reftable_log_record_clear(&logs[i]);
     ++		reftable_log_record_release(&logs[i]);
      +	}
     -+	reftable_clear_dir(dir);
     -+	reftable_log_record_clear(&log);
     ++	clear_dir(dir);
     ++	reftable_log_record_release(&log);
      +}
      +
      +static int write_nothing(struct reftable_writer *wr, void *arg)
     @@ reftable/stack_test.c (new)
      +	char dir[256] = "/tmp/stack_test.XXXXXX";
      +	struct reftable_stack *st2 = NULL;
      +
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_stack_add(st, &write_nothing, NULL);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	err = reftable_new_stack(&st2, dir, cfg);
     -+	assert_err(err);
     -+	reftable_clear_dir(dir);
     ++	EXPECT_ERR(err);
     ++	clear_dir(dir);
      +	reftable_stack_destroy(st);
      +	reftable_stack_destroy(st2);
      +}
     @@ reftable/stack_test.c (new)
      +	char dir[256] = "/tmp/stack_test.XXXXXX";
      +	int err, i;
      +	int N = 100;
     -+	assert(mkdtemp(dir));
     ++	EXPECT(mkdtemp(dir));
      +
      +	err = reftable_new_stack(&st, dir, cfg);
     -+	assert_err(err);
     ++	EXPECT_ERR(err);
      +
      +	for (i = 0; i < N; i++) {
      +		char name[100];
     @@ reftable/stack_test.c (new)
      +		snprintf(name, sizeof(name), "branch%04d", i);
      +
      +		err = reftable_stack_add(st, &write_test_ref, &ref);
     -+		assert_err(err);
     ++		EXPECT_ERR(err);
      +
     -+		assert(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
     ++		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
      +	}
      +
     -+	assert(reftable_stack_compaction_stats(st)->entries_written <
     ++	EXPECT(reftable_stack_compaction_stats(st)->entries_written <
      +	       (uint64_t)(N * fastlog2(N)));
      +
      +	reftable_stack_destroy(st);
     -+	reftable_clear_dir(dir);
     ++	clear_dir(dir);
      +}
      +
      +int stack_test_main(int argc, const char *argv[])
      +{
     -+	add_test_case("test_reftable_stack_uptodate",
     -+		      &test_reftable_stack_uptodate);
     -+	add_test_case("test_reftable_stack_transaction_api",
     -+		      &test_reftable_stack_transaction_api);
     -+	add_test_case("test_reftable_stack_hash_id",
     -+		      &test_reftable_stack_hash_id);
     -+	add_test_case("test_sizes_to_segments_all_equal",
     -+		      &test_sizes_to_segments_all_equal);
     -+	add_test_case("test_reftable_stack_auto_compaction",
     -+		      &test_reftable_stack_auto_compaction);
     -+	add_test_case("test_reftable_stack_validate_refname",
     -+		      &test_reftable_stack_validate_refname);
     -+	add_test_case("test_reftable_stack_update_index_check",
     -+		      &test_reftable_stack_update_index_check);
     -+	add_test_case("test_reftable_stack_lock_failure",
     -+		      &test_reftable_stack_lock_failure);
     -+	add_test_case("test_reftable_stack_log_normalize",
     -+		      &test_reftable_stack_log_normalize);
     -+	add_test_case("test_reftable_stack_tombstone",
     -+		      &test_reftable_stack_tombstone);
     -+	add_test_case("test_reftable_stack_add_one",
     -+		      &test_reftable_stack_add_one);
     -+	add_test_case("test_empty_add", test_empty_add);
     -+	add_test_case("test_reflog_expire", test_reflog_expire);
     -+	add_test_case("test_suggest_compaction_segment",
     -+		      &test_suggest_compaction_segment);
     -+	add_test_case("test_suggest_compaction_segment_nothing",
     -+		      &test_suggest_compaction_segment_nothing);
     -+	add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     -+	add_test_case("test_sizes_to_segments_empty",
     -+		      &test_sizes_to_segments_empty);
     -+	add_test_case("test_log2", &test_log2);
     -+	add_test_case("test_parse_names", &test_parse_names);
     -+	add_test_case("test_read_file", &test_read_file);
     -+	add_test_case("test_names_equal", &test_names_equal);
     -+	add_test_case("test_reftable_stack_add", &test_reftable_stack_add);
     -+	return test_main(argc, argv);
     ++	test_reftable_stack_uptodate();
     ++	test_reftable_stack_transaction_api();
     ++	test_reftable_stack_hash_id();
     ++	test_sizes_to_segments_all_equal();
     ++	test_reftable_stack_auto_compaction();
     ++	test_reftable_stack_validate_refname();
     ++	test_reftable_stack_update_index_check();
     ++	test_reftable_stack_lock_failure();
     ++	test_reftable_stack_log_normalize();
     ++	test_reftable_stack_tombstone();
     ++	test_reftable_stack_add_one();
     ++	test_empty_add();
     ++	test_reflog_expire();
     ++	test_suggest_compaction_segment();
     ++	test_suggest_compaction_segment_nothing();
     ++	test_sizes_to_segments();
     ++	test_sizes_to_segments_empty();
     ++	test_log2();
     ++	test_parse_names();
     ++	test_read_file();
     ++	test_names_equal();
     ++	test_reftable_stack_add();
     ++	return 0;
      +}
      
     - ## reftable/update.sh (new) ##
     -@@
     -+#!/bin/sh
     -+
     -+set -eu
     -+
     -+# Override this to import from somewhere else, say "../reftable".
     -+SRC=${SRC:-origin}
     -+BRANCH=${BRANCH:-master}
     -+
     -+((git --git-dir reftable-repo/.git fetch -f ${SRC} ${BRANCH}:import && cd reftable-repo && git checkout -f $(git rev-parse import) ) ||
     -+   git clone https://github.com/google/reftable reftable-repo)
     -+
     -+cp reftable-repo/c/*.[ch] reftable/
     -+cp reftable-repo/c/include/*.[ch] reftable/
     -+cp reftable-repo/LICENSE reftable/
     -+
     -+git --git-dir reftable-repo/.git show --no-patch --format=oneline HEAD \
     -+  > reftable/VERSION
     -+
     -+mv reftable/system.h reftable/system.h~
     -+sed 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|'  < reftable/system.h~ > reftable/system.h
     -+
     -+git add reftable/*.[ch] reftable/LICENSE reftable/VERSION
     -
       ## t/helper/test-reftable.c ##
     -@@
     - int cmd__reftable(int argc, const char **argv)
     +@@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
       {
     + 	basics_test_main(argc, argv);
       	block_test_main(argc, argv);
      +	merged_test_main(argc, argv);
       	record_test_main(argc, argv);
      +	refname_test_main(argc, argv);
       	reftable_test_main(argc, argv);
     - 	strbuf_test_main(argc, argv);
      +	stack_test_main(argc, argv);
       	tree_test_main(argc, argv);
       	return 0;
  -:  ---------- > 14:  a590865a70 Reftable support for git-core
  -:  ---------- > 15:  57626bfe2d git-prompt: prepare for reftable refs backend
 13:  c535f838d6 ! 16:  6229da992e reftable: "test-tool dump-reftable" command.

@@ Metadata
Author: Han-Wen Nienhuys <[email protected]>

## Commit message ##
- reftable: "test-tool dump-reftable" command.
+ Add "test-tool dump-reftable" command.

This command dumps individual tables or a stack of of tables.

## Makefile ##
-@@ Makefile: REFTABLE_OBJS += reftable/writer.o
- REFTABLE_OBJS += reftable/zlib-compat.o +@@ Makefile: REFTABLE_OBJS += reftable/zlib-compat.o

+ REFTABLE_TEST_OBJS += reftable/basics_test.o REFTABLE_TEST_OBJS += reftable/block_test.o +REFTABLE_TEST_OBJS += reftable/dump.o
-+REFTABLE_TEST_OBJS += reftable/merged_test.o
+ REFTABLE_TEST_OBJS += reftable/merged_test.o REFTABLE_TEST_OBJS += reftable/record_test.o REFTABLE_TEST_OBJS += reftable/refname_test.o
- REFTABLE_TEST_OBJS += reftable/reftable_test.o

- ## reftable/iter.c ##
-@@ reftable/iter.c: void reftable_iterator_destroy(struct reftable_iterator *it)
- int reftable_iterator_next_ref(struct reftable_iterator *it,
- struct reftable_ref_record *ref)
- {
-- struct reftable_record rec = { 0 };
-+ struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, ref);
- return iterator_next(it, &rec);
- }
-@@ reftable/iter.c: int reftable_iterator_next_ref(struct reftable_iterator *it,
- int reftable_iterator_next_log(struct reftable_iterator *it,
- struct reftable_log_record *log)
- {
-- struct reftable_record rec = { 0 };
-+ struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, log);
- return iterator_next(it, &rec);
- }
-@@ reftable/iter.c: static int filtering_ref_iterator_next(void *iter_arg,
- }
+ ## reftable/dump.c ##
+@@ reftable/dump.c: license that can be found in the LICENSE file or at
+ #include <unistd.h>
+ #include <string.h>

- if (fri->double_check) {
-- struct reftable_iterator it = { 0 };
-+ struct reftable_iterator it = { NULL };
+-#include "reftable.h"
++#include "reftable-blocksource.h"
++#include "reftable-error.h"
++#include "reftable-merged.h"
++#include "reftable-record.h"
+ #include "reftable-tests.h"
++#include "reftable-writer.h"
++#include "reftable-iterator.h"
++#include "reftable-reader.h"
++#include "reftable-stack.h"

- err = reftable_table_seek_ref(&fri->tab, &it,
- ref->refname);
-
- ## reftable/reader.c ##
-@@ reftable/reader.c: static int parse_footer(struct reftable_reader *r, uint8_t *footer,
- int init_reader(struct reftable_reader *r, struct reftable_block_source *source,
- const char *name)
- {
-- struct reftable_block footer = { 0 };
-- struct reftable_block header = { 0 };
-+ struct reftable_block footer = { NULL };
-+ struct reftable_block header = { NULL };
- int err = 0;
+ static uint32_t hash_id;

- memset(r, 0, sizeof(struct reftable_reader));
-@@ reftable/reader.c: int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
+ static int dump_table(const char *tablename)
{
- int32_t guess_block_size = r->block_size ? r->block_size :
- DEFAULT_BLOCK_SIZE;
-- struct reftable_block block = { 0 };
-+ struct reftable_block block = { NULL };
- uint8_t block_typ = 0;
- int err = 0;
- uint32_t header_off = next_off ? 0 : header_size(r->version);
-@@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
- struct reftable_record *rec)
+- struct reftable_block_source src = { 0 };
++ struct reftable_block_source src = { NULL };
+ int err = reftable_block_source_from_file(&src, tablename);
+- struct reftable_iterator it = { 0 };
+- struct reftable_ref_record ref = { 0 };
+- struct reftable_log_record log = { 0 };
++ struct reftable_iterator it = { NULL };
++ struct reftable_ref_record ref = { NULL };
++ struct reftable_log_record log = { NULL };
+ struct reftable_reader *r = NULL;
+
+ if (err < 0)
+@@ reftable/dump.c: static int dump_table(const char *tablename)
+ reftable_ref_record_print(&ref, hash_id);
+ }
+ reftable_iterator_destroy(&it);
+- reftable_ref_record_clear(&ref);
++ reftable_ref_record_release(&ref);
+
+ err = reftable_reader_seek_log(r, &it, "");
+ if (err < 0) {
+@@ reftable/dump.c: static int dump_table(const char *tablename)
+ reftable_log_record_print(&log, hash_id);
+ }
+ reftable_iterator_destroy(&it);
+- reftable_log_record_clear(&log);
++ reftable_log_record_release(&log);
+
+ reftable_reader_free(r);
+ return 0;
+@@ reftable/dump.c: static int dump_stack(const char *stackdir)
{
- struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-- struct reftable_record want_index_rec = { 0 };
-+ struct reftable_record want_index_rec = { NULL };
- struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-- struct reftable_record index_result_rec = { 0 };
-+ struct reftable_record index_result_rec = { NULL };
- struct table_iter index_iter = TABLE_ITER_INIT;
- struct table_iter next = TABLE_ITER_INIT;
- int err = 0;
-@@ reftable/reader.c: int reftable_reader_seek_ref(struct reftable_reader *r,
- struct reftable_ref_record ref = {
- .refname = (char *)name,
- };
-- struct reftable_record rec = { 0 };
-+ struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
- return reader_seek(r, it, &rec);
- }
-@@ reftable/reader.c: int reftable_reader_seek_log_at(struct reftable_reader *r,
- .refname = (char *)name,
- .update_index = update_index,
- };
-- struct reftable_record rec = { 0 };
-+ struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
- return reader_seek(r, it, &rec);
- }
-@@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
- .hash_prefix = oid,
- .hash_prefix_len = r->object_id_len,
- };
-- struct reftable_record want_rec = { 0 };
-- struct reftable_iterator oit = { 0 };
-- struct reftable_obj_record got = { 0 };
-- struct reftable_record got_rec = { 0 };
-+ struct reftable_record want_rec = { NULL };
-+ struct reftable_iterator oit = { NULL };
-+ struct reftable_obj_record got = { NULL };
-+ struct reftable_record got_rec = { NULL };
- int err = 0;
- struct indexed_table_ref_iter *itr = NULL;
+ struct reftable_stack *stack = NULL;
+ struct reftable_write_options cfg = {};
+- struct reftable_iterator it = { 0 };
+- struct reftable_ref_record ref = { 0 };
+- struct reftable_log_record log = { 0 };
++ struct reftable_iterator it = { NULL };
++ struct reftable_ref_record ref = { NULL };
++ struct reftable_log_record log = { NULL };
+ struct reftable_merged_table *merged = NULL;

+ int err = reftable_new_stack(&stack, stackdir, cfg);
+@@ reftable/dump.c: static int dump_stack(const char *stackdir)
+ reftable_ref_record_print(&ref, hash_id);
+ }
+ reftable_iterator_destroy(&it);
+- reftable_ref_record_clear(&ref);
++ reftable_ref_record_release(&ref);
+
+ err = reftable_merged_table_seek_log(merged, &it, "");
+ if (err < 0) {
+@@ reftable/dump.c: static int dump_stack(const char *stackdir)
+ reftable_log_record_print(&log, hash_id);
+ }
+ reftable_iterator_destroy(&it);
+- reftable_log_record_clear(&log);
++ reftable_log_record_release(&log);
+
+ reftable_stack_destroy(stack);
+ return 0;

## t/helper/test-reftable.c ##
@@ t/helper/test-reftable.c: int cmd__reftable(int argc, const char **argv)
@@ t/helper/test-tool.h: int cmd__dump_cache_tree(int argc, const char **argv);
int cmd__dump_untracked_cache(int argc, const char **argv);
+int cmd__dump_reftable(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv);
+ int cmd__fast_rebase(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv);
- int cmd__genzeros(int argc, const char **argv);

Signature · 1 line
gitgitgadget
#79 lore raw

PATCH v3 1/16 move sleep_millisec to git-compat-util.h

From: Han-Wen Nienhuys <[email protected]>

The sleep function is defined in wrapper.c, so it makes more sense to be a in system compatibility header.

---
 cache.h           | 1 -
 git-compat-util.h | 2 ++
 2 files changed, 2 insertions(+), 1 deletion(-)
cache.h +0 -1
index c0072d43b1..e986cf4ea9 100644
@@ -1960,7 +1960,6 @@ int stat_validity_check(struct stat_validity *sv, const char *path);
 void stat_validity_update(struct stat_validity *sv, int fd);
 
 int versioncmp(const char *s1, const char *s2);
-void sleep_millisec(int millisec);
 
 /*
  * Create a directory and (if share is nonzero) adjust its permissions
git-compat-util.h +2 -0
index adfea06897..7d509c5022 100644
@@ -1354,4 +1354,6 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset)
 	((uintptr_t)&(ptr)->member - (uintptr_t)(ptr))
 #endif /* !__GNUC__ */
 
+void sleep_millisec(int millisec);
+
 #endif
Signature · 1 line
gitgitgadget
#80 lore raw

PATCH v3 2/16 init-db: set the_repository->hash_algo early on

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable backend needs to know the hash algorithm for writing the initialization hash table.

The initial reftable contains a symref HEAD => "main" (or "master"), which is agnostic to the size of hash value, but this is an exceptional circumstance, and the reftable library does not cater for this exceptional. It insists that all tables in the stack have a consistent format ID for the hash algorithm.

Call set_repo_hash_algo directly after reading out GIT_DEFAULT_HASH.

---
 builtin/init-db.c | 1 +
 1 file changed, 1 insertion(+)
builtin/init-db.c +1 -0
index 01bc648d41..5c8c67fec6 100644
@@ -391,6 +391,7 @@ static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash
 			die(_("unknown hash algorithm '%s'"), env);
 		repo_fmt->hash_algo = env_algo;
 	}
+	repo_set_hash_algo(the_repository, repo_fmt->hash_algo);
 }
 
 int init_db(const char *git_dir, const char *real_git_dir,
Signature · 1 line
gitgitgadget
#81 lore raw

Re: [PATCH v3 02/16] init-db: set the_repository->hash_algo early on

Quoted 8 lines · Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable backend needs to know the hash algorithm for writing the initialization hash table.

The initial reftable contains a symref HEAD => "main" (or "master"), which is agnostic to the size of hash value, but this is an exceptional circumstance, and the reftable library does not cater for this exceptional. It insists that all

rephrase: "does not cater to this exception" maybe?

#82 lore raw

PATCH v3 3/16 reftable: add LICENSE

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

TODO: relicense?

---
 reftable/LICENSE | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 reftable/LICENSE
reftable/LICENSE new file +31 -0
new file mode 100644
index 0000000000..402e0f9356
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Signature · 1 line
gitgitgadget
#84 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 4 lines · Ævar Arnfjörð Bjarmason

On Thu, Nov 26 2020, Han-Wen Nienhuys via GitGitGadget wrote:

Quoted 8 lines
Quoted 3 lines

From: Han-Wen Nienhuys <[email protected]>

TODO: relicense?

On the topics of TODOs, it would be nice to have an answer/some idea the "I am concerned..." question in about
https://lore.kernel.org/git/[email protected]/ relevant to the license etc.

Long term objective is for this to live in git proper, but remains independently compilable, so it can be used by libgit2 as well.

This is achieved by having the code only depend on functions in strbuf.h and git-compat-util.h.

I would like to enforce the separation by having an actual standalone compile, but the discussion over the code to do so has caused more heat than light, so I have deferred this by reworking standalone code to to be on file level, and only importing the non-standalone code. In one of the other patches, you found a place where I had overlooked a REFTABLE_STANDALONE #ifdef; I'll remove that. I'll also remove the update.sh script.

Until the reftable code is actually merged into git, I work on the reftable library itself at github.com/google/reftable. I've asked our opensource team if we could switch off the CLA enforcement for this repo, to facilitate back & forth imports of fixes.

The current version of the patchseries mainly addresses overall feedback from the Git team at Google to split the public API along commits too, and updates the series so it applies to master again.

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#85 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 4 lines · Han-Wen Nienhuys

Until the reftable code is actually merged into git, I work on the reftable library itself at github.com/google/reftable. I've asked our opensource team if we could switch off the CLA enforcement for this repo, to facilitate back & forth imports of fixes.

I've asked our lawyers, but this is not currently possible, unfortunately.

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#86 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 8 lines · Han-Wen Nienhuys
Quoted 4 lines · Han-Wen Nienhuys

Until the reftable code is actually merged into git, I work on the reftable library itself at github.com/google/reftable. I've asked our opensource team if we could switch off the CLA enforcement for this repo, to facilitate back & forth imports of fixes.

I've asked our lawyers, but this is not currently possible, unfortunately.

Maybe I'm not understanding something correctly.

Git's license is GPLv2. When you add your Signed-off-by line you are saying you are authorized to provide this code to the Git project under its open source license.

Therefore, I should be able to take your patches, and rework them in any way I want as long as I keep Git's license.

I don't need your permission, you are supposedly giving it by adding your Signed-off-by line.

Read: https://developercertificate.org/

If you are not authorized to provide these patches under an open source license, then you should not add your Signed-off-by line.

And if these patches cannot be used by the Git project, nor can we work on them in a collaborative manner, then what's the point in providing them?

Sounds to me Google has not made its mind about actually contributing these changes.

Or am I missing something?

Cheers.

Signature · 1 line
Felipe Contreras
#87 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 9 lines · Felipe Contreras
Quoted 7 lines · Han-Wen Nienhuys
Quoted 4 lines · Han-Wen Nienhuys

Until the reftable code is actually merged into git, I work on the reftable library itself at github.com/google/reftable. I've asked our opensource team if we could switch off the CLA enforcement for this repo, to facilitate back & forth imports of fixes.

Quoted 8 lines
Quoted 1 line

I've asked our lawyers, but this is not currently possible, unfortunately.

Maybe I'm not understanding something correctly.

Sounds to me Google has not made its mind about actually contributing these changes.

Or am I missing something?

The restrictions are not about the patches themselves. They are about restricting what gets posted under github.com/google/ .

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#88 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 11 lines · Han-Wen Nienhuys
Quoted 6 lines · Felipe Contreras

On Mon, Nov 30, 2020 at 2:28 PM Han-Wen Nienhuys <[email protected]> wrote:

Sounds to me Google has not made its mind about actually contributing these changes.

Or am I missing something?

The restrictions are not about the patches themselves. They are about restricting what gets posted under github.com/google/ .

I see.

But it doesn't have to be posted on github.com/google/, it doesn't have to be posted on github.com at all. If the code is under an open source license, you (or anyone) can post it anywhere.

Signature · 1 line
Felipe Contreras
#89 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 10 lines · Felipe Contreras
Quoted 9 lines · Han-Wen Nienhuys
Quoted 6 lines · Felipe Contreras

On Mon, Nov 30, 2020 at 2:28 PM Han-Wen Nienhuys <[email protected]> wrote:

Sounds to me Google has not made its mind about actually contributing these changes.

Or am I missing something?

Quoted 8 lines
Quoted 2 lines

The restrictions are not about the patches themselves. They are about restricting what gets posted under github.com/google/ .

I see.

But it doesn't have to be posted on github.com/google/, it doesn't have to be posted on github.com at all. If the code is under an open source license, you (or anyone) can post it anywhere.

The libgit2.git and git.git codebases are under different licenses. Therefore if the goal is to have code that's used in both it's not viable to e.g. store it in git.git under our current contribution policies.

The first contributor to submit a fix to it under GPLv2 as opposed to "any GPL or LGPL version" or whatever will preclude its subsequent import into libgit2.

Assigning copyright to Google is a way around that. They own your work, and then they re-license it under whatever license those two projects use.

But as I pointed out in [1] it requires contributors to git.git's reftable/ directory & Junio to play along with that scheme, least the whole process come to a halt. Maybe that's worth it, maybe not. But seems like something the series should very explicitly highlight and document e.g. in Documentation/SubmittingPatches.

We do have some in-tree code in that state already, but it's mostly/entirely one-off imports of GNU/FSF code like compat/regex/ or sh-i18n--envsubst.c that we're not actively working on in any way, and isn't core to git like the reftable would be.

There's individual contributors who just don't like the idea of copyright assignment and wouldn't do it for that reason, but there's also contributors who do paid-for work for their employers on git.git sometimes.

E.g. I've done such work under terms that were basically "you can contribute to open source as part of your job, and under the license the relevant project uses". Going through the legal process of changing that to "we, your employer, who own copyright to your work, agree to license it to this third party project/company/whatever for the purposes of an open source contribution" would in my experience be *very much* an uphill battle.

So I think even if the individual contributor wouldn't mind the copyright assignment (I'd personally be fine with that), e.g. I've been in employment relationships where I'd just forget about sending a patch if it needed assignment, because there's no way I'd be getting it past legal, in the same way Han-Wen seems to be having an uphill battle with Google's lawyers.

1. https://lore.kernel.org/git/[email protected]/

#90 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 14 lines · Ævar Arnfjörð Bjarmason
Quoted 10 lines · Felipe Contreras
Quoted 9 lines · Han-Wen Nienhuys
Quoted 6 lines · Felipe Contreras

On Mon, Nov 30, 2020 at 2:28 PM Han-Wen Nienhuys <[email protected]> wrote:

Sounds to me Google has not made its mind about actually contributing these changes.

Or am I missing something?

Quoted 8 lines
Quoted 8 lines
Quoted 2 lines

The restrictions are not about the patches themselves. They are about restricting what gets posted under github.com/google/ .

I see.

But it doesn't have to be posted on github.com/google/, it doesn't have to be posted on github.com at all. If the code is under an open source license, you (or anyone) can post it anywhere.

I am also bound by my employment contract to follow company instructions. While you (Felipe) can post code where you want, the same is not true for me.

Quoted 5 lines

The libgit2.git and git.git codebases are under different licenses. Therefore if the goal is to have code that's used in both it's not viable to e.g. store it in git.git under our current contribution policies.
[..]

Thanks for sketching the dilemmas here. Me and Jun are talking to the open source folks here at Google, so I am optimistic that we will find some solution. Please give us a few days to sort this out.

Quoted 8 lines

The first contributor to submit a fix to it under GPLv2 as opposed to "any GPL or LGPL version" or whatever will preclude its subsequent import into libgit2.

Assigning copyright to Google is a way around that. They own your work, and then they re-license it under whatever license those two projects use.

Quoted 5 lines

But as I pointed out in [1] it requires contributors to git.git's reftable/ directory & Junio to play along with that scheme, least the whole process come to a halt. Maybe that's worth it, maybe not. But seems like something the series should very explicitly highlight and document e.g. in Documentation/SubmittingPatches.

FWIW, the plan of record is still for this code to have its source of truth in the Git project. Once it's there, there is no need for Google to have CLAs for contributions to the reftable/ directory.

There is still the open question of how to set the license terms for this code, so it is OK for it to be copied into libgit2. I picked the current license (BSD) because it was the most liberal I could find. (Maybe I should document that in the commit message.)

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#91 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

Quoted 9 lines · Han-Wen Nienhuys
Quoted 5 lines · Felipe Contreras
Quoted 4 lines

But it doesn't have to be posted on github.com/google/, it doesn't have to be posted on github.com at all. If the code is under an open source license, you (or anyone) can post it anywhere.

I am also bound by my employment contract to follow company instructions. While you (Felipe) can post code where you want, the same is not true for me.

I understand that, but what if a stubborn person decides to do that, and the code starts diverging from github.com/google/? So essentially a fork is created.

I don't think that's in the best interest of Google, and that would force their hand to either a) allow you to contribute to the open repo, or b) allow open source people to contribute to the google repo.

I find it silly that these thought experiments have to be explained to corporate lawyers, or worse; they actually have to be done so the lawyers get the point. But alas, that's what working in a big corporation entails.

Good luck with that. (No sarcasm intended)

Signature · 1 line
Felipe Contreras
#92 lore raw

Re: [PATCH v3 03/16] reftable: add LICENSE

In reply to Ævar Arnfjörð Bjarmason
Quoted 3 lines · Ævar Arnfjörð Bjarmason

Assigning copyright to Google is a way around that. They own your work, and then they re-license it under whatever license those two projects use.

That's the default. Unless otherwise stated; you retain the copyright while contributing code to open source projects.

Generally when open source projects want to change license they have to get the agreement from the majority of copyright holders.

Some projects do ask you to give away your copyright, but I'm not comfortable with that. If I want to contribute my code with GPLv2, that's the license I want. If a project I contribute to wants to switch to GPLv3, then they have to remove my code (or do something illegal).

So I see nothing wrong with Google owning the copyright and controlling under which license(s) their code is distributed. In fact, I would see something wrong with the opposite; them giving away their copyright.

Cheers.

Signature · 1 line
Felipe Contreras
#93 lore raw

PATCH v3 4/16 reftable: add error related functionality

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable/ directory is structured as a library, so it cannot crash on misuse. Instead, it returns an error codes.

In addition, the error code can be used to signal conditions from lower levels of the library to be handled by higher levels of the library. For example, a transaction might legitimately write an empty reftable file, but in that case, we'd want to shortcut the transaction overhead.

---
 reftable/error.c          | 39 ++++++++++++++++++++++++
 reftable/reftable-error.h | 62 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 101 insertions(+)
 create mode 100644 reftable/error.c
 create mode 100644 reftable/reftable-error.h
reftable/error.c new file +39 -0
new file mode 100644
index 0000000000..1d60137ea7
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reftable-error.h"
+
+#include <stdio.h>
+
+const char *reftable_error_str(int err)
+{
+	static char buf[250];
+	switch (err) {
+	case REFTABLE_IO_ERROR:
+		return "I/O error";
+	case REFTABLE_FORMAT_ERROR:
+		return "corrupt reftable file";
+	case REFTABLE_NOT_EXIST_ERROR:
+		return "file does not exist";
+	case REFTABLE_LOCK_ERROR:
+		return "data is outdated";
+	case REFTABLE_API_ERROR:
+		return "misuse of the reftable API";
+	case REFTABLE_ZLIB_ERROR:
+		return "zlib failure";
+	case REFTABLE_NAME_CONFLICT:
+		return "file/directory conflict";
+	case REFTABLE_REFNAME_ERROR:
+		return "invalid refname";
+	case -1:
+		return "general error";
+	default:
+		snprintf(buf, sizeof(buf), "unknown error code %d", err);
+		return buf;
+	}
+}
reftable/reftable-error.h new file +62 -0

62 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#94 lore raw

Re: [PATCH v3 04/16] reftable: add error related functionality

reftable/reftable-error.h line 15

Quoted 4 lines · Han-Wen Nienhuys via GitGitGadget
reftable/reftable-error.h new file +4 -0
+/*
+ Errors in reftable calls are signaled with negative integer return values. 0
+ means success.
+*/

This is the format of multi-line comments:

/*
* A very long
* multi-line comment.
*/

reftable/reftable-error.h line 21

Quoted 6 lines
reftable/reftable-error.h new file +6 -0
+enum reftable_error {
+       /* Unexpected file system behavior */
+       REFTABLE_IO_ERROR = -2,
+
+       /* Format inconsistency on reading data
+        */

No need for a multi-line comment here.

reftable/reftable-error.h line 26

Quoted 5 lines
reftable/reftable-error.h new file +5 -0
+       REFTABLE_FORMAT_ERROR = -3,
+
+       /* File does not exist. Returned from block_source_from_file(),  because
+          it needs special handling in stack.
+       */

Once again, and for the rest of the file.

Signature · 1 line
Felipe Contreras
#95 lore raw

Re: [PATCH v3 04/16] reftable: add error related functionality

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 15 lines · Han-Wen Nienhuys via GitGitGadget

The reftable/ directory is structured as a library, so it cannot crash on misuse. Instead, it returns an error codes.

In addition, the error code can be used to signal conditions from lower levels of the library to be handled by higher levels of the library. For example, a transaction might legitimately write an empty reftable file, but in that case, we'd want to shortcut the transaction overhead.
[...]

reftable/error.c new file +7 -0
+	static char buf[250];
+	switch (err) {
+	case REFTABLE_IO_ERROR:
+		return "I/O error";
+	case REFTABLE_FORMAT_ERROR:
+		return "corrupt reftable file";
+	case REFTABLE_NOT_EXIST_ERROR:

reftable/error.c line 30

Quoted 10 lines
reftable/error.c new file +9 -0
+		return "file does not exist";
+	case REFTABLE_LOCK_ERROR:
+		return "data is outdated";
+	case REFTABLE_API_ERROR:
+		return "misuse of the reftable API";
+	case REFTABLE_ZLIB_ERROR:
+		return "zlib failure";
+	case REFTABLE_NAME_CONFLICT:
+		return "file/directory conflict";
[...]

Not for this series, but I wonder if the eventual integration into the i18n framework in git needs some thought, seems all the (including presumably stuff show to users) UI strings are hardcoded English at the moment.

#96 lore raw

Re: [PATCH v3 04/16] reftable: add error related functionality

Quoted 4 lines · Ævar Arnfjörð Bjarmason

Not for this series, but I wonder if the eventual integration into the i18n framework in git needs some thought, seems all the (including presumably stuff show to users) UI strings are hardcoded English at the moment.

AFAIK, these are the only strings that potentially show up in the UI.

If these strings need to be i18n'd. It is probably easier for git-core to provide its own version of reftable_error_str() ? Or we could remove reftable_error_str from the official API?

Signature · 11 lines
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#97 lore raw

PATCH v3 5/16 reftable: utility functions

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

---
 Makefile                     |  24 +++++-
 reftable/basics.c            | 144 +++++++++++++++++++++++++++++++++++
 reftable/basics.h            |  56 ++++++++++++++
 reftable/basics_test.c       |  56 ++++++++++++++
 reftable/compat.h            |  48 ++++++++++++
 reftable/publicbasics.c      |  58 ++++++++++++++
 reftable/reftable-malloc.h   |  20 +++++
 reftable/reftable-tests.h    |  22 ++++++
 reftable/system.h            |  32 ++++++++
 reftable/test_framework.c    |  23 ++++++
 reftable/test_framework.h    |  48 ++++++++++++
 t/helper/test-reftable.c     |   9 +++
 t/helper/test-tool.c         |   3 +-
 t/helper/test-tool.h         |   1 +
 t/t0032-reftable-unittest.sh |  14 ++++
 15 files changed, 554 insertions(+), 4 deletions(-)
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/basics_test.c
 create mode 100644 reftable/compat.h
 create mode 100644 reftable/publicbasics.c
 create mode 100644 reftable/reftable-malloc.h
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 t/helper/test-reftable.c
 create mode 100755 t/t0032-reftable-unittest.sh
Makefile +21 -3
index dd1cf41e00..5ce78772fd 100644
@@ -732,6 +732,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o
 TEST_BUILTINS_OBJS += test-read-graph.o
 TEST_BUILTINS_OBJS += test-read-midx.o
 TEST_BUILTINS_OBJS += test-ref-store.o
+TEST_BUILTINS_OBJS += test-reftable.o
 TEST_BUILTINS_OBJS += test-regex.o
 TEST_BUILTINS_OBJS += test-repository.o
 TEST_BUILTINS_OBJS += test-revision-walking.o
@@ -821,6 +822,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
+REFTABLE_LIB = reftable/libreftable.a
+REFTABLE_TEST_LIB = reftable/libreftable_test.a
 
 GENERATED_H += command-list.h
 GENERATED_H += config-list.h
@@ -1185,7 +1188,7 @@ THIRD_PARTY_SOURCES += compat/regex/%
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2386,10 +2389,19 @@ XDIFF_OBJS += xdiff/xpatience.o
 XDIFF_OBJS += xdiff/xprepare.o
 XDIFF_OBJS += xdiff/xutils.o
 
+REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/error.o
+REFTABLE_OBJS += reftable/publicbasics.o
+
+REFTABLE_TEST_OBJS += reftable/test_framework.o
+REFTABLE_TEST_OBJS += reftable/basics_test.o
+
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 	$(XDIFF_OBJS) \
 	$(FUZZ_OBJS) \
+	$(REFTABLE_OBJS) \
+	$(REFTABLE_TEST_OBJS) \
 	common-main.o \
 	git.o
 ifndef NO_CURL
@@ -2541,6 +2553,12 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+$(REFTABLE_LIB): $(REFTABLE_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -2822,7 +2840,7 @@ perf: all
 
 t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: t/helper/test-tool$X
@@ -3151,7 +3169,7 @@ cocciclean:
 clean: profile-clean coverage-clean cocciclean
 	$(RM) *.res
 	$(RM) $(OBJECTS)
-	$(RM) $(LIB_FILE) $(XDIFF_LIB)
+	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
reftable/basics.c new file +144 -0

144 lines not on this page · raw message

reftable/basics.h new file +56 -0

56 lines not on this page · raw message

reftable/basics_test.c new file +56 -0

56 lines not on this page · raw message

reftable/compat.h new file +48 -0

48 lines not on this page · raw message

reftable/publicbasics.c new file +58 -0

58 lines not on this page · raw message

reftable/reftable-malloc.h new file +20 -0
new file mode 100644
index 0000000000..f3571b5f4f
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include <stddef.h>
+
+/*
+  Overrides the functions to use for memory management.
+ */
+void reftable_set_alloc(void *(*malloc)(size_t),
+			void *(*realloc)(void *, size_t), void (*free)(void *));
+
+#endif
reftable/reftable-tests.h new file +22 -0
new file mode 100644
index 0000000000..5e7698ae65
@@ -0,0 +1,22 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_TESTS_H
+#define REFTABLE_TESTS_H
+
+int basics_test_main(int argc, const char **argv);
+int block_test_main(int argc, const char **argv);
+int merged_test_main(int argc, const char **argv);
+int record_test_main(int argc, const char **argv);
+int refname_test_main(int argc, const char **argv);
+int reftable_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
reftable/system.h new file +32 -0
new file mode 100644
index 0000000000..07277ca062
@@ -0,0 +1,32 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+
+#include <zlib.h>
+
+struct strbuf;
+/* In git, this is declared in dir.h */
+int remove_dir_recursively(struct strbuf *path, int flags);
+
+#define SHA1_ID 0x73686131
+#define SHA256_ID 0x73323536
+#define SHA1_SIZE 20
+#define SHA256_SIZE 32
+
+/* This is uncompress2, which is only available in zlib as of 2017.
+ */
+int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
+			       const Bytef *source, uLong *sourceLen);
+int hash_size(uint32_t id);
+
+#endif
reftable/test_framework.c new file +23 -0
new file mode 100644
index 0000000000..a5ff4e2a2d
@@ -0,0 +1,23 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+#include "test_framework.h"
+
+#include "basics.h"
+
+void set_test_hash(uint8_t *p, int i)
+{
+	memset(p, (uint8_t)i, hash_size(SHA1_ID));
+}
+
+int strbuf_add_void(void *b, const void *data, size_t sz)
+{
+	strbuf_add((struct strbuf *)b, data, sz);
+	return sz;
+}
reftable/test_framework.h new file +48 -0

48 lines not on this page · raw message

t/helper/test-reftable.c new file +9 -0
new file mode 100644
index 0000000000..3b58e423e7
@@ -0,0 +1,9 @@
+#include "reftable/reftable-tests.h"
+#include "test-tool.h"
+
+int cmd__reftable(int argc, const char **argv)
+{
+	basics_test_main(argc, argv);
+
+	return 0;
+}
t/helper/test-tool.c +2 -1
index 9d6d14d929..0208a0a41c 100644
@@ -48,13 +48,14 @@ static struct test_cmd cmds[] = {
 	{ "path-utils", cmd__path_utils },
 	{ "pkt-line", cmd__pkt_line },
 	{ "prio-queue", cmd__prio_queue },
-	{ "proc-receive", cmd__proc_receive},
+	{ "proc-receive", cmd__proc_receive },
 	{ "progress", cmd__progress },
 	{ "reach", cmd__reach },
 	{ "read-cache", cmd__read_cache },
 	{ "read-graph", cmd__read_graph },
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
+	{ "reftable", cmd__reftable },
 	{ "regex", cmd__regex },
 	{ "repository", cmd__repository },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index a6470ff62c..1de39ce5b5 100644
@@ -44,6 +44,7 @@ int cmd__read_cache(int argc, const char **argv);
 int cmd__read_graph(int argc, const char **argv);
 int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
+int cmd__reftable(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
 int cmd__repository(int argc, const char **argv);
 int cmd__revision_walking(int argc, const char **argv);
t/t0032-reftable-unittest.sh new file +14 -0
new file mode 100755
index 0000000000..10e21a0590
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Google LLC
+#
+
+test_description='reftable unittests'
+
+. ./test-lib.sh
+
+test_expect_success 'unittests' '
+       test-tool reftable
+'
+
+test_done
Signature · 1 line
gitgitgadget
#98 lore raw

Re: [PATCH v3 05/16] reftable: utility functions

reftable/basics.c line 33

Quoted 4 lines · Han-Wen Nienhuys via GitGitGadget
reftable/basics.c new file +4 -0
+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)
+{
+       size_t lo = 0;
+       size_t hi = sz;

Is there anything wrong with:

  size_t lo = 0, hi = sz;

reftable/basics.c line 37

Quoted 3 lines
reftable/basics.c new file +3 -0
+       /* invariant: (hi == sz) || f(hi) == true
+          (lo == 0 && f(0) == true) || fi(lo) == false
+        */

This doesn't follow the multi-line format. And perhaps English would be better.

reftable/basics.c line 46

Quoted 9 lines
reftable/basics.c new file +9 -0
+       while (hi - lo > 1) {
+               size_t mid = lo + (hi - lo) / 2;
+
+               int val = f(mid, args);
+               if (val) {
+                       hi = mid;
+               } else {
+                       lo = mid;
+               }

Unnecessary braces are frowned upon. Also, why store the value if you are not going to use it later?

if (f(mid, args))
hi = mid;
else
lo = mid;

reftable/basics.c line 49

Quoted 3 lines
reftable/basics.c new file +3 -0
+       }
+
+       if (lo == 0) {

if (!lo)

reftable/basics.c line 54

Quoted 5 lines
reftable/basics.c new file +5 -0
+               if (f(0, args)) {
+                       return 0;
+               } else {
+                       return 1;
+               }

return f(0, args) ? 0 : 1;

Or just:

return !f(0, args);

reftable/basics.c line 57

Quoted 3 lines
reftable/basics.c new file +3 -0
+       }
+
+       return hi;

It would be much clearer to just do:

if (lo)
return hi;
else
return !f(0, args);

reftable/basics.c line 65

Quoted 8 lines
reftable/basics.c new file +8 -0
+}
+
+void free_names(char **a)
+{
+       char **p = a;
+       if (p == NULL) {
+               return;
+       }

Once again:

if (!p)

    return;

reftable/basics.c line 69

Quoted 4 lines
reftable/basics.c new file +4 -0
+       while (*p) {
+               reftable_free(*p);
+               p++;
+       }

This is the same thing as:

if (!a)

    return;
  for (p = a; *p; p++)
    reftable_free(*p);

reftable/basics.c line 82

Quoted 10 lines
reftable/basics.c new file +10 -0
+int names_length(char **names)
+{
+       int len = 0;
+       char **p = names;
+       while (*p) {
+               p++;
+               len++;
+       }
+       return len;
+}

Is there any need to keep track of len? It's always p - names.

This should do it:

  char **p;
  for (p = names; *p; p++);
  return p - names;

reftable/basics.c line 92

Quoted 9 lines
reftable/basics.c new file +9 -0
+void parse_names(char *buf, int size, char ***namesp)
+{
+       char **names = NULL;
+       size_t names_cap = 0;
+       size_t names_len = 0;
+
+       char *p = buf;
+       char *end = buf + size;
+       while (p < end) {

Yet another while that can be a for:

for (p = buf; p < end; p = next + 1)

reftable/basics.c line 98

Quoted 6 lines
reftable/basics.c new file +6 -0
+               char *next = strchr(p, '\n');
+               if (next != NULL) {
+                       *next = 0;
+               } else {
+                       next = end;
+               }

if (next)
*next = 0;
else
next = end;

Quoted 1 line

+ if (p < next) {

Or just:

if (p >= next)

    continue;

reftable/basics.c line 103

Quoted 4 lines
reftable/basics.c new file +4 -0
+                       if (names_len == names_cap) {
+                               names_cap = 2 * names_cap + 1;
+                               names = reftable_realloc(
+                                       names, names_cap * sizeof(char *));

sizeof(*names);

reftable/basics.c line 113

Quoted 10 lines
reftable/basics.c new file +10 -0
+                       }
+                       names[names_len++] = xstrdup(p);
+               }
+               p = next + 1;
+       }
+
+       if (names_len == names_cap) {
+               names_cap = 2 * names_cap + 1;
+               names = reftable_realloc(names, names_cap * sizeof(char *));
+       }

This is repeated, maybe a macro like ALLOC_GROW(), or literally ALLOC_GROW().

Quoted 4 lines
reftable/basics.c new file +4 -0
+       names[names_len] = NULL;
+       *namesp = names;
+}
+

reftable/basics.c line 128

Quoted 10 lines
reftable/basics.c new file +10 -0
+int names_equal(char **a, char **b)
+{
+       while (*a && *b) {
+               if (strcmp(*a, *b)) {
+                       return 0;
+               }
+
+               a++;
+               b++;
+       }

Again:

  for (; *a && *b, a++, b++) {
    if (strcmp(*a, *b)
      return 0;
  }

But instead of moving multiple pointers:

  for (i = 0; a[i] && b[i], i++) {
    if (strcmp(a[i], b[i])
      return 0;
  }

reftable/basics.c line 136

Quoted 4 lines
reftable/basics.c new file +4 -0
+int common_prefix_size(struct strbuf *a, struct strbuf *b)
+{
+       int p = 0;
+       while (p < a->len && p < b->len) {

Once again, this can easily be a for.

reftable/basics.c line 144

Quoted 8 lines
reftable/basics.c new file +8 -0
+               if (a->buf[p] != b->buf[p]) {
+                       break;
+               }
+               p++;
+       }
+
+       return p;
+}

Cheers.

Signature · 1 line
Felipe Contreras
#99 lore raw

Re: [PATCH v3 05/16] reftable: utility functions

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 3 lines · Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This commit provides basic utility classes for the reftable library.

Re Johannes's comments in v2
(https://lore.kernel.org/git/[email protected]/)
about strbuf.c etc. being duplicated I see that's gone now.

It seems you did some version of my suggestion in
https://lore.kernel.org/git/[email protected]/ of factoring out that code, except here:

reftable/compat.h line 20

Quoted 8 lines

[...]

reftable/compat.h new file +7 -0
+#ifdef REFTABLE_STANDALONE
+
+/* functions that git-core provides, for standalone compilation */
+#include <stdint.h>
+
+uint64_t get_be64(void *in);
+void put_be64(void *out, uint64_t i);

It's unclear whether that's an omission, or needed for some reason (is git.git ever going to have REFTABLE_STANDALONE=true?

The update shellscript is also gone from v2->v3, is that out of tree now somewhere? How does one test with the upstream code & presumably do some munging on it to make it diff-able with what's going to be in git.git?

It would be really useful to have that sort of info/summary in the cover letter between serieses. It's not that I mind e.g. having some of this REFTABLE_STANDALONE code here. Just having to do archaeology trying to figure out changes in this rather large series...

#100 lore raw

PATCH v3 6/16 reftable: add blocksource, an abstraction for random access reads

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable format is usually used with files for storage. However, we abstract away this using the blocksource data structure. This has two advantages:

* log blocks are zlib compressed, and handling them is simplified if we can discard byte segments from within the block layer.

* for unittests, it is useful to read and write in-memory. The blocksource allows us to abstract the data away from on-disk files.

---
 Makefile                        |   1 +
 reftable/blocksource.c          | 148 ++++++++++++++++++++++++++++++++
 reftable/blocksource.h          |  22 +++++
 reftable/reftable-blocksource.h |  50 +++++++++++
 4 files changed, 221 insertions(+)
 create mode 100644 reftable/blocksource.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/reftable-blocksource.h
Makefile +1 -0
index 5ce78772fd..030b3c0204 100644
@@ -2391,6 +2391,7 @@ XDIFF_OBJS += xdiff/xutils.o
 
 REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/error.o
+REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 
 REFTABLE_TEST_OBJS += reftable/test_framework.o
reftable/blocksource.c new file +148 -0

148 lines not on this page · raw message

reftable/blocksource.h new file +22 -0
new file mode 100644
index 0000000000..072e2727ad
@@ -0,0 +1,22 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "system.h"
+
+struct reftable_block_source;
+
+/* Create an in-memory block source for reading reftables */
+void block_source_from_strbuf(struct reftable_block_source *bs,
+			      struct strbuf *buf);
+
+struct reftable_block_source malloc_block_source(void);
+
+#endif
reftable/reftable-blocksource.h new file +50 -0

50 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#101 lore raw

PATCH v3 7/16 reftable: (de)serialization for the polymorphic record type.

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable format is structured as a sequence of blocks, and each block contains a sequence of prefix-compressed key-value records. There are 4 types of records, and they have similarities in how they must be handled. This is achieved by introducing a polymorphic 'record' type that encapsulates ref, log, index and object records.

---
 Makefile                   |    2 +
 reftable/constants.h       |   21 +
 reftable/record.c          | 1116 ++++++++++++++++++++++++++++++++++++
 reftable/record.h          |  139 +++++
 reftable/record_test.c     |  404 +++++++++++++
 reftable/reftable-record.h |   73 +++
 t/helper/test-reftable.c   |    2 +-
 7 files changed, 1756 insertions(+), 1 deletion(-)
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/reftable-record.h
Makefile +2 -0
index 030b3c0204..199aec9267 100644
@@ -2393,7 +2393,9 @@ REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/error.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
+REFTABLE_OBJS += reftable/record.o
 
+REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/basics_test.o
 
reftable/constants.h new file +21 -0
new file mode 100644
index 0000000000..5eee72c4c1
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
reftable/record.c new file +1116 -0

1,116 lines not on this page · raw message

reftable/record.h new file +139 -0

139 lines not on this page · raw message

reftable/record_test.c new file +404 -0

404 lines not on this page · raw message

reftable/reftable-record.h new file +73 -0

73 lines not on this page · raw message

t/helper/test-reftable.c +1 -1
index 3b58e423e7..09d4b83ef9 100644
@@ -4,6 +4,6 @@
 int cmd__reftable(int argc, const char **argv)
 {
 	basics_test_main(argc, argv);
-
+	record_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#102 lore raw

PATCH v3 8/16 reftable: reading/writing blocks

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable format is structured as a sequence of block. Within a block, records are prefix compressed, with an index of offsets for fully expand keys to enable binary search within blocks.

This commit provides the logic to read and write these blocks.

Includes a code snippet copied from zlib

---
 Makefile                 |   3 +
 reftable/.gitattributes  |   1 +
 reftable/block.c         | 440 +++++++++++++++++++++++++++++++++++++++
 reftable/block.h         | 129 ++++++++++++
 reftable/block_test.c    | 119 +++++++++++
 reftable/zlib-compat.c   |  92 ++++++++
 t/helper/test-reftable.c |   1 +
 7 files changed, 785 insertions(+)
 create mode 100644 reftable/.gitattributes
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/zlib-compat.c
Makefile +3 -0
index 199aec9267..fcfe4075c3 100644
@@ -2391,10 +2391,13 @@ XDIFF_OBJS += xdiff/xutils.o
 
 REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/error.o
+REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/zlib-compat.o
 
+REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/basics_test.o
reftable/.gitattributes new file +1 -0
new file mode 100644
index 0000000000..f44451a379
@@ -0,0 +1 @@
+/zlib-compat.c	whitespace=-indent-with-non-tab,-trailing-space
reftable/block.c new file +440 -0

440 lines not on this page · raw message

reftable/block.h new file +129 -0

129 lines not on this page · raw message

reftable/block_test.c new file +119 -0

119 lines not on this page · raw message

reftable/zlib-compat.c new file +92 -0

92 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 09d4b83ef9..c9deeaf08c 100644
@@ -4,6 +4,7 @@
 int cmd__reftable(int argc, const char **argv)
 {
 	basics_test_main(argc, argv);
+	block_test_main(argc, argv);
 	record_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#103 lore raw

PATCH v3 9/16 reftable: a generic binary tree implementation

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

The reftable format includes support for an (OID => ref) map. This map can speed up visibility and reachability checks. In particular, various operations along the fetch/push path within Gerrit have ben sped up by using this structure.

The map is constructed with help of a binary tree. Object IDs are hashes, so they are uniformly distributed. Hence, the tree does not attempt forced rebalancing.

---
 Makefile                 |  4 ++-
 reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++
 reftable/tree.h          | 34 ++++++++++++++++++++++
 reftable/tree_test.c     | 61 ++++++++++++++++++++++++++++++++++++++
 t/helper/test-reftable.c |  1 +
 5 files changed, 162 insertions(+), 1 deletion(-)
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
Makefile +3 -1
index fcfe4075c3..89564d4c06 100644
@@ -2395,12 +2395,14 @@ REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
+REFTABLE_TEST_OBJS += reftable/basics_test.o
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
-REFTABLE_TEST_OBJS += reftable/basics_test.o
+REFTABLE_TEST_OBJS += reftable/tree_test.o
 
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
reftable/tree.c new file +63 -0

63 lines not on this page · raw message

reftable/tree.h new file +34 -0
new file mode 100644
index 0000000000..954512e9a3
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+/* tree_node is a generic binary search tree. */
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+/* looks for `key` in `rootp` using `compare` as comparison function. If insert
+   is set, insert the key if it's not found. Else, return NULL.
+*/
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+
+/* performs an infix walk of the tree. */
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+
+/*
+  deallocates the tree nodes recursively. Keys should be deallocated separately
+  by walking over the tree. */
+void tree_free(struct tree_node *t);
+
+#endif
reftable/tree_test.c new file +61 -0

61 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index c9deeaf08c..050551fa69 100644
@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)
 	basics_test_main(argc, argv);
 	block_test_main(argc, argv);
 	record_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#104 lore raw

PATCH v3 10/16 reftable: write reftable files

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile                   |   1 +
 reftable/reftable-writer.h | 149 ++++++++
 reftable/writer.c          | 676 +++++++++++++++++++++++++++++++++++++
 reftable/writer.h          |  50 +++
 4 files changed, 876 insertions(+)
 create mode 100644 reftable/reftable-writer.h
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
Makefile +1 -0
index 89564d4c06..3e33a409f0 100644
@@ -2396,6 +2396,7 @@ REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/record.o
 REFTABLE_OBJS += reftable/tree.o
+REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
 REFTABLE_TEST_OBJS += reftable/basics_test.o
reftable/reftable-writer.h new file +149 -0

149 lines not on this page · raw message

reftable/writer.c new file +676 -0

676 lines not on this page · raw message

reftable/writer.h new file +50 -0

50 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#105 lore raw

PATCH v3 11/16 reftable: read reftable files

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

This supports reading a single reftable file.

The commit introduces an abstract iterator type, which captures the usecases both of reading individual refs, and iterating over a segment of the ref namespace.

---
 Makefile                     |   3 +
 reftable/iter.c              | 242 ++++++++++++
 reftable/iter.h              |  75 ++++
 reftable/reader.c            | 733 +++++++++++++++++++++++++++++++++++
 reftable/reader.h            |  75 ++++
 reftable/reftable-iterator.h |  37 ++
 reftable/reftable-reader.h   |  89 +++++
 7 files changed, 1254 insertions(+)
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/reftable-iterator.h
 create mode 100644 reftable/reftable-reader.h
Makefile +3 -0
index 3e33a409f0..51dbcb1b9c 100644
@@ -2393,8 +2393,11 @@ REFTABLE_OBJS += reftable/basics.o
 REFTABLE_OBJS += reftable/error.o
 REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
+REFTABLE_OBJS += reftable/iter.o
 REFTABLE_OBJS += reftable/publicbasics.o
+REFTABLE_OBJS += reftable/reader.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/reftable.o
 REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
reftable/iter.c new file +242 -0

242 lines not on this page · raw message

reftable/iter.h new file +75 -0

75 lines not on this page · raw message

reftable/reader.c new file +733 -0

733 lines not on this page · raw message

reftable/reader.h new file +75 -0

75 lines not on this page · raw message

reftable/reftable-iterator.h new file +37 -0
new file mode 100644
index 0000000000..a3d6be7ae0
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_ITERATOR_H
+#define REFTABLE_ITERATOR_H
+
+#include "reftable-record.h"
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable.
+*/
+struct reftable_iterator {
+	struct reftable_iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_ref(struct reftable_iterator *it,
+			       struct reftable_ref_record *ref);
+
+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int reftable_iterator_next_log(struct reftable_iterator *it,
+			       struct reftable_log_record *log);
+
+/* releases resources associated with an iterator. */
+void reftable_iterator_destroy(struct reftable_iterator *it);
+
+#endif
reftable/reftable-reader.h new file +89 -0

89 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#106 lore raw

PATCH v3 12/16 reftable: reftable file level tests

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

With support for reading and writing files in place, we can construct files (in memory) and attempt to read them back.

Because some sections of the format are optional (eg. indices, log entries), we have to exercise this code using multiple sizes of input data

---
 Makefile                 |   1 +
 reftable/reftable_test.c | 577 +++++++++++++++++++++++++++++++++++++++
 t/helper/test-reftable.c |   1 +
 3 files changed, 579 insertions(+)
 create mode 100644 reftable/reftable_test.c
Makefile +1 -0
index 51dbcb1b9c..b20acdac0f 100644
@@ -2405,6 +2405,7 @@ REFTABLE_OBJS += reftable/zlib-compat.o
 REFTABLE_TEST_OBJS += reftable/basics_test.o
 REFTABLE_TEST_OBJS += reftable/block_test.o
 REFTABLE_TEST_OBJS += reftable/record_test.o
+REFTABLE_TEST_OBJS += reftable/reftable_test.o
 REFTABLE_TEST_OBJS += reftable/test_framework.o
 REFTABLE_TEST_OBJS += reftable/tree_test.o
 
reftable/reftable_test.c new file +577 -0

577 lines not on this page · raw message

t/helper/test-reftable.c +1 -0
index 050551fa69..fdf9258673 100644
@@ -6,6 +6,7 @@ int cmd__reftable(int argc, const char **argv)
 	basics_test_main(argc, argv);
 	block_test_main(argc, argv);
 	record_test_main(argc, argv);
+	reftable_test_main(argc, argv);
 	tree_test_main(argc, argv);
 	return 0;
 }
Signature · 1 line
gitgitgadget
#107 lore raw

PATCH v3 13/16 reftable: rest of library

In reply to Han-Wen Nienhuys via GitGitGadget

From: Han-Wen Nienhuys <[email protected]>

---
 Makefile                    |    4 +
 reftable/VERSION            |    1 +
 reftable/dump.c             |  212 ++++++
 reftable/merged.c           |  366 ++++++++++
 reftable/merged.h           |   35 +
 reftable/merged_test.c      |  333 ++++++++++
 reftable/pq.c               |  115 ++++
 reftable/pq.h               |   32 +
 reftable/refname.c          |  209 ++++++
 reftable/refname.h          |   29 +
 reftable/refname_test.c     |  100 +++
 reftable/reftable-generic.h |   49 ++
 reftable/reftable-merged.h  |   68 ++
 reftable/reftable-stack.h   |  116 ++++
 reftable/reftable.c         |   98 +++
 reftable/stack.c            | 1247 +++++++++++++++++++++++++++++++++++
 reftable/stack.h            |   41 ++
 reftable/stack_test.c       |  781 ++++++++++++++++++++++
 t/helper/test-reftable.c    |    3 +
 19 files changed, 3839 insertions(+)
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-generic.h
 create mode 100644 reftable/reftable-merged.h
 create mode 100644 reftable/reftable-stack.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
Makefile +4 -0
index b20acdac0f..278c489da4 100644
@@ -2394,10 +2394,14 @@ REFTABLE_OBJS += reftable/error.o
 REFTABLE_OBJS += reftable/block.o
 REFTABLE_OBJS += reftable/blocksource.o
 REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/merged.o
+REFTABLE_OBJS += reftable/pq.o
 REFTABLE_OBJS += reftable/publicbasics.o
 REFTABLE_OBJS += reftable/reader.o
 REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/refname.o
 REFTABLE_OBJS += reftable/reftable.o
+REFTABLE_OBJS += reftable/stack.o
 REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
reftable/VERSION new file +1 -0
new file mode 100644
index 0000000000..7553c395eb
@@ -0,0 +1 @@
+b8ec0f74c74cb6752eb2033ad8e755a9c19aad15 C: add missing header
reftable/dump.c new file +212 -0

212 lines not on this page · raw message

reftable/merged.c new file +366 -0

366 lines not on this page · raw message

reftable/merged.h new file +35 -0
new file mode 100644
index 0000000000..8c4d4d58d7
@@ -0,0 +1,35 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+
+struct reftable_merged_table {
+	struct reftable_table *stack;
+	size_t stack_len;
+	uint32_t hash_id;
+	int suppress_deletions;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct reftable_iterator *stack;
+	uint32_t hash_id;
+	size_t stack_len;
+	uint8_t typ;
+	int suppress_deletions;
+	struct merged_iter_pqueue pq;
+};
+
+void merged_table_release(struct reftable_merged_table *mt);
+
+#endif
reftable/merged_test.c new file +333 -0

333 lines not on this page · raw message

reftable/pq.c new file +115 -0

115 lines not on this page · raw message

reftable/pq.h new file +32 -0
new file mode 100644
index 0000000000..385d2fb139
@@ -0,0 +1,32 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	int index;
+	struct reftable_record rec;
+};
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	size_t len;
+	size_t cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);
+
+#endif
reftable/refname.c new file +209 -0

209 lines not on this page · raw message

reftable/refname.h new file +29 -0
new file mode 100644
index 0000000000..a24b40fcb4
@@ -0,0 +1,29 @@
+/*
+  Copyright 2020 Google LLC
+
+  Use of this source code is governed by a BSD-style
+  license that can be found in the LICENSE file or at
+  https://developers.google.com/open-source/licenses/bsd
+*/
+#ifndef REFNAME_H
+#define REFNAME_H
+
+#include "reftable-record.h"
+#include "reftable-generic.h"
+
+struct modification {
+	struct reftable_table tab;
+
+	char **add;
+	size_t add_len;
+
+	char **del;
+	size_t del_len;
+};
+
+int validate_ref_record_addition(struct reftable_table tab,
+				 struct reftable_ref_record *recs, size_t sz);
+
+int modification_validate(struct modification *mod);
+
+#endif
reftable/refname_test.c new file +100 -0

100 lines not on this page · raw message

reftable/reftable-generic.h new file +49 -0

49 lines not on this page · raw message

reftable/reftable-merged.h new file +68 -0

68 lines not on this page · raw message

reftable/reftable-stack.h new file +116 -0

116 lines not on this page · raw message

reftable/reftable.c new file +98 -0

98 lines not on this page · raw message

reftable/stack.c new file +1247 -0

1,223 lines not on this page · raw message

reftable/stack.h new file +41 -0

41 lines not on this page · raw message

reftable/stack_test.c new file +781 -0

781 lines not on this page · raw message

t/helper/test-reftable.c +3 -0
index fdf9258673..3b702f4855 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
gitgitgadget

Part of the diff is not shown here. View the raw message.