{"thread":{"id":"56405","subject":"[PATCH 01/19] hash.h: provide constants for the hash IDs","startedAt":"2021-08-30T14:58:03Z","lastAt":"2022-01-13T19:09:17Z","messageCount":111,"participants":["Han-Wen Nienhuys via GitGitGadget","Han-Wen Nienhuys","Carlo Marcelo Arenas Belón","Junio C Hamano","Carlo Arenas","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":19},"messages":[{"id":"434093","messageId":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":null,"subject":"[PATCH 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:36Z","receivedAt":"2021-08-30T14:58:03Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"The reftable format is described in Documentation/technical/reftable.txt.\n\nThis is a fully reentrant implementation of reading and writing the reftable\nfile format, and should be suitable for embedding in libgit2 too. It does\nnot hook the code up to git to function as a ref storage backend yet.\n\nHan-Wen Nienhuys (19):\n  hash.h: provide constants for the hash IDs\n  reftable: RFC: add LICENSE\n  reftable: add error related functionality\n  reftable: utility functions\n  reftable: add blocksource, an abstraction for random access reads\n  reftable: (de)serialization for the polymorphic record type.\n  Provide zlib's uncompress2 from compat/zlib-compat.c\n  reftable: reading/writing blocks\n  reftable: a generic binary tree implementation\n  reftable: write reftable files\n  reftable: generic interface to tables\n  reftable: read reftable files\n  reftable: reftable file level tests\n  reftable: add a heap-based priority queue for reftable records\n  reftable: add merged table view\n  reftable: implement refname validation\n  reftable: implement stack, a mutable database of reftable files.\n  reftable: add dump utility\n  Add \"test-tool dump-reftable\" command.\n\n Makefile                                   |   53 +-\n ci/lib.sh                                  |    1 +\n compat/.gitattributes                      |    1 +\n compat/zlib-uncompress2.c                  |   92 ++\n config.mak.uname                           |    1 +\n configure.ac                               |   13 +\n contrib/buildsystems/CMakeLists.txt        |   14 +-\n contrib/buildsystems/Generators/Vcxproj.pm |   11 +-\n hash.h                                     |    6 +\n object-file.c                              |    7 +-\n reftable/LICENSE                           |   31 +\n reftable/basics.c                          |  128 ++\n reftable/basics.h                          |   60 +\n reftable/basics_test.c                     |   98 ++\n reftable/block.c                           |  448 +++++++\n reftable/block.h                           |  127 ++\n reftable/block_test.c                      |  120 ++\n reftable/blocksource.c                     |  148 +++\n reftable/blocksource.h                     |   22 +\n reftable/constants.h                       |   21 +\n reftable/dump.c                            |  107 ++\n reftable/error.c                           |   41 +\n reftable/generic.c                         |  169 +++\n reftable/generic.h                         |   32 +\n reftable/iter.c                            |  194 +++\n reftable/iter.h                            |   69 +\n reftable/merged.c                          |  362 +++++\n reftable/merged.h                          |   35 +\n reftable/merged_test.c                     |  292 ++++\n reftable/pq.c                              |  115 ++\n reftable/pq.h                              |   32 +\n reftable/pq_test.c                         |   72 +\n reftable/publicbasics.c                    |   58 +\n reftable/reader.c                          |  801 +++++++++++\n reftable/reader.h                          |   66 +\n reftable/readwrite_test.c                  |  652 +++++++++\n reftable/record.c                          | 1212 +++++++++++++++++\n reftable/record.h                          |  139 ++\n reftable/record_test.c                     |  412 ++++++\n reftable/refname.c                         |  209 +++\n reftable/refname.h                         |   29 +\n reftable/refname_test.c                    |  102 ++\n reftable/reftable-blocksource.h            |   49 +\n reftable/reftable-error.h                  |   62 +\n reftable/reftable-generic.h                |   47 +\n reftable/reftable-iterator.h               |   39 +\n reftable/reftable-malloc.h                 |   18 +\n reftable/reftable-merged.h                 |   72 +\n reftable/reftable-reader.h                 |  101 ++\n reftable/reftable-record.h                 |  114 ++\n reftable/reftable-stack.h                  |  128 ++\n reftable/reftable-tests.h                  |   23 +\n reftable/reftable-writer.h                 |  148 +++\n reftable/reftable.c                        |  115 ++\n reftable/stack.c                           | 1396 ++++++++++++++++++++\n reftable/stack.h                           |   41 +\n reftable/stack_test.c                      |  949 +++++++++++++\n reftable/system.h                          |   24 +\n reftable/test_framework.c                  |   23 +\n reftable/test_framework.h                  |   53 +\n reftable/tree.c                            |   63 +\n reftable/tree.h                            |   34 +\n reftable/tree_test.c                       |   61 +\n reftable/writer.c                          |  690 ++++++++++\n reftable/writer.h                          |   50 +\n t/helper/test-reftable.c                   |   21 +\n t/helper/test-tool.c                       |    4 +-\n t/helper/test-tool.h                       |    2 +\n t/t0032-reftable-unittest.sh               |   15 +\n 69 files changed, 10932 insertions(+), 12 deletions(-)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n create mode 100644 reftable/LICENSE\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/dump.c\n create mode 100644 reftable/error.c\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/readwrite_test.c\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n create mode 100644 reftable/reftable-blocksource.h\n create mode 100644 reftable/reftable-error.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-merged.h\n create mode 100644 reftable/reftable-reader.h\n create mode 100644 reftable/reftable-record.h\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/reftable.c\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\n\nbase-commit: c4203212e360b25a1c69467b5a8437d45a373cac\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1081%2Fhanwen%2Fjust-library-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1081/hanwen/just-library-v1\nPull-Request: https://github.com/git/git/pull/1081\n-- \ngitgitgadget\n"},{"id":"434090","messageId":"b9ee8cd8e9fd030910fd0cee37c01fd9a44dd430.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 01/19] hash.h: provide constants for the hash IDs","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:37Z","receivedAt":"2021-08-30T14:58:04Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will simplify referencing them from code that is not deeply integrated with\nGit, in particular, the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n hash.h        | 6 ++++++\n object-file.c | 7 ++-----\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/hash.h b/hash.h\nindex 9e25c40e9ac..5d40368f18a 100644\n--- a/hash.h\n+++ b/hash.h\n@@ -95,12 +95,18 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s\n /* Number of algorithms supported (including unknown). */\n #define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)\n \n+/* \"sha1\", big-endian */\n+#define GIT_SHA1_FORMAT_ID 0x73686131\n+\n /* The length in bytes and in hex digits of an object name (SHA-1 value). */\n #define GIT_SHA1_RAWSZ 20\n #define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)\n /* The block size of SHA-1. */\n #define GIT_SHA1_BLKSZ 64\n \n+/* \"s256\", big-endian */\n+#define GIT_SHA256_FORMAT_ID 0x73323536\n+\n /* The length in bytes and in hex digits of an object name (SHA-256 value). */\n #define GIT_SHA256_RAWSZ 32\n #define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)\ndiff --git a/object-file.c b/object-file.c\nindex a8be8994814..7bfd5e6e2e9 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -164,7 +164,6 @@ static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx)\n \tBUG(\"trying to finalize unknown hash\");\n }\n \n-\n const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t{\n \t\tNULL,\n@@ -183,8 +182,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha1\",\n-\t\t/* \"sha1\", big-endian */\n-\t\t0x73686131,\n+\t\tGIT_SHA1_FORMAT_ID,\n \t\tGIT_SHA1_RAWSZ,\n \t\tGIT_SHA1_HEXSZ,\n \t\tGIT_SHA1_BLKSZ,\n@@ -199,8 +197,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha256\",\n-\t\t/* \"s256\", big-endian */\n-\t\t0x73323536,\n+\t\tGIT_SHA256_FORMAT_ID,\n \t\tGIT_SHA256_RAWSZ,\n \t\tGIT_SHA256_HEXSZ,\n \t\tGIT_SHA256_BLKSZ,\n-- \ngitgitgadget\n\n"},{"id":"434091","messageId":"ea6f4f8c1506d9e8a6671018bb2b9d47e772cb34.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 03/19] reftable: add error related functionality","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:39Z","receivedAt":"2021-08-30T14:58:07Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable/ directory is structured as a library, so it cannot\ncrash on misuse. Instead, it returns an error codes.\n\nIn addition, the error code can be used to signal conditions from lower levels\nof the library to be handled by higher levels of the library. For example, a\ntransaction might legitimately write an empty reftable file, but in that case,\nwe'd want to shortcut the transaction overhead.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/error.c          | 41 ++++++++++++++++++++++++++\n reftable/reftable-error.h | 62 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 103 insertions(+)\n create mode 100644 reftable/error.c\n create mode 100644 reftable/reftable-error.h\n\ndiff --git a/reftable/error.c b/reftable/error.c\nnew file mode 100644\nindex 00000000000..f6f16def921\n--- /dev/null\n+++ b/reftable/error.c\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-error.h\"\n+\n+#include <stdio.h>\n+\n+const char *reftable_error_str(int err)\n+{\n+\tstatic char buf[250];\n+\tswitch (err) {\n+\tcase REFTABLE_IO_ERROR:\n+\t\treturn \"I/O error\";\n+\tcase REFTABLE_FORMAT_ERROR:\n+\t\treturn \"corrupt reftable file\";\n+\tcase REFTABLE_NOT_EXIST_ERROR:\n+\t\treturn \"file does not exist\";\n+\tcase REFTABLE_LOCK_ERROR:\n+\t\treturn \"data is outdated\";\n+\tcase REFTABLE_API_ERROR:\n+\t\treturn \"misuse of the reftable API\";\n+\tcase REFTABLE_ZLIB_ERROR:\n+\t\treturn \"zlib failure\";\n+\tcase REFTABLE_NAME_CONFLICT:\n+\t\treturn \"file/directory conflict\";\n+\tcase REFTABLE_EMPTY_TABLE_ERROR:\n+\t\treturn \"wrote empty table\";\n+\tcase REFTABLE_REFNAME_ERROR:\n+\t\treturn \"invalid refname\";\n+\tcase -1:\n+\t\treturn \"general error\";\n+\tdefault:\n+\t\tsnprintf(buf, sizeof(buf), \"unknown error code %d\", err);\n+\t\treturn buf;\n+\t}\n+}\ndiff --git a/reftable/reftable-error.h b/reftable/reftable-error.h\nnew file mode 100644\nindex 00000000000..6f89bedf1a5\n--- /dev/null\n+++ b/reftable/reftable-error.h\n@@ -0,0 +1,62 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ERROR_H\n+#define REFTABLE_ERROR_H\n+\n+/*\n+ * Errors in reftable calls are signaled with negative integer return values. 0\n+ * means success.\n+ */\n+enum reftable_error {\n+\t/* Unexpected file system behavior */\n+\tREFTABLE_IO_ERROR = -2,\n+\n+\t/* Format inconsistency on reading data */\n+\tREFTABLE_FORMAT_ERROR = -3,\n+\n+\t/* File does not exist. Returned from block_source_from_file(), because\n+\t * it needs special handling in stack.\n+\t */\n+\tREFTABLE_NOT_EXIST_ERROR = -4,\n+\n+\t/* Trying to write out-of-date data. */\n+\tREFTABLE_LOCK_ERROR = -5,\n+\n+\t/* Misuse of the API:\n+\t *  - on writing a record with NULL refname.\n+\t *  - on writing a reftable_ref_record outside the table limits\n+\t *  - on writing a ref or log record before the stack's\n+\t * next_update_inde*x\n+\t *  - on writing a log record with multiline message with\n+\t *  exact_log_message unset\n+\t *  - on reading a reftable_ref_record from log iterator, or vice versa.\n+\t *\n+\t * When a call misuses the API, the internal state of the library is\n+\t * kept unchanged.\n+\t */\n+\tREFTABLE_API_ERROR = -6,\n+\n+\t/* Decompression error */\n+\tREFTABLE_ZLIB_ERROR = -7,\n+\n+\t/* Wrote a table without blocks. */\n+\tREFTABLE_EMPTY_TABLE_ERROR = -8,\n+\n+\t/* Dir/file conflict. */\n+\tREFTABLE_NAME_CONFLICT = -9,\n+\n+\t/* Invalid ref name. */\n+\tREFTABLE_REFNAME_ERROR = -10,\n+};\n+\n+/* convert the numeric error code to a string. The string should not be\n+ * deallocated. */\n+const char *reftable_error_str(int err);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"434092","messageId":"2d31c783fb5624a8dcbd4fedda9b7687ed784690.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 02/19] reftable: RFC: add LICENSE","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:38Z","receivedAt":"2021-08-30T14:58:08Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe objective of this code is to be usable as a C library, so it can be reused\nin libgit2.\n\nThis is currently using a BSD license as it is the liberal license I could find,\nbut this could be changed to whatever fits the stated goal above.\n\nThis code is currently imported from github.com/hanwen/reftable. Once this code\nlands in git.git, the C code will be removed from github.com/hanwen/reftable,\nand the git.git code will be the source of truth.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/LICENSE | 31 +++++++++++++++++++++++++++++++\n 1 file changed, 31 insertions(+)\n create mode 100644 reftable/LICENSE\n\ndiff --git a/reftable/LICENSE b/reftable/LICENSE\nnew file mode 100644\nindex 00000000000..402e0f9356b\n--- /dev/null\n+++ b/reftable/LICENSE\n@@ -0,0 +1,31 @@\n+BSD License\n+\n+Copyright (c) 2020, Google LLC\n+All rights reserved.\n+\n+Redistribution and use in source and binary forms, with or without\n+modification, are permitted provided that the following conditions are\n+met:\n+\n+* Redistributions of source code must retain the above copyright notice,\n+this list of conditions and the following disclaimer.\n+\n+* Redistributions in binary form must reproduce the above copyright\n+notice, this list of conditions and the following disclaimer in the\n+documentation and/or other materials provided with the distribution.\n+\n+* Neither the name of Google LLC nor the names of its contributors may\n+be used to endorse or promote products derived from this software\n+without specific prior written permission.\n+\n+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n-- \ngitgitgadget\n\n"},{"id":"434094","messageId":"fd5c5c9f33848fe2477f1c1fd0888ad553b3d88d.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 04/19] reftable: utility functions","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:40Z","receivedAt":"2021-08-30T14:58:10Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis commit provides basic utility classes for the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                                   |  25 +++-\n contrib/buildsystems/CMakeLists.txt        |  14 ++-\n contrib/buildsystems/Generators/Vcxproj.pm |  11 +-\n reftable/basics.c                          | 128 +++++++++++++++++++++\n reftable/basics.h                          |  60 ++++++++++\n reftable/basics_test.c                     |  98 ++++++++++++++++\n reftable/publicbasics.c                    |  58 ++++++++++\n reftable/reftable-malloc.h                 |  18 +++\n reftable/reftable-tests.h                  |  22 ++++\n reftable/system.h                          |  24 ++++\n reftable/test_framework.c                  |  23 ++++\n reftable/test_framework.h                  |  53 +++++++++\n t/helper/test-reftable.c                   |   9 ++\n t/helper/test-tool.c                       |   3 +-\n t/helper/test-tool.h                       |   1 +\n t/t0032-reftable-unittest.sh               |  15 +++\n 16 files changed, 555 insertions(+), 7 deletions(-)\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\ndiff --git a/Makefile b/Makefile\nindex d1feab008fc..98c965c61fd 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -743,6 +743,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o\n TEST_BUILTINS_OBJS += test-read-graph.o\n TEST_BUILTINS_OBJS += test-read-midx.o\n TEST_BUILTINS_OBJS += test-ref-store.o\n+TEST_BUILTINS_OBJS += test-reftable.o\n TEST_BUILTINS_OBJS += test-regex.o\n TEST_BUILTINS_OBJS += test-repository.o\n TEST_BUILTINS_OBJS += test-revision-walking.o\n@@ -821,6 +822,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n XDIFF_LIB = xdiff/lib.a\n+REFTABLE_LIB = reftable/libreftable.a\n+REFTABLE_TEST_LIB = reftable/libreftable_test.a\n \n GENERATED_H += command-list.h\n GENERATED_H += config-list.h\n@@ -1195,7 +1198,7 @@ THIRD_PARTY_SOURCES += compat/regex/%\n THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)\n+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)\n EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2446,7 +2449,15 @@ XDIFF_OBJS += xdiff/xutils.o\n .PHONY: xdiff-objs\n xdiff-objs: $(XDIFF_OBJS)\n \n+REFTABLE_OBJS += reftable/basics.o\n+REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/publicbasics.o\n+\n+REFTABLE_TEST_OBJS += reftable/test_framework.o\n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n+\n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n+\n .PHONY: test-objs\n test-objs: $(TEST_OBJS)\n \n@@ -2462,6 +2473,8 @@ OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n OBJECTS += $(XDIFF_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n+OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n+\n ifndef NO_CURL\n \tOBJECTS += http.o http-walker.o remote-curl.o\n endif\n@@ -2612,6 +2625,12 @@ $(LIB_FILE): $(LIB_OBJS)\n $(XDIFF_LIB): $(XDIFF_OBJS)\n \t$(QUIET_AR)$(AR) $(ARFLAGS) $@ $^\n \n+$(REFTABLE_LIB): $(REFTABLE_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n export DEFAULT_EDITOR DEFAULT_PAGER\n \n Documentation/GIT-EXCLUDED-PROGRAMS: FORCE\n@@ -2904,7 +2923,7 @@ perf: all\n \n t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \n-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)\n+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n \n check-sha1:: t/helper/test-tool$X\n@@ -3234,7 +3253,7 @@ cocciclean:\n clean: profile-clean coverage-clean cocciclean\n \t$(RM) *.res\n \t$(RM) $(OBJECTS)\n-\t$(RM) $(LIB_FILE) $(XDIFF_LIB)\n+\t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 171b4124afe..c2bf5bdffc6 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -640,6 +640,12 @@ parse_makefile_for_sources(libxdiff_SOURCES \"XDIFF_OBJS\")\n list(TRANSFORM libxdiff_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n add_library(xdiff STATIC ${libxdiff_SOURCES})\n \n+#reftable\n+parse_makefile_for_sources(reftable_SOURCES \"REFTABLE_OBJS\")\n+\n+list(TRANSFORM reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+add_library(reftable STATIC ${reftable_SOURCES})\n+\n if(WIN32)\n \tif(NOT MSVC)#use windres when compiling with gcc and clang\n \t\tadd_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res\n@@ -662,7 +668,7 @@ endif()\n #link all required libraries to common-main\n add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)\n \n-target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})\n+target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})\n if(Intl_FOUND)\n \ttarget_link_libraries(common-main ${Intl_LIBRARIES})\n endif()\n@@ -902,11 +908,15 @@ if(BUILD_TESTING)\n add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)\n target_link_libraries(test-fake-ssh common-main)\n \n+#reftable-tests\n+parse_makefile_for_sources(test-reftable_SOURCES \"REFTABLE_TEST_OBJS\")\n+list(TRANSFORM test-reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+\n #test-tool\n parse_makefile_for_sources(test-tool_SOURCES \"TEST_BUILTINS_OBJS\")\n \n list(TRANSFORM test-tool_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/t/helper/\")\n-add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})\n+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})\n target_link_libraries(test-tool common-main)\n \n set_target_properties(test-fake-ssh test-tool\ndiff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm\nindex d2584450ba1..1a25789d285 100644\n--- a/contrib/buildsystems/Generators/Vcxproj.pm\n+++ b/contrib/buildsystems/Generators/Vcxproj.pm\n@@ -77,7 +77,7 @@ sub createProject {\n     my $libs_release = \"\\n    \";\n     my $libs_debug = \"\\n    \";\n     if (!$static_library) {\n-      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n+      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib|reftable\\/libreftable\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n       $libs_debug = $libs_release;\n       $libs_debug =~ s/zlib\\.lib/zlibd\\.lib/g;\n       $libs_debug =~ s/libexpat\\.lib/libexpatd\\.lib/g;\n@@ -232,6 +232,7 @@ EOM\n EOM\n     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {\n       my $uuid_libgit = $$build_structure{\"LIBS_libgit_GUID\"};\n+      my $uuid_libreftable = $$build_structure{\"LIBS_reftable/libreftable_GUID\"};\n       my $uuid_xdiff_lib = $$build_structure{\"LIBS_xdiff/lib_GUID\"};\n \n       print F << \"EOM\";\n@@ -241,6 +242,14 @@ EOM\n       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n     </ProjectReference>\n EOM\n+      if (!($name =~ /xdiff|libreftable/)) {\n+        print F << \"EOM\";\n+    <ProjectReference Include=\"$cdup\\\\reftable\\\\libreftable\\\\libreftable.vcxproj\">\n+      <Project>$uuid_libreftable</Project>\n+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n+    </ProjectReference>\n+EOM\n+      }\n       if (!($name =~ 'xdiff')) {\n         print F << \"EOM\";\n     <ProjectReference Include=\"$cdup\\\\xdiff\\\\lib\\\\xdiff_lib.vcxproj\">\ndiff --git a/reftable/basics.c b/reftable/basics.c\nnew file mode 100644\nindex 00000000000..f761e48028c\n--- /dev/null\n+++ b/reftable/basics.c\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+\n+void put_be24(uint8_t *out, uint32_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 16) & 0xff);\n+\tout[1] = (uint8_t)((i >> 8) & 0xff);\n+\tout[2] = (uint8_t)(i & 0xff);\n+}\n+\n+uint32_t get_be24(uint8_t *in)\n+{\n+\treturn (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |\n+\t       (uint32_t)(in[2]);\n+}\n+\n+void put_be16(uint8_t *out, uint16_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 8) & 0xff);\n+\tout[1] = (uint8_t)(i & 0xff);\n+}\n+\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)\n+{\n+\tsize_t lo = 0;\n+\tsize_t hi = sz;\n+\n+\t/* Invariants:\n+\t *\n+\t *  (hi == sz) || f(hi) == true\n+\t *  (lo == 0 && f(0) == true) || fi(lo) == false\n+\t */\n+\twhile (hi - lo > 1) {\n+\t\tsize_t mid = lo + (hi - lo) / 2;\n+\n+\t\tif (f(mid, args))\n+\t\t\thi = mid;\n+\t\telse\n+\t\t\tlo = mid;\n+\t}\n+\n+\tif (lo)\n+\t\treturn hi;\n+\n+\treturn f(0, args) ? 0 : 1;\n+}\n+\n+void free_names(char **a)\n+{\n+\tchar **p;\n+\tif (!a) {\n+\t\treturn;\n+\t}\n+\tfor (p = a; *p; p++) {\n+\t\treftable_free(*p);\n+\t}\n+\treftable_free(a);\n+}\n+\n+int names_length(char **names)\n+{\n+\tchar **p = names;\n+\tfor (; *p; p++) {\n+\t\t/* empty */\n+\t}\n+\treturn p - names;\n+}\n+\n+void parse_names(char *buf, int size, char ***namesp)\n+{\n+\tchar **names = NULL;\n+\tsize_t names_cap = 0;\n+\tsize_t names_len = 0;\n+\n+\tchar *p = buf;\n+\tchar *end = buf + size;\n+\twhile (p < end) {\n+\t\tchar *next = strchr(p, '\\n');\n+\t\tif (next && next < end) {\n+\t\t\t*next = 0;\n+\t\t} else {\n+\t\t\tnext = end;\n+\t\t}\n+\t\tif (p < next) {\n+\t\t\tif (names_len == names_cap) {\n+\t\t\t\tnames_cap = 2 * names_cap + 1;\n+\t\t\t\tnames = reftable_realloc(\n+\t\t\t\t\tnames, names_cap * sizeof(*names));\n+\t\t\t}\n+\t\t\tnames[names_len++] = xstrdup(p);\n+\t\t}\n+\t\tp = next + 1;\n+\t}\n+\n+\tnames = reftable_realloc(names, (names_len + 1) * sizeof(*names));\n+\tnames[names_len] = NULL;\n+\t*namesp = names;\n+}\n+\n+int names_equal(char **a, char **b)\n+{\n+\tint i = 0;\n+\tfor (; a[i] && b[i]; i++) {\n+\t\tif (strcmp(a[i], b[i])) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn a[i] == b[i];\n+}\n+\n+int common_prefix_size(struct strbuf *a, struct strbuf *b)\n+{\n+\tint p = 0;\n+\tfor (; p < a->len && p < b->len; p++) {\n+\t\tif (a->buf[p] != b->buf[p])\n+\t\t\tbreak;\n+\t}\n+\n+\treturn p;\n+}\ndiff --git a/reftable/basics.h b/reftable/basics.h\nnew file mode 100644\nindex 00000000000..096b36862b9\n--- /dev/null\n+++ b/reftable/basics.h\n@@ -0,0 +1,60 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BASICS_H\n+#define BASICS_H\n+\n+/*\n+ * miscellaneous utilities that are not provided by Git.\n+ */\n+\n+#include \"system.h\"\n+\n+/* Bigendian en/decoding of integers */\n+\n+void put_be24(uint8_t *out, uint32_t i);\n+uint32_t get_be24(uint8_t *in);\n+void put_be16(uint8_t *out, uint16_t i);\n+\n+/*\n+ * find smallest index i in [0, sz) at which f(i) is true, assuming\n+ * that f is ascending. Return sz if f(i) is false for all indices.\n+ *\n+ * Contrary to bsearch(3), this returns something useful if the argument is not\n+ * found.\n+ */\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);\n+\n+/*\n+ * Frees a NULL terminated array of malloced strings. The array itself is also\n+ * freed.\n+ */\n+void free_names(char **a);\n+\n+/* parse a newline separated list of names. `size` is the length of the buffer,\n+ * without terminating '\\0'. Empty names are discarded. */\n+void parse_names(char *buf, int size, char ***namesp);\n+\n+/* compares two NULL-terminated arrays of strings. */\n+int names_equal(char **a, char **b);\n+\n+/* returns the array size of a NULL-terminated array of strings. */\n+int names_length(char **names);\n+\n+/* Allocation routines; they invoke the functions set through\n+ * reftable_set_alloc() */\n+void *reftable_malloc(size_t sz);\n+void *reftable_realloc(void *p, size_t sz);\n+void reftable_free(void *p);\n+void *reftable_calloc(size_t sz);\n+\n+/* Find the longest shared prefix size of `a` and `b` */\n+struct strbuf;\n+int common_prefix_size(struct strbuf *a, struct strbuf *b);\n+\n+#endif\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nnew file mode 100644\nindex 00000000000..1fcd2297256\n--- /dev/null\n+++ b/reftable/basics_test.c\n@@ -0,0 +1,98 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct binsearch_args {\n+\tint key;\n+\tint *arr;\n+};\n+\n+static int binsearch_func(size_t i, void *void_args)\n+{\n+\tstruct binsearch_args *args = void_args;\n+\n+\treturn args->key < args->arr[i];\n+}\n+\n+static void test_binsearch(void)\n+{\n+\tint arr[] = { 2, 4, 6, 8, 10 };\n+\tsize_t sz = ARRAY_SIZE(arr);\n+\tstruct binsearch_args args = {\n+\t\t.arr = arr,\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 1; i < 11; i++) {\n+\t\tint res;\n+\t\targs.key = i;\n+\t\tres = binsearch(sz, &binsearch_func, &args);\n+\n+\t\tif (res < sz) {\n+\t\t\tEXPECT(args.key < arr[res]);\n+\t\t\tif (res > 0) {\n+\t\t\t\tEXPECT(args.key >= arr[res - 1]);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tEXPECT(args.key == 10 || args.key == 11);\n+\t\t}\n+\t}\n+}\n+\n+static void test_names_length(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(a) == 2);\n+}\n+\n+static void test_parse_names_normal(void)\n+{\n+\tchar in[] = \"a\\nb\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!strcmp(out[1], \"b\"));\n+\tEXPECT(!out[2]);\n+\tfree_names(out);\n+}\n+\n+static void test_parse_names_drop_empty(void)\n+{\n+\tchar in[] = \"a\\n\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!out[1]);\n+\tfree_names(out);\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct strbuf s1 = STRBUF_INIT;\n+\tstruct strbuf s2 = STRBUF_INIT;\n+\tstrbuf_addstr(&s1, \"abcdef\");\n+\tstrbuf_addstr(&s2, \"abc\");\n+\tEXPECT(common_prefix_size(&s1, &s2) == 3);\n+\tstrbuf_release(&s1);\n+\tstrbuf_release(&s2);\n+}\n+\n+int basics_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_parse_names_normal);\n+\tRUN_TEST(test_parse_names_drop_empty);\n+\tRUN_TEST(test_binsearch);\n+\tRUN_TEST(test_names_length);\n+\treturn 0;\n+}\ndiff --git a/reftable/publicbasics.c b/reftable/publicbasics.c\nnew file mode 100644\nindex 00000000000..bd0a02d3f68\n--- /dev/null\n+++ b/reftable/publicbasics.c\n@@ -0,0 +1,58 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-malloc.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;\n+static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;\n+static void (*reftable_free_ptr)(void *) = &free;\n+\n+void *reftable_malloc(size_t sz)\n+{\n+\treturn (*reftable_malloc_ptr)(sz);\n+}\n+\n+void *reftable_realloc(void *p, size_t sz)\n+{\n+\treturn (*reftable_realloc_ptr)(p, sz);\n+}\n+\n+void reftable_free(void *p)\n+{\n+\treftable_free_ptr(p);\n+}\n+\n+void *reftable_calloc(size_t sz)\n+{\n+\tvoid *p = reftable_malloc(sz);\n+\tmemset(p, 0, sz);\n+\treturn p;\n+}\n+\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *))\n+{\n+\treftable_malloc_ptr = malloc;\n+\treftable_realloc_ptr = realloc;\n+\treftable_free_ptr = free;\n+}\n+\n+int hash_size(uint32_t id)\n+{\n+\tswitch (id) {\n+\tcase 0:\n+\tcase GIT_SHA1_FORMAT_ID:\n+\t\treturn GIT_SHA1_RAWSZ;\n+\tcase GIT_SHA256_FORMAT_ID:\n+\t\treturn GIT_SHA256_RAWSZ;\n+\t}\n+\tabort();\n+}\ndiff --git a/reftable/reftable-malloc.h b/reftable/reftable-malloc.h\nnew file mode 100644\nindex 00000000000..5f2185f1f34\n--- /dev/null\n+++ b/reftable/reftable-malloc.h\n@@ -0,0 +1,18 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_H\n+#define REFTABLE_H\n+\n+#include <stddef.h>\n+\n+/* Overrides the functions to use for memory management. */\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *));\n+\n+#endif\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nnew file mode 100644\nindex 00000000000..5e7698ae654\n--- /dev/null\n+++ b/reftable/reftable-tests.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_TESTS_H\n+#define REFTABLE_TESTS_H\n+\n+int basics_test_main(int argc, const char **argv);\n+int block_test_main(int argc, const char **argv);\n+int merged_test_main(int argc, const char **argv);\n+int record_test_main(int argc, const char **argv);\n+int refname_test_main(int argc, const char **argv);\n+int reftable_test_main(int argc, const char **argv);\n+int stack_test_main(int argc, const char **argv);\n+int tree_test_main(int argc, const char **argv);\n+int reftable_dump_main(int argc, char *const *argv);\n+\n+#endif\ndiff --git a/reftable/system.h b/reftable/system.h\nnew file mode 100644\nindex 00000000000..4f62827b83b\n--- /dev/null\n+++ b/reftable/system.h\n@@ -0,0 +1,24 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef SYSTEM_H\n+#define SYSTEM_H\n+\n+/* This header glues the reftable library to the rest of Git */\n+\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"hash.h\" /* hash ID, sizes.*/\n+#include \"dir.h\" /* remove_dir_recursively, for tests.*/\n+\n+#include <zlib.h>\n+\n+struct strbuf;\n+int hash_size(uint32_t id);\n+\n+#endif\ndiff --git a/reftable/test_framework.c b/reftable/test_framework.c\nnew file mode 100644\nindex 00000000000..84ac972cad0\n--- /dev/null\n+++ b/reftable/test_framework.c\n@@ -0,0 +1,23 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"test_framework.h\"\n+\n+#include \"basics.h\"\n+\n+void set_test_hash(uint8_t *p, int i)\n+{\n+\tmemset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID));\n+}\n+\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz)\n+{\n+\tstrbuf_add(b, data, sz);\n+\treturn sz;\n+}\ndiff --git a/reftable/test_framework.h b/reftable/test_framework.h\nnew file mode 100644\nindex 00000000000..774cb275bf6\n--- /dev/null\n+++ b/reftable/test_framework.h\n@@ -0,0 +1,53 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TEST_FRAMEWORK_H\n+#define TEST_FRAMEWORK_H\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+\n+#define EXPECT_ERR(c)                                                  \\\n+\tif (c != 0) {                                                  \\\n+\t\tfflush(stderr);                                        \\\n+\t\tfflush(stdout);                                        \\\n+\t\tfprintf(stderr, \"%s: %d: error == %d (%s), want 0\\n\",  \\\n+\t\t\t__FILE__, __LINE__, c, reftable_error_str(c)); \\\n+\t\tabort();                                               \\\n+\t}\n+\n+#define EXPECT_STREQ(a, b)                                               \\\n+\tif (strcmp(a, b)) {                                              \\\n+\t\tfflush(stderr);                                          \\\n+\t\tfflush(stdout);                                          \\\n+\t\tfprintf(stderr, \"%s:%d: %s (%s) != %s (%s)\\n\", __FILE__, \\\n+\t\t\t__LINE__, #a, a, #b, b);                         \\\n+\t\tabort();                                                 \\\n+\t}\n+\n+#define EXPECT(c)                                                          \\\n+\tif (!(c)) {                                                        \\\n+\t\tfflush(stderr);                                            \\\n+\t\tfflush(stdout);                                            \\\n+\t\tfprintf(stderr, \"%s: %d: failed assertion %s\\n\", __FILE__, \\\n+\t\t\t__LINE__, #c);                                     \\\n+\t\tabort();                                                   \\\n+\t}\n+\n+#define RUN_TEST(f)                          \\\n+\tfprintf(stderr, \"running %s\\n\", #f); \\\n+\tfflush(stderr);                      \\\n+\tf();\n+\n+void set_test_hash(uint8_t *p, int i);\n+\n+/* Like strbuf_add, but suitable for passing to reftable_new_writer\n+ */\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nnew file mode 100644\nindex 00000000000..3b58e423e7b\n--- /dev/null\n+++ b/t/helper/test-reftable.c\n@@ -0,0 +1,9 @@\n+#include \"reftable/reftable-tests.h\"\n+#include \"test-tool.h\"\n+\n+int cmd__reftable(int argc, const char **argv)\n+{\n+\tbasics_test_main(argc, argv);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 3ce5585e53a..f7c888ffda7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -53,13 +53,14 @@ static struct test_cmd cmds[] = {\n \t{ \"pcre2-config\", cmd__pcre2_config },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n-\t{ \"proc-receive\", cmd__proc_receive},\n+\t{ \"proc-receive\", cmd__proc_receive },\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\n \t{ \"read-graph\", cmd__read_graph },\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n+\t{ \"reftable\", cmd__reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 9f0f5228508..25f77469146 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -49,6 +49,7 @@ int cmd__read_cache(int argc, const char **argv);\n int cmd__read_graph(int argc, const char **argv);\n int cmd__read_midx(int argc, const char **argv);\n int cmd__ref_store(int argc, const char **argv);\n+int cmd__reftable(int argc, const char **argv);\n int cmd__regex(int argc, const char **argv);\n int cmd__repository(int argc, const char **argv);\n int cmd__revision_walking(int argc, const char **argv);\ndiff --git a/t/t0032-reftable-unittest.sh b/t/t0032-reftable-unittest.sh\nnew file mode 100755\nindex 00000000000..0ed14971a58\n--- /dev/null\n+++ b/t/t0032-reftable-unittest.sh\n@@ -0,0 +1,15 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Google LLC\n+#\n+\n+test_description='reftable unittests'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'unittests' '\n+\tTMPDIR=$(pwd) && export TMPDIR &&\n+\ttest-tool reftable\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"434095","messageId":"c11f06ed6fba27e7334df67e6aea5da976c39568.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 05/19] reftable: add blocksource, an abstraction for random access reads","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:41Z","receivedAt":"2021-08-30T14:58:12Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is usually used with files for storage. However, we abstract\naway this using the blocksource data structure. This has two advantages:\n\n* log blocks are zlib compressed, and handling them is simplified if we can\n  discard byte segments from within the block layer.\n\n* for unittests, it is useful to read and write in-memory. The blocksource\n  allows us to abstract the data away from on-disk files.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                        |   1 +\n reftable/blocksource.c          | 148 ++++++++++++++++++++++++++++++++\n reftable/blocksource.h          |  22 +++++\n reftable/reftable-blocksource.h |  49 +++++++++++\n 4 files changed, 220 insertions(+)\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/reftable-blocksource.h\n\ndiff --git a/Makefile b/Makefile\nindex 98c965c61fd..621ac53d09f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2451,6 +2451,7 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n \n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/blocksource.c b/reftable/blocksource.c\nnew file mode 100644\nindex 00000000000..0044eecd9aa\n--- /dev/null\n+++ b/reftable/blocksource.c\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+\n+static void strbuf_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void strbuf_close(void *b)\n+{\n+}\n+\n+static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t     uint32_t size)\n+{\n+\tstruct strbuf *b = v;\n+\tassert(off + size <= b->len);\n+\tdest->data = reftable_calloc(size);\n+\tmemcpy(dest->data, b->buf + off, size);\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static uint64_t strbuf_size(void *b)\n+{\n+\treturn ((struct strbuf *)b)->len;\n+}\n+\n+static struct reftable_block_source_vtable strbuf_vtable = {\n+\t.size = &strbuf_size,\n+\t.read_block = &strbuf_read_block,\n+\t.return_block = &strbuf_return_block,\n+\t.close = &strbuf_close,\n+};\n+\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf)\n+{\n+\tassert(!bs->ops);\n+\tbs->ops = &strbuf_vtable;\n+\tbs->arg = buf;\n+}\n+\n+static void malloc_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static struct reftable_block_source_vtable malloc_vtable = {\n+\t.return_block = &malloc_return_block,\n+};\n+\n+static struct reftable_block_source malloc_block_source_instance = {\n+\t.ops = &malloc_vtable,\n+};\n+\n+struct reftable_block_source malloc_block_source(void)\n+{\n+\treturn malloc_block_source_instance;\n+}\n+\n+struct file_block_source {\n+\tint fd;\n+\tuint64_t size;\n+};\n+\n+static uint64_t file_size(void *b)\n+{\n+\treturn ((struct file_block_source *)b)->size;\n+}\n+\n+static void file_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void file_close(void *b)\n+{\n+\tint fd = ((struct file_block_source *)b)->fd;\n+\tif (fd > 0) {\n+\t\tclose(fd);\n+\t\t((struct file_block_source *)b)->fd = 0;\n+\t}\n+\n+\treftable_free(b);\n+}\n+\n+static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t   uint32_t size)\n+{\n+\tstruct file_block_source *b = v;\n+\tassert(off + size <= b->size);\n+\tdest->data = reftable_malloc(size);\n+\tif (pread(b->fd, dest->data, size, off) != size)\n+\t\treturn -1;\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static struct reftable_block_source_vtable file_vtable = {\n+\t.size = &file_size,\n+\t.read_block = &file_read_block,\n+\t.return_block = &file_return_block,\n+\t.close = &file_close,\n+};\n+\n+int reftable_block_source_from_file(struct reftable_block_source *bs,\n+\t\t\t\t    const char *name)\n+{\n+\tstruct stat st = { 0 };\n+\tint err = 0;\n+\tint fd = open(name, O_RDONLY);\n+\tstruct file_block_source *p = NULL;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\treturn REFTABLE_NOT_EXIST_ERROR;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\terr = fstat(fd, &st);\n+\tif (err < 0)\n+\t\treturn -1;\n+\n+\tp = reftable_calloc(sizeof(struct file_block_source));\n+\tp->size = st.st_size;\n+\tp->fd = fd;\n+\n+\tassert(!bs->ops);\n+\tbs->ops = &file_vtable;\n+\tbs->arg = p;\n+\treturn 0;\n+}\ndiff --git a/reftable/blocksource.h b/reftable/blocksource.h\nnew file mode 100644\nindex 00000000000..072e2727ad2\n--- /dev/null\n+++ b/reftable/blocksource.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCKSOURCE_H\n+#define BLOCKSOURCE_H\n+\n+#include \"system.h\"\n+\n+struct reftable_block_source;\n+\n+/* Create an in-memory block source for reading reftables */\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf);\n+\n+struct reftable_block_source malloc_block_source(void);\n+\n+#endif\ndiff --git a/reftable/reftable-blocksource.h b/reftable/reftable-blocksource.h\nnew file mode 100644\nindex 00000000000..5aa3990a573\n--- /dev/null\n+++ b/reftable/reftable-blocksource.h\n@@ -0,0 +1,49 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_BLOCKSOURCE_H\n+#define REFTABLE_BLOCKSOURCE_H\n+\n+#include <stdint.h>\n+\n+/* block_source is a generic wrapper for a seekable readable file.\n+ */\n+struct reftable_block_source {\n+\tstruct reftable_block_source_vtable *ops;\n+\tvoid *arg;\n+};\n+\n+/* a contiguous segment of bytes. It keeps track of its generating block_source\n+ * so it can return itself into the pool. */\n+struct reftable_block {\n+\tuint8_t *data;\n+\tint len;\n+\tstruct reftable_block_source source;\n+};\n+\n+/* block_source_vtable are the operations that make up block_source */\n+struct reftable_block_source_vtable {\n+\t/* returns the size of a block source */\n+\tuint64_t (*size)(void *source);\n+\n+\t/* reads a segment from the block source. It is an error to read\n+\t   beyond the end of the block */\n+\tint (*read_block)(void *source, struct reftable_block *dest,\n+\t\t\t  uint64_t off, uint32_t size);\n+\t/* mark the block as read; may return the data back to malloc */\n+\tvoid (*return_block)(void *source, struct reftable_block *blockp);\n+\n+\t/* release all resources associated with the block source */\n+\tvoid (*close)(void *source);\n+};\n+\n+/* opens a file on the file system as a block_source */\n+int reftable_block_source_from_file(struct reftable_block_source *block_src,\n+\t\t\t\t    const char *name);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"434097","messageId":"aa9b8e05dbdd8c6ad5ff687b5a1aefe31d78853b.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 07/19] Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:43Z","receivedAt":"2021-08-30T14:58:13Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will be needed for reading reflog blocks in reftable.\n\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |  7 +++\n ci/lib.sh                 |  1 +\n compat/.gitattributes     |  1 +\n compat/zlib-uncompress2.c | 92 +++++++++++++++++++++++++++++++++++++++\n config.mak.uname          |  1 +\n configure.ac              | 13 ++++++\n 6 files changed, 115 insertions(+)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n\ndiff --git a/Makefile b/Makefile\nindex 02a83a67467..ec89320080b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -256,6 +256,8 @@ all::\n #\n # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.\n #\n+# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.\n+#\n # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,\n # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)\n #\n@@ -1738,6 +1740,11 @@ ifdef NO_DEFLATE_BOUND\n \tBASIC_CFLAGS += -DNO_DEFLATE_BOUND\n endif\n \n+ifdef NO_UNCOMPRESS2\n+\tBASIC_CFLAGS += -DNO_UNCOMPRESS2\n+\tREFTABLE_OBJS += compat/zlib-uncompress2.o\n+endif\n+\n ifdef NO_POSIX_GOODIES\n \tBASIC_CFLAGS += -DNO_POSIX_GOODIES\n endif\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex 476c3f369f5..5711c63979d 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -224,6 +224,7 @@ linux-gcc-default)\n \t;;\n Linux32)\n \tCC=gcc\n+\tMAKEFLAGS=\"$MAKEFLAGS NO_UNCOMPRESS2=1\"\n \t;;\n linux-musl)\n \tCC=gcc\ndiff --git a/compat/.gitattributes b/compat/.gitattributes\nnew file mode 100644\nindex 00000000000..40dbfb170da\n--- /dev/null\n+++ b/compat/.gitattributes\n@@ -0,0 +1 @@\n+/zlib-uncompress2.c\twhitespace=-indent-with-non-tab,-trailing-space\ndiff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c\nnew file mode 100644\nindex 00000000000..6893bb469ce\n--- /dev/null\n+++ b/compat/zlib-uncompress2.c\n@@ -0,0 +1,92 @@\n+/* taken from zlib's uncompr.c\n+\n+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f\n+   Author: Mark Adler <madler@alumni.caltech.edu>\n+   Date:   Sun Jan 15 09:18:46 2017 -0800\n+\n+       zlib 1.2.11\n+\n+*/\n+\n+/*\n+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler\n+ * For conditions of distribution and use, see copyright notice in zlib.h\n+ */\n+\n+#include <zlib.h>\n+\n+/* clang-format off */\n+\n+/* ===========================================================================\n+     Decompresses the source buffer into the destination buffer.  *sourceLen is\n+   the byte length of the source buffer. Upon entry, *destLen is the total size\n+   of the destination buffer, which must be large enough to hold the entire\n+   uncompressed data. (The size of the uncompressed data must have been saved\n+   previously by the compressor and transmitted to the decompressor by some\n+   mechanism outside the scope of this compression library.) Upon exit,\n+   *destLen is the size of the decompressed data and *sourceLen is the number\n+   of source bytes consumed. Upon return, source + *sourceLen points to the\n+   first unused input byte.\n+\n+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough\n+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or\n+   Z_DATA_ERROR if the input data was corrupted, including if the input data is\n+   an incomplete zlib stream.\n+*/\n+int ZEXPORT uncompress2 (\n+    Bytef *dest,\n+    uLongf *destLen,\n+    const Bytef *source,\n+    uLong *sourceLen) {\n+    z_stream stream;\n+    int err;\n+    const uInt max = (uInt)-1;\n+    uLong len, left;\n+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */\n+\n+    len = *sourceLen;\n+    if (*destLen) {\n+        left = *destLen;\n+        *destLen = 0;\n+    }\n+    else {\n+        left = 1;\n+        dest = buf;\n+    }\n+\n+    stream.next_in = (z_const Bytef *)source;\n+    stream.avail_in = 0;\n+    stream.zalloc = (alloc_func)0;\n+    stream.zfree = (free_func)0;\n+    stream.opaque = (voidpf)0;\n+\n+    err = inflateInit(&stream);\n+    if (err != Z_OK) return err;\n+\n+    stream.next_out = dest;\n+    stream.avail_out = 0;\n+\n+    do {\n+        if (stream.avail_out == 0) {\n+            stream.avail_out = left > (uLong)max ? max : (uInt)left;\n+            left -= stream.avail_out;\n+        }\n+        if (stream.avail_in == 0) {\n+            stream.avail_in = len > (uLong)max ? max : (uInt)len;\n+            len -= stream.avail_in;\n+        }\n+        err = inflate(&stream, Z_NO_FLUSH);\n+    } while (err == Z_OK);\n+\n+    *sourceLen -= len + stream.avail_in;\n+    if (dest != buf)\n+        *destLen = stream.total_out;\n+    else if (stream.total_out && err == Z_BUF_ERROR)\n+        left = 1;\n+\n+    inflateEnd(&stream);\n+    return err == Z_STREAM_END ? Z_OK :\n+           err == Z_NEED_DICT ? Z_DATA_ERROR  :\n+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :\n+           err;\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 76516aaa9a5..c1890e4bc8d 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -258,6 +258,7 @@ ifeq ($(uname_S),FreeBSD)\n \tFILENO_IS_A_MACRO = UnfortunatelyYes\n endif\n ifeq ($(uname_S),OpenBSD)\n+\tNO_UNCOMPRESS2 = YesPlease\n \tNO_STRCASESTR = YesPlease\n \tNO_MEMMEM = YesPlease\n \tUSE_ST_TIMESPEC = YesPlease\ndiff --git a/configure.ac b/configure.ac\nindex 031e8d3fee8..c3a913103d0 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -672,9 +672,22 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],\n \tNO_DEFLATE_BOUND=yes])\n LIBS=\"$old_LIBS\"\n \n+AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [\n+AC_LANG_PROGRAM([#include <zlib.h>],\n+ [uncompress2(NULL,NULL,NULL,NULL);])])\n+AC_MSG_CHECKING([for uncompress2 in -lz])\n+old_LIBS=\"$LIBS\"\n+LIBS=\"$LIBS -lz\"\n+AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],\n+\t[AC_MSG_RESULT([yes])],\n+\t[AC_MSG_RESULT([no])\n+\tNO_UNCOMPRESS2=yes])\n+LIBS=\"$old_LIBS\"\n+\n GIT_UNSTASH_FLAGS($ZLIB_PATH)\n \n GIT_CONF_SUBST([NO_DEFLATE_BOUND])\n+GIT_CONF_SUBST([NO_UNCOMPRESS2])\n \n #\n # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,\n-- \ngitgitgadget\n\n"},{"id":"434096","messageId":"23977331cf801212df96084838df10c83712a8a5.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 06/19] reftable: (de)serialization for the polymorphic record type.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:42Z","receivedAt":"2021-08-30T14:58:14Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of blocks, and each block\ncontains a sequence of prefix-compressed key-value records. There are 4 types of\nrecords, and they have similarities in how they must be handled. This is\nachieved by introducing a polymorphic 'record' type that encapsulates ref, log,\nindex and object records.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |    2 +\n reftable/constants.h       |   21 +\n reftable/record.c          | 1212 ++++++++++++++++++++++++++++++++++++\n reftable/record.h          |  139 +++++\n reftable/record_test.c     |  412 ++++++++++++\n reftable/reftable-record.h |  114 ++++\n t/helper/test-reftable.c   |    2 +-\n 7 files changed, 1901 insertions(+), 1 deletion(-)\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/reftable-record.h\n\ndiff --git a/Makefile b/Makefile\nindex 621ac53d09f..02a83a67467 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2453,7 +2453,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n \ndiff --git a/reftable/constants.h b/reftable/constants.h\nnew file mode 100644\nindex 00000000000..5eee72c4c11\n--- /dev/null\n+++ b/reftable/constants.h\n@@ -0,0 +1,21 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef CONSTANTS_H\n+#define CONSTANTS_H\n+\n+#define BLOCK_TYPE_LOG 'g'\n+#define BLOCK_TYPE_INDEX 'i'\n+#define BLOCK_TYPE_REF 'r'\n+#define BLOCK_TYPE_OBJ 'o'\n+#define BLOCK_TYPE_ANY 0\n+\n+#define MAX_RESTARTS ((1 << 16) - 1)\n+#define DEFAULT_BLOCK_SIZE 4096\n+\n+#endif\ndiff --git a/reftable/record.c b/reftable/record.c\nnew file mode 100644\nindex 00000000000..6a5dac32dc6\n--- /dev/null\n+++ b/reftable/record.c\n@@ -0,0 +1,1212 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+/* record.c - methods for different types of records. */\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"constants.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+\n+int get_var_int(uint64_t *dest, struct string_view *in)\n+{\n+\tint ptr = 0;\n+\tuint64_t val;\n+\n+\tif (in->len == 0)\n+\t\treturn -1;\n+\tval = in->buf[ptr] & 0x7f;\n+\n+\twhile (in->buf[ptr] & 0x80) {\n+\t\tptr++;\n+\t\tif (ptr > in->len) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tval = (val + 1) << 7 | (uint64_t)(in->buf[ptr] & 0x7f);\n+\t}\n+\n+\t*dest = val;\n+\treturn ptr + 1;\n+}\n+\n+int put_var_int(struct string_view *dest, uint64_t val)\n+{\n+\tuint8_t buf[10] = { 0 };\n+\tint i = 9;\n+\tint n = 0;\n+\tbuf[i] = (uint8_t)(val & 0x7f);\n+\ti--;\n+\twhile (1) {\n+\t\tval >>= 7;\n+\t\tif (!val) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tval--;\n+\t\tbuf[i] = 0x80 | (uint8_t)(val & 0x7f);\n+\t\ti--;\n+\t}\n+\n+\tn = sizeof(buf) - i - 1;\n+\tif (dest->len < n)\n+\t\treturn -1;\n+\tmemcpy(dest->buf, &buf[i + 1], n);\n+\treturn n;\n+}\n+\n+int reftable_is_block_type(uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\tcase BLOCK_TYPE_LOG:\n+\tcase BLOCK_TYPE_OBJ:\n+\tcase BLOCK_TYPE_INDEX:\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn rec->value.val1;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.target_value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static int decode_string(struct strbuf *dest, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t tsize = 0;\n+\tint n = get_var_int(&tsize, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\tif (in.len < tsize)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, in.buf, tsize);\n+\tstring_view_consume(&in, tsize);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static int encode_string(char *str, struct string_view s)\n+{\n+\tstruct string_view start = s;\n+\tint l = strlen(str);\n+\tint n = put_var_int(&s, l);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\tif (s.len < l)\n+\t\treturn -1;\n+\tmemcpy(s.buf, str, l);\n+\tstring_view_consume(&s, l);\n+\n+\treturn start.len - s.len;\n+}\n+\n+int reftable_encode_key(int *restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra)\n+{\n+\tstruct string_view start = dest;\n+\tint prefix_len = common_prefix_size(&prev_key, &key);\n+\tuint64_t suffix_len = key.len - prefix_len;\n+\tint n = put_var_int(&dest, (uint64_t)prefix_len);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\t*restart = (prefix_len == 0);\n+\n+\tn = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\tif (dest.len < suffix_len)\n+\t\treturn -1;\n+\tmemcpy(dest.buf, key.buf + prefix_len, suffix_len);\n+\tstring_view_consume(&dest, suffix_len);\n+\n+\treturn start.len - dest.len;\n+}\n+\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t prefix_len = 0;\n+\tuint64_t suffix_len = 0;\n+\tint n = get_var_int(&prefix_len, &in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tif (prefix_len > last_key.len)\n+\t\treturn -1;\n+\n+\tn = get_var_int(&suffix_len, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\t*extra = (uint8_t)(suffix_len & 0x7);\n+\tsuffix_len >>= 3;\n+\n+\tif (in.len < suffix_len)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(key);\n+\tstrbuf_add(key, last_key.buf, prefix_len);\n+\tstrbuf_add(key, in.buf, suffix_len);\n+\tstring_view_consume(&in, suffix_len);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static void reftable_ref_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_ref_record *rec =\n+\t\t(const struct reftable_ref_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, rec->refname);\n+}\n+\n+static void reftable_ref_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_ref_record *ref = rec;\n+\tconst struct reftable_ref_record *src = src_rec;\n+\tassert(hash_size > 0);\n+\n+\t/* This is simple and correct, but we could probably reuse the hash\n+\t * fields. */\n+\treftable_ref_record_release(ref);\n+\tif (src->refname) {\n+\t\tref->refname = xstrdup(src->refname);\n+\t}\n+\tref->update_index = src->update_index;\n+\tref->value_type = src->value_type;\n+\tswitch (src->value_type) {\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tref->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val1, src->value.val1, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tref->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.value, src->value.val2.value, hash_size);\n+\t\tref->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.target_value,\n+\t\t       src->value.val2.target_value, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tref->value.symref = xstrdup(src->value.symref);\n+\t\tbreak;\n+\t}\n+}\n+\n+static char hexdigit(int c)\n+{\n+\tif (c <= 9)\n+\t\treturn '0' + c;\n+\treturn 'a' + (c - 10);\n+}\n+\n+static void hex_format(char *dest, uint8_t *src, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (src) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < hash_size; i++) {\n+\t\t\tdest[2 * i] = hexdigit(src[i] >> 4);\n+\t\t\tdest[2 * i + 1] = hexdigit(src[i] & 0xf);\n+\t\t}\n+\t\tdest[2 * hash_size] = 0;\n+\t}\n+}\n+\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */\n+\tprintf(\"ref{%s(%\" PRIu64 \") \", ref->refname, ref->update_index);\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tprintf(\"=> %s\", ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\thex_format(hex, ref->value.val2.value, hash_size(hash_id));\n+\t\tprintf(\"val 2 %s\", hex);\n+\t\thex_format(hex, ref->value.val2.target_value,\n+\t\t\t   hash_size(hash_id));\n+\t\tprintf(\"(T %s)\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\thex_format(hex, ref->value.val1, hash_size(hash_id));\n+\t\tprintf(\"val 1 %s\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tprintf(\"delete\");\n+\t\tbreak;\n+\t}\n+\tprintf(\"}\\n\");\n+}\n+\n+static void reftable_ref_record_release_void(void *rec)\n+{\n+\treftable_ref_record_release(rec);\n+}\n+\n+void reftable_ref_record_release(struct reftable_ref_record *ref)\n+{\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treftable_free(ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treftable_free(ref->value.val2.target_value);\n+\t\treftable_free(ref->value.val2.value);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\treftable_free(ref->value.val1);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treftable_free(ref->refname);\n+\tmemset(ref, 0, sizeof(struct reftable_ref_record));\n+}\n+\n+static uint8_t reftable_ref_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\treturn r->value_type;\n+}\n+\n+static int reftable_ref_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\tstruct string_view start = s;\n+\tint n = put_var_int(&s, r->update_index);\n+\tassert(hash_size > 0);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tn = encode_string(r->value.symref, s);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (s.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val2.value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tmemcpy(s.buf, r->value.val2.target_value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (s.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val1, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_ref_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct reftable_ref_record *r = rec;\n+\tstruct string_view start = in;\n+\tuint64_t update_index = 0;\n+\tint n = get_var_int(&update_index, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\treftable_ref_record_release(r);\n+\n+\tassert(hash_size > 0);\n+\n+\tr->refname = reftable_realloc(r->refname, key.len + 1);\n+\tmemcpy(r->refname, key.buf, key.len);\n+\tr->update_index = update_index;\n+\tr->refname[key.len] = 0;\n+\tr->value_type = val_type;\n+\tswitch (val_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (in.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val1, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (in.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\n+\t\tr->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.target_value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_SYMREF: {\n+\t\tstruct strbuf dest = STRBUF_INIT;\n+\t\tint n = decode_string(&dest, in);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\t\tr->value.symref = dest.buf;\n+\t} break;\n+\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t\tbreak;\n+\t}\n+\n+\treturn start.len - in.len;\n+}\n+\n+static int reftable_ref_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_ref_record_is_deletion(\n+\t\t(const struct reftable_ref_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_ref_record_vtable = {\n+\t.key = &reftable_ref_record_key,\n+\t.type = BLOCK_TYPE_REF,\n+\t.copy_from = &reftable_ref_record_copy_from,\n+\t.val_type = &reftable_ref_record_val_type,\n+\t.encode = &reftable_ref_record_encode,\n+\t.decode = &reftable_ref_record_decode,\n+\t.release = &reftable_ref_record_release_void,\n+\t.is_deletion = &reftable_ref_record_is_deletion_void,\n+};\n+\n+static void reftable_obj_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_obj_record *rec =\n+\t\t(const struct reftable_obj_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len);\n+}\n+\n+static void reftable_obj_record_release(void *rec)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tFREE_AND_NULL(obj->hash_prefix);\n+\tFREE_AND_NULL(obj->offsets);\n+\tmemset(obj, 0, sizeof(struct reftable_obj_record));\n+}\n+\n+static void reftable_obj_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tconst struct reftable_obj_record *src =\n+\t\t(const struct reftable_obj_record *)src_rec;\n+\n+\treftable_obj_record_release(obj);\n+\t*obj = *src;\n+\tobj->hash_prefix = reftable_malloc(obj->hash_prefix_len);\n+\tmemcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);\n+\n+\tobj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));\n+\tCOPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);\n+}\n+\n+static uint8_t reftable_obj_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tif (r->offset_len > 0 && r->offset_len < 8)\n+\t\treturn r->offset_len;\n+\treturn 0;\n+}\n+\n+static int reftable_obj_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tstruct string_view start = s;\n+\tint i = 0;\n+\tint n = 0;\n+\tuint64_t last = 0;\n+\tif (r->offset_len == 0 || r->offset_len >= 8) {\n+\t\tn = put_var_int(&s, r->offset_len);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t}\n+\tif (r->offset_len == 0)\n+\t\treturn start.len - s.len;\n+\tn = put_var_int(&s, r->offsets[0]);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tlast = r->offsets[0];\n+\tfor (i = 1; i < r->offset_len; i++) {\n+\t\tint n = put_var_int(&s, r->offsets[i] - last);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tlast = r->offsets[i];\n+\t}\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_obj_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_obj_record *r = rec;\n+\tuint64_t count = val_type;\n+\tint n = 0;\n+\tuint64_t last;\n+\tint j;\n+\tr->hash_prefix = reftable_malloc(key.len);\n+\tmemcpy(r->hash_prefix, key.buf, key.len);\n+\tr->hash_prefix_len = key.len;\n+\n+\tif (val_type == 0) {\n+\t\tn = get_var_int(&count, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\n+\t\tstring_view_consume(&in, n);\n+\t}\n+\n+\tr->offsets = NULL;\n+\tr->offset_len = 0;\n+\tif (count == 0)\n+\t\treturn start.len - in.len;\n+\n+\tr->offsets = reftable_malloc(count * sizeof(uint64_t));\n+\tr->offset_len = count;\n+\n+\tn = get_var_int(&r->offsets[0], &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\tlast = r->offsets[0];\n+\tj = 1;\n+\twhile (j < count) {\n+\t\tuint64_t delta = 0;\n+\t\tint n = get_var_int(&delta, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\n+\t\tlast = r->offsets[j] = (delta + last);\n+\t\tj++;\n+\t}\n+\treturn start.len - in.len;\n+}\n+\n+static int not_a_deletion(const void *p)\n+{\n+\treturn 0;\n+}\n+\n+static struct reftable_record_vtable reftable_obj_record_vtable = {\n+\t.key = &reftable_obj_record_key,\n+\t.type = BLOCK_TYPE_OBJ,\n+\t.copy_from = &reftable_obj_record_copy_from,\n+\t.val_type = &reftable_obj_record_val_type,\n+\t.encode = &reftable_obj_record_encode,\n+\t.decode = &reftable_obj_record_decode,\n+\t.release = &reftable_obj_record_release,\n+\t.is_deletion = not_a_deletion,\n+};\n+\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[GIT_SHA256_RAWSZ + 1] = { 0 };\n+\n+\tswitch (log->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") delete\", log->refname,\n+\t\t       log->update_index);\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") %s <%s> %\" PRIu64 \" %04d\\n\",\n+\t\t       log->refname, log->update_index, log->value.update.name,\n+\t\t       log->value.update.email, log->value.update.time,\n+\t\t       log->value.update.tz_offset);\n+\t\thex_format(hex, log->value.update.old_hash, hash_size(hash_id));\n+\t\tprintf(\"%s => \", hex);\n+\t\thex_format(hex, log->value.update.new_hash, hash_size(hash_id));\n+\t\tprintf(\"%s\\n\\n%s\\n}\\n\", hex, log->value.update.message);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_log_record *rec =\n+\t\t(const struct reftable_log_record *)r;\n+\tint len = strlen(rec->refname);\n+\tuint8_t i64[8];\n+\tuint64_t ts = 0;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, (uint8_t *)rec->refname, len + 1);\n+\n+\tts = (~ts) - rec->update_index;\n+\tput_be64(&i64[0], ts);\n+\tstrbuf_add(dest, i64, sizeof(i64));\n+}\n+\n+static void reftable_log_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_log_record *dst = rec;\n+\tconst struct reftable_log_record *src =\n+\t\t(const struct reftable_log_record *)src_rec;\n+\n+\treftable_log_record_release(dst);\n+\t*dst = *src;\n+\tif (dst->refname) {\n+\t\tdst->refname = xstrdup(dst->refname);\n+\t}\n+\tswitch (dst->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tif (dst->value.update.email) {\n+\t\t\tdst->value.update.email =\n+\t\t\t\txstrdup(dst->value.update.email);\n+\t\t}\n+\t\tif (dst->value.update.name) {\n+\t\t\tdst->value.update.name =\n+\t\t\t\txstrdup(dst->value.update.name);\n+\t\t}\n+\t\tif (dst->value.update.message) {\n+\t\t\tdst->value.update.message =\n+\t\t\t\txstrdup(dst->value.update.message);\n+\t\t}\n+\n+\t\tif (dst->value.update.new_hash) {\n+\t\t\tdst->value.update.new_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.new_hash,\n+\t\t\t       src->value.update.new_hash, hash_size);\n+\t\t}\n+\t\tif (dst->value.update.old_hash) {\n+\t\t\tdst->value.update.old_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.old_hash,\n+\t\t\t       src->value.update.old_hash, hash_size);\n+\t\t}\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_release_void(void *rec)\n+{\n+\tstruct reftable_log_record *r = rec;\n+\treftable_log_record_release(r);\n+}\n+\n+void reftable_log_record_release(struct reftable_log_record *r)\n+{\n+\treftable_free(r->refname);\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treftable_free(r->value.update.new_hash);\n+\t\treftable_free(r->value.update.old_hash);\n+\t\treftable_free(r->value.update.name);\n+\t\treftable_free(r->value.update.email);\n+\t\treftable_free(r->value.update.message);\n+\t\tbreak;\n+\t}\n+\tmemset(r, 0, sizeof(struct reftable_log_record));\n+}\n+\n+static uint8_t reftable_log_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_log_record *log =\n+\t\t(const struct reftable_log_record *)rec;\n+\n+\treturn reftable_log_record_is_deletion(log) ? 0 : 1;\n+}\n+\n+static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 };\n+\n+static int reftable_log_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_log_record *r = rec;\n+\tstruct string_view start = s;\n+\tint n = 0;\n+\tuint8_t *oldh = NULL;\n+\tuint8_t *newh = NULL;\n+\tif (reftable_log_record_is_deletion(r))\n+\t\treturn 0;\n+\n+\toldh = r->value.update.old_hash;\n+\tnewh = r->value.update.new_hash;\n+\tif (!oldh) {\n+\t\toldh = zero;\n+\t}\n+\tif (!newh) {\n+\t\tnewh = zero;\n+\t}\n+\n+\tif (s.len < 2 * hash_size)\n+\t\treturn -1;\n+\n+\tmemcpy(s.buf, oldh, hash_size);\n+\tmemcpy(s.buf + hash_size, newh, hash_size);\n+\tstring_view_consume(&s, 2 * hash_size);\n+\n+\tn = encode_string(r->value.update.name ? r->value.update.name : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = encode_string(r->value.update.email ? r->value.update.email : \"\",\n+\t\t\t  s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = put_var_int(&s, r->value.update.time);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tif (s.len < 2)\n+\t\treturn -1;\n+\n+\tput_be16(s.buf, r->value.update.tz_offset);\n+\tstring_view_consume(&s, 2);\n+\n+\tn = encode_string(\n+\t\tr->value.update.message ? r->value.update.message : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_log_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_log_record *r = rec;\n+\tuint64_t max = 0;\n+\tuint64_t ts = 0;\n+\tstruct strbuf dest = STRBUF_INIT;\n+\tint n;\n+\n+\tif (key.len <= 9 || key.buf[key.len - 9] != 0)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->refname = reftable_realloc(r->refname, key.len - 8);\n+\tmemcpy(r->refname, key.buf, key.len - 8);\n+\tts = get_be64(key.buf + key.len - 8);\n+\n+\tr->update_index = (~max) - ts;\n+\n+\tif (val_type != r->value_type) {\n+\t\tswitch (r->value_type) {\n+\t\tcase REFTABLE_LOG_UPDATE:\n+\t\t\tFREE_AND_NULL(r->value.update.old_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.new_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.message);\n+\t\t\tFREE_AND_NULL(r->value.update.email);\n+\t\t\tFREE_AND_NULL(r->value.update.name);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_LOG_DELETION:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tr->value_type = val_type;\n+\tif (val_type == REFTABLE_LOG_DELETION)\n+\t\treturn 0;\n+\n+\tif (in.len < 2 * hash_size)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->value.update.old_hash =\n+\t\treftable_realloc(r->value.update.old_hash, hash_size);\n+\tr->value.update.new_hash =\n+\t\treftable_realloc(r->value.update.new_hash, hash_size);\n+\n+\tmemcpy(r->value.update.old_hash, in.buf, hash_size);\n+\tmemcpy(r->value.update.new_hash, in.buf + hash_size, hash_size);\n+\n+\tstring_view_consume(&in, 2 * hash_size);\n+\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.name =\n+\t\treftable_realloc(r->value.update.name, dest.len + 1);\n+\tmemcpy(r->value.update.name, dest.buf, dest.len);\n+\tr->value.update.name[dest.len] = 0;\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.email =\n+\t\treftable_realloc(r->value.update.email, dest.len + 1);\n+\tmemcpy(r->value.update.email, dest.buf, dest.len);\n+\tr->value.update.email[dest.len] = 0;\n+\n+\tts = 0;\n+\tn = get_var_int(&ts, &in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\tr->value.update.time = ts;\n+\tif (in.len < 2)\n+\t\tgoto done;\n+\n+\tr->value.update.tz_offset = get_be16(in.buf);\n+\tstring_view_consume(&in, 2);\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.message =\n+\t\treftable_realloc(r->value.update.message, dest.len + 1);\n+\tmemcpy(r->value.update.message, dest.buf, dest.len);\n+\tr->value.update.message[dest.len] = 0;\n+\n+\tstrbuf_release(&dest);\n+\treturn start.len - in.len;\n+\n+done:\n+\tstrbuf_release(&dest);\n+\treturn REFTABLE_FORMAT_ERROR;\n+}\n+\n+static int null_streq(char *a, char *b)\n+{\n+\tchar *empty = \"\";\n+\tif (!a)\n+\t\ta = empty;\n+\n+\tif (!b)\n+\t\tb = empty;\n+\n+\treturn 0 == strcmp(a, b);\n+}\n+\n+static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)\n+{\n+\tif (!a)\n+\t\ta = zero;\n+\n+\tif (!b)\n+\t\tb = zero;\n+\n+\treturn !memcmp(a, b, sz);\n+}\n+\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size)\n+{\n+\tif (!(null_streq(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\treturn 1;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treturn null_streq(a->value.update.name, b->value.update.name) &&\n+\t\t       a->value.update.time == b->value.update.time &&\n+\t\t       a->value.update.tz_offset == b->value.update.tz_offset &&\n+\t\t       null_streq(a->value.update.email,\n+\t\t\t\t  b->value.update.email) &&\n+\t\t       null_streq(a->value.update.message,\n+\t\t\t\t  b->value.update.message) &&\n+\t\t       zero_hash_eq(a->value.update.old_hash,\n+\t\t\t\t    b->value.update.old_hash, hash_size) &&\n+\t\t       zero_hash_eq(a->value.update.new_hash,\n+\t\t\t\t    b->value.update.new_hash, hash_size);\n+\t}\n+\n+\tabort();\n+}\n+\n+static int reftable_log_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_log_record_is_deletion(\n+\t\t(const struct reftable_log_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_log_record_vtable = {\n+\t.key = &reftable_log_record_key,\n+\t.type = BLOCK_TYPE_LOG,\n+\t.copy_from = &reftable_log_record_copy_from,\n+\t.val_type = &reftable_log_record_val_type,\n+\t.encode = &reftable_log_record_encode,\n+\t.decode = &reftable_log_record_decode,\n+\t.release = &reftable_log_record_release_void,\n+\t.is_deletion = &reftable_log_record_is_deletion_void,\n+};\n+\n+struct reftable_record reftable_new_record(uint8_t typ)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF: {\n+\t\tstruct reftable_ref_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_ref_record));\n+\t\treftable_record_from_ref(&rec, r);\n+\t\treturn rec;\n+\t}\n+\n+\tcase BLOCK_TYPE_OBJ: {\n+\t\tstruct reftable_obj_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_obj_record));\n+\t\treftable_record_from_obj(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_LOG: {\n+\t\tstruct reftable_log_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_log_record));\n+\t\treftable_record_from_log(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_INDEX: {\n+\t\tstruct reftable_index_record empty = { .last_key =\n+\t\t\t\t\t\t\t       STRBUF_INIT };\n+\t\tstruct reftable_index_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_index_record));\n+\t\t*r = empty;\n+\t\treftable_record_from_index(&rec, r);\n+\t\treturn rec;\n+\t}\n+\t}\n+\tabort();\n+\treturn rec;\n+}\n+\n+/* clear out the record, yielding the reftable_record data that was\n+ * encapsulated. */\n+static void *reftable_record_yield(struct reftable_record *rec)\n+{\n+\tvoid *p = rec->data;\n+\trec->data = NULL;\n+\treturn p;\n+}\n+\n+void reftable_record_destroy(struct reftable_record *rec)\n+{\n+\treftable_record_release(rec);\n+\treftable_free(reftable_record_yield(rec));\n+}\n+\n+static void reftable_index_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_index_record *rec = r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addbuf(dest, &rec->last_key);\n+}\n+\n+static void reftable_index_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t    int hash_size)\n+{\n+\tstruct reftable_index_record *dst = rec;\n+\tconst struct reftable_index_record *src = src_rec;\n+\n+\tstrbuf_reset(&dst->last_key);\n+\tstrbuf_addbuf(&dst->last_key, &src->last_key);\n+\tdst->offset = src->offset;\n+}\n+\n+static void reftable_index_record_release(void *rec)\n+{\n+\tstruct reftable_index_record *idx = rec;\n+\tstrbuf_release(&idx->last_key);\n+}\n+\n+static uint8_t reftable_index_record_val_type(const void *rec)\n+{\n+\treturn 0;\n+}\n+\n+static int reftable_index_record_encode(const void *rec, struct string_view out,\n+\t\t\t\t\tint hash_size)\n+{\n+\tconst struct reftable_index_record *r =\n+\t\t(const struct reftable_index_record *)rec;\n+\tstruct string_view start = out;\n+\n+\tint n = put_var_int(&out, r->offset);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&out, n);\n+\n+\treturn start.len - out.len;\n+}\n+\n+static int reftable_index_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t\tuint8_t val_type, struct string_view in,\n+\t\t\t\t\tint hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_index_record *r = rec;\n+\tint n = 0;\n+\n+\tstrbuf_reset(&r->last_key);\n+\tstrbuf_addbuf(&r->last_key, &key);\n+\n+\tn = get_var_int(&r->offset, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&in, n);\n+\treturn start.len - in.len;\n+}\n+\n+static struct reftable_record_vtable reftable_index_record_vtable = {\n+\t.key = &reftable_index_record_key,\n+\t.type = BLOCK_TYPE_INDEX,\n+\t.copy_from = &reftable_index_record_copy_from,\n+\t.val_type = &reftable_index_record_val_type,\n+\t.encode = &reftable_index_record_encode,\n+\t.decode = &reftable_index_record_decode,\n+\t.release = &reftable_index_record_release,\n+\t.is_deletion = &not_a_deletion,\n+};\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)\n+{\n+\trec->ops->key(rec->data, dest);\n+}\n+\n+uint8_t reftable_record_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->type;\n+}\n+\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size)\n+{\n+\treturn rec->ops->encode(rec->data, dest, hash_size);\n+}\n+\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size)\n+{\n+\tassert(src->ops->type == rec->ops->type);\n+\n+\trec->ops->copy_from(rec->data, src->data, hash_size);\n+}\n+\n+uint8_t reftable_record_val_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->val_type(rec->data);\n+}\n+\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src, int hash_size)\n+{\n+\treturn rec->ops->decode(rec->data, key, extra, src, hash_size);\n+}\n+\n+void reftable_record_release(struct reftable_record *rec)\n+{\n+\trec->ops->release(rec->data);\n+}\n+\n+int reftable_record_is_deletion(struct reftable_record *rec)\n+{\n+\treturn rec->ops->is_deletion(rec->data);\n+}\n+\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *ref_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = ref_rec;\n+\trec->ops = &reftable_ref_record_vtable;\n+}\n+\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *obj_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = obj_rec;\n+\trec->ops = &reftable_obj_record_vtable;\n+}\n+\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *index_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = index_rec;\n+\trec->ops = &reftable_index_record_vtable;\n+}\n+\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *log_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = log_rec;\n+\trec->ops = &reftable_log_record_vtable;\n+}\n+\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_REF);\n+\treturn rec->data;\n+}\n+\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_LOG);\n+\treturn rec->data;\n+}\n+\n+static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)\n+{\n+\tif (a && b)\n+\t\treturn !memcmp(a, b, hash_size);\n+\n+\treturn a == b;\n+}\n+\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (!(0 == strcmp(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treturn !strcmp(a->value.symref, b->value.symref);\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn hash_equal(a->value.val2.value, b->value.val2.value,\n+\t\t\t\t  hash_size) &&\n+\t\t       hash_equal(a->value.val2.target_value,\n+\t\t\t\t  b->value.val2.target_value, hash_size);\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn hash_equal(a->value.val1, b->value.val1, hash_size);\n+\tcase REFTABLE_REF_DELETION:\n+\t\treturn 1;\n+\tdefault:\n+\t\tabort();\n+\t}\n+}\n+\n+int reftable_ref_record_compare_name(const void *a, const void *b)\n+{\n+\treturn strcmp(((struct reftable_ref_record *)a)->refname,\n+\t\t      ((struct reftable_ref_record *)b)->refname);\n+}\n+\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref)\n+{\n+\treturn ref->value_type == REFTABLE_REF_DELETION;\n+}\n+\n+int reftable_log_record_compare_key(const void *a, const void *b)\n+{\n+\tconst struct reftable_log_record *la = a;\n+\tconst struct reftable_log_record *lb = b;\n+\n+\tint cmp = strcmp(la->refname, lb->refname);\n+\tif (cmp)\n+\t\treturn cmp;\n+\tif (la->update_index > lb->update_index)\n+\t\treturn -1;\n+\treturn (la->update_index < lb->update_index) ? 1 : 0;\n+}\n+\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log)\n+{\n+\treturn (log->value_type == REFTABLE_LOG_DELETION);\n+}\n+\n+void string_view_consume(struct string_view *s, int n)\n+{\n+\ts->buf += n;\n+\ts->len -= n;\n+}\ndiff --git a/reftable/record.h b/reftable/record.h\nnew file mode 100644\nindex 00000000000..498e8c50bf4\n--- /dev/null\n+++ b/reftable/record.h\n@@ -0,0 +1,139 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef RECORD_H\n+#define RECORD_H\n+\n+#include \"system.h\"\n+\n+#include <stdint.h>\n+\n+#include \"reftable-record.h\"\n+\n+/*\n+ * A substring of existing string data. This structure takes no responsibility\n+ * for the lifetime of the data it points to.\n+ */\n+struct string_view {\n+\tuint8_t *buf;\n+\tsize_t len;\n+};\n+\n+/* Advance `s.buf` by `n`, and decrease length. */\n+void string_view_consume(struct string_view *s, int n);\n+\n+/* utilities for de/encoding varints */\n+\n+int get_var_int(uint64_t *dest, struct string_view *in);\n+int put_var_int(struct string_view *dest, uint64_t val);\n+\n+/* Methods for records. */\n+struct reftable_record_vtable {\n+\t/* encode the key of to a uint8_t strbuf. */\n+\tvoid (*key)(const void *rec, struct strbuf *dest);\n+\n+\t/* The record type of ('r' for ref). */\n+\tuint8_t type;\n+\n+\tvoid (*copy_from)(void *dest, const void *src, int hash_size);\n+\n+\t/* a value of [0..7], indicating record subvariants (eg. ref vs. symref\n+\t * vs ref deletion) */\n+\tuint8_t (*val_type)(const void *rec);\n+\n+\t/* encodes rec into dest, returning how much space was used. */\n+\tint (*encode)(const void *rec, struct string_view dest, int hash_size);\n+\n+\t/* decode data from `src` into the record. */\n+\tint (*decode)(void *rec, struct strbuf key, uint8_t extra,\n+\t\t      struct string_view src, int hash_size);\n+\n+\t/* deallocate and null the record. */\n+\tvoid (*release)(void *rec);\n+\n+\t/* is this a tombstone? */\n+\tint (*is_deletion)(const void *rec);\n+};\n+\n+/* record is a generic wrapper for different types of records. */\n+struct reftable_record {\n+\tvoid *data;\n+\tstruct reftable_record_vtable *ops;\n+};\n+\n+/* returns true for recognized block types. Block start with the block type. */\n+int reftable_is_block_type(uint8_t typ);\n+\n+/* creates a malloced record of the given type. Dispose with record_destroy */\n+struct reftable_record reftable_new_record(uint8_t typ);\n+\n+/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns\n+ * number of bytes written. */\n+int reftable_encode_key(int *is_restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra);\n+\n+/* Decode into `key` and `extra` from `in` */\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in);\n+\n+/* reftable_index_record are used internally to speed up lookups. */\n+struct reftable_index_record {\n+\tuint64_t offset; /* Offset of block */\n+\tstruct strbuf last_key; /* Last key of the block. */\n+};\n+\n+/* reftable_obj_record stores an object ID => ref mapping. */\n+struct reftable_obj_record {\n+\tuint8_t *hash_prefix; /* leading bytes of the object ID */\n+\tint hash_prefix_len; /* number of leading bytes. Constant\n+\t\t\t      * across a single table. */\n+\tuint64_t *offsets; /* a vector of file offsets. */\n+\tint offset_len;\n+};\n+\n+/* see struct record_vtable */\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);\n+uint8_t reftable_record_type(struct reftable_record *rec);\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size);\n+uint8_t reftable_record_val_type(struct reftable_record *rec);\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size);\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src,\n+\t\t\t   int hash_size);\n+int reftable_record_is_deletion(struct reftable_record *rec);\n+\n+/* zeroes out the embedded record */\n+void reftable_record_release(struct reftable_record *rec);\n+\n+/* clear and deallocate embedded record, and zero `rec`. */\n+void reftable_record_destroy(struct reftable_record *rec);\n+\n+/* initialize generic records from concrete records. The generic record should\n+ * be zeroed out. */\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *objrec);\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *idxrec);\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *refrec);\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *logrec);\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);\n+\n+/* for qsort. */\n+int reftable_ref_record_compare_name(const void *a, const void *b);\n+\n+/* for qsort. */\n+int reftable_log_record_compare_key(const void *a, const void *b);\n+\n+#endif\ndiff --git a/reftable/record_test.c b/reftable/record_test.c\nnew file mode 100644\nindex 00000000000..f4ad7cace41\n--- /dev/null\n+++ b/reftable/record_test.c\n@@ -0,0 +1,412 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_copy(struct reftable_record *rec)\n+{\n+\tstruct reftable_record copy =\n+\t\treftable_new_record(reftable_record_type(rec));\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\t/* do it twice to catch memory leaks */\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\tswitch (reftable_record_type(&copy)) {\n+\tcase BLOCK_TYPE_REF:\n+\t\tEXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),\n+\t\t\t\t\t\t reftable_record_as_ref(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\tcase BLOCK_TYPE_LOG:\n+\t\tEXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),\n+\t\t\t\t\t\t reftable_record_as_log(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\t}\n+\treftable_record_destroy(&copy);\n+}\n+\n+static void test_varint_roundtrip(void)\n+{\n+\tuint64_t inputs[] = { 0,\n+\t\t\t      1,\n+\t\t\t      27,\n+\t\t\t      127,\n+\t\t\t      128,\n+\t\t\t      257,\n+\t\t\t      4096,\n+\t\t\t      ((uint64_t)1 << 63),\n+\t\t\t      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(inputs); i++) {\n+\t\tuint8_t dest[10];\n+\n+\t\tstruct string_view out = {\n+\t\t\t.buf = dest,\n+\t\t\t.len = sizeof(dest),\n+\t\t};\n+\t\tuint64_t in = inputs[i];\n+\t\tint n = put_var_int(&out, in);\n+\t\tuint64_t got = 0;\n+\n+\t\tEXPECT(n > 0);\n+\t\tout.len = n;\n+\t\tn = get_var_int(&got, &out);\n+\t\tEXPECT(n > 0);\n+\n+\t\tEXPECT(got == in);\n+\t}\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct {\n+\t\tconst char *a, *b;\n+\t\tint want;\n+\t} cases[] = {\n+\t\t{ \"abc\", \"ab\", 2 },\n+\t\t{ \"\", \"abc\", 0 },\n+\t\t{ \"abc\", \"abd\", 2 },\n+\t\t{ \"abc\", \"pqr\", 0 },\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\tEXPECT(common_prefix_size(&a, &b) == cases[i].want);\n+\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n+}\n+\n+static void set_hash(uint8_t *h, int j)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < hash_size(GIT_SHA1_FORMAT_ID); i++) {\n+\t\th[i] = (j >> i) & 0xff;\n+\t}\n+}\n+\n+static void test_reftable_ref_record_roundtrip(void)\n+{\n+\tint i = 0;\n+\n+\tfor (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {\n+\t\tstruct reftable_ref_record in = { NULL };\n+\t\tstruct reftable_ref_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\n+\t\tint n, m;\n+\n+\t\tin.value_type = i;\n+\t\tswitch (i) {\n+\t\tcase REFTABLE_REF_DELETION:\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL1:\n+\t\t\tin.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val1, 1);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL2:\n+\t\t\tin.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.value, 1);\n+\t\t\tin.value.val2.target_value =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.target_value, 2);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_SYMREF:\n+\t\t\tin.value.symref = xstrdup(\"target\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tin.refname = xstrdup(\"refs/heads/master\");\n+\n+\t\treftable_record_from_ref(&rec, &in);\n+\t\ttest_copy(&rec);\n+\n+\t\tEXPECT(reftable_record_val_type(&rec) == i);\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\n+\t\t/* decode into a non-zero reftable_record to test for leaks. */\n+\n+\t\treftable_record_from_ref(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, i, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));\n+\t\treftable_record_release(&rec_out);\n+\n+\t\tstrbuf_release(&key);\n+\t\treftable_ref_record_release(&in);\n+\t}\n+}\n+\n+static void test_reftable_log_record_equal(void)\n+{\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t}\n+\t};\n+\n+\tEXPECT(!reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\tin[1].update_index = in[0].update_index;\n+\tEXPECT(reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\treftable_log_record_release(&in[0]);\n+\treftable_log_record_release(&in[1]);\n+}\n+\n+static void test_reftable_log_record_roundtrip(void)\n+{\n+\tint i;\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.old_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.new_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"han-wen\"),\n+\t\t\t\t\t.email = xstrdup(\"hanwen@google.com\"),\n+\t\t\t\t\t.message = xstrdup(\"test\"),\n+\t\t\t\t\t.time = 1577123507,\n+\t\t\t\t\t.tz_offset = 100,\n+\t\t\t\t},\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t}\n+\t};\n+\tset_test_hash(in[0].value.update.new_hash, 1);\n+\tset_test_hash(in[0].value.update.old_hash, 2);\n+\tfor (i = 0; i < ARRAY_SIZE(in); i++) {\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\t/* populate out, to check for leaks. */\n+\t\tstruct reftable_log_record out = {\n+\t\t\t.refname = xstrdup(\"old name\"),\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"old name\"),\n+\t\t\t\t\t.email = xstrdup(\"old@email\"),\n+\t\t\t\t\t.message = xstrdup(\"old message\"),\n+\t\t\t\t},\n+\t\t\t},\n+\t\t};\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m, valtype;\n+\n+\t\treftable_record_from_log(&rec, &in[i]);\n+\n+\t\ttest_copy(&rec);\n+\n+\t\treftable_record_key(&rec, &key);\n+\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n >= 0);\n+\t\treftable_record_from_log(&rec_out, &out);\n+\t\tvaltype = reftable_record_val_type(&rec);\n+\t\tm = reftable_record_decode(&rec_out, key, valtype, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&in[i]);\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_u24_roundtrip(void)\n+{\n+\tuint32_t in = 0x112233;\n+\tuint8_t dest[3];\n+\tuint32_t out;\n+\tput_be24(dest, in);\n+\tout = get_be24(dest);\n+\tEXPECT(in == out);\n+}\n+\n+static void test_key_roundtrip(void)\n+{\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct strbuf roundtrip = STRBUF_INIT;\n+\tint restart;\n+\tuint8_t extra;\n+\tint n, m;\n+\tuint8_t rt_extra;\n+\n+\tstrbuf_addstr(&last_key, \"refs/heads/master\");\n+\tstrbuf_addstr(&key, \"refs/tags/bla\");\n+\textra = 6;\n+\tn = reftable_encode_key(&restart, dest, last_key, key, extra);\n+\tEXPECT(!restart);\n+\tEXPECT(n > 0);\n+\n+\tm = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);\n+\tEXPECT(n == m);\n+\tEXPECT(0 == strbuf_cmp(&key, &roundtrip));\n+\tEXPECT(rt_extra == extra);\n+\n+\tstrbuf_release(&last_key);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&roundtrip);\n+}\n+\n+static void test_reftable_obj_record_roundtrip(void)\n+{\n+\tuint8_t testHash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 4, 0 };\n+\tuint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };\n+\tstruct reftable_obj_record recs[3] = { {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 3,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 9,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t       } };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(recs); i++) {\n+\t\tstruct reftable_obj_record in = recs[i];\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_obj_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m;\n+\t\tuint8_t extra;\n+\n+\t\treftable_record_from_obj(&rec, &in);\n+\t\ttest_copy(&rec);\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\t\textra = reftable_record_val_type(&rec);\n+\t\treftable_record_from_obj(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, extra, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(in.hash_prefix_len == out.hash_prefix_len);\n+\t\tEXPECT(in.offset_len == out.offset_len);\n+\n+\t\tEXPECT(!memcmp(in.hash_prefix, out.hash_prefix,\n+\t\t\t       in.hash_prefix_len));\n+\t\tEXPECT(0 == memcmp(in.offsets, out.offsets,\n+\t\t\t\t   sizeof(uint64_t) * in.offset_len));\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_reftable_index_record_roundtrip(void)\n+{\n+\tstruct reftable_index_record in = {\n+\t\t.offset = 42,\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_index_record out = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record out_rec = { NULL };\n+\tint n, m;\n+\tuint8_t extra;\n+\n+\tstrbuf_addstr(&in.last_key, \"refs/heads/master\");\n+\treftable_record_from_index(&rec, &in);\n+\treftable_record_key(&rec, &key);\n+\ttest_copy(&rec);\n+\n+\tEXPECT(0 == strbuf_cmp(&key, &in.last_key));\n+\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(n > 0);\n+\n+\textra = reftable_record_val_type(&rec);\n+\treftable_record_from_index(&out_rec, &out);\n+\tm = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(m == n);\n+\n+\tEXPECT(in.offset == out.offset);\n+\n+\treftable_record_release(&out_rec);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&in.last_key);\n+}\n+\n+int record_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_reftable_log_record_equal);\n+\tRUN_TEST(test_reftable_log_record_roundtrip);\n+\tRUN_TEST(test_reftable_ref_record_roundtrip);\n+\tRUN_TEST(test_varint_roundtrip);\n+\tRUN_TEST(test_key_roundtrip);\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_reftable_obj_record_roundtrip);\n+\tRUN_TEST(test_reftable_index_record_roundtrip);\n+\tRUN_TEST(test_u24_roundtrip);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-record.h b/reftable/reftable-record.h\nnew file mode 100644\nindex 00000000000..5370d2288c7\n--- /dev/null\n+++ b/reftable/reftable-record.h\n@@ -0,0 +1,114 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_RECORD_H\n+#define REFTABLE_RECORD_H\n+\n+#include <stdint.h>\n+\n+/*\n+ * Basic data types\n+ *\n+ * Reftables store the state of each ref in struct reftable_ref_record, and they\n+ * store a sequence of reflog updates in struct reftable_log_record.\n+ */\n+\n+/* reftable_ref_record holds a ref database entry target_value */\n+struct reftable_ref_record {\n+\tchar *refname; /* Name of the ref, malloced. */\n+\tuint64_t update_index; /* Logical timestamp at which this value is\n+\t\t\t\t* written */\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_REF_DELETION = 0x0,\n+\n+\t\t/* a simple ref */\n+\t\tREFTABLE_REF_VAL1 = 0x1,\n+\t\t/* a tag, plus its peeled hash */\n+\t\tREFTABLE_REF_VAL2 = 0x2,\n+\n+\t\t/* a symbolic reference */\n+\t\tREFTABLE_REF_SYMREF = 0x3,\n+#define REFTABLE_NR_REF_VALUETYPES 4\n+\t} value_type;\n+\tunion {\n+\t\tuint8_t *val1; /* malloced hash. */\n+\t\tstruct {\n+\t\t\tuint8_t *value; /* first value, malloced hash  */\n+\t\t\tuint8_t *target_value; /* second value, malloced hash */\n+\t\t} val2;\n+\t\tchar *symref; /* referent, malloced 0-terminated string */\n+\t} value;\n+};\n+\n+/* Returns the first hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);\n+\n+/* Returns the second hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);\n+\n+/* returns whether 'ref' represents a deletion */\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);\n+\n+/* prints a reftable_ref_record onto stdout. Useful for debugging. */\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id);\n+\n+/* frees and nulls all pointer values inside `ref`. */\n+void reftable_ref_record_release(struct reftable_ref_record *ref);\n+\n+/* returns whether two reftable_ref_records are the same. Useful for testing. */\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size);\n+\n+/* reftable_log_record holds a reflog entry */\n+struct reftable_log_record {\n+\tchar *refname;\n+\tuint64_t update_index; /* logical timestamp of a transactional update.\n+\t\t\t\t*/\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_LOG_DELETION = 0x0,\n+\n+\t\t/* a simple update */\n+\t\tREFTABLE_LOG_UPDATE = 0x1,\n+#define REFTABLE_NR_LOG_VALUETYPES 2\n+\t} value_type;\n+\n+\tunion {\n+\t\tstruct {\n+\t\t\tuint8_t *new_hash;\n+\t\t\tuint8_t *old_hash;\n+\t\t\tchar *name;\n+\t\t\tchar *email;\n+\t\t\tuint64_t time;\n+\t\t\tint16_t tz_offset;\n+\t\t\tchar *message;\n+\t\t} update;\n+\t} value;\n+};\n+\n+/* returns whether 'ref' represents the deletion of a log record. */\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log);\n+\n+/* frees and nulls all pointer values. */\n+void reftable_log_record_release(struct reftable_log_record *log);\n+\n+/* returns whether two records are equal. Useful for testing. */\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size);\n+\n+/* dumps a reftable_log_record on stdout, for debugging/testing. */\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 3b58e423e7b..09d4b83ef9b 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,6 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n-\n+\trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"434098","messageId":"49a9a00465dc65463eec44089f9b571a2145901d.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 09/19] reftable: a generic binary tree implementation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:45Z","receivedAt":"2021-08-30T14:58:20Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format includes support for an (OID => ref) map. This map can speed\nup visibility and reachability checks. In particular, various operations along\nthe fetch/push path within Gerrit have ben sped up by using this structure.\n\nThe map is constructed with help of a binary tree. Object IDs are hashes, so\nthey are uniformly distributed. Hence, the tree does not attempt forced\nrebalancing.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |  4 ++-\n reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++\n reftable/tree.h          | 34 ++++++++++++++++++++++\n reftable/tree_test.c     | 61 ++++++++++++++++++++++++++++++++++++++\n t/helper/test-reftable.c |  1 +\n 5 files changed, 162 insertions(+), 1 deletion(-)\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 65bd39ecdc0..2e5a9f40ed1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,11 +2462,13 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/tree.o\n \n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n-REFTABLE_TEST_OBJS += reftable/basics_test.o\n+REFTABLE_TEST_OBJS += reftable/tree_test.o\n \n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \ndiff --git a/reftable/tree.c b/reftable/tree.c\nnew file mode 100644\nindex 00000000000..82db7995dd6\n--- /dev/null\n+++ b/reftable/tree.c\n@@ -0,0 +1,63 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert)\n+{\n+\tint res;\n+\tif (*rootp == NULL) {\n+\t\tif (!insert) {\n+\t\t\treturn NULL;\n+\t\t} else {\n+\t\t\tstruct tree_node *n =\n+\t\t\t\treftable_calloc(sizeof(struct tree_node));\n+\t\t\tn->key = key;\n+\t\t\t*rootp = n;\n+\t\t\treturn *rootp;\n+\t\t}\n+\t}\n+\n+\tres = compare(key, (*rootp)->key);\n+\tif (res < 0)\n+\t\treturn tree_search(key, &(*rootp)->left, compare, insert);\n+\telse if (res > 0)\n+\t\treturn tree_search(key, &(*rootp)->right, compare, insert);\n+\treturn *rootp;\n+}\n+\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg)\n+{\n+\tif (t->left) {\n+\t\tinfix_walk(t->left, action, arg);\n+\t}\n+\taction(arg, t->key);\n+\tif (t->right) {\n+\t\tinfix_walk(t->right, action, arg);\n+\t}\n+}\n+\n+void tree_free(struct tree_node *t)\n+{\n+\tif (t == NULL) {\n+\t\treturn;\n+\t}\n+\tif (t->left) {\n+\t\ttree_free(t->left);\n+\t}\n+\tif (t->right) {\n+\t\ttree_free(t->right);\n+\t}\n+\treftable_free(t);\n+}\ndiff --git a/reftable/tree.h b/reftable/tree.h\nnew file mode 100644\nindex 00000000000..fbdd002e23a\n--- /dev/null\n+++ b/reftable/tree.h\n@@ -0,0 +1,34 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TREE_H\n+#define TREE_H\n+\n+/* tree_node is a generic binary search tree. */\n+struct tree_node {\n+\tvoid *key;\n+\tstruct tree_node *left, *right;\n+};\n+\n+/* looks for `key` in `rootp` using `compare` as comparison function. If insert\n+ * is set, insert the key if it's not found. Else, return NULL.\n+ */\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert);\n+\n+/* performs an infix walk of the tree. */\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg);\n+\n+/*\n+ * deallocates the tree nodes recursively. Keys should be deallocated separately\n+ * by walking over the tree. */\n+void tree_free(struct tree_node *t);\n+\n+#endif\ndiff --git a/reftable/tree_test.c b/reftable/tree_test.c\nnew file mode 100644\nindex 00000000000..09a970e17b9\n--- /dev/null\n+++ b/reftable/tree_test.c\n@@ -0,0 +1,61 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static int test_compare(const void *a, const void *b)\n+{\n+\treturn (char *)a - (char *)b;\n+}\n+\n+struct curry {\n+\tvoid *last;\n+};\n+\n+static void check_increasing(void *arg, void *key)\n+{\n+\tstruct curry *c = arg;\n+\tif (c->last) {\n+\t\tassert(test_compare(c->last, key) < 0);\n+\t}\n+\tc->last = key;\n+}\n+\n+static void test_tree(void)\n+{\n+\tstruct tree_node *root = NULL;\n+\n+\tvoid *values[11] = { NULL };\n+\tstruct tree_node *nodes[11] = { NULL };\n+\tint i = 1;\n+\tstruct curry c = { NULL };\n+\tdo {\n+\t\tnodes[i] = tree_search(values + i, &root, &test_compare, 1);\n+\t\ti = (i * 7) % 11;\n+\t} while (i != 1);\n+\n+\tfor (i = 1; i < ARRAY_SIZE(nodes); i++) {\n+\t\tassert(values + i == nodes[i]->key);\n+\t\tassert(nodes[i] ==\n+\t\t       tree_search(values + i, &root, &test_compare, 0));\n+\t}\n+\n+\tinfix_walk(root, check_increasing, &c);\n+\ttree_free(root);\n+}\n+\n+int tree_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_tree);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c9deeaf08c7..050551fa698 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"434099","messageId":"2648e05c51061474c89541d126ba145e95d6b092.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 08/19] reftable: reading/writing blocks","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:44Z","receivedAt":"2021-08-30T14:58:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of block. Within a block,\nrecords are prefix compressed, with an index of offsets for fully expand keys to\nenable binary search within blocks.\n\nThis commit provides the logic to read and write these blocks.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   2 +\n reftable/block.c         | 448 +++++++++++++++++++++++++++++++++++++++\n reftable/block.h         | 127 +++++++++++\n reftable/block_test.c    | 120 +++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 698 insertions(+)\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n\ndiff --git a/Makefile b/Makefile\nindex ec89320080b..65bd39ecdc0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2458,10 +2458,12 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\ndiff --git a/reftable/block.c b/reftable/block.c\nnew file mode 100644\nindex 00000000000..eb5268dd3a6\n--- /dev/null\n+++ b/reftable/block.c\n@@ -0,0 +1,448 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+#include <zlib.h>\n+\n+#ifdef NO_UNCOMPRESS2\n+/*\n+ * This is uncompress2, which is only available in zlib >= 1.2.9\n+ * (released as of early 2017)\n+ */\n+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n+\t\tuLong *sourceLen);\n+#endif\n+\n+int header_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 24;\n+\tcase 2:\n+\t\treturn 28;\n+\t}\n+\tabort();\n+}\n+\n+int footer_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 68;\n+\tcase 2:\n+\t\treturn 72;\n+\t}\n+\tabort();\n+}\n+\n+static int block_writer_register_restart(struct block_writer *w, int n,\n+\t\t\t\t\t int is_restart, struct strbuf *key)\n+{\n+\tint rlen = w->restart_len;\n+\tif (rlen >= MAX_RESTARTS) {\n+\t\tis_restart = 0;\n+\t}\n+\n+\tif (is_restart) {\n+\t\trlen++;\n+\t}\n+\tif (2 + 3 * rlen + n > w->block_size - w->next)\n+\t\treturn -1;\n+\tif (is_restart) {\n+\t\tif (w->restart_len == w->restart_cap) {\n+\t\t\tw->restart_cap = w->restart_cap * 2 + 1;\n+\t\t\tw->restarts = reftable_realloc(\n+\t\t\t\tw->restarts, sizeof(uint32_t) * w->restart_cap);\n+\t\t}\n+\n+\t\tw->restarts[w->restart_len++] = w->next;\n+\t}\n+\n+\tw->next += n;\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, key);\n+\tw->entries++;\n+\treturn 0;\n+}\n+\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size)\n+{\n+\tbw->buf = buf;\n+\tbw->hash_size = hash_size;\n+\tbw->block_size = block_size;\n+\tbw->header_off = header_off;\n+\tbw->buf[header_off] = typ;\n+\tbw->next = header_off + 4;\n+\tbw->restart_interval = 16;\n+\tbw->entries = 0;\n+\tbw->restart_len = 0;\n+\tbw->last_key.len = 0;\n+}\n+\n+uint8_t block_writer_type(struct block_writer *bw)\n+{\n+\treturn bw->buf[bw->header_off];\n+}\n+\n+/* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on\n+   success */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tstruct strbuf last =\n+\t\tw->entries % w->restart_interval == 0 ? empty : w->last_key;\n+\tstruct string_view out = {\n+\t\t.buf = w->buf + w->next,\n+\t\t.len = w->block_size - w->next,\n+\t};\n+\n+\tstruct string_view start = out;\n+\n+\tint is_restart = 0;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint n = 0;\n+\n+\treftable_record_key(rec, &key);\n+\tn = reftable_encode_key(&is_restart, out, last, key,\n+\t\t\t\treftable_record_val_type(rec));\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tn = reftable_record_encode(rec, out, w->hash_size);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tif (block_writer_register_restart(w, start.len - out.len, is_restart,\n+\t\t\t\t\t  &key) < 0)\n+\t\tgoto done;\n+\n+\tstrbuf_release(&key);\n+\treturn 0;\n+\n+done:\n+\tstrbuf_release(&key);\n+\treturn -1;\n+}\n+\n+int block_writer_finish(struct block_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->restart_len; i++) {\n+\t\tput_be24(w->buf + w->next, w->restarts[i]);\n+\t\tw->next += 3;\n+\t}\n+\n+\tput_be16(w->buf + w->next, w->restart_len);\n+\tw->next += 2;\n+\tput_be24(w->buf + 1 + w->header_off, w->next);\n+\n+\tif (block_writer_type(w) == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + w->header_off;\n+\t\tuint8_t *compressed = NULL;\n+\t\tint zresult = 0;\n+\t\tuLongf src_len = w->next - block_header_skip;\n+\t\tsize_t dest_cap = src_len;\n+\n+\t\tcompressed = reftable_malloc(dest_cap);\n+\t\twhile (1) {\n+\t\t\tuLongf out_dest_len = dest_cap;\n+\n+\t\t\tzresult = compress2(compressed, &out_dest_len,\n+\t\t\t\t\t    w->buf + block_header_skip, src_len,\n+\t\t\t\t\t    9);\n+\t\t\tif (zresult == Z_BUF_ERROR) {\n+\t\t\t\tdest_cap *= 2;\n+\t\t\t\tcompressed =\n+\t\t\t\t\treftable_realloc(compressed, dest_cap);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (Z_OK != zresult) {\n+\t\t\t\treftable_free(compressed);\n+\t\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t\t}\n+\n+\t\t\tmemcpy(w->buf + block_header_skip, compressed,\n+\t\t\t       out_dest_len);\n+\t\t\tw->next = out_dest_len + block_header_skip;\n+\t\t\treftable_free(compressed);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn w->next;\n+}\n+\n+uint8_t block_reader_type(struct block_reader *r)\n+{\n+\treturn r->block.data[r->header_off];\n+}\n+\n+int block_reader_init(struct block_reader *br, struct reftable_block *block,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size)\n+{\n+\tuint32_t full_block_size = table_block_size;\n+\tuint8_t typ = block->data[header_off];\n+\tuint32_t sz = get_be24(block->data + header_off + 1);\n+\n+\tuint16_t restart_count = 0;\n+\tuint32_t restart_start = 0;\n+\tuint8_t *restart_bytes = NULL;\n+\n+\tif (!reftable_is_block_type(typ))\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tif (typ == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + header_off;\n+\t\tuLongf dst_len = sz - block_header_skip; /* total size of dest\n+\t\t\t\t\t\t\t    buffer. */\n+\t\tuLongf src_len = block->len - block_header_skip;\n+\t\t/* Log blocks specify the *uncompressed* size in their header.\n+\t\t */\n+\t\tuint8_t *uncompressed = reftable_malloc(sz);\n+\n+\t\t/* Copy over the block header verbatim. It's not compressed. */\n+\t\tmemcpy(uncompressed, block->data, block_header_skip);\n+\n+\t\t/* Uncompress */\n+\t\tif (Z_OK !=\n+\t\t    uncompress2(uncompressed + block_header_skip, &dst_len,\n+\t\t\t\tblock->data + block_header_skip, &src_len)) {\n+\t\t\treftable_free(uncompressed);\n+\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t}\n+\n+\t\tif (dst_len + block_header_skip != sz)\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\t\t/* We're done with the input data. */\n+\t\treftable_block_done(block);\n+\t\tblock->data = uncompressed;\n+\t\tblock->len = sz;\n+\t\tblock->source = malloc_block_source();\n+\t\tfull_block_size = src_len + block_header_skip;\n+\t} else if (full_block_size == 0) {\n+\t\tfull_block_size = sz;\n+\t} else if (sz < full_block_size && sz < block->len &&\n+\t\t   block->data[sz] != 0) {\n+\t\t/* If the block is smaller than the full block size, it is\n+\t\t   padded (data followed by '\\0') or the next block is\n+\t\t   unaligned. */\n+\t\tfull_block_size = sz;\n+\t}\n+\n+\trestart_count = get_be16(block->data + sz - 2);\n+\trestart_start = sz - 2 - 3 * restart_count;\n+\trestart_bytes = block->data + restart_start;\n+\n+\t/* transfer ownership. */\n+\tbr->block = *block;\n+\tblock->data = NULL;\n+\tblock->len = 0;\n+\n+\tbr->hash_size = hash_size;\n+\tbr->block_len = restart_start;\n+\tbr->full_block_size = full_block_size;\n+\tbr->header_off = header_off;\n+\tbr->restart_count = restart_count;\n+\tbr->restart_bytes = restart_bytes;\n+\n+\treturn 0;\n+}\n+\n+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)\n+{\n+\treturn get_be24(br->restart_bytes + 3 * i);\n+}\n+\n+void block_reader_start(struct block_reader *br, struct block_iter *it)\n+{\n+\tit->br = br;\n+\tstrbuf_reset(&it->last_key);\n+\tit->next_off = br->header_off + 4;\n+}\n+\n+struct restart_find_args {\n+\tint error;\n+\tstruct strbuf key;\n+\tstruct block_reader *r;\n+};\n+\n+static int restart_key_less(size_t idx, void *args)\n+{\n+\tstruct restart_find_args *a = args;\n+\tuint32_t off = block_reader_restart_offset(a->r, idx);\n+\tstruct string_view in = {\n+\t\t.buf = a->r->block.data + off,\n+\t\t.len = a->r->block_len - off,\n+\t};\n+\n+\t/* the restart key is verbatim in the block, so this could avoid the\n+\t   alloc for decoding the key */\n+\tstruct strbuf rkey = STRBUF_INIT;\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tuint8_t unused_extra;\n+\tint n = reftable_decode_key(&rkey, &unused_extra, last_key, in);\n+\tint result;\n+\tif (n < 0) {\n+\t\ta->error = 1;\n+\t\treturn -1;\n+\t}\n+\n+\tresult = strbuf_cmp(&a->key, &rkey);\n+\tstrbuf_release(&rkey);\n+\treturn result;\n+}\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)\n+{\n+\tdest->br = src->br;\n+\tdest->next_off = src->next_off;\n+\tstrbuf_reset(&dest->last_key);\n+\tstrbuf_addbuf(&dest->last_key, &src->last_key);\n+}\n+\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec)\n+{\n+\tstruct string_view in = {\n+\t\t.buf = it->br->block.data + it->next_off,\n+\t\t.len = it->br->block_len - it->next_off,\n+\t};\n+\tstruct string_view start = in;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tuint8_t extra = 0;\n+\tint n = 0;\n+\n+\tif (it->next_off >= it->br->block_len)\n+\t\treturn 1;\n+\n+\tn = reftable_decode_key(&key, &extra, it->last_key, in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\n+\tstring_view_consume(&in, n);\n+\tn = reftable_record_decode(rec, key, extra, in, it->br->hash_size);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tstrbuf_reset(&it->last_key);\n+\tstrbuf_addbuf(&it->last_key, &key);\n+\tit->next_off += start.len - in.len;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tint off = br->header_off + 4;\n+\tstruct string_view in = {\n+\t\t.buf = br->block.data + off,\n+\t\t.len = br->block_len - off,\n+\t};\n+\n+\tuint8_t extra = 0;\n+\tint n = reftable_decode_key(key, &extra, empty, in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\treturn 0;\n+}\n+\n+int block_iter_seek(struct block_iter *it, struct strbuf *want)\n+{\n+\treturn block_reader_seek(it->br, it, want);\n+}\n+\n+void block_iter_close(struct block_iter *it)\n+{\n+\tstrbuf_release(&it->last_key);\n+}\n+\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want)\n+{\n+\tstruct restart_find_args args = {\n+\t\t.key = *want,\n+\t\t.r = br,\n+\t};\n+\tstruct reftable_record rec = reftable_new_record(block_reader_type(br));\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = 0;\n+\tstruct block_iter next = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\n+\tint i = binsearch(br->restart_count, &restart_key_less, &args);\n+\tif (args.error) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tit->br = br;\n+\tif (i > 0) {\n+\t\ti--;\n+\t\tit->next_off = block_reader_restart_offset(br, i);\n+\t} else {\n+\t\tit->next_off = br->header_off + 4;\n+\t}\n+\n+\t/* We're looking for the last entry less/equal than the wanted key, so\n+\t   we have to go one entry too far and then back up.\n+\t*/\n+\twhile (1) {\n+\t\tblock_iter_copy_from(&next, it);\n+\t\terr = block_iter_next(&next, &rec);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tif (err > 0 || strbuf_cmp(&key, want) >= 0) {\n+\t\t\terr = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tblock_iter_copy_from(it, &next);\n+\t}\n+\n+done:\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&next.last_key);\n+\treftable_record_destroy(&rec);\n+\n+\treturn err;\n+}\n+\n+void block_writer_release(struct block_writer *bw)\n+{\n+\tFREE_AND_NULL(bw->restarts);\n+\tstrbuf_release(&bw->last_key);\n+\t/* the block is not owned. */\n+}\n+\n+void reftable_block_done(struct reftable_block *blockp)\n+{\n+\tstruct reftable_block_source source = blockp->source;\n+\tif (blockp && source.ops)\n+\t\tsource.ops->return_block(source.arg, blockp);\n+\tblockp->data = NULL;\n+\tblockp->len = 0;\n+\tblockp->source.ops = NULL;\n+\tblockp->source.arg = NULL;\n+}\ndiff --git a/reftable/block.h b/reftable/block.h\nnew file mode 100644\nindex 00000000000..e207706a644\n--- /dev/null\n+++ b/reftable/block.h\n@@ -0,0 +1,127 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCK_H\n+#define BLOCK_H\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Writes reftable blocks. The block_writer is reused across blocks to minimize\n+ * allocation overhead.\n+ */\n+struct block_writer {\n+\tuint8_t *buf;\n+\tuint32_t block_size;\n+\n+\t/* Offset ofof the global header. Nonzero in the first block only. */\n+\tuint32_t header_off;\n+\n+\t/* How often to restart keys. */\n+\tint restart_interval;\n+\tint hash_size;\n+\n+\t/* Offset of next uint8_t to write. */\n+\tuint32_t next;\n+\tuint32_t *restarts;\n+\tuint32_t restart_len;\n+\tuint32_t restart_cap;\n+\n+\tstruct strbuf last_key;\n+\tint entries;\n+};\n+\n+/*\n+ * initializes the blockwriter to write `typ` entries, using `buf` as temporary\n+ * storage. `buf` is not owned by the block_writer. */\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size);\n+\n+/* returns the block type (eg. 'r' for ref records. */\n+uint8_t block_writer_type(struct block_writer *bw);\n+\n+/* appends the record, or -1 if it doesn't fit. */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec);\n+\n+/* appends the key restarts, and compress the block if necessary. */\n+int block_writer_finish(struct block_writer *w);\n+\n+/* clears out internally allocated block_writer members. */\n+void block_writer_release(struct block_writer *bw);\n+\n+/* Read a block. */\n+struct block_reader {\n+\t/* offset of the block header; nonzero for the first block in a\n+\t * reftable. */\n+\tuint32_t header_off;\n+\n+\t/* the memory block */\n+\tstruct reftable_block block;\n+\tint hash_size;\n+\n+\t/* size of the data, excluding restart data. */\n+\tuint32_t block_len;\n+\tuint8_t *restart_bytes;\n+\tuint16_t restart_count;\n+\n+\t/* size of the data in the file. For log blocks, this is the compressed\n+\t * size. */\n+\tuint32_t full_block_size;\n+};\n+\n+/* Iterate over entries in a block */\n+struct block_iter {\n+\t/* offset within the block of the next entry to read. */\n+\tuint32_t next_off;\n+\tstruct block_reader *br;\n+\n+\t/* key for last entry we read. */\n+\tstruct strbuf last_key;\n+};\n+\n+/* initializes a block reader. */\n+int block_reader_init(struct block_reader *br, struct reftable_block *bl,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size);\n+\n+/* Position `it` at start of the block */\n+void block_reader_start(struct block_reader *br, struct block_iter *it);\n+\n+/* Position `it` to the `want` key in the block */\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want);\n+\n+/* Returns the block type (eg. 'r' for refs) */\n+uint8_t block_reader_type(struct block_reader *r);\n+\n+/* Decodes the first key in the block */\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key);\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);\n+\n+/* return < 0 for error, 0 for OK, > 0 for EOF. */\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec);\n+\n+/* Seek to `want` with in the block pointed to by `it` */\n+int block_iter_seek(struct block_iter *it, struct strbuf *want);\n+\n+/* deallocate memory for `it`. The block reader and its block is left intact. */\n+void block_iter_close(struct block_iter *it);\n+\n+/* size of file header, depending on format version */\n+int header_size(int version);\n+\n+/* size of file footer, depending on format version */\n+int footer_size(int version);\n+\n+/* returns a block to its source. */\n+void reftable_block_done(struct reftable_block *ret);\n+\n+#endif\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nnew file mode 100644\nindex 00000000000..4b3ea262dcb\n--- /dev/null\n+++ b/reftable/block_test.c\n@@ -0,0 +1,120 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"system.h\"\n+#include \"blocksource.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_block_read_write(void)\n+{\n+\tconst int header_off = 21; /* random */\n+\tchar *names[30];\n+\tconst int N = ARRAY_SIZE(names);\n+\tconst int block_size = 1024;\n+\tstruct reftable_block block = { NULL };\n+\tstruct block_writer bw = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_record rec = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\tint j = 0;\n+\tstruct strbuf want = STRBUF_INIT;\n+\n+\tblock.data = reftable_calloc(block_size);\n+\tblock.len = block_size;\n+\tblock.source = malloc_block_source();\n+\tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n+\t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n+\treftable_record_from_ref(&rec, &ref);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tsnprintf(name, sizeof(name), \"branch%02d\", i);\n+\t\tmemset(hash, i, sizeof(hash));\n+\n+\t\tref.refname = name;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\n+\t\tnames[i] = xstrdup(name);\n+\t\tn = block_writer_add(&bw, &rec);\n+\t\tref.refname = NULL;\n+\t\tref.value_type = REFTABLE_REF_DELETION;\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = block_writer_finish(&bw);\n+\tEXPECT(n > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);\n+\n+\tblock_reader_start(&br, &it);\n+\n+\twhile (1) {\n+\t\tint r = block_iter_next(&it, &rec);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT_STREQ(names[j], ref.refname);\n+\t\tj++;\n+\t}\n+\n+\treftable_record_release(&rec);\n+\tblock_iter_close(&it);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\t\tstrbuf_reset(&want);\n+\t\tstrbuf_addstr(&want, names[i]);\n+\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\n+\t\tEXPECT_STREQ(names[i], ref.refname);\n+\n+\t\twant.len--;\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT_STREQ(names[10 * (i / 10)], ref.refname);\n+\n+\t\tblock_iter_close(&it);\n+\t}\n+\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\tstrbuf_release(&want);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+}\n+\n+int block_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_block_read_write);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 09d4b83ef9b..c9deeaf08c7 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,7 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n+\tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"434100","messageId":"0f32d6f4c91b2717877ea3f5fa33d7661cb2db7b.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 11/19] reftable: generic interface to tables","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:47Z","receivedAt":"2021-08-30T14:58:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                     |   3 +\n reftable/generic.c           | 169 +++++++++++++++++++++++++++++++++++\n reftable/generic.h           |  32 +++++++\n reftable/reftable-generic.h  |  47 ++++++++++\n reftable/reftable-iterator.h |  39 ++++++++\n reftable/reftable.c          | 115 ++++++++++++++++++++++++\n 6 files changed, 405 insertions(+)\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable.c\n\ndiff --git a/Makefile b/Makefile\nindex 05be43355a6..18093366acb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,6 +2462,9 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/refname.o\n+REFTABLE_OBJS += reftable/generic.o\n+REFTABLE_OBJS += reftable/stack.o\n REFTABLE_OBJS += reftable/tree.o\n REFTABLE_OBJS += reftable/writer.o\n \ndiff --git a/reftable/generic.c b/reftable/generic.c\nnew file mode 100644\nindex 00000000000..7a8a738d860\n--- /dev/null\n+++ b/reftable/generic.c\n@@ -0,0 +1,169 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = ~((uint64_t)0),\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+int reftable_table_print(struct reftable_table *tab) {\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\tuint32_t hash_id = reftable_table_hash_id(tab);\n+\tint err = reftable_table_seek_ref(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_ref_record_print(&ref, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_table_seek_log(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_log_record_print(&log, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_log_record_release(&log);\n+\treturn 0;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\ndiff --git a/reftable/generic.h b/reftable/generic.h\nnew file mode 100644\nindex 00000000000..98886a06402\n--- /dev/null\n+++ b/reftable/generic.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef GENERIC_H\n+#define GENERIC_H\n+\n+#include \"record.h\"\n+#include \"reftable-generic.h\"\n+\n+/* generic interface to reftables */\n+struct reftable_table_vtable {\n+\tint (*seek_record)(void *tab, struct reftable_iterator *it,\n+\t\t\t   struct reftable_record *);\n+\tuint32_t (*hash_id)(void *tab);\n+\tuint64_t (*min_update_index)(void *tab);\n+\tuint64_t (*max_update_index)(void *tab);\n+};\n+\n+struct reftable_iterator_vtable {\n+\tint (*next)(void *iter_arg, struct reftable_record *rec);\n+\tvoid (*close)(void *iter_arg);\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it);\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec);\n+\n+#endif\ndiff --git a/reftable/reftable-generic.h b/reftable/reftable-generic.h\nnew file mode 100644\nindex 00000000000..d239751a778\n--- /dev/null\n+++ b/reftable/reftable-generic.h\n@@ -0,0 +1,47 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_GENERIC_H\n+#define REFTABLE_GENERIC_H\n+\n+#include \"reftable-iterator.h\"\n+\n+struct reftable_table_vtable;\n+\n+/*\n+ * Provides a unified API for reading tables, either merged tables, or single\n+ * readers. */\n+struct reftable_table {\n+\tstruct reftable_table_vtable *ops;\n+\tvoid *table_arg;\n+};\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID from a generic reftable_table */\n+uint32_t reftable_table_hash_id(struct reftable_table *tab);\n+\n+/* returns the max update_index covered by this table. */\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab);\n+\n+/* returns the min update_index covered by this table. */\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0\n+   for success, and 1 if ref not found. */\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* dump table contents onto stdout for debugging */\n+int reftable_table_print(struct reftable_table *tab);\n+\n+#endif\ndiff --git a/reftable/reftable-iterator.h b/reftable/reftable-iterator.h\nnew file mode 100644\nindex 00000000000..d3eee7af357\n--- /dev/null\n+++ b/reftable/reftable-iterator.h\n@@ -0,0 +1,39 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ITERATOR_H\n+#define REFTABLE_ITERATOR_H\n+\n+#include \"reftable-record.h\"\n+\n+struct reftable_iterator_vtable;\n+\n+/* iterator is the generic interface for walking over data stored in a\n+ * reftable.\n+ */\n+struct reftable_iterator {\n+\tstruct reftable_iterator_vtable *ops;\n+\tvoid *iter_arg;\n+};\n+\n+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref);\n+\n+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log);\n+\n+/* releases resources associated with an iterator. */\n+void reftable_iterator_destroy(struct reftable_iterator *it);\n+\n+#endif\ndiff --git a/reftable/reftable.c b/reftable/reftable.c\nnew file mode 100644\nindex 00000000000..0e4607a7cd6\n--- /dev/null\n+++ b/reftable/reftable.c\n@@ -0,0 +1,115 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\n-- \ngitgitgadget\n\n"},{"id":"434101","messageId":"7ee46496081bfeef4c953e8839539616f50fecbb.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 14/19] reftable: add a heap-based priority queue for reftable records","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:50Z","receivedAt":"2021-08-30T14:58:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis is needed to create a merged view multiple reftables\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   2 +\n reftable/pq.c             | 115 ++++++++++++++++++++++++++++++++++++++\n reftable/pq.h             |  32 +++++++++++\n reftable/pq_test.c        |  72 ++++++++++++++++++++++++\n reftable/reftable-tests.h |   1 +\n t/helper/test-reftable.c  |   1 +\n 6 files changed, 223 insertions(+)\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 4466aa99765..e80f253ecc3 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,6 +2462,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n@@ -2472,6 +2473,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/pq.c b/reftable/pq.c\nnew file mode 100644\nindex 00000000000..8918d158e2d\n--- /dev/null\n+++ b/reftable/pq.c\n@@ -0,0 +1,115 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"pq.h\"\n+\n+#include \"reftable-record.h\"\n+#include \"system.h\"\n+#include \"basics.h\"\n+\n+static int pq_less(struct pq_entry a, struct pq_entry b)\n+{\n+\tstruct strbuf ak = STRBUF_INIT;\n+\tstruct strbuf bk = STRBUF_INIT;\n+\tint cmp = 0;\n+\treftable_record_key(&a.rec, &ak);\n+\treftable_record_key(&b.rec, &bk);\n+\n+\tcmp = strbuf_cmp(&ak, &bk);\n+\n+\tstrbuf_release(&ak);\n+\tstrbuf_release(&bk);\n+\n+\tif (cmp == 0)\n+\t\treturn a.index > b.index;\n+\n+\treturn cmp < 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.heap[0];\n+}\n+\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.len == 0;\n+}\n+\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n+{\n+\tint i = 0;\n+\tfor (i = 1; i < pq.len; i++) {\n+\t\tint parent = (i - 1) / 2;\n+\n+\t\tassert(pq_less(pq.heap[parent], pq.heap[i]));\n+\t}\n+}\n+\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tstruct pq_entry e = pq->heap[0];\n+\tpq->heap[0] = pq->heap[pq->len - 1];\n+\tpq->len--;\n+\n+\ti = 0;\n+\twhile (i < pq->len) {\n+\t\tint min = i;\n+\t\tint j = 2 * i + 1;\n+\t\tint k = 2 * i + 2;\n+\t\tif (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {\n+\t\t\tmin = j;\n+\t\t}\n+\t\tif (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {\n+\t\t\tmin = k;\n+\t\t}\n+\n+\t\tif (min == i) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[i], pq->heap[min]);\n+\t\ti = min;\n+\t}\n+\n+\treturn e;\n+}\n+\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)\n+{\n+\tint i = 0;\n+\tif (pq->len == pq->cap) {\n+\t\tpq->cap = 2 * pq->cap + 1;\n+\t\tpq->heap = reftable_realloc(pq->heap,\n+\t\t\t\t\t    pq->cap * sizeof(struct pq_entry));\n+\t}\n+\n+\tpq->heap[pq->len++] = e;\n+\ti = pq->len - 1;\n+\twhile (i > 0) {\n+\t\tint j = (i - 1) / 2;\n+\t\tif (pq_less(pq->heap[j], pq->heap[i])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[j], pq->heap[i]);\n+\n+\t\ti = j;\n+\t}\n+}\n+\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < pq->len; i++) {\n+\t\treftable_record_destroy(&pq->heap[i].rec);\n+\t}\n+\tFREE_AND_NULL(pq->heap);\n+\tpq->len = pq->cap = 0;\n+}\ndiff --git a/reftable/pq.h b/reftable/pq.h\nnew file mode 100644\nindex 00000000000..385d2fb139a\n--- /dev/null\n+++ b/reftable/pq.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef PQ_H\n+#define PQ_H\n+\n+#include \"record.h\"\n+\n+struct pq_entry {\n+\tint index;\n+\tstruct reftable_record rec;\n+};\n+\n+struct merged_iter_pqueue {\n+\tstruct pq_entry *heap;\n+\tsize_t len;\n+\tsize_t cap;\n+};\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);\n+\n+#endif\ndiff --git a/reftable/pq_test.c b/reftable/pq_test.c\nnew file mode 100644\nindex 00000000000..ad21673e854\n--- /dev/null\n+++ b/reftable/pq_test.c\n@@ -0,0 +1,72 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"pq.h\"\n+#include \"record.h\"\n+#include \"reftable-tests.h\"\n+#include \"test_framework.h\"\n+\n+static void test_pq(void)\n+{\n+\tchar *names[54] = { NULL };\n+\tint N = ARRAY_SIZE(names) - 1;\n+\n+\tstruct merged_iter_pqueue pq = { NULL };\n+\tconst char *last = NULL;\n+\n+\tint i = 0;\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tsnprintf(name, sizeof(name), \"%02d\", i);\n+\t\tnames[i] = xstrdup(name);\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct reftable_record rec =\n+\t\t\treftable_new_record(BLOCK_TYPE_REF);\n+\t\tstruct pq_entry e = { 0 };\n+\n+\t\treftable_record_as_ref(&rec)->refname = names[i];\n+\t\te.rec = rec;\n+\t\tmerged_iter_pqueue_add(&pq, e);\n+\t\tmerged_iter_pqueue_check(pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\twhile (!merged_iter_pqueue_is_empty(pq)) {\n+\t\tstruct pq_entry e = merged_iter_pqueue_remove(&pq);\n+\t\tstruct reftable_ref_record *ref =\n+\t\t\treftable_record_as_ref(&e.rec);\n+\n+\t\tmerged_iter_pqueue_check(pq);\n+\n+\t\tif (last) {\n+\t\t\tassert(strcmp(last, ref->refname) < 0);\n+\t\t}\n+\t\tlast = ref->refname;\n+\t\tref->refname = NULL;\n+\t\treftable_free(ref);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\n+\tmerged_iter_pqueue_release(&pq);\n+}\n+\n+int pq_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_pq);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 3d541fa5c0c..0019cbcfa49 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -12,6 +12,7 @@ https://developers.google.com/open-source/licenses/bsd\n int basics_test_main(int argc, const char **argv);\n int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n+int pq_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n int readwrite_test_main(int argc, const char **argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 898aba836fd..0b5a1701df1 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"434102","messageId":"c00876afd263f4a0c5b4ebefa9bafe47afd8a6b9.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 10/19] reftable: write reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:46Z","receivedAt":"2021-08-30T14:58:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   1 +\n reftable/reftable-writer.h | 148 ++++++++\n reftable/writer.c          | 690 +++++++++++++++++++++++++++++++++++++\n reftable/writer.h          |  50 +++\n 4 files changed, 889 insertions(+)\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n\ndiff --git a/Makefile b/Makefile\nindex 2e5a9f40ed1..05be43355a6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2463,6 +2463,7 @@ REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/tree.o\n+REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\ndiff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h\nnew file mode 100644\nindex 00000000000..af36462ced5\n--- /dev/null\n+++ b/reftable/reftable-writer.h\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_WRITER_H\n+#define REFTABLE_WRITER_H\n+\n+#include \"reftable-record.h\"\n+\n+#include <stdint.h>\n+#include <unistd.h> /* ssize_t */\n+\n+/* Writing single reftables */\n+\n+/* reftable_write_options sets options for writing a single reftable. */\n+struct reftable_write_options {\n+\t/* boolean: do not pad out blocks to block size. */\n+\tunsigned unpadded : 1;\n+\n+\t/* the blocksize. Should be less than 2^24. */\n+\tuint32_t block_size;\n+\n+\t/* boolean: do not generate a SHA1 => ref index. */\n+\tunsigned skip_index_objects : 1;\n+\n+\t/* how often to write complete keys in each block. */\n+\tint restart_interval;\n+\n+\t/* 4-byte identifier (\"sha1\", \"s256\") of the hash.\n+\t * Defaults to SHA1 if unset\n+\t */\n+\tuint32_t hash_id;\n+\n+\t/* boolean: do not check ref names for validity or dir/file conflicts.\n+\t */\n+\tunsigned skip_name_check : 1;\n+\n+\t/* boolean: copy log messages exactly. If unset, check that the message\n+\t *   is a single line, and add '\\n' if missing.\n+\t */\n+\tunsigned exact_log_message : 1;\n+};\n+\n+/* reftable_block_stats holds statistics for a single block type */\n+struct reftable_block_stats {\n+\t/* total number of entries written */\n+\tint entries;\n+\t/* total number of key restarts */\n+\tint restarts;\n+\t/* total number of blocks */\n+\tint blocks;\n+\t/* total number of index blocks */\n+\tint index_blocks;\n+\t/* depth of the index */\n+\tint max_index_level;\n+\n+\t/* offset of the first block for this type */\n+\tuint64_t offset;\n+\t/* offset of the top level index block for this type, or 0 if not\n+\t * present */\n+\tuint64_t index_offset;\n+};\n+\n+/* stats holds overall statistics for a single reftable */\n+struct reftable_stats {\n+\t/* total number of blocks written. */\n+\tint blocks;\n+\t/* stats for ref data */\n+\tstruct reftable_block_stats ref_stats;\n+\t/* stats for the SHA1 to ref map. */\n+\tstruct reftable_block_stats obj_stats;\n+\t/* stats for index blocks */\n+\tstruct reftable_block_stats idx_stats;\n+\t/* stats for log blocks */\n+\tstruct reftable_block_stats log_stats;\n+\n+\t/* disambiguation length of shortened object IDs. */\n+\tint object_id_len;\n+};\n+\n+/* reftable_new_writer creates a new writer */\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts);\n+\n+/* Set the range of update indices for the records we will add. When writing a\n+   table into a stack, the min should be at least\n+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.\n+\n+   For transactional updates to a stack, typically min==max, and the\n+   update_index can be obtained by inspeciting the stack. When converting an\n+   existing ref database into a single reftable, this would be a range of\n+   update-index timestamps.\n+ */\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max);\n+\n+/*\n+  Add a reftable_ref_record. The record should have names that come after\n+  already added records.\n+\n+  The update_index must be within the limits set by\n+  reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an\n+  REFTABLE_API_ERROR error to write a ref record after a log record.\n+*/\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/*\n+  Convenience function to add multiple reftable_ref_records; the function sorts\n+  the records before adding them, reordering the records array passed in.\n+*/\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n);\n+\n+/*\n+  adds reftable_log_records. Log records are keyed by (refname, decreasing\n+  update_index). The key for the record added must come after the already added\n+  log records.\n+*/\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/*\n+  Convenience function to add multiple reftable_log_records; the function sorts\n+  the records before adding them, reordering records array passed in.\n+*/\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n);\n+\n+/* reftable_writer_close finalizes the reftable. The writer is retained so\n+ * statistics can be inspected. */\n+int reftable_writer_close(struct reftable_writer *w);\n+\n+/* writer_stats returns the statistics on the reftable being written.\n+\n+   This struct becomes invalid when the writer is freed.\n+ */\n+const struct reftable_stats *writer_stats(struct reftable_writer *w);\n+\n+/* reftable_writer_free deallocates memory for the writer */\n+void reftable_writer_free(struct reftable_writer *w);\n+\n+#endif\ndiff --git a/reftable/writer.c b/reftable/writer.c\nnew file mode 100644\nindex 00000000000..3ca721e9f64\n--- /dev/null\n+++ b/reftable/writer.c\n@@ -0,0 +1,690 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"writer.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"tree.h\"\n+#include \"reftable-error.h\"\n+\n+/* finishes a block, and writes it to storage */\n+static int writer_flush_block(struct reftable_writer *w);\n+\n+/* deallocates memory related to the index */\n+static void writer_clear_index(struct reftable_writer *w);\n+\n+/* finishes writing a 'r' (refs) or 'g' (reflogs) section */\n+static int writer_finish_public_section(struct reftable_writer *w);\n+\n+static struct reftable_block_stats *\n+writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase 'r':\n+\t\treturn &w->stats.ref_stats;\n+\tcase 'o':\n+\t\treturn &w->stats.obj_stats;\n+\tcase 'i':\n+\t\treturn &w->stats.idx_stats;\n+\tcase 'g':\n+\t\treturn &w->stats.log_stats;\n+\t}\n+\tabort();\n+\treturn NULL;\n+}\n+\n+/* write data, queuing the padding for the next write. Returns negative for\n+ * error. */\n+static int padded_write(struct reftable_writer *w, uint8_t *data, size_t len,\n+\t\t\tint padding)\n+{\n+\tint n = 0;\n+\tif (w->pending_padding > 0) {\n+\t\tuint8_t *zeroed = reftable_calloc(w->pending_padding);\n+\t\tint n = w->write(w->write_arg, zeroed, w->pending_padding);\n+\t\tif (n < 0)\n+\t\t\treturn n;\n+\n+\t\tw->pending_padding = 0;\n+\t\treftable_free(zeroed);\n+\t}\n+\n+\tw->pending_padding = padding;\n+\tn = w->write(w->write_arg, data, len);\n+\tif (n < 0)\n+\t\treturn n;\n+\tn += padding;\n+\treturn 0;\n+}\n+\n+static void options_set_defaults(struct reftable_write_options *opts)\n+{\n+\tif (opts->restart_interval == 0) {\n+\t\topts->restart_interval = 16;\n+\t}\n+\n+\tif (opts->hash_id == 0) {\n+\t\topts->hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\tif (opts->block_size == 0) {\n+\t\topts->block_size = DEFAULT_BLOCK_SIZE;\n+\t}\n+}\n+\n+static int writer_version(struct reftable_writer *w)\n+{\n+\treturn (w->opts.hash_id == 0 || w->opts.hash_id == GIT_SHA1_FORMAT_ID) ?\n+\t\t\t     1 :\n+\t\t\t     2;\n+}\n+\n+static int writer_write_header(struct reftable_writer *w, uint8_t *dest)\n+{\n+\tmemcpy(dest, \"REFT\", 4);\n+\n+\tdest[4] = writer_version(w);\n+\n+\tput_be24(dest + 5, w->opts.block_size);\n+\tput_be64(dest + 8, w->min_update_index);\n+\tput_be64(dest + 16, w->max_update_index);\n+\tif (writer_version(w) == 2) {\n+\t\tput_be32(dest + 24, w->opts.hash_id);\n+\t}\n+\treturn header_size(writer_version(w));\n+}\n+\n+static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)\n+{\n+\tint block_start = 0;\n+\tif (w->next == 0) {\n+\t\tblock_start = header_size(writer_version(w));\n+\t}\n+\n+\tstrbuf_release(&w->last_key);\n+\tblock_writer_init(&w->block_writer_data, typ, w->block,\n+\t\t\t  w->opts.block_size, block_start,\n+\t\t\t  hash_size(w->opts.hash_id));\n+\tw->block_writer = &w->block_writer_data;\n+\tw->block_writer->restart_interval = w->opts.restart_interval;\n+}\n+\n+static struct strbuf reftable_empty_strbuf = STRBUF_INIT;\n+\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts)\n+{\n+\tstruct reftable_writer *wp =\n+\t\treftable_calloc(sizeof(struct reftable_writer));\n+\tstrbuf_init(&wp->block_writer_data.last_key, 0);\n+\toptions_set_defaults(opts);\n+\tif (opts->block_size >= (1 << 24)) {\n+\t\t/* TODO - error return? */\n+\t\tabort();\n+\t}\n+\twp->last_key = reftable_empty_strbuf;\n+\twp->block = reftable_calloc(opts->block_size);\n+\twp->write = writer_func;\n+\twp->write_arg = writer_arg;\n+\twp->opts = *opts;\n+\twriter_reinit_block_writer(wp, BLOCK_TYPE_REF);\n+\n+\treturn wp;\n+}\n+\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max)\n+{\n+\tw->min_update_index = min;\n+\tw->max_update_index = max;\n+}\n+\n+void reftable_writer_free(struct reftable_writer *w)\n+{\n+\treftable_free(w->block);\n+\treftable_free(w);\n+}\n+\n+struct obj_index_tree_node {\n+\tstruct strbuf hash;\n+\tuint64_t *offsets;\n+\tsize_t offset_len;\n+\tsize_t offset_cap;\n+};\n+\n+#define OBJ_INDEX_TREE_NODE_INIT    \\\n+\t{                           \\\n+\t\t.hash = STRBUF_INIT \\\n+\t}\n+\n+static int obj_index_tree_node_compare(const void *a, const void *b)\n+{\n+\treturn strbuf_cmp(&((const struct obj_index_tree_node *)a)->hash,\n+\t\t\t  &((const struct obj_index_tree_node *)b)->hash);\n+}\n+\n+static void writer_index_hash(struct reftable_writer *w, struct strbuf *hash)\n+{\n+\tuint64_t off = w->next;\n+\n+\tstruct obj_index_tree_node want = { .hash = *hash };\n+\n+\tstruct tree_node *node = tree_search(&want, &w->obj_index_tree,\n+\t\t\t\t\t     &obj_index_tree_node_compare, 0);\n+\tstruct obj_index_tree_node *key = NULL;\n+\tif (node == NULL) {\n+\t\tstruct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;\n+\t\tkey = reftable_malloc(sizeof(struct obj_index_tree_node));\n+\t\t*key = empty;\n+\n+\t\tstrbuf_reset(&key->hash);\n+\t\tstrbuf_addbuf(&key->hash, hash);\n+\t\ttree_search((void *)key, &w->obj_index_tree,\n+\t\t\t    &obj_index_tree_node_compare, 1);\n+\t} else {\n+\t\tkey = node->key;\n+\t}\n+\n+\tif (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {\n+\t\treturn;\n+\t}\n+\n+\tif (key->offset_len == key->offset_cap) {\n+\t\tkey->offset_cap = 2 * key->offset_cap + 1;\n+\t\tkey->offsets = reftable_realloc(\n+\t\t\tkey->offsets, sizeof(uint64_t) * key->offset_cap);\n+\t}\n+\n+\tkey->offsets[key->offset_len++] = off;\n+}\n+\n+static int writer_add_record(struct reftable_writer *w,\n+\t\t\t     struct reftable_record *rec)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = -1;\n+\treftable_record_key(rec, &key);\n+\tif (strbuf_cmp(&w->last_key, &key) >= 0) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, &key);\n+\tif (w->block_writer == NULL) {\n+\t\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\t}\n+\n+\tassert(block_writer_type(w->block_writer) == reftable_record_type(rec));\n+\n+\tif (block_writer_add(w->block_writer, rec) == 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\terr = block_writer_add(w->block_writer, rec);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = 0;\n+done:\n+\tstrbuf_release(&key);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_ref_record copy = *ref;\n+\tint err = 0;\n+\n+\tif (ref->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\tif (ref->update_index < w->min_update_index ||\n+\t    ref->update_index > w->max_update_index)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\treftable_record_from_ref(&rec, &copy);\n+\tcopy.update_index -= w->min_update_index;\n+\n+\terr = writer_add_record(w, &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, (char *)reftable_ref_record_val1(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, reftable_ref_record_val2(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(refs, n, reftable_ref_record_compare_name);\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_ref(w, &refs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int reftable_writer_add_log_verbatim(struct reftable_writer *w,\n+\t\t\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tif (w->block_writer &&\n+\t    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {\n+\t\tint err = writer_finish_public_section(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tw->next -= w->pending_padding;\n+\tw->pending_padding = 0;\n+\n+\treftable_record_from_log(&rec, log);\n+\treturn writer_add_record(w, &rec);\n+}\n+\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tchar *input_log_message = NULL;\n+\tstruct strbuf cleaned_message = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (log->value_type == REFTABLE_LOG_DELETION)\n+\t\treturn reftable_writer_add_log_verbatim(w, log);\n+\n+\tif (log->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\tinput_log_message = log->value.update.message;\n+\tif (!w->opts.exact_log_message && log->value.update.message) {\n+\t\tstrbuf_addstr(&cleaned_message, log->value.update.message);\n+\t\twhile (cleaned_message.len &&\n+\t\t       cleaned_message.buf[cleaned_message.len - 1] == '\\n')\n+\t\t\tstrbuf_setlen(&cleaned_message,\n+\t\t\t\t      cleaned_message.len - 1);\n+\t\tif (strchr(cleaned_message.buf, '\\n')) {\n+\t\t\t/* multiple lines not allowed. */\n+\t\t\terr = REFTABLE_API_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tstrbuf_addstr(&cleaned_message, \"\\n\");\n+\t\tlog->value.update.message = cleaned_message.buf;\n+\t}\n+\n+\terr = reftable_writer_add_log_verbatim(w, log);\n+\tlog->value.update.message = input_log_message;\n+done:\n+\tstrbuf_release(&cleaned_message);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(logs, n, reftable_log_record_compare_key);\n+\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_log(w, &logs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int writer_finish_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tuint64_t index_start = 0;\n+\tint max_level = 0;\n+\tint threshold = w->opts.unpadded ? 1 : 3;\n+\tint before_blocks = w->stats.idx_stats.blocks;\n+\tint err = writer_flush_block(w);\n+\tint i = 0;\n+\tstruct reftable_block_stats *bstats = NULL;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\twhile (w->index_len > threshold) {\n+\t\tstruct reftable_index_record *idx = NULL;\n+\t\tint idx_len = 0;\n+\n+\t\tmax_level++;\n+\t\tindex_start = w->next;\n+\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\tidx = w->index;\n+\t\tidx_len = w->index_len;\n+\n+\t\tw->index = NULL;\n+\t\tw->index_len = 0;\n+\t\tw->index_cap = 0;\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstruct reftable_record rec = { NULL };\n+\t\t\treftable_record_from_index(&rec, idx + i);\n+\t\t\tif (block_writer_add(w->block_writer, &rec) == 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\terr = writer_flush_block(w);\n+\t\t\tif (err < 0)\n+\t\t\t\treturn err;\n+\n+\t\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\t\terr = block_writer_add(w->block_writer, &rec);\n+\t\t\tif (err != 0) {\n+\t\t\t\t/* write into fresh block should always succeed\n+\t\t\t\t */\n+\t\t\t\tabort();\n+\t\t\t}\n+\t\t}\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstrbuf_release(&idx[i].last_key);\n+\t\t}\n+\t\treftable_free(idx);\n+\t}\n+\n+\twriter_clear_index(w);\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tbstats = writer_reftable_block_stats(w, typ);\n+\tbstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;\n+\tbstats->index_offset = index_start;\n+\tbstats->max_index_level = max_level;\n+\n+\t/* Reinit lastKey, as the next section can start with any key. */\n+\tw->last_key.len = 0;\n+\n+\treturn 0;\n+}\n+\n+struct common_prefix_arg {\n+\tstruct strbuf *last;\n+\tint max;\n+};\n+\n+static void update_common(void *void_arg, void *key)\n+{\n+\tstruct common_prefix_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tif (arg->last) {\n+\t\tint n = common_prefix_size(&entry->hash, arg->last);\n+\t\tif (n > arg->max) {\n+\t\t\targ->max = n;\n+\t\t}\n+\t}\n+\targ->last = &entry->hash;\n+}\n+\n+struct write_record_arg {\n+\tstruct reftable_writer *w;\n+\tint err;\n+};\n+\n+static void write_object_record(void *void_arg, void *key)\n+{\n+\tstruct write_record_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tstruct reftable_obj_record obj_rec = {\n+\t\t.hash_prefix = (uint8_t *)entry->hash.buf,\n+\t\t.hash_prefix_len = arg->w->stats.object_id_len,\n+\t\t.offsets = entry->offsets,\n+\t\t.offset_len = entry->offset_len,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\treftable_record_from_obj(&rec, &obj_rec);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\n+\targ->err = writer_flush_block(arg->w);\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\twriter_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\tobj_rec.offset_len = 0;\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\n+\t/* Should be able to write into a fresh block. */\n+\tassert(arg->err == 0);\n+\n+done:;\n+}\n+\n+static void object_record_free(void *void_arg, void *key)\n+{\n+\tstruct obj_index_tree_node *entry = key;\n+\n+\tFREE_AND_NULL(entry->offsets);\n+\tstrbuf_release(&entry->hash);\n+\treftable_free(entry);\n+}\n+\n+static int writer_dump_object_index(struct reftable_writer *w)\n+{\n+\tstruct write_record_arg closure = { .w = w };\n+\tstruct common_prefix_arg common = { NULL };\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &update_common, &common);\n+\t}\n+\tw->stats.object_id_len = common.max + 1;\n+\n+\twriter_reinit_block_writer(w, BLOCK_TYPE_OBJ);\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &write_object_record, &closure);\n+\t}\n+\n+\tif (closure.err < 0)\n+\t\treturn closure.err;\n+\treturn writer_finish_section(w);\n+}\n+\n+static int writer_finish_public_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = 0;\n+\tint err = 0;\n+\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\n+\ttyp = block_writer_type(w->block_writer);\n+\terr = writer_finish_section(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\tif (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&\n+\t    w->stats.ref_stats.index_blocks > 0) {\n+\t\terr = writer_dump_object_index(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &object_record_free, NULL);\n+\t\ttree_free(w->obj_index_tree);\n+\t\tw->obj_index_tree = NULL;\n+\t}\n+\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+int reftable_writer_close(struct reftable_writer *w)\n+{\n+\tuint8_t footer[72];\n+\tuint8_t *p = footer;\n+\tint err = writer_finish_public_section(w);\n+\tint empty_table = w->next == 0;\n+\tif (err != 0)\n+\t\tgoto done;\n+\tw->pending_padding = 0;\n+\tif (empty_table) {\n+\t\t/* Empty tables need a header anyway. */\n+\t\tuint8_t header[28];\n+\t\tint n = writer_write_header(w, header);\n+\t\terr = padded_write(w, header, n, 0);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\tp += writer_write_header(w, footer);\n+\tput_be64(p, w->stats.ref_stats.index_offset);\n+\tp += 8;\n+\tput_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);\n+\tp += 8;\n+\tput_be64(p, w->stats.obj_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be64(p, w->stats.log_stats.offset);\n+\tp += 8;\n+\tput_be64(p, w->stats.log_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be32(p, crc32(0, footer, p - footer));\n+\tp += 4;\n+\n+\terr = padded_write(w, footer, footer_size(writer_version(w)), 0);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (empty_table) {\n+\t\terr = REFTABLE_EMPTY_TABLE_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\t/* free up memory. */\n+\tblock_writer_release(&w->block_writer_data);\n+\twriter_clear_index(w);\n+\tstrbuf_release(&w->last_key);\n+\treturn err;\n+}\n+\n+static void writer_clear_index(struct reftable_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->index_len; i++) {\n+\t\tstrbuf_release(&w->index[i].last_key);\n+\t}\n+\n+\tFREE_AND_NULL(w->index);\n+\tw->index_len = 0;\n+\tw->index_cap = 0;\n+}\n+\n+static const int debug = 0;\n+\n+static int writer_flush_nonempty_block(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tstruct reftable_block_stats *bstats =\n+\t\twriter_reftable_block_stats(w, typ);\n+\tuint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;\n+\tint raw_bytes = block_writer_finish(w->block_writer);\n+\tint padding = 0;\n+\tint err = 0;\n+\tstruct reftable_index_record ir = { .last_key = STRBUF_INIT };\n+\tif (raw_bytes < 0)\n+\t\treturn raw_bytes;\n+\n+\tif (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {\n+\t\tpadding = w->opts.block_size - raw_bytes;\n+\t}\n+\n+\tif (block_typ_off > 0) {\n+\t\tbstats->offset = block_typ_off;\n+\t}\n+\n+\tbstats->entries += w->block_writer->entries;\n+\tbstats->restarts += w->block_writer->restart_len;\n+\tbstats->blocks++;\n+\tw->stats.blocks++;\n+\n+\tif (debug) {\n+\t\tfprintf(stderr, \"block %c off %\" PRIu64 \" sz %d (%d)\\n\", typ,\n+\t\t\tw->next, raw_bytes,\n+\t\t\tget_be24(w->block + w->block_writer->header_off + 1));\n+\t}\n+\n+\tif (w->next == 0) {\n+\t\twriter_write_header(w, w->block);\n+\t}\n+\n+\terr = padded_write(w, w->block, raw_bytes, padding);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (w->index_cap == w->index_len) {\n+\t\tw->index_cap = 2 * w->index_cap + 1;\n+\t\tw->index = reftable_realloc(\n+\t\t\tw->index,\n+\t\t\tsizeof(struct reftable_index_record) * w->index_cap);\n+\t}\n+\n+\tir.offset = w->next;\n+\tstrbuf_reset(&ir.last_key);\n+\tstrbuf_addbuf(&ir.last_key, &w->block_writer->last_key);\n+\tw->index[w->index_len] = ir;\n+\n+\tw->index_len++;\n+\tw->next += padding + raw_bytes;\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+static int writer_flush_block(struct reftable_writer *w)\n+{\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\tif (w->block_writer->entries == 0)\n+\t\treturn 0;\n+\treturn writer_flush_nonempty_block(w);\n+}\n+\n+const struct reftable_stats *writer_stats(struct reftable_writer *w)\n+{\n+\treturn &w->stats;\n+}\ndiff --git a/reftable/writer.h b/reftable/writer.h\nnew file mode 100644\nindex 00000000000..09b88673d97\n--- /dev/null\n+++ b/reftable/writer.h\n@@ -0,0 +1,50 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef WRITER_H\n+#define WRITER_H\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"tree.h\"\n+#include \"reftable-writer.h\"\n+\n+struct reftable_writer {\n+\tssize_t (*write)(void *, const void *, size_t);\n+\tvoid *write_arg;\n+\tint pending_padding;\n+\tstruct strbuf last_key;\n+\n+\t/* offset of next block to write. */\n+\tuint64_t next;\n+\tuint64_t min_update_index, max_update_index;\n+\tstruct reftable_write_options opts;\n+\n+\t/* memory buffer for writing */\n+\tuint8_t *block;\n+\n+\t/* writer for the current section. NULL or points to\n+\t * block_writer_data */\n+\tstruct block_writer *block_writer;\n+\n+\tstruct block_writer block_writer_data;\n+\n+\t/* pending index records for the current section */\n+\tstruct reftable_index_record *index;\n+\tsize_t index_len;\n+\tsize_t index_cap;\n+\n+\t/*\n+\t * tree for use with tsearch; used to populate the 'o' inverse OID\n+\t * map */\n+\tstruct tree_node *obj_index_tree;\n+\n+\tstruct reftable_stats stats;\n+};\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"434103","messageId":"4bbefe977ae3dec1920e363a9b2d9700c54e5943.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 13/19] reftable: reftable file level tests","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:49Z","receivedAt":"2021-08-30T14:58:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nWith support for reading and writing files in place, we can construct files (in\nmemory) and attempt to read them back.\n\nBecause some sections of the format are optional (eg. indices, log entries), we\nhave to exercise this code using multiple sizes of input data\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   1 +\n reftable/readwrite_test.c | 652 ++++++++++++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   2 +-\n t/helper/test-reftable.c  |   1 +\n 4 files changed, 655 insertions(+), 1 deletion(-)\n create mode 100644 reftable/readwrite_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 91952268e51..4466aa99765 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2473,6 +2473,7 @@ REFTABLE_OBJS += reftable/writer.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n+REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nnew file mode 100644\nindex 00000000000..5f6bcc2f775\n--- /dev/null\n+++ b/reftable/readwrite_test.c\n@@ -0,0 +1,652 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+\n+static const int update_index = 5;\n+\n+static void test_buffer(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_block out = { NULL };\n+\tint n;\n+\tuint8_t in[] = \"hello\";\n+\tstrbuf_add(&buf, in, sizeof(in));\n+\tblock_source_from_strbuf(&source, &buf);\n+\tEXPECT(block_source_size(&source) == 6);\n+\tn = block_source_read_block(&source, &out, 0, sizeof(in));\n+\tEXPECT(n == sizeof(in));\n+\tEXPECT(!memcmp(in, out.data, n));\n+\treftable_block_done(&out);\n+\n+\tn = block_source_read_block(&source, &out, 1, 2);\n+\tEXPECT(n == 2);\n+\tEXPECT(!memcmp(out.data, \"el\", 2));\n+\n+\treftable_block_done(&out);\n+\tblock_source_close(&source);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void write_table(char ***names, struct strbuf *buf, int N,\n+\t\t\tint block_size, uint32_t hash_id)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = block_size,\n+\t\t.hash_id = hash_id,\n+\t};\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, buf, &opts);\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0, n;\n+\tstruct reftable_log_record log = { NULL };\n+\tconst struct reftable_stats *stats = NULL;\n+\t*names = reftable_calloc(sizeof(char *) * (N + 1));\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tref.refname = name;\n+\t\tref.update_index = update_index;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\t\t(*names)[i] = xstrdup(name);\n+\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tlog.refname = name;\n+\t\tlog.update_index = update_index;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.new_hash = hash;\n+\t\tlog.value.update.message = \"message\";\n+\n+\t\tn = reftable_writer_add_log(w, &log);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tfor (i = 0; i < stats->ref_stats.blocks; i++) {\n+\t\tint off = i * opts.block_size;\n+\t\tif (off == 0) {\n+\t\t\toff = header_size(\n+\t\t\t\t(hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1);\n+\t\t}\n+\t\tEXPECT(buf->buf[off] == 'r');\n+\t}\n+\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+}\n+\n+static void test_log_buffer_size(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 4096,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record\n+\t\tlog = { .refname = \"refs/heads/master\",\n+\t\t\t.update_index = 0xa,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = { .update = {\n+\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .tz_offset = 100,\n+\t\t\t\t\t   .time = 0x5e430672,\n+\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t   } } };\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\t/* This tests buffer extension for log compression. Must use a random\n+\t   hash, to ensure that the compressed part is larger than the original.\n+\t*/\n+\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\tfor (i = 0; i < GIT_SHA1_RAWSZ; i++) {\n+\t\thash1[i] = (uint8_t)(rand() % 256);\n+\t\thash2[i] = (uint8_t)(rand() % 256);\n+\t}\n+\tlog.value.update.old_hash = hash1;\n+\tlog.value.update.new_hash = hash2;\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\terr = reftable_writer_add_log(w, &log);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_log_write_read(void)\n+{\n+\tint N = 2;\n+\tchar **names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint err;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tstruct reftable_log_record log = { NULL };\n+\tint n;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tconst struct reftable_stats *stats = NULL;\n+\treftable_writer_set_limits(w, 0, N);\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[256];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tsnprintf(name, sizeof(name), \"b%02d%0*d\", i, 130, 7);\n+\t\tnames[i] = xstrdup(name);\n+\t\tref.refname = name;\n+\t\tref.update_index = i;\n+\n+\t\terr = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_log_record log = { NULL };\n+\t\tset_test_hash(hash1, i);\n+\t\tset_test_hash(hash2, i + 1);\n+\n+\t\tlog.refname = names[i];\n+\t\tlog.update_index = i;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.old_hash = hash1;\n+\t\tlog.value.update.new_hash = hash2;\n+\n+\t\terr = reftable_writer_add_log(w, &log);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[N - 1]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* end of iteration. */\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT(0 < err);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_reader_seek_log(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\ti = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT_STREQ(names[i], log.refname);\n+\t\tEXPECT(i == log.update_index);\n+\t\ti++;\n+\t\treftable_log_record_release(&log);\n+\t}\n+\n+\tEXPECT(i == N);\n+\treftable_iterator_destroy(&it);\n+\n+\t/* cleanup. */\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_sequential(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err = 0;\n+\tint j = 0;\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint r = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT(0 == strcmp(names[j], ref.refname));\n+\t\tEXPECT(update_index == ref.update_index);\n+\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == N);\n+\treftable_iterator_destroy(&it);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\n+\treader_close(&rd);\n+}\n+\n+static void test_table_write_small_table(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 1;\n+\twrite_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);\n+\tEXPECT(buf.len < 200);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+}\n+\n+static void test_table_read_api(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record log = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[0]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_log(&it, &log);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_free(names);\n+\treader_close(&rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_table_read_write_seek(int index, int hash_id)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i = 0;\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct strbuf pastLast = STRBUF_INIT;\n+\tstruct reftable_ref_record ref = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, hash_id);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tEXPECT(hash_id == reftable_reader_hash_id(&rd));\n+\n+\tif (!index) {\n+\t\trd.ref_offsets.index_offset = 0;\n+\t} else {\n+\t\tEXPECT(rd.ref_offsets.index_offset > 0);\n+\t}\n+\n+\tfor (i = 1; i < N; i++) {\n+\t\tint err = reftable_reader_seek_ref(&rd, &it, names[i]);\n+\t\tEXPECT_ERR(err);\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(0 == strcmp(names[i], ref.refname));\n+\t\tEXPECT(REFTABLE_REF_VAL1 == ref.value_type);\n+\t\tEXPECT(i == ref.value.val1[0]);\n+\n+\t\treftable_ref_record_release(&ref);\n+\t\treftable_iterator_destroy(&it);\n+\t}\n+\n+\tstrbuf_addstr(&pastLast, names[N - 1]);\n+\tstrbuf_addstr(&pastLast, \"/\");\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, pastLast.buf);\n+\tif (err == 0) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err > 0);\n+\t} else {\n+\t\tEXPECT(err > 0);\n+\t}\n+\n+\tstrbuf_release(&pastLast);\n+\treftable_iterator_destroy(&it);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_free(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_seek_linear(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_linear_sha256(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_index(void)\n+{\n+\ttest_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_refs_for(int indexed)\n+{\n+\tint N = 50;\n+\tchar **want_names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint want_names_len = 0;\n+\tuint8_t want_hash[GIT_SHA1_RAWSZ];\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tint err;\n+\tstruct reftable_reader rd;\n+\tstruct reftable_block_source source = { NULL };\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint j;\n+\n+\tset_test_hash(want_hash, 4);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tchar fill[51] = { 0 };\n+\t\tchar name[100];\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ];\n+\t\tuint8_t hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\n+\t\tmemset(hash, i, sizeof(hash));\n+\t\tmemset(fill, 'x', 50);\n+\t\t/* Put the variable part in the start */\n+\t\tsnprintf(name, sizeof(name), \"br%02d%s\", i, fill);\n+\t\tname[40] = 0;\n+\t\tref.refname = name;\n+\n+\t\tset_test_hash(hash1, i / 4);\n+\t\tset_test_hash(hash2, 3 + i / 4);\n+\t\tref.value_type = REFTABLE_REF_VAL2;\n+\t\tref.value.val2.value = hash1;\n+\t\tref.value.val2.target_value = hash2;\n+\n+\t\t/* 80 bytes / entry, so 3 entries per block. Yields 17\n+\t\t */\n+\t\t/* blocks. */\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\n+\t\tif (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||\n+\t\t    !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {\n+\t\t\twant_names[want_names_len++] = xstrdup(name);\n+\t\t}\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tif (!indexed) {\n+\t\trd.obj_offsets.is_present = 0;\n+\t}\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_reader_refs_for(&rd, &it, want_hash);\n+\tEXPECT_ERR(err);\n+\n+\tj = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err >= 0);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT(j < want_names_len);\n+\t\tEXPECT(0 == strcmp(ref.refname, want_names[j]));\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == want_names_len);\n+\n+\tstrbuf_release(&buf);\n+\tfree_names(want_names);\n+\treftable_iterator_destroy(&it);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_refs_for_no_index(void)\n+{\n+\ttest_table_refs_for(0);\n+}\n+\n+static void test_table_refs_for_obj_index(void)\n+{\n+\ttest_table_refs_for(1);\n+}\n+\n+static void test_write_empty_table(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_ref_record rec = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);\n+\treftable_writer_free(w);\n+\n+\tEXPECT(buf.len == header_size(1) + footer_size(1));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &rec);\n+\tEXPECT(err > 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_write_key_order(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record refs[2] = {\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}, {\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}\n+\t};\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\terr = reftable_writer_add_ref(w, &refs[0]);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_add_ref(w, &refs[1]);\n+\tprintf(\"%d\\n\", err);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_writer_close(w);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_corrupt_table_empty(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+}\n+\n+static void test_corrupt_table(void)\n+{\n+\tuint8_t zeros[1024] = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\tstrbuf_add(&buf, zeros, sizeof(zeros));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\tstrbuf_release(&buf);\n+}\n+\n+int readwrite_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_corrupt_table);\n+\tRUN_TEST(test_corrupt_table_empty);\n+\tRUN_TEST(test_log_write_read);\n+\tRUN_TEST(test_write_key_order);\n+\tRUN_TEST(test_table_read_write_seek_linear_sha256);\n+\tRUN_TEST(test_log_buffer_size);\n+\tRUN_TEST(test_table_write_small_table);\n+\tRUN_TEST(test_buffer);\n+\tRUN_TEST(test_table_read_api);\n+\tRUN_TEST(test_table_read_write_sequential);\n+\tRUN_TEST(test_table_read_write_seek_linear);\n+\tRUN_TEST(test_table_read_write_seek_index);\n+\tRUN_TEST(test_table_refs_for_no_index);\n+\tRUN_TEST(test_table_refs_for_obj_index);\n+\tRUN_TEST(test_write_empty_table);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 5e7698ae654..3d541fa5c0c 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -14,7 +14,7 @@ int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n-int reftable_test_main(int argc, const char **argv);\n+int readwrite_test_main(int argc, const char **argv);\n int stack_test_main(int argc, const char **argv);\n int tree_test_main(int argc, const char **argv);\n int reftable_dump_main(int argc, char *const *argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 050551fa698..898aba836fd 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,6 +6,7 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"434104","messageId":"a77878993aa02edc1582f0fb87ad1570aba1c12c.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 12/19] reftable: read reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:48Z","receivedAt":"2021-08-30T14:58:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis supports reading a single reftable file.\n\nThe commit introduces an abstract iterator type, which captures the usecases\nboth of reading individual refs, and iterating over a segment of the ref\nnamespace.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/iter.c            | 194 +++++++++\n reftable/iter.h            |  69 ++++\n reftable/reader.c          | 801 +++++++++++++++++++++++++++++++++++++\n reftable/reader.h          |  66 +++\n reftable/reftable-reader.h | 101 +++++\n 6 files changed, 1233 insertions(+)\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/reftable-reader.h\n\ndiff --git a/Makefile b/Makefile\nindex 18093366acb..91952268e51 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2460,7 +2460,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n+REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n REFTABLE_OBJS += reftable/generic.o\ndiff --git a/reftable/iter.c b/reftable/iter.c\nnew file mode 100644\nindex 00000000000..93d04f735b8\n--- /dev/null\n+++ b/reftable/iter.c\n@@ -0,0 +1,194 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"iter.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"generic.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"reftable-error.h\"\n+\n+int iterator_is_null(struct reftable_iterator *it)\n+{\n+\treturn !it->ops;\n+}\n+\n+static void filtering_ref_iterator_close(void *iter_arg)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstrbuf_release(&fri->oid);\n+\treftable_iterator_destroy(&fri->it);\n+}\n+\n+static int filtering_ref_iterator_next(void *iter_arg,\n+\t\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\tint err = 0;\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&fri->it, ref);\n+\t\tif (err != 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (fri->double_check) {\n+\t\t\tstruct reftable_iterator it = { NULL };\n+\n+\t\t\terr = reftable_table_seek_ref(&fri->tab, &it,\n+\t\t\t\t\t\t      ref->refname);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = reftable_iterator_next_ref(&it, ref);\n+\t\t\t}\n+\n+\t\t\treftable_iterator_destroy(&it);\n+\n+\t\t\tif (err < 0) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (err > 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL2 &&\n+\t\t    (!memcmp(fri->oid.buf, ref->value.val2.target_value,\n+\t\t\t     fri->oid.len) ||\n+\t\t     !memcmp(fri->oid.buf, ref->value.val2.value,\n+\t\t\t     fri->oid.len)))\n+\t\t\treturn 0;\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL1 &&\n+\t\t    !memcmp(fri->oid.buf, ref->value.val1, fri->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treftable_ref_record_release(ref);\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable filtering_ref_iterator_vtable = {\n+\t.next = &filtering_ref_iterator_next,\n+\t.close = &filtering_ref_iterator_close,\n+};\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *it,\n+\t\t\t\t\t  struct filtering_ref_iterator *fri)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = fri;\n+\tit->ops = &filtering_ref_iterator_vtable;\n+}\n+\n+static void indexed_table_ref_iter_close(void *p)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tblock_iter_close(&it->cur);\n+\treftable_block_done(&it->block_reader.block);\n+\treftable_free(it->offsets);\n+\tstrbuf_release(&it->oid);\n+}\n+\n+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)\n+{\n+\tuint64_t off;\n+\tint err = 0;\n+\tif (it->offset_idx == it->offset_len) {\n+\t\tit->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\n+\treftable_block_done(&it->block_reader.block);\n+\n+\toff = it->offsets[it->offset_idx++];\n+\terr = reader_init_block_reader(it->r, &it->block_reader, off,\n+\t\t\t\t       BLOCK_TYPE_REF);\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\tif (err > 0) {\n+\t\t/* indexed block does not exist. */\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\t}\n+\tblock_reader_start(&it->block_reader, &it->cur);\n+\treturn 0;\n+}\n+\n+static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\n+\twhile (1) {\n+\t\tint err = block_iter_next(&it->cur, rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\terr = indexed_table_ref_iter_next_block(it);\n+\t\t\tif (err < 0) {\n+\t\t\t\treturn err;\n+\t\t\t}\n+\n+\t\t\tif (it->is_finished) {\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\t/* BUG */\n+\t\tif (!memcmp(it->oid.buf, ref->value.val2.target_value,\n+\t\t\t    it->oid.len) ||\n+\t\t    !memcmp(it->oid.buf, ref->value.val2.value, it->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+}\n+\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len)\n+{\n+\tstruct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT;\n+\tstruct indexed_table_ref_iter *itr =\n+\t\treftable_calloc(sizeof(struct indexed_table_ref_iter));\n+\tint err = 0;\n+\n+\t*itr = empty;\n+\titr->r = r;\n+\tstrbuf_add(&itr->oid, oid, oid_len);\n+\n+\titr->offsets = offsets;\n+\titr->offset_len = offset_len;\n+\n+\terr = indexed_table_ref_iter_next_block(itr);\n+\tif (err < 0) {\n+\t\treftable_free(itr);\n+\t} else {\n+\t\t*dest = itr;\n+\t}\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {\n+\t.next = &indexed_table_ref_iter_next,\n+\t.close = &indexed_table_ref_iter_close,\n+};\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = itr;\n+\tit->ops = &indexed_table_ref_iter_vtable;\n+}\ndiff --git a/reftable/iter.h b/reftable/iter.h\nnew file mode 100644\nindex 00000000000..09eb0cbfa59\n--- /dev/null\n+++ b/reftable/iter.h\n@@ -0,0 +1,69 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef ITER_H\n+#define ITER_H\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"record.h\"\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+/* Returns true for a zeroed out iterator, such as the one returned from\n+ * iterator_destroy. */\n+int iterator_is_null(struct reftable_iterator *it);\n+\n+/* iterator that produces only ref records that point to `oid` */\n+struct filtering_ref_iterator {\n+\tint double_check;\n+\tstruct reftable_table tab;\n+\tstruct strbuf oid;\n+\tstruct reftable_iterator it;\n+};\n+#define FILTERING_REF_ITERATOR_INIT \\\n+\t{                           \\\n+\t\t.oid = STRBUF_INIT  \\\n+\t}\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *,\n+\t\t\t\t\t  struct filtering_ref_iterator *);\n+\n+/* iterator that produces only ref records that point to `oid`,\n+ * but using the object index.\n+ */\n+struct indexed_table_ref_iter {\n+\tstruct reftable_reader *r;\n+\tstruct strbuf oid;\n+\n+\t/* mutable */\n+\tuint64_t *offsets;\n+\n+\t/* Points to the next offset to read. */\n+\tint offset_idx;\n+\tint offset_len;\n+\tstruct block_reader block_reader;\n+\tstruct block_iter cur;\n+\tint is_finished;\n+};\n+\n+#define INDEXED_TABLE_REF_ITER_INIT                                     \\\n+\t{                                                               \\\n+\t\t.cur = { .last_key = STRBUF_INIT }, .oid = STRBUF_INIT, \\\n+\t}\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr);\n+\n+/* Takes ownership of `offsets` */\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len);\n+\n+#endif\ndiff --git a/reftable/reader.c b/reftable/reader.c\nnew file mode 100644\nindex 00000000000..ab7a981eb8e\n--- /dev/null\n+++ b/reftable/reader.c\n@@ -0,0 +1,801 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reader.h\"\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"generic.h\"\n+#include \"iter.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-generic.h\"\n+#include \"tree.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source)\n+{\n+\treturn source->ops->size(source->arg);\n+}\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size)\n+{\n+\tint result = source->ops->read_block(source->arg, dest, off, size);\n+\tdest->source = *source;\n+\treturn result;\n+}\n+\n+void block_source_close(struct reftable_block_source *source)\n+{\n+\tif (!source->ops) {\n+\t\treturn;\n+\t}\n+\n+\tsource->ops->close(source->arg);\n+\tsource->ops = NULL;\n+}\n+\n+static struct reftable_reader_offsets *\n+reader_offsets_for(struct reftable_reader *r, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\t\treturn &r->ref_offsets;\n+\tcase BLOCK_TYPE_LOG:\n+\t\treturn &r->log_offsets;\n+\tcase BLOCK_TYPE_OBJ:\n+\t\treturn &r->obj_offsets;\n+\t}\n+\tabort();\n+}\n+\n+static int reader_get_block(struct reftable_reader *r,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t sz)\n+{\n+\tif (off >= r->size)\n+\t\treturn 0;\n+\n+\tif (off + sz > r->size) {\n+\t\tsz = r->size - off;\n+\t}\n+\n+\treturn block_source_read_block(&r->source, dest, off, sz);\n+}\n+\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r)\n+{\n+\treturn r->hash_id;\n+}\n+\n+const char *reader_name(struct reftable_reader *r)\n+{\n+\treturn r->name;\n+}\n+\n+static int parse_footer(struct reftable_reader *r, uint8_t *footer,\n+\t\t\tuint8_t *header)\n+{\n+\tuint8_t *f = footer;\n+\tuint8_t first_block_typ;\n+\tint err = 0;\n+\tuint32_t computed_crc;\n+\tuint32_t file_crc;\n+\n+\tif (memcmp(f, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tf += 4;\n+\n+\tif (memcmp(footer, header, header_size(r->version))) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tf++;\n+\tr->block_size = get_be24(f);\n+\n+\tf += 3;\n+\tr->min_update_index = get_be64(f);\n+\tf += 8;\n+\tr->max_update_index = get_be64(f);\n+\tf += 8;\n+\n+\tif (r->version == 1) {\n+\t\tr->hash_id = GIT_SHA1_FORMAT_ID;\n+\t} else {\n+\t\tr->hash_id = get_be32(f);\n+\t\tswitch (r->hash_id) {\n+\t\tcase GIT_SHA1_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tcase GIT_SHA256_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tf += 4;\n+\t}\n+\n+\tr->ref_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->obj_offsets.offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);\n+\tr->obj_offsets.offset >>= 5;\n+\n+\tr->obj_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tcomputed_crc = crc32(0, footer, f - footer);\n+\tfile_crc = get_be32(f);\n+\tf += 4;\n+\tif (computed_crc != file_crc) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tfirst_block_typ = header[header_size(r->version)];\n+\tr->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF);\n+\tr->ref_offsets.offset = 0;\n+\tr->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG ||\n+\t\t\t\t     r->log_offsets.offset > 0);\n+\tr->obj_offsets.is_present = r->obj_offsets.offset > 0;\n+\terr = 0;\n+done:\n+\treturn err;\n+}\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name)\n+{\n+\tstruct reftable_block footer = { NULL };\n+\tstruct reftable_block header = { NULL };\n+\tint err = 0;\n+\tuint64_t file_size = block_source_size(source);\n+\n+\t/* Need +1 to read type of first block. */\n+\tuint32_t read_size = header_size(2) + 1; /* read v2 because it's larger.  */\n+\tmemset(r, 0, sizeof(struct reftable_reader));\n+\n+\tif (read_size > file_size) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = block_source_read_block(source, &header, 0, read_size);\n+\tif (err != read_size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tif (memcmp(header.data, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tr->version = header.data[4];\n+\tif (r->version != 1 && r->version != 2) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tr->size = file_size - footer_size(r->version);\n+\tr->source = *source;\n+\tr->name = xstrdup(name);\n+\tr->hash_id = 0;\n+\n+\terr = block_source_read_block(source, &footer, r->size,\n+\t\t\t\t      footer_size(r->version));\n+\tif (err != footer_size(r->version)) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = parse_footer(r, footer.data, header.data);\n+done:\n+\treftable_block_done(&footer);\n+\treftable_block_done(&header);\n+\treturn err;\n+}\n+\n+struct table_iter {\n+\tstruct reftable_reader *r;\n+\tuint8_t typ;\n+\tuint64_t block_off;\n+\tstruct block_iter bi;\n+\tint is_finished;\n+};\n+#define TABLE_ITER_INIT                          \\\n+\t{                                        \\\n+\t\t.bi = {.last_key = STRBUF_INIT } \\\n+\t}\n+\n+static void table_iter_copy_from(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = src->block_off;\n+\tdest->is_finished = src->is_finished;\n+\tblock_iter_copy_from(&dest->bi, &src->bi);\n+}\n+\n+static int table_iter_next_in_block(struct table_iter *ti,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tint res = block_iter_next(&ti->bi, rec);\n+\tif (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {\n+\t\t((struct reftable_ref_record *)rec->data)->update_index +=\n+\t\t\tti->r->min_update_index;\n+\t}\n+\n+\treturn res;\n+}\n+\n+static void table_iter_block_done(struct table_iter *ti)\n+{\n+\tif (!ti->bi.br) {\n+\t\treturn;\n+\t}\n+\treftable_block_done(&ti->bi.br->block);\n+\tFREE_AND_NULL(ti->bi.br);\n+\n+\tti->bi.last_key.len = 0;\n+\tti->bi.next_off = 0;\n+}\n+\n+static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off,\n+\t\t\t\t  int version)\n+{\n+\tint32_t result = 0;\n+\n+\tif (off == 0) {\n+\t\tdata += header_size(version);\n+\t}\n+\n+\t*typ = data[0];\n+\tif (reftable_is_block_type(*typ)) {\n+\t\tresult = get_be24(data + 1);\n+\t}\n+\treturn result;\n+}\n+\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ)\n+{\n+\tint32_t guess_block_size = r->block_size ? r->block_size :\n+\t\t\t\t\t\t\t DEFAULT_BLOCK_SIZE;\n+\tstruct reftable_block block = { NULL };\n+\tuint8_t block_typ = 0;\n+\tint err = 0;\n+\tuint32_t header_off = next_off ? 0 : header_size(r->version);\n+\tint32_t block_size = 0;\n+\n+\tif (next_off >= r->size)\n+\t\treturn 1;\n+\n+\terr = reader_get_block(r, &block, next_off, guess_block_size);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tblock_size = extract_block_size(block.data, &block_typ, next_off,\n+\t\t\t\t\tr->version);\n+\tif (block_size < 0)\n+\t\treturn block_size;\n+\n+\tif (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {\n+\t\treftable_block_done(&block);\n+\t\treturn 1;\n+\t}\n+\n+\tif (block_size > guess_block_size) {\n+\t\treftable_block_done(&block);\n+\t\terr = reader_get_block(r, &block, next_off, block_size);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn block_reader_init(br, &block, header_off, r->block_size,\n+\t\t\t\t hash_size(r->hash_id));\n+}\n+\n+static int table_iter_next_block(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tuint64_t next_block_off = src->block_off + src->bi.br->full_block_size;\n+\tstruct block_reader br = { 0 };\n+\tint err = 0;\n+\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = next_block_off;\n+\n+\terr = reader_init_block_reader(src->r, &br, next_block_off, src->typ);\n+\tif (err > 0) {\n+\t\tdest->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\tif (err != 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct block_reader *brp =\n+\t\t\treftable_malloc(sizeof(struct block_reader));\n+\t\t*brp = br;\n+\n+\t\tdest->is_finished = 0;\n+\t\tblock_reader_start(brp, &dest->bi);\n+\t}\n+\treturn 0;\n+}\n+\n+static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)\n+{\n+\tif (reftable_record_type(rec) != ti->typ)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\twhile (1) {\n+\t\tstruct table_iter next = TABLE_ITER_INIT;\n+\t\tint err = 0;\n+\t\tif (ti->is_finished) {\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\terr = table_iter_next_in_block(ti, rec);\n+\t\tif (err <= 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err != 0) {\n+\t\t\tti->is_finished = 1;\n+\t\t}\n+\t\ttable_iter_block_done(ti);\n+\t\tif (err != 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\ttable_iter_copy_from(ti, &next);\n+\t\tblock_iter_close(&next.bi);\n+\t}\n+}\n+\n+static int table_iter_next_void(void *ti, struct reftable_record *rec)\n+{\n+\treturn table_iter_next(ti, rec);\n+}\n+\n+static void table_iter_close(void *p)\n+{\n+\tstruct table_iter *ti = p;\n+\ttable_iter_block_done(ti);\n+\tblock_iter_close(&ti->bi);\n+}\n+\n+static struct reftable_iterator_vtable table_iter_vtable = {\n+\t.next = &table_iter_next_void,\n+\t.close = &table_iter_close,\n+};\n+\n+static void iterator_from_table_iter(struct reftable_iterator *it,\n+\t\t\t\t     struct table_iter *ti)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = ti;\n+\tit->ops = &table_iter_vtable;\n+}\n+\n+static int reader_table_iter_at(struct reftable_reader *r,\n+\t\t\t\tstruct table_iter *ti, uint64_t off,\n+\t\t\t\tuint8_t typ)\n+{\n+\tstruct block_reader br = { 0 };\n+\tstruct block_reader *brp = NULL;\n+\n+\tint err = reader_init_block_reader(r, &br, off, typ);\n+\tif (err != 0)\n+\t\treturn err;\n+\n+\tbrp = reftable_malloc(sizeof(struct block_reader));\n+\t*brp = br;\n+\tti->r = r;\n+\tti->typ = block_reader_type(brp);\n+\tti->block_off = off;\n+\tblock_reader_start(brp, &ti->bi);\n+\treturn 0;\n+}\n+\n+static int reader_start(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\tuint8_t typ, int index)\n+{\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tuint64_t off = offs->offset;\n+\tif (index) {\n+\t\toff = offs->index_offset;\n+\t\tif (off == 0) {\n+\t\t\treturn 1;\n+\t\t}\n+\t\ttyp = BLOCK_TYPE_INDEX;\n+\t}\n+\n+\treturn reader_table_iter_at(r, ti, off, typ);\n+}\n+\n+static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\t      struct reftable_record *want)\n+{\n+\tstruct reftable_record rec =\n+\t\treftable_new_record(reftable_record_type(want));\n+\tstruct strbuf want_key = STRBUF_INIT;\n+\tstruct strbuf got_key = STRBUF_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = -1;\n+\n+\treftable_record_key(want, &want_key);\n+\n+\twhile (1) {\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = block_reader_first_key(next.bi.br, &got_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (strbuf_cmp(&got_key, &want_key) > 0) {\n+\t\t\ttable_iter_block_done(&next);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_block_done(ti);\n+\t\ttable_iter_copy_from(ti, &next);\n+\t}\n+\n+\terr = block_iter_seek(&ti->bi, &want_key);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = 0;\n+\n+done:\n+\tblock_iter_close(&next.bi);\n+\treftable_record_destroy(&rec);\n+\tstrbuf_release(&want_key);\n+\tstrbuf_release(&got_key);\n+\treturn err;\n+}\n+\n+static int reader_seek_indexed(struct reftable_reader *r,\n+\t\t\t       struct reftable_iterator *it,\n+\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct reftable_index_record want_index = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record want_index_rec = { NULL };\n+\tstruct reftable_index_record index_result = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record index_result_rec = { NULL };\n+\tstruct table_iter index_iter = TABLE_ITER_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = 0;\n+\n+\treftable_record_key(rec, &want_index.last_key);\n+\treftable_record_from_index(&want_index_rec, &want_index);\n+\treftable_record_from_index(&index_result_rec, &index_result);\n+\n+\terr = reader_start(r, &index_iter, reftable_record_type(rec), 1);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reader_seek_linear(r, &index_iter, &want_index_rec);\n+\twhile (1) {\n+\t\terr = table_iter_next(&index_iter, &index_result_rec);\n+\t\ttable_iter_block_done(&index_iter);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = reader_table_iter_at(r, &next, index_result.offset, 0);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = block_iter_seek(&next.bi, &want_index.last_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (next.typ == reftable_record_type(rec)) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (next.typ != BLOCK_TYPE_INDEX) {\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_copy_from(&index_iter, &next);\n+\t}\n+\n+\tif (err == 0) {\n+\t\tstruct table_iter empty = TABLE_ITER_INIT;\n+\t\tstruct table_iter *malloced =\n+\t\t\treftable_calloc(sizeof(struct table_iter));\n+\t\t*malloced = empty;\n+\t\ttable_iter_copy_from(malloced, &next);\n+\t\titerator_from_table_iter(it, malloced);\n+\t}\n+done:\n+\tblock_iter_close(&next.bi);\n+\ttable_iter_close(&index_iter);\n+\treftable_record_release(&want_index_rec);\n+\treftable_record_release(&index_result_rec);\n+\treturn err;\n+}\n+\n+static int reader_seek_internal(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it,\n+\t\t\t\tstruct reftable_record *rec)\n+{\n+\tstruct reftable_reader_offsets *offs =\n+\t\treader_offsets_for(r, reftable_record_type(rec));\n+\tuint64_t idx = offs->index_offset;\n+\tstruct table_iter ti = TABLE_ITER_INIT;\n+\tint err = 0;\n+\tif (idx > 0)\n+\t\treturn reader_seek_indexed(r, it, rec);\n+\n+\terr = reader_start(r, &ti, reftable_record_type(rec), 0);\n+\tif (err < 0)\n+\t\treturn err;\n+\terr = reader_seek_linear(r, &ti, rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct table_iter *p =\n+\t\t\treftable_malloc(sizeof(struct table_iter));\n+\t\t*p = ti;\n+\t\titerator_from_table_iter(it, p);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\tstruct reftable_record *rec)\n+{\n+\tuint8_t typ = reftable_record_type(rec);\n+\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tif (!offs->is_present) {\n+\t\titerator_set_empty(it);\n+\t\treturn 0;\n+\t}\n+\n+\treturn reader_seek_internal(r, it, rec);\n+}\n+\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_reader_seek_log_at(r, it, name, max);\n+}\n+\n+void reader_close(struct reftable_reader *r)\n+{\n+\tblock_source_close(&r->source);\n+\tFREE_AND_NULL(r->name);\n+}\n+\n+int reftable_new_reader(struct reftable_reader **p,\n+\t\t\tstruct reftable_block_source *src, char const *name)\n+{\n+\tstruct reftable_reader *rd =\n+\t\treftable_calloc(sizeof(struct reftable_reader));\n+\tint err = init_reader(rd, src, name);\n+\tif (err == 0) {\n+\t\t*p = rd;\n+\t} else {\n+\t\tblock_source_close(src);\n+\t\treftable_free(rd);\n+\t}\n+\treturn err;\n+}\n+\n+void reftable_reader_free(struct reftable_reader *r)\n+{\n+\treader_close(r);\n+\treftable_free(r);\n+}\n+\n+static int reftable_reader_refs_for_indexed(struct reftable_reader *r,\n+\t\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t\t    uint8_t *oid)\n+{\n+\tstruct reftable_obj_record want = {\n+\t\t.hash_prefix = oid,\n+\t\t.hash_prefix_len = r->object_id_len,\n+\t};\n+\tstruct reftable_record want_rec = { NULL };\n+\tstruct reftable_iterator oit = { NULL };\n+\tstruct reftable_obj_record got = { NULL };\n+\tstruct reftable_record got_rec = { NULL };\n+\tint err = 0;\n+\tstruct indexed_table_ref_iter *itr = NULL;\n+\n+\t/* Look through the reverse index. */\n+\treftable_record_from_obj(&want_rec, &want);\n+\terr = reader_seek(r, &oit, &want_rec);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\t/* read out the reftable_obj_record */\n+\treftable_record_from_obj(&got_rec, &got);\n+\terr = iterator_next(&oit, &got_rec);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 0 ||\n+\t    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {\n+\t\t/* didn't find it; return empty iterator */\n+\t\titerator_set_empty(it);\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),\n+\t\t\t\t\t got.offsets, got.offset_len);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tgot.offsets = NULL;\n+\titerator_from_indexed_table_ref_iter(it, itr);\n+\n+done:\n+\treftable_iterator_destroy(&oit);\n+\treftable_record_release(&got_rec);\n+\treturn err;\n+}\n+\n+static int reftable_reader_refs_for_unindexed(struct reftable_reader *r,\n+\t\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t\t      uint8_t *oid)\n+{\n+\tstruct table_iter ti_empty = TABLE_ITER_INIT;\n+\tstruct table_iter *ti = reftable_calloc(sizeof(struct table_iter));\n+\tstruct filtering_ref_iterator *filter = NULL;\n+\tstruct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT;\n+\tint oid_len = hash_size(r->hash_id);\n+\tint err;\n+\n+\t*ti = ti_empty;\n+\terr = reader_start(r, ti, BLOCK_TYPE_REF, 0);\n+\tif (err < 0) {\n+\t\treftable_free(ti);\n+\t\treturn err;\n+\t}\n+\n+\tfilter = reftable_malloc(sizeof(struct filtering_ref_iterator));\n+\t*filter = empty;\n+\n+\tstrbuf_add(&filter->oid, oid, oid_len);\n+\treftable_table_from_reader(&filter->tab, r);\n+\tfilter->double_check = 0;\n+\titerator_from_table_iter(&filter->it, ti);\n+\n+\titerator_from_filtering_ref_iterator(it, filter);\n+\treturn 0;\n+}\n+\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid)\n+{\n+\tif (r->obj_offsets.is_present)\n+\t\treturn reftable_reader_refs_for_indexed(r, it, oid);\n+\treturn reftable_reader_refs_for_unindexed(r, it, oid);\n+}\n+\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r)\n+{\n+\treturn r->max_update_index;\n+}\n+\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r)\n+{\n+\treturn r->min_update_index;\n+}\n+\n+/* generic table interface. */\n+\n+static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,\n+\t\t\t\t     struct reftable_record *rec)\n+{\n+\treturn reader_seek(tab, it, rec);\n+}\n+\n+static uint32_t reftable_reader_hash_id_void(void *tab)\n+{\n+\treturn reftable_reader_hash_id(tab);\n+}\n+\n+static uint64_t reftable_reader_min_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_reader_max_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable reader_vtable = {\n+\t.seek_record = reftable_reader_seek_void,\n+\t.hash_id = reftable_reader_hash_id_void,\n+\t.min_update_index = reftable_reader_min_update_index_void,\n+\t.max_update_index = reftable_reader_max_update_index_void,\n+};\n+\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &reader_vtable;\n+\ttab->table_arg = reader;\n+}\n+\n+\n+int reftable_reader_print_file(const char *tablename)\n+{\n+\tstruct reftable_block_source src = { NULL };\n+\tint err = reftable_block_source_from_file(&src, tablename);\n+\tstruct reftable_reader *r = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&r, &src, tablename);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\treftable_table_from_reader(&tab, r);\n+\terr = reftable_table_print(&tab);\n+done:\n+\treftable_reader_free(r);\n+\treturn err;\n+}\ndiff --git a/reftable/reader.h b/reftable/reader.h\nnew file mode 100644\nindex 00000000000..39583e5dbcd\n--- /dev/null\n+++ b/reftable/reader.h\n@@ -0,0 +1,66 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef READER_H\n+#define READER_H\n+\n+#include \"block.h\"\n+#include \"record.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source);\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size);\n+void block_source_close(struct reftable_block_source *source);\n+\n+/* metadata for a block type */\n+struct reftable_reader_offsets {\n+\tint is_present;\n+\tuint64_t offset;\n+\tuint64_t index_offset;\n+};\n+\n+/* The state for reading a reftable file. */\n+struct reftable_reader {\n+\t/* for convience, associate a name with the instance. */\n+\tchar *name;\n+\tstruct reftable_block_source source;\n+\n+\t/* Size of the file, excluding the footer. */\n+\tuint64_t size;\n+\n+\t/* 'sha1' for SHA1, 's256' for SHA-256 */\n+\tuint32_t hash_id;\n+\n+\tuint32_t block_size;\n+\tuint64_t min_update_index;\n+\tuint64_t max_update_index;\n+\t/* Length of the OID keys in the 'o' section */\n+\tint object_id_len;\n+\tint version;\n+\n+\tstruct reftable_reader_offsets ref_offsets;\n+\tstruct reftable_reader_offsets obj_offsets;\n+\tstruct reftable_reader_offsets log_offsets;\n+};\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name);\n+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\tstruct reftable_record *rec);\n+void reader_close(struct reftable_reader *r);\n+const char *reader_name(struct reftable_reader *r);\n+\n+/* initialize a block reader to read from `r` */\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ);\n+\n+#endif\ndiff --git a/reftable/reftable-reader.h b/reftable/reftable-reader.h\nnew file mode 100644\nindex 00000000000..4a4bc2fdf85\n--- /dev/null\n+++ b/reftable/reftable-reader.h\n@@ -0,0 +1,101 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_READER_H\n+#define REFTABLE_READER_H\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Reading single tables\n+ *\n+ * The follow routines are for reading single files. For an\n+ * application-level interface, skip ahead to struct\n+ * reftable_merged_table and struct reftable_stack.\n+ */\n+\n+/* The reader struct is a handle to an open reftable file. */\n+struct reftable_reader;\n+\n+/* Generic table. */\n+struct reftable_table;\n+\n+/* reftable_new_reader opens a reftable for reading. If successful,\n+ * returns 0 code and sets pp. The name is used for creating a\n+ * stack. Typically, it is the basename of the file. The block source\n+ * `src` is owned by the reader, and is closed on calling\n+ * reftable_reader_destroy(). On error, the block source `src` is\n+ * closed as well.\n+ */\n+int reftable_new_reader(struct reftable_reader **pp,\n+\t\t\tstruct reftable_block_source *src, const char *name);\n+\n+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted\n+   in the table.  To seek to the start of the table, use name = \"\".\n+\n+   example:\n+\n+   struct reftable_reader *r = NULL;\n+   int err = reftable_new_reader(&r, &src, \"filename\");\n+   if (err < 0) { ... }\n+   struct reftable_iterator it  = {0};\n+   err = reftable_reader_seek_ref(r, &it, \"refs/heads/master\");\n+   if (err < 0) { ... }\n+   struct reftable_ref_record ref  = {0};\n+   while (1) {\n+   err = reftable_iterator_next_ref(&it, &ref);\n+   if (err > 0) {\n+   break;\n+   }\n+   if (err < 0) {\n+   ..error handling..\n+   }\n+   ..found..\n+   }\n+   reftable_iterator_destroy(&it);\n+   reftable_ref_record_release(&ref);\n+*/\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID used in this table. */\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r);\n+\n+/* seek to logs for the given name, older than update_index. To seek to the\n+   start of the table, use name = \"\".\n+*/\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index);\n+\n+/* seek to newest log entry for given name. */\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* closes and deallocates a reader. */\n+void reftable_reader_free(struct reftable_reader *);\n+\n+/* return an iterator for the refs pointing to `oid`. */\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid);\n+\n+/* return the max_update_index for a table */\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);\n+\n+/* return the min_update_index for a table */\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);\n+\n+/* creates a generic table from a file reader. */\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader);\n+\n+/* print table onto stdout for debugging. */\n+int reftable_reader_print_file(const char *tablename);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"434105","messageId":"8bc889aaae87d4702713219baa0cb03c2cf10b84.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 15/19] reftable: add merged table view","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:51Z","receivedAt":"2021-08-30T14:58:22Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis adds an abstract, read-only interface to the ref database.\n\nThis primitive is used to construct the read view of the ref database\n(the read view is constructed by merging several *.ref files). It also\nprovides the mechanism to provide a unified view of the refs in the main\nrepository and the per-worktree refs.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/merged.c          | 362 +++++++++++++++++++++++++++++++++++++\n reftable/merged.h          |  35 ++++\n reftable/merged_test.c     | 292 ++++++++++++++++++++++++++++++\n reftable/reftable-merged.h |  72 ++++++++\n t/helper/test-reftable.c   |   1 +\n 6 files changed, 764 insertions(+)\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/reftable-merged.h\n\ndiff --git a/Makefile b/Makefile\nindex e80f253ecc3..6c8545b9eb7 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,6 +2462,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/merged.o\n REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n@@ -2473,6 +2474,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\ndiff --git a/reftable/merged.c b/reftable/merged.c\nnew file mode 100644\nindex 00000000000..e5b53da6db3\n--- /dev/null\n+++ b/reftable/merged.c\n@@ -0,0 +1,362 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"constants.h\"\n+#include \"iter.h\"\n+#include \"pq.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+\n+static int merged_iter_init(struct merged_iter *mi)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\t\tint err = iterator_next(&mi->stack[i], &rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t\t\treftable_record_destroy(&rec);\n+\t\t} else {\n+\t\t\tstruct pq_entry e = {\n+\t\t\t\t.rec = rec,\n+\t\t\t\t.index = i,\n+\t\t\t};\n+\t\t\tmerged_iter_pqueue_add(&mi->pq, e);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merged_iter_close(void *p)\n+{\n+\tstruct merged_iter *mi = p;\n+\tint i = 0;\n+\tmerged_iter_pqueue_release(&mi->pq);\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t}\n+\treftable_free(mi->stack);\n+}\n+\n+static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,\n+\t\t\t\t\t       size_t idx)\n+{\n+\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\tstruct pq_entry e = {\n+\t\t.rec = rec,\n+\t\t.index = idx,\n+\t};\n+\tint err = iterator_next(&mi->stack[idx], &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (err > 0) {\n+\t\treftable_iterator_destroy(&mi->stack[idx]);\n+\t\treftable_record_destroy(&rec);\n+\t\treturn 0;\n+\t}\n+\n+\tmerged_iter_pqueue_add(&mi->pq, e);\n+\treturn 0;\n+}\n+\n+static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)\n+{\n+\tif (iterator_is_null(&mi->stack[idx]))\n+\t\treturn 0;\n+\treturn merged_iter_advance_nonnull_subiter(mi, idx);\n+}\n+\n+static int merged_iter_next_entry(struct merged_iter *mi,\n+\t\t\t\t  struct reftable_record *rec)\n+{\n+\tstruct strbuf entry_key = STRBUF_INIT;\n+\tstruct pq_entry entry = { 0 };\n+\tint err = 0;\n+\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\tentry = merged_iter_pqueue_remove(&mi->pq);\n+\terr = merged_iter_advance_subiter(mi, entry.index);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\t/*\n+\t  One can also use reftable as datacenter-local storage, where the ref\n+\t  database is maintained in globally consistent database (eg.\n+\t  CockroachDB or Spanner). In this scenario, replication delays together\n+\t  with compaction may cause newer tables to contain older entries. In\n+\t  such a deployment, the loop below must be changed to collect all\n+\t  entries for the same key, and return new the newest one.\n+\t*/\n+\treftable_record_key(&entry.rec, &entry_key);\n+\twhile (!merged_iter_pqueue_is_empty(mi->pq)) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(mi->pq);\n+\t\tstruct strbuf k = STRBUF_INIT;\n+\t\tint err = 0, cmp = 0;\n+\n+\t\treftable_record_key(&top.rec, &k);\n+\n+\t\tcmp = strbuf_cmp(&k, &entry_key);\n+\t\tstrbuf_release(&k);\n+\n+\t\tif (cmp > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmerged_iter_pqueue_remove(&mi->pq);\n+\t\terr = merged_iter_advance_subiter(mi, top.index);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_record_destroy(&top.rec);\n+\t}\n+\n+\treftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));\n+\treftable_record_destroy(&entry.rec);\n+\tstrbuf_release(&entry_key);\n+\treturn 0;\n+}\n+\n+static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)\n+{\n+\twhile (1) {\n+\t\tint err = merged_iter_next_entry(mi, rec);\n+\t\tif (err == 0 && mi->suppress_deletions &&\n+\t\t    reftable_record_is_deletion(rec)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treturn err;\n+\t}\n+}\n+\n+static int merged_iter_next_void(void *p, struct reftable_record *rec)\n+{\n+\tstruct merged_iter *mi = p;\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\treturn merged_iter_next(mi, rec);\n+}\n+\n+static struct reftable_iterator_vtable merged_iter_vtable = {\n+\t.next = &merged_iter_next_void,\n+\t.close = &merged_iter_close,\n+};\n+\n+static void iterator_from_merged_iter(struct reftable_iterator *it,\n+\t\t\t\t      struct merged_iter *mi)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = mi;\n+\tit->ops = &merged_iter_vtable;\n+}\n+\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id)\n+{\n+\tstruct reftable_merged_table *m = NULL;\n+\tuint64_t last_max = 0;\n+\tuint64_t first_min = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t min = reftable_table_min_update_index(&stack[i]);\n+\t\tuint64_t max = reftable_table_max_update_index(&stack[i]);\n+\n+\t\tif (reftable_table_hash_id(&stack[i]) != hash_id) {\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\t\t}\n+\t\tif (i == 0 || min < first_min) {\n+\t\t\tfirst_min = min;\n+\t\t}\n+\t\tif (i == 0 || max > last_max) {\n+\t\t\tlast_max = max;\n+\t\t}\n+\t}\n+\n+\tm = reftable_calloc(sizeof(struct reftable_merged_table));\n+\tm->stack = stack;\n+\tm->stack_len = n;\n+\tm->min = first_min;\n+\tm->max = last_max;\n+\tm->hash_id = hash_id;\n+\t*dest = m;\n+\treturn 0;\n+}\n+\n+/* clears the list of subtable, without affecting the readers themselves. */\n+void merged_table_release(struct reftable_merged_table *mt)\n+{\n+\tFREE_AND_NULL(mt->stack);\n+\tmt->stack_len = 0;\n+}\n+\n+void reftable_merged_table_free(struct reftable_merged_table *mt)\n+{\n+\tif (!mt) {\n+\t\treturn;\n+\t}\n+\tmerged_table_release(mt);\n+\treftable_free(mt);\n+}\n+\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->max;\n+}\n+\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->min;\n+}\n+\n+static int reftable_table_seek_record(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      struct reftable_record *rec)\n+{\n+\treturn tab->ops->seek_record(tab->table_arg, it, rec);\n+}\n+\n+static int merged_table_seek_record(struct reftable_merged_table *mt,\n+\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tstruct reftable_iterator *iters = reftable_calloc(\n+\t\tsizeof(struct reftable_iterator) * mt->stack_len);\n+\tstruct merged_iter merged = {\n+\t\t.stack = iters,\n+\t\t.typ = reftable_record_type(rec),\n+\t\t.hash_id = mt->hash_id,\n+\t\t.suppress_deletions = mt->suppress_deletions,\n+\t};\n+\tint n = 0;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < mt->stack_len && err == 0; i++) {\n+\t\tint e = reftable_table_seek_record(&mt->stack[i], &iters[n],\n+\t\t\t\t\t\t   rec);\n+\t\tif (e < 0) {\n+\t\t\terr = e;\n+\t\t}\n+\t\tif (e == 0) {\n+\t\t\tn++;\n+\t\t}\n+\t}\n+\tif (err < 0) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < n; i++) {\n+\t\t\treftable_iterator_destroy(&iters[i]);\n+\t\t}\n+\t\treftable_free(iters);\n+\t\treturn err;\n+\t}\n+\n+\tmerged.stack_len = n;\n+\terr = merged_iter_init(&merged);\n+\tif (err < 0) {\n+\t\tmerged_iter_close(&merged);\n+\t\treturn err;\n+\t} else {\n+\t\tstruct merged_iter *p =\n+\t\t\treftable_malloc(sizeof(struct merged_iter));\n+\t\t*p = merged;\n+\t\titerator_from_merged_iter(it, p);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_merged_table_seek_log_at(mt, it, name, max);\n+}\n+\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt)\n+{\n+\treturn mt->hash_id;\n+}\n+\n+static int reftable_merged_table_seek_void(void *tab,\n+\t\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t\t   struct reftable_record *rec)\n+{\n+\treturn merged_table_seek_record(tab, it, rec);\n+}\n+\n+static uint32_t reftable_merged_table_hash_id_void(void *tab)\n+{\n+\treturn reftable_merged_table_hash_id(tab);\n+}\n+\n+static uint64_t reftable_merged_table_min_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_merged_table_max_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable merged_table_vtable = {\n+\t.seek_record = reftable_merged_table_seek_void,\n+\t.hash_id = reftable_merged_table_hash_id_void,\n+\t.min_update_index = reftable_merged_table_min_update_index_void,\n+\t.max_update_index = reftable_merged_table_max_update_index_void,\n+};\n+\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *merged)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &merged_table_vtable;\n+\ttab->table_arg = merged;\n+}\ndiff --git a/reftable/merged.h b/reftable/merged.h\nnew file mode 100644\nindex 00000000000..8c4d4d58d77\n--- /dev/null\n+++ b/reftable/merged.h\n@@ -0,0 +1,35 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef MERGED_H\n+#define MERGED_H\n+\n+#include \"pq.h\"\n+\n+struct reftable_merged_table {\n+\tstruct reftable_table *stack;\n+\tsize_t stack_len;\n+\tuint32_t hash_id;\n+\tint suppress_deletions;\n+\n+\tuint64_t min;\n+\tuint64_t max;\n+};\n+\n+struct merged_iter {\n+\tstruct reftable_iterator *stack;\n+\tuint32_t hash_id;\n+\tsize_t stack_len;\n+\tuint8_t typ;\n+\tint suppress_deletions;\n+\tstruct merged_iter_pqueue pq;\n+};\n+\n+void merged_table_release(struct reftable_merged_table *mt);\n+\n+#endif\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nnew file mode 100644\nindex 00000000000..1e2afe37b8b\n--- /dev/null\n+++ b/reftable/merged_test.c\n@@ -0,0 +1,292 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-generic.h\"\n+#include \"reftable-writer.h\"\n+\n+static void write_test_table(struct strbuf *buf,\n+\t\t\t     struct reftable_ref_record refs[], int n)\n+{\n+\tint min = 0xffffffff;\n+\tint max = 0;\n+\tint i = 0;\n+\tint err;\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_writer *w = NULL;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t ui = refs[i].update_index;\n+\t\tif (ui > max) {\n+\t\t\tmax = ui;\n+\t\t}\n+\t\tif (ui < min) {\n+\t\t\tmin = ui;\n+\t\t}\n+\t}\n+\n+\tw = reftable_new_writer(&strbuf_add_void, buf, &opts);\n+\treftable_writer_set_limits(w, min, max);\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t before = refs[i].update_index;\n+\t\tint n = reftable_writer_add_ref(w, &refs[i]);\n+\t\tassert(n == 0);\n+\t\tassert(before == refs[i].update_index);\n+\t}\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\n+\treftable_writer_free(w);\n+}\n+\n+static struct reftable_merged_table *\n+merged_table_from_records(struct reftable_ref_record **refs,\n+\t\t\t  struct reftable_block_source **source,\n+\t\t\t  struct reftable_reader ***readers, int *sizes,\n+\t\t\t  struct strbuf *buf, int n)\n+{\n+\tint i = 0;\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err;\n+\tstruct reftable_table *tabs =\n+\t\treftable_calloc(n * sizeof(struct reftable_table));\n+\t*readers = reftable_calloc(n * sizeof(struct reftable_reader *));\n+\t*source = reftable_calloc(n * sizeof(**source));\n+\tfor (i = 0; i < n; i++) {\n+\t\twrite_test_table(&buf[i], refs[i], sizes[i]);\n+\t\tblock_source_from_strbuf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_new_reader(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tEXPECT_ERR(err);\n+\t\treftable_table_from_reader(&tabs[i], (*readers)[i]);\n+\t}\n+\n+\terr = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\treturn mt;\n+}\n+\n+static void readers_destroy(struct reftable_reader **readers, size_t n)\n+{\n+\tint i = 0;\n+\tfor (; i < n; i++)\n+\t\treftable_reader_free(readers[i]);\n+\treftable_free(readers);\n+}\n+\n+static void test_merged_between(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };\n+\n+\tstruct reftable_ref_record r1[] = { {\n+\t\t.refname = \"b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = hash1,\n+\t} };\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2 };\n+\tint sizes[] = { 1, 1 };\n+\tstruct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);\n+\tint i;\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\tEXPECT(ref.update_index == 2);\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treaders_destroy(readers, 2);\n+\treftable_merged_table_free(mt);\n+\tfor (i = 0; i < ARRAY_SIZE(bufs); i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treftable_free(bs);\n+}\n+\n+static void test_merged(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };\n+\tuint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\tstruct reftable_ref_record r3[] = {\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash2,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"d\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t};\n+\n+\tstruct reftable_ref_record want[] = {\n+\t\tr2[0],\n+\t\tr1[1],\n+\t\tr3[0],\n+\t\tr3[1],\n+\t};\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n+\tint sizes[3] = { 3, 1, 2 };\n+\tstruct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tstruct reftable_ref_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tint i = 0;\n+\n+\tEXPECT_ERR(err);\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (len == cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\tout = reftable_realloc(\n+\t\t\t\tout, sizeof(struct reftable_ref_record) * cap);\n+\t\t}\n+\t\tout[len++] = ref;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tassert(ARRAY_SIZE(want) == len);\n+\tfor (i = 0; i < len; i++) {\n+\t\tassert(reftable_ref_record_equal(&want[i], &out[i],\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t}\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&out[i]);\n+\t}\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+static void test_default_write_opts(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"master\",\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);\n+\tuint32_t hash_id;\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_merged_table *merged = NULL;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\thash_id = reftable_reader_hash_id(rd);\n+\tassert(hash_id == GIT_SHA1_FORMAT_ID);\n+\n+\treftable_table_from_reader(&tab[0], rd);\n+\terr = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\treftable_reader_free(rd);\n+\treftable_merged_table_free(merged);\n+\tstrbuf_release(&buf);\n+}\n+\n+/* XXX test refs_for(oid) */\n+\n+int merged_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_merged_between);\n+\tRUN_TEST(test_merged);\n+\tRUN_TEST(test_default_write_opts);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-merged.h b/reftable/reftable-merged.h\nnew file mode 100644\nindex 00000000000..1a6d16915ab\n--- /dev/null\n+++ b/reftable/reftable-merged.h\n@@ -0,0 +1,72 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_MERGED_H\n+#define REFTABLE_MERGED_H\n+\n+#include \"reftable-iterator.h\"\n+\n+/*\n+ * Merged tables\n+ *\n+ * A ref database kept in a sequence of table files. The merged_table presents a\n+ * unified view to reading (seeking, iterating) a sequence of immutable tables.\n+ *\n+ * The merged tables are on purpose kept disconnected from their actual storage\n+ * (eg. files on disk), because it is useful to merge tables aren't files. For\n+ * example, the per-workspace and global ref namespace can be implemented as a\n+ * merged table of two stacks of file-backed reftables.\n+ */\n+\n+/* A merged table is implements seeking/iterating over a stack of tables. */\n+struct reftable_merged_table;\n+\n+/* A generic reftable; see below. */\n+struct reftable_table;\n+\n+/* reftable_new_merged_table creates a new merged table. It takes ownership of\n+   the stack array.\n+*/\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id);\n+\n+/* returns an iterator positioned just before 'name' */\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns an iterator for log entry, at given update_index */\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index);\n+\n+/* like reftable_merged_table_seek_log_at but look for the newest entry. */\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns the max update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);\n+\n+/* returns the min update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);\n+\n+/* releases memory for the merged_table */\n+void reftable_merged_table_free(struct reftable_merged_table *m);\n+\n+/* return the hash ID of the merged table. */\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);\n+\n+/* create a generic table from reftable_merged_table */\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *table);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 0b5a1701df1..8087f2da4e6 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"434106","messageId":"9a30c1c4666fd27e6bdaf6677b11c45aa869260c.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 16/19] reftable: implement refname validation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:52Z","receivedAt":"2021-08-30T14:58:25Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe packed/loose format has restrictions on refnames: a and a/b cannot\ncoexist. This limitation does not apply to reftable per se, but must be\nmaintained for interoperability. This code adds validation routines to\nabort transactions that are trying to add invalid names.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   1 +\n reftable/refname.c       | 209 +++++++++++++++++++++++++++++++++++++++\n reftable/refname.h       |  29 ++++++\n reftable/refname_test.c  | 102 +++++++++++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 342 insertions(+)\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 6c8545b9eb7..827a7190140 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2478,6 +2478,7 @@ REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n+REFTABLE_TEST_OBJS += reftable/refname_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/refname.c b/reftable/refname.c\nnew file mode 100644\nindex 00000000000..95734969324\n--- /dev/null\n+++ b/reftable/refname.c\n@@ -0,0 +1,209 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+#include \"refname.h\"\n+#include \"reftable-iterator.h\"\n+\n+struct find_arg {\n+\tchar **names;\n+\tconst char *want;\n+};\n+\n+static int find_name(size_t k, void *arg)\n+{\n+\tstruct find_arg *f_arg = arg;\n+\treturn strcmp(f_arg->names[k], f_arg->want) >= 0;\n+}\n+\n+static int modification_has_ref(struct modification *mod, const char *name)\n+{\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len && !strcmp(mod->add[idx], name)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (mod->del_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->del,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\tif (idx < mod->del_len && !strcmp(mod->del[idx], name)) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\terr = reftable_table_read_ref(&mod->tab, name, &ref);\n+\treftable_ref_record_release(&ref);\n+\treturn err;\n+}\n+\n+static void modification_release(struct modification *mod)\n+{\n+\t/* don't delete the strings themselves; they're owned by ref records.\n+\t */\n+\tFREE_AND_NULL(mod->add);\n+\tFREE_AND_NULL(mod->del);\n+\tmod->add_len = 0;\n+\tmod->del_len = 0;\n+}\n+\n+static int modification_has_ref_with_prefix(struct modification *mod,\n+\t\t\t\t\t    const char *prefix)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = prefix,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len &&\n+\t\t    !strncmp(prefix, mod->add[idx], strlen(prefix)))\n+\t\t\tgoto done;\n+\t}\n+\terr = reftable_table_seek_ref(&mod->tab, &it, prefix);\n+\tif (err)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\n+\t\tif (mod->del_len > 0) {\n+\t\t\tstruct find_arg arg = {\n+\t\t\t\t.names = mod->del,\n+\t\t\t\t.want = ref.refname,\n+\t\t\t};\n+\t\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\t\tif (idx < mod->del_len &&\n+\t\t\t    !strcmp(ref.refname, mod->del[idx])) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (strncmp(ref.refname, prefix, strlen(prefix))) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int validate_refname(const char *name)\n+{\n+\twhile (1) {\n+\t\tchar *next = strchr(name, '/');\n+\t\tif (!*name) {\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\t}\n+\t\tif (!next) {\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (next - name == 0 || (next - name == 1 && *name == '.') ||\n+\t\t    (next - name == 2 && name[0] == '.' && name[1] == '.'))\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\tname = next + 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz)\n+{\n+\tstruct modification mod = {\n+\t\t.tab = tab,\n+\t\t.add = reftable_calloc(sizeof(char *) * sz),\n+\t\t.del = reftable_calloc(sizeof(char *) * sz),\n+\t};\n+\tint i = 0;\n+\tint err = 0;\n+\tfor (; i < sz; i++) {\n+\t\tif (reftable_ref_record_is_deletion(&recs[i])) {\n+\t\t\tmod.del[mod.del_len++] = recs[i].refname;\n+\t\t} else {\n+\t\t\tmod.add[mod.add_len++] = recs[i].refname;\n+\t\t}\n+\t}\n+\n+\terr = modification_validate(&mod);\n+\tmodification_release(&mod);\n+\treturn err;\n+}\n+\n+static void strbuf_trim_component(struct strbuf *sl)\n+{\n+\twhile (sl->len > 0) {\n+\t\tint is_slash = (sl->buf[sl->len - 1] == '/');\n+\t\tstrbuf_setlen(sl, sl->len - 1);\n+\t\tif (is_slash)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+int modification_validate(struct modification *mod)\n+{\n+\tstruct strbuf slashed = STRBUF_INIT;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (; i < mod->add_len; i++) {\n+\t\terr = validate_refname(mod->add[i]);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\tstrbuf_addstr(&slashed, \"/\");\n+\n+\t\terr = modification_has_ref_with_prefix(mod, slashed.buf);\n+\t\tif (err == 0) {\n+\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\twhile (slashed.len) {\n+\t\t\tstrbuf_trim_component(&slashed);\n+\t\t\terr = modification_has_ref(mod, slashed.buf);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\t}\n+\terr = 0;\n+done:\n+\tstrbuf_release(&slashed);\n+\treturn err;\n+}\ndiff --git a/reftable/refname.h b/reftable/refname.h\nnew file mode 100644\nindex 00000000000..a24b40fcb42\n--- /dev/null\n+++ b/reftable/refname.h\n@@ -0,0 +1,29 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+#ifndef REFNAME_H\n+#define REFNAME_H\n+\n+#include \"reftable-record.h\"\n+#include \"reftable-generic.h\"\n+\n+struct modification {\n+\tstruct reftable_table tab;\n+\n+\tchar **add;\n+\tsize_t add_len;\n+\n+\tchar **del;\n+\tsize_t del_len;\n+};\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz);\n+\n+int modification_validate(struct modification *mod);\n+\n+#endif\ndiff --git a/reftable/refname_test.c b/reftable/refname_test.c\nnew file mode 100644\nindex 00000000000..8645cd93bbd\n--- /dev/null\n+++ b/reftable/refname_test.c\n@@ -0,0 +1,102 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-writer.h\"\n+#include \"system.h\"\n+\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct testcase {\n+\tchar *add;\n+\tchar *del;\n+\tint error_code;\n+};\n+\n+static void test_conflict(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"a/b\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"destination\", /* make sure it's not a symref.\n+\t\t\t\t\t\t*/\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct testcase cases[] = {\n+\t\t{ \"a/b/c\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"b\", NULL, 0 },\n+\t\t{ \"a\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"a\", \"a/b\", 0 },\n+\n+\t\t{ \"p/\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p//q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/./q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/../q\", NULL, REFTABLE_REFNAME_ERROR },\n+\n+\t\t{ \"a/b/c\", \"a/b\", 0 },\n+\t\t{ NULL, \"a//b\", 0 },\n+\t};\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\treftable_table_from_reader(&tab, rd);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct modification mod = {\n+\t\t\t.tab = tab,\n+\t\t};\n+\n+\t\tif (cases[i].add) {\n+\t\t\tmod.add = &cases[i].add;\n+\t\t\tmod.add_len = 1;\n+\t\t}\n+\t\tif (cases[i].del) {\n+\t\t\tmod.del = &cases[i].del;\n+\t\t\tmod.del_len = 1;\n+\t\t}\n+\n+\t\terr = modification_validate(&mod);\n+\t\tEXPECT(err == cases[i].error_code);\n+\t}\n+\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+int refname_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_conflict);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 8087f2da4e6..c8db6852c35 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -8,6 +8,7 @@ int cmd__reftable(int argc, const char **argv)\n \tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"434107","messageId":"0170f20b5e75fad4894d935eccb5eb0c0af31e77.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 18/19] reftable: add dump utility","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:54Z","receivedAt":"2021-08-30T14:58:28Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nprovide a command-line utility for inspecting individual tables, and\ninspecting a complete ref database\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/dump.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 107 insertions(+)\n create mode 100644 reftable/dump.c\n\ndiff --git a/reftable/dump.c b/reftable/dump.c\nnew file mode 100644\nindex 00000000000..155953d1b82\n--- /dev/null\n+++ b/reftable/dump.c\n@@ -0,0 +1,107 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"git-compat-util.h\"\n+#include \"hash.h\"\n+\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+#include \"reftable-stack.h\"\n+#include \"reftable-generic.h\"\n+\n+#include <stddef.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <unistd.h>\n+#include <string.h>\n+\n+static int compact_stack(const char *stackdir)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { 0 };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_stack_compact_all(stack, NULL);\n+\tif (err < 0)\n+\t\tgoto done;\n+done:\n+\tif (stack) {\n+\t\treftable_stack_destroy(stack);\n+\t}\n+\treturn err;\n+}\n+\n+static void print_help(void)\n+{\n+\tprintf(\"usage: dump [-cst] arg\\n\\n\"\n+\t       \"options: \\n\"\n+\t       \"  -c compact\\n\"\n+\t       \"  -t dump table\\n\"\n+\t       \"  -s dump stack\\n\"\n+\t       \"  -6 sha256 hash format\\n\"\n+\t       \"  -h this help\\n\"\n+\t       \"\\n\");\n+}\n+\n+int reftable_dump_main(int argc, char *const *argv)\n+{\n+\tint err = 0;\n+\tint opt_dump_table = 0;\n+\tint opt_dump_stack = 0;\n+\tint opt_compact = 0;\n+\tuint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;\n+\tconst char *arg = NULL, *argv0 = argv[0];\n+\n+\tfor (; argc > 1; argv++, argc--)\n+\t\tif (*argv[1] != '-')\n+\t\t\tbreak;\n+\t\telse if (!strcmp(\"-t\", argv[1]))\n+\t\t\topt_dump_table = 1;\n+\t\telse if (!strcmp(\"-6\", argv[1]))\n+\t\t\topt_hash_id = GIT_SHA256_FORMAT_ID;\n+\t\telse if (!strcmp(\"-s\", argv[1]))\n+\t\t\topt_dump_stack = 1;\n+\t\telse if (!strcmp(\"-c\", argv[1]))\n+\t\t\topt_compact = 1;\n+\t\telse if (!strcmp(\"-?\", argv[1]) || !strcmp(\"-h\", argv[1])) {\n+\t\t\tprint_help();\n+\t\t\treturn 2;\n+\t\t}\n+\n+\tif (argc != 2) {\n+\t\tfprintf(stderr, \"need argument\\n\");\n+\t\tprint_help();\n+\t\treturn 2;\n+\t}\n+\n+\targ = argv[1];\n+\n+\tif (opt_dump_table) {\n+\t\terr = reftable_reader_print_file(arg);\n+\t} else if (opt_dump_stack) {\n+\t\terr = reftable_stack_print_directory(arg, opt_hash_id);\n+\t} else if (opt_compact) {\n+\t\terr = compact_stack(arg);\n+\t}\n+\n+\tif (err < 0) {\n+\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv0, arg,\n+\t\t\treftable_error_str(err));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n-- \ngitgitgadget\n\n"},{"id":"434108","messageId":"4fc6b5cbf1674907f9b36cc6bd678d289b83b48c.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 19/19] Add \"test-tool dump-reftable\" command.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:55Z","receivedAt":"2021-08-30T14:58:31Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis command dumps individual tables or a stack of of tables.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 | 1 +\n t/helper/test-reftable.c | 5 +++++\n t/helper/test-tool.c     | 1 +\n t/helper/test-tool.h     | 1 +\n 4 files changed, 8 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 8be7e05ff99..3d80975c706 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2474,6 +2474,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/dump.o\n REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 996da85f7b5..26b03d7b789 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -14,3 +14,8 @@ int cmd__reftable(int argc, const char **argv)\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n+\n+int cmd__dump_reftable(int argc, const char **argv)\n+{\n+\treturn reftable_dump_main(argc, (char *const *)argv);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f7c888ffda7..338a57b104d 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -61,6 +61,7 @@ static struct test_cmd cmds[] = {\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n \t{ \"reftable\", cmd__reftable },\n+\t{ \"dump-reftable\", cmd__dump_reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 25f77469146..48cee1f4a2d 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -19,6 +19,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);\n int cmd__dump_fsmonitor(int argc, const char **argv);\n int cmd__dump_split_index(int argc, const char **argv);\n int cmd__dump_untracked_cache(int argc, const char **argv);\n+int cmd__dump_reftable(int argc, const char **argv);\n int cmd__example_decorate(int argc, const char **argv);\n int cmd__fast_rebase(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n-- \ngitgitgadget\n"},{"id":"434109","messageId":"b099c42d65d8d57a5b759efef60a279a60a208a7.1630335476.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 17/19] reftable: implement stack, a mutable database of reftable files.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-30T14:57:53Z","receivedAt":"2021-08-30T14:58:31Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |    1 +\n reftable/reftable-stack.h |  128 ++++\n reftable/stack.c          | 1396 +++++++++++++++++++++++++++++++++++++\n reftable/stack.h          |   41 ++\n reftable/stack_test.c     |  949 +++++++++++++++++++++++++\n t/helper/test-reftable.c  |    1 +\n 6 files changed, 2516 insertions(+)\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 827a7190140..8be7e05ff99 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2479,6 +2479,7 @@ REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/refname_test.o\n+REFTABLE_TEST_OBJS += reftable/stack_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h\nnew file mode 100644\nindex 00000000000..1b602dda58a\n--- /dev/null\n+++ b/reftable/reftable-stack.h\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_STACK_H\n+#define REFTABLE_STACK_H\n+\n+#include \"reftable-writer.h\"\n+\n+/*\n+ * The stack presents an interface to a mutable sequence of reftables.\n+\n+ * A stack can be mutated by pushing a table to the top of the stack.\n+\n+ * The reftable_stack automatically compacts files on disk to ensure good\n+ * amortized performance.\n+ *\n+ * For windows and other platforms that cannot have open files as rename\n+ * destinations, concurrent access from multiple processes needs the rand()\n+ * random seed to be randomized.\n+ */\n+struct reftable_stack;\n+\n+/* open a new reftable stack. The tables along with the table list will be\n+ *  stored in 'dir'. Typically, this should be .git/reftables.\n+ */\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config);\n+\n+/* returns the update_index at which a next table should be written. */\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);\n+\n+/* holds a transaction to add tables at the top of a stack. */\n+struct reftable_addition;\n+\n+/*\n+ * returns a new transaction to add reftables to the given stack. As a side\n+ * effect, the ref database is locked.\n+ */\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st);\n+\n+/* Adds a reftable to transaction. */\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg);\n+\n+/* Commits the transaction, releasing the lock. After calling this,\n+ * reftable_addition_destroy should still be called.\n+ */\n+int reftable_addition_commit(struct reftable_addition *add);\n+\n+/* Release all non-committed data from the transaction, and deallocate the\n+ * transaction. Releases the lock if held. */\n+void reftable_addition_destroy(struct reftable_addition *add);\n+\n+/* add a new table to the stack. The write_table function must call\n+ * reftable_writer_set_limits, add refs and return an error value. */\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t  void *write_arg),\n+\t\t       void *write_arg);\n+\n+/* returns the merged_table for seeking. This table is valid until the\n+ * next write or reload, and should not be closed or deleted.\n+ */\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st);\n+\n+/* frees all resources associated with the stack. */\n+void reftable_stack_destroy(struct reftable_stack *st);\n+\n+/* Reloads the stack if necessary. This is very cheap to run if the stack was up\n+ * to date */\n+int reftable_stack_reload(struct reftable_stack *st);\n+\n+/* Policy for expiring reflog entries. */\n+struct reftable_log_expiry_config {\n+\t/* Drop entries older than this timestamp */\n+\tuint64_t time;\n+\n+\t/* Drop older entries */\n+\tuint64_t min_update_index;\n+};\n+\n+/* compacts all reftables into a giant table. Expire reflog entries if config is\n+ * non-NULL */\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+\n+/* heuristically compact unbalanced table stack. */\n+int reftable_stack_auto_compact(struct reftable_stack *st);\n+\n+/* delete stale .ref tables. */\n+int reftable_stack_clean(struct reftable_stack *st);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* convenience function to read a single log. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/* statistics on past compactions. */\n+struct reftable_compaction_stats {\n+\tuint64_t bytes; /* total number of bytes written */\n+\tuint64_t entries_written; /* total number of entries written, including\n+\t\t\t\t     failures. */\n+\tint attempts; /* how often we tried to compact */\n+\tint failures; /* failures happen on concurrent updates */\n+};\n+\n+/* return statistics for compaction up till now. */\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st);\n+\n+/* print the entire stack represented by the directory */\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);\n+\n+#endif\ndiff --git a/reftable/stack.c b/reftable/stack.c\nnew file mode 100644\nindex 00000000000..48e22a6c184\n--- /dev/null\n+++ b/reftable/stack.c\n@@ -0,0 +1,1396 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+#include \"merged.h\"\n+#include \"reader.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-merged.h\"\n+#include \"writer.h\"\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg);\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name);\n+static void reftable_addition_close(struct reftable_addition *add);\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open);\n+\n+static void stack_filename(struct strbuf *dest, struct reftable_stack *st,\n+\t\t\t   const char *name)\n+{\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, st->reftable_dir);\n+\tstrbuf_addstr(dest, \"/\");\n+\tstrbuf_addstr(dest, name);\n+}\n+\n+static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)\n+{\n+\tint *fdp = (int *)arg;\n+\treturn write(*fdp, data, sz);\n+}\n+\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config)\n+{\n+\tstruct reftable_stack *p =\n+\t\treftable_calloc(sizeof(struct reftable_stack));\n+\tstruct strbuf list_file_name = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (config.hash_id == 0) {\n+\t\tconfig.hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\n+\t*dest = NULL;\n+\n+\tstrbuf_reset(&list_file_name);\n+\tstrbuf_addstr(&list_file_name, dir);\n+\tstrbuf_addstr(&list_file_name, \"/tables.list\");\n+\n+\tp->list_file = strbuf_detach(&list_file_name, NULL);\n+\tp->reftable_dir = xstrdup(dir);\n+\tp->config = config;\n+\n+\terr = reftable_stack_reload_maybe_reuse(p, 1);\n+\tif (err < 0) {\n+\t\treftable_stack_destroy(p);\n+\t} else {\n+\t\t*dest = p;\n+\t}\n+\treturn err;\n+}\n+\n+static int fd_read_lines(int fd, char ***namesp)\n+{\n+\toff_t size = lseek(fd, 0, SEEK_END);\n+\tchar *buf = NULL;\n+\tint err = 0;\n+\tif (size < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\terr = lseek(fd, 0, SEEK_SET);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tbuf = reftable_malloc(size + 1);\n+\tif (read(fd, buf, size) != size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\tbuf[size] = 0;\n+\n+\tparse_names(buf, size, namesp);\n+\n+done:\n+\treftable_free(buf);\n+\treturn err;\n+}\n+\n+int read_lines(const char *filename, char ***namesp)\n+{\n+\tint fd = open(filename, O_RDONLY, 0644);\n+\tint err = 0;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\t*namesp = reftable_calloc(sizeof(char *));\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\terr = fd_read_lines(fd, namesp);\n+\tclose(fd);\n+\treturn err;\n+}\n+\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st)\n+{\n+\treturn st->merged;\n+}\n+\n+static int has_name(char **names, const char *name)\n+{\n+\twhile (*names) {\n+\t\tif (!strcmp(*names, name))\n+\t\t\treturn 1;\n+\t\tnames++;\n+\t}\n+\treturn 0;\n+}\n+\n+/* Close and free the stack */\n+void reftable_stack_destroy(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = 0;\n+\tif (st->merged) {\n+\t\treftable_merged_table_free(st->merged);\n+\t\tst->merged = NULL;\n+\t}\n+\n+\terr = read_lines(st->list_file, &names);\n+\tif (err < 0) {\n+\t\tFREE_AND_NULL(names);\n+\t}\n+\n+\tif (st->readers) {\n+\t\tint i = 0;\n+\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\tfor (i = 0; i < st->readers_len; i++) {\n+\t\t\tconst char *name = reader_name(st->readers[i]);\n+\t\t\tstrbuf_reset(&filename);\n+\t\t\tif (names && !has_name(names, name)) {\n+\t\t\t\tstack_filename(&filename, st, name);\n+\t\t\t}\n+\t\t\treftable_reader_free(st->readers[i]);\n+\n+\t\t\tif (filename.len) {\n+\t\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\t\tunlink(filename.buf);\n+\t\t\t}\n+\t\t}\n+\t\tstrbuf_release(&filename);\n+\t\tst->readers_len = 0;\n+\t\tFREE_AND_NULL(st->readers);\n+\t}\n+\tFREE_AND_NULL(st->list_file);\n+\tFREE_AND_NULL(st->reftable_dir);\n+\treftable_free(st);\n+\tfree_names(names);\n+}\n+\n+static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n+\t\t\t\t\t\t   int cur_len)\n+{\n+\tstruct reftable_reader **cur =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * cur_len);\n+\tint i = 0;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tcur[i] = st->readers[i];\n+\t}\n+\treturn cur;\n+}\n+\n+static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+\t\t\t\t      int reuse_open)\n+{\n+\tint cur_len = !st->merged ? 0 : st->merged->stack_len;\n+\tstruct reftable_reader **cur = stack_copy_readers(st, cur_len);\n+\tint err = 0;\n+\tint names_len = names_length(names);\n+\tstruct reftable_reader **new_readers =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * names_len);\n+\tstruct reftable_table *new_tables =\n+\t\treftable_calloc(sizeof(struct reftable_table) * names_len);\n+\tint new_readers_len = 0;\n+\tstruct reftable_merged_table *new_merged = NULL;\n+\tint i;\n+\n+\twhile (*names) {\n+\t\tstruct reftable_reader *rd = NULL;\n+\t\tchar *name = *names++;\n+\n+\t\t/* this is linear; we assume compaction keeps the number of\n+\t\t   tables under control so this is not quadratic. */\n+\t\tint j = 0;\n+\t\tfor (j = 0; reuse_open && j < cur_len; j++) {\n+\t\t\tif (cur[j] && 0 == strcmp(cur[j]->name, name)) {\n+\t\t\t\trd = cur[j];\n+\t\t\t\tcur[j] = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!rd) {\n+\t\t\tstruct reftable_block_source src = { NULL };\n+\t\t\tstruct strbuf table_path = STRBUF_INIT;\n+\t\t\tstack_filename(&table_path, st, name);\n+\n+\t\t\terr = reftable_block_source_from_file(&src,\n+\t\t\t\t\t\t\t      table_path.buf);\n+\t\t\tstrbuf_release(&table_path);\n+\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\n+\t\t\terr = reftable_new_reader(&rd, &src, name);\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\n+\t\tnew_readers[new_readers_len] = rd;\n+\t\treftable_table_from_reader(&new_tables[new_readers_len], rd);\n+\t\tnew_readers_len++;\n+\t}\n+\n+\t/* success! */\n+\terr = reftable_new_merged_table(&new_merged, new_tables,\n+\t\t\t\t\tnew_readers_len, st->config.hash_id);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tnew_tables = NULL;\n+\tst->readers_len = new_readers_len;\n+\tif (st->merged) {\n+\t\tmerged_table_release(st->merged);\n+\t\treftable_merged_table_free(st->merged);\n+\t}\n+\tif (st->readers) {\n+\t\treftable_free(st->readers);\n+\t}\n+\tst->readers = new_readers;\n+\tnew_readers = NULL;\n+\tnew_readers_len = 0;\n+\n+\tnew_merged->suppress_deletions = 1;\n+\tst->merged = new_merged;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tif (cur[i]) {\n+\t\t\tconst char *name = reader_name(cur[i]);\n+\t\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\t\tstack_filename(&filename, st, name);\n+\n+\t\t\treader_close(cur[i]);\n+\t\t\treftable_reader_free(cur[i]);\n+\n+\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\tunlink(filename.buf);\n+\n+\t\t\tstrbuf_release(&filename);\n+\t\t}\n+\t}\n+\n+done:\n+\tfor (i = 0; i < new_readers_len; i++) {\n+\t\treader_close(new_readers[i]);\n+\t\treftable_reader_free(new_readers[i]);\n+\t}\n+\treftable_free(new_readers);\n+\treftable_free(new_tables);\n+\treftable_free(cur);\n+\treturn err;\n+}\n+\n+/* return negative if a before b. */\n+static int tv_cmp(struct timeval *a, struct timeval *b)\n+{\n+\ttime_t diff = a->tv_sec - b->tv_sec;\n+\tint udiff = a->tv_usec - b->tv_usec;\n+\n+\tif (diff != 0)\n+\t\treturn diff;\n+\n+\treturn udiff;\n+}\n+\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open)\n+{\n+\tstruct timeval deadline = { 0 };\n+\tint err = gettimeofday(&deadline, NULL);\n+\tint64_t delay = 0;\n+\tint tries = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tdeadline.tv_sec += 3;\n+\twhile (1) {\n+\t\tchar **names = NULL;\n+\t\tchar **names_after = NULL;\n+\t\tstruct timeval now = { 0 };\n+\t\tint err = gettimeofday(&now, NULL);\n+\t\tint err2 = 0;\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* Only look at deadlines after the first few times. This\n+\t\t   simplifies debugging in GDB */\n+\t\ttries++;\n+\t\tif (tries > 3 && tv_cmp(&now, &deadline) >= 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = read_lines(st->list_file, &names);\n+\t\tif (err < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\tif (err == 0) {\n+\t\t\tfree_names(names);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err != REFTABLE_NOT_EXIST_ERROR) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent\n+\t\t   writer. Check if there was one by checking if the name list\n+\t\t   changed.\n+\t\t*/\n+\t\terr2 = read_lines(st->list_file, &names_after);\n+\t\tif (err2 < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err2;\n+\t\t}\n+\n+\t\tif (names_equal(names_after, names)) {\n+\t\t\tfree_names(names);\n+\t\t\tfree_names(names_after);\n+\t\t\treturn err;\n+\t\t}\n+\t\tfree_names(names);\n+\t\tfree_names(names_after);\n+\n+\t\tdelay = delay + (delay * rand()) / RAND_MAX + 1;\n+\t\tsleep_millisec(delay);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* -1 = error\n+ 0 = up to date\n+ 1 = changed. */\n+static int stack_uptodate(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = read_lines(st->list_file, &names);\n+\tint i = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tfor (i = 0; i < st->readers_len; i++) {\n+\t\tif (!names[i]) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (strcmp(st->readers[i]->name, names[i])) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tif (names[st->merged->stack_len]) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tfree_names(names);\n+\treturn err;\n+}\n+\n+int reftable_stack_reload(struct reftable_stack *st)\n+{\n+\tint err = stack_uptodate(st);\n+\tif (err > 0)\n+\t\treturn reftable_stack_reload_maybe_reuse(st, 1);\n+\treturn err;\n+}\n+\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write)(struct reftable_writer *wr, void *arg),\n+\t\t       void *arg)\n+{\n+\tint err = stack_try_add(st, write, arg);\n+\tif (err < 0) {\n+\t\tif (err == REFTABLE_LOCK_ERROR) {\n+\t\t\t/* Ignore error return, we want to propagate\n+\t\t\t   REFTABLE_LOCK_ERROR.\n+\t\t\t*/\n+\t\t\treftable_stack_reload(st);\n+\t\t}\n+\t\treturn err;\n+\t}\n+\n+\tif (!st->disable_auto_compact)\n+\t\treturn reftable_stack_auto_compact(st);\n+\n+\treturn 0;\n+}\n+\n+static void format_name(struct strbuf *dest, uint64_t min, uint64_t max)\n+{\n+\tchar buf[100];\n+\tuint32_t rnd = (uint32_t)rand();\n+\tsnprintf(buf, sizeof(buf), \"0x%012\" PRIx64 \"-0x%012\" PRIx64 \"-%08x\",\n+\t\t min, max, rnd);\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, buf);\n+}\n+\n+struct reftable_addition {\n+\tint lock_file_fd;\n+\tstruct strbuf lock_file_name;\n+\tstruct reftable_stack *stack;\n+\n+\tchar **new_tables;\n+\tint new_tables_len;\n+\tuint64_t next_update_index;\n+};\n+\n+#define REFTABLE_ADDITION_INIT                \\\n+\t{                                     \\\n+\t\t.lock_file_name = STRBUF_INIT \\\n+\t}\n+\n+static int reftable_stack_init_addition(struct reftable_addition *add,\n+\t\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tadd->stack = st;\n+\n+\tstrbuf_reset(&add->lock_file_name);\n+\tstrbuf_addstr(&add->lock_file_name, st->list_file);\n+\tstrbuf_addstr(&add->lock_file_name, \".lock\");\n+\n+\tadd->lock_file_fd = open(add->lock_file_name.buf,\n+\t\t\t\t O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (add->lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = REFTABLE_LOCK_ERROR;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\terr = stack_uptodate(st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 1) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->next_update_index = reftable_stack_next_update_index(st);\n+done:\n+\tif (err) {\n+\t\treftable_addition_close(add);\n+\t}\n+\treturn err;\n+}\n+\n+static void reftable_addition_close(struct reftable_addition *add)\n+{\n+\tint i = 0;\n+\tstruct strbuf nm = STRBUF_INIT;\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstack_filename(&nm, add->stack, add->new_tables[i]);\n+\t\tunlink(nm.buf);\n+\t\treftable_free(add->new_tables[i]);\n+\t\tadd->new_tables[i] = NULL;\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\tif (add->lock_file_fd > 0) {\n+\t\tclose(add->lock_file_fd);\n+\t\tadd->lock_file_fd = 0;\n+\t}\n+\tif (add->lock_file_name.len > 0) {\n+\t\tunlink(add->lock_file_name.buf);\n+\t\tstrbuf_release(&add->lock_file_name);\n+\t}\n+\n+\tstrbuf_release(&nm);\n+}\n+\n+void reftable_addition_destroy(struct reftable_addition *add)\n+{\n+\tif (!add) {\n+\t\treturn;\n+\t}\n+\treftable_addition_close(add);\n+\treftable_free(add);\n+}\n+\n+int reftable_addition_commit(struct reftable_addition *add)\n+{\n+\tstruct strbuf table_list = STRBUF_INIT;\n+\tint i = 0;\n+\tint err = 0;\n+\tif (add->new_tables_len == 0)\n+\t\tgoto done;\n+\n+\tfor (i = 0; i < add->stack->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->stack->readers[i]->name);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->new_tables[i]);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\n+\terr = write(add->lock_file_fd, table_list.buf, table_list.len);\n+\tstrbuf_release(&table_list);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = close(add->lock_file_fd);\n+\tadd->lock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(add->lock_file_name.buf, add->stack->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\t/* success, no more state to clean up. */\n+\tstrbuf_release(&add->lock_file_name);\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\treftable_free(add->new_tables[i]);\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\terr = reftable_stack_reload(add->stack);\n+done:\n+\treftable_addition_close(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tstruct reftable_addition empty = REFTABLE_ADDITION_INIT;\n+\t*dest = reftable_calloc(sizeof(**dest));\n+\t**dest = empty;\n+\terr = reftable_stack_init_addition(*dest, st);\n+\tif (err) {\n+\t\treftable_free(*dest);\n+\t\t*dest = NULL;\n+\t}\n+\treturn err;\n+}\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg)\n+{\n+\tstruct reftable_addition add = REFTABLE_ADDITION_INIT;\n+\tint err = reftable_stack_init_addition(&add, st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tif (err > 0) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_addition_add(&add, write_table, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_addition_commit(&add);\n+done:\n+\treftable_addition_close(&add);\n+\treturn err;\n+}\n+\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf tab_file_name = STRBUF_INIT;\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\tint tab_fd = 0;\n+\n+\tstrbuf_reset(&next_name);\n+\tformat_name(&next_name, add->next_update_index, add->next_update_index);\n+\n+\tstack_filename(&temp_tab_file_name, add->stack, next_name.buf);\n+\tstrbuf_addstr(&temp_tab_file_name, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab_file_name.buf);\n+\tif (tab_fd < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd,\n+\t\t\t\t &add->stack->config);\n+\terr = write_table(wr, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_writer_close(wr);\n+\tif (err == REFTABLE_EMPTY_TABLE_ERROR) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = stack_check_addition(add->stack, temp_tab_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (wr->min_update_index < add->next_update_index) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tformat_name(&next_name, wr->min_update_index, wr->max_update_index);\n+\tstrbuf_addstr(&next_name, \".ref\");\n+\n+\tstack_filename(&tab_file_name, add->stack, next_name.buf);\n+\n+\t/*\n+\t  On windows, this relies on rand() picking a unique destination name.\n+\t  Maybe we should do retry loop as well?\n+\t */\n+\terr = rename(temp_tab_file_name.buf, tab_file_name.buf);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->new_tables = reftable_realloc(add->new_tables,\n+\t\t\t\t\t   sizeof(*add->new_tables) *\n+\t\t\t\t\t\t   (add->new_tables_len + 1));\n+\tadd->new_tables[add->new_tables_len] = strbuf_detach(&next_name, NULL);\n+\tadd->new_tables_len++;\n+done:\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (temp_tab_file_name.len > 0) {\n+\t\tunlink(temp_tab_file_name.buf);\n+\t}\n+\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&tab_file_name);\n+\tstrbuf_release(&next_name);\n+\treftable_writer_free(wr);\n+\treturn err;\n+}\n+\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st)\n+{\n+\tint sz = st->merged->stack_len;\n+\tif (sz > 0)\n+\t\treturn reftable_reader_max_update_index(st->readers[sz - 1]) +\n+\t\t       1;\n+\treturn 1;\n+}\n+\n+static int stack_compact_locked(struct reftable_stack *st, int first, int last,\n+\t\t\t\tstruct strbuf *temp_tab,\n+\t\t\t\tstruct reftable_log_expiry_config *config)\n+{\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tint tab_fd = -1;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\n+\tformat_name(&next_name,\n+\t\t    reftable_reader_min_update_index(st->readers[first]),\n+\t\t    reftable_reader_max_update_index(st->readers[last]));\n+\n+\tstack_filename(temp_tab, st, next_name.buf);\n+\tstrbuf_addstr(temp_tab, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab->buf);\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);\n+\n+\terr = stack_write_compact(st, wr, first, last, config);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = reftable_writer_close(wr);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\n+done:\n+\treftable_writer_free(wr);\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (err != 0 && temp_tab->len > 0) {\n+\t\tunlink(temp_tab->buf);\n+\t\tstrbuf_release(temp_tab);\n+\t}\n+\tstrbuf_release(&next_name);\n+\treturn err;\n+}\n+\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\tint subtabs_len = last - first + 1;\n+\tstruct reftable_table *subtabs = reftable_calloc(\n+\t\tsizeof(struct reftable_table) * (last - first + 1));\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err = 0;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\n+\tuint64_t entries = 0;\n+\n+\tint i = 0, j = 0;\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct reftable_reader *t = st->readers[i];\n+\t\treftable_table_from_reader(&subtabs[j++], t);\n+\t\tst->stats.bytes += t->size;\n+\t}\n+\treftable_writer_set_limits(wr, st->readers[first]->min_update_index,\n+\t\t\t\t   st->readers[last]->max_update_index);\n+\n+\terr = reftable_new_merged_table(&mt, subtabs, subtabs_len,\n+\t\t\t\t\tst->config.hash_id);\n+\tif (err < 0) {\n+\t\treftable_free(subtabs);\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_merged_table_seek_ref(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (first == 0 && reftable_ref_record_is_deletion(&ref)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_ref(wr, &ref);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_merged_table_seek_log(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (first == 0 && reftable_log_record_is_deletion(&log)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->min_update_index > 0 &&\n+\t\t    log.update_index < config->min_update_index) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->time > 0 &&\n+\t\t    log.value.update.time < config->time) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_log(wr, &log);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\tif (mt) {\n+\t\tmerged_table_release(mt);\n+\t\treftable_merged_table_free(mt);\n+\t}\n+\treftable_ref_record_release(&ref);\n+\treftable_log_record_release(&log);\n+\tst->stats.entries_written += entries;\n+\treturn err;\n+}\n+\n+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */\n+static int stack_compact_range(struct reftable_stack *st, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *expiry)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf new_table_name = STRBUF_INIT;\n+\tstruct strbuf lock_file_name = STRBUF_INIT;\n+\tstruct strbuf ref_list_contents = STRBUF_INIT;\n+\tstruct strbuf new_table_path = STRBUF_INIT;\n+\tint err = 0;\n+\tint have_lock = 0;\n+\tint lock_file_fd = 0;\n+\tint compact_count = last - first + 1;\n+\tchar **listp = NULL;\n+\tchar **delete_on_success =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tchar **subtable_locks =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tint i = 0;\n+\tint j = 0;\n+\tint is_empty_table = 0;\n+\n+\tif (first > last || (!expiry && first == last)) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\tst->stats.attempts++;\n+\n+\tstrbuf_reset(&lock_file_name);\n+\tstrbuf_addstr(&lock_file_name, st->list_file);\n+\tstrbuf_addstr(&lock_file_name, \".lock\");\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\t/* Don't want to write to the lock for now.  */\n+\tclose(lock_file_fd);\n+\tlock_file_fd = 0;\n+\n+\thave_lock = 1;\n+\terr = stack_uptodate(st);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct strbuf subtab_file_name = STRBUF_INIT;\n+\t\tstruct strbuf subtab_lock = STRBUF_INIT;\n+\t\tint sublock_file_fd = -1;\n+\n+\t\tstack_filename(&subtab_file_name, st,\n+\t\t\t       reader_name(st->readers[i]));\n+\n+\t\tstrbuf_reset(&subtab_lock);\n+\t\tstrbuf_addbuf(&subtab_lock, &subtab_file_name);\n+\t\tstrbuf_addstr(&subtab_lock, \".lock\");\n+\n+\t\tsublock_file_fd = open(subtab_lock.buf,\n+\t\t\t\t       O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\t\tif (sublock_file_fd > 0) {\n+\t\t\tclose(sublock_file_fd);\n+\t\t} else if (sublock_file_fd < 0) {\n+\t\t\tif (errno == EEXIST) {\n+\t\t\t\terr = 1;\n+\t\t\t} else {\n+\t\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\t}\n+\t\t}\n+\n+\t\tsubtable_locks[j] = subtab_lock.buf;\n+\t\tdelete_on_success[j] = subtab_file_name.buf;\n+\t\tj++;\n+\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\terr = unlink(lock_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\thave_lock = 0;\n+\n+\terr = stack_compact_locked(st, first, last, &temp_tab_file_name,\n+\t\t\t\t   expiry);\n+\t/* Compaction + tombstones can create an empty table out of non-empty\n+\t * tables. */\n+\tis_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);\n+\tif (is_empty_table) {\n+\t\terr = 0;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\thave_lock = 1;\n+\n+\tformat_name(&new_table_name, st->readers[first]->min_update_index,\n+\t\t    st->readers[last]->max_update_index);\n+\tstrbuf_addstr(&new_table_name, \".ref\");\n+\n+\tstack_filename(&new_table_path, st, new_table_name.buf);\n+\n+\tif (!is_empty_table) {\n+\t\t/* retry? */\n+\t\terr = rename(temp_tab_file_name.buf, new_table_path.buf);\n+\t\tif (err < 0) {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < first; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tif (!is_empty_table) {\n+\t\tstrbuf_addbuf(&ref_list_contents, &new_table_name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tfor (i = last + 1; i < st->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\n+\terr = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\terr = close(lock_file_fd);\n+\tlock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(lock_file_name.buf, st->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\thave_lock = 0;\n+\n+\t/* Reload the stack before deleting. On windows, we can only delete the\n+\t   files after we closed them.\n+\t*/\n+\terr = reftable_stack_reload_maybe_reuse(st, first < last);\n+\n+\tlistp = delete_on_success;\n+\twhile (*listp) {\n+\t\tif (strcmp(*listp, new_table_path.buf)) {\n+\t\t\tunlink(*listp);\n+\t\t}\n+\t\tlistp++;\n+\t}\n+\n+done:\n+\tfree_names(delete_on_success);\n+\n+\tlistp = subtable_locks;\n+\twhile (*listp) {\n+\t\tunlink(*listp);\n+\t\tlistp++;\n+\t}\n+\tfree_names(subtable_locks);\n+\tif (lock_file_fd > 0) {\n+\t\tclose(lock_file_fd);\n+\t\tlock_file_fd = 0;\n+\t}\n+\tif (have_lock) {\n+\t\tunlink(lock_file_name.buf);\n+\t}\n+\tstrbuf_release(&new_table_name);\n+\tstrbuf_release(&new_table_path);\n+\tstrbuf_release(&ref_list_contents);\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&lock_file_name);\n+\treturn err;\n+}\n+\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\treturn stack_compact_range(st, 0, st->merged->stack_len - 1, config);\n+}\n+\n+static int stack_compact_range_stats(struct reftable_stack *st, int first,\n+\t\t\t\t     int last,\n+\t\t\t\t     struct reftable_log_expiry_config *config)\n+{\n+\tint err = stack_compact_range(st, first, last, config);\n+\tif (err > 0) {\n+\t\tst->stats.failures++;\n+\t}\n+\treturn err;\n+}\n+\n+static int segment_size(struct segment *s)\n+{\n+\treturn s->end - s->start;\n+}\n+\n+int fastlog2(uint64_t sz)\n+{\n+\tint l = 0;\n+\tif (sz == 0)\n+\t\treturn 0;\n+\tfor (; sz; sz /= 2) {\n+\t\tl++;\n+\t}\n+\treturn l - 1;\n+}\n+\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)\n+{\n+\tstruct segment *segs = reftable_calloc(sizeof(struct segment) * n);\n+\tint next = 0;\n+\tstruct segment cur = { 0 };\n+\tint i = 0;\n+\n+\tif (n == 0) {\n+\t\t*seglen = 0;\n+\t\treturn segs;\n+\t}\n+\tfor (i = 0; i < n; i++) {\n+\t\tint log = fastlog2(sizes[i]);\n+\t\tif (cur.log != log && cur.bytes > 0) {\n+\t\t\tstruct segment fresh = {\n+\t\t\t\t.start = i,\n+\t\t\t};\n+\n+\t\t\tsegs[next++] = cur;\n+\t\t\tcur = fresh;\n+\t\t}\n+\n+\t\tcur.log = log;\n+\t\tcur.end = i + 1;\n+\t\tcur.bytes += sizes[i];\n+\t}\n+\tsegs[next++] = cur;\n+\t*seglen = next;\n+\treturn segs;\n+}\n+\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, sizes, n);\n+\tstruct segment min_seg = {\n+\t\t.log = 64,\n+\t};\n+\tint i = 0;\n+\tfor (i = 0; i < seglen; i++) {\n+\t\tif (segment_size(&segs[i]) == 1) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (segs[i].log < min_seg.log) {\n+\t\t\tmin_seg = segs[i];\n+\t\t}\n+\t}\n+\n+\twhile (min_seg.start > 0) {\n+\t\tint prev = min_seg.start - 1;\n+\t\tif (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmin_seg.start = prev;\n+\t\tmin_seg.bytes += sizes[prev];\n+\t}\n+\n+\treftable_free(segs);\n+\treturn min_seg;\n+}\n+\n+static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes =\n+\t\treftable_calloc(sizeof(uint64_t) * st->merged->stack_len);\n+\tint version = (st->config.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2;\n+\tint overhead = header_size(version) - 1;\n+\tint i = 0;\n+\tfor (i = 0; i < st->merged->stack_len; i++) {\n+\t\tsizes[i] = st->readers[i]->size - overhead;\n+\t}\n+\treturn sizes;\n+}\n+\n+int reftable_stack_auto_compact(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes = stack_table_sizes_for_compaction(st);\n+\tstruct segment seg =\n+\t\tsuggest_compaction_segment(sizes, st->merged->stack_len);\n+\treftable_free(sizes);\n+\tif (segment_size(&seg) > 0)\n+\t\treturn stack_compact_range_stats(st, seg.start, seg.end - 1,\n+\t\t\t\t\t\t NULL);\n+\n+\treturn 0;\n+}\n+\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st)\n+{\n+\treturn &st->stats;\n+}\n+\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_table tab = { NULL };\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\treturn reftable_table_read_ref(&tab, refname, ref);\n+}\n+\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_merged_table *mt = reftable_stack_merged_table(st);\n+\tint err = reftable_merged_table_seek_log(mt, &it, refname);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_log(&it, log);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(log->refname, refname) ||\n+\t    reftable_log_record_is_deletion(log)) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tif (err) {\n+\t\treftable_log_record_release(log);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name)\n+{\n+\tint err = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct reftable_ref_record *refs = NULL;\n+\tstruct reftable_iterator it = { NULL };\n+\tint cap = 0;\n+\tint len = 0;\n+\tint i = 0;\n+\n+\tif (st->config.skip_name_check)\n+\t\treturn 0;\n+\n+\terr = reftable_block_source_from_file(&src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tif (err > 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (len >= cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\trefs = reftable_realloc(refs, cap * sizeof(refs[0]));\n+\t\t}\n+\n+\t\trefs[len++] = ref;\n+\t}\n+\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\n+\terr = validate_ref_record_addition(tab, refs, len);\n+\n+done:\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t}\n+\n+\tfree(refs);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\treturn err;\n+}\n+\n+static int is_table_name(const char *s)\n+{\n+\tconst char *dot = strrchr(s, '.');\n+\treturn dot && !strcmp(dot, \".ref\");\n+}\n+\n+static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max,\n+\t\t\t\t     const char *name)\n+{\n+\tint err = 0;\n+\tuint64_t update_idx = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct strbuf table_path = STRBUF_INIT;\n+\tstack_filename(&table_path, st, name);\n+\n+\terr = reftable_block_source_from_file(&src, table_path.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tupdate_idx = reftable_reader_max_update_index(rd);\n+\treftable_reader_free(rd);\n+\n+\tif (update_idx <= max) {\n+\t\tunlink(table_path.buf);\n+\t}\n+done:\n+\tstrbuf_release(&table_path);\n+}\n+\n+static int reftable_stack_clean_locked(struct reftable_stack *st)\n+{\n+\tuint64_t max = reftable_merged_table_max_update_index(\n+\t\treftable_stack_merged_table(st));\n+\tDIR *dir = opendir(st->reftable_dir);\n+\tstruct dirent *d = NULL;\n+\tif (!dir) {\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tint i = 0;\n+\t\tint found = 0;\n+\t\tif (!is_table_name(d->d_name))\n+\t\t\tcontinue;\n+\n+\t\tfor (i = 0; !found && i < st->readers_len; i++) {\n+\t\t\tfound = !strcmp(reader_name(st->readers[i]), d->d_name);\n+\t\t}\n+\t\tif (found)\n+\t\t\tcontinue;\n+\n+\t\tremove_maybe_stale_table(st, max, d->d_name);\n+\t}\n+\n+\tclosedir(dir);\n+\treturn 0;\n+}\n+\n+int reftable_stack_clean(struct reftable_stack *st)\n+{\n+\tstruct reftable_addition *add = NULL;\n+\tint err = reftable_stack_new_addition(&add, st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_reload(st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_clean_locked(st);\n+\n+done:\n+\treftable_addition_destroy(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { .hash_id = hash_id };\n+\tstruct reftable_merged_table *merged = NULL;\n+\tstruct reftable_table table = { NULL };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tmerged = reftable_stack_merged_table(stack);\n+\treftable_table_from_merged_table(&table, merged);\n+\terr = reftable_table_print(&table);\n+done:\n+\tif (stack)\n+\t\treftable_stack_destroy(stack);\n+\treturn err;\n+}\ndiff --git a/reftable/stack.h b/reftable/stack.h\nnew file mode 100644\nindex 00000000000..f57005846e5\n--- /dev/null\n+++ b/reftable/stack.h\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef STACK_H\n+#define STACK_H\n+\n+#include \"system.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-stack.h\"\n+\n+struct reftable_stack {\n+\tchar *list_file;\n+\tchar *reftable_dir;\n+\tint disable_auto_compact;\n+\n+\tstruct reftable_write_options config;\n+\n+\tstruct reftable_reader **readers;\n+\tsize_t readers_len;\n+\tstruct reftable_merged_table *merged;\n+\tstruct reftable_compaction_stats stats;\n+};\n+\n+int read_lines(const char *filename, char ***lines);\n+\n+struct segment {\n+\tint start, end;\n+\tint log;\n+\tuint64_t bytes;\n+};\n+\n+int fastlog2(uint64_t sz);\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n);\n+\n+#endif\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nnew file mode 100644\nindex 00000000000..7a4641ab601\n--- /dev/null\n+++ b/reftable/stack_test.c\n@@ -0,0 +1,949 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+\n+#include \"reftable-reader.h\"\n+#include \"merged.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+#include <sys/types.h>\n+#include <dirent.h>\n+\n+static void clear_dir(const char *dirname)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, dirname);\n+\tremove_dir_recursively(&path, 0);\n+\tstrbuf_release(&path);\n+}\n+\n+static int count_dir_entries(const char *dirname)\n+{\n+\tDIR *dir = opendir(dirname);\n+\tint len = 0;\n+\tstruct dirent *d;\n+\tif (dir == NULL)\n+\t\treturn 0;\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tif (!strcmp(d->d_name, \"..\") || !strcmp(d->d_name, \".\"))\n+\t\t\tcontinue;\n+\t\tlen++;\n+\t}\n+\tclosedir(dir);\n+\treturn len;\n+}\n+\n+// Work linenumber into the tempdir, so we can see which tests forget to\n+// cleanup.\n+static char *get_tmp_template(int linenumber)\n+{\n+\tconst char *tmp = getenv(\"TMPDIR\");\n+\tstatic char template[1024];\n+\tsnprintf(template, sizeof(template) - 1, \"%s/stack_test-%d.XXXXXX\",\n+\t\t tmp ? tmp : \"/tmp\", linenumber);\n+\treturn template;\n+}\n+\n+static char *get_tmp_dir(int linenumber)\n+{\n+\tchar *dir = get_tmp_template(linenumber);\n+\tEXPECT(mkdtemp(dir));\n+\treturn dir;\n+}\n+\n+static void test_read_file(void)\n+{\n+\tchar *fn = get_tmp_template(__LINE__);\n+\tint fd = mkstemp(fn);\n+\tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n+\tint n, err;\n+\tchar **names = NULL;\n+\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tint i = 0;\n+\n+\tEXPECT(fd > 0);\n+\tn = write(fd, out, strlen(out));\n+\tEXPECT(n == strlen(out));\n+\terr = close(fd);\n+\tEXPECT(err >= 0);\n+\n+\terr = read_lines(fn, &names);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; names[i]; i++) {\n+\t\tEXPECT(0 == strcmp(want[i], names[i]));\n+\t}\n+\tfree_names(names);\n+\tremove(fn);\n+}\n+\n+static void test_parse_names(void)\n+{\n+\tchar buf[] = \"line\\n\";\n+\tchar **names = NULL;\n+\tparse_names(buf, strlen(buf), &names);\n+\n+\tEXPECT(NULL != names[0]);\n+\tEXPECT(0 == strcmp(names[0], \"line\"));\n+\tEXPECT(NULL == names[1]);\n+\tfree_names(names);\n+}\n+\n+static void test_names_equal(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tchar *c[] = { \"a\", \"b\", NULL };\n+\n+\tEXPECT(names_equal(a, a));\n+\tEXPECT(!names_equal(a, b));\n+\tEXPECT(!names_equal(a, c));\n+}\n+\n+static int write_test_ref(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct reftable_ref_record *ref = arg;\n+\treftable_writer_set_limits(wr, ref->update_index, ref->update_index);\n+\treturn reftable_writer_add_ref(wr, ref);\n+}\n+\n+struct write_log_arg {\n+\tstruct reftable_log_record *log;\n+\tuint64_t update_index;\n+};\n+\n+static int write_test_log(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct write_log_arg *wla = arg;\n+\n+\treftable_writer_set_limits(wr, wla->update_index, wla->update_index);\n+\treturn reftable_writer_add_log(wr, wla->log);\n+}\n+\n+static void test_reftable_stack_add_one(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\tprintf(\"testing print functionality:\\n\");\n+\terr = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_uptodate(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL;\n+\tstruct reftable_stack *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"branch2\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\n+\t/* simulate multi-process access to the same stack\n+\t   by creating two stacks for the same directory.\n+\t */\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st1, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_LOCK_ERROR);\n+\n+\terr = reftable_stack_reload(st2);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT_ERR(err);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_transaction_api(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_addition *add = NULL;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_new_addition(&add, st);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_add(add, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_commit(add);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(REFTABLE_REF_SYMREF == dest.value_type);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_validate_refname(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint i;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"a/b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tchar *additions[] = { \"a\", \"a/b/c\" };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(additions); i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = additions[i],\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT(err == REFTABLE_NAME_CONFLICT);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static int write_error(struct reftable_writer *wr, void *arg)\n+{\n+\treturn *((int *)arg);\n+}\n+\n+static void test_reftable_stack_update_index_check(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"name1\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"name2\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_lock_failure(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err, i;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {\n+\t\terr = reftable_stack_add(st, &write_error, &i);\n+\t\tEXPECT(err == i);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_add(void)\n+{\n+\tint i = 0;\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t.exact_log_message = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tst->disable_auto_compact = 1;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tset_test_hash(refs[i].value.val1, i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = N + i + 1;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_ref_record dest = { NULL };\n+\n+\t\tint err = reftable_stack_read_ref(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_ref_record_equal(&dest, refs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_ref_record_release(&dest);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_log_record dest = { NULL };\n+\t\tint err = reftable_stack_read_log(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_log_record_equal(&dest, logs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&dest);\n+\t}\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_log_normalize(void)\n+{\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t0,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tuint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };\n+\n+\tstruct reftable_log_record input = { .refname = \"branch\",\n+\t\t\t\t\t     .update_index = 1,\n+\t\t\t\t\t     .value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t\t\t     .value = { .update = {\n+\t\t\t\t\t\t\t\t.new_hash = h1,\n+\t\t\t\t\t\t\t\t.old_hash = h2,\n+\t\t\t\t\t\t\t} } };\n+\tstruct reftable_log_record dest = {\n+\t\t.update_index = 0,\n+\t};\n+\tstruct write_log_arg arg = {\n+\t\t.log = &input,\n+\t\t.update_index = 1,\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tinput.value.update.message = \"one\\ntwo\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tinput.value.update.message = \"one\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n+\n+\tinput.value.update.message = \"two\\n\";\n+\targ.update_index = 2;\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"two\\n\"));\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\treftable_log_record_release(&dest);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_tombstone(void)\n+{\n+\tint i = 0;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\tstruct reftable_ref_record dest = { NULL };\n+\tstruct reftable_log_record log_dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\t/* even entries add the refs, odd entries delete them. */\n+\tfor (i = 0; i < N; i++) {\n+\t\tconst char *buf = \"branch\";\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\tif (i % 2 == 0) {\n+\t\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(refs[i].value.val1, i);\n+\t\t}\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\t/* update_index is part of the key. */\n+\t\tlogs[i].update_index = 42;\n+\t\tif (i % 2 == 0) {\n+\t\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\t\tlogs[i].value.update.new_hash =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t\t\tlogs[i].value.update.email =\n+\t\t\t\txstrdup(\"identity@invalid\");\n+\t\t}\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_log_record_release(&log_dest);\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\treftable_log_record_release(&log_dest);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_hash_id(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"master\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"target\",\n+\t\t.update_index = 1,\n+\t};\n+\tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n+\tstruct reftable_stack *st32 = NULL;\n+\tstruct reftable_write_options cfg_default = { 0 };\n+\tstruct reftable_stack *st_default = NULL;\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* can't read it with the wrong hash ID. */\n+\terr = reftable_new_stack(&st32, dir, cfg32);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\t/* check that we can read it back with default config too. */\n+\terr = reftable_new_stack(&st_default, dir, cfg_default);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st_default, \"master\", &dest);\n+\tEXPECT_ERR(err);\n+\n+\tEXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st_default);\n+\tclear_dir(dir);\n+}\n+\n+static void test_log2(void)\n+{\n+\tEXPECT(1 == fastlog2(3));\n+\tEXPECT(2 == fastlog2(4));\n+\tEXPECT(2 == fastlog2(5));\n+}\n+\n+static void test_sizes_to_segments(void)\n+{\n+\tuint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };\n+\t/* .................0  1  2  3  4  5 */\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(segs[2].log == 3);\n+\tEXPECT(segs[2].start == 5);\n+\tEXPECT(segs[2].end == 6);\n+\n+\tEXPECT(segs[1].log == 2);\n+\tEXPECT(segs[1].start == 2);\n+\tEXPECT(segs[1].end == 5);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_empty(void)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, NULL, 0);\n+\tEXPECT(seglen == 0);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_all_equal(void)\n+{\n+\tuint64_t sizes[] = { 5, 5 };\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(seglen == 1);\n+\tEXPECT(segs[0].start == 0);\n+\tEXPECT(segs[0].end == 2);\n+\treftable_free(segs);\n+}\n+\n+static void test_suggest_compaction_segment(void)\n+{\n+\tuint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };\n+\t/* .................0    1    2  3   4  5  6 */\n+\tstruct segment min =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(min.start == 2);\n+\tEXPECT(min.end == 7);\n+}\n+\n+static void test_suggest_compaction_segment_nothing(void)\n+{\n+\tuint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };\n+\tstruct segment result =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(result.start == result.end);\n+}\n+\n+static void test_reflog_expire(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_log_record logs[20] = { { NULL } };\n+\tint N = ARRAY_SIZE(logs) - 1;\n+\tint i = 0;\n+\tint err;\n+\tstruct reftable_log_expiry_config expiry = {\n+\t\t.time = 10,\n+\t};\n+\tstruct reftable_log_record log = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = i;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.time = i;\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[9].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[11].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\texpiry.min_update_index = 15;\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[14].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[16].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i <= N; i++) {\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+\treftable_log_record_release(&log);\n+}\n+\n+static int write_nothing(struct reftable_writer *wr, void *arg)\n+{\n+\treftable_writer_set_limits(wr, 1, 1);\n+\treturn 0;\n+}\n+\n+static void test_empty_add(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_stack *st2 = NULL;\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_nothing, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tclear_dir(dir);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st2);\n+}\n+\n+static void test_reftable_stack_auto_compaction(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 100;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\n+\t\tEXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));\n+\t}\n+\n+\tEXPECT(reftable_stack_compaction_stats(st)->entries_written <\n+\t       (uint64_t)(N * fastlog2(N)));\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\tclear_dir(dir);\n+}\n+\n+static void unclean_stack_close(struct reftable_stack *st)\n+{\n+\t// break abstraction boundary to simulate unclean shutdown.\n+\tint i = 0;\n+\tfor (; i < st->readers_len; i++) {\n+\t\treftable_reader_free(st->readers[i]);\n+\t}\n+\tst->readers_len = 0;\n+\tFREE_AND_NULL(st->readers);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent_clean(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tunclean_stack_close(st1);\n+\tunclean_stack_close(st2);\n+\n+\terr = reftable_new_stack(&st3, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_clean(st3);\n+\tEXPECT_ERR(err);\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\treftable_stack_destroy(st3);\n+\n+\tclear_dir(dir);\n+}\n+\n+int stack_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_empty_add);\n+\tRUN_TEST(test_log2);\n+\tRUN_TEST(test_names_equal);\n+\tRUN_TEST(test_parse_names);\n+\tRUN_TEST(test_read_file);\n+\tRUN_TEST(test_reflog_expire);\n+\tRUN_TEST(test_reftable_stack_add);\n+\tRUN_TEST(test_reftable_stack_add_one);\n+\tRUN_TEST(test_reftable_stack_auto_compaction);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent_clean);\n+\tRUN_TEST(test_reftable_stack_hash_id);\n+\tRUN_TEST(test_reftable_stack_lock_failure);\n+\tRUN_TEST(test_reftable_stack_log_normalize);\n+\tRUN_TEST(test_reftable_stack_tombstone);\n+\tRUN_TEST(test_reftable_stack_transaction_api);\n+\tRUN_TEST(test_reftable_stack_update_index_check);\n+\tRUN_TEST(test_reftable_stack_uptodate);\n+\tRUN_TEST(test_reftable_stack_validate_refname);\n+\tRUN_TEST(test_sizes_to_segments);\n+\tRUN_TEST(test_sizes_to_segments_all_equal);\n+\tRUN_TEST(test_sizes_to_segments_empty);\n+\tRUN_TEST(test_suggest_compaction_segment);\n+\tRUN_TEST(test_suggest_compaction_segment_nothing);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c8db6852c35..996da85f7b5 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -10,6 +10,7 @@ int cmd__reftable(int argc, const char **argv)\n \trecord_test_main(argc, argv);\n \trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n+\tstack_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"434111","messageId":"CAFQ2z_P-AE9weWbu-p-ivw1OgSk_hU0j8bFWOU-SZX510_n42w@mail.gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Han-Wen Nienhuys","fromEmail":"hanwen@google.com","sentAt":"2021-08-30T15:22:18Z","receivedAt":"2021-08-30T15:22:33Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"On Mon, Aug 30, 2021 at 5:05 PM Han-Wen Nienhuys via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> The reftable format is described in Documentation/technical/reftable.txt.\n>\n> This is a fully reentrant implementation of reading and writing the reftable\n> file format, and should be suitable for embedding in libgit2 too. It does\n> not hook the code up to git to function as a ref storage backend yet.\n\nJunio, per discussion with AEvar, let's kick out the full reftable\ntopic from next, and put in this topic. This is unlikely to have\nconflicts with any other efforts, and is necessary for the full\nreftable support anyway.\n\n-- \nHan-Wen Nienhuys - Google Munich\nI work 80%. Don't expect answers from me on Fridays.\n--\nGoogle Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich\nRegistergericht und -nummer: Hamburg, HRB 86891\nSitz der Gesellschaft: Hamburg\nGeschäftsführer: Paul Manicle, Halimah DeLaine Prado\n"},{"id":"434489","messageId":"20210902061219.44655-1-carenas@gmail.com","threadId":"56405","inReplyTo":"aa9b8e05dbdd8c6ad5ff687b5a1aefe31d78853b.1630335476.git.gitgitgadget@gmail.com","subject":"[PATCH] fixup! Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-02T06:12:19Z","receivedAt":"2021-09-02T06:12:34Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"OpenBSD 7.0 (currently in beta) comes with zlib 1.2.11 and no longer needs\nthis compatibility layer.\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n config.mak.uname | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 019c88d5df..e3b7343ed2 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -256,7 +256,10 @@ ifeq ($(uname_S),FreeBSD)\n \tFILENO_IS_A_MACRO = UnfortunatelyYes\n endif\n ifeq ($(uname_S),OpenBSD)\n-\tNO_UNCOMPRESS2 = YesPlease\n+\t# Versions < 7.0 need compatibility layer\n+\tifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n+\t\tNO_UNCOMPRESS2 = UnfortunatelyYes\n+\tendif\n \tNO_STRCASESTR = YesPlease\n \tNO_MEMMEM = YesPlease\n \tUSE_ST_TIMESPEC = YesPlease\n-- \n2.33.0.481.g26d3bed244\n\n"},{"id":"435033","messageId":"20210908074557.38141-1-carenas@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH 0/4] fixup for hn/reftable","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-08T07:45:53Z","receivedAt":"2021-09-08T07:46:12Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"DEVELOPER=1 wasn't working well in OpenBSD until cb/makefile-apple-clang\n(which just hit next), so I missed it in my previous run.\n\nhopefully it is not too late to fix this, otherwise.\n\nAs usual they should apply to the patch they refer to and are based on the\nlast reroll in seen (tip 1427aef0bd as of e881a4ab6c (Merge branch\n'hn/reftable' into seen, 2021-09-07)\n\nExcept from the last patch (and two other minor refactors I couldn't avoid)\nthey could all be one single change to move the declaration for the zlib\ncompat function to some place where it could be visible from the two files\nthat will use it, but it was spread on multiple files to easy autosquashing\n\nCarlo Marcelo Arenas Belón (4):\n  fixup! reftable: reading/writing blocks\n  fixup! reftable: utility functions\n  fixup! Provide zlib's uncompress2 from compat/zlib-compat.c\n  fixup! reftable: utility functions\n\n compat/zlib-uncompress2.c |  3 +++\n reftable/basics.h         |  1 -\n reftable/block.c          |  9 ---------\n reftable/system.h         | 10 +++++++++-\n 4 files changed, 12 insertions(+), 11 deletions(-)\n\n-- \n2.33.0.481.g26d3bed244\n\n"},{"id":"435034","messageId":"20210908074557.38141-2-carenas@gmail.com","threadId":"56405","inReplyTo":"20210908074557.38141-1-carenas@gmail.com","subject":"[PATCH 1/4] fixup! reftable: reading/writing blocks","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-08T07:45:54Z","receivedAt":"2021-09-08T07:46:14Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"move the definition to \"system.h\" because it needs to be accessible\nalso to the compat file.\n---\n reftable/block.c | 9 ---------\n 1 file changed, 9 deletions(-)\n\ndiff --git a/reftable/block.c b/reftable/block.c\nindex eb5268dd3a..11387b260a 100644\n--- a/reftable/block.c\n+++ b/reftable/block.c\n@@ -15,15 +15,6 @@ license that can be found in the LICENSE file or at\n #include \"system.h\"\n #include <zlib.h>\n \n-#ifdef NO_UNCOMPRESS2\n-/*\n- * This is uncompress2, which is only available in zlib >= 1.2.9\n- * (released as of early 2017)\n- */\n-int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n-\t\tuLong *sourceLen);\n-#endif\n-\n int header_size(int version)\n {\n \tswitch (version) {\n-- \n2.33.0.481.g26d3bed244\n\n"},{"id":"435035","messageId":"20210908074557.38141-3-carenas@gmail.com","threadId":"56405","inReplyTo":"20210908074557.38141-1-carenas@gmail.com","subject":"[PATCH 2/4] fixup! reftable: utility functions","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-08T07:45:55Z","receivedAt":"2021-09-08T07:46:25Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"remove forward declaration for strbuf that is no longer needed once\nstrbuf.h was included.\n\nadd a conditional declaration for the zlib function that then can\nbe used in both the compat definition and the block.c user without\ntriggering -Wmissing-prototypes:\n\ncompat/zlib-uncompress2.c:36:13: error: no previous prototype for function\n      'uncompress2' [-Werror,-Wmissing-prototypes]\nint ZEXPORT uncompress2 (\n            ^\ncompat/zlib-uncompress2.c:36:1: note: declare 'static' if the function is not\n      intended to be used outside of this translation unit\nint ZEXPORT uncompress2 (\n^\nstatic\n1 error generated.\ngmake: *** [Makefile:2571: compat/zlib-uncompress2.o] Error 1\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/system.h | 10 +++++++++-\n 1 file changed, 9 insertions(+), 1 deletion(-)\n\ndiff --git a/reftable/system.h b/reftable/system.h\nindex 4f62827b83..4907306c0c 100644\n--- a/reftable/system.h\n+++ b/reftable/system.h\n@@ -18,7 +18,15 @@ license that can be found in the LICENSE file or at\n \n #include <zlib.h>\n \n-struct strbuf;\n+#ifdef NO_UNCOMPRESS2\n+/*\n+ * This is uncompress2, which is only available in zlib >= 1.2.9\n+ * (released as of early 2017)\n+ */\n+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n+\t\tuLong *sourceLen);\n+#endif\n+\n int hash_size(uint32_t id);\n \n #endif\n-- \n2.33.0.481.g26d3bed244\n\n"},{"id":"435036","messageId":"20210908074557.38141-4-carenas@gmail.com","threadId":"56405","inReplyTo":"20210908074557.38141-1-carenas@gmail.com","subject":"[PATCH 3/4] fixup! Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-08T07:45:56Z","receivedAt":"2021-09-08T07:46:33Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"while at it, make sure z_conf is defined to null (usually coming\nfrom zconf.h, but optional and otherwise to fail with older zlib\nversions)\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n compat/zlib-uncompress2.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c\nindex 23b72b49c4..722610b971 100644\n--- a/compat/zlib-uncompress2.c\n+++ b/compat/zlib-uncompress2.c\n@@ -8,6 +8,9 @@\n \n */\n \n+#include \"../reftable/system.h\"\n+#define z_const\n+\n /*\n  * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler\n  * For conditions of distribution and use, see copyright notice in zlib.h\n-- \n2.33.0.481.g26d3bed244\n\n"},{"id":"435037","messageId":"20210908074557.38141-5-carenas@gmail.com","threadId":"56405","inReplyTo":"20210908074557.38141-1-carenas@gmail.com","subject":"[PATCH 4/4] fixup! reftable: utility functions","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-08T07:45:57Z","receivedAt":"2021-09-08T07:46:40Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"---\n reftable/basics.h | 1 -\n 1 file changed, 1 deletion(-)\n\ndiff --git a/reftable/basics.h b/reftable/basics.h\nindex 096b36862b..0645b74196 100644\n--- a/reftable/basics.h\n+++ b/reftable/basics.h\n@@ -54,7 +54,6 @@ void reftable_free(void *p);\n void *reftable_calloc(size_t sz);\n \n /* Find the longest shared prefix size of `a` and `b` */\n-struct strbuf;\n int common_prefix_size(struct strbuf *a, struct strbuf *b);\n \n #endif\n-- \n2.33.0.481.g26d3bed244\n\n"},{"id":"435138","messageId":"xmqq8s0652bx.fsf@gitster.g","threadId":"56405","inReplyTo":"20210908074557.38141-1-carenas@gmail.com","subject":"Re: [PATCH 0/4] fixup for hn/reftable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-08T18:50:26Z","receivedAt":"2021-09-08T18:50:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carlo Marcelo Arenas Belón  <carenas@gmail.com> writes:\n\n> DEVELOPER=1 wasn't working well in OpenBSD until cb/makefile-apple-clang\n> (which just hit next), so I missed it in my previous run.\n>\n> hopefully it is not too late to fix this, otherwise.\n\nI haven't merged them to 'next', so it is easy to accept a\nreroll.\n\n> As usual they should apply to the patch they refer to and are based on the\n> last reroll in seen (tip 1427aef0bd as of e881a4ab6c (Merge branch\n> 'hn/reftable' into seen, 2021-09-07)\n>\n> Except from the last patch (and two other minor refactors I couldn't avoid)\n> they could all be one single change to move the declaration for the zlib\n> compat function to some place where it could be visible from the two files\n> that will use it, but it was spread on multiple files to easy autosquashing\n>\n> Carlo Marcelo Arenas Belón (4):\n>   fixup! reftable: reading/writing blocks\n>   fixup! reftable: utility functions\n>   fixup! Provide zlib's uncompress2 from compat/zlib-compat.c\n>   fixup! reftable: utility functions\n>\n>  compat/zlib-uncompress2.c |  3 +++\n>  reftable/basics.h         |  1 -\n>  reftable/block.c          |  9 ---------\n>  reftable/system.h         | 10 +++++++++-\n>  4 files changed, 12 insertions(+), 11 deletions(-)\n"},{"id":"435296","messageId":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.git.git.1630335476.gitgitgadget@gmail.com","subject":"[PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:25Z","receivedAt":"2021-09-09T18:47:52Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"The reftable format is described in Documentation/technical/reftable.txt.\n\nThis is a fully reentrant implementation of reading and writing the reftable\nfile format, and should be suitable for embedding in libgit2 too. It does\nnot hook the code up to git to function as a ref storage backend yet.\n\nv2:\n\n * fold in OpenBSD fixes by Carlo Belón.\n\nHan-Wen Nienhuys (19):\n  hash.h: provide constants for the hash IDs\n  reftable: RFC: add LICENSE\n  reftable: add error related functionality\n  reftable: utility functions\n  reftable: add blocksource, an abstraction for random access reads\n  reftable: (de)serialization for the polymorphic record type.\n  Provide zlib's uncompress2 from compat/zlib-compat.c\n  reftable: reading/writing blocks\n  reftable: a generic binary tree implementation\n  reftable: write reftable files\n  reftable: generic interface to tables\n  reftable: read reftable files\n  reftable: reftable file level tests\n  reftable: add a heap-based priority queue for reftable records\n  reftable: add merged table view\n  reftable: implement refname validation\n  reftable: implement stack, a mutable database of reftable files.\n  reftable: add dump utility\n  Add \"test-tool dump-reftable\" command.\n\n Makefile                                   |   53 +-\n ci/lib.sh                                  |    1 +\n compat/.gitattributes                      |    1 +\n compat/zlib-uncompress2.c                  |   95 ++\n config.mak.uname                           |    5 +\n configure.ac                               |   13 +\n contrib/buildsystems/CMakeLists.txt        |   14 +-\n contrib/buildsystems/Generators/Vcxproj.pm |   11 +-\n hash.h                                     |    6 +\n object-file.c                              |    7 +-\n reftable/LICENSE                           |   31 +\n reftable/basics.c                          |  128 ++\n reftable/basics.h                          |   60 +\n reftable/basics_test.c                     |   98 ++\n reftable/block.c                           |  439 ++++++\n reftable/block.h                           |  127 ++\n reftable/block_test.c                      |  120 ++\n reftable/blocksource.c                     |  148 +++\n reftable/blocksource.h                     |   22 +\n reftable/constants.h                       |   21 +\n reftable/dump.c                            |  107 ++\n reftable/error.c                           |   41 +\n reftable/generic.c                         |  169 +++\n reftable/generic.h                         |   32 +\n reftable/iter.c                            |  194 +++\n reftable/iter.h                            |   69 +\n reftable/merged.c                          |  362 +++++\n reftable/merged.h                          |   35 +\n reftable/merged_test.c                     |  292 ++++\n reftable/pq.c                              |  105 ++\n reftable/pq.h                              |   33 +\n reftable/pq_test.c                         |   82 ++\n reftable/publicbasics.c                    |   58 +\n reftable/reader.c                          |  801 +++++++++++\n reftable/reader.h                          |   66 +\n reftable/readwrite_test.c                  |  652 +++++++++\n reftable/record.c                          | 1212 +++++++++++++++++\n reftable/record.h                          |  139 ++\n reftable/record_test.c                     |  412 ++++++\n reftable/refname.c                         |  209 +++\n reftable/refname.h                         |   29 +\n reftable/refname_test.c                    |  102 ++\n reftable/reftable-blocksource.h            |   49 +\n reftable/reftable-error.h                  |   62 +\n reftable/reftable-generic.h                |   47 +\n reftable/reftable-iterator.h               |   39 +\n reftable/reftable-malloc.h                 |   18 +\n reftable/reftable-merged.h                 |   72 +\n reftable/reftable-reader.h                 |  101 ++\n reftable/reftable-record.h                 |  114 ++\n reftable/reftable-stack.h                  |  128 ++\n reftable/reftable-tests.h                  |   23 +\n reftable/reftable-writer.h                 |  148 +++\n reftable/reftable.c                        |  115 ++\n reftable/stack.c                           | 1396 ++++++++++++++++++++\n reftable/stack.h                           |   41 +\n reftable/stack_test.c                      |  949 +++++++++++++\n reftable/system.h                          |   32 +\n reftable/test_framework.c                  |   23 +\n reftable/test_framework.h                  |   53 +\n reftable/tree.c                            |   63 +\n reftable/tree.h                            |   34 +\n reftable/tree_test.c                       |   61 +\n reftable/writer.c                          |  690 ++++++++++\n reftable/writer.h                          |   50 +\n t/helper/test-reftable.c                   |   21 +\n t/helper/test-tool.c                       |    4 +-\n t/helper/test-tool.h                       |    2 +\n t/t0032-reftable-unittest.sh               |   15 +\n 69 files changed, 10939 insertions(+), 12 deletions(-)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n create mode 100644 reftable/LICENSE\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/dump.c\n create mode 100644 reftable/error.c\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/readwrite_test.c\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n create mode 100644 reftable/reftable-blocksource.h\n create mode 100644 reftable/reftable-error.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-merged.h\n create mode 100644 reftable/reftable-reader.h\n create mode 100644 reftable/reftable-record.h\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/reftable.c\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\n\nbase-commit: e0a2f5cbc585657e757385ad918f167f519cfb96\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1081%2Fhanwen%2Fjust-library-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1081/hanwen/just-library-v2\nPull-Request: https://github.com/git/git/pull/1081\n\nRange-diff vs v1:\n\n  1:  b9ee8cd8e9f =  1:  8534c69cf84 hash.h: provide constants for the hash IDs\n  2:  2d31c783fb5 =  2:  52968d8e83f reftable: RFC: add LICENSE\n  3:  ea6f4f8c150 =  3:  84c8b396df5 reftable: add error related functionality\n  4:  fd5c5c9f338 !  4:  fa58f3d73e7 reftable: utility functions\n     @@ Makefile: OBJECTS += $(PROGRAM_OBJS)\n       endif\n      @@ Makefile: $(LIB_FILE): $(LIB_OBJS)\n       $(XDIFF_LIB): $(XDIFF_OBJS)\n     - \t$(QUIET_AR)$(AR) $(ARFLAGS) $@ $^\n     + \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n       \n      +$(REFTABLE_LIB): $(REFTABLE_OBJS)\n      +\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n     @@ reftable/system.h (new)\n      +\n      +#include <zlib.h>\n      +\n     -+struct strbuf;\n     ++#ifdef NO_UNCOMPRESS2\n     ++/*\n     ++ * This is uncompress2, which is only available in zlib >= 1.2.9\n     ++ * (released as of early 2017)\n     ++ */\n     ++int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n     ++\t\tuLong *sourceLen);\n     ++#endif\n     ++\n      +int hash_size(uint32_t id);\n      +\n      +#endif\n  5:  c11f06ed6fb =  5:  e9206532126 reftable: add blocksource, an abstraction for random access reads\n  6:  23977331cf8 =  6:  651e195fe77 reftable: (de)serialization for the polymorphic record type.\n  7:  aa9b8e05dbd !  7:  e595dbc6c84 Provide zlib's uncompress2 from compat/zlib-compat.c\n     @@ compat/zlib-uncompress2.c (new)\n      +\n      +*/\n      +\n     ++#include \"../reftable/system.h\"\n     ++#define z_const\n     ++\n      +/*\n      + * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler\n      + * For conditions of distribution and use, see copyright notice in zlib.h\n     @@ config.mak.uname: ifeq ($(uname_S),FreeBSD)\n       endif\n       ifeq ($(uname_S),OpenBSD)\n      +\tNO_UNCOMPRESS2 = YesPlease\n     ++\t# Versions < 7.0 need compatibility layer\n     ++\tifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n     ++\t\tNO_UNCOMPRESS2 = UnfortunatelyYes\n     ++\tendif\n       \tNO_STRCASESTR = YesPlease\n       \tNO_MEMMEM = YesPlease\n       \tUSE_ST_TIMESPEC = YesPlease\n  8:  2648e05c510 !  8:  97f7ee04886 reftable: reading/writing blocks\n     @@ reftable/block.c (new)\n      +#include \"system.h\"\n      +#include <zlib.h>\n      +\n     -+#ifdef NO_UNCOMPRESS2\n     -+/*\n     -+ * This is uncompress2, which is only available in zlib >= 1.2.9\n     -+ * (released as of early 2017)\n     -+ */\n     -+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n     -+\t\tuLong *sourceLen);\n     -+#endif\n     -+\n      +int header_size(int version)\n      +{\n      +\tswitch (version) {\n  9:  49a9a00465d !  9:  8d2e8be5795 reftable: a generic binary tree implementation\n     @@ reftable/tree_test.c (new)\n      +{\n      +\tstruct curry *c = arg;\n      +\tif (c->last) {\n     -+\t\tassert(test_compare(c->last, key) < 0);\n     ++\t\tEXPECT(test_compare(c->last, key) < 0);\n      +\t}\n      +\tc->last = key;\n      +}\n     @@ reftable/tree_test.c (new)\n      +\t} while (i != 1);\n      +\n      +\tfor (i = 1; i < ARRAY_SIZE(nodes); i++) {\n     -+\t\tassert(values + i == nodes[i]->key);\n     -+\t\tassert(nodes[i] ==\n     ++\t\tEXPECT(values + i == nodes[i]->key);\n     ++\t\tEXPECT(nodes[i] ==\n      +\t\t       tree_search(values + i, &root, &test_compare, 0));\n      +\t}\n      +\n 10:  c00876afd26 = 10:  965af6ec065 reftable: write reftable files\n 11:  0f32d6f4c91 = 11:  4e259380fda reftable: generic interface to tables\n 12:  a77878993aa = 12:  a43d3512164 reftable: read reftable files\n 13:  4bbefe977ae = 13:  f0e159cd853 reftable: reftable file level tests\n 14:  7ee46496081 ! 14:  5a5c108d601 reftable: add a heap-based priority queue for reftable records\n     @@ reftable/pq.c (new)\n      +#include \"system.h\"\n      +#include \"basics.h\"\n      +\n     -+static int pq_less(struct pq_entry a, struct pq_entry b)\n     ++int pq_less(struct pq_entry *a, struct pq_entry *b)\n      +{\n      +\tstruct strbuf ak = STRBUF_INIT;\n      +\tstruct strbuf bk = STRBUF_INIT;\n      +\tint cmp = 0;\n     -+\treftable_record_key(&a.rec, &ak);\n     -+\treftable_record_key(&b.rec, &bk);\n     ++\treftable_record_key(&a->rec, &ak);\n     ++\treftable_record_key(&b->rec, &bk);\n      +\n      +\tcmp = strbuf_cmp(&ak, &bk);\n      +\n     @@ reftable/pq.c (new)\n      +\tstrbuf_release(&bk);\n      +\n      +\tif (cmp == 0)\n     -+\t\treturn a.index > b.index;\n     ++\t\treturn a->index > b->index;\n      +\n      +\treturn cmp < 0;\n      +}\n     @@ reftable/pq.c (new)\n      +\treturn pq.len == 0;\n      +}\n      +\n     -+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n     -+{\n     -+\tint i = 0;\n     -+\tfor (i = 1; i < pq.len; i++) {\n     -+\t\tint parent = (i - 1) / 2;\n     -+\n     -+\t\tassert(pq_less(pq.heap[parent], pq.heap[i]));\n     -+\t}\n     -+}\n     -+\n      +struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)\n      +{\n      +\tint i = 0;\n     @@ reftable/pq.c (new)\n      +\t\tint min = i;\n      +\t\tint j = 2 * i + 1;\n      +\t\tint k = 2 * i + 2;\n     -+\t\tif (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {\n     ++\t\tif (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) {\n      +\t\t\tmin = j;\n      +\t\t}\n     -+\t\tif (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {\n     ++\t\tif (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) {\n      +\t\t\tmin = k;\n      +\t\t}\n      +\n     @@ reftable/pq.c (new)\n      +\ti = pq->len - 1;\n      +\twhile (i > 0) {\n      +\t\tint j = (i - 1) / 2;\n     -+\t\tif (pq_less(pq->heap[j], pq->heap[i])) {\n     ++\t\tif (pq_less(&pq->heap[j], &pq->heap[i])) {\n      +\t\t\tbreak;\n      +\t\t}\n      +\n     @@ reftable/pq.h (new)\n      +struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);\n      +void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);\n      +void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);\n     ++int pq_less(struct pq_entry *a, struct pq_entry *b);\n      +\n      +#endif\n      \n     @@ reftable/pq_test.c (new)\n      +#include \"reftable-tests.h\"\n      +#include \"test_framework.h\"\n      +\n     ++void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n     ++{\n     ++\tint i = 0;\n     ++\tfor (i = 1; i < pq.len; i++) {\n     ++\t\tint parent = (i - 1) / 2;\n     ++\n     ++\t\tassert(pq_less(&pq.heap[parent], &pq.heap[i]));\n     ++\t}\n     ++}\n     ++\n      +static void test_pq(void)\n      +{\n      +\tchar *names[54] = { NULL };\n     @@ reftable/pq_test.c (new)\n      +\t\tmerged_iter_pqueue_check(pq);\n      +\n      +\t\tif (last) {\n     -+\t\t\tassert(strcmp(last, ref->refname) < 0);\n     ++\t\t\tEXPECT(strcmp(last, ref->refname) < 0);\n      +\t\t}\n      +\t\tlast = ref->refname;\n      +\t\tref->refname = NULL;\n 15:  8bc889aaae8 ! 15:  905ffa58f36 reftable: add merged table view\n     @@ reftable/merged_test.c (new)\n      +\tfor (i = 0; i < n; i++) {\n      +\t\tuint64_t before = refs[i].update_index;\n      +\t\tint n = reftable_writer_add_ref(w, &refs[i]);\n     -+\t\tassert(n == 0);\n     -+\t\tassert(before == refs[i].update_index);\n     ++\t\tEXPECT(n == 0);\n     ++\t\tEXPECT(before == refs[i].update_index);\n      +\t}\n      +\n      +\terr = reftable_writer_close(w);\n     @@ reftable/merged_test.c (new)\n      +\t}\n      +\treftable_iterator_destroy(&it);\n      +\n     -+\tassert(ARRAY_SIZE(want) == len);\n     ++\tEXPECT(ARRAY_SIZE(want) == len);\n      +\tfor (i = 0; i < len; i++) {\n     -+\t\tassert(reftable_ref_record_equal(&want[i], &out[i],\n     ++\t\tEXPECT(reftable_ref_record_equal(&want[i], &out[i],\n      +\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n      +\t}\n      +\tfor (i = 0; i < len; i++) {\n     @@ reftable/merged_test.c (new)\n      +\tEXPECT_ERR(err);\n      +\n      +\thash_id = reftable_reader_hash_id(rd);\n     -+\tassert(hash_id == GIT_SHA1_FORMAT_ID);\n     ++\tEXPECT(hash_id == GIT_SHA1_FORMAT_ID);\n      +\n      +\treftable_table_from_reader(&tab[0], rd);\n      +\terr = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);\n 16:  9a30c1c4666 = 16:  e9598889af2 reftable: implement refname validation\n 17:  b099c42d65d = 17:  8b0ee68c636 reftable: implement stack, a mutable database of reftable files.\n 18:  0170f20b5e7 = 18:  a9bebe45bde reftable: add dump utility\n 19:  4fc6b5cbf16 = 19:  104fbc7502f Add \"test-tool dump-reftable\" command.\n\n-- \ngitgitgadget\n"},{"id":"435294","messageId":"8534c69cf84e2e4d39f57d5cafa4dec22d82ee7d.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 01/19] hash.h: provide constants for the hash IDs","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:26Z","receivedAt":"2021-09-09T18:47:53Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will simplify referencing them from code that is not deeply integrated with\nGit, in particular, the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n hash.h        | 6 ++++++\n object-file.c | 7 ++-----\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/hash.h b/hash.h\nindex 9e25c40e9ac..5d40368f18a 100644\n--- a/hash.h\n+++ b/hash.h\n@@ -95,12 +95,18 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s\n /* Number of algorithms supported (including unknown). */\n #define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)\n \n+/* \"sha1\", big-endian */\n+#define GIT_SHA1_FORMAT_ID 0x73686131\n+\n /* The length in bytes and in hex digits of an object name (SHA-1 value). */\n #define GIT_SHA1_RAWSZ 20\n #define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)\n /* The block size of SHA-1. */\n #define GIT_SHA1_BLKSZ 64\n \n+/* \"s256\", big-endian */\n+#define GIT_SHA256_FORMAT_ID 0x73323536\n+\n /* The length in bytes and in hex digits of an object name (SHA-256 value). */\n #define GIT_SHA256_RAWSZ 32\n #define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)\ndiff --git a/object-file.c b/object-file.c\nindex a8be8994814..7bfd5e6e2e9 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -164,7 +164,6 @@ static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx)\n \tBUG(\"trying to finalize unknown hash\");\n }\n \n-\n const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t{\n \t\tNULL,\n@@ -183,8 +182,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha1\",\n-\t\t/* \"sha1\", big-endian */\n-\t\t0x73686131,\n+\t\tGIT_SHA1_FORMAT_ID,\n \t\tGIT_SHA1_RAWSZ,\n \t\tGIT_SHA1_HEXSZ,\n \t\tGIT_SHA1_BLKSZ,\n@@ -199,8 +197,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha256\",\n-\t\t/* \"s256\", big-endian */\n-\t\t0x73323536,\n+\t\tGIT_SHA256_FORMAT_ID,\n \t\tGIT_SHA256_RAWSZ,\n \t\tGIT_SHA256_HEXSZ,\n \t\tGIT_SHA256_BLKSZ,\n-- \ngitgitgadget\n\n"},{"id":"435295","messageId":"52968d8e83f392d3a12a6fb8483206aecb3ccf1c.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 02/19] reftable: RFC: add LICENSE","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:27Z","receivedAt":"2021-09-09T18:47:54Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe objective of this code is to be usable as a C library, so it can be reused\nin libgit2.\n\nThis is currently using a BSD license as it is the liberal license I could find,\nbut this could be changed to whatever fits the stated goal above.\n\nThis code is currently imported from github.com/hanwen/reftable. Once this code\nlands in git.git, the C code will be removed from github.com/hanwen/reftable,\nand the git.git code will be the source of truth.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/LICENSE | 31 +++++++++++++++++++++++++++++++\n 1 file changed, 31 insertions(+)\n create mode 100644 reftable/LICENSE\n\ndiff --git a/reftable/LICENSE b/reftable/LICENSE\nnew file mode 100644\nindex 00000000000..402e0f9356b\n--- /dev/null\n+++ b/reftable/LICENSE\n@@ -0,0 +1,31 @@\n+BSD License\n+\n+Copyright (c) 2020, Google LLC\n+All rights reserved.\n+\n+Redistribution and use in source and binary forms, with or without\n+modification, are permitted provided that the following conditions are\n+met:\n+\n+* Redistributions of source code must retain the above copyright notice,\n+this list of conditions and the following disclaimer.\n+\n+* Redistributions in binary form must reproduce the above copyright\n+notice, this list of conditions and the following disclaimer in the\n+documentation and/or other materials provided with the distribution.\n+\n+* Neither the name of Google LLC nor the names of its contributors may\n+be used to endorse or promote products derived from this software\n+without specific prior written permission.\n+\n+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n-- \ngitgitgadget\n\n"},{"id":"435297","messageId":"84c8b396df53c23daeeb0ef4c6ef263724413a9e.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 03/19] reftable: add error related functionality","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:28Z","receivedAt":"2021-09-09T18:47:56Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable/ directory is structured as a library, so it cannot\ncrash on misuse. Instead, it returns an error codes.\n\nIn addition, the error code can be used to signal conditions from lower levels\nof the library to be handled by higher levels of the library. For example, a\ntransaction might legitimately write an empty reftable file, but in that case,\nwe'd want to shortcut the transaction overhead.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/error.c          | 41 ++++++++++++++++++++++++++\n reftable/reftable-error.h | 62 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 103 insertions(+)\n create mode 100644 reftable/error.c\n create mode 100644 reftable/reftable-error.h\n\ndiff --git a/reftable/error.c b/reftable/error.c\nnew file mode 100644\nindex 00000000000..f6f16def921\n--- /dev/null\n+++ b/reftable/error.c\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-error.h\"\n+\n+#include <stdio.h>\n+\n+const char *reftable_error_str(int err)\n+{\n+\tstatic char buf[250];\n+\tswitch (err) {\n+\tcase REFTABLE_IO_ERROR:\n+\t\treturn \"I/O error\";\n+\tcase REFTABLE_FORMAT_ERROR:\n+\t\treturn \"corrupt reftable file\";\n+\tcase REFTABLE_NOT_EXIST_ERROR:\n+\t\treturn \"file does not exist\";\n+\tcase REFTABLE_LOCK_ERROR:\n+\t\treturn \"data is outdated\";\n+\tcase REFTABLE_API_ERROR:\n+\t\treturn \"misuse of the reftable API\";\n+\tcase REFTABLE_ZLIB_ERROR:\n+\t\treturn \"zlib failure\";\n+\tcase REFTABLE_NAME_CONFLICT:\n+\t\treturn \"file/directory conflict\";\n+\tcase REFTABLE_EMPTY_TABLE_ERROR:\n+\t\treturn \"wrote empty table\";\n+\tcase REFTABLE_REFNAME_ERROR:\n+\t\treturn \"invalid refname\";\n+\tcase -1:\n+\t\treturn \"general error\";\n+\tdefault:\n+\t\tsnprintf(buf, sizeof(buf), \"unknown error code %d\", err);\n+\t\treturn buf;\n+\t}\n+}\ndiff --git a/reftable/reftable-error.h b/reftable/reftable-error.h\nnew file mode 100644\nindex 00000000000..6f89bedf1a5\n--- /dev/null\n+++ b/reftable/reftable-error.h\n@@ -0,0 +1,62 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ERROR_H\n+#define REFTABLE_ERROR_H\n+\n+/*\n+ * Errors in reftable calls are signaled with negative integer return values. 0\n+ * means success.\n+ */\n+enum reftable_error {\n+\t/* Unexpected file system behavior */\n+\tREFTABLE_IO_ERROR = -2,\n+\n+\t/* Format inconsistency on reading data */\n+\tREFTABLE_FORMAT_ERROR = -3,\n+\n+\t/* File does not exist. Returned from block_source_from_file(), because\n+\t * it needs special handling in stack.\n+\t */\n+\tREFTABLE_NOT_EXIST_ERROR = -4,\n+\n+\t/* Trying to write out-of-date data. */\n+\tREFTABLE_LOCK_ERROR = -5,\n+\n+\t/* Misuse of the API:\n+\t *  - on writing a record with NULL refname.\n+\t *  - on writing a reftable_ref_record outside the table limits\n+\t *  - on writing a ref or log record before the stack's\n+\t * next_update_inde*x\n+\t *  - on writing a log record with multiline message with\n+\t *  exact_log_message unset\n+\t *  - on reading a reftable_ref_record from log iterator, or vice versa.\n+\t *\n+\t * When a call misuses the API, the internal state of the library is\n+\t * kept unchanged.\n+\t */\n+\tREFTABLE_API_ERROR = -6,\n+\n+\t/* Decompression error */\n+\tREFTABLE_ZLIB_ERROR = -7,\n+\n+\t/* Wrote a table without blocks. */\n+\tREFTABLE_EMPTY_TABLE_ERROR = -8,\n+\n+\t/* Dir/file conflict. */\n+\tREFTABLE_NAME_CONFLICT = -9,\n+\n+\t/* Invalid ref name. */\n+\tREFTABLE_REFNAME_ERROR = -10,\n+};\n+\n+/* convert the numeric error code to a string. The string should not be\n+ * deallocated. */\n+const char *reftable_error_str(int err);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"435298","messageId":"fa58f3d73e789e8de4edc0eb3eb93e837cfc6cfa.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 04/19] reftable: utility functions","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:29Z","receivedAt":"2021-09-09T18:48:23Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis commit provides basic utility classes for the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                                   |  25 +++-\n contrib/buildsystems/CMakeLists.txt        |  14 ++-\n contrib/buildsystems/Generators/Vcxproj.pm |  11 +-\n reftable/basics.c                          | 128 +++++++++++++++++++++\n reftable/basics.h                          |  60 ++++++++++\n reftable/basics_test.c                     |  98 ++++++++++++++++\n reftable/publicbasics.c                    |  58 ++++++++++\n reftable/reftable-malloc.h                 |  18 +++\n reftable/reftable-tests.h                  |  22 ++++\n reftable/system.h                          |  32 ++++++\n reftable/test_framework.c                  |  23 ++++\n reftable/test_framework.h                  |  53 +++++++++\n t/helper/test-reftable.c                   |   9 ++\n t/helper/test-tool.c                       |   3 +-\n t/helper/test-tool.h                       |   1 +\n t/t0032-reftable-unittest.sh               |  15 +++\n 16 files changed, 563 insertions(+), 7 deletions(-)\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\ndiff --git a/Makefile b/Makefile\nindex 429c276058d..0bf5e49aa61 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -743,6 +743,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o\n TEST_BUILTINS_OBJS += test-read-graph.o\n TEST_BUILTINS_OBJS += test-read-midx.o\n TEST_BUILTINS_OBJS += test-ref-store.o\n+TEST_BUILTINS_OBJS += test-reftable.o\n TEST_BUILTINS_OBJS += test-regex.o\n TEST_BUILTINS_OBJS += test-repository.o\n TEST_BUILTINS_OBJS += test-revision-walking.o\n@@ -821,6 +822,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n XDIFF_LIB = xdiff/lib.a\n+REFTABLE_LIB = reftable/libreftable.a\n+REFTABLE_TEST_LIB = reftable/libreftable_test.a\n \n GENERATED_H += command-list.h\n GENERATED_H += config-list.h\n@@ -1195,7 +1198,7 @@ THIRD_PARTY_SOURCES += compat/regex/%\n THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)\n+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)\n EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2446,7 +2449,15 @@ XDIFF_OBJS += xdiff/xutils.o\n .PHONY: xdiff-objs\n xdiff-objs: $(XDIFF_OBJS)\n \n+REFTABLE_OBJS += reftable/basics.o\n+REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/publicbasics.o\n+\n+REFTABLE_TEST_OBJS += reftable/test_framework.o\n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n+\n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n+\n .PHONY: test-objs\n test-objs: $(TEST_OBJS)\n \n@@ -2462,6 +2473,8 @@ OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n OBJECTS += $(XDIFF_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n+OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n+\n ifndef NO_CURL\n \tOBJECTS += http.o http-walker.o remote-curl.o\n endif\n@@ -2612,6 +2625,12 @@ $(LIB_FILE): $(LIB_OBJS)\n $(XDIFF_LIB): $(XDIFF_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n+$(REFTABLE_LIB): $(REFTABLE_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n export DEFAULT_EDITOR DEFAULT_PAGER\n \n Documentation/GIT-EXCLUDED-PROGRAMS: FORCE\n@@ -2904,7 +2923,7 @@ perf: all\n \n t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \n-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)\n+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n \n check-sha1:: t/helper/test-tool$X\n@@ -3234,7 +3253,7 @@ cocciclean:\n clean: profile-clean coverage-clean cocciclean\n \t$(RM) *.res\n \t$(RM) $(OBJECTS)\n-\t$(RM) $(LIB_FILE) $(XDIFF_LIB)\n+\t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 171b4124afe..c2bf5bdffc6 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -640,6 +640,12 @@ parse_makefile_for_sources(libxdiff_SOURCES \"XDIFF_OBJS\")\n list(TRANSFORM libxdiff_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n add_library(xdiff STATIC ${libxdiff_SOURCES})\n \n+#reftable\n+parse_makefile_for_sources(reftable_SOURCES \"REFTABLE_OBJS\")\n+\n+list(TRANSFORM reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+add_library(reftable STATIC ${reftable_SOURCES})\n+\n if(WIN32)\n \tif(NOT MSVC)#use windres when compiling with gcc and clang\n \t\tadd_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res\n@@ -662,7 +668,7 @@ endif()\n #link all required libraries to common-main\n add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)\n \n-target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})\n+target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})\n if(Intl_FOUND)\n \ttarget_link_libraries(common-main ${Intl_LIBRARIES})\n endif()\n@@ -902,11 +908,15 @@ if(BUILD_TESTING)\n add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)\n target_link_libraries(test-fake-ssh common-main)\n \n+#reftable-tests\n+parse_makefile_for_sources(test-reftable_SOURCES \"REFTABLE_TEST_OBJS\")\n+list(TRANSFORM test-reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+\n #test-tool\n parse_makefile_for_sources(test-tool_SOURCES \"TEST_BUILTINS_OBJS\")\n \n list(TRANSFORM test-tool_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/t/helper/\")\n-add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})\n+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})\n target_link_libraries(test-tool common-main)\n \n set_target_properties(test-fake-ssh test-tool\ndiff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm\nindex d2584450ba1..1a25789d285 100644\n--- a/contrib/buildsystems/Generators/Vcxproj.pm\n+++ b/contrib/buildsystems/Generators/Vcxproj.pm\n@@ -77,7 +77,7 @@ sub createProject {\n     my $libs_release = \"\\n    \";\n     my $libs_debug = \"\\n    \";\n     if (!$static_library) {\n-      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n+      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib|reftable\\/libreftable\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n       $libs_debug = $libs_release;\n       $libs_debug =~ s/zlib\\.lib/zlibd\\.lib/g;\n       $libs_debug =~ s/libexpat\\.lib/libexpatd\\.lib/g;\n@@ -232,6 +232,7 @@ EOM\n EOM\n     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {\n       my $uuid_libgit = $$build_structure{\"LIBS_libgit_GUID\"};\n+      my $uuid_libreftable = $$build_structure{\"LIBS_reftable/libreftable_GUID\"};\n       my $uuid_xdiff_lib = $$build_structure{\"LIBS_xdiff/lib_GUID\"};\n \n       print F << \"EOM\";\n@@ -241,6 +242,14 @@ EOM\n       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n     </ProjectReference>\n EOM\n+      if (!($name =~ /xdiff|libreftable/)) {\n+        print F << \"EOM\";\n+    <ProjectReference Include=\"$cdup\\\\reftable\\\\libreftable\\\\libreftable.vcxproj\">\n+      <Project>$uuid_libreftable</Project>\n+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n+    </ProjectReference>\n+EOM\n+      }\n       if (!($name =~ 'xdiff')) {\n         print F << \"EOM\";\n     <ProjectReference Include=\"$cdup\\\\xdiff\\\\lib\\\\xdiff_lib.vcxproj\">\ndiff --git a/reftable/basics.c b/reftable/basics.c\nnew file mode 100644\nindex 00000000000..f761e48028c\n--- /dev/null\n+++ b/reftable/basics.c\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+\n+void put_be24(uint8_t *out, uint32_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 16) & 0xff);\n+\tout[1] = (uint8_t)((i >> 8) & 0xff);\n+\tout[2] = (uint8_t)(i & 0xff);\n+}\n+\n+uint32_t get_be24(uint8_t *in)\n+{\n+\treturn (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |\n+\t       (uint32_t)(in[2]);\n+}\n+\n+void put_be16(uint8_t *out, uint16_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 8) & 0xff);\n+\tout[1] = (uint8_t)(i & 0xff);\n+}\n+\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)\n+{\n+\tsize_t lo = 0;\n+\tsize_t hi = sz;\n+\n+\t/* Invariants:\n+\t *\n+\t *  (hi == sz) || f(hi) == true\n+\t *  (lo == 0 && f(0) == true) || fi(lo) == false\n+\t */\n+\twhile (hi - lo > 1) {\n+\t\tsize_t mid = lo + (hi - lo) / 2;\n+\n+\t\tif (f(mid, args))\n+\t\t\thi = mid;\n+\t\telse\n+\t\t\tlo = mid;\n+\t}\n+\n+\tif (lo)\n+\t\treturn hi;\n+\n+\treturn f(0, args) ? 0 : 1;\n+}\n+\n+void free_names(char **a)\n+{\n+\tchar **p;\n+\tif (!a) {\n+\t\treturn;\n+\t}\n+\tfor (p = a; *p; p++) {\n+\t\treftable_free(*p);\n+\t}\n+\treftable_free(a);\n+}\n+\n+int names_length(char **names)\n+{\n+\tchar **p = names;\n+\tfor (; *p; p++) {\n+\t\t/* empty */\n+\t}\n+\treturn p - names;\n+}\n+\n+void parse_names(char *buf, int size, char ***namesp)\n+{\n+\tchar **names = NULL;\n+\tsize_t names_cap = 0;\n+\tsize_t names_len = 0;\n+\n+\tchar *p = buf;\n+\tchar *end = buf + size;\n+\twhile (p < end) {\n+\t\tchar *next = strchr(p, '\\n');\n+\t\tif (next && next < end) {\n+\t\t\t*next = 0;\n+\t\t} else {\n+\t\t\tnext = end;\n+\t\t}\n+\t\tif (p < next) {\n+\t\t\tif (names_len == names_cap) {\n+\t\t\t\tnames_cap = 2 * names_cap + 1;\n+\t\t\t\tnames = reftable_realloc(\n+\t\t\t\t\tnames, names_cap * sizeof(*names));\n+\t\t\t}\n+\t\t\tnames[names_len++] = xstrdup(p);\n+\t\t}\n+\t\tp = next + 1;\n+\t}\n+\n+\tnames = reftable_realloc(names, (names_len + 1) * sizeof(*names));\n+\tnames[names_len] = NULL;\n+\t*namesp = names;\n+}\n+\n+int names_equal(char **a, char **b)\n+{\n+\tint i = 0;\n+\tfor (; a[i] && b[i]; i++) {\n+\t\tif (strcmp(a[i], b[i])) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn a[i] == b[i];\n+}\n+\n+int common_prefix_size(struct strbuf *a, struct strbuf *b)\n+{\n+\tint p = 0;\n+\tfor (; p < a->len && p < b->len; p++) {\n+\t\tif (a->buf[p] != b->buf[p])\n+\t\t\tbreak;\n+\t}\n+\n+\treturn p;\n+}\ndiff --git a/reftable/basics.h b/reftable/basics.h\nnew file mode 100644\nindex 00000000000..096b36862b9\n--- /dev/null\n+++ b/reftable/basics.h\n@@ -0,0 +1,60 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BASICS_H\n+#define BASICS_H\n+\n+/*\n+ * miscellaneous utilities that are not provided by Git.\n+ */\n+\n+#include \"system.h\"\n+\n+/* Bigendian en/decoding of integers */\n+\n+void put_be24(uint8_t *out, uint32_t i);\n+uint32_t get_be24(uint8_t *in);\n+void put_be16(uint8_t *out, uint16_t i);\n+\n+/*\n+ * find smallest index i in [0, sz) at which f(i) is true, assuming\n+ * that f is ascending. Return sz if f(i) is false for all indices.\n+ *\n+ * Contrary to bsearch(3), this returns something useful if the argument is not\n+ * found.\n+ */\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);\n+\n+/*\n+ * Frees a NULL terminated array of malloced strings. The array itself is also\n+ * freed.\n+ */\n+void free_names(char **a);\n+\n+/* parse a newline separated list of names. `size` is the length of the buffer,\n+ * without terminating '\\0'. Empty names are discarded. */\n+void parse_names(char *buf, int size, char ***namesp);\n+\n+/* compares two NULL-terminated arrays of strings. */\n+int names_equal(char **a, char **b);\n+\n+/* returns the array size of a NULL-terminated array of strings. */\n+int names_length(char **names);\n+\n+/* Allocation routines; they invoke the functions set through\n+ * reftable_set_alloc() */\n+void *reftable_malloc(size_t sz);\n+void *reftable_realloc(void *p, size_t sz);\n+void reftable_free(void *p);\n+void *reftable_calloc(size_t sz);\n+\n+/* Find the longest shared prefix size of `a` and `b` */\n+struct strbuf;\n+int common_prefix_size(struct strbuf *a, struct strbuf *b);\n+\n+#endif\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nnew file mode 100644\nindex 00000000000..1fcd2297256\n--- /dev/null\n+++ b/reftable/basics_test.c\n@@ -0,0 +1,98 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct binsearch_args {\n+\tint key;\n+\tint *arr;\n+};\n+\n+static int binsearch_func(size_t i, void *void_args)\n+{\n+\tstruct binsearch_args *args = void_args;\n+\n+\treturn args->key < args->arr[i];\n+}\n+\n+static void test_binsearch(void)\n+{\n+\tint arr[] = { 2, 4, 6, 8, 10 };\n+\tsize_t sz = ARRAY_SIZE(arr);\n+\tstruct binsearch_args args = {\n+\t\t.arr = arr,\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 1; i < 11; i++) {\n+\t\tint res;\n+\t\targs.key = i;\n+\t\tres = binsearch(sz, &binsearch_func, &args);\n+\n+\t\tif (res < sz) {\n+\t\t\tEXPECT(args.key < arr[res]);\n+\t\t\tif (res > 0) {\n+\t\t\t\tEXPECT(args.key >= arr[res - 1]);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tEXPECT(args.key == 10 || args.key == 11);\n+\t\t}\n+\t}\n+}\n+\n+static void test_names_length(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(a) == 2);\n+}\n+\n+static void test_parse_names_normal(void)\n+{\n+\tchar in[] = \"a\\nb\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!strcmp(out[1], \"b\"));\n+\tEXPECT(!out[2]);\n+\tfree_names(out);\n+}\n+\n+static void test_parse_names_drop_empty(void)\n+{\n+\tchar in[] = \"a\\n\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!out[1]);\n+\tfree_names(out);\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct strbuf s1 = STRBUF_INIT;\n+\tstruct strbuf s2 = STRBUF_INIT;\n+\tstrbuf_addstr(&s1, \"abcdef\");\n+\tstrbuf_addstr(&s2, \"abc\");\n+\tEXPECT(common_prefix_size(&s1, &s2) == 3);\n+\tstrbuf_release(&s1);\n+\tstrbuf_release(&s2);\n+}\n+\n+int basics_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_parse_names_normal);\n+\tRUN_TEST(test_parse_names_drop_empty);\n+\tRUN_TEST(test_binsearch);\n+\tRUN_TEST(test_names_length);\n+\treturn 0;\n+}\ndiff --git a/reftable/publicbasics.c b/reftable/publicbasics.c\nnew file mode 100644\nindex 00000000000..bd0a02d3f68\n--- /dev/null\n+++ b/reftable/publicbasics.c\n@@ -0,0 +1,58 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-malloc.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;\n+static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;\n+static void (*reftable_free_ptr)(void *) = &free;\n+\n+void *reftable_malloc(size_t sz)\n+{\n+\treturn (*reftable_malloc_ptr)(sz);\n+}\n+\n+void *reftable_realloc(void *p, size_t sz)\n+{\n+\treturn (*reftable_realloc_ptr)(p, sz);\n+}\n+\n+void reftable_free(void *p)\n+{\n+\treftable_free_ptr(p);\n+}\n+\n+void *reftable_calloc(size_t sz)\n+{\n+\tvoid *p = reftable_malloc(sz);\n+\tmemset(p, 0, sz);\n+\treturn p;\n+}\n+\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *))\n+{\n+\treftable_malloc_ptr = malloc;\n+\treftable_realloc_ptr = realloc;\n+\treftable_free_ptr = free;\n+}\n+\n+int hash_size(uint32_t id)\n+{\n+\tswitch (id) {\n+\tcase 0:\n+\tcase GIT_SHA1_FORMAT_ID:\n+\t\treturn GIT_SHA1_RAWSZ;\n+\tcase GIT_SHA256_FORMAT_ID:\n+\t\treturn GIT_SHA256_RAWSZ;\n+\t}\n+\tabort();\n+}\ndiff --git a/reftable/reftable-malloc.h b/reftable/reftable-malloc.h\nnew file mode 100644\nindex 00000000000..5f2185f1f34\n--- /dev/null\n+++ b/reftable/reftable-malloc.h\n@@ -0,0 +1,18 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_H\n+#define REFTABLE_H\n+\n+#include <stddef.h>\n+\n+/* Overrides the functions to use for memory management. */\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *));\n+\n+#endif\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nnew file mode 100644\nindex 00000000000..5e7698ae654\n--- /dev/null\n+++ b/reftable/reftable-tests.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_TESTS_H\n+#define REFTABLE_TESTS_H\n+\n+int basics_test_main(int argc, const char **argv);\n+int block_test_main(int argc, const char **argv);\n+int merged_test_main(int argc, const char **argv);\n+int record_test_main(int argc, const char **argv);\n+int refname_test_main(int argc, const char **argv);\n+int reftable_test_main(int argc, const char **argv);\n+int stack_test_main(int argc, const char **argv);\n+int tree_test_main(int argc, const char **argv);\n+int reftable_dump_main(int argc, char *const *argv);\n+\n+#endif\ndiff --git a/reftable/system.h b/reftable/system.h\nnew file mode 100644\nindex 00000000000..4907306c0c5\n--- /dev/null\n+++ b/reftable/system.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef SYSTEM_H\n+#define SYSTEM_H\n+\n+/* This header glues the reftable library to the rest of Git */\n+\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"hash.h\" /* hash ID, sizes.*/\n+#include \"dir.h\" /* remove_dir_recursively, for tests.*/\n+\n+#include <zlib.h>\n+\n+#ifdef NO_UNCOMPRESS2\n+/*\n+ * This is uncompress2, which is only available in zlib >= 1.2.9\n+ * (released as of early 2017)\n+ */\n+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n+\t\tuLong *sourceLen);\n+#endif\n+\n+int hash_size(uint32_t id);\n+\n+#endif\ndiff --git a/reftable/test_framework.c b/reftable/test_framework.c\nnew file mode 100644\nindex 00000000000..84ac972cad0\n--- /dev/null\n+++ b/reftable/test_framework.c\n@@ -0,0 +1,23 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"test_framework.h\"\n+\n+#include \"basics.h\"\n+\n+void set_test_hash(uint8_t *p, int i)\n+{\n+\tmemset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID));\n+}\n+\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz)\n+{\n+\tstrbuf_add(b, data, sz);\n+\treturn sz;\n+}\ndiff --git a/reftable/test_framework.h b/reftable/test_framework.h\nnew file mode 100644\nindex 00000000000..774cb275bf6\n--- /dev/null\n+++ b/reftable/test_framework.h\n@@ -0,0 +1,53 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TEST_FRAMEWORK_H\n+#define TEST_FRAMEWORK_H\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+\n+#define EXPECT_ERR(c)                                                  \\\n+\tif (c != 0) {                                                  \\\n+\t\tfflush(stderr);                                        \\\n+\t\tfflush(stdout);                                        \\\n+\t\tfprintf(stderr, \"%s: %d: error == %d (%s), want 0\\n\",  \\\n+\t\t\t__FILE__, __LINE__, c, reftable_error_str(c)); \\\n+\t\tabort();                                               \\\n+\t}\n+\n+#define EXPECT_STREQ(a, b)                                               \\\n+\tif (strcmp(a, b)) {                                              \\\n+\t\tfflush(stderr);                                          \\\n+\t\tfflush(stdout);                                          \\\n+\t\tfprintf(stderr, \"%s:%d: %s (%s) != %s (%s)\\n\", __FILE__, \\\n+\t\t\t__LINE__, #a, a, #b, b);                         \\\n+\t\tabort();                                                 \\\n+\t}\n+\n+#define EXPECT(c)                                                          \\\n+\tif (!(c)) {                                                        \\\n+\t\tfflush(stderr);                                            \\\n+\t\tfflush(stdout);                                            \\\n+\t\tfprintf(stderr, \"%s: %d: failed assertion %s\\n\", __FILE__, \\\n+\t\t\t__LINE__, #c);                                     \\\n+\t\tabort();                                                   \\\n+\t}\n+\n+#define RUN_TEST(f)                          \\\n+\tfprintf(stderr, \"running %s\\n\", #f); \\\n+\tfflush(stderr);                      \\\n+\tf();\n+\n+void set_test_hash(uint8_t *p, int i);\n+\n+/* Like strbuf_add, but suitable for passing to reftable_new_writer\n+ */\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nnew file mode 100644\nindex 00000000000..3b58e423e7b\n--- /dev/null\n+++ b/t/helper/test-reftable.c\n@@ -0,0 +1,9 @@\n+#include \"reftable/reftable-tests.h\"\n+#include \"test-tool.h\"\n+\n+int cmd__reftable(int argc, const char **argv)\n+{\n+\tbasics_test_main(argc, argv);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 3ce5585e53a..f7c888ffda7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -53,13 +53,14 @@ static struct test_cmd cmds[] = {\n \t{ \"pcre2-config\", cmd__pcre2_config },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n-\t{ \"proc-receive\", cmd__proc_receive},\n+\t{ \"proc-receive\", cmd__proc_receive },\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\n \t{ \"read-graph\", cmd__read_graph },\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n+\t{ \"reftable\", cmd__reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 9f0f5228508..25f77469146 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -49,6 +49,7 @@ int cmd__read_cache(int argc, const char **argv);\n int cmd__read_graph(int argc, const char **argv);\n int cmd__read_midx(int argc, const char **argv);\n int cmd__ref_store(int argc, const char **argv);\n+int cmd__reftable(int argc, const char **argv);\n int cmd__regex(int argc, const char **argv);\n int cmd__repository(int argc, const char **argv);\n int cmd__revision_walking(int argc, const char **argv);\ndiff --git a/t/t0032-reftable-unittest.sh b/t/t0032-reftable-unittest.sh\nnew file mode 100755\nindex 00000000000..0ed14971a58\n--- /dev/null\n+++ b/t/t0032-reftable-unittest.sh\n@@ -0,0 +1,15 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Google LLC\n+#\n+\n+test_description='reftable unittests'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'unittests' '\n+\tTMPDIR=$(pwd) && export TMPDIR &&\n+\ttest-tool reftable\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"435299","messageId":"651e195fe771a37b23140cf774435086e3f8f752.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 06/19] reftable: (de)serialization for the polymorphic record type.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:31Z","receivedAt":"2021-09-09T18:48:28Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of blocks, and each block\ncontains a sequence of prefix-compressed key-value records. There are 4 types of\nrecords, and they have similarities in how they must be handled. This is\nachieved by introducing a polymorphic 'record' type that encapsulates ref, log,\nindex and object records.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |    2 +\n reftable/constants.h       |   21 +\n reftable/record.c          | 1212 ++++++++++++++++++++++++++++++++++++\n reftable/record.h          |  139 +++++\n reftable/record_test.c     |  412 ++++++++++++\n reftable/reftable-record.h |  114 ++++\n t/helper/test-reftable.c   |    2 +-\n 7 files changed, 1901 insertions(+), 1 deletion(-)\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/reftable-record.h\n\ndiff --git a/Makefile b/Makefile\nindex c7ffc27a727..14d025ea424 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2453,7 +2453,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n \ndiff --git a/reftable/constants.h b/reftable/constants.h\nnew file mode 100644\nindex 00000000000..5eee72c4c11\n--- /dev/null\n+++ b/reftable/constants.h\n@@ -0,0 +1,21 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef CONSTANTS_H\n+#define CONSTANTS_H\n+\n+#define BLOCK_TYPE_LOG 'g'\n+#define BLOCK_TYPE_INDEX 'i'\n+#define BLOCK_TYPE_REF 'r'\n+#define BLOCK_TYPE_OBJ 'o'\n+#define BLOCK_TYPE_ANY 0\n+\n+#define MAX_RESTARTS ((1 << 16) - 1)\n+#define DEFAULT_BLOCK_SIZE 4096\n+\n+#endif\ndiff --git a/reftable/record.c b/reftable/record.c\nnew file mode 100644\nindex 00000000000..6a5dac32dc6\n--- /dev/null\n+++ b/reftable/record.c\n@@ -0,0 +1,1212 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+/* record.c - methods for different types of records. */\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"constants.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+\n+int get_var_int(uint64_t *dest, struct string_view *in)\n+{\n+\tint ptr = 0;\n+\tuint64_t val;\n+\n+\tif (in->len == 0)\n+\t\treturn -1;\n+\tval = in->buf[ptr] & 0x7f;\n+\n+\twhile (in->buf[ptr] & 0x80) {\n+\t\tptr++;\n+\t\tif (ptr > in->len) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tval = (val + 1) << 7 | (uint64_t)(in->buf[ptr] & 0x7f);\n+\t}\n+\n+\t*dest = val;\n+\treturn ptr + 1;\n+}\n+\n+int put_var_int(struct string_view *dest, uint64_t val)\n+{\n+\tuint8_t buf[10] = { 0 };\n+\tint i = 9;\n+\tint n = 0;\n+\tbuf[i] = (uint8_t)(val & 0x7f);\n+\ti--;\n+\twhile (1) {\n+\t\tval >>= 7;\n+\t\tif (!val) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tval--;\n+\t\tbuf[i] = 0x80 | (uint8_t)(val & 0x7f);\n+\t\ti--;\n+\t}\n+\n+\tn = sizeof(buf) - i - 1;\n+\tif (dest->len < n)\n+\t\treturn -1;\n+\tmemcpy(dest->buf, &buf[i + 1], n);\n+\treturn n;\n+}\n+\n+int reftable_is_block_type(uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\tcase BLOCK_TYPE_LOG:\n+\tcase BLOCK_TYPE_OBJ:\n+\tcase BLOCK_TYPE_INDEX:\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn rec->value.val1;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.target_value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static int decode_string(struct strbuf *dest, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t tsize = 0;\n+\tint n = get_var_int(&tsize, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\tif (in.len < tsize)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, in.buf, tsize);\n+\tstring_view_consume(&in, tsize);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static int encode_string(char *str, struct string_view s)\n+{\n+\tstruct string_view start = s;\n+\tint l = strlen(str);\n+\tint n = put_var_int(&s, l);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\tif (s.len < l)\n+\t\treturn -1;\n+\tmemcpy(s.buf, str, l);\n+\tstring_view_consume(&s, l);\n+\n+\treturn start.len - s.len;\n+}\n+\n+int reftable_encode_key(int *restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra)\n+{\n+\tstruct string_view start = dest;\n+\tint prefix_len = common_prefix_size(&prev_key, &key);\n+\tuint64_t suffix_len = key.len - prefix_len;\n+\tint n = put_var_int(&dest, (uint64_t)prefix_len);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\t*restart = (prefix_len == 0);\n+\n+\tn = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\tif (dest.len < suffix_len)\n+\t\treturn -1;\n+\tmemcpy(dest.buf, key.buf + prefix_len, suffix_len);\n+\tstring_view_consume(&dest, suffix_len);\n+\n+\treturn start.len - dest.len;\n+}\n+\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t prefix_len = 0;\n+\tuint64_t suffix_len = 0;\n+\tint n = get_var_int(&prefix_len, &in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tif (prefix_len > last_key.len)\n+\t\treturn -1;\n+\n+\tn = get_var_int(&suffix_len, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\t*extra = (uint8_t)(suffix_len & 0x7);\n+\tsuffix_len >>= 3;\n+\n+\tif (in.len < suffix_len)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(key);\n+\tstrbuf_add(key, last_key.buf, prefix_len);\n+\tstrbuf_add(key, in.buf, suffix_len);\n+\tstring_view_consume(&in, suffix_len);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static void reftable_ref_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_ref_record *rec =\n+\t\t(const struct reftable_ref_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, rec->refname);\n+}\n+\n+static void reftable_ref_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_ref_record *ref = rec;\n+\tconst struct reftable_ref_record *src = src_rec;\n+\tassert(hash_size > 0);\n+\n+\t/* This is simple and correct, but we could probably reuse the hash\n+\t * fields. */\n+\treftable_ref_record_release(ref);\n+\tif (src->refname) {\n+\t\tref->refname = xstrdup(src->refname);\n+\t}\n+\tref->update_index = src->update_index;\n+\tref->value_type = src->value_type;\n+\tswitch (src->value_type) {\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tref->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val1, src->value.val1, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tref->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.value, src->value.val2.value, hash_size);\n+\t\tref->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.target_value,\n+\t\t       src->value.val2.target_value, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tref->value.symref = xstrdup(src->value.symref);\n+\t\tbreak;\n+\t}\n+}\n+\n+static char hexdigit(int c)\n+{\n+\tif (c <= 9)\n+\t\treturn '0' + c;\n+\treturn 'a' + (c - 10);\n+}\n+\n+static void hex_format(char *dest, uint8_t *src, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (src) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < hash_size; i++) {\n+\t\t\tdest[2 * i] = hexdigit(src[i] >> 4);\n+\t\t\tdest[2 * i + 1] = hexdigit(src[i] & 0xf);\n+\t\t}\n+\t\tdest[2 * hash_size] = 0;\n+\t}\n+}\n+\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */\n+\tprintf(\"ref{%s(%\" PRIu64 \") \", ref->refname, ref->update_index);\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tprintf(\"=> %s\", ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\thex_format(hex, ref->value.val2.value, hash_size(hash_id));\n+\t\tprintf(\"val 2 %s\", hex);\n+\t\thex_format(hex, ref->value.val2.target_value,\n+\t\t\t   hash_size(hash_id));\n+\t\tprintf(\"(T %s)\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\thex_format(hex, ref->value.val1, hash_size(hash_id));\n+\t\tprintf(\"val 1 %s\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tprintf(\"delete\");\n+\t\tbreak;\n+\t}\n+\tprintf(\"}\\n\");\n+}\n+\n+static void reftable_ref_record_release_void(void *rec)\n+{\n+\treftable_ref_record_release(rec);\n+}\n+\n+void reftable_ref_record_release(struct reftable_ref_record *ref)\n+{\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treftable_free(ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treftable_free(ref->value.val2.target_value);\n+\t\treftable_free(ref->value.val2.value);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\treftable_free(ref->value.val1);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treftable_free(ref->refname);\n+\tmemset(ref, 0, sizeof(struct reftable_ref_record));\n+}\n+\n+static uint8_t reftable_ref_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\treturn r->value_type;\n+}\n+\n+static int reftable_ref_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\tstruct string_view start = s;\n+\tint n = put_var_int(&s, r->update_index);\n+\tassert(hash_size > 0);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tn = encode_string(r->value.symref, s);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (s.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val2.value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tmemcpy(s.buf, r->value.val2.target_value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (s.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val1, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_ref_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct reftable_ref_record *r = rec;\n+\tstruct string_view start = in;\n+\tuint64_t update_index = 0;\n+\tint n = get_var_int(&update_index, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\treftable_ref_record_release(r);\n+\n+\tassert(hash_size > 0);\n+\n+\tr->refname = reftable_realloc(r->refname, key.len + 1);\n+\tmemcpy(r->refname, key.buf, key.len);\n+\tr->update_index = update_index;\n+\tr->refname[key.len] = 0;\n+\tr->value_type = val_type;\n+\tswitch (val_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (in.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val1, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (in.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\n+\t\tr->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.target_value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_SYMREF: {\n+\t\tstruct strbuf dest = STRBUF_INIT;\n+\t\tint n = decode_string(&dest, in);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\t\tr->value.symref = dest.buf;\n+\t} break;\n+\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t\tbreak;\n+\t}\n+\n+\treturn start.len - in.len;\n+}\n+\n+static int reftable_ref_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_ref_record_is_deletion(\n+\t\t(const struct reftable_ref_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_ref_record_vtable = {\n+\t.key = &reftable_ref_record_key,\n+\t.type = BLOCK_TYPE_REF,\n+\t.copy_from = &reftable_ref_record_copy_from,\n+\t.val_type = &reftable_ref_record_val_type,\n+\t.encode = &reftable_ref_record_encode,\n+\t.decode = &reftable_ref_record_decode,\n+\t.release = &reftable_ref_record_release_void,\n+\t.is_deletion = &reftable_ref_record_is_deletion_void,\n+};\n+\n+static void reftable_obj_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_obj_record *rec =\n+\t\t(const struct reftable_obj_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len);\n+}\n+\n+static void reftable_obj_record_release(void *rec)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tFREE_AND_NULL(obj->hash_prefix);\n+\tFREE_AND_NULL(obj->offsets);\n+\tmemset(obj, 0, sizeof(struct reftable_obj_record));\n+}\n+\n+static void reftable_obj_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tconst struct reftable_obj_record *src =\n+\t\t(const struct reftable_obj_record *)src_rec;\n+\n+\treftable_obj_record_release(obj);\n+\t*obj = *src;\n+\tobj->hash_prefix = reftable_malloc(obj->hash_prefix_len);\n+\tmemcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);\n+\n+\tobj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));\n+\tCOPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);\n+}\n+\n+static uint8_t reftable_obj_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tif (r->offset_len > 0 && r->offset_len < 8)\n+\t\treturn r->offset_len;\n+\treturn 0;\n+}\n+\n+static int reftable_obj_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tstruct string_view start = s;\n+\tint i = 0;\n+\tint n = 0;\n+\tuint64_t last = 0;\n+\tif (r->offset_len == 0 || r->offset_len >= 8) {\n+\t\tn = put_var_int(&s, r->offset_len);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t}\n+\tif (r->offset_len == 0)\n+\t\treturn start.len - s.len;\n+\tn = put_var_int(&s, r->offsets[0]);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tlast = r->offsets[0];\n+\tfor (i = 1; i < r->offset_len; i++) {\n+\t\tint n = put_var_int(&s, r->offsets[i] - last);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tlast = r->offsets[i];\n+\t}\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_obj_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_obj_record *r = rec;\n+\tuint64_t count = val_type;\n+\tint n = 0;\n+\tuint64_t last;\n+\tint j;\n+\tr->hash_prefix = reftable_malloc(key.len);\n+\tmemcpy(r->hash_prefix, key.buf, key.len);\n+\tr->hash_prefix_len = key.len;\n+\n+\tif (val_type == 0) {\n+\t\tn = get_var_int(&count, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\n+\t\tstring_view_consume(&in, n);\n+\t}\n+\n+\tr->offsets = NULL;\n+\tr->offset_len = 0;\n+\tif (count == 0)\n+\t\treturn start.len - in.len;\n+\n+\tr->offsets = reftable_malloc(count * sizeof(uint64_t));\n+\tr->offset_len = count;\n+\n+\tn = get_var_int(&r->offsets[0], &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\tlast = r->offsets[0];\n+\tj = 1;\n+\twhile (j < count) {\n+\t\tuint64_t delta = 0;\n+\t\tint n = get_var_int(&delta, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\n+\t\tlast = r->offsets[j] = (delta + last);\n+\t\tj++;\n+\t}\n+\treturn start.len - in.len;\n+}\n+\n+static int not_a_deletion(const void *p)\n+{\n+\treturn 0;\n+}\n+\n+static struct reftable_record_vtable reftable_obj_record_vtable = {\n+\t.key = &reftable_obj_record_key,\n+\t.type = BLOCK_TYPE_OBJ,\n+\t.copy_from = &reftable_obj_record_copy_from,\n+\t.val_type = &reftable_obj_record_val_type,\n+\t.encode = &reftable_obj_record_encode,\n+\t.decode = &reftable_obj_record_decode,\n+\t.release = &reftable_obj_record_release,\n+\t.is_deletion = not_a_deletion,\n+};\n+\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[GIT_SHA256_RAWSZ + 1] = { 0 };\n+\n+\tswitch (log->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") delete\", log->refname,\n+\t\t       log->update_index);\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") %s <%s> %\" PRIu64 \" %04d\\n\",\n+\t\t       log->refname, log->update_index, log->value.update.name,\n+\t\t       log->value.update.email, log->value.update.time,\n+\t\t       log->value.update.tz_offset);\n+\t\thex_format(hex, log->value.update.old_hash, hash_size(hash_id));\n+\t\tprintf(\"%s => \", hex);\n+\t\thex_format(hex, log->value.update.new_hash, hash_size(hash_id));\n+\t\tprintf(\"%s\\n\\n%s\\n}\\n\", hex, log->value.update.message);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_log_record *rec =\n+\t\t(const struct reftable_log_record *)r;\n+\tint len = strlen(rec->refname);\n+\tuint8_t i64[8];\n+\tuint64_t ts = 0;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, (uint8_t *)rec->refname, len + 1);\n+\n+\tts = (~ts) - rec->update_index;\n+\tput_be64(&i64[0], ts);\n+\tstrbuf_add(dest, i64, sizeof(i64));\n+}\n+\n+static void reftable_log_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_log_record *dst = rec;\n+\tconst struct reftable_log_record *src =\n+\t\t(const struct reftable_log_record *)src_rec;\n+\n+\treftable_log_record_release(dst);\n+\t*dst = *src;\n+\tif (dst->refname) {\n+\t\tdst->refname = xstrdup(dst->refname);\n+\t}\n+\tswitch (dst->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tif (dst->value.update.email) {\n+\t\t\tdst->value.update.email =\n+\t\t\t\txstrdup(dst->value.update.email);\n+\t\t}\n+\t\tif (dst->value.update.name) {\n+\t\t\tdst->value.update.name =\n+\t\t\t\txstrdup(dst->value.update.name);\n+\t\t}\n+\t\tif (dst->value.update.message) {\n+\t\t\tdst->value.update.message =\n+\t\t\t\txstrdup(dst->value.update.message);\n+\t\t}\n+\n+\t\tif (dst->value.update.new_hash) {\n+\t\t\tdst->value.update.new_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.new_hash,\n+\t\t\t       src->value.update.new_hash, hash_size);\n+\t\t}\n+\t\tif (dst->value.update.old_hash) {\n+\t\t\tdst->value.update.old_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.old_hash,\n+\t\t\t       src->value.update.old_hash, hash_size);\n+\t\t}\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_release_void(void *rec)\n+{\n+\tstruct reftable_log_record *r = rec;\n+\treftable_log_record_release(r);\n+}\n+\n+void reftable_log_record_release(struct reftable_log_record *r)\n+{\n+\treftable_free(r->refname);\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treftable_free(r->value.update.new_hash);\n+\t\treftable_free(r->value.update.old_hash);\n+\t\treftable_free(r->value.update.name);\n+\t\treftable_free(r->value.update.email);\n+\t\treftable_free(r->value.update.message);\n+\t\tbreak;\n+\t}\n+\tmemset(r, 0, sizeof(struct reftable_log_record));\n+}\n+\n+static uint8_t reftable_log_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_log_record *log =\n+\t\t(const struct reftable_log_record *)rec;\n+\n+\treturn reftable_log_record_is_deletion(log) ? 0 : 1;\n+}\n+\n+static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 };\n+\n+static int reftable_log_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_log_record *r = rec;\n+\tstruct string_view start = s;\n+\tint n = 0;\n+\tuint8_t *oldh = NULL;\n+\tuint8_t *newh = NULL;\n+\tif (reftable_log_record_is_deletion(r))\n+\t\treturn 0;\n+\n+\toldh = r->value.update.old_hash;\n+\tnewh = r->value.update.new_hash;\n+\tif (!oldh) {\n+\t\toldh = zero;\n+\t}\n+\tif (!newh) {\n+\t\tnewh = zero;\n+\t}\n+\n+\tif (s.len < 2 * hash_size)\n+\t\treturn -1;\n+\n+\tmemcpy(s.buf, oldh, hash_size);\n+\tmemcpy(s.buf + hash_size, newh, hash_size);\n+\tstring_view_consume(&s, 2 * hash_size);\n+\n+\tn = encode_string(r->value.update.name ? r->value.update.name : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = encode_string(r->value.update.email ? r->value.update.email : \"\",\n+\t\t\t  s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = put_var_int(&s, r->value.update.time);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tif (s.len < 2)\n+\t\treturn -1;\n+\n+\tput_be16(s.buf, r->value.update.tz_offset);\n+\tstring_view_consume(&s, 2);\n+\n+\tn = encode_string(\n+\t\tr->value.update.message ? r->value.update.message : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_log_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_log_record *r = rec;\n+\tuint64_t max = 0;\n+\tuint64_t ts = 0;\n+\tstruct strbuf dest = STRBUF_INIT;\n+\tint n;\n+\n+\tif (key.len <= 9 || key.buf[key.len - 9] != 0)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->refname = reftable_realloc(r->refname, key.len - 8);\n+\tmemcpy(r->refname, key.buf, key.len - 8);\n+\tts = get_be64(key.buf + key.len - 8);\n+\n+\tr->update_index = (~max) - ts;\n+\n+\tif (val_type != r->value_type) {\n+\t\tswitch (r->value_type) {\n+\t\tcase REFTABLE_LOG_UPDATE:\n+\t\t\tFREE_AND_NULL(r->value.update.old_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.new_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.message);\n+\t\t\tFREE_AND_NULL(r->value.update.email);\n+\t\t\tFREE_AND_NULL(r->value.update.name);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_LOG_DELETION:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tr->value_type = val_type;\n+\tif (val_type == REFTABLE_LOG_DELETION)\n+\t\treturn 0;\n+\n+\tif (in.len < 2 * hash_size)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->value.update.old_hash =\n+\t\treftable_realloc(r->value.update.old_hash, hash_size);\n+\tr->value.update.new_hash =\n+\t\treftable_realloc(r->value.update.new_hash, hash_size);\n+\n+\tmemcpy(r->value.update.old_hash, in.buf, hash_size);\n+\tmemcpy(r->value.update.new_hash, in.buf + hash_size, hash_size);\n+\n+\tstring_view_consume(&in, 2 * hash_size);\n+\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.name =\n+\t\treftable_realloc(r->value.update.name, dest.len + 1);\n+\tmemcpy(r->value.update.name, dest.buf, dest.len);\n+\tr->value.update.name[dest.len] = 0;\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.email =\n+\t\treftable_realloc(r->value.update.email, dest.len + 1);\n+\tmemcpy(r->value.update.email, dest.buf, dest.len);\n+\tr->value.update.email[dest.len] = 0;\n+\n+\tts = 0;\n+\tn = get_var_int(&ts, &in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\tr->value.update.time = ts;\n+\tif (in.len < 2)\n+\t\tgoto done;\n+\n+\tr->value.update.tz_offset = get_be16(in.buf);\n+\tstring_view_consume(&in, 2);\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.message =\n+\t\treftable_realloc(r->value.update.message, dest.len + 1);\n+\tmemcpy(r->value.update.message, dest.buf, dest.len);\n+\tr->value.update.message[dest.len] = 0;\n+\n+\tstrbuf_release(&dest);\n+\treturn start.len - in.len;\n+\n+done:\n+\tstrbuf_release(&dest);\n+\treturn REFTABLE_FORMAT_ERROR;\n+}\n+\n+static int null_streq(char *a, char *b)\n+{\n+\tchar *empty = \"\";\n+\tif (!a)\n+\t\ta = empty;\n+\n+\tif (!b)\n+\t\tb = empty;\n+\n+\treturn 0 == strcmp(a, b);\n+}\n+\n+static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)\n+{\n+\tif (!a)\n+\t\ta = zero;\n+\n+\tif (!b)\n+\t\tb = zero;\n+\n+\treturn !memcmp(a, b, sz);\n+}\n+\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size)\n+{\n+\tif (!(null_streq(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\treturn 1;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treturn null_streq(a->value.update.name, b->value.update.name) &&\n+\t\t       a->value.update.time == b->value.update.time &&\n+\t\t       a->value.update.tz_offset == b->value.update.tz_offset &&\n+\t\t       null_streq(a->value.update.email,\n+\t\t\t\t  b->value.update.email) &&\n+\t\t       null_streq(a->value.update.message,\n+\t\t\t\t  b->value.update.message) &&\n+\t\t       zero_hash_eq(a->value.update.old_hash,\n+\t\t\t\t    b->value.update.old_hash, hash_size) &&\n+\t\t       zero_hash_eq(a->value.update.new_hash,\n+\t\t\t\t    b->value.update.new_hash, hash_size);\n+\t}\n+\n+\tabort();\n+}\n+\n+static int reftable_log_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_log_record_is_deletion(\n+\t\t(const struct reftable_log_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_log_record_vtable = {\n+\t.key = &reftable_log_record_key,\n+\t.type = BLOCK_TYPE_LOG,\n+\t.copy_from = &reftable_log_record_copy_from,\n+\t.val_type = &reftable_log_record_val_type,\n+\t.encode = &reftable_log_record_encode,\n+\t.decode = &reftable_log_record_decode,\n+\t.release = &reftable_log_record_release_void,\n+\t.is_deletion = &reftable_log_record_is_deletion_void,\n+};\n+\n+struct reftable_record reftable_new_record(uint8_t typ)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF: {\n+\t\tstruct reftable_ref_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_ref_record));\n+\t\treftable_record_from_ref(&rec, r);\n+\t\treturn rec;\n+\t}\n+\n+\tcase BLOCK_TYPE_OBJ: {\n+\t\tstruct reftable_obj_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_obj_record));\n+\t\treftable_record_from_obj(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_LOG: {\n+\t\tstruct reftable_log_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_log_record));\n+\t\treftable_record_from_log(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_INDEX: {\n+\t\tstruct reftable_index_record empty = { .last_key =\n+\t\t\t\t\t\t\t       STRBUF_INIT };\n+\t\tstruct reftable_index_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_index_record));\n+\t\t*r = empty;\n+\t\treftable_record_from_index(&rec, r);\n+\t\treturn rec;\n+\t}\n+\t}\n+\tabort();\n+\treturn rec;\n+}\n+\n+/* clear out the record, yielding the reftable_record data that was\n+ * encapsulated. */\n+static void *reftable_record_yield(struct reftable_record *rec)\n+{\n+\tvoid *p = rec->data;\n+\trec->data = NULL;\n+\treturn p;\n+}\n+\n+void reftable_record_destroy(struct reftable_record *rec)\n+{\n+\treftable_record_release(rec);\n+\treftable_free(reftable_record_yield(rec));\n+}\n+\n+static void reftable_index_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_index_record *rec = r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addbuf(dest, &rec->last_key);\n+}\n+\n+static void reftable_index_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t    int hash_size)\n+{\n+\tstruct reftable_index_record *dst = rec;\n+\tconst struct reftable_index_record *src = src_rec;\n+\n+\tstrbuf_reset(&dst->last_key);\n+\tstrbuf_addbuf(&dst->last_key, &src->last_key);\n+\tdst->offset = src->offset;\n+}\n+\n+static void reftable_index_record_release(void *rec)\n+{\n+\tstruct reftable_index_record *idx = rec;\n+\tstrbuf_release(&idx->last_key);\n+}\n+\n+static uint8_t reftable_index_record_val_type(const void *rec)\n+{\n+\treturn 0;\n+}\n+\n+static int reftable_index_record_encode(const void *rec, struct string_view out,\n+\t\t\t\t\tint hash_size)\n+{\n+\tconst struct reftable_index_record *r =\n+\t\t(const struct reftable_index_record *)rec;\n+\tstruct string_view start = out;\n+\n+\tint n = put_var_int(&out, r->offset);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&out, n);\n+\n+\treturn start.len - out.len;\n+}\n+\n+static int reftable_index_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t\tuint8_t val_type, struct string_view in,\n+\t\t\t\t\tint hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_index_record *r = rec;\n+\tint n = 0;\n+\n+\tstrbuf_reset(&r->last_key);\n+\tstrbuf_addbuf(&r->last_key, &key);\n+\n+\tn = get_var_int(&r->offset, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&in, n);\n+\treturn start.len - in.len;\n+}\n+\n+static struct reftable_record_vtable reftable_index_record_vtable = {\n+\t.key = &reftable_index_record_key,\n+\t.type = BLOCK_TYPE_INDEX,\n+\t.copy_from = &reftable_index_record_copy_from,\n+\t.val_type = &reftable_index_record_val_type,\n+\t.encode = &reftable_index_record_encode,\n+\t.decode = &reftable_index_record_decode,\n+\t.release = &reftable_index_record_release,\n+\t.is_deletion = &not_a_deletion,\n+};\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)\n+{\n+\trec->ops->key(rec->data, dest);\n+}\n+\n+uint8_t reftable_record_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->type;\n+}\n+\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size)\n+{\n+\treturn rec->ops->encode(rec->data, dest, hash_size);\n+}\n+\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size)\n+{\n+\tassert(src->ops->type == rec->ops->type);\n+\n+\trec->ops->copy_from(rec->data, src->data, hash_size);\n+}\n+\n+uint8_t reftable_record_val_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->val_type(rec->data);\n+}\n+\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src, int hash_size)\n+{\n+\treturn rec->ops->decode(rec->data, key, extra, src, hash_size);\n+}\n+\n+void reftable_record_release(struct reftable_record *rec)\n+{\n+\trec->ops->release(rec->data);\n+}\n+\n+int reftable_record_is_deletion(struct reftable_record *rec)\n+{\n+\treturn rec->ops->is_deletion(rec->data);\n+}\n+\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *ref_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = ref_rec;\n+\trec->ops = &reftable_ref_record_vtable;\n+}\n+\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *obj_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = obj_rec;\n+\trec->ops = &reftable_obj_record_vtable;\n+}\n+\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *index_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = index_rec;\n+\trec->ops = &reftable_index_record_vtable;\n+}\n+\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *log_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = log_rec;\n+\trec->ops = &reftable_log_record_vtable;\n+}\n+\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_REF);\n+\treturn rec->data;\n+}\n+\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_LOG);\n+\treturn rec->data;\n+}\n+\n+static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)\n+{\n+\tif (a && b)\n+\t\treturn !memcmp(a, b, hash_size);\n+\n+\treturn a == b;\n+}\n+\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (!(0 == strcmp(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treturn !strcmp(a->value.symref, b->value.symref);\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn hash_equal(a->value.val2.value, b->value.val2.value,\n+\t\t\t\t  hash_size) &&\n+\t\t       hash_equal(a->value.val2.target_value,\n+\t\t\t\t  b->value.val2.target_value, hash_size);\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn hash_equal(a->value.val1, b->value.val1, hash_size);\n+\tcase REFTABLE_REF_DELETION:\n+\t\treturn 1;\n+\tdefault:\n+\t\tabort();\n+\t}\n+}\n+\n+int reftable_ref_record_compare_name(const void *a, const void *b)\n+{\n+\treturn strcmp(((struct reftable_ref_record *)a)->refname,\n+\t\t      ((struct reftable_ref_record *)b)->refname);\n+}\n+\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref)\n+{\n+\treturn ref->value_type == REFTABLE_REF_DELETION;\n+}\n+\n+int reftable_log_record_compare_key(const void *a, const void *b)\n+{\n+\tconst struct reftable_log_record *la = a;\n+\tconst struct reftable_log_record *lb = b;\n+\n+\tint cmp = strcmp(la->refname, lb->refname);\n+\tif (cmp)\n+\t\treturn cmp;\n+\tif (la->update_index > lb->update_index)\n+\t\treturn -1;\n+\treturn (la->update_index < lb->update_index) ? 1 : 0;\n+}\n+\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log)\n+{\n+\treturn (log->value_type == REFTABLE_LOG_DELETION);\n+}\n+\n+void string_view_consume(struct string_view *s, int n)\n+{\n+\ts->buf += n;\n+\ts->len -= n;\n+}\ndiff --git a/reftable/record.h b/reftable/record.h\nnew file mode 100644\nindex 00000000000..498e8c50bf4\n--- /dev/null\n+++ b/reftable/record.h\n@@ -0,0 +1,139 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef RECORD_H\n+#define RECORD_H\n+\n+#include \"system.h\"\n+\n+#include <stdint.h>\n+\n+#include \"reftable-record.h\"\n+\n+/*\n+ * A substring of existing string data. This structure takes no responsibility\n+ * for the lifetime of the data it points to.\n+ */\n+struct string_view {\n+\tuint8_t *buf;\n+\tsize_t len;\n+};\n+\n+/* Advance `s.buf` by `n`, and decrease length. */\n+void string_view_consume(struct string_view *s, int n);\n+\n+/* utilities for de/encoding varints */\n+\n+int get_var_int(uint64_t *dest, struct string_view *in);\n+int put_var_int(struct string_view *dest, uint64_t val);\n+\n+/* Methods for records. */\n+struct reftable_record_vtable {\n+\t/* encode the key of to a uint8_t strbuf. */\n+\tvoid (*key)(const void *rec, struct strbuf *dest);\n+\n+\t/* The record type of ('r' for ref). */\n+\tuint8_t type;\n+\n+\tvoid (*copy_from)(void *dest, const void *src, int hash_size);\n+\n+\t/* a value of [0..7], indicating record subvariants (eg. ref vs. symref\n+\t * vs ref deletion) */\n+\tuint8_t (*val_type)(const void *rec);\n+\n+\t/* encodes rec into dest, returning how much space was used. */\n+\tint (*encode)(const void *rec, struct string_view dest, int hash_size);\n+\n+\t/* decode data from `src` into the record. */\n+\tint (*decode)(void *rec, struct strbuf key, uint8_t extra,\n+\t\t      struct string_view src, int hash_size);\n+\n+\t/* deallocate and null the record. */\n+\tvoid (*release)(void *rec);\n+\n+\t/* is this a tombstone? */\n+\tint (*is_deletion)(const void *rec);\n+};\n+\n+/* record is a generic wrapper for different types of records. */\n+struct reftable_record {\n+\tvoid *data;\n+\tstruct reftable_record_vtable *ops;\n+};\n+\n+/* returns true for recognized block types. Block start with the block type. */\n+int reftable_is_block_type(uint8_t typ);\n+\n+/* creates a malloced record of the given type. Dispose with record_destroy */\n+struct reftable_record reftable_new_record(uint8_t typ);\n+\n+/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns\n+ * number of bytes written. */\n+int reftable_encode_key(int *is_restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra);\n+\n+/* Decode into `key` and `extra` from `in` */\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in);\n+\n+/* reftable_index_record are used internally to speed up lookups. */\n+struct reftable_index_record {\n+\tuint64_t offset; /* Offset of block */\n+\tstruct strbuf last_key; /* Last key of the block. */\n+};\n+\n+/* reftable_obj_record stores an object ID => ref mapping. */\n+struct reftable_obj_record {\n+\tuint8_t *hash_prefix; /* leading bytes of the object ID */\n+\tint hash_prefix_len; /* number of leading bytes. Constant\n+\t\t\t      * across a single table. */\n+\tuint64_t *offsets; /* a vector of file offsets. */\n+\tint offset_len;\n+};\n+\n+/* see struct record_vtable */\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);\n+uint8_t reftable_record_type(struct reftable_record *rec);\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size);\n+uint8_t reftable_record_val_type(struct reftable_record *rec);\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size);\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src,\n+\t\t\t   int hash_size);\n+int reftable_record_is_deletion(struct reftable_record *rec);\n+\n+/* zeroes out the embedded record */\n+void reftable_record_release(struct reftable_record *rec);\n+\n+/* clear and deallocate embedded record, and zero `rec`. */\n+void reftable_record_destroy(struct reftable_record *rec);\n+\n+/* initialize generic records from concrete records. The generic record should\n+ * be zeroed out. */\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *objrec);\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *idxrec);\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *refrec);\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *logrec);\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);\n+\n+/* for qsort. */\n+int reftable_ref_record_compare_name(const void *a, const void *b);\n+\n+/* for qsort. */\n+int reftable_log_record_compare_key(const void *a, const void *b);\n+\n+#endif\ndiff --git a/reftable/record_test.c b/reftable/record_test.c\nnew file mode 100644\nindex 00000000000..f4ad7cace41\n--- /dev/null\n+++ b/reftable/record_test.c\n@@ -0,0 +1,412 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_copy(struct reftable_record *rec)\n+{\n+\tstruct reftable_record copy =\n+\t\treftable_new_record(reftable_record_type(rec));\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\t/* do it twice to catch memory leaks */\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\tswitch (reftable_record_type(&copy)) {\n+\tcase BLOCK_TYPE_REF:\n+\t\tEXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),\n+\t\t\t\t\t\t reftable_record_as_ref(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\tcase BLOCK_TYPE_LOG:\n+\t\tEXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),\n+\t\t\t\t\t\t reftable_record_as_log(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\t}\n+\treftable_record_destroy(&copy);\n+}\n+\n+static void test_varint_roundtrip(void)\n+{\n+\tuint64_t inputs[] = { 0,\n+\t\t\t      1,\n+\t\t\t      27,\n+\t\t\t      127,\n+\t\t\t      128,\n+\t\t\t      257,\n+\t\t\t      4096,\n+\t\t\t      ((uint64_t)1 << 63),\n+\t\t\t      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(inputs); i++) {\n+\t\tuint8_t dest[10];\n+\n+\t\tstruct string_view out = {\n+\t\t\t.buf = dest,\n+\t\t\t.len = sizeof(dest),\n+\t\t};\n+\t\tuint64_t in = inputs[i];\n+\t\tint n = put_var_int(&out, in);\n+\t\tuint64_t got = 0;\n+\n+\t\tEXPECT(n > 0);\n+\t\tout.len = n;\n+\t\tn = get_var_int(&got, &out);\n+\t\tEXPECT(n > 0);\n+\n+\t\tEXPECT(got == in);\n+\t}\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct {\n+\t\tconst char *a, *b;\n+\t\tint want;\n+\t} cases[] = {\n+\t\t{ \"abc\", \"ab\", 2 },\n+\t\t{ \"\", \"abc\", 0 },\n+\t\t{ \"abc\", \"abd\", 2 },\n+\t\t{ \"abc\", \"pqr\", 0 },\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\tEXPECT(common_prefix_size(&a, &b) == cases[i].want);\n+\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n+}\n+\n+static void set_hash(uint8_t *h, int j)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < hash_size(GIT_SHA1_FORMAT_ID); i++) {\n+\t\th[i] = (j >> i) & 0xff;\n+\t}\n+}\n+\n+static void test_reftable_ref_record_roundtrip(void)\n+{\n+\tint i = 0;\n+\n+\tfor (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {\n+\t\tstruct reftable_ref_record in = { NULL };\n+\t\tstruct reftable_ref_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\n+\t\tint n, m;\n+\n+\t\tin.value_type = i;\n+\t\tswitch (i) {\n+\t\tcase REFTABLE_REF_DELETION:\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL1:\n+\t\t\tin.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val1, 1);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL2:\n+\t\t\tin.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.value, 1);\n+\t\t\tin.value.val2.target_value =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.target_value, 2);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_SYMREF:\n+\t\t\tin.value.symref = xstrdup(\"target\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tin.refname = xstrdup(\"refs/heads/master\");\n+\n+\t\treftable_record_from_ref(&rec, &in);\n+\t\ttest_copy(&rec);\n+\n+\t\tEXPECT(reftable_record_val_type(&rec) == i);\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\n+\t\t/* decode into a non-zero reftable_record to test for leaks. */\n+\n+\t\treftable_record_from_ref(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, i, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));\n+\t\treftable_record_release(&rec_out);\n+\n+\t\tstrbuf_release(&key);\n+\t\treftable_ref_record_release(&in);\n+\t}\n+}\n+\n+static void test_reftable_log_record_equal(void)\n+{\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t}\n+\t};\n+\n+\tEXPECT(!reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\tin[1].update_index = in[0].update_index;\n+\tEXPECT(reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\treftable_log_record_release(&in[0]);\n+\treftable_log_record_release(&in[1]);\n+}\n+\n+static void test_reftable_log_record_roundtrip(void)\n+{\n+\tint i;\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.old_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.new_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"han-wen\"),\n+\t\t\t\t\t.email = xstrdup(\"hanwen@google.com\"),\n+\t\t\t\t\t.message = xstrdup(\"test\"),\n+\t\t\t\t\t.time = 1577123507,\n+\t\t\t\t\t.tz_offset = 100,\n+\t\t\t\t},\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t}\n+\t};\n+\tset_test_hash(in[0].value.update.new_hash, 1);\n+\tset_test_hash(in[0].value.update.old_hash, 2);\n+\tfor (i = 0; i < ARRAY_SIZE(in); i++) {\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\t/* populate out, to check for leaks. */\n+\t\tstruct reftable_log_record out = {\n+\t\t\t.refname = xstrdup(\"old name\"),\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"old name\"),\n+\t\t\t\t\t.email = xstrdup(\"old@email\"),\n+\t\t\t\t\t.message = xstrdup(\"old message\"),\n+\t\t\t\t},\n+\t\t\t},\n+\t\t};\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m, valtype;\n+\n+\t\treftable_record_from_log(&rec, &in[i]);\n+\n+\t\ttest_copy(&rec);\n+\n+\t\treftable_record_key(&rec, &key);\n+\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n >= 0);\n+\t\treftable_record_from_log(&rec_out, &out);\n+\t\tvaltype = reftable_record_val_type(&rec);\n+\t\tm = reftable_record_decode(&rec_out, key, valtype, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&in[i]);\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_u24_roundtrip(void)\n+{\n+\tuint32_t in = 0x112233;\n+\tuint8_t dest[3];\n+\tuint32_t out;\n+\tput_be24(dest, in);\n+\tout = get_be24(dest);\n+\tEXPECT(in == out);\n+}\n+\n+static void test_key_roundtrip(void)\n+{\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct strbuf roundtrip = STRBUF_INIT;\n+\tint restart;\n+\tuint8_t extra;\n+\tint n, m;\n+\tuint8_t rt_extra;\n+\n+\tstrbuf_addstr(&last_key, \"refs/heads/master\");\n+\tstrbuf_addstr(&key, \"refs/tags/bla\");\n+\textra = 6;\n+\tn = reftable_encode_key(&restart, dest, last_key, key, extra);\n+\tEXPECT(!restart);\n+\tEXPECT(n > 0);\n+\n+\tm = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);\n+\tEXPECT(n == m);\n+\tEXPECT(0 == strbuf_cmp(&key, &roundtrip));\n+\tEXPECT(rt_extra == extra);\n+\n+\tstrbuf_release(&last_key);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&roundtrip);\n+}\n+\n+static void test_reftable_obj_record_roundtrip(void)\n+{\n+\tuint8_t testHash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 4, 0 };\n+\tuint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };\n+\tstruct reftable_obj_record recs[3] = { {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 3,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 9,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t       } };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(recs); i++) {\n+\t\tstruct reftable_obj_record in = recs[i];\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_obj_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m;\n+\t\tuint8_t extra;\n+\n+\t\treftable_record_from_obj(&rec, &in);\n+\t\ttest_copy(&rec);\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\t\textra = reftable_record_val_type(&rec);\n+\t\treftable_record_from_obj(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, extra, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(in.hash_prefix_len == out.hash_prefix_len);\n+\t\tEXPECT(in.offset_len == out.offset_len);\n+\n+\t\tEXPECT(!memcmp(in.hash_prefix, out.hash_prefix,\n+\t\t\t       in.hash_prefix_len));\n+\t\tEXPECT(0 == memcmp(in.offsets, out.offsets,\n+\t\t\t\t   sizeof(uint64_t) * in.offset_len));\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_reftable_index_record_roundtrip(void)\n+{\n+\tstruct reftable_index_record in = {\n+\t\t.offset = 42,\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_index_record out = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record out_rec = { NULL };\n+\tint n, m;\n+\tuint8_t extra;\n+\n+\tstrbuf_addstr(&in.last_key, \"refs/heads/master\");\n+\treftable_record_from_index(&rec, &in);\n+\treftable_record_key(&rec, &key);\n+\ttest_copy(&rec);\n+\n+\tEXPECT(0 == strbuf_cmp(&key, &in.last_key));\n+\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(n > 0);\n+\n+\textra = reftable_record_val_type(&rec);\n+\treftable_record_from_index(&out_rec, &out);\n+\tm = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(m == n);\n+\n+\tEXPECT(in.offset == out.offset);\n+\n+\treftable_record_release(&out_rec);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&in.last_key);\n+}\n+\n+int record_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_reftable_log_record_equal);\n+\tRUN_TEST(test_reftable_log_record_roundtrip);\n+\tRUN_TEST(test_reftable_ref_record_roundtrip);\n+\tRUN_TEST(test_varint_roundtrip);\n+\tRUN_TEST(test_key_roundtrip);\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_reftable_obj_record_roundtrip);\n+\tRUN_TEST(test_reftable_index_record_roundtrip);\n+\tRUN_TEST(test_u24_roundtrip);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-record.h b/reftable/reftable-record.h\nnew file mode 100644\nindex 00000000000..5370d2288c7\n--- /dev/null\n+++ b/reftable/reftable-record.h\n@@ -0,0 +1,114 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_RECORD_H\n+#define REFTABLE_RECORD_H\n+\n+#include <stdint.h>\n+\n+/*\n+ * Basic data types\n+ *\n+ * Reftables store the state of each ref in struct reftable_ref_record, and they\n+ * store a sequence of reflog updates in struct reftable_log_record.\n+ */\n+\n+/* reftable_ref_record holds a ref database entry target_value */\n+struct reftable_ref_record {\n+\tchar *refname; /* Name of the ref, malloced. */\n+\tuint64_t update_index; /* Logical timestamp at which this value is\n+\t\t\t\t* written */\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_REF_DELETION = 0x0,\n+\n+\t\t/* a simple ref */\n+\t\tREFTABLE_REF_VAL1 = 0x1,\n+\t\t/* a tag, plus its peeled hash */\n+\t\tREFTABLE_REF_VAL2 = 0x2,\n+\n+\t\t/* a symbolic reference */\n+\t\tREFTABLE_REF_SYMREF = 0x3,\n+#define REFTABLE_NR_REF_VALUETYPES 4\n+\t} value_type;\n+\tunion {\n+\t\tuint8_t *val1; /* malloced hash. */\n+\t\tstruct {\n+\t\t\tuint8_t *value; /* first value, malloced hash  */\n+\t\t\tuint8_t *target_value; /* second value, malloced hash */\n+\t\t} val2;\n+\t\tchar *symref; /* referent, malloced 0-terminated string */\n+\t} value;\n+};\n+\n+/* Returns the first hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);\n+\n+/* Returns the second hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);\n+\n+/* returns whether 'ref' represents a deletion */\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);\n+\n+/* prints a reftable_ref_record onto stdout. Useful for debugging. */\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id);\n+\n+/* frees and nulls all pointer values inside `ref`. */\n+void reftable_ref_record_release(struct reftable_ref_record *ref);\n+\n+/* returns whether two reftable_ref_records are the same. Useful for testing. */\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size);\n+\n+/* reftable_log_record holds a reflog entry */\n+struct reftable_log_record {\n+\tchar *refname;\n+\tuint64_t update_index; /* logical timestamp of a transactional update.\n+\t\t\t\t*/\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_LOG_DELETION = 0x0,\n+\n+\t\t/* a simple update */\n+\t\tREFTABLE_LOG_UPDATE = 0x1,\n+#define REFTABLE_NR_LOG_VALUETYPES 2\n+\t} value_type;\n+\n+\tunion {\n+\t\tstruct {\n+\t\t\tuint8_t *new_hash;\n+\t\t\tuint8_t *old_hash;\n+\t\t\tchar *name;\n+\t\t\tchar *email;\n+\t\t\tuint64_t time;\n+\t\t\tint16_t tz_offset;\n+\t\t\tchar *message;\n+\t\t} update;\n+\t} value;\n+};\n+\n+/* returns whether 'ref' represents the deletion of a log record. */\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log);\n+\n+/* frees and nulls all pointer values. */\n+void reftable_log_record_release(struct reftable_log_record *log);\n+\n+/* returns whether two records are equal. Useful for testing. */\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size);\n+\n+/* dumps a reftable_log_record on stdout, for debugging/testing. */\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 3b58e423e7b..09d4b83ef9b 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,6 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n-\n+\trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"435300","messageId":"e595dbc6c843fdb01c333fd20031d59716804b88.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 07/19] Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:32Z","receivedAt":"2021-09-09T18:48:36Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will be needed for reading reflog blocks in reftable.\n\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |  7 +++\n ci/lib.sh                 |  1 +\n compat/.gitattributes     |  1 +\n compat/zlib-uncompress2.c | 95 +++++++++++++++++++++++++++++++++++++++\n config.mak.uname          |  5 +++\n configure.ac              | 13 ++++++\n 6 files changed, 122 insertions(+)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n\ndiff --git a/Makefile b/Makefile\nindex 14d025ea424..1c2d0e6be7c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -256,6 +256,8 @@ all::\n #\n # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.\n #\n+# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.\n+#\n # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,\n # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)\n #\n@@ -1738,6 +1740,11 @@ ifdef NO_DEFLATE_BOUND\n \tBASIC_CFLAGS += -DNO_DEFLATE_BOUND\n endif\n \n+ifdef NO_UNCOMPRESS2\n+\tBASIC_CFLAGS += -DNO_UNCOMPRESS2\n+\tREFTABLE_OBJS += compat/zlib-uncompress2.o\n+endif\n+\n ifdef NO_POSIX_GOODIES\n \tBASIC_CFLAGS += -DNO_POSIX_GOODIES\n endif\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex 476c3f369f5..5711c63979d 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -224,6 +224,7 @@ linux-gcc-default)\n \t;;\n Linux32)\n \tCC=gcc\n+\tMAKEFLAGS=\"$MAKEFLAGS NO_UNCOMPRESS2=1\"\n \t;;\n linux-musl)\n \tCC=gcc\ndiff --git a/compat/.gitattributes b/compat/.gitattributes\nnew file mode 100644\nindex 00000000000..40dbfb170da\n--- /dev/null\n+++ b/compat/.gitattributes\n@@ -0,0 +1 @@\n+/zlib-uncompress2.c\twhitespace=-indent-with-non-tab,-trailing-space\ndiff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c\nnew file mode 100644\nindex 00000000000..3dcb90b2839\n--- /dev/null\n+++ b/compat/zlib-uncompress2.c\n@@ -0,0 +1,95 @@\n+/* taken from zlib's uncompr.c\n+\n+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f\n+   Author: Mark Adler <madler@alumni.caltech.edu>\n+   Date:   Sun Jan 15 09:18:46 2017 -0800\n+\n+       zlib 1.2.11\n+\n+*/\n+\n+#include \"../reftable/system.h\"\n+#define z_const\n+\n+/*\n+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler\n+ * For conditions of distribution and use, see copyright notice in zlib.h\n+ */\n+\n+#include <zlib.h>\n+\n+/* clang-format off */\n+\n+/* ===========================================================================\n+     Decompresses the source buffer into the destination buffer.  *sourceLen is\n+   the byte length of the source buffer. Upon entry, *destLen is the total size\n+   of the destination buffer, which must be large enough to hold the entire\n+   uncompressed data. (The size of the uncompressed data must have been saved\n+   previously by the compressor and transmitted to the decompressor by some\n+   mechanism outside the scope of this compression library.) Upon exit,\n+   *destLen is the size of the decompressed data and *sourceLen is the number\n+   of source bytes consumed. Upon return, source + *sourceLen points to the\n+   first unused input byte.\n+\n+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough\n+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or\n+   Z_DATA_ERROR if the input data was corrupted, including if the input data is\n+   an incomplete zlib stream.\n+*/\n+int ZEXPORT uncompress2 (\n+    Bytef *dest,\n+    uLongf *destLen,\n+    const Bytef *source,\n+    uLong *sourceLen) {\n+    z_stream stream;\n+    int err;\n+    const uInt max = (uInt)-1;\n+    uLong len, left;\n+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */\n+\n+    len = *sourceLen;\n+    if (*destLen) {\n+        left = *destLen;\n+        *destLen = 0;\n+    }\n+    else {\n+        left = 1;\n+        dest = buf;\n+    }\n+\n+    stream.next_in = (z_const Bytef *)source;\n+    stream.avail_in = 0;\n+    stream.zalloc = (alloc_func)0;\n+    stream.zfree = (free_func)0;\n+    stream.opaque = (voidpf)0;\n+\n+    err = inflateInit(&stream);\n+    if (err != Z_OK) return err;\n+\n+    stream.next_out = dest;\n+    stream.avail_out = 0;\n+\n+    do {\n+        if (stream.avail_out == 0) {\n+            stream.avail_out = left > (uLong)max ? max : (uInt)left;\n+            left -= stream.avail_out;\n+        }\n+        if (stream.avail_in == 0) {\n+            stream.avail_in = len > (uLong)max ? max : (uInt)len;\n+            len -= stream.avail_in;\n+        }\n+        err = inflate(&stream, Z_NO_FLUSH);\n+    } while (err == Z_OK);\n+\n+    *sourceLen -= len + stream.avail_in;\n+    if (dest != buf)\n+        *destLen = stream.total_out;\n+    else if (stream.total_out && err == Z_BUF_ERROR)\n+        left = 1;\n+\n+    inflateEnd(&stream);\n+    return err == Z_STREAM_END ? Z_OK :\n+           err == Z_NEED_DICT ? Z_DATA_ERROR  :\n+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :\n+           err;\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 76516aaa9a5..9beacda4434 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -258,6 +258,11 @@ ifeq ($(uname_S),FreeBSD)\n \tFILENO_IS_A_MACRO = UnfortunatelyYes\n endif\n ifeq ($(uname_S),OpenBSD)\n+\tNO_UNCOMPRESS2 = YesPlease\n+\t# Versions < 7.0 need compatibility layer\n+\tifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n+\t\tNO_UNCOMPRESS2 = UnfortunatelyYes\n+\tendif\n \tNO_STRCASESTR = YesPlease\n \tNO_MEMMEM = YesPlease\n \tUSE_ST_TIMESPEC = YesPlease\ndiff --git a/configure.ac b/configure.ac\nindex 031e8d3fee8..c3a913103d0 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -672,9 +672,22 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],\n \tNO_DEFLATE_BOUND=yes])\n LIBS=\"$old_LIBS\"\n \n+AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [\n+AC_LANG_PROGRAM([#include <zlib.h>],\n+ [uncompress2(NULL,NULL,NULL,NULL);])])\n+AC_MSG_CHECKING([for uncompress2 in -lz])\n+old_LIBS=\"$LIBS\"\n+LIBS=\"$LIBS -lz\"\n+AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],\n+\t[AC_MSG_RESULT([yes])],\n+\t[AC_MSG_RESULT([no])\n+\tNO_UNCOMPRESS2=yes])\n+LIBS=\"$old_LIBS\"\n+\n GIT_UNSTASH_FLAGS($ZLIB_PATH)\n \n GIT_CONF_SUBST([NO_DEFLATE_BOUND])\n+GIT_CONF_SUBST([NO_UNCOMPRESS2])\n \n #\n # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,\n-- \ngitgitgadget\n\n"},{"id":"435301","messageId":"e9206532126ea165abb3ebd157ae3b0750e865cc.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 05/19] reftable: add blocksource, an abstraction for random access reads","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:30Z","receivedAt":"2021-09-09T18:48:40Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is usually used with files for storage. However, we abstract\naway this using the blocksource data structure. This has two advantages:\n\n* log blocks are zlib compressed, and handling them is simplified if we can\n  discard byte segments from within the block layer.\n\n* for unittests, it is useful to read and write in-memory. The blocksource\n  allows us to abstract the data away from on-disk files.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                        |   1 +\n reftable/blocksource.c          | 148 ++++++++++++++++++++++++++++++++\n reftable/blocksource.h          |  22 +++++\n reftable/reftable-blocksource.h |  49 +++++++++++\n 4 files changed, 220 insertions(+)\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/reftable-blocksource.h\n\ndiff --git a/Makefile b/Makefile\nindex 0bf5e49aa61..c7ffc27a727 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2451,6 +2451,7 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n \n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/blocksource.c b/reftable/blocksource.c\nnew file mode 100644\nindex 00000000000..0044eecd9aa\n--- /dev/null\n+++ b/reftable/blocksource.c\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+\n+static void strbuf_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void strbuf_close(void *b)\n+{\n+}\n+\n+static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t     uint32_t size)\n+{\n+\tstruct strbuf *b = v;\n+\tassert(off + size <= b->len);\n+\tdest->data = reftable_calloc(size);\n+\tmemcpy(dest->data, b->buf + off, size);\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static uint64_t strbuf_size(void *b)\n+{\n+\treturn ((struct strbuf *)b)->len;\n+}\n+\n+static struct reftable_block_source_vtable strbuf_vtable = {\n+\t.size = &strbuf_size,\n+\t.read_block = &strbuf_read_block,\n+\t.return_block = &strbuf_return_block,\n+\t.close = &strbuf_close,\n+};\n+\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf)\n+{\n+\tassert(!bs->ops);\n+\tbs->ops = &strbuf_vtable;\n+\tbs->arg = buf;\n+}\n+\n+static void malloc_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static struct reftable_block_source_vtable malloc_vtable = {\n+\t.return_block = &malloc_return_block,\n+};\n+\n+static struct reftable_block_source malloc_block_source_instance = {\n+\t.ops = &malloc_vtable,\n+};\n+\n+struct reftable_block_source malloc_block_source(void)\n+{\n+\treturn malloc_block_source_instance;\n+}\n+\n+struct file_block_source {\n+\tint fd;\n+\tuint64_t size;\n+};\n+\n+static uint64_t file_size(void *b)\n+{\n+\treturn ((struct file_block_source *)b)->size;\n+}\n+\n+static void file_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void file_close(void *b)\n+{\n+\tint fd = ((struct file_block_source *)b)->fd;\n+\tif (fd > 0) {\n+\t\tclose(fd);\n+\t\t((struct file_block_source *)b)->fd = 0;\n+\t}\n+\n+\treftable_free(b);\n+}\n+\n+static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t   uint32_t size)\n+{\n+\tstruct file_block_source *b = v;\n+\tassert(off + size <= b->size);\n+\tdest->data = reftable_malloc(size);\n+\tif (pread(b->fd, dest->data, size, off) != size)\n+\t\treturn -1;\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static struct reftable_block_source_vtable file_vtable = {\n+\t.size = &file_size,\n+\t.read_block = &file_read_block,\n+\t.return_block = &file_return_block,\n+\t.close = &file_close,\n+};\n+\n+int reftable_block_source_from_file(struct reftable_block_source *bs,\n+\t\t\t\t    const char *name)\n+{\n+\tstruct stat st = { 0 };\n+\tint err = 0;\n+\tint fd = open(name, O_RDONLY);\n+\tstruct file_block_source *p = NULL;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\treturn REFTABLE_NOT_EXIST_ERROR;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\terr = fstat(fd, &st);\n+\tif (err < 0)\n+\t\treturn -1;\n+\n+\tp = reftable_calloc(sizeof(struct file_block_source));\n+\tp->size = st.st_size;\n+\tp->fd = fd;\n+\n+\tassert(!bs->ops);\n+\tbs->ops = &file_vtable;\n+\tbs->arg = p;\n+\treturn 0;\n+}\ndiff --git a/reftable/blocksource.h b/reftable/blocksource.h\nnew file mode 100644\nindex 00000000000..072e2727ad2\n--- /dev/null\n+++ b/reftable/blocksource.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCKSOURCE_H\n+#define BLOCKSOURCE_H\n+\n+#include \"system.h\"\n+\n+struct reftable_block_source;\n+\n+/* Create an in-memory block source for reading reftables */\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf);\n+\n+struct reftable_block_source malloc_block_source(void);\n+\n+#endif\ndiff --git a/reftable/reftable-blocksource.h b/reftable/reftable-blocksource.h\nnew file mode 100644\nindex 00000000000..5aa3990a573\n--- /dev/null\n+++ b/reftable/reftable-blocksource.h\n@@ -0,0 +1,49 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_BLOCKSOURCE_H\n+#define REFTABLE_BLOCKSOURCE_H\n+\n+#include <stdint.h>\n+\n+/* block_source is a generic wrapper for a seekable readable file.\n+ */\n+struct reftable_block_source {\n+\tstruct reftable_block_source_vtable *ops;\n+\tvoid *arg;\n+};\n+\n+/* a contiguous segment of bytes. It keeps track of its generating block_source\n+ * so it can return itself into the pool. */\n+struct reftable_block {\n+\tuint8_t *data;\n+\tint len;\n+\tstruct reftable_block_source source;\n+};\n+\n+/* block_source_vtable are the operations that make up block_source */\n+struct reftable_block_source_vtable {\n+\t/* returns the size of a block source */\n+\tuint64_t (*size)(void *source);\n+\n+\t/* reads a segment from the block source. It is an error to read\n+\t   beyond the end of the block */\n+\tint (*read_block)(void *source, struct reftable_block *dest,\n+\t\t\t  uint64_t off, uint32_t size);\n+\t/* mark the block as read; may return the data back to malloc */\n+\tvoid (*return_block)(void *source, struct reftable_block *blockp);\n+\n+\t/* release all resources associated with the block source */\n+\tvoid (*close)(void *source);\n+};\n+\n+/* opens a file on the file system as a block_source */\n+int reftable_block_source_from_file(struct reftable_block_source *block_src,\n+\t\t\t\t    const char *name);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"435302","messageId":"97f7ee04886d573f6b0f2e0d54a853cf016e7d74.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 08/19] reftable: reading/writing blocks","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:33Z","receivedAt":"2021-09-09T18:48:42Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of block. Within a block,\nrecords are prefix compressed, with an index of offsets for fully expand keys to\nenable binary search within blocks.\n\nThis commit provides the logic to read and write these blocks.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   2 +\n reftable/block.c         | 439 +++++++++++++++++++++++++++++++++++++++\n reftable/block.h         | 127 +++++++++++\n reftable/block_test.c    | 120 +++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 689 insertions(+)\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 1c2d0e6be7c..14a0a1f7643 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2458,10 +2458,12 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\ndiff --git a/reftable/block.c b/reftable/block.c\nnew file mode 100644\nindex 00000000000..11387b260aa\n--- /dev/null\n+++ b/reftable/block.c\n@@ -0,0 +1,439 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+#include <zlib.h>\n+\n+int header_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 24;\n+\tcase 2:\n+\t\treturn 28;\n+\t}\n+\tabort();\n+}\n+\n+int footer_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 68;\n+\tcase 2:\n+\t\treturn 72;\n+\t}\n+\tabort();\n+}\n+\n+static int block_writer_register_restart(struct block_writer *w, int n,\n+\t\t\t\t\t int is_restart, struct strbuf *key)\n+{\n+\tint rlen = w->restart_len;\n+\tif (rlen >= MAX_RESTARTS) {\n+\t\tis_restart = 0;\n+\t}\n+\n+\tif (is_restart) {\n+\t\trlen++;\n+\t}\n+\tif (2 + 3 * rlen + n > w->block_size - w->next)\n+\t\treturn -1;\n+\tif (is_restart) {\n+\t\tif (w->restart_len == w->restart_cap) {\n+\t\t\tw->restart_cap = w->restart_cap * 2 + 1;\n+\t\t\tw->restarts = reftable_realloc(\n+\t\t\t\tw->restarts, sizeof(uint32_t) * w->restart_cap);\n+\t\t}\n+\n+\t\tw->restarts[w->restart_len++] = w->next;\n+\t}\n+\n+\tw->next += n;\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, key);\n+\tw->entries++;\n+\treturn 0;\n+}\n+\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size)\n+{\n+\tbw->buf = buf;\n+\tbw->hash_size = hash_size;\n+\tbw->block_size = block_size;\n+\tbw->header_off = header_off;\n+\tbw->buf[header_off] = typ;\n+\tbw->next = header_off + 4;\n+\tbw->restart_interval = 16;\n+\tbw->entries = 0;\n+\tbw->restart_len = 0;\n+\tbw->last_key.len = 0;\n+}\n+\n+uint8_t block_writer_type(struct block_writer *bw)\n+{\n+\treturn bw->buf[bw->header_off];\n+}\n+\n+/* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on\n+   success */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tstruct strbuf last =\n+\t\tw->entries % w->restart_interval == 0 ? empty : w->last_key;\n+\tstruct string_view out = {\n+\t\t.buf = w->buf + w->next,\n+\t\t.len = w->block_size - w->next,\n+\t};\n+\n+\tstruct string_view start = out;\n+\n+\tint is_restart = 0;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint n = 0;\n+\n+\treftable_record_key(rec, &key);\n+\tn = reftable_encode_key(&is_restart, out, last, key,\n+\t\t\t\treftable_record_val_type(rec));\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tn = reftable_record_encode(rec, out, w->hash_size);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tif (block_writer_register_restart(w, start.len - out.len, is_restart,\n+\t\t\t\t\t  &key) < 0)\n+\t\tgoto done;\n+\n+\tstrbuf_release(&key);\n+\treturn 0;\n+\n+done:\n+\tstrbuf_release(&key);\n+\treturn -1;\n+}\n+\n+int block_writer_finish(struct block_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->restart_len; i++) {\n+\t\tput_be24(w->buf + w->next, w->restarts[i]);\n+\t\tw->next += 3;\n+\t}\n+\n+\tput_be16(w->buf + w->next, w->restart_len);\n+\tw->next += 2;\n+\tput_be24(w->buf + 1 + w->header_off, w->next);\n+\n+\tif (block_writer_type(w) == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + w->header_off;\n+\t\tuint8_t *compressed = NULL;\n+\t\tint zresult = 0;\n+\t\tuLongf src_len = w->next - block_header_skip;\n+\t\tsize_t dest_cap = src_len;\n+\n+\t\tcompressed = reftable_malloc(dest_cap);\n+\t\twhile (1) {\n+\t\t\tuLongf out_dest_len = dest_cap;\n+\n+\t\t\tzresult = compress2(compressed, &out_dest_len,\n+\t\t\t\t\t    w->buf + block_header_skip, src_len,\n+\t\t\t\t\t    9);\n+\t\t\tif (zresult == Z_BUF_ERROR) {\n+\t\t\t\tdest_cap *= 2;\n+\t\t\t\tcompressed =\n+\t\t\t\t\treftable_realloc(compressed, dest_cap);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (Z_OK != zresult) {\n+\t\t\t\treftable_free(compressed);\n+\t\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t\t}\n+\n+\t\t\tmemcpy(w->buf + block_header_skip, compressed,\n+\t\t\t       out_dest_len);\n+\t\t\tw->next = out_dest_len + block_header_skip;\n+\t\t\treftable_free(compressed);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn w->next;\n+}\n+\n+uint8_t block_reader_type(struct block_reader *r)\n+{\n+\treturn r->block.data[r->header_off];\n+}\n+\n+int block_reader_init(struct block_reader *br, struct reftable_block *block,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size)\n+{\n+\tuint32_t full_block_size = table_block_size;\n+\tuint8_t typ = block->data[header_off];\n+\tuint32_t sz = get_be24(block->data + header_off + 1);\n+\n+\tuint16_t restart_count = 0;\n+\tuint32_t restart_start = 0;\n+\tuint8_t *restart_bytes = NULL;\n+\n+\tif (!reftable_is_block_type(typ))\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tif (typ == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + header_off;\n+\t\tuLongf dst_len = sz - block_header_skip; /* total size of dest\n+\t\t\t\t\t\t\t    buffer. */\n+\t\tuLongf src_len = block->len - block_header_skip;\n+\t\t/* Log blocks specify the *uncompressed* size in their header.\n+\t\t */\n+\t\tuint8_t *uncompressed = reftable_malloc(sz);\n+\n+\t\t/* Copy over the block header verbatim. It's not compressed. */\n+\t\tmemcpy(uncompressed, block->data, block_header_skip);\n+\n+\t\t/* Uncompress */\n+\t\tif (Z_OK !=\n+\t\t    uncompress2(uncompressed + block_header_skip, &dst_len,\n+\t\t\t\tblock->data + block_header_skip, &src_len)) {\n+\t\t\treftable_free(uncompressed);\n+\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t}\n+\n+\t\tif (dst_len + block_header_skip != sz)\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\t\t/* We're done with the input data. */\n+\t\treftable_block_done(block);\n+\t\tblock->data = uncompressed;\n+\t\tblock->len = sz;\n+\t\tblock->source = malloc_block_source();\n+\t\tfull_block_size = src_len + block_header_skip;\n+\t} else if (full_block_size == 0) {\n+\t\tfull_block_size = sz;\n+\t} else if (sz < full_block_size && sz < block->len &&\n+\t\t   block->data[sz] != 0) {\n+\t\t/* If the block is smaller than the full block size, it is\n+\t\t   padded (data followed by '\\0') or the next block is\n+\t\t   unaligned. */\n+\t\tfull_block_size = sz;\n+\t}\n+\n+\trestart_count = get_be16(block->data + sz - 2);\n+\trestart_start = sz - 2 - 3 * restart_count;\n+\trestart_bytes = block->data + restart_start;\n+\n+\t/* transfer ownership. */\n+\tbr->block = *block;\n+\tblock->data = NULL;\n+\tblock->len = 0;\n+\n+\tbr->hash_size = hash_size;\n+\tbr->block_len = restart_start;\n+\tbr->full_block_size = full_block_size;\n+\tbr->header_off = header_off;\n+\tbr->restart_count = restart_count;\n+\tbr->restart_bytes = restart_bytes;\n+\n+\treturn 0;\n+}\n+\n+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)\n+{\n+\treturn get_be24(br->restart_bytes + 3 * i);\n+}\n+\n+void block_reader_start(struct block_reader *br, struct block_iter *it)\n+{\n+\tit->br = br;\n+\tstrbuf_reset(&it->last_key);\n+\tit->next_off = br->header_off + 4;\n+}\n+\n+struct restart_find_args {\n+\tint error;\n+\tstruct strbuf key;\n+\tstruct block_reader *r;\n+};\n+\n+static int restart_key_less(size_t idx, void *args)\n+{\n+\tstruct restart_find_args *a = args;\n+\tuint32_t off = block_reader_restart_offset(a->r, idx);\n+\tstruct string_view in = {\n+\t\t.buf = a->r->block.data + off,\n+\t\t.len = a->r->block_len - off,\n+\t};\n+\n+\t/* the restart key is verbatim in the block, so this could avoid the\n+\t   alloc for decoding the key */\n+\tstruct strbuf rkey = STRBUF_INIT;\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tuint8_t unused_extra;\n+\tint n = reftable_decode_key(&rkey, &unused_extra, last_key, in);\n+\tint result;\n+\tif (n < 0) {\n+\t\ta->error = 1;\n+\t\treturn -1;\n+\t}\n+\n+\tresult = strbuf_cmp(&a->key, &rkey);\n+\tstrbuf_release(&rkey);\n+\treturn result;\n+}\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)\n+{\n+\tdest->br = src->br;\n+\tdest->next_off = src->next_off;\n+\tstrbuf_reset(&dest->last_key);\n+\tstrbuf_addbuf(&dest->last_key, &src->last_key);\n+}\n+\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec)\n+{\n+\tstruct string_view in = {\n+\t\t.buf = it->br->block.data + it->next_off,\n+\t\t.len = it->br->block_len - it->next_off,\n+\t};\n+\tstruct string_view start = in;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tuint8_t extra = 0;\n+\tint n = 0;\n+\n+\tif (it->next_off >= it->br->block_len)\n+\t\treturn 1;\n+\n+\tn = reftable_decode_key(&key, &extra, it->last_key, in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\n+\tstring_view_consume(&in, n);\n+\tn = reftable_record_decode(rec, key, extra, in, it->br->hash_size);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tstrbuf_reset(&it->last_key);\n+\tstrbuf_addbuf(&it->last_key, &key);\n+\tit->next_off += start.len - in.len;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tint off = br->header_off + 4;\n+\tstruct string_view in = {\n+\t\t.buf = br->block.data + off,\n+\t\t.len = br->block_len - off,\n+\t};\n+\n+\tuint8_t extra = 0;\n+\tint n = reftable_decode_key(key, &extra, empty, in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\treturn 0;\n+}\n+\n+int block_iter_seek(struct block_iter *it, struct strbuf *want)\n+{\n+\treturn block_reader_seek(it->br, it, want);\n+}\n+\n+void block_iter_close(struct block_iter *it)\n+{\n+\tstrbuf_release(&it->last_key);\n+}\n+\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want)\n+{\n+\tstruct restart_find_args args = {\n+\t\t.key = *want,\n+\t\t.r = br,\n+\t};\n+\tstruct reftable_record rec = reftable_new_record(block_reader_type(br));\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = 0;\n+\tstruct block_iter next = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\n+\tint i = binsearch(br->restart_count, &restart_key_less, &args);\n+\tif (args.error) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tit->br = br;\n+\tif (i > 0) {\n+\t\ti--;\n+\t\tit->next_off = block_reader_restart_offset(br, i);\n+\t} else {\n+\t\tit->next_off = br->header_off + 4;\n+\t}\n+\n+\t/* We're looking for the last entry less/equal than the wanted key, so\n+\t   we have to go one entry too far and then back up.\n+\t*/\n+\twhile (1) {\n+\t\tblock_iter_copy_from(&next, it);\n+\t\terr = block_iter_next(&next, &rec);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tif (err > 0 || strbuf_cmp(&key, want) >= 0) {\n+\t\t\terr = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tblock_iter_copy_from(it, &next);\n+\t}\n+\n+done:\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&next.last_key);\n+\treftable_record_destroy(&rec);\n+\n+\treturn err;\n+}\n+\n+void block_writer_release(struct block_writer *bw)\n+{\n+\tFREE_AND_NULL(bw->restarts);\n+\tstrbuf_release(&bw->last_key);\n+\t/* the block is not owned. */\n+}\n+\n+void reftable_block_done(struct reftable_block *blockp)\n+{\n+\tstruct reftable_block_source source = blockp->source;\n+\tif (blockp && source.ops)\n+\t\tsource.ops->return_block(source.arg, blockp);\n+\tblockp->data = NULL;\n+\tblockp->len = 0;\n+\tblockp->source.ops = NULL;\n+\tblockp->source.arg = NULL;\n+}\ndiff --git a/reftable/block.h b/reftable/block.h\nnew file mode 100644\nindex 00000000000..e207706a644\n--- /dev/null\n+++ b/reftable/block.h\n@@ -0,0 +1,127 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCK_H\n+#define BLOCK_H\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Writes reftable blocks. The block_writer is reused across blocks to minimize\n+ * allocation overhead.\n+ */\n+struct block_writer {\n+\tuint8_t *buf;\n+\tuint32_t block_size;\n+\n+\t/* Offset ofof the global header. Nonzero in the first block only. */\n+\tuint32_t header_off;\n+\n+\t/* How often to restart keys. */\n+\tint restart_interval;\n+\tint hash_size;\n+\n+\t/* Offset of next uint8_t to write. */\n+\tuint32_t next;\n+\tuint32_t *restarts;\n+\tuint32_t restart_len;\n+\tuint32_t restart_cap;\n+\n+\tstruct strbuf last_key;\n+\tint entries;\n+};\n+\n+/*\n+ * initializes the blockwriter to write `typ` entries, using `buf` as temporary\n+ * storage. `buf` is not owned by the block_writer. */\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size);\n+\n+/* returns the block type (eg. 'r' for ref records. */\n+uint8_t block_writer_type(struct block_writer *bw);\n+\n+/* appends the record, or -1 if it doesn't fit. */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec);\n+\n+/* appends the key restarts, and compress the block if necessary. */\n+int block_writer_finish(struct block_writer *w);\n+\n+/* clears out internally allocated block_writer members. */\n+void block_writer_release(struct block_writer *bw);\n+\n+/* Read a block. */\n+struct block_reader {\n+\t/* offset of the block header; nonzero for the first block in a\n+\t * reftable. */\n+\tuint32_t header_off;\n+\n+\t/* the memory block */\n+\tstruct reftable_block block;\n+\tint hash_size;\n+\n+\t/* size of the data, excluding restart data. */\n+\tuint32_t block_len;\n+\tuint8_t *restart_bytes;\n+\tuint16_t restart_count;\n+\n+\t/* size of the data in the file. For log blocks, this is the compressed\n+\t * size. */\n+\tuint32_t full_block_size;\n+};\n+\n+/* Iterate over entries in a block */\n+struct block_iter {\n+\t/* offset within the block of the next entry to read. */\n+\tuint32_t next_off;\n+\tstruct block_reader *br;\n+\n+\t/* key for last entry we read. */\n+\tstruct strbuf last_key;\n+};\n+\n+/* initializes a block reader. */\n+int block_reader_init(struct block_reader *br, struct reftable_block *bl,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size);\n+\n+/* Position `it` at start of the block */\n+void block_reader_start(struct block_reader *br, struct block_iter *it);\n+\n+/* Position `it` to the `want` key in the block */\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want);\n+\n+/* Returns the block type (eg. 'r' for refs) */\n+uint8_t block_reader_type(struct block_reader *r);\n+\n+/* Decodes the first key in the block */\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key);\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);\n+\n+/* return < 0 for error, 0 for OK, > 0 for EOF. */\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec);\n+\n+/* Seek to `want` with in the block pointed to by `it` */\n+int block_iter_seek(struct block_iter *it, struct strbuf *want);\n+\n+/* deallocate memory for `it`. The block reader and its block is left intact. */\n+void block_iter_close(struct block_iter *it);\n+\n+/* size of file header, depending on format version */\n+int header_size(int version);\n+\n+/* size of file footer, depending on format version */\n+int footer_size(int version);\n+\n+/* returns a block to its source. */\n+void reftable_block_done(struct reftable_block *ret);\n+\n+#endif\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nnew file mode 100644\nindex 00000000000..4b3ea262dcb\n--- /dev/null\n+++ b/reftable/block_test.c\n@@ -0,0 +1,120 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"system.h\"\n+#include \"blocksource.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_block_read_write(void)\n+{\n+\tconst int header_off = 21; /* random */\n+\tchar *names[30];\n+\tconst int N = ARRAY_SIZE(names);\n+\tconst int block_size = 1024;\n+\tstruct reftable_block block = { NULL };\n+\tstruct block_writer bw = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_record rec = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\tint j = 0;\n+\tstruct strbuf want = STRBUF_INIT;\n+\n+\tblock.data = reftable_calloc(block_size);\n+\tblock.len = block_size;\n+\tblock.source = malloc_block_source();\n+\tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n+\t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n+\treftable_record_from_ref(&rec, &ref);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tsnprintf(name, sizeof(name), \"branch%02d\", i);\n+\t\tmemset(hash, i, sizeof(hash));\n+\n+\t\tref.refname = name;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\n+\t\tnames[i] = xstrdup(name);\n+\t\tn = block_writer_add(&bw, &rec);\n+\t\tref.refname = NULL;\n+\t\tref.value_type = REFTABLE_REF_DELETION;\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = block_writer_finish(&bw);\n+\tEXPECT(n > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);\n+\n+\tblock_reader_start(&br, &it);\n+\n+\twhile (1) {\n+\t\tint r = block_iter_next(&it, &rec);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT_STREQ(names[j], ref.refname);\n+\t\tj++;\n+\t}\n+\n+\treftable_record_release(&rec);\n+\tblock_iter_close(&it);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\t\tstrbuf_reset(&want);\n+\t\tstrbuf_addstr(&want, names[i]);\n+\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\n+\t\tEXPECT_STREQ(names[i], ref.refname);\n+\n+\t\twant.len--;\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT_STREQ(names[10 * (i / 10)], ref.refname);\n+\n+\t\tblock_iter_close(&it);\n+\t}\n+\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\tstrbuf_release(&want);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+}\n+\n+int block_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_block_read_write);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 09d4b83ef9b..c9deeaf08c7 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,7 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n+\tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"435303","messageId":"8d2e8be5795c9e32726fe519b1c1979a581e3a44.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 09/19] reftable: a generic binary tree implementation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:34Z","receivedAt":"2021-09-09T18:48:43Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format includes support for an (OID => ref) map. This map can speed\nup visibility and reachability checks. In particular, various operations along\nthe fetch/push path within Gerrit have ben sped up by using this structure.\n\nThe map is constructed with help of a binary tree. Object IDs are hashes, so\nthey are uniformly distributed. Hence, the tree does not attempt forced\nrebalancing.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |  4 ++-\n reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++\n reftable/tree.h          | 34 ++++++++++++++++++++++\n reftable/tree_test.c     | 61 ++++++++++++++++++++++++++++++++++++++\n t/helper/test-reftable.c |  1 +\n 5 files changed, 162 insertions(+), 1 deletion(-)\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 14a0a1f7643..157ff1edba8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,11 +2462,13 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/tree.o\n \n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n-REFTABLE_TEST_OBJS += reftable/basics_test.o\n+REFTABLE_TEST_OBJS += reftable/tree_test.o\n \n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \ndiff --git a/reftable/tree.c b/reftable/tree.c\nnew file mode 100644\nindex 00000000000..82db7995dd6\n--- /dev/null\n+++ b/reftable/tree.c\n@@ -0,0 +1,63 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert)\n+{\n+\tint res;\n+\tif (*rootp == NULL) {\n+\t\tif (!insert) {\n+\t\t\treturn NULL;\n+\t\t} else {\n+\t\t\tstruct tree_node *n =\n+\t\t\t\treftable_calloc(sizeof(struct tree_node));\n+\t\t\tn->key = key;\n+\t\t\t*rootp = n;\n+\t\t\treturn *rootp;\n+\t\t}\n+\t}\n+\n+\tres = compare(key, (*rootp)->key);\n+\tif (res < 0)\n+\t\treturn tree_search(key, &(*rootp)->left, compare, insert);\n+\telse if (res > 0)\n+\t\treturn tree_search(key, &(*rootp)->right, compare, insert);\n+\treturn *rootp;\n+}\n+\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg)\n+{\n+\tif (t->left) {\n+\t\tinfix_walk(t->left, action, arg);\n+\t}\n+\taction(arg, t->key);\n+\tif (t->right) {\n+\t\tinfix_walk(t->right, action, arg);\n+\t}\n+}\n+\n+void tree_free(struct tree_node *t)\n+{\n+\tif (t == NULL) {\n+\t\treturn;\n+\t}\n+\tif (t->left) {\n+\t\ttree_free(t->left);\n+\t}\n+\tif (t->right) {\n+\t\ttree_free(t->right);\n+\t}\n+\treftable_free(t);\n+}\ndiff --git a/reftable/tree.h b/reftable/tree.h\nnew file mode 100644\nindex 00000000000..fbdd002e23a\n--- /dev/null\n+++ b/reftable/tree.h\n@@ -0,0 +1,34 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TREE_H\n+#define TREE_H\n+\n+/* tree_node is a generic binary search tree. */\n+struct tree_node {\n+\tvoid *key;\n+\tstruct tree_node *left, *right;\n+};\n+\n+/* looks for `key` in `rootp` using `compare` as comparison function. If insert\n+ * is set, insert the key if it's not found. Else, return NULL.\n+ */\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert);\n+\n+/* performs an infix walk of the tree. */\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg);\n+\n+/*\n+ * deallocates the tree nodes recursively. Keys should be deallocated separately\n+ * by walking over the tree. */\n+void tree_free(struct tree_node *t);\n+\n+#endif\ndiff --git a/reftable/tree_test.c b/reftable/tree_test.c\nnew file mode 100644\nindex 00000000000..cbff1255886\n--- /dev/null\n+++ b/reftable/tree_test.c\n@@ -0,0 +1,61 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static int test_compare(const void *a, const void *b)\n+{\n+\treturn (char *)a - (char *)b;\n+}\n+\n+struct curry {\n+\tvoid *last;\n+};\n+\n+static void check_increasing(void *arg, void *key)\n+{\n+\tstruct curry *c = arg;\n+\tif (c->last) {\n+\t\tEXPECT(test_compare(c->last, key) < 0);\n+\t}\n+\tc->last = key;\n+}\n+\n+static void test_tree(void)\n+{\n+\tstruct tree_node *root = NULL;\n+\n+\tvoid *values[11] = { NULL };\n+\tstruct tree_node *nodes[11] = { NULL };\n+\tint i = 1;\n+\tstruct curry c = { NULL };\n+\tdo {\n+\t\tnodes[i] = tree_search(values + i, &root, &test_compare, 1);\n+\t\ti = (i * 7) % 11;\n+\t} while (i != 1);\n+\n+\tfor (i = 1; i < ARRAY_SIZE(nodes); i++) {\n+\t\tEXPECT(values + i == nodes[i]->key);\n+\t\tEXPECT(nodes[i] ==\n+\t\t       tree_search(values + i, &root, &test_compare, 0));\n+\t}\n+\n+\tinfix_walk(root, check_increasing, &c);\n+\ttree_free(root);\n+}\n+\n+int tree_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_tree);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c9deeaf08c7..050551fa698 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"435304","messageId":"965af6ec06563dd5eb82d0d3c178d5b4778a3792.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 10/19] reftable: write reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:35Z","receivedAt":"2021-09-09T18:48:45Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   1 +\n reftable/reftable-writer.h | 148 ++++++++\n reftable/writer.c          | 690 +++++++++++++++++++++++++++++++++++++\n reftable/writer.h          |  50 +++\n 4 files changed, 889 insertions(+)\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n\ndiff --git a/Makefile b/Makefile\nindex 157ff1edba8..de5c4026201 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2463,6 +2463,7 @@ REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/tree.o\n+REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\ndiff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h\nnew file mode 100644\nindex 00000000000..af36462ced5\n--- /dev/null\n+++ b/reftable/reftable-writer.h\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_WRITER_H\n+#define REFTABLE_WRITER_H\n+\n+#include \"reftable-record.h\"\n+\n+#include <stdint.h>\n+#include <unistd.h> /* ssize_t */\n+\n+/* Writing single reftables */\n+\n+/* reftable_write_options sets options for writing a single reftable. */\n+struct reftable_write_options {\n+\t/* boolean: do not pad out blocks to block size. */\n+\tunsigned unpadded : 1;\n+\n+\t/* the blocksize. Should be less than 2^24. */\n+\tuint32_t block_size;\n+\n+\t/* boolean: do not generate a SHA1 => ref index. */\n+\tunsigned skip_index_objects : 1;\n+\n+\t/* how often to write complete keys in each block. */\n+\tint restart_interval;\n+\n+\t/* 4-byte identifier (\"sha1\", \"s256\") of the hash.\n+\t * Defaults to SHA1 if unset\n+\t */\n+\tuint32_t hash_id;\n+\n+\t/* boolean: do not check ref names for validity or dir/file conflicts.\n+\t */\n+\tunsigned skip_name_check : 1;\n+\n+\t/* boolean: copy log messages exactly. If unset, check that the message\n+\t *   is a single line, and add '\\n' if missing.\n+\t */\n+\tunsigned exact_log_message : 1;\n+};\n+\n+/* reftable_block_stats holds statistics for a single block type */\n+struct reftable_block_stats {\n+\t/* total number of entries written */\n+\tint entries;\n+\t/* total number of key restarts */\n+\tint restarts;\n+\t/* total number of blocks */\n+\tint blocks;\n+\t/* total number of index blocks */\n+\tint index_blocks;\n+\t/* depth of the index */\n+\tint max_index_level;\n+\n+\t/* offset of the first block for this type */\n+\tuint64_t offset;\n+\t/* offset of the top level index block for this type, or 0 if not\n+\t * present */\n+\tuint64_t index_offset;\n+};\n+\n+/* stats holds overall statistics for a single reftable */\n+struct reftable_stats {\n+\t/* total number of blocks written. */\n+\tint blocks;\n+\t/* stats for ref data */\n+\tstruct reftable_block_stats ref_stats;\n+\t/* stats for the SHA1 to ref map. */\n+\tstruct reftable_block_stats obj_stats;\n+\t/* stats for index blocks */\n+\tstruct reftable_block_stats idx_stats;\n+\t/* stats for log blocks */\n+\tstruct reftable_block_stats log_stats;\n+\n+\t/* disambiguation length of shortened object IDs. */\n+\tint object_id_len;\n+};\n+\n+/* reftable_new_writer creates a new writer */\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts);\n+\n+/* Set the range of update indices for the records we will add. When writing a\n+   table into a stack, the min should be at least\n+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.\n+\n+   For transactional updates to a stack, typically min==max, and the\n+   update_index can be obtained by inspeciting the stack. When converting an\n+   existing ref database into a single reftable, this would be a range of\n+   update-index timestamps.\n+ */\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max);\n+\n+/*\n+  Add a reftable_ref_record. The record should have names that come after\n+  already added records.\n+\n+  The update_index must be within the limits set by\n+  reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an\n+  REFTABLE_API_ERROR error to write a ref record after a log record.\n+*/\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/*\n+  Convenience function to add multiple reftable_ref_records; the function sorts\n+  the records before adding them, reordering the records array passed in.\n+*/\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n);\n+\n+/*\n+  adds reftable_log_records. Log records are keyed by (refname, decreasing\n+  update_index). The key for the record added must come after the already added\n+  log records.\n+*/\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/*\n+  Convenience function to add multiple reftable_log_records; the function sorts\n+  the records before adding them, reordering records array passed in.\n+*/\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n);\n+\n+/* reftable_writer_close finalizes the reftable. The writer is retained so\n+ * statistics can be inspected. */\n+int reftable_writer_close(struct reftable_writer *w);\n+\n+/* writer_stats returns the statistics on the reftable being written.\n+\n+   This struct becomes invalid when the writer is freed.\n+ */\n+const struct reftable_stats *writer_stats(struct reftable_writer *w);\n+\n+/* reftable_writer_free deallocates memory for the writer */\n+void reftable_writer_free(struct reftable_writer *w);\n+\n+#endif\ndiff --git a/reftable/writer.c b/reftable/writer.c\nnew file mode 100644\nindex 00000000000..3ca721e9f64\n--- /dev/null\n+++ b/reftable/writer.c\n@@ -0,0 +1,690 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"writer.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"tree.h\"\n+#include \"reftable-error.h\"\n+\n+/* finishes a block, and writes it to storage */\n+static int writer_flush_block(struct reftable_writer *w);\n+\n+/* deallocates memory related to the index */\n+static void writer_clear_index(struct reftable_writer *w);\n+\n+/* finishes writing a 'r' (refs) or 'g' (reflogs) section */\n+static int writer_finish_public_section(struct reftable_writer *w);\n+\n+static struct reftable_block_stats *\n+writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase 'r':\n+\t\treturn &w->stats.ref_stats;\n+\tcase 'o':\n+\t\treturn &w->stats.obj_stats;\n+\tcase 'i':\n+\t\treturn &w->stats.idx_stats;\n+\tcase 'g':\n+\t\treturn &w->stats.log_stats;\n+\t}\n+\tabort();\n+\treturn NULL;\n+}\n+\n+/* write data, queuing the padding for the next write. Returns negative for\n+ * error. */\n+static int padded_write(struct reftable_writer *w, uint8_t *data, size_t len,\n+\t\t\tint padding)\n+{\n+\tint n = 0;\n+\tif (w->pending_padding > 0) {\n+\t\tuint8_t *zeroed = reftable_calloc(w->pending_padding);\n+\t\tint n = w->write(w->write_arg, zeroed, w->pending_padding);\n+\t\tif (n < 0)\n+\t\t\treturn n;\n+\n+\t\tw->pending_padding = 0;\n+\t\treftable_free(zeroed);\n+\t}\n+\n+\tw->pending_padding = padding;\n+\tn = w->write(w->write_arg, data, len);\n+\tif (n < 0)\n+\t\treturn n;\n+\tn += padding;\n+\treturn 0;\n+}\n+\n+static void options_set_defaults(struct reftable_write_options *opts)\n+{\n+\tif (opts->restart_interval == 0) {\n+\t\topts->restart_interval = 16;\n+\t}\n+\n+\tif (opts->hash_id == 0) {\n+\t\topts->hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\tif (opts->block_size == 0) {\n+\t\topts->block_size = DEFAULT_BLOCK_SIZE;\n+\t}\n+}\n+\n+static int writer_version(struct reftable_writer *w)\n+{\n+\treturn (w->opts.hash_id == 0 || w->opts.hash_id == GIT_SHA1_FORMAT_ID) ?\n+\t\t\t     1 :\n+\t\t\t     2;\n+}\n+\n+static int writer_write_header(struct reftable_writer *w, uint8_t *dest)\n+{\n+\tmemcpy(dest, \"REFT\", 4);\n+\n+\tdest[4] = writer_version(w);\n+\n+\tput_be24(dest + 5, w->opts.block_size);\n+\tput_be64(dest + 8, w->min_update_index);\n+\tput_be64(dest + 16, w->max_update_index);\n+\tif (writer_version(w) == 2) {\n+\t\tput_be32(dest + 24, w->opts.hash_id);\n+\t}\n+\treturn header_size(writer_version(w));\n+}\n+\n+static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)\n+{\n+\tint block_start = 0;\n+\tif (w->next == 0) {\n+\t\tblock_start = header_size(writer_version(w));\n+\t}\n+\n+\tstrbuf_release(&w->last_key);\n+\tblock_writer_init(&w->block_writer_data, typ, w->block,\n+\t\t\t  w->opts.block_size, block_start,\n+\t\t\t  hash_size(w->opts.hash_id));\n+\tw->block_writer = &w->block_writer_data;\n+\tw->block_writer->restart_interval = w->opts.restart_interval;\n+}\n+\n+static struct strbuf reftable_empty_strbuf = STRBUF_INIT;\n+\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts)\n+{\n+\tstruct reftable_writer *wp =\n+\t\treftable_calloc(sizeof(struct reftable_writer));\n+\tstrbuf_init(&wp->block_writer_data.last_key, 0);\n+\toptions_set_defaults(opts);\n+\tif (opts->block_size >= (1 << 24)) {\n+\t\t/* TODO - error return? */\n+\t\tabort();\n+\t}\n+\twp->last_key = reftable_empty_strbuf;\n+\twp->block = reftable_calloc(opts->block_size);\n+\twp->write = writer_func;\n+\twp->write_arg = writer_arg;\n+\twp->opts = *opts;\n+\twriter_reinit_block_writer(wp, BLOCK_TYPE_REF);\n+\n+\treturn wp;\n+}\n+\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max)\n+{\n+\tw->min_update_index = min;\n+\tw->max_update_index = max;\n+}\n+\n+void reftable_writer_free(struct reftable_writer *w)\n+{\n+\treftable_free(w->block);\n+\treftable_free(w);\n+}\n+\n+struct obj_index_tree_node {\n+\tstruct strbuf hash;\n+\tuint64_t *offsets;\n+\tsize_t offset_len;\n+\tsize_t offset_cap;\n+};\n+\n+#define OBJ_INDEX_TREE_NODE_INIT    \\\n+\t{                           \\\n+\t\t.hash = STRBUF_INIT \\\n+\t}\n+\n+static int obj_index_tree_node_compare(const void *a, const void *b)\n+{\n+\treturn strbuf_cmp(&((const struct obj_index_tree_node *)a)->hash,\n+\t\t\t  &((const struct obj_index_tree_node *)b)->hash);\n+}\n+\n+static void writer_index_hash(struct reftable_writer *w, struct strbuf *hash)\n+{\n+\tuint64_t off = w->next;\n+\n+\tstruct obj_index_tree_node want = { .hash = *hash };\n+\n+\tstruct tree_node *node = tree_search(&want, &w->obj_index_tree,\n+\t\t\t\t\t     &obj_index_tree_node_compare, 0);\n+\tstruct obj_index_tree_node *key = NULL;\n+\tif (node == NULL) {\n+\t\tstruct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;\n+\t\tkey = reftable_malloc(sizeof(struct obj_index_tree_node));\n+\t\t*key = empty;\n+\n+\t\tstrbuf_reset(&key->hash);\n+\t\tstrbuf_addbuf(&key->hash, hash);\n+\t\ttree_search((void *)key, &w->obj_index_tree,\n+\t\t\t    &obj_index_tree_node_compare, 1);\n+\t} else {\n+\t\tkey = node->key;\n+\t}\n+\n+\tif (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {\n+\t\treturn;\n+\t}\n+\n+\tif (key->offset_len == key->offset_cap) {\n+\t\tkey->offset_cap = 2 * key->offset_cap + 1;\n+\t\tkey->offsets = reftable_realloc(\n+\t\t\tkey->offsets, sizeof(uint64_t) * key->offset_cap);\n+\t}\n+\n+\tkey->offsets[key->offset_len++] = off;\n+}\n+\n+static int writer_add_record(struct reftable_writer *w,\n+\t\t\t     struct reftable_record *rec)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = -1;\n+\treftable_record_key(rec, &key);\n+\tif (strbuf_cmp(&w->last_key, &key) >= 0) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, &key);\n+\tif (w->block_writer == NULL) {\n+\t\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\t}\n+\n+\tassert(block_writer_type(w->block_writer) == reftable_record_type(rec));\n+\n+\tif (block_writer_add(w->block_writer, rec) == 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\terr = block_writer_add(w->block_writer, rec);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = 0;\n+done:\n+\tstrbuf_release(&key);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_ref_record copy = *ref;\n+\tint err = 0;\n+\n+\tif (ref->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\tif (ref->update_index < w->min_update_index ||\n+\t    ref->update_index > w->max_update_index)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\treftable_record_from_ref(&rec, &copy);\n+\tcopy.update_index -= w->min_update_index;\n+\n+\terr = writer_add_record(w, &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, (char *)reftable_ref_record_val1(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, reftable_ref_record_val2(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(refs, n, reftable_ref_record_compare_name);\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_ref(w, &refs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int reftable_writer_add_log_verbatim(struct reftable_writer *w,\n+\t\t\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tif (w->block_writer &&\n+\t    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {\n+\t\tint err = writer_finish_public_section(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tw->next -= w->pending_padding;\n+\tw->pending_padding = 0;\n+\n+\treftable_record_from_log(&rec, log);\n+\treturn writer_add_record(w, &rec);\n+}\n+\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tchar *input_log_message = NULL;\n+\tstruct strbuf cleaned_message = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (log->value_type == REFTABLE_LOG_DELETION)\n+\t\treturn reftable_writer_add_log_verbatim(w, log);\n+\n+\tif (log->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\tinput_log_message = log->value.update.message;\n+\tif (!w->opts.exact_log_message && log->value.update.message) {\n+\t\tstrbuf_addstr(&cleaned_message, log->value.update.message);\n+\t\twhile (cleaned_message.len &&\n+\t\t       cleaned_message.buf[cleaned_message.len - 1] == '\\n')\n+\t\t\tstrbuf_setlen(&cleaned_message,\n+\t\t\t\t      cleaned_message.len - 1);\n+\t\tif (strchr(cleaned_message.buf, '\\n')) {\n+\t\t\t/* multiple lines not allowed. */\n+\t\t\terr = REFTABLE_API_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tstrbuf_addstr(&cleaned_message, \"\\n\");\n+\t\tlog->value.update.message = cleaned_message.buf;\n+\t}\n+\n+\terr = reftable_writer_add_log_verbatim(w, log);\n+\tlog->value.update.message = input_log_message;\n+done:\n+\tstrbuf_release(&cleaned_message);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(logs, n, reftable_log_record_compare_key);\n+\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_log(w, &logs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int writer_finish_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tuint64_t index_start = 0;\n+\tint max_level = 0;\n+\tint threshold = w->opts.unpadded ? 1 : 3;\n+\tint before_blocks = w->stats.idx_stats.blocks;\n+\tint err = writer_flush_block(w);\n+\tint i = 0;\n+\tstruct reftable_block_stats *bstats = NULL;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\twhile (w->index_len > threshold) {\n+\t\tstruct reftable_index_record *idx = NULL;\n+\t\tint idx_len = 0;\n+\n+\t\tmax_level++;\n+\t\tindex_start = w->next;\n+\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\tidx = w->index;\n+\t\tidx_len = w->index_len;\n+\n+\t\tw->index = NULL;\n+\t\tw->index_len = 0;\n+\t\tw->index_cap = 0;\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstruct reftable_record rec = { NULL };\n+\t\t\treftable_record_from_index(&rec, idx + i);\n+\t\t\tif (block_writer_add(w->block_writer, &rec) == 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\terr = writer_flush_block(w);\n+\t\t\tif (err < 0)\n+\t\t\t\treturn err;\n+\n+\t\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\t\terr = block_writer_add(w->block_writer, &rec);\n+\t\t\tif (err != 0) {\n+\t\t\t\t/* write into fresh block should always succeed\n+\t\t\t\t */\n+\t\t\t\tabort();\n+\t\t\t}\n+\t\t}\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstrbuf_release(&idx[i].last_key);\n+\t\t}\n+\t\treftable_free(idx);\n+\t}\n+\n+\twriter_clear_index(w);\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tbstats = writer_reftable_block_stats(w, typ);\n+\tbstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;\n+\tbstats->index_offset = index_start;\n+\tbstats->max_index_level = max_level;\n+\n+\t/* Reinit lastKey, as the next section can start with any key. */\n+\tw->last_key.len = 0;\n+\n+\treturn 0;\n+}\n+\n+struct common_prefix_arg {\n+\tstruct strbuf *last;\n+\tint max;\n+};\n+\n+static void update_common(void *void_arg, void *key)\n+{\n+\tstruct common_prefix_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tif (arg->last) {\n+\t\tint n = common_prefix_size(&entry->hash, arg->last);\n+\t\tif (n > arg->max) {\n+\t\t\targ->max = n;\n+\t\t}\n+\t}\n+\targ->last = &entry->hash;\n+}\n+\n+struct write_record_arg {\n+\tstruct reftable_writer *w;\n+\tint err;\n+};\n+\n+static void write_object_record(void *void_arg, void *key)\n+{\n+\tstruct write_record_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tstruct reftable_obj_record obj_rec = {\n+\t\t.hash_prefix = (uint8_t *)entry->hash.buf,\n+\t\t.hash_prefix_len = arg->w->stats.object_id_len,\n+\t\t.offsets = entry->offsets,\n+\t\t.offset_len = entry->offset_len,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\treftable_record_from_obj(&rec, &obj_rec);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\n+\targ->err = writer_flush_block(arg->w);\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\twriter_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\tobj_rec.offset_len = 0;\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\n+\t/* Should be able to write into a fresh block. */\n+\tassert(arg->err == 0);\n+\n+done:;\n+}\n+\n+static void object_record_free(void *void_arg, void *key)\n+{\n+\tstruct obj_index_tree_node *entry = key;\n+\n+\tFREE_AND_NULL(entry->offsets);\n+\tstrbuf_release(&entry->hash);\n+\treftable_free(entry);\n+}\n+\n+static int writer_dump_object_index(struct reftable_writer *w)\n+{\n+\tstruct write_record_arg closure = { .w = w };\n+\tstruct common_prefix_arg common = { NULL };\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &update_common, &common);\n+\t}\n+\tw->stats.object_id_len = common.max + 1;\n+\n+\twriter_reinit_block_writer(w, BLOCK_TYPE_OBJ);\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &write_object_record, &closure);\n+\t}\n+\n+\tif (closure.err < 0)\n+\t\treturn closure.err;\n+\treturn writer_finish_section(w);\n+}\n+\n+static int writer_finish_public_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = 0;\n+\tint err = 0;\n+\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\n+\ttyp = block_writer_type(w->block_writer);\n+\terr = writer_finish_section(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\tif (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&\n+\t    w->stats.ref_stats.index_blocks > 0) {\n+\t\terr = writer_dump_object_index(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &object_record_free, NULL);\n+\t\ttree_free(w->obj_index_tree);\n+\t\tw->obj_index_tree = NULL;\n+\t}\n+\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+int reftable_writer_close(struct reftable_writer *w)\n+{\n+\tuint8_t footer[72];\n+\tuint8_t *p = footer;\n+\tint err = writer_finish_public_section(w);\n+\tint empty_table = w->next == 0;\n+\tif (err != 0)\n+\t\tgoto done;\n+\tw->pending_padding = 0;\n+\tif (empty_table) {\n+\t\t/* Empty tables need a header anyway. */\n+\t\tuint8_t header[28];\n+\t\tint n = writer_write_header(w, header);\n+\t\terr = padded_write(w, header, n, 0);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\tp += writer_write_header(w, footer);\n+\tput_be64(p, w->stats.ref_stats.index_offset);\n+\tp += 8;\n+\tput_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);\n+\tp += 8;\n+\tput_be64(p, w->stats.obj_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be64(p, w->stats.log_stats.offset);\n+\tp += 8;\n+\tput_be64(p, w->stats.log_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be32(p, crc32(0, footer, p - footer));\n+\tp += 4;\n+\n+\terr = padded_write(w, footer, footer_size(writer_version(w)), 0);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (empty_table) {\n+\t\terr = REFTABLE_EMPTY_TABLE_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\t/* free up memory. */\n+\tblock_writer_release(&w->block_writer_data);\n+\twriter_clear_index(w);\n+\tstrbuf_release(&w->last_key);\n+\treturn err;\n+}\n+\n+static void writer_clear_index(struct reftable_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->index_len; i++) {\n+\t\tstrbuf_release(&w->index[i].last_key);\n+\t}\n+\n+\tFREE_AND_NULL(w->index);\n+\tw->index_len = 0;\n+\tw->index_cap = 0;\n+}\n+\n+static const int debug = 0;\n+\n+static int writer_flush_nonempty_block(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tstruct reftable_block_stats *bstats =\n+\t\twriter_reftable_block_stats(w, typ);\n+\tuint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;\n+\tint raw_bytes = block_writer_finish(w->block_writer);\n+\tint padding = 0;\n+\tint err = 0;\n+\tstruct reftable_index_record ir = { .last_key = STRBUF_INIT };\n+\tif (raw_bytes < 0)\n+\t\treturn raw_bytes;\n+\n+\tif (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {\n+\t\tpadding = w->opts.block_size - raw_bytes;\n+\t}\n+\n+\tif (block_typ_off > 0) {\n+\t\tbstats->offset = block_typ_off;\n+\t}\n+\n+\tbstats->entries += w->block_writer->entries;\n+\tbstats->restarts += w->block_writer->restart_len;\n+\tbstats->blocks++;\n+\tw->stats.blocks++;\n+\n+\tif (debug) {\n+\t\tfprintf(stderr, \"block %c off %\" PRIu64 \" sz %d (%d)\\n\", typ,\n+\t\t\tw->next, raw_bytes,\n+\t\t\tget_be24(w->block + w->block_writer->header_off + 1));\n+\t}\n+\n+\tif (w->next == 0) {\n+\t\twriter_write_header(w, w->block);\n+\t}\n+\n+\terr = padded_write(w, w->block, raw_bytes, padding);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (w->index_cap == w->index_len) {\n+\t\tw->index_cap = 2 * w->index_cap + 1;\n+\t\tw->index = reftable_realloc(\n+\t\t\tw->index,\n+\t\t\tsizeof(struct reftable_index_record) * w->index_cap);\n+\t}\n+\n+\tir.offset = w->next;\n+\tstrbuf_reset(&ir.last_key);\n+\tstrbuf_addbuf(&ir.last_key, &w->block_writer->last_key);\n+\tw->index[w->index_len] = ir;\n+\n+\tw->index_len++;\n+\tw->next += padding + raw_bytes;\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+static int writer_flush_block(struct reftable_writer *w)\n+{\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\tif (w->block_writer->entries == 0)\n+\t\treturn 0;\n+\treturn writer_flush_nonempty_block(w);\n+}\n+\n+const struct reftable_stats *writer_stats(struct reftable_writer *w)\n+{\n+\treturn &w->stats;\n+}\ndiff --git a/reftable/writer.h b/reftable/writer.h\nnew file mode 100644\nindex 00000000000..09b88673d97\n--- /dev/null\n+++ b/reftable/writer.h\n@@ -0,0 +1,50 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef WRITER_H\n+#define WRITER_H\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"tree.h\"\n+#include \"reftable-writer.h\"\n+\n+struct reftable_writer {\n+\tssize_t (*write)(void *, const void *, size_t);\n+\tvoid *write_arg;\n+\tint pending_padding;\n+\tstruct strbuf last_key;\n+\n+\t/* offset of next block to write. */\n+\tuint64_t next;\n+\tuint64_t min_update_index, max_update_index;\n+\tstruct reftable_write_options opts;\n+\n+\t/* memory buffer for writing */\n+\tuint8_t *block;\n+\n+\t/* writer for the current section. NULL or points to\n+\t * block_writer_data */\n+\tstruct block_writer *block_writer;\n+\n+\tstruct block_writer block_writer_data;\n+\n+\t/* pending index records for the current section */\n+\tstruct reftable_index_record *index;\n+\tsize_t index_len;\n+\tsize_t index_cap;\n+\n+\t/*\n+\t * tree for use with tsearch; used to populate the 'o' inverse OID\n+\t * map */\n+\tstruct tree_node *obj_index_tree;\n+\n+\tstruct reftable_stats stats;\n+};\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"435305","messageId":"4e259380fda937e55140fe1feff07bb69061646b.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 11/19] reftable: generic interface to tables","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:36Z","receivedAt":"2021-09-09T18:48:48Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                     |   3 +\n reftable/generic.c           | 169 +++++++++++++++++++++++++++++++++++\n reftable/generic.h           |  32 +++++++\n reftable/reftable-generic.h  |  47 ++++++++++\n reftable/reftable-iterator.h |  39 ++++++++\n reftable/reftable.c          | 115 ++++++++++++++++++++++++\n 6 files changed, 405 insertions(+)\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable.c\n\ndiff --git a/Makefile b/Makefile\nindex de5c4026201..02cb06f82ec 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,6 +2462,9 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/refname.o\n+REFTABLE_OBJS += reftable/generic.o\n+REFTABLE_OBJS += reftable/stack.o\n REFTABLE_OBJS += reftable/tree.o\n REFTABLE_OBJS += reftable/writer.o\n \ndiff --git a/reftable/generic.c b/reftable/generic.c\nnew file mode 100644\nindex 00000000000..7a8a738d860\n--- /dev/null\n+++ b/reftable/generic.c\n@@ -0,0 +1,169 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = ~((uint64_t)0),\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+int reftable_table_print(struct reftable_table *tab) {\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\tuint32_t hash_id = reftable_table_hash_id(tab);\n+\tint err = reftable_table_seek_ref(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_ref_record_print(&ref, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_table_seek_log(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_log_record_print(&log, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_log_record_release(&log);\n+\treturn 0;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\ndiff --git a/reftable/generic.h b/reftable/generic.h\nnew file mode 100644\nindex 00000000000..98886a06402\n--- /dev/null\n+++ b/reftable/generic.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef GENERIC_H\n+#define GENERIC_H\n+\n+#include \"record.h\"\n+#include \"reftable-generic.h\"\n+\n+/* generic interface to reftables */\n+struct reftable_table_vtable {\n+\tint (*seek_record)(void *tab, struct reftable_iterator *it,\n+\t\t\t   struct reftable_record *);\n+\tuint32_t (*hash_id)(void *tab);\n+\tuint64_t (*min_update_index)(void *tab);\n+\tuint64_t (*max_update_index)(void *tab);\n+};\n+\n+struct reftable_iterator_vtable {\n+\tint (*next)(void *iter_arg, struct reftable_record *rec);\n+\tvoid (*close)(void *iter_arg);\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it);\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec);\n+\n+#endif\ndiff --git a/reftable/reftable-generic.h b/reftable/reftable-generic.h\nnew file mode 100644\nindex 00000000000..d239751a778\n--- /dev/null\n+++ b/reftable/reftable-generic.h\n@@ -0,0 +1,47 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_GENERIC_H\n+#define REFTABLE_GENERIC_H\n+\n+#include \"reftable-iterator.h\"\n+\n+struct reftable_table_vtable;\n+\n+/*\n+ * Provides a unified API for reading tables, either merged tables, or single\n+ * readers. */\n+struct reftable_table {\n+\tstruct reftable_table_vtable *ops;\n+\tvoid *table_arg;\n+};\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID from a generic reftable_table */\n+uint32_t reftable_table_hash_id(struct reftable_table *tab);\n+\n+/* returns the max update_index covered by this table. */\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab);\n+\n+/* returns the min update_index covered by this table. */\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0\n+   for success, and 1 if ref not found. */\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* dump table contents onto stdout for debugging */\n+int reftable_table_print(struct reftable_table *tab);\n+\n+#endif\ndiff --git a/reftable/reftable-iterator.h b/reftable/reftable-iterator.h\nnew file mode 100644\nindex 00000000000..d3eee7af357\n--- /dev/null\n+++ b/reftable/reftable-iterator.h\n@@ -0,0 +1,39 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ITERATOR_H\n+#define REFTABLE_ITERATOR_H\n+\n+#include \"reftable-record.h\"\n+\n+struct reftable_iterator_vtable;\n+\n+/* iterator is the generic interface for walking over data stored in a\n+ * reftable.\n+ */\n+struct reftable_iterator {\n+\tstruct reftable_iterator_vtable *ops;\n+\tvoid *iter_arg;\n+};\n+\n+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref);\n+\n+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log);\n+\n+/* releases resources associated with an iterator. */\n+void reftable_iterator_destroy(struct reftable_iterator *it);\n+\n+#endif\ndiff --git a/reftable/reftable.c b/reftable/reftable.c\nnew file mode 100644\nindex 00000000000..0e4607a7cd6\n--- /dev/null\n+++ b/reftable/reftable.c\n@@ -0,0 +1,115 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\n-- \ngitgitgadget\n\n"},{"id":"435306","messageId":"a43d35121640183c8f2669ce56fb46c108f2b565.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 12/19] reftable: read reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:37Z","receivedAt":"2021-09-09T18:48:49Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis supports reading a single reftable file.\n\nThe commit introduces an abstract iterator type, which captures the usecases\nboth of reading individual refs, and iterating over a segment of the ref\nnamespace.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/iter.c            | 194 +++++++++\n reftable/iter.h            |  69 ++++\n reftable/reader.c          | 801 +++++++++++++++++++++++++++++++++++++\n reftable/reader.h          |  66 +++\n reftable/reftable-reader.h | 101 +++++\n 6 files changed, 1233 insertions(+)\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/reftable-reader.h\n\ndiff --git a/Makefile b/Makefile\nindex 02cb06f82ec..c057c23d4a8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2460,7 +2460,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n+REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n REFTABLE_OBJS += reftable/generic.o\ndiff --git a/reftable/iter.c b/reftable/iter.c\nnew file mode 100644\nindex 00000000000..93d04f735b8\n--- /dev/null\n+++ b/reftable/iter.c\n@@ -0,0 +1,194 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"iter.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"generic.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"reftable-error.h\"\n+\n+int iterator_is_null(struct reftable_iterator *it)\n+{\n+\treturn !it->ops;\n+}\n+\n+static void filtering_ref_iterator_close(void *iter_arg)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstrbuf_release(&fri->oid);\n+\treftable_iterator_destroy(&fri->it);\n+}\n+\n+static int filtering_ref_iterator_next(void *iter_arg,\n+\t\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\tint err = 0;\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&fri->it, ref);\n+\t\tif (err != 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (fri->double_check) {\n+\t\t\tstruct reftable_iterator it = { NULL };\n+\n+\t\t\terr = reftable_table_seek_ref(&fri->tab, &it,\n+\t\t\t\t\t\t      ref->refname);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = reftable_iterator_next_ref(&it, ref);\n+\t\t\t}\n+\n+\t\t\treftable_iterator_destroy(&it);\n+\n+\t\t\tif (err < 0) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (err > 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL2 &&\n+\t\t    (!memcmp(fri->oid.buf, ref->value.val2.target_value,\n+\t\t\t     fri->oid.len) ||\n+\t\t     !memcmp(fri->oid.buf, ref->value.val2.value,\n+\t\t\t     fri->oid.len)))\n+\t\t\treturn 0;\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL1 &&\n+\t\t    !memcmp(fri->oid.buf, ref->value.val1, fri->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treftable_ref_record_release(ref);\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable filtering_ref_iterator_vtable = {\n+\t.next = &filtering_ref_iterator_next,\n+\t.close = &filtering_ref_iterator_close,\n+};\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *it,\n+\t\t\t\t\t  struct filtering_ref_iterator *fri)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = fri;\n+\tit->ops = &filtering_ref_iterator_vtable;\n+}\n+\n+static void indexed_table_ref_iter_close(void *p)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tblock_iter_close(&it->cur);\n+\treftable_block_done(&it->block_reader.block);\n+\treftable_free(it->offsets);\n+\tstrbuf_release(&it->oid);\n+}\n+\n+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)\n+{\n+\tuint64_t off;\n+\tint err = 0;\n+\tif (it->offset_idx == it->offset_len) {\n+\t\tit->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\n+\treftable_block_done(&it->block_reader.block);\n+\n+\toff = it->offsets[it->offset_idx++];\n+\terr = reader_init_block_reader(it->r, &it->block_reader, off,\n+\t\t\t\t       BLOCK_TYPE_REF);\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\tif (err > 0) {\n+\t\t/* indexed block does not exist. */\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\t}\n+\tblock_reader_start(&it->block_reader, &it->cur);\n+\treturn 0;\n+}\n+\n+static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\n+\twhile (1) {\n+\t\tint err = block_iter_next(&it->cur, rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\terr = indexed_table_ref_iter_next_block(it);\n+\t\t\tif (err < 0) {\n+\t\t\t\treturn err;\n+\t\t\t}\n+\n+\t\t\tif (it->is_finished) {\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\t/* BUG */\n+\t\tif (!memcmp(it->oid.buf, ref->value.val2.target_value,\n+\t\t\t    it->oid.len) ||\n+\t\t    !memcmp(it->oid.buf, ref->value.val2.value, it->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+}\n+\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len)\n+{\n+\tstruct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT;\n+\tstruct indexed_table_ref_iter *itr =\n+\t\treftable_calloc(sizeof(struct indexed_table_ref_iter));\n+\tint err = 0;\n+\n+\t*itr = empty;\n+\titr->r = r;\n+\tstrbuf_add(&itr->oid, oid, oid_len);\n+\n+\titr->offsets = offsets;\n+\titr->offset_len = offset_len;\n+\n+\terr = indexed_table_ref_iter_next_block(itr);\n+\tif (err < 0) {\n+\t\treftable_free(itr);\n+\t} else {\n+\t\t*dest = itr;\n+\t}\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {\n+\t.next = &indexed_table_ref_iter_next,\n+\t.close = &indexed_table_ref_iter_close,\n+};\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = itr;\n+\tit->ops = &indexed_table_ref_iter_vtable;\n+}\ndiff --git a/reftable/iter.h b/reftable/iter.h\nnew file mode 100644\nindex 00000000000..09eb0cbfa59\n--- /dev/null\n+++ b/reftable/iter.h\n@@ -0,0 +1,69 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef ITER_H\n+#define ITER_H\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"record.h\"\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+/* Returns true for a zeroed out iterator, such as the one returned from\n+ * iterator_destroy. */\n+int iterator_is_null(struct reftable_iterator *it);\n+\n+/* iterator that produces only ref records that point to `oid` */\n+struct filtering_ref_iterator {\n+\tint double_check;\n+\tstruct reftable_table tab;\n+\tstruct strbuf oid;\n+\tstruct reftable_iterator it;\n+};\n+#define FILTERING_REF_ITERATOR_INIT \\\n+\t{                           \\\n+\t\t.oid = STRBUF_INIT  \\\n+\t}\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *,\n+\t\t\t\t\t  struct filtering_ref_iterator *);\n+\n+/* iterator that produces only ref records that point to `oid`,\n+ * but using the object index.\n+ */\n+struct indexed_table_ref_iter {\n+\tstruct reftable_reader *r;\n+\tstruct strbuf oid;\n+\n+\t/* mutable */\n+\tuint64_t *offsets;\n+\n+\t/* Points to the next offset to read. */\n+\tint offset_idx;\n+\tint offset_len;\n+\tstruct block_reader block_reader;\n+\tstruct block_iter cur;\n+\tint is_finished;\n+};\n+\n+#define INDEXED_TABLE_REF_ITER_INIT                                     \\\n+\t{                                                               \\\n+\t\t.cur = { .last_key = STRBUF_INIT }, .oid = STRBUF_INIT, \\\n+\t}\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr);\n+\n+/* Takes ownership of `offsets` */\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len);\n+\n+#endif\ndiff --git a/reftable/reader.c b/reftable/reader.c\nnew file mode 100644\nindex 00000000000..ab7a981eb8e\n--- /dev/null\n+++ b/reftable/reader.c\n@@ -0,0 +1,801 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reader.h\"\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"generic.h\"\n+#include \"iter.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-generic.h\"\n+#include \"tree.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source)\n+{\n+\treturn source->ops->size(source->arg);\n+}\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size)\n+{\n+\tint result = source->ops->read_block(source->arg, dest, off, size);\n+\tdest->source = *source;\n+\treturn result;\n+}\n+\n+void block_source_close(struct reftable_block_source *source)\n+{\n+\tif (!source->ops) {\n+\t\treturn;\n+\t}\n+\n+\tsource->ops->close(source->arg);\n+\tsource->ops = NULL;\n+}\n+\n+static struct reftable_reader_offsets *\n+reader_offsets_for(struct reftable_reader *r, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\t\treturn &r->ref_offsets;\n+\tcase BLOCK_TYPE_LOG:\n+\t\treturn &r->log_offsets;\n+\tcase BLOCK_TYPE_OBJ:\n+\t\treturn &r->obj_offsets;\n+\t}\n+\tabort();\n+}\n+\n+static int reader_get_block(struct reftable_reader *r,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t sz)\n+{\n+\tif (off >= r->size)\n+\t\treturn 0;\n+\n+\tif (off + sz > r->size) {\n+\t\tsz = r->size - off;\n+\t}\n+\n+\treturn block_source_read_block(&r->source, dest, off, sz);\n+}\n+\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r)\n+{\n+\treturn r->hash_id;\n+}\n+\n+const char *reader_name(struct reftable_reader *r)\n+{\n+\treturn r->name;\n+}\n+\n+static int parse_footer(struct reftable_reader *r, uint8_t *footer,\n+\t\t\tuint8_t *header)\n+{\n+\tuint8_t *f = footer;\n+\tuint8_t first_block_typ;\n+\tint err = 0;\n+\tuint32_t computed_crc;\n+\tuint32_t file_crc;\n+\n+\tif (memcmp(f, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tf += 4;\n+\n+\tif (memcmp(footer, header, header_size(r->version))) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tf++;\n+\tr->block_size = get_be24(f);\n+\n+\tf += 3;\n+\tr->min_update_index = get_be64(f);\n+\tf += 8;\n+\tr->max_update_index = get_be64(f);\n+\tf += 8;\n+\n+\tif (r->version == 1) {\n+\t\tr->hash_id = GIT_SHA1_FORMAT_ID;\n+\t} else {\n+\t\tr->hash_id = get_be32(f);\n+\t\tswitch (r->hash_id) {\n+\t\tcase GIT_SHA1_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tcase GIT_SHA256_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tf += 4;\n+\t}\n+\n+\tr->ref_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->obj_offsets.offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);\n+\tr->obj_offsets.offset >>= 5;\n+\n+\tr->obj_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tcomputed_crc = crc32(0, footer, f - footer);\n+\tfile_crc = get_be32(f);\n+\tf += 4;\n+\tif (computed_crc != file_crc) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tfirst_block_typ = header[header_size(r->version)];\n+\tr->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF);\n+\tr->ref_offsets.offset = 0;\n+\tr->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG ||\n+\t\t\t\t     r->log_offsets.offset > 0);\n+\tr->obj_offsets.is_present = r->obj_offsets.offset > 0;\n+\terr = 0;\n+done:\n+\treturn err;\n+}\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name)\n+{\n+\tstruct reftable_block footer = { NULL };\n+\tstruct reftable_block header = { NULL };\n+\tint err = 0;\n+\tuint64_t file_size = block_source_size(source);\n+\n+\t/* Need +1 to read type of first block. */\n+\tuint32_t read_size = header_size(2) + 1; /* read v2 because it's larger.  */\n+\tmemset(r, 0, sizeof(struct reftable_reader));\n+\n+\tif (read_size > file_size) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = block_source_read_block(source, &header, 0, read_size);\n+\tif (err != read_size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tif (memcmp(header.data, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tr->version = header.data[4];\n+\tif (r->version != 1 && r->version != 2) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tr->size = file_size - footer_size(r->version);\n+\tr->source = *source;\n+\tr->name = xstrdup(name);\n+\tr->hash_id = 0;\n+\n+\terr = block_source_read_block(source, &footer, r->size,\n+\t\t\t\t      footer_size(r->version));\n+\tif (err != footer_size(r->version)) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = parse_footer(r, footer.data, header.data);\n+done:\n+\treftable_block_done(&footer);\n+\treftable_block_done(&header);\n+\treturn err;\n+}\n+\n+struct table_iter {\n+\tstruct reftable_reader *r;\n+\tuint8_t typ;\n+\tuint64_t block_off;\n+\tstruct block_iter bi;\n+\tint is_finished;\n+};\n+#define TABLE_ITER_INIT                          \\\n+\t{                                        \\\n+\t\t.bi = {.last_key = STRBUF_INIT } \\\n+\t}\n+\n+static void table_iter_copy_from(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = src->block_off;\n+\tdest->is_finished = src->is_finished;\n+\tblock_iter_copy_from(&dest->bi, &src->bi);\n+}\n+\n+static int table_iter_next_in_block(struct table_iter *ti,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tint res = block_iter_next(&ti->bi, rec);\n+\tif (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {\n+\t\t((struct reftable_ref_record *)rec->data)->update_index +=\n+\t\t\tti->r->min_update_index;\n+\t}\n+\n+\treturn res;\n+}\n+\n+static void table_iter_block_done(struct table_iter *ti)\n+{\n+\tif (!ti->bi.br) {\n+\t\treturn;\n+\t}\n+\treftable_block_done(&ti->bi.br->block);\n+\tFREE_AND_NULL(ti->bi.br);\n+\n+\tti->bi.last_key.len = 0;\n+\tti->bi.next_off = 0;\n+}\n+\n+static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off,\n+\t\t\t\t  int version)\n+{\n+\tint32_t result = 0;\n+\n+\tif (off == 0) {\n+\t\tdata += header_size(version);\n+\t}\n+\n+\t*typ = data[0];\n+\tif (reftable_is_block_type(*typ)) {\n+\t\tresult = get_be24(data + 1);\n+\t}\n+\treturn result;\n+}\n+\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ)\n+{\n+\tint32_t guess_block_size = r->block_size ? r->block_size :\n+\t\t\t\t\t\t\t DEFAULT_BLOCK_SIZE;\n+\tstruct reftable_block block = { NULL };\n+\tuint8_t block_typ = 0;\n+\tint err = 0;\n+\tuint32_t header_off = next_off ? 0 : header_size(r->version);\n+\tint32_t block_size = 0;\n+\n+\tif (next_off >= r->size)\n+\t\treturn 1;\n+\n+\terr = reader_get_block(r, &block, next_off, guess_block_size);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tblock_size = extract_block_size(block.data, &block_typ, next_off,\n+\t\t\t\t\tr->version);\n+\tif (block_size < 0)\n+\t\treturn block_size;\n+\n+\tif (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {\n+\t\treftable_block_done(&block);\n+\t\treturn 1;\n+\t}\n+\n+\tif (block_size > guess_block_size) {\n+\t\treftable_block_done(&block);\n+\t\terr = reader_get_block(r, &block, next_off, block_size);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn block_reader_init(br, &block, header_off, r->block_size,\n+\t\t\t\t hash_size(r->hash_id));\n+}\n+\n+static int table_iter_next_block(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tuint64_t next_block_off = src->block_off + src->bi.br->full_block_size;\n+\tstruct block_reader br = { 0 };\n+\tint err = 0;\n+\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = next_block_off;\n+\n+\terr = reader_init_block_reader(src->r, &br, next_block_off, src->typ);\n+\tif (err > 0) {\n+\t\tdest->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\tif (err != 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct block_reader *brp =\n+\t\t\treftable_malloc(sizeof(struct block_reader));\n+\t\t*brp = br;\n+\n+\t\tdest->is_finished = 0;\n+\t\tblock_reader_start(brp, &dest->bi);\n+\t}\n+\treturn 0;\n+}\n+\n+static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)\n+{\n+\tif (reftable_record_type(rec) != ti->typ)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\twhile (1) {\n+\t\tstruct table_iter next = TABLE_ITER_INIT;\n+\t\tint err = 0;\n+\t\tif (ti->is_finished) {\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\terr = table_iter_next_in_block(ti, rec);\n+\t\tif (err <= 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err != 0) {\n+\t\t\tti->is_finished = 1;\n+\t\t}\n+\t\ttable_iter_block_done(ti);\n+\t\tif (err != 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\ttable_iter_copy_from(ti, &next);\n+\t\tblock_iter_close(&next.bi);\n+\t}\n+}\n+\n+static int table_iter_next_void(void *ti, struct reftable_record *rec)\n+{\n+\treturn table_iter_next(ti, rec);\n+}\n+\n+static void table_iter_close(void *p)\n+{\n+\tstruct table_iter *ti = p;\n+\ttable_iter_block_done(ti);\n+\tblock_iter_close(&ti->bi);\n+}\n+\n+static struct reftable_iterator_vtable table_iter_vtable = {\n+\t.next = &table_iter_next_void,\n+\t.close = &table_iter_close,\n+};\n+\n+static void iterator_from_table_iter(struct reftable_iterator *it,\n+\t\t\t\t     struct table_iter *ti)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = ti;\n+\tit->ops = &table_iter_vtable;\n+}\n+\n+static int reader_table_iter_at(struct reftable_reader *r,\n+\t\t\t\tstruct table_iter *ti, uint64_t off,\n+\t\t\t\tuint8_t typ)\n+{\n+\tstruct block_reader br = { 0 };\n+\tstruct block_reader *brp = NULL;\n+\n+\tint err = reader_init_block_reader(r, &br, off, typ);\n+\tif (err != 0)\n+\t\treturn err;\n+\n+\tbrp = reftable_malloc(sizeof(struct block_reader));\n+\t*brp = br;\n+\tti->r = r;\n+\tti->typ = block_reader_type(brp);\n+\tti->block_off = off;\n+\tblock_reader_start(brp, &ti->bi);\n+\treturn 0;\n+}\n+\n+static int reader_start(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\tuint8_t typ, int index)\n+{\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tuint64_t off = offs->offset;\n+\tif (index) {\n+\t\toff = offs->index_offset;\n+\t\tif (off == 0) {\n+\t\t\treturn 1;\n+\t\t}\n+\t\ttyp = BLOCK_TYPE_INDEX;\n+\t}\n+\n+\treturn reader_table_iter_at(r, ti, off, typ);\n+}\n+\n+static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\t      struct reftable_record *want)\n+{\n+\tstruct reftable_record rec =\n+\t\treftable_new_record(reftable_record_type(want));\n+\tstruct strbuf want_key = STRBUF_INIT;\n+\tstruct strbuf got_key = STRBUF_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = -1;\n+\n+\treftable_record_key(want, &want_key);\n+\n+\twhile (1) {\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = block_reader_first_key(next.bi.br, &got_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (strbuf_cmp(&got_key, &want_key) > 0) {\n+\t\t\ttable_iter_block_done(&next);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_block_done(ti);\n+\t\ttable_iter_copy_from(ti, &next);\n+\t}\n+\n+\terr = block_iter_seek(&ti->bi, &want_key);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = 0;\n+\n+done:\n+\tblock_iter_close(&next.bi);\n+\treftable_record_destroy(&rec);\n+\tstrbuf_release(&want_key);\n+\tstrbuf_release(&got_key);\n+\treturn err;\n+}\n+\n+static int reader_seek_indexed(struct reftable_reader *r,\n+\t\t\t       struct reftable_iterator *it,\n+\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct reftable_index_record want_index = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record want_index_rec = { NULL };\n+\tstruct reftable_index_record index_result = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record index_result_rec = { NULL };\n+\tstruct table_iter index_iter = TABLE_ITER_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = 0;\n+\n+\treftable_record_key(rec, &want_index.last_key);\n+\treftable_record_from_index(&want_index_rec, &want_index);\n+\treftable_record_from_index(&index_result_rec, &index_result);\n+\n+\terr = reader_start(r, &index_iter, reftable_record_type(rec), 1);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reader_seek_linear(r, &index_iter, &want_index_rec);\n+\twhile (1) {\n+\t\terr = table_iter_next(&index_iter, &index_result_rec);\n+\t\ttable_iter_block_done(&index_iter);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = reader_table_iter_at(r, &next, index_result.offset, 0);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = block_iter_seek(&next.bi, &want_index.last_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (next.typ == reftable_record_type(rec)) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (next.typ != BLOCK_TYPE_INDEX) {\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_copy_from(&index_iter, &next);\n+\t}\n+\n+\tif (err == 0) {\n+\t\tstruct table_iter empty = TABLE_ITER_INIT;\n+\t\tstruct table_iter *malloced =\n+\t\t\treftable_calloc(sizeof(struct table_iter));\n+\t\t*malloced = empty;\n+\t\ttable_iter_copy_from(malloced, &next);\n+\t\titerator_from_table_iter(it, malloced);\n+\t}\n+done:\n+\tblock_iter_close(&next.bi);\n+\ttable_iter_close(&index_iter);\n+\treftable_record_release(&want_index_rec);\n+\treftable_record_release(&index_result_rec);\n+\treturn err;\n+}\n+\n+static int reader_seek_internal(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it,\n+\t\t\t\tstruct reftable_record *rec)\n+{\n+\tstruct reftable_reader_offsets *offs =\n+\t\treader_offsets_for(r, reftable_record_type(rec));\n+\tuint64_t idx = offs->index_offset;\n+\tstruct table_iter ti = TABLE_ITER_INIT;\n+\tint err = 0;\n+\tif (idx > 0)\n+\t\treturn reader_seek_indexed(r, it, rec);\n+\n+\terr = reader_start(r, &ti, reftable_record_type(rec), 0);\n+\tif (err < 0)\n+\t\treturn err;\n+\terr = reader_seek_linear(r, &ti, rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct table_iter *p =\n+\t\t\treftable_malloc(sizeof(struct table_iter));\n+\t\t*p = ti;\n+\t\titerator_from_table_iter(it, p);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\tstruct reftable_record *rec)\n+{\n+\tuint8_t typ = reftable_record_type(rec);\n+\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tif (!offs->is_present) {\n+\t\titerator_set_empty(it);\n+\t\treturn 0;\n+\t}\n+\n+\treturn reader_seek_internal(r, it, rec);\n+}\n+\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_reader_seek_log_at(r, it, name, max);\n+}\n+\n+void reader_close(struct reftable_reader *r)\n+{\n+\tblock_source_close(&r->source);\n+\tFREE_AND_NULL(r->name);\n+}\n+\n+int reftable_new_reader(struct reftable_reader **p,\n+\t\t\tstruct reftable_block_source *src, char const *name)\n+{\n+\tstruct reftable_reader *rd =\n+\t\treftable_calloc(sizeof(struct reftable_reader));\n+\tint err = init_reader(rd, src, name);\n+\tif (err == 0) {\n+\t\t*p = rd;\n+\t} else {\n+\t\tblock_source_close(src);\n+\t\treftable_free(rd);\n+\t}\n+\treturn err;\n+}\n+\n+void reftable_reader_free(struct reftable_reader *r)\n+{\n+\treader_close(r);\n+\treftable_free(r);\n+}\n+\n+static int reftable_reader_refs_for_indexed(struct reftable_reader *r,\n+\t\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t\t    uint8_t *oid)\n+{\n+\tstruct reftable_obj_record want = {\n+\t\t.hash_prefix = oid,\n+\t\t.hash_prefix_len = r->object_id_len,\n+\t};\n+\tstruct reftable_record want_rec = { NULL };\n+\tstruct reftable_iterator oit = { NULL };\n+\tstruct reftable_obj_record got = { NULL };\n+\tstruct reftable_record got_rec = { NULL };\n+\tint err = 0;\n+\tstruct indexed_table_ref_iter *itr = NULL;\n+\n+\t/* Look through the reverse index. */\n+\treftable_record_from_obj(&want_rec, &want);\n+\terr = reader_seek(r, &oit, &want_rec);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\t/* read out the reftable_obj_record */\n+\treftable_record_from_obj(&got_rec, &got);\n+\terr = iterator_next(&oit, &got_rec);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 0 ||\n+\t    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {\n+\t\t/* didn't find it; return empty iterator */\n+\t\titerator_set_empty(it);\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),\n+\t\t\t\t\t got.offsets, got.offset_len);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tgot.offsets = NULL;\n+\titerator_from_indexed_table_ref_iter(it, itr);\n+\n+done:\n+\treftable_iterator_destroy(&oit);\n+\treftable_record_release(&got_rec);\n+\treturn err;\n+}\n+\n+static int reftable_reader_refs_for_unindexed(struct reftable_reader *r,\n+\t\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t\t      uint8_t *oid)\n+{\n+\tstruct table_iter ti_empty = TABLE_ITER_INIT;\n+\tstruct table_iter *ti = reftable_calloc(sizeof(struct table_iter));\n+\tstruct filtering_ref_iterator *filter = NULL;\n+\tstruct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT;\n+\tint oid_len = hash_size(r->hash_id);\n+\tint err;\n+\n+\t*ti = ti_empty;\n+\terr = reader_start(r, ti, BLOCK_TYPE_REF, 0);\n+\tif (err < 0) {\n+\t\treftable_free(ti);\n+\t\treturn err;\n+\t}\n+\n+\tfilter = reftable_malloc(sizeof(struct filtering_ref_iterator));\n+\t*filter = empty;\n+\n+\tstrbuf_add(&filter->oid, oid, oid_len);\n+\treftable_table_from_reader(&filter->tab, r);\n+\tfilter->double_check = 0;\n+\titerator_from_table_iter(&filter->it, ti);\n+\n+\titerator_from_filtering_ref_iterator(it, filter);\n+\treturn 0;\n+}\n+\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid)\n+{\n+\tif (r->obj_offsets.is_present)\n+\t\treturn reftable_reader_refs_for_indexed(r, it, oid);\n+\treturn reftable_reader_refs_for_unindexed(r, it, oid);\n+}\n+\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r)\n+{\n+\treturn r->max_update_index;\n+}\n+\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r)\n+{\n+\treturn r->min_update_index;\n+}\n+\n+/* generic table interface. */\n+\n+static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,\n+\t\t\t\t     struct reftable_record *rec)\n+{\n+\treturn reader_seek(tab, it, rec);\n+}\n+\n+static uint32_t reftable_reader_hash_id_void(void *tab)\n+{\n+\treturn reftable_reader_hash_id(tab);\n+}\n+\n+static uint64_t reftable_reader_min_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_reader_max_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable reader_vtable = {\n+\t.seek_record = reftable_reader_seek_void,\n+\t.hash_id = reftable_reader_hash_id_void,\n+\t.min_update_index = reftable_reader_min_update_index_void,\n+\t.max_update_index = reftable_reader_max_update_index_void,\n+};\n+\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &reader_vtable;\n+\ttab->table_arg = reader;\n+}\n+\n+\n+int reftable_reader_print_file(const char *tablename)\n+{\n+\tstruct reftable_block_source src = { NULL };\n+\tint err = reftable_block_source_from_file(&src, tablename);\n+\tstruct reftable_reader *r = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&r, &src, tablename);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\treftable_table_from_reader(&tab, r);\n+\terr = reftable_table_print(&tab);\n+done:\n+\treftable_reader_free(r);\n+\treturn err;\n+}\ndiff --git a/reftable/reader.h b/reftable/reader.h\nnew file mode 100644\nindex 00000000000..39583e5dbcd\n--- /dev/null\n+++ b/reftable/reader.h\n@@ -0,0 +1,66 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef READER_H\n+#define READER_H\n+\n+#include \"block.h\"\n+#include \"record.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source);\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size);\n+void block_source_close(struct reftable_block_source *source);\n+\n+/* metadata for a block type */\n+struct reftable_reader_offsets {\n+\tint is_present;\n+\tuint64_t offset;\n+\tuint64_t index_offset;\n+};\n+\n+/* The state for reading a reftable file. */\n+struct reftable_reader {\n+\t/* for convience, associate a name with the instance. */\n+\tchar *name;\n+\tstruct reftable_block_source source;\n+\n+\t/* Size of the file, excluding the footer. */\n+\tuint64_t size;\n+\n+\t/* 'sha1' for SHA1, 's256' for SHA-256 */\n+\tuint32_t hash_id;\n+\n+\tuint32_t block_size;\n+\tuint64_t min_update_index;\n+\tuint64_t max_update_index;\n+\t/* Length of the OID keys in the 'o' section */\n+\tint object_id_len;\n+\tint version;\n+\n+\tstruct reftable_reader_offsets ref_offsets;\n+\tstruct reftable_reader_offsets obj_offsets;\n+\tstruct reftable_reader_offsets log_offsets;\n+};\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name);\n+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\tstruct reftable_record *rec);\n+void reader_close(struct reftable_reader *r);\n+const char *reader_name(struct reftable_reader *r);\n+\n+/* initialize a block reader to read from `r` */\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ);\n+\n+#endif\ndiff --git a/reftable/reftable-reader.h b/reftable/reftable-reader.h\nnew file mode 100644\nindex 00000000000..4a4bc2fdf85\n--- /dev/null\n+++ b/reftable/reftable-reader.h\n@@ -0,0 +1,101 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_READER_H\n+#define REFTABLE_READER_H\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Reading single tables\n+ *\n+ * The follow routines are for reading single files. For an\n+ * application-level interface, skip ahead to struct\n+ * reftable_merged_table and struct reftable_stack.\n+ */\n+\n+/* The reader struct is a handle to an open reftable file. */\n+struct reftable_reader;\n+\n+/* Generic table. */\n+struct reftable_table;\n+\n+/* reftable_new_reader opens a reftable for reading. If successful,\n+ * returns 0 code and sets pp. The name is used for creating a\n+ * stack. Typically, it is the basename of the file. The block source\n+ * `src` is owned by the reader, and is closed on calling\n+ * reftable_reader_destroy(). On error, the block source `src` is\n+ * closed as well.\n+ */\n+int reftable_new_reader(struct reftable_reader **pp,\n+\t\t\tstruct reftable_block_source *src, const char *name);\n+\n+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted\n+   in the table.  To seek to the start of the table, use name = \"\".\n+\n+   example:\n+\n+   struct reftable_reader *r = NULL;\n+   int err = reftable_new_reader(&r, &src, \"filename\");\n+   if (err < 0) { ... }\n+   struct reftable_iterator it  = {0};\n+   err = reftable_reader_seek_ref(r, &it, \"refs/heads/master\");\n+   if (err < 0) { ... }\n+   struct reftable_ref_record ref  = {0};\n+   while (1) {\n+   err = reftable_iterator_next_ref(&it, &ref);\n+   if (err > 0) {\n+   break;\n+   }\n+   if (err < 0) {\n+   ..error handling..\n+   }\n+   ..found..\n+   }\n+   reftable_iterator_destroy(&it);\n+   reftable_ref_record_release(&ref);\n+*/\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID used in this table. */\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r);\n+\n+/* seek to logs for the given name, older than update_index. To seek to the\n+   start of the table, use name = \"\".\n+*/\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index);\n+\n+/* seek to newest log entry for given name. */\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* closes and deallocates a reader. */\n+void reftable_reader_free(struct reftable_reader *);\n+\n+/* return an iterator for the refs pointing to `oid`. */\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid);\n+\n+/* return the max_update_index for a table */\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);\n+\n+/* return the min_update_index for a table */\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);\n+\n+/* creates a generic table from a file reader. */\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader);\n+\n+/* print table onto stdout for debugging. */\n+int reftable_reader_print_file(const char *tablename);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"435307","messageId":"5a5c108d601ef6904f74597889deb09f8009c695.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 14/19] reftable: add a heap-based priority queue for reftable records","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:39Z","receivedAt":"2021-09-09T18:48:56Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis is needed to create a merged view multiple reftables\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   2 +\n reftable/pq.c             | 105 ++++++++++++++++++++++++++++++++++++++\n reftable/pq.h             |  33 ++++++++++++\n reftable/pq_test.c        |  82 +++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   1 +\n t/helper/test-reftable.c  |   1 +\n 6 files changed, 224 insertions(+)\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 1c0bb449027..d2c9b0097a7 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,6 +2462,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n@@ -2472,6 +2473,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/pq.c b/reftable/pq.c\nnew file mode 100644\nindex 00000000000..efc474017a2\n--- /dev/null\n+++ b/reftable/pq.c\n@@ -0,0 +1,105 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"pq.h\"\n+\n+#include \"reftable-record.h\"\n+#include \"system.h\"\n+#include \"basics.h\"\n+\n+int pq_less(struct pq_entry *a, struct pq_entry *b)\n+{\n+\tstruct strbuf ak = STRBUF_INIT;\n+\tstruct strbuf bk = STRBUF_INIT;\n+\tint cmp = 0;\n+\treftable_record_key(&a->rec, &ak);\n+\treftable_record_key(&b->rec, &bk);\n+\n+\tcmp = strbuf_cmp(&ak, &bk);\n+\n+\tstrbuf_release(&ak);\n+\tstrbuf_release(&bk);\n+\n+\tif (cmp == 0)\n+\t\treturn a->index > b->index;\n+\n+\treturn cmp < 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.heap[0];\n+}\n+\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.len == 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tstruct pq_entry e = pq->heap[0];\n+\tpq->heap[0] = pq->heap[pq->len - 1];\n+\tpq->len--;\n+\n+\ti = 0;\n+\twhile (i < pq->len) {\n+\t\tint min = i;\n+\t\tint j = 2 * i + 1;\n+\t\tint k = 2 * i + 2;\n+\t\tif (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) {\n+\t\t\tmin = j;\n+\t\t}\n+\t\tif (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) {\n+\t\t\tmin = k;\n+\t\t}\n+\n+\t\tif (min == i) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[i], pq->heap[min]);\n+\t\ti = min;\n+\t}\n+\n+\treturn e;\n+}\n+\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)\n+{\n+\tint i = 0;\n+\tif (pq->len == pq->cap) {\n+\t\tpq->cap = 2 * pq->cap + 1;\n+\t\tpq->heap = reftable_realloc(pq->heap,\n+\t\t\t\t\t    pq->cap * sizeof(struct pq_entry));\n+\t}\n+\n+\tpq->heap[pq->len++] = e;\n+\ti = pq->len - 1;\n+\twhile (i > 0) {\n+\t\tint j = (i - 1) / 2;\n+\t\tif (pq_less(&pq->heap[j], &pq->heap[i])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[j], pq->heap[i]);\n+\n+\t\ti = j;\n+\t}\n+}\n+\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < pq->len; i++) {\n+\t\treftable_record_destroy(&pq->heap[i].rec);\n+\t}\n+\tFREE_AND_NULL(pq->heap);\n+\tpq->len = pq->cap = 0;\n+}\ndiff --git a/reftable/pq.h b/reftable/pq.h\nnew file mode 100644\nindex 00000000000..56fc1b6d873\n--- /dev/null\n+++ b/reftable/pq.h\n@@ -0,0 +1,33 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef PQ_H\n+#define PQ_H\n+\n+#include \"record.h\"\n+\n+struct pq_entry {\n+\tint index;\n+\tstruct reftable_record rec;\n+};\n+\n+struct merged_iter_pqueue {\n+\tstruct pq_entry *heap;\n+\tsize_t len;\n+\tsize_t cap;\n+};\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);\n+int pq_less(struct pq_entry *a, struct pq_entry *b);\n+\n+#endif\ndiff --git a/reftable/pq_test.c b/reftable/pq_test.c\nnew file mode 100644\nindex 00000000000..aaa86d12247\n--- /dev/null\n+++ b/reftable/pq_test.c\n@@ -0,0 +1,82 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"pq.h\"\n+#include \"record.h\"\n+#include \"reftable-tests.h\"\n+#include \"test_framework.h\"\n+\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n+{\n+\tint i = 0;\n+\tfor (i = 1; i < pq.len; i++) {\n+\t\tint parent = (i - 1) / 2;\n+\n+\t\tassert(pq_less(&pq.heap[parent], &pq.heap[i]));\n+\t}\n+}\n+\n+static void test_pq(void)\n+{\n+\tchar *names[54] = { NULL };\n+\tint N = ARRAY_SIZE(names) - 1;\n+\n+\tstruct merged_iter_pqueue pq = { NULL };\n+\tconst char *last = NULL;\n+\n+\tint i = 0;\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tsnprintf(name, sizeof(name), \"%02d\", i);\n+\t\tnames[i] = xstrdup(name);\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct reftable_record rec =\n+\t\t\treftable_new_record(BLOCK_TYPE_REF);\n+\t\tstruct pq_entry e = { 0 };\n+\n+\t\treftable_record_as_ref(&rec)->refname = names[i];\n+\t\te.rec = rec;\n+\t\tmerged_iter_pqueue_add(&pq, e);\n+\t\tmerged_iter_pqueue_check(pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\twhile (!merged_iter_pqueue_is_empty(pq)) {\n+\t\tstruct pq_entry e = merged_iter_pqueue_remove(&pq);\n+\t\tstruct reftable_ref_record *ref =\n+\t\t\treftable_record_as_ref(&e.rec);\n+\n+\t\tmerged_iter_pqueue_check(pq);\n+\n+\t\tif (last) {\n+\t\t\tEXPECT(strcmp(last, ref->refname) < 0);\n+\t\t}\n+\t\tlast = ref->refname;\n+\t\tref->refname = NULL;\n+\t\treftable_free(ref);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\n+\tmerged_iter_pqueue_release(&pq);\n+}\n+\n+int pq_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_pq);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 3d541fa5c0c..0019cbcfa49 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -12,6 +12,7 @@ https://developers.google.com/open-source/licenses/bsd\n int basics_test_main(int argc, const char **argv);\n int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n+int pq_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n int readwrite_test_main(int argc, const char **argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 898aba836fd..0b5a1701df1 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"435308","messageId":"f0e159cd8537e150c733a284e66512ad92231574.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 13/19] reftable: reftable file level tests","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:38Z","receivedAt":"2021-09-09T18:49:11Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nWith support for reading and writing files in place, we can construct files (in\nmemory) and attempt to read them back.\n\nBecause some sections of the format are optional (eg. indices, log entries), we\nhave to exercise this code using multiple sizes of input data\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   1 +\n reftable/readwrite_test.c | 652 ++++++++++++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   2 +-\n t/helper/test-reftable.c  |   1 +\n 4 files changed, 655 insertions(+), 1 deletion(-)\n create mode 100644 reftable/readwrite_test.c\n\ndiff --git a/Makefile b/Makefile\nindex c057c23d4a8..1c0bb449027 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2473,6 +2473,7 @@ REFTABLE_OBJS += reftable/writer.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n+REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nnew file mode 100644\nindex 00000000000..5f6bcc2f775\n--- /dev/null\n+++ b/reftable/readwrite_test.c\n@@ -0,0 +1,652 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+\n+static const int update_index = 5;\n+\n+static void test_buffer(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_block out = { NULL };\n+\tint n;\n+\tuint8_t in[] = \"hello\";\n+\tstrbuf_add(&buf, in, sizeof(in));\n+\tblock_source_from_strbuf(&source, &buf);\n+\tEXPECT(block_source_size(&source) == 6);\n+\tn = block_source_read_block(&source, &out, 0, sizeof(in));\n+\tEXPECT(n == sizeof(in));\n+\tEXPECT(!memcmp(in, out.data, n));\n+\treftable_block_done(&out);\n+\n+\tn = block_source_read_block(&source, &out, 1, 2);\n+\tEXPECT(n == 2);\n+\tEXPECT(!memcmp(out.data, \"el\", 2));\n+\n+\treftable_block_done(&out);\n+\tblock_source_close(&source);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void write_table(char ***names, struct strbuf *buf, int N,\n+\t\t\tint block_size, uint32_t hash_id)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = block_size,\n+\t\t.hash_id = hash_id,\n+\t};\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, buf, &opts);\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0, n;\n+\tstruct reftable_log_record log = { NULL };\n+\tconst struct reftable_stats *stats = NULL;\n+\t*names = reftable_calloc(sizeof(char *) * (N + 1));\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tref.refname = name;\n+\t\tref.update_index = update_index;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\t\t(*names)[i] = xstrdup(name);\n+\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tlog.refname = name;\n+\t\tlog.update_index = update_index;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.new_hash = hash;\n+\t\tlog.value.update.message = \"message\";\n+\n+\t\tn = reftable_writer_add_log(w, &log);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tfor (i = 0; i < stats->ref_stats.blocks; i++) {\n+\t\tint off = i * opts.block_size;\n+\t\tif (off == 0) {\n+\t\t\toff = header_size(\n+\t\t\t\t(hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1);\n+\t\t}\n+\t\tEXPECT(buf->buf[off] == 'r');\n+\t}\n+\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+}\n+\n+static void test_log_buffer_size(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 4096,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record\n+\t\tlog = { .refname = \"refs/heads/master\",\n+\t\t\t.update_index = 0xa,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = { .update = {\n+\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .tz_offset = 100,\n+\t\t\t\t\t   .time = 0x5e430672,\n+\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t   } } };\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\t/* This tests buffer extension for log compression. Must use a random\n+\t   hash, to ensure that the compressed part is larger than the original.\n+\t*/\n+\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\tfor (i = 0; i < GIT_SHA1_RAWSZ; i++) {\n+\t\thash1[i] = (uint8_t)(rand() % 256);\n+\t\thash2[i] = (uint8_t)(rand() % 256);\n+\t}\n+\tlog.value.update.old_hash = hash1;\n+\tlog.value.update.new_hash = hash2;\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\terr = reftable_writer_add_log(w, &log);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_log_write_read(void)\n+{\n+\tint N = 2;\n+\tchar **names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint err;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tstruct reftable_log_record log = { NULL };\n+\tint n;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tconst struct reftable_stats *stats = NULL;\n+\treftable_writer_set_limits(w, 0, N);\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[256];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tsnprintf(name, sizeof(name), \"b%02d%0*d\", i, 130, 7);\n+\t\tnames[i] = xstrdup(name);\n+\t\tref.refname = name;\n+\t\tref.update_index = i;\n+\n+\t\terr = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_log_record log = { NULL };\n+\t\tset_test_hash(hash1, i);\n+\t\tset_test_hash(hash2, i + 1);\n+\n+\t\tlog.refname = names[i];\n+\t\tlog.update_index = i;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.old_hash = hash1;\n+\t\tlog.value.update.new_hash = hash2;\n+\n+\t\terr = reftable_writer_add_log(w, &log);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[N - 1]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* end of iteration. */\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT(0 < err);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_reader_seek_log(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\ti = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT_STREQ(names[i], log.refname);\n+\t\tEXPECT(i == log.update_index);\n+\t\ti++;\n+\t\treftable_log_record_release(&log);\n+\t}\n+\n+\tEXPECT(i == N);\n+\treftable_iterator_destroy(&it);\n+\n+\t/* cleanup. */\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_sequential(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err = 0;\n+\tint j = 0;\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint r = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT(0 == strcmp(names[j], ref.refname));\n+\t\tEXPECT(update_index == ref.update_index);\n+\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == N);\n+\treftable_iterator_destroy(&it);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\n+\treader_close(&rd);\n+}\n+\n+static void test_table_write_small_table(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 1;\n+\twrite_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);\n+\tEXPECT(buf.len < 200);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+}\n+\n+static void test_table_read_api(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record log = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[0]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_log(&it, &log);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_free(names);\n+\treader_close(&rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_table_read_write_seek(int index, int hash_id)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i = 0;\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct strbuf pastLast = STRBUF_INIT;\n+\tstruct reftable_ref_record ref = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, hash_id);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tEXPECT(hash_id == reftable_reader_hash_id(&rd));\n+\n+\tif (!index) {\n+\t\trd.ref_offsets.index_offset = 0;\n+\t} else {\n+\t\tEXPECT(rd.ref_offsets.index_offset > 0);\n+\t}\n+\n+\tfor (i = 1; i < N; i++) {\n+\t\tint err = reftable_reader_seek_ref(&rd, &it, names[i]);\n+\t\tEXPECT_ERR(err);\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(0 == strcmp(names[i], ref.refname));\n+\t\tEXPECT(REFTABLE_REF_VAL1 == ref.value_type);\n+\t\tEXPECT(i == ref.value.val1[0]);\n+\n+\t\treftable_ref_record_release(&ref);\n+\t\treftable_iterator_destroy(&it);\n+\t}\n+\n+\tstrbuf_addstr(&pastLast, names[N - 1]);\n+\tstrbuf_addstr(&pastLast, \"/\");\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, pastLast.buf);\n+\tif (err == 0) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err > 0);\n+\t} else {\n+\t\tEXPECT(err > 0);\n+\t}\n+\n+\tstrbuf_release(&pastLast);\n+\treftable_iterator_destroy(&it);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_free(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_seek_linear(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_linear_sha256(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_index(void)\n+{\n+\ttest_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_refs_for(int indexed)\n+{\n+\tint N = 50;\n+\tchar **want_names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint want_names_len = 0;\n+\tuint8_t want_hash[GIT_SHA1_RAWSZ];\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tint err;\n+\tstruct reftable_reader rd;\n+\tstruct reftable_block_source source = { NULL };\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint j;\n+\n+\tset_test_hash(want_hash, 4);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tchar fill[51] = { 0 };\n+\t\tchar name[100];\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ];\n+\t\tuint8_t hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\n+\t\tmemset(hash, i, sizeof(hash));\n+\t\tmemset(fill, 'x', 50);\n+\t\t/* Put the variable part in the start */\n+\t\tsnprintf(name, sizeof(name), \"br%02d%s\", i, fill);\n+\t\tname[40] = 0;\n+\t\tref.refname = name;\n+\n+\t\tset_test_hash(hash1, i / 4);\n+\t\tset_test_hash(hash2, 3 + i / 4);\n+\t\tref.value_type = REFTABLE_REF_VAL2;\n+\t\tref.value.val2.value = hash1;\n+\t\tref.value.val2.target_value = hash2;\n+\n+\t\t/* 80 bytes / entry, so 3 entries per block. Yields 17\n+\t\t */\n+\t\t/* blocks. */\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\n+\t\tif (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||\n+\t\t    !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {\n+\t\t\twant_names[want_names_len++] = xstrdup(name);\n+\t\t}\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tif (!indexed) {\n+\t\trd.obj_offsets.is_present = 0;\n+\t}\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_reader_refs_for(&rd, &it, want_hash);\n+\tEXPECT_ERR(err);\n+\n+\tj = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err >= 0);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT(j < want_names_len);\n+\t\tEXPECT(0 == strcmp(ref.refname, want_names[j]));\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == want_names_len);\n+\n+\tstrbuf_release(&buf);\n+\tfree_names(want_names);\n+\treftable_iterator_destroy(&it);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_refs_for_no_index(void)\n+{\n+\ttest_table_refs_for(0);\n+}\n+\n+static void test_table_refs_for_obj_index(void)\n+{\n+\ttest_table_refs_for(1);\n+}\n+\n+static void test_write_empty_table(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_ref_record rec = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);\n+\treftable_writer_free(w);\n+\n+\tEXPECT(buf.len == header_size(1) + footer_size(1));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &rec);\n+\tEXPECT(err > 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_write_key_order(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record refs[2] = {\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}, {\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}\n+\t};\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\terr = reftable_writer_add_ref(w, &refs[0]);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_add_ref(w, &refs[1]);\n+\tprintf(\"%d\\n\", err);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_writer_close(w);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_corrupt_table_empty(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+}\n+\n+static void test_corrupt_table(void)\n+{\n+\tuint8_t zeros[1024] = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\tstrbuf_add(&buf, zeros, sizeof(zeros));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\tstrbuf_release(&buf);\n+}\n+\n+int readwrite_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_corrupt_table);\n+\tRUN_TEST(test_corrupt_table_empty);\n+\tRUN_TEST(test_log_write_read);\n+\tRUN_TEST(test_write_key_order);\n+\tRUN_TEST(test_table_read_write_seek_linear_sha256);\n+\tRUN_TEST(test_log_buffer_size);\n+\tRUN_TEST(test_table_write_small_table);\n+\tRUN_TEST(test_buffer);\n+\tRUN_TEST(test_table_read_api);\n+\tRUN_TEST(test_table_read_write_sequential);\n+\tRUN_TEST(test_table_read_write_seek_linear);\n+\tRUN_TEST(test_table_read_write_seek_index);\n+\tRUN_TEST(test_table_refs_for_no_index);\n+\tRUN_TEST(test_table_refs_for_obj_index);\n+\tRUN_TEST(test_write_empty_table);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 5e7698ae654..3d541fa5c0c 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -14,7 +14,7 @@ int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n-int reftable_test_main(int argc, const char **argv);\n+int readwrite_test_main(int argc, const char **argv);\n int stack_test_main(int argc, const char **argv);\n int tree_test_main(int argc, const char **argv);\n int reftable_dump_main(int argc, char *const *argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 050551fa698..898aba836fd 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,6 +6,7 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"435309","messageId":"e9598889af27e1a585f3742be037c238779fdff9.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 16/19] reftable: implement refname validation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:41Z","receivedAt":"2021-09-09T18:49:17Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe packed/loose format has restrictions on refnames: a and a/b cannot\ncoexist. This limitation does not apply to reftable per se, but must be\nmaintained for interoperability. This code adds validation routines to\nabort transactions that are trying to add invalid names.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   1 +\n reftable/refname.c       | 209 +++++++++++++++++++++++++++++++++++++++\n reftable/refname.h       |  29 ++++++\n reftable/refname_test.c  | 102 +++++++++++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 342 insertions(+)\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 27f29779c9a..76761ef16a6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2478,6 +2478,7 @@ REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n+REFTABLE_TEST_OBJS += reftable/refname_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/refname.c b/reftable/refname.c\nnew file mode 100644\nindex 00000000000..95734969324\n--- /dev/null\n+++ b/reftable/refname.c\n@@ -0,0 +1,209 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+#include \"refname.h\"\n+#include \"reftable-iterator.h\"\n+\n+struct find_arg {\n+\tchar **names;\n+\tconst char *want;\n+};\n+\n+static int find_name(size_t k, void *arg)\n+{\n+\tstruct find_arg *f_arg = arg;\n+\treturn strcmp(f_arg->names[k], f_arg->want) >= 0;\n+}\n+\n+static int modification_has_ref(struct modification *mod, const char *name)\n+{\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len && !strcmp(mod->add[idx], name)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (mod->del_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->del,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\tif (idx < mod->del_len && !strcmp(mod->del[idx], name)) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\terr = reftable_table_read_ref(&mod->tab, name, &ref);\n+\treftable_ref_record_release(&ref);\n+\treturn err;\n+}\n+\n+static void modification_release(struct modification *mod)\n+{\n+\t/* don't delete the strings themselves; they're owned by ref records.\n+\t */\n+\tFREE_AND_NULL(mod->add);\n+\tFREE_AND_NULL(mod->del);\n+\tmod->add_len = 0;\n+\tmod->del_len = 0;\n+}\n+\n+static int modification_has_ref_with_prefix(struct modification *mod,\n+\t\t\t\t\t    const char *prefix)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = prefix,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len &&\n+\t\t    !strncmp(prefix, mod->add[idx], strlen(prefix)))\n+\t\t\tgoto done;\n+\t}\n+\terr = reftable_table_seek_ref(&mod->tab, &it, prefix);\n+\tif (err)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\n+\t\tif (mod->del_len > 0) {\n+\t\t\tstruct find_arg arg = {\n+\t\t\t\t.names = mod->del,\n+\t\t\t\t.want = ref.refname,\n+\t\t\t};\n+\t\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\t\tif (idx < mod->del_len &&\n+\t\t\t    !strcmp(ref.refname, mod->del[idx])) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (strncmp(ref.refname, prefix, strlen(prefix))) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int validate_refname(const char *name)\n+{\n+\twhile (1) {\n+\t\tchar *next = strchr(name, '/');\n+\t\tif (!*name) {\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\t}\n+\t\tif (!next) {\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (next - name == 0 || (next - name == 1 && *name == '.') ||\n+\t\t    (next - name == 2 && name[0] == '.' && name[1] == '.'))\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\tname = next + 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz)\n+{\n+\tstruct modification mod = {\n+\t\t.tab = tab,\n+\t\t.add = reftable_calloc(sizeof(char *) * sz),\n+\t\t.del = reftable_calloc(sizeof(char *) * sz),\n+\t};\n+\tint i = 0;\n+\tint err = 0;\n+\tfor (; i < sz; i++) {\n+\t\tif (reftable_ref_record_is_deletion(&recs[i])) {\n+\t\t\tmod.del[mod.del_len++] = recs[i].refname;\n+\t\t} else {\n+\t\t\tmod.add[mod.add_len++] = recs[i].refname;\n+\t\t}\n+\t}\n+\n+\terr = modification_validate(&mod);\n+\tmodification_release(&mod);\n+\treturn err;\n+}\n+\n+static void strbuf_trim_component(struct strbuf *sl)\n+{\n+\twhile (sl->len > 0) {\n+\t\tint is_slash = (sl->buf[sl->len - 1] == '/');\n+\t\tstrbuf_setlen(sl, sl->len - 1);\n+\t\tif (is_slash)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+int modification_validate(struct modification *mod)\n+{\n+\tstruct strbuf slashed = STRBUF_INIT;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (; i < mod->add_len; i++) {\n+\t\terr = validate_refname(mod->add[i]);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\tstrbuf_addstr(&slashed, \"/\");\n+\n+\t\terr = modification_has_ref_with_prefix(mod, slashed.buf);\n+\t\tif (err == 0) {\n+\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\twhile (slashed.len) {\n+\t\t\tstrbuf_trim_component(&slashed);\n+\t\t\terr = modification_has_ref(mod, slashed.buf);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\t}\n+\terr = 0;\n+done:\n+\tstrbuf_release(&slashed);\n+\treturn err;\n+}\ndiff --git a/reftable/refname.h b/reftable/refname.h\nnew file mode 100644\nindex 00000000000..a24b40fcb42\n--- /dev/null\n+++ b/reftable/refname.h\n@@ -0,0 +1,29 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+#ifndef REFNAME_H\n+#define REFNAME_H\n+\n+#include \"reftable-record.h\"\n+#include \"reftable-generic.h\"\n+\n+struct modification {\n+\tstruct reftable_table tab;\n+\n+\tchar **add;\n+\tsize_t add_len;\n+\n+\tchar **del;\n+\tsize_t del_len;\n+};\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz);\n+\n+int modification_validate(struct modification *mod);\n+\n+#endif\ndiff --git a/reftable/refname_test.c b/reftable/refname_test.c\nnew file mode 100644\nindex 00000000000..8645cd93bbd\n--- /dev/null\n+++ b/reftable/refname_test.c\n@@ -0,0 +1,102 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-writer.h\"\n+#include \"system.h\"\n+\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct testcase {\n+\tchar *add;\n+\tchar *del;\n+\tint error_code;\n+};\n+\n+static void test_conflict(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"a/b\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"destination\", /* make sure it's not a symref.\n+\t\t\t\t\t\t*/\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct testcase cases[] = {\n+\t\t{ \"a/b/c\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"b\", NULL, 0 },\n+\t\t{ \"a\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"a\", \"a/b\", 0 },\n+\n+\t\t{ \"p/\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p//q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/./q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/../q\", NULL, REFTABLE_REFNAME_ERROR },\n+\n+\t\t{ \"a/b/c\", \"a/b\", 0 },\n+\t\t{ NULL, \"a//b\", 0 },\n+\t};\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\treftable_table_from_reader(&tab, rd);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct modification mod = {\n+\t\t\t.tab = tab,\n+\t\t};\n+\n+\t\tif (cases[i].add) {\n+\t\t\tmod.add = &cases[i].add;\n+\t\t\tmod.add_len = 1;\n+\t\t}\n+\t\tif (cases[i].del) {\n+\t\t\tmod.del = &cases[i].del;\n+\t\t\tmod.del_len = 1;\n+\t\t}\n+\n+\t\terr = modification_validate(&mod);\n+\t\tEXPECT(err == cases[i].error_code);\n+\t}\n+\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+int refname_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_conflict);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 8087f2da4e6..c8db6852c35 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -8,6 +8,7 @@ int cmd__reftable(int argc, const char **argv)\n \tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"435310","messageId":"905ffa58f362c440ccbaf88583420c69f64d0c18.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 15/19] reftable: add merged table view","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:40Z","receivedAt":"2021-09-09T18:49:25Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis adds an abstract, read-only interface to the ref database.\n\nThis primitive is used to construct the read view of the ref database\n(the read view is constructed by merging several *.ref files). It also\nprovides the mechanism to provide a unified view of the refs in the main\nrepository and the per-worktree refs.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/merged.c          | 362 +++++++++++++++++++++++++++++++++++++\n reftable/merged.h          |  35 ++++\n reftable/merged_test.c     | 292 ++++++++++++++++++++++++++++++\n reftable/reftable-merged.h |  72 ++++++++\n t/helper/test-reftable.c   |   1 +\n 6 files changed, 764 insertions(+)\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/reftable-merged.h\n\ndiff --git a/Makefile b/Makefile\nindex d2c9b0097a7..27f29779c9a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2462,6 +2462,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/merged.o\n REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n@@ -2473,6 +2474,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\ndiff --git a/reftable/merged.c b/reftable/merged.c\nnew file mode 100644\nindex 00000000000..e5b53da6db3\n--- /dev/null\n+++ b/reftable/merged.c\n@@ -0,0 +1,362 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"constants.h\"\n+#include \"iter.h\"\n+#include \"pq.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+\n+static int merged_iter_init(struct merged_iter *mi)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\t\tint err = iterator_next(&mi->stack[i], &rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t\t\treftable_record_destroy(&rec);\n+\t\t} else {\n+\t\t\tstruct pq_entry e = {\n+\t\t\t\t.rec = rec,\n+\t\t\t\t.index = i,\n+\t\t\t};\n+\t\t\tmerged_iter_pqueue_add(&mi->pq, e);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merged_iter_close(void *p)\n+{\n+\tstruct merged_iter *mi = p;\n+\tint i = 0;\n+\tmerged_iter_pqueue_release(&mi->pq);\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t}\n+\treftable_free(mi->stack);\n+}\n+\n+static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,\n+\t\t\t\t\t       size_t idx)\n+{\n+\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\tstruct pq_entry e = {\n+\t\t.rec = rec,\n+\t\t.index = idx,\n+\t};\n+\tint err = iterator_next(&mi->stack[idx], &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (err > 0) {\n+\t\treftable_iterator_destroy(&mi->stack[idx]);\n+\t\treftable_record_destroy(&rec);\n+\t\treturn 0;\n+\t}\n+\n+\tmerged_iter_pqueue_add(&mi->pq, e);\n+\treturn 0;\n+}\n+\n+static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)\n+{\n+\tif (iterator_is_null(&mi->stack[idx]))\n+\t\treturn 0;\n+\treturn merged_iter_advance_nonnull_subiter(mi, idx);\n+}\n+\n+static int merged_iter_next_entry(struct merged_iter *mi,\n+\t\t\t\t  struct reftable_record *rec)\n+{\n+\tstruct strbuf entry_key = STRBUF_INIT;\n+\tstruct pq_entry entry = { 0 };\n+\tint err = 0;\n+\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\tentry = merged_iter_pqueue_remove(&mi->pq);\n+\terr = merged_iter_advance_subiter(mi, entry.index);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\t/*\n+\t  One can also use reftable as datacenter-local storage, where the ref\n+\t  database is maintained in globally consistent database (eg.\n+\t  CockroachDB or Spanner). In this scenario, replication delays together\n+\t  with compaction may cause newer tables to contain older entries. In\n+\t  such a deployment, the loop below must be changed to collect all\n+\t  entries for the same key, and return new the newest one.\n+\t*/\n+\treftable_record_key(&entry.rec, &entry_key);\n+\twhile (!merged_iter_pqueue_is_empty(mi->pq)) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(mi->pq);\n+\t\tstruct strbuf k = STRBUF_INIT;\n+\t\tint err = 0, cmp = 0;\n+\n+\t\treftable_record_key(&top.rec, &k);\n+\n+\t\tcmp = strbuf_cmp(&k, &entry_key);\n+\t\tstrbuf_release(&k);\n+\n+\t\tif (cmp > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmerged_iter_pqueue_remove(&mi->pq);\n+\t\terr = merged_iter_advance_subiter(mi, top.index);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_record_destroy(&top.rec);\n+\t}\n+\n+\treftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));\n+\treftable_record_destroy(&entry.rec);\n+\tstrbuf_release(&entry_key);\n+\treturn 0;\n+}\n+\n+static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)\n+{\n+\twhile (1) {\n+\t\tint err = merged_iter_next_entry(mi, rec);\n+\t\tif (err == 0 && mi->suppress_deletions &&\n+\t\t    reftable_record_is_deletion(rec)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treturn err;\n+\t}\n+}\n+\n+static int merged_iter_next_void(void *p, struct reftable_record *rec)\n+{\n+\tstruct merged_iter *mi = p;\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\treturn merged_iter_next(mi, rec);\n+}\n+\n+static struct reftable_iterator_vtable merged_iter_vtable = {\n+\t.next = &merged_iter_next_void,\n+\t.close = &merged_iter_close,\n+};\n+\n+static void iterator_from_merged_iter(struct reftable_iterator *it,\n+\t\t\t\t      struct merged_iter *mi)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = mi;\n+\tit->ops = &merged_iter_vtable;\n+}\n+\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id)\n+{\n+\tstruct reftable_merged_table *m = NULL;\n+\tuint64_t last_max = 0;\n+\tuint64_t first_min = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t min = reftable_table_min_update_index(&stack[i]);\n+\t\tuint64_t max = reftable_table_max_update_index(&stack[i]);\n+\n+\t\tif (reftable_table_hash_id(&stack[i]) != hash_id) {\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\t\t}\n+\t\tif (i == 0 || min < first_min) {\n+\t\t\tfirst_min = min;\n+\t\t}\n+\t\tif (i == 0 || max > last_max) {\n+\t\t\tlast_max = max;\n+\t\t}\n+\t}\n+\n+\tm = reftable_calloc(sizeof(struct reftable_merged_table));\n+\tm->stack = stack;\n+\tm->stack_len = n;\n+\tm->min = first_min;\n+\tm->max = last_max;\n+\tm->hash_id = hash_id;\n+\t*dest = m;\n+\treturn 0;\n+}\n+\n+/* clears the list of subtable, without affecting the readers themselves. */\n+void merged_table_release(struct reftable_merged_table *mt)\n+{\n+\tFREE_AND_NULL(mt->stack);\n+\tmt->stack_len = 0;\n+}\n+\n+void reftable_merged_table_free(struct reftable_merged_table *mt)\n+{\n+\tif (!mt) {\n+\t\treturn;\n+\t}\n+\tmerged_table_release(mt);\n+\treftable_free(mt);\n+}\n+\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->max;\n+}\n+\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->min;\n+}\n+\n+static int reftable_table_seek_record(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      struct reftable_record *rec)\n+{\n+\treturn tab->ops->seek_record(tab->table_arg, it, rec);\n+}\n+\n+static int merged_table_seek_record(struct reftable_merged_table *mt,\n+\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tstruct reftable_iterator *iters = reftable_calloc(\n+\t\tsizeof(struct reftable_iterator) * mt->stack_len);\n+\tstruct merged_iter merged = {\n+\t\t.stack = iters,\n+\t\t.typ = reftable_record_type(rec),\n+\t\t.hash_id = mt->hash_id,\n+\t\t.suppress_deletions = mt->suppress_deletions,\n+\t};\n+\tint n = 0;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < mt->stack_len && err == 0; i++) {\n+\t\tint e = reftable_table_seek_record(&mt->stack[i], &iters[n],\n+\t\t\t\t\t\t   rec);\n+\t\tif (e < 0) {\n+\t\t\terr = e;\n+\t\t}\n+\t\tif (e == 0) {\n+\t\t\tn++;\n+\t\t}\n+\t}\n+\tif (err < 0) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < n; i++) {\n+\t\t\treftable_iterator_destroy(&iters[i]);\n+\t\t}\n+\t\treftable_free(iters);\n+\t\treturn err;\n+\t}\n+\n+\tmerged.stack_len = n;\n+\terr = merged_iter_init(&merged);\n+\tif (err < 0) {\n+\t\tmerged_iter_close(&merged);\n+\t\treturn err;\n+\t} else {\n+\t\tstruct merged_iter *p =\n+\t\t\treftable_malloc(sizeof(struct merged_iter));\n+\t\t*p = merged;\n+\t\titerator_from_merged_iter(it, p);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_merged_table_seek_log_at(mt, it, name, max);\n+}\n+\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt)\n+{\n+\treturn mt->hash_id;\n+}\n+\n+static int reftable_merged_table_seek_void(void *tab,\n+\t\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t\t   struct reftable_record *rec)\n+{\n+\treturn merged_table_seek_record(tab, it, rec);\n+}\n+\n+static uint32_t reftable_merged_table_hash_id_void(void *tab)\n+{\n+\treturn reftable_merged_table_hash_id(tab);\n+}\n+\n+static uint64_t reftable_merged_table_min_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_merged_table_max_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable merged_table_vtable = {\n+\t.seek_record = reftable_merged_table_seek_void,\n+\t.hash_id = reftable_merged_table_hash_id_void,\n+\t.min_update_index = reftable_merged_table_min_update_index_void,\n+\t.max_update_index = reftable_merged_table_max_update_index_void,\n+};\n+\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *merged)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &merged_table_vtable;\n+\ttab->table_arg = merged;\n+}\ndiff --git a/reftable/merged.h b/reftable/merged.h\nnew file mode 100644\nindex 00000000000..8c4d4d58d77\n--- /dev/null\n+++ b/reftable/merged.h\n@@ -0,0 +1,35 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef MERGED_H\n+#define MERGED_H\n+\n+#include \"pq.h\"\n+\n+struct reftable_merged_table {\n+\tstruct reftable_table *stack;\n+\tsize_t stack_len;\n+\tuint32_t hash_id;\n+\tint suppress_deletions;\n+\n+\tuint64_t min;\n+\tuint64_t max;\n+};\n+\n+struct merged_iter {\n+\tstruct reftable_iterator *stack;\n+\tuint32_t hash_id;\n+\tsize_t stack_len;\n+\tuint8_t typ;\n+\tint suppress_deletions;\n+\tstruct merged_iter_pqueue pq;\n+};\n+\n+void merged_table_release(struct reftable_merged_table *mt);\n+\n+#endif\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nnew file mode 100644\nindex 00000000000..da607401141\n--- /dev/null\n+++ b/reftable/merged_test.c\n@@ -0,0 +1,292 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-generic.h\"\n+#include \"reftable-writer.h\"\n+\n+static void write_test_table(struct strbuf *buf,\n+\t\t\t     struct reftable_ref_record refs[], int n)\n+{\n+\tint min = 0xffffffff;\n+\tint max = 0;\n+\tint i = 0;\n+\tint err;\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_writer *w = NULL;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t ui = refs[i].update_index;\n+\t\tif (ui > max) {\n+\t\t\tmax = ui;\n+\t\t}\n+\t\tif (ui < min) {\n+\t\t\tmin = ui;\n+\t\t}\n+\t}\n+\n+\tw = reftable_new_writer(&strbuf_add_void, buf, &opts);\n+\treftable_writer_set_limits(w, min, max);\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t before = refs[i].update_index;\n+\t\tint n = reftable_writer_add_ref(w, &refs[i]);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT(before == refs[i].update_index);\n+\t}\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\n+\treftable_writer_free(w);\n+}\n+\n+static struct reftable_merged_table *\n+merged_table_from_records(struct reftable_ref_record **refs,\n+\t\t\t  struct reftable_block_source **source,\n+\t\t\t  struct reftable_reader ***readers, int *sizes,\n+\t\t\t  struct strbuf *buf, int n)\n+{\n+\tint i = 0;\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err;\n+\tstruct reftable_table *tabs =\n+\t\treftable_calloc(n * sizeof(struct reftable_table));\n+\t*readers = reftable_calloc(n * sizeof(struct reftable_reader *));\n+\t*source = reftable_calloc(n * sizeof(**source));\n+\tfor (i = 0; i < n; i++) {\n+\t\twrite_test_table(&buf[i], refs[i], sizes[i]);\n+\t\tblock_source_from_strbuf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_new_reader(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tEXPECT_ERR(err);\n+\t\treftable_table_from_reader(&tabs[i], (*readers)[i]);\n+\t}\n+\n+\terr = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\treturn mt;\n+}\n+\n+static void readers_destroy(struct reftable_reader **readers, size_t n)\n+{\n+\tint i = 0;\n+\tfor (; i < n; i++)\n+\t\treftable_reader_free(readers[i]);\n+\treftable_free(readers);\n+}\n+\n+static void test_merged_between(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };\n+\n+\tstruct reftable_ref_record r1[] = { {\n+\t\t.refname = \"b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = hash1,\n+\t} };\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2 };\n+\tint sizes[] = { 1, 1 };\n+\tstruct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);\n+\tint i;\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\tEXPECT(ref.update_index == 2);\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treaders_destroy(readers, 2);\n+\treftable_merged_table_free(mt);\n+\tfor (i = 0; i < ARRAY_SIZE(bufs); i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treftable_free(bs);\n+}\n+\n+static void test_merged(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };\n+\tuint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\tstruct reftable_ref_record r3[] = {\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash2,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"d\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t};\n+\n+\tstruct reftable_ref_record want[] = {\n+\t\tr2[0],\n+\t\tr1[1],\n+\t\tr3[0],\n+\t\tr3[1],\n+\t};\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n+\tint sizes[3] = { 3, 1, 2 };\n+\tstruct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tstruct reftable_ref_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tint i = 0;\n+\n+\tEXPECT_ERR(err);\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (len == cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\tout = reftable_realloc(\n+\t\t\t\tout, sizeof(struct reftable_ref_record) * cap);\n+\t\t}\n+\t\tout[len++] = ref;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tEXPECT(ARRAY_SIZE(want) == len);\n+\tfor (i = 0; i < len; i++) {\n+\t\tEXPECT(reftable_ref_record_equal(&want[i], &out[i],\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t}\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&out[i]);\n+\t}\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+static void test_default_write_opts(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"master\",\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);\n+\tuint32_t hash_id;\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_merged_table *merged = NULL;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\thash_id = reftable_reader_hash_id(rd);\n+\tEXPECT(hash_id == GIT_SHA1_FORMAT_ID);\n+\n+\treftable_table_from_reader(&tab[0], rd);\n+\terr = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\treftable_reader_free(rd);\n+\treftable_merged_table_free(merged);\n+\tstrbuf_release(&buf);\n+}\n+\n+/* XXX test refs_for(oid) */\n+\n+int merged_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_merged_between);\n+\tRUN_TEST(test_merged);\n+\tRUN_TEST(test_default_write_opts);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-merged.h b/reftable/reftable-merged.h\nnew file mode 100644\nindex 00000000000..1a6d16915ab\n--- /dev/null\n+++ b/reftable/reftable-merged.h\n@@ -0,0 +1,72 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_MERGED_H\n+#define REFTABLE_MERGED_H\n+\n+#include \"reftable-iterator.h\"\n+\n+/*\n+ * Merged tables\n+ *\n+ * A ref database kept in a sequence of table files. The merged_table presents a\n+ * unified view to reading (seeking, iterating) a sequence of immutable tables.\n+ *\n+ * The merged tables are on purpose kept disconnected from their actual storage\n+ * (eg. files on disk), because it is useful to merge tables aren't files. For\n+ * example, the per-workspace and global ref namespace can be implemented as a\n+ * merged table of two stacks of file-backed reftables.\n+ */\n+\n+/* A merged table is implements seeking/iterating over a stack of tables. */\n+struct reftable_merged_table;\n+\n+/* A generic reftable; see below. */\n+struct reftable_table;\n+\n+/* reftable_new_merged_table creates a new merged table. It takes ownership of\n+   the stack array.\n+*/\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id);\n+\n+/* returns an iterator positioned just before 'name' */\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns an iterator for log entry, at given update_index */\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index);\n+\n+/* like reftable_merged_table_seek_log_at but look for the newest entry. */\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns the max update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);\n+\n+/* returns the min update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);\n+\n+/* releases memory for the merged_table */\n+void reftable_merged_table_free(struct reftable_merged_table *m);\n+\n+/* return the hash ID of the merged table. */\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);\n+\n+/* create a generic table from reftable_merged_table */\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *table);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 0b5a1701df1..8087f2da4e6 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"435312","messageId":"8b0ee68c636a10eca73092e7a4c823811977ccc8.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 17/19] reftable: implement stack, a mutable database of reftable files.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:42Z","receivedAt":"2021-09-09T18:49:26Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |    1 +\n reftable/reftable-stack.h |  128 ++++\n reftable/stack.c          | 1396 +++++++++++++++++++++++++++++++++++++\n reftable/stack.h          |   41 ++\n reftable/stack_test.c     |  949 +++++++++++++++++++++++++\n t/helper/test-reftable.c  |    1 +\n 6 files changed, 2516 insertions(+)\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 76761ef16a6..619b1b15ce0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2479,6 +2479,7 @@ REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/refname_test.o\n+REFTABLE_TEST_OBJS += reftable/stack_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h\nnew file mode 100644\nindex 00000000000..1b602dda58a\n--- /dev/null\n+++ b/reftable/reftable-stack.h\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_STACK_H\n+#define REFTABLE_STACK_H\n+\n+#include \"reftable-writer.h\"\n+\n+/*\n+ * The stack presents an interface to a mutable sequence of reftables.\n+\n+ * A stack can be mutated by pushing a table to the top of the stack.\n+\n+ * The reftable_stack automatically compacts files on disk to ensure good\n+ * amortized performance.\n+ *\n+ * For windows and other platforms that cannot have open files as rename\n+ * destinations, concurrent access from multiple processes needs the rand()\n+ * random seed to be randomized.\n+ */\n+struct reftable_stack;\n+\n+/* open a new reftable stack. The tables along with the table list will be\n+ *  stored in 'dir'. Typically, this should be .git/reftables.\n+ */\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config);\n+\n+/* returns the update_index at which a next table should be written. */\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);\n+\n+/* holds a transaction to add tables at the top of a stack. */\n+struct reftable_addition;\n+\n+/*\n+ * returns a new transaction to add reftables to the given stack. As a side\n+ * effect, the ref database is locked.\n+ */\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st);\n+\n+/* Adds a reftable to transaction. */\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg);\n+\n+/* Commits the transaction, releasing the lock. After calling this,\n+ * reftable_addition_destroy should still be called.\n+ */\n+int reftable_addition_commit(struct reftable_addition *add);\n+\n+/* Release all non-committed data from the transaction, and deallocate the\n+ * transaction. Releases the lock if held. */\n+void reftable_addition_destroy(struct reftable_addition *add);\n+\n+/* add a new table to the stack. The write_table function must call\n+ * reftable_writer_set_limits, add refs and return an error value. */\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t  void *write_arg),\n+\t\t       void *write_arg);\n+\n+/* returns the merged_table for seeking. This table is valid until the\n+ * next write or reload, and should not be closed or deleted.\n+ */\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st);\n+\n+/* frees all resources associated with the stack. */\n+void reftable_stack_destroy(struct reftable_stack *st);\n+\n+/* Reloads the stack if necessary. This is very cheap to run if the stack was up\n+ * to date */\n+int reftable_stack_reload(struct reftable_stack *st);\n+\n+/* Policy for expiring reflog entries. */\n+struct reftable_log_expiry_config {\n+\t/* Drop entries older than this timestamp */\n+\tuint64_t time;\n+\n+\t/* Drop older entries */\n+\tuint64_t min_update_index;\n+};\n+\n+/* compacts all reftables into a giant table. Expire reflog entries if config is\n+ * non-NULL */\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+\n+/* heuristically compact unbalanced table stack. */\n+int reftable_stack_auto_compact(struct reftable_stack *st);\n+\n+/* delete stale .ref tables. */\n+int reftable_stack_clean(struct reftable_stack *st);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* convenience function to read a single log. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/* statistics on past compactions. */\n+struct reftable_compaction_stats {\n+\tuint64_t bytes; /* total number of bytes written */\n+\tuint64_t entries_written; /* total number of entries written, including\n+\t\t\t\t     failures. */\n+\tint attempts; /* how often we tried to compact */\n+\tint failures; /* failures happen on concurrent updates */\n+};\n+\n+/* return statistics for compaction up till now. */\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st);\n+\n+/* print the entire stack represented by the directory */\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);\n+\n+#endif\ndiff --git a/reftable/stack.c b/reftable/stack.c\nnew file mode 100644\nindex 00000000000..48e22a6c184\n--- /dev/null\n+++ b/reftable/stack.c\n@@ -0,0 +1,1396 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+#include \"merged.h\"\n+#include \"reader.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-merged.h\"\n+#include \"writer.h\"\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg);\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name);\n+static void reftable_addition_close(struct reftable_addition *add);\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open);\n+\n+static void stack_filename(struct strbuf *dest, struct reftable_stack *st,\n+\t\t\t   const char *name)\n+{\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, st->reftable_dir);\n+\tstrbuf_addstr(dest, \"/\");\n+\tstrbuf_addstr(dest, name);\n+}\n+\n+static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)\n+{\n+\tint *fdp = (int *)arg;\n+\treturn write(*fdp, data, sz);\n+}\n+\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config)\n+{\n+\tstruct reftable_stack *p =\n+\t\treftable_calloc(sizeof(struct reftable_stack));\n+\tstruct strbuf list_file_name = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (config.hash_id == 0) {\n+\t\tconfig.hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\n+\t*dest = NULL;\n+\n+\tstrbuf_reset(&list_file_name);\n+\tstrbuf_addstr(&list_file_name, dir);\n+\tstrbuf_addstr(&list_file_name, \"/tables.list\");\n+\n+\tp->list_file = strbuf_detach(&list_file_name, NULL);\n+\tp->reftable_dir = xstrdup(dir);\n+\tp->config = config;\n+\n+\terr = reftable_stack_reload_maybe_reuse(p, 1);\n+\tif (err < 0) {\n+\t\treftable_stack_destroy(p);\n+\t} else {\n+\t\t*dest = p;\n+\t}\n+\treturn err;\n+}\n+\n+static int fd_read_lines(int fd, char ***namesp)\n+{\n+\toff_t size = lseek(fd, 0, SEEK_END);\n+\tchar *buf = NULL;\n+\tint err = 0;\n+\tif (size < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\terr = lseek(fd, 0, SEEK_SET);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tbuf = reftable_malloc(size + 1);\n+\tif (read(fd, buf, size) != size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\tbuf[size] = 0;\n+\n+\tparse_names(buf, size, namesp);\n+\n+done:\n+\treftable_free(buf);\n+\treturn err;\n+}\n+\n+int read_lines(const char *filename, char ***namesp)\n+{\n+\tint fd = open(filename, O_RDONLY, 0644);\n+\tint err = 0;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\t*namesp = reftable_calloc(sizeof(char *));\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\terr = fd_read_lines(fd, namesp);\n+\tclose(fd);\n+\treturn err;\n+}\n+\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st)\n+{\n+\treturn st->merged;\n+}\n+\n+static int has_name(char **names, const char *name)\n+{\n+\twhile (*names) {\n+\t\tif (!strcmp(*names, name))\n+\t\t\treturn 1;\n+\t\tnames++;\n+\t}\n+\treturn 0;\n+}\n+\n+/* Close and free the stack */\n+void reftable_stack_destroy(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = 0;\n+\tif (st->merged) {\n+\t\treftable_merged_table_free(st->merged);\n+\t\tst->merged = NULL;\n+\t}\n+\n+\terr = read_lines(st->list_file, &names);\n+\tif (err < 0) {\n+\t\tFREE_AND_NULL(names);\n+\t}\n+\n+\tif (st->readers) {\n+\t\tint i = 0;\n+\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\tfor (i = 0; i < st->readers_len; i++) {\n+\t\t\tconst char *name = reader_name(st->readers[i]);\n+\t\t\tstrbuf_reset(&filename);\n+\t\t\tif (names && !has_name(names, name)) {\n+\t\t\t\tstack_filename(&filename, st, name);\n+\t\t\t}\n+\t\t\treftable_reader_free(st->readers[i]);\n+\n+\t\t\tif (filename.len) {\n+\t\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\t\tunlink(filename.buf);\n+\t\t\t}\n+\t\t}\n+\t\tstrbuf_release(&filename);\n+\t\tst->readers_len = 0;\n+\t\tFREE_AND_NULL(st->readers);\n+\t}\n+\tFREE_AND_NULL(st->list_file);\n+\tFREE_AND_NULL(st->reftable_dir);\n+\treftable_free(st);\n+\tfree_names(names);\n+}\n+\n+static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n+\t\t\t\t\t\t   int cur_len)\n+{\n+\tstruct reftable_reader **cur =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * cur_len);\n+\tint i = 0;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tcur[i] = st->readers[i];\n+\t}\n+\treturn cur;\n+}\n+\n+static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+\t\t\t\t      int reuse_open)\n+{\n+\tint cur_len = !st->merged ? 0 : st->merged->stack_len;\n+\tstruct reftable_reader **cur = stack_copy_readers(st, cur_len);\n+\tint err = 0;\n+\tint names_len = names_length(names);\n+\tstruct reftable_reader **new_readers =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * names_len);\n+\tstruct reftable_table *new_tables =\n+\t\treftable_calloc(sizeof(struct reftable_table) * names_len);\n+\tint new_readers_len = 0;\n+\tstruct reftable_merged_table *new_merged = NULL;\n+\tint i;\n+\n+\twhile (*names) {\n+\t\tstruct reftable_reader *rd = NULL;\n+\t\tchar *name = *names++;\n+\n+\t\t/* this is linear; we assume compaction keeps the number of\n+\t\t   tables under control so this is not quadratic. */\n+\t\tint j = 0;\n+\t\tfor (j = 0; reuse_open && j < cur_len; j++) {\n+\t\t\tif (cur[j] && 0 == strcmp(cur[j]->name, name)) {\n+\t\t\t\trd = cur[j];\n+\t\t\t\tcur[j] = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!rd) {\n+\t\t\tstruct reftable_block_source src = { NULL };\n+\t\t\tstruct strbuf table_path = STRBUF_INIT;\n+\t\t\tstack_filename(&table_path, st, name);\n+\n+\t\t\terr = reftable_block_source_from_file(&src,\n+\t\t\t\t\t\t\t      table_path.buf);\n+\t\t\tstrbuf_release(&table_path);\n+\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\n+\t\t\terr = reftable_new_reader(&rd, &src, name);\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\n+\t\tnew_readers[new_readers_len] = rd;\n+\t\treftable_table_from_reader(&new_tables[new_readers_len], rd);\n+\t\tnew_readers_len++;\n+\t}\n+\n+\t/* success! */\n+\terr = reftable_new_merged_table(&new_merged, new_tables,\n+\t\t\t\t\tnew_readers_len, st->config.hash_id);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tnew_tables = NULL;\n+\tst->readers_len = new_readers_len;\n+\tif (st->merged) {\n+\t\tmerged_table_release(st->merged);\n+\t\treftable_merged_table_free(st->merged);\n+\t}\n+\tif (st->readers) {\n+\t\treftable_free(st->readers);\n+\t}\n+\tst->readers = new_readers;\n+\tnew_readers = NULL;\n+\tnew_readers_len = 0;\n+\n+\tnew_merged->suppress_deletions = 1;\n+\tst->merged = new_merged;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tif (cur[i]) {\n+\t\t\tconst char *name = reader_name(cur[i]);\n+\t\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\t\tstack_filename(&filename, st, name);\n+\n+\t\t\treader_close(cur[i]);\n+\t\t\treftable_reader_free(cur[i]);\n+\n+\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\tunlink(filename.buf);\n+\n+\t\t\tstrbuf_release(&filename);\n+\t\t}\n+\t}\n+\n+done:\n+\tfor (i = 0; i < new_readers_len; i++) {\n+\t\treader_close(new_readers[i]);\n+\t\treftable_reader_free(new_readers[i]);\n+\t}\n+\treftable_free(new_readers);\n+\treftable_free(new_tables);\n+\treftable_free(cur);\n+\treturn err;\n+}\n+\n+/* return negative if a before b. */\n+static int tv_cmp(struct timeval *a, struct timeval *b)\n+{\n+\ttime_t diff = a->tv_sec - b->tv_sec;\n+\tint udiff = a->tv_usec - b->tv_usec;\n+\n+\tif (diff != 0)\n+\t\treturn diff;\n+\n+\treturn udiff;\n+}\n+\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open)\n+{\n+\tstruct timeval deadline = { 0 };\n+\tint err = gettimeofday(&deadline, NULL);\n+\tint64_t delay = 0;\n+\tint tries = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tdeadline.tv_sec += 3;\n+\twhile (1) {\n+\t\tchar **names = NULL;\n+\t\tchar **names_after = NULL;\n+\t\tstruct timeval now = { 0 };\n+\t\tint err = gettimeofday(&now, NULL);\n+\t\tint err2 = 0;\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* Only look at deadlines after the first few times. This\n+\t\t   simplifies debugging in GDB */\n+\t\ttries++;\n+\t\tif (tries > 3 && tv_cmp(&now, &deadline) >= 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = read_lines(st->list_file, &names);\n+\t\tif (err < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\tif (err == 0) {\n+\t\t\tfree_names(names);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err != REFTABLE_NOT_EXIST_ERROR) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent\n+\t\t   writer. Check if there was one by checking if the name list\n+\t\t   changed.\n+\t\t*/\n+\t\terr2 = read_lines(st->list_file, &names_after);\n+\t\tif (err2 < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err2;\n+\t\t}\n+\n+\t\tif (names_equal(names_after, names)) {\n+\t\t\tfree_names(names);\n+\t\t\tfree_names(names_after);\n+\t\t\treturn err;\n+\t\t}\n+\t\tfree_names(names);\n+\t\tfree_names(names_after);\n+\n+\t\tdelay = delay + (delay * rand()) / RAND_MAX + 1;\n+\t\tsleep_millisec(delay);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* -1 = error\n+ 0 = up to date\n+ 1 = changed. */\n+static int stack_uptodate(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = read_lines(st->list_file, &names);\n+\tint i = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tfor (i = 0; i < st->readers_len; i++) {\n+\t\tif (!names[i]) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (strcmp(st->readers[i]->name, names[i])) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tif (names[st->merged->stack_len]) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tfree_names(names);\n+\treturn err;\n+}\n+\n+int reftable_stack_reload(struct reftable_stack *st)\n+{\n+\tint err = stack_uptodate(st);\n+\tif (err > 0)\n+\t\treturn reftable_stack_reload_maybe_reuse(st, 1);\n+\treturn err;\n+}\n+\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write)(struct reftable_writer *wr, void *arg),\n+\t\t       void *arg)\n+{\n+\tint err = stack_try_add(st, write, arg);\n+\tif (err < 0) {\n+\t\tif (err == REFTABLE_LOCK_ERROR) {\n+\t\t\t/* Ignore error return, we want to propagate\n+\t\t\t   REFTABLE_LOCK_ERROR.\n+\t\t\t*/\n+\t\t\treftable_stack_reload(st);\n+\t\t}\n+\t\treturn err;\n+\t}\n+\n+\tif (!st->disable_auto_compact)\n+\t\treturn reftable_stack_auto_compact(st);\n+\n+\treturn 0;\n+}\n+\n+static void format_name(struct strbuf *dest, uint64_t min, uint64_t max)\n+{\n+\tchar buf[100];\n+\tuint32_t rnd = (uint32_t)rand();\n+\tsnprintf(buf, sizeof(buf), \"0x%012\" PRIx64 \"-0x%012\" PRIx64 \"-%08x\",\n+\t\t min, max, rnd);\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, buf);\n+}\n+\n+struct reftable_addition {\n+\tint lock_file_fd;\n+\tstruct strbuf lock_file_name;\n+\tstruct reftable_stack *stack;\n+\n+\tchar **new_tables;\n+\tint new_tables_len;\n+\tuint64_t next_update_index;\n+};\n+\n+#define REFTABLE_ADDITION_INIT                \\\n+\t{                                     \\\n+\t\t.lock_file_name = STRBUF_INIT \\\n+\t}\n+\n+static int reftable_stack_init_addition(struct reftable_addition *add,\n+\t\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tadd->stack = st;\n+\n+\tstrbuf_reset(&add->lock_file_name);\n+\tstrbuf_addstr(&add->lock_file_name, st->list_file);\n+\tstrbuf_addstr(&add->lock_file_name, \".lock\");\n+\n+\tadd->lock_file_fd = open(add->lock_file_name.buf,\n+\t\t\t\t O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (add->lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = REFTABLE_LOCK_ERROR;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\terr = stack_uptodate(st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 1) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->next_update_index = reftable_stack_next_update_index(st);\n+done:\n+\tif (err) {\n+\t\treftable_addition_close(add);\n+\t}\n+\treturn err;\n+}\n+\n+static void reftable_addition_close(struct reftable_addition *add)\n+{\n+\tint i = 0;\n+\tstruct strbuf nm = STRBUF_INIT;\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstack_filename(&nm, add->stack, add->new_tables[i]);\n+\t\tunlink(nm.buf);\n+\t\treftable_free(add->new_tables[i]);\n+\t\tadd->new_tables[i] = NULL;\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\tif (add->lock_file_fd > 0) {\n+\t\tclose(add->lock_file_fd);\n+\t\tadd->lock_file_fd = 0;\n+\t}\n+\tif (add->lock_file_name.len > 0) {\n+\t\tunlink(add->lock_file_name.buf);\n+\t\tstrbuf_release(&add->lock_file_name);\n+\t}\n+\n+\tstrbuf_release(&nm);\n+}\n+\n+void reftable_addition_destroy(struct reftable_addition *add)\n+{\n+\tif (!add) {\n+\t\treturn;\n+\t}\n+\treftable_addition_close(add);\n+\treftable_free(add);\n+}\n+\n+int reftable_addition_commit(struct reftable_addition *add)\n+{\n+\tstruct strbuf table_list = STRBUF_INIT;\n+\tint i = 0;\n+\tint err = 0;\n+\tif (add->new_tables_len == 0)\n+\t\tgoto done;\n+\n+\tfor (i = 0; i < add->stack->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->stack->readers[i]->name);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->new_tables[i]);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\n+\terr = write(add->lock_file_fd, table_list.buf, table_list.len);\n+\tstrbuf_release(&table_list);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = close(add->lock_file_fd);\n+\tadd->lock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(add->lock_file_name.buf, add->stack->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\t/* success, no more state to clean up. */\n+\tstrbuf_release(&add->lock_file_name);\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\treftable_free(add->new_tables[i]);\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\terr = reftable_stack_reload(add->stack);\n+done:\n+\treftable_addition_close(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tstruct reftable_addition empty = REFTABLE_ADDITION_INIT;\n+\t*dest = reftable_calloc(sizeof(**dest));\n+\t**dest = empty;\n+\terr = reftable_stack_init_addition(*dest, st);\n+\tif (err) {\n+\t\treftable_free(*dest);\n+\t\t*dest = NULL;\n+\t}\n+\treturn err;\n+}\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg)\n+{\n+\tstruct reftable_addition add = REFTABLE_ADDITION_INIT;\n+\tint err = reftable_stack_init_addition(&add, st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tif (err > 0) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_addition_add(&add, write_table, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_addition_commit(&add);\n+done:\n+\treftable_addition_close(&add);\n+\treturn err;\n+}\n+\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf tab_file_name = STRBUF_INIT;\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\tint tab_fd = 0;\n+\n+\tstrbuf_reset(&next_name);\n+\tformat_name(&next_name, add->next_update_index, add->next_update_index);\n+\n+\tstack_filename(&temp_tab_file_name, add->stack, next_name.buf);\n+\tstrbuf_addstr(&temp_tab_file_name, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab_file_name.buf);\n+\tif (tab_fd < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd,\n+\t\t\t\t &add->stack->config);\n+\terr = write_table(wr, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_writer_close(wr);\n+\tif (err == REFTABLE_EMPTY_TABLE_ERROR) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = stack_check_addition(add->stack, temp_tab_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (wr->min_update_index < add->next_update_index) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tformat_name(&next_name, wr->min_update_index, wr->max_update_index);\n+\tstrbuf_addstr(&next_name, \".ref\");\n+\n+\tstack_filename(&tab_file_name, add->stack, next_name.buf);\n+\n+\t/*\n+\t  On windows, this relies on rand() picking a unique destination name.\n+\t  Maybe we should do retry loop as well?\n+\t */\n+\terr = rename(temp_tab_file_name.buf, tab_file_name.buf);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->new_tables = reftable_realloc(add->new_tables,\n+\t\t\t\t\t   sizeof(*add->new_tables) *\n+\t\t\t\t\t\t   (add->new_tables_len + 1));\n+\tadd->new_tables[add->new_tables_len] = strbuf_detach(&next_name, NULL);\n+\tadd->new_tables_len++;\n+done:\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (temp_tab_file_name.len > 0) {\n+\t\tunlink(temp_tab_file_name.buf);\n+\t}\n+\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&tab_file_name);\n+\tstrbuf_release(&next_name);\n+\treftable_writer_free(wr);\n+\treturn err;\n+}\n+\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st)\n+{\n+\tint sz = st->merged->stack_len;\n+\tif (sz > 0)\n+\t\treturn reftable_reader_max_update_index(st->readers[sz - 1]) +\n+\t\t       1;\n+\treturn 1;\n+}\n+\n+static int stack_compact_locked(struct reftable_stack *st, int first, int last,\n+\t\t\t\tstruct strbuf *temp_tab,\n+\t\t\t\tstruct reftable_log_expiry_config *config)\n+{\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tint tab_fd = -1;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\n+\tformat_name(&next_name,\n+\t\t    reftable_reader_min_update_index(st->readers[first]),\n+\t\t    reftable_reader_max_update_index(st->readers[last]));\n+\n+\tstack_filename(temp_tab, st, next_name.buf);\n+\tstrbuf_addstr(temp_tab, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab->buf);\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);\n+\n+\terr = stack_write_compact(st, wr, first, last, config);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = reftable_writer_close(wr);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\n+done:\n+\treftable_writer_free(wr);\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (err != 0 && temp_tab->len > 0) {\n+\t\tunlink(temp_tab->buf);\n+\t\tstrbuf_release(temp_tab);\n+\t}\n+\tstrbuf_release(&next_name);\n+\treturn err;\n+}\n+\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\tint subtabs_len = last - first + 1;\n+\tstruct reftable_table *subtabs = reftable_calloc(\n+\t\tsizeof(struct reftable_table) * (last - first + 1));\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err = 0;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\n+\tuint64_t entries = 0;\n+\n+\tint i = 0, j = 0;\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct reftable_reader *t = st->readers[i];\n+\t\treftable_table_from_reader(&subtabs[j++], t);\n+\t\tst->stats.bytes += t->size;\n+\t}\n+\treftable_writer_set_limits(wr, st->readers[first]->min_update_index,\n+\t\t\t\t   st->readers[last]->max_update_index);\n+\n+\terr = reftable_new_merged_table(&mt, subtabs, subtabs_len,\n+\t\t\t\t\tst->config.hash_id);\n+\tif (err < 0) {\n+\t\treftable_free(subtabs);\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_merged_table_seek_ref(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (first == 0 && reftable_ref_record_is_deletion(&ref)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_ref(wr, &ref);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_merged_table_seek_log(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (first == 0 && reftable_log_record_is_deletion(&log)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->min_update_index > 0 &&\n+\t\t    log.update_index < config->min_update_index) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->time > 0 &&\n+\t\t    log.value.update.time < config->time) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_log(wr, &log);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\tif (mt) {\n+\t\tmerged_table_release(mt);\n+\t\treftable_merged_table_free(mt);\n+\t}\n+\treftable_ref_record_release(&ref);\n+\treftable_log_record_release(&log);\n+\tst->stats.entries_written += entries;\n+\treturn err;\n+}\n+\n+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */\n+static int stack_compact_range(struct reftable_stack *st, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *expiry)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf new_table_name = STRBUF_INIT;\n+\tstruct strbuf lock_file_name = STRBUF_INIT;\n+\tstruct strbuf ref_list_contents = STRBUF_INIT;\n+\tstruct strbuf new_table_path = STRBUF_INIT;\n+\tint err = 0;\n+\tint have_lock = 0;\n+\tint lock_file_fd = 0;\n+\tint compact_count = last - first + 1;\n+\tchar **listp = NULL;\n+\tchar **delete_on_success =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tchar **subtable_locks =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tint i = 0;\n+\tint j = 0;\n+\tint is_empty_table = 0;\n+\n+\tif (first > last || (!expiry && first == last)) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\tst->stats.attempts++;\n+\n+\tstrbuf_reset(&lock_file_name);\n+\tstrbuf_addstr(&lock_file_name, st->list_file);\n+\tstrbuf_addstr(&lock_file_name, \".lock\");\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\t/* Don't want to write to the lock for now.  */\n+\tclose(lock_file_fd);\n+\tlock_file_fd = 0;\n+\n+\thave_lock = 1;\n+\terr = stack_uptodate(st);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct strbuf subtab_file_name = STRBUF_INIT;\n+\t\tstruct strbuf subtab_lock = STRBUF_INIT;\n+\t\tint sublock_file_fd = -1;\n+\n+\t\tstack_filename(&subtab_file_name, st,\n+\t\t\t       reader_name(st->readers[i]));\n+\n+\t\tstrbuf_reset(&subtab_lock);\n+\t\tstrbuf_addbuf(&subtab_lock, &subtab_file_name);\n+\t\tstrbuf_addstr(&subtab_lock, \".lock\");\n+\n+\t\tsublock_file_fd = open(subtab_lock.buf,\n+\t\t\t\t       O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\t\tif (sublock_file_fd > 0) {\n+\t\t\tclose(sublock_file_fd);\n+\t\t} else if (sublock_file_fd < 0) {\n+\t\t\tif (errno == EEXIST) {\n+\t\t\t\terr = 1;\n+\t\t\t} else {\n+\t\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\t}\n+\t\t}\n+\n+\t\tsubtable_locks[j] = subtab_lock.buf;\n+\t\tdelete_on_success[j] = subtab_file_name.buf;\n+\t\tj++;\n+\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\terr = unlink(lock_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\thave_lock = 0;\n+\n+\terr = stack_compact_locked(st, first, last, &temp_tab_file_name,\n+\t\t\t\t   expiry);\n+\t/* Compaction + tombstones can create an empty table out of non-empty\n+\t * tables. */\n+\tis_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);\n+\tif (is_empty_table) {\n+\t\terr = 0;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\thave_lock = 1;\n+\n+\tformat_name(&new_table_name, st->readers[first]->min_update_index,\n+\t\t    st->readers[last]->max_update_index);\n+\tstrbuf_addstr(&new_table_name, \".ref\");\n+\n+\tstack_filename(&new_table_path, st, new_table_name.buf);\n+\n+\tif (!is_empty_table) {\n+\t\t/* retry? */\n+\t\terr = rename(temp_tab_file_name.buf, new_table_path.buf);\n+\t\tif (err < 0) {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < first; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tif (!is_empty_table) {\n+\t\tstrbuf_addbuf(&ref_list_contents, &new_table_name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tfor (i = last + 1; i < st->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\n+\terr = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\terr = close(lock_file_fd);\n+\tlock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(lock_file_name.buf, st->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\thave_lock = 0;\n+\n+\t/* Reload the stack before deleting. On windows, we can only delete the\n+\t   files after we closed them.\n+\t*/\n+\terr = reftable_stack_reload_maybe_reuse(st, first < last);\n+\n+\tlistp = delete_on_success;\n+\twhile (*listp) {\n+\t\tif (strcmp(*listp, new_table_path.buf)) {\n+\t\t\tunlink(*listp);\n+\t\t}\n+\t\tlistp++;\n+\t}\n+\n+done:\n+\tfree_names(delete_on_success);\n+\n+\tlistp = subtable_locks;\n+\twhile (*listp) {\n+\t\tunlink(*listp);\n+\t\tlistp++;\n+\t}\n+\tfree_names(subtable_locks);\n+\tif (lock_file_fd > 0) {\n+\t\tclose(lock_file_fd);\n+\t\tlock_file_fd = 0;\n+\t}\n+\tif (have_lock) {\n+\t\tunlink(lock_file_name.buf);\n+\t}\n+\tstrbuf_release(&new_table_name);\n+\tstrbuf_release(&new_table_path);\n+\tstrbuf_release(&ref_list_contents);\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&lock_file_name);\n+\treturn err;\n+}\n+\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\treturn stack_compact_range(st, 0, st->merged->stack_len - 1, config);\n+}\n+\n+static int stack_compact_range_stats(struct reftable_stack *st, int first,\n+\t\t\t\t     int last,\n+\t\t\t\t     struct reftable_log_expiry_config *config)\n+{\n+\tint err = stack_compact_range(st, first, last, config);\n+\tif (err > 0) {\n+\t\tst->stats.failures++;\n+\t}\n+\treturn err;\n+}\n+\n+static int segment_size(struct segment *s)\n+{\n+\treturn s->end - s->start;\n+}\n+\n+int fastlog2(uint64_t sz)\n+{\n+\tint l = 0;\n+\tif (sz == 0)\n+\t\treturn 0;\n+\tfor (; sz; sz /= 2) {\n+\t\tl++;\n+\t}\n+\treturn l - 1;\n+}\n+\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)\n+{\n+\tstruct segment *segs = reftable_calloc(sizeof(struct segment) * n);\n+\tint next = 0;\n+\tstruct segment cur = { 0 };\n+\tint i = 0;\n+\n+\tif (n == 0) {\n+\t\t*seglen = 0;\n+\t\treturn segs;\n+\t}\n+\tfor (i = 0; i < n; i++) {\n+\t\tint log = fastlog2(sizes[i]);\n+\t\tif (cur.log != log && cur.bytes > 0) {\n+\t\t\tstruct segment fresh = {\n+\t\t\t\t.start = i,\n+\t\t\t};\n+\n+\t\t\tsegs[next++] = cur;\n+\t\t\tcur = fresh;\n+\t\t}\n+\n+\t\tcur.log = log;\n+\t\tcur.end = i + 1;\n+\t\tcur.bytes += sizes[i];\n+\t}\n+\tsegs[next++] = cur;\n+\t*seglen = next;\n+\treturn segs;\n+}\n+\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, sizes, n);\n+\tstruct segment min_seg = {\n+\t\t.log = 64,\n+\t};\n+\tint i = 0;\n+\tfor (i = 0; i < seglen; i++) {\n+\t\tif (segment_size(&segs[i]) == 1) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (segs[i].log < min_seg.log) {\n+\t\t\tmin_seg = segs[i];\n+\t\t}\n+\t}\n+\n+\twhile (min_seg.start > 0) {\n+\t\tint prev = min_seg.start - 1;\n+\t\tif (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmin_seg.start = prev;\n+\t\tmin_seg.bytes += sizes[prev];\n+\t}\n+\n+\treftable_free(segs);\n+\treturn min_seg;\n+}\n+\n+static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes =\n+\t\treftable_calloc(sizeof(uint64_t) * st->merged->stack_len);\n+\tint version = (st->config.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2;\n+\tint overhead = header_size(version) - 1;\n+\tint i = 0;\n+\tfor (i = 0; i < st->merged->stack_len; i++) {\n+\t\tsizes[i] = st->readers[i]->size - overhead;\n+\t}\n+\treturn sizes;\n+}\n+\n+int reftable_stack_auto_compact(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes = stack_table_sizes_for_compaction(st);\n+\tstruct segment seg =\n+\t\tsuggest_compaction_segment(sizes, st->merged->stack_len);\n+\treftable_free(sizes);\n+\tif (segment_size(&seg) > 0)\n+\t\treturn stack_compact_range_stats(st, seg.start, seg.end - 1,\n+\t\t\t\t\t\t NULL);\n+\n+\treturn 0;\n+}\n+\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st)\n+{\n+\treturn &st->stats;\n+}\n+\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_table tab = { NULL };\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\treturn reftable_table_read_ref(&tab, refname, ref);\n+}\n+\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_merged_table *mt = reftable_stack_merged_table(st);\n+\tint err = reftable_merged_table_seek_log(mt, &it, refname);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_log(&it, log);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(log->refname, refname) ||\n+\t    reftable_log_record_is_deletion(log)) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tif (err) {\n+\t\treftable_log_record_release(log);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name)\n+{\n+\tint err = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct reftable_ref_record *refs = NULL;\n+\tstruct reftable_iterator it = { NULL };\n+\tint cap = 0;\n+\tint len = 0;\n+\tint i = 0;\n+\n+\tif (st->config.skip_name_check)\n+\t\treturn 0;\n+\n+\terr = reftable_block_source_from_file(&src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tif (err > 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (len >= cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\trefs = reftable_realloc(refs, cap * sizeof(refs[0]));\n+\t\t}\n+\n+\t\trefs[len++] = ref;\n+\t}\n+\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\n+\terr = validate_ref_record_addition(tab, refs, len);\n+\n+done:\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t}\n+\n+\tfree(refs);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\treturn err;\n+}\n+\n+static int is_table_name(const char *s)\n+{\n+\tconst char *dot = strrchr(s, '.');\n+\treturn dot && !strcmp(dot, \".ref\");\n+}\n+\n+static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max,\n+\t\t\t\t     const char *name)\n+{\n+\tint err = 0;\n+\tuint64_t update_idx = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct strbuf table_path = STRBUF_INIT;\n+\tstack_filename(&table_path, st, name);\n+\n+\terr = reftable_block_source_from_file(&src, table_path.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tupdate_idx = reftable_reader_max_update_index(rd);\n+\treftable_reader_free(rd);\n+\n+\tif (update_idx <= max) {\n+\t\tunlink(table_path.buf);\n+\t}\n+done:\n+\tstrbuf_release(&table_path);\n+}\n+\n+static int reftable_stack_clean_locked(struct reftable_stack *st)\n+{\n+\tuint64_t max = reftable_merged_table_max_update_index(\n+\t\treftable_stack_merged_table(st));\n+\tDIR *dir = opendir(st->reftable_dir);\n+\tstruct dirent *d = NULL;\n+\tif (!dir) {\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tint i = 0;\n+\t\tint found = 0;\n+\t\tif (!is_table_name(d->d_name))\n+\t\t\tcontinue;\n+\n+\t\tfor (i = 0; !found && i < st->readers_len; i++) {\n+\t\t\tfound = !strcmp(reader_name(st->readers[i]), d->d_name);\n+\t\t}\n+\t\tif (found)\n+\t\t\tcontinue;\n+\n+\t\tremove_maybe_stale_table(st, max, d->d_name);\n+\t}\n+\n+\tclosedir(dir);\n+\treturn 0;\n+}\n+\n+int reftable_stack_clean(struct reftable_stack *st)\n+{\n+\tstruct reftable_addition *add = NULL;\n+\tint err = reftable_stack_new_addition(&add, st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_reload(st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_clean_locked(st);\n+\n+done:\n+\treftable_addition_destroy(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { .hash_id = hash_id };\n+\tstruct reftable_merged_table *merged = NULL;\n+\tstruct reftable_table table = { NULL };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tmerged = reftable_stack_merged_table(stack);\n+\treftable_table_from_merged_table(&table, merged);\n+\terr = reftable_table_print(&table);\n+done:\n+\tif (stack)\n+\t\treftable_stack_destroy(stack);\n+\treturn err;\n+}\ndiff --git a/reftable/stack.h b/reftable/stack.h\nnew file mode 100644\nindex 00000000000..f57005846e5\n--- /dev/null\n+++ b/reftable/stack.h\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef STACK_H\n+#define STACK_H\n+\n+#include \"system.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-stack.h\"\n+\n+struct reftable_stack {\n+\tchar *list_file;\n+\tchar *reftable_dir;\n+\tint disable_auto_compact;\n+\n+\tstruct reftable_write_options config;\n+\n+\tstruct reftable_reader **readers;\n+\tsize_t readers_len;\n+\tstruct reftable_merged_table *merged;\n+\tstruct reftable_compaction_stats stats;\n+};\n+\n+int read_lines(const char *filename, char ***lines);\n+\n+struct segment {\n+\tint start, end;\n+\tint log;\n+\tuint64_t bytes;\n+};\n+\n+int fastlog2(uint64_t sz);\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n);\n+\n+#endif\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nnew file mode 100644\nindex 00000000000..7a4641ab601\n--- /dev/null\n+++ b/reftable/stack_test.c\n@@ -0,0 +1,949 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+\n+#include \"reftable-reader.h\"\n+#include \"merged.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+#include <sys/types.h>\n+#include <dirent.h>\n+\n+static void clear_dir(const char *dirname)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, dirname);\n+\tremove_dir_recursively(&path, 0);\n+\tstrbuf_release(&path);\n+}\n+\n+static int count_dir_entries(const char *dirname)\n+{\n+\tDIR *dir = opendir(dirname);\n+\tint len = 0;\n+\tstruct dirent *d;\n+\tif (dir == NULL)\n+\t\treturn 0;\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tif (!strcmp(d->d_name, \"..\") || !strcmp(d->d_name, \".\"))\n+\t\t\tcontinue;\n+\t\tlen++;\n+\t}\n+\tclosedir(dir);\n+\treturn len;\n+}\n+\n+// Work linenumber into the tempdir, so we can see which tests forget to\n+// cleanup.\n+static char *get_tmp_template(int linenumber)\n+{\n+\tconst char *tmp = getenv(\"TMPDIR\");\n+\tstatic char template[1024];\n+\tsnprintf(template, sizeof(template) - 1, \"%s/stack_test-%d.XXXXXX\",\n+\t\t tmp ? tmp : \"/tmp\", linenumber);\n+\treturn template;\n+}\n+\n+static char *get_tmp_dir(int linenumber)\n+{\n+\tchar *dir = get_tmp_template(linenumber);\n+\tEXPECT(mkdtemp(dir));\n+\treturn dir;\n+}\n+\n+static void test_read_file(void)\n+{\n+\tchar *fn = get_tmp_template(__LINE__);\n+\tint fd = mkstemp(fn);\n+\tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n+\tint n, err;\n+\tchar **names = NULL;\n+\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tint i = 0;\n+\n+\tEXPECT(fd > 0);\n+\tn = write(fd, out, strlen(out));\n+\tEXPECT(n == strlen(out));\n+\terr = close(fd);\n+\tEXPECT(err >= 0);\n+\n+\terr = read_lines(fn, &names);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; names[i]; i++) {\n+\t\tEXPECT(0 == strcmp(want[i], names[i]));\n+\t}\n+\tfree_names(names);\n+\tremove(fn);\n+}\n+\n+static void test_parse_names(void)\n+{\n+\tchar buf[] = \"line\\n\";\n+\tchar **names = NULL;\n+\tparse_names(buf, strlen(buf), &names);\n+\n+\tEXPECT(NULL != names[0]);\n+\tEXPECT(0 == strcmp(names[0], \"line\"));\n+\tEXPECT(NULL == names[1]);\n+\tfree_names(names);\n+}\n+\n+static void test_names_equal(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tchar *c[] = { \"a\", \"b\", NULL };\n+\n+\tEXPECT(names_equal(a, a));\n+\tEXPECT(!names_equal(a, b));\n+\tEXPECT(!names_equal(a, c));\n+}\n+\n+static int write_test_ref(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct reftable_ref_record *ref = arg;\n+\treftable_writer_set_limits(wr, ref->update_index, ref->update_index);\n+\treturn reftable_writer_add_ref(wr, ref);\n+}\n+\n+struct write_log_arg {\n+\tstruct reftable_log_record *log;\n+\tuint64_t update_index;\n+};\n+\n+static int write_test_log(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct write_log_arg *wla = arg;\n+\n+\treftable_writer_set_limits(wr, wla->update_index, wla->update_index);\n+\treturn reftable_writer_add_log(wr, wla->log);\n+}\n+\n+static void test_reftable_stack_add_one(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\tprintf(\"testing print functionality:\\n\");\n+\terr = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_uptodate(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL;\n+\tstruct reftable_stack *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"branch2\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\n+\t/* simulate multi-process access to the same stack\n+\t   by creating two stacks for the same directory.\n+\t */\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st1, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_LOCK_ERROR);\n+\n+\terr = reftable_stack_reload(st2);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT_ERR(err);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_transaction_api(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_addition *add = NULL;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_new_addition(&add, st);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_add(add, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_commit(add);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(REFTABLE_REF_SYMREF == dest.value_type);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_validate_refname(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint i;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"a/b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tchar *additions[] = { \"a\", \"a/b/c\" };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(additions); i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = additions[i],\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT(err == REFTABLE_NAME_CONFLICT);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static int write_error(struct reftable_writer *wr, void *arg)\n+{\n+\treturn *((int *)arg);\n+}\n+\n+static void test_reftable_stack_update_index_check(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"name1\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"name2\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_lock_failure(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err, i;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {\n+\t\terr = reftable_stack_add(st, &write_error, &i);\n+\t\tEXPECT(err == i);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_add(void)\n+{\n+\tint i = 0;\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t.exact_log_message = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tst->disable_auto_compact = 1;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tset_test_hash(refs[i].value.val1, i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = N + i + 1;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_ref_record dest = { NULL };\n+\n+\t\tint err = reftable_stack_read_ref(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_ref_record_equal(&dest, refs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_ref_record_release(&dest);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_log_record dest = { NULL };\n+\t\tint err = reftable_stack_read_log(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_log_record_equal(&dest, logs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&dest);\n+\t}\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_log_normalize(void)\n+{\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t0,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tuint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };\n+\n+\tstruct reftable_log_record input = { .refname = \"branch\",\n+\t\t\t\t\t     .update_index = 1,\n+\t\t\t\t\t     .value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t\t\t     .value = { .update = {\n+\t\t\t\t\t\t\t\t.new_hash = h1,\n+\t\t\t\t\t\t\t\t.old_hash = h2,\n+\t\t\t\t\t\t\t} } };\n+\tstruct reftable_log_record dest = {\n+\t\t.update_index = 0,\n+\t};\n+\tstruct write_log_arg arg = {\n+\t\t.log = &input,\n+\t\t.update_index = 1,\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tinput.value.update.message = \"one\\ntwo\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tinput.value.update.message = \"one\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n+\n+\tinput.value.update.message = \"two\\n\";\n+\targ.update_index = 2;\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"two\\n\"));\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\treftable_log_record_release(&dest);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_tombstone(void)\n+{\n+\tint i = 0;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\tstruct reftable_ref_record dest = { NULL };\n+\tstruct reftable_log_record log_dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\t/* even entries add the refs, odd entries delete them. */\n+\tfor (i = 0; i < N; i++) {\n+\t\tconst char *buf = \"branch\";\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\tif (i % 2 == 0) {\n+\t\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(refs[i].value.val1, i);\n+\t\t}\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\t/* update_index is part of the key. */\n+\t\tlogs[i].update_index = 42;\n+\t\tif (i % 2 == 0) {\n+\t\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\t\tlogs[i].value.update.new_hash =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t\t\tlogs[i].value.update.email =\n+\t\t\t\txstrdup(\"identity@invalid\");\n+\t\t}\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_log_record_release(&log_dest);\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\treftable_log_record_release(&log_dest);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_hash_id(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"master\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"target\",\n+\t\t.update_index = 1,\n+\t};\n+\tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n+\tstruct reftable_stack *st32 = NULL;\n+\tstruct reftable_write_options cfg_default = { 0 };\n+\tstruct reftable_stack *st_default = NULL;\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* can't read it with the wrong hash ID. */\n+\terr = reftable_new_stack(&st32, dir, cfg32);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\t/* check that we can read it back with default config too. */\n+\terr = reftable_new_stack(&st_default, dir, cfg_default);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st_default, \"master\", &dest);\n+\tEXPECT_ERR(err);\n+\n+\tEXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st_default);\n+\tclear_dir(dir);\n+}\n+\n+static void test_log2(void)\n+{\n+\tEXPECT(1 == fastlog2(3));\n+\tEXPECT(2 == fastlog2(4));\n+\tEXPECT(2 == fastlog2(5));\n+}\n+\n+static void test_sizes_to_segments(void)\n+{\n+\tuint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };\n+\t/* .................0  1  2  3  4  5 */\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(segs[2].log == 3);\n+\tEXPECT(segs[2].start == 5);\n+\tEXPECT(segs[2].end == 6);\n+\n+\tEXPECT(segs[1].log == 2);\n+\tEXPECT(segs[1].start == 2);\n+\tEXPECT(segs[1].end == 5);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_empty(void)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, NULL, 0);\n+\tEXPECT(seglen == 0);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_all_equal(void)\n+{\n+\tuint64_t sizes[] = { 5, 5 };\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(seglen == 1);\n+\tEXPECT(segs[0].start == 0);\n+\tEXPECT(segs[0].end == 2);\n+\treftable_free(segs);\n+}\n+\n+static void test_suggest_compaction_segment(void)\n+{\n+\tuint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };\n+\t/* .................0    1    2  3   4  5  6 */\n+\tstruct segment min =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(min.start == 2);\n+\tEXPECT(min.end == 7);\n+}\n+\n+static void test_suggest_compaction_segment_nothing(void)\n+{\n+\tuint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };\n+\tstruct segment result =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(result.start == result.end);\n+}\n+\n+static void test_reflog_expire(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_log_record logs[20] = { { NULL } };\n+\tint N = ARRAY_SIZE(logs) - 1;\n+\tint i = 0;\n+\tint err;\n+\tstruct reftable_log_expiry_config expiry = {\n+\t\t.time = 10,\n+\t};\n+\tstruct reftable_log_record log = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = i;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.time = i;\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[9].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[11].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\texpiry.min_update_index = 15;\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[14].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[16].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i <= N; i++) {\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+\treftable_log_record_release(&log);\n+}\n+\n+static int write_nothing(struct reftable_writer *wr, void *arg)\n+{\n+\treftable_writer_set_limits(wr, 1, 1);\n+\treturn 0;\n+}\n+\n+static void test_empty_add(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_stack *st2 = NULL;\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_nothing, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tclear_dir(dir);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st2);\n+}\n+\n+static void test_reftable_stack_auto_compaction(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 100;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\n+\t\tEXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));\n+\t}\n+\n+\tEXPECT(reftable_stack_compaction_stats(st)->entries_written <\n+\t       (uint64_t)(N * fastlog2(N)));\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\tclear_dir(dir);\n+}\n+\n+static void unclean_stack_close(struct reftable_stack *st)\n+{\n+\t// break abstraction boundary to simulate unclean shutdown.\n+\tint i = 0;\n+\tfor (; i < st->readers_len; i++) {\n+\t\treftable_reader_free(st->readers[i]);\n+\t}\n+\tst->readers_len = 0;\n+\tFREE_AND_NULL(st->readers);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent_clean(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tunclean_stack_close(st1);\n+\tunclean_stack_close(st2);\n+\n+\terr = reftable_new_stack(&st3, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_clean(st3);\n+\tEXPECT_ERR(err);\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\treftable_stack_destroy(st3);\n+\n+\tclear_dir(dir);\n+}\n+\n+int stack_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_empty_add);\n+\tRUN_TEST(test_log2);\n+\tRUN_TEST(test_names_equal);\n+\tRUN_TEST(test_parse_names);\n+\tRUN_TEST(test_read_file);\n+\tRUN_TEST(test_reflog_expire);\n+\tRUN_TEST(test_reftable_stack_add);\n+\tRUN_TEST(test_reftable_stack_add_one);\n+\tRUN_TEST(test_reftable_stack_auto_compaction);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent_clean);\n+\tRUN_TEST(test_reftable_stack_hash_id);\n+\tRUN_TEST(test_reftable_stack_lock_failure);\n+\tRUN_TEST(test_reftable_stack_log_normalize);\n+\tRUN_TEST(test_reftable_stack_tombstone);\n+\tRUN_TEST(test_reftable_stack_transaction_api);\n+\tRUN_TEST(test_reftable_stack_update_index_check);\n+\tRUN_TEST(test_reftable_stack_uptodate);\n+\tRUN_TEST(test_reftable_stack_validate_refname);\n+\tRUN_TEST(test_sizes_to_segments);\n+\tRUN_TEST(test_sizes_to_segments_all_equal);\n+\tRUN_TEST(test_sizes_to_segments_empty);\n+\tRUN_TEST(test_suggest_compaction_segment);\n+\tRUN_TEST(test_suggest_compaction_segment_nothing);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c8db6852c35..996da85f7b5 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -10,6 +10,7 @@ int cmd__reftable(int argc, const char **argv)\n \trecord_test_main(argc, argv);\n \trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n+\tstack_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"435311","messageId":"a9bebe45bde34ef6c447f9dfa399c0a625ec5ffd.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 18/19] reftable: add dump utility","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:43Z","receivedAt":"2021-09-09T18:49:27Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nprovide a command-line utility for inspecting individual tables, and\ninspecting a complete ref database\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/dump.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 107 insertions(+)\n create mode 100644 reftable/dump.c\n\ndiff --git a/reftable/dump.c b/reftable/dump.c\nnew file mode 100644\nindex 00000000000..155953d1b82\n--- /dev/null\n+++ b/reftable/dump.c\n@@ -0,0 +1,107 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"git-compat-util.h\"\n+#include \"hash.h\"\n+\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+#include \"reftable-stack.h\"\n+#include \"reftable-generic.h\"\n+\n+#include <stddef.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <unistd.h>\n+#include <string.h>\n+\n+static int compact_stack(const char *stackdir)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { 0 };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_stack_compact_all(stack, NULL);\n+\tif (err < 0)\n+\t\tgoto done;\n+done:\n+\tif (stack) {\n+\t\treftable_stack_destroy(stack);\n+\t}\n+\treturn err;\n+}\n+\n+static void print_help(void)\n+{\n+\tprintf(\"usage: dump [-cst] arg\\n\\n\"\n+\t       \"options: \\n\"\n+\t       \"  -c compact\\n\"\n+\t       \"  -t dump table\\n\"\n+\t       \"  -s dump stack\\n\"\n+\t       \"  -6 sha256 hash format\\n\"\n+\t       \"  -h this help\\n\"\n+\t       \"\\n\");\n+}\n+\n+int reftable_dump_main(int argc, char *const *argv)\n+{\n+\tint err = 0;\n+\tint opt_dump_table = 0;\n+\tint opt_dump_stack = 0;\n+\tint opt_compact = 0;\n+\tuint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;\n+\tconst char *arg = NULL, *argv0 = argv[0];\n+\n+\tfor (; argc > 1; argv++, argc--)\n+\t\tif (*argv[1] != '-')\n+\t\t\tbreak;\n+\t\telse if (!strcmp(\"-t\", argv[1]))\n+\t\t\topt_dump_table = 1;\n+\t\telse if (!strcmp(\"-6\", argv[1]))\n+\t\t\topt_hash_id = GIT_SHA256_FORMAT_ID;\n+\t\telse if (!strcmp(\"-s\", argv[1]))\n+\t\t\topt_dump_stack = 1;\n+\t\telse if (!strcmp(\"-c\", argv[1]))\n+\t\t\topt_compact = 1;\n+\t\telse if (!strcmp(\"-?\", argv[1]) || !strcmp(\"-h\", argv[1])) {\n+\t\t\tprint_help();\n+\t\t\treturn 2;\n+\t\t}\n+\n+\tif (argc != 2) {\n+\t\tfprintf(stderr, \"need argument\\n\");\n+\t\tprint_help();\n+\t\treturn 2;\n+\t}\n+\n+\targ = argv[1];\n+\n+\tif (opt_dump_table) {\n+\t\terr = reftable_reader_print_file(arg);\n+\t} else if (opt_dump_stack) {\n+\t\terr = reftable_stack_print_directory(arg, opt_hash_id);\n+\t} else if (opt_compact) {\n+\t\terr = compact_stack(arg);\n+\t}\n+\n+\tif (err < 0) {\n+\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv0, arg,\n+\t\t\treftable_error_str(err));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n-- \ngitgitgadget\n\n"},{"id":"435313","messageId":"104fbc7502ffe3258d74bef240c7e2f5d91219b4.1631213265.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v2 19/19] Add \"test-tool dump-reftable\" command.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-09T18:47:44Z","receivedAt":"2021-09-09T18:49:35Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis command dumps individual tables or a stack of of tables.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 | 1 +\n t/helper/test-reftable.c | 5 +++++\n t/helper/test-tool.c     | 1 +\n t/helper/test-tool.h     | 1 +\n 4 files changed, 8 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 619b1b15ce0..8ec68a70405 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2474,6 +2474,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/dump.o\n REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 996da85f7b5..26b03d7b789 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -14,3 +14,8 @@ int cmd__reftable(int argc, const char **argv)\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n+\n+int cmd__dump_reftable(int argc, const char **argv)\n+{\n+\treturn reftable_dump_main(argc, (char *const *)argv);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f7c888ffda7..338a57b104d 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -61,6 +61,7 @@ static struct test_cmd cmds[] = {\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n \t{ \"reftable\", cmd__reftable },\n+\t{ \"dump-reftable\", cmd__dump_reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 25f77469146..48cee1f4a2d 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -19,6 +19,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);\n int cmd__dump_fsmonitor(int argc, const char **argv);\n int cmd__dump_split_index(int argc, const char **argv);\n int cmd__dump_untracked_cache(int argc, const char **argv);\n+int cmd__dump_reftable(int argc, const char **argv);\n int cmd__example_decorate(int argc, const char **argv);\n int cmd__fast_rebase(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n-- \ngitgitgadget\n"},{"id":"435328","messageId":"xmqqczphy0t0.fsf@gitster.g","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-09T20:02:51Z","receivedAt":"2021-09-09T20:03:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Han-Wen Nienhuys via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> The reftable format is described in Documentation/technical/reftable.txt.\n>\n> This is a fully reentrant implementation of reading and writing the reftable\n> file format, and should be suitable for embedding in libgit2 too. It does\n> not hook the code up to git to function as a ref storage backend yet.\n>\n> v2:\n>\n>  * fold in OpenBSD fixes by Carlo Belón.\n\nThanks, both.  Will replace.\n"},{"id":"435331","messageId":"xmqq7dfpxzfw.fsf@gitster.g","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-09T20:32:19Z","receivedAt":"2021-09-09T20:32:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Han-Wen Nienhuys via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> The reftable format is described in Documentation/technical/reftable.txt.\n>\n> This is a fully reentrant implementation of reading and writing the reftable\n> file format, and should be suitable for embedding in libgit2 too. It does\n> not hook the code up to git to function as a ref storage backend yet.\n\nNot a question for Han-Wen, but I am wondering how much style and\nother consistency guidelines we have in our C code to the files in\nthis directory.  \n\nI am guessing that rules like \"no decl after statement\" and \"no decl\nin the set-up part of the for loop control\" (i.e. \"for (int i = 0;\n...\"  is a no-no) should apply equally to this code, but it might be\nOK to deviate from rules that are only meant to help human readers [*]\nwithout affecting compilation.\n\nOpinions?\n\n\n[Footnote]\n\n * multi-line comment style.\n\n * asterisk sticks to identifier not to type\n\n * not enclosing a single-statement block inside {}\n\n * empty statement looks like\n\n\t; /* empty */\n"},{"id":"435701","messageId":"CAFQ2z_Pa+KmCYV224XwMXO1iFCNA=PXj5iKaQU3LYGYTK_+qsw@mail.gmail.com","threadId":"56405","inReplyTo":"xmqq7dfpxzfw.fsf@gitster.g","subject":"Re: [PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Han-Wen Nienhuys","fromEmail":"hanwen@google.com","sentAt":"2021-09-13T10:14:34Z","receivedAt":"2021-09-13T10:14:48Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"On Thu, Sep 9, 2021 at 10:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Han-Wen Nienhuys via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > The reftable format is described in Documentation/technical/reftable.txt.\n> >\n> > This is a fully reentrant implementation of reading and writing the reftable\n> > file format, and should be suitable for embedding in libgit2 too. It does\n> > not hook the code up to git to function as a ref storage backend yet.\n>\n> Not a question for Han-Wen, but I am wondering how much style and\n> other consistency guidelines we have in our C code to the files in\n> this directory.\n\nI am Han-Wen, but I'm not sure what you are saying here.\n\n> I am guessing that rules like \"no decl after statement\" and \"no decl\n> in the set-up part of the for loop control\" (i.e. \"for (int i = 0;\n> ...\"  is a no-no) should apply equally to this code, but it might be\n> OK to deviate from rules that are only meant to help human readers [*]\n> without affecting compilation.\n>\n> Opinions?\n\nThe code has a different style because I wrote it separately from Git.\nI'm not wedded to its current style, and most styling can easily be\nchanged. If you have specific things that should be addressed, let me\nknow.\n\n-- \nHan-Wen Nienhuys - Google Munich\nI work 80%. Don't expect answers from me on Fridays.\n--\n\nGoogle Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich\n\nRegistergericht und -nummer: Hamburg, HRB 86891\n\nSitz der Gesellschaft: Hamburg\n\nGeschäftsführer: Paul Manicle, Halimah DeLaine Prado\n"},{"id":"435766","messageId":"xmqqee9sfhuu.fsf@gitster.g","threadId":"56405","inReplyTo":"CAFQ2z_Pa+KmCYV224XwMXO1iFCNA=PXj5iKaQU3LYGYTK_+qsw@mail.gmail.com","subject":"Re: [PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-13T18:30:49Z","receivedAt":"2021-09-13T18:31:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Han-Wen Nienhuys <hanwen@google.com> writes:\n\n> On Thu, Sep 9, 2021 at 10:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> \"Han-Wen Nienhuys via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>> > The reftable format is described in Documentation/technical/reftable.txt.\n>> >\n>> > This is a fully reentrant implementation of reading and writing the reftable\n>> > file format, and should be suitable for embedding in libgit2 too. It does\n>> > not hook the code up to git to function as a ref storage backend yet.\n>>\n>> Not a question for Han-Wen, but I am wondering how much style and\n>> other consistency guidelines we have in our C code to the files in\n>> this directory.\n>\n> I am Han-Wen, but I'm not sure what you are saying here.\n\nSorry, the sentence is unreadable because I missed a verb above\n(insert \"should apply\" between \"code\" and \"to\").  \n\nI was asking for opinions on how we should treat this piece of code.\nWe loosen \"style guidelines\" on borrowed code that is maintained\nelsewhere to ease re-importing, but code we maintain in-tree are\nsubject to our style guide.  I am not sure how this part that is\nmeant to be reusable in other projects like libgit2 should be\ntreated.\n\n>> I am guessing that rules like \"no decl after statement\" and \"no decl\n>> in the set-up part of the for loop control\" (i.e. \"for (int i = 0;\n>> ...\"  is a no-no) should apply equally to this code, but it might be\n>> OK to deviate from rules that are only meant to help human readers [*]\n>> without affecting compilation.\n>>\n>> Opinions?\n>\n> The code has a different style because I wrote it separately from Git.\n> I'm not wedded to its current style, and most styling can easily be\n> changed. If you have specific things that should be addressed, let me\n> know.\n\nThe question was for other reviewers to help us come up with what\nthe \"specific things\" ought to be.  I saw style differences around\ncomments and code formatting (everything I listed in the footnote,\nplus, // comment which I forgot to mention) which may or may not\nturn out to be part of that \"specific things\", because they do not\nbreak compilation.\n"},{"id":"435777","messageId":"CAPUEsphPvtXymQWExXqJqn6U1TiWx=u483oNODPZuL5pRubveg@mail.gmail.com","threadId":"56405","inReplyTo":"xmqqee9sfhuu.fsf@gitster.g","subject":"Re: [PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Carlo Arenas","fromEmail":"carenas@gmail.com","sentAt":"2021-09-13T19:29:25Z","receivedAt":"2021-09-13T19:29:39Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"On Mon, Sep 13, 2021 at 11:30 AM Junio C Hamano <gitster@pobox.com> wrote:\n> Han-Wen Nienhuys <hanwen@google.com> writes:\n> > The code has a different style because I wrote it separately from Git.\n> > I'm not wedded to its current style, and most styling can easily be\n> > changed. If you have specific things that should be addressed, let me\n> > know.\n>\n> The question was for other reviewers to help us come up with what\n> the \"specific things\" ought to be.  I saw style differences around\n> comments and code formatting (everything I listed in the footnote,\n> plus, // comment which I forgot to mention) which may or may not\n> turn out to be part of that \"specific things\", because they do not\n> break compilation.\n\nthey will be flagged by the compiler in pedantic mode when in gnu89 mode though\n\n  reftable/stack_test.c:49:1: warning: C++ style comments are not\nallowed in ISO C\n90\n     49 | // Work linenumber into the tempdir, so we can see which\ntests forget to\n        | ^\n  reftable/stack_test.c:49:1: note: (this will be reported only once\nper input file)\n\nand are probably an easy thing to fix\n\nCarlo\n"},{"id":"435788","messageId":"xmqqtuiodxjj.fsf@gitster.g","threadId":"56405","inReplyTo":"CAPUEsphPvtXymQWExXqJqn6U1TiWx=u483oNODPZuL5pRubveg@mail.gmail.com","subject":"Re: [PATCH v2 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-13T20:34:56Z","receivedAt":"2021-09-13T20:34:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carlo Arenas <carenas@gmail.com> writes:\n\n> On Mon, Sep 13, 2021 at 11:30 AM Junio C Hamano <gitster@pobox.com> wrote:\n>> Han-Wen Nienhuys <hanwen@google.com> writes:\n>> > The code has a different style because I wrote it separately from Git.\n>> > I'm not wedded to its current style, and most styling can easily be\n>> > changed. If you have specific things that should be addressed, let me\n>> > know.\n>>\n>> The question was for other reviewers to help us come up with what\n>> the \"specific things\" ought to be.  I saw style differences around\n>> comments and code formatting (everything I listed in the footnote,\n>> plus, // comment which I forgot to mention) which may or may not\n>> turn out to be part of that \"specific things\", because they do not\n>> break compilation.\n>\n> they will be flagged by the compiler in pedantic mode when in gnu89 mode though\n>\n>   reftable/stack_test.c:49:1: warning: C++ style comments are not\n> allowed in ISO C\n> 90\n>      49 | // Work linenumber into the tempdir, so we can see which\n> tests forget to\n>         | ^\n>   reftable/stack_test.c:49:1: note: (this will be reported only once\n> per input file)\n>\n> and are probably an easy thing to fix\n\nI guess I still wasn't clear enough, then.  Of course, there is no\nquestion that we do not tolerate compilation-breaking deviation from\nour coding guidelines.  The \"loosening\" exception I was alluding to\nwere those rules that do not break compilation but help human readers\ndue to consistent writing.  I do not think\n\n    /* this comment\n     * goes against our house style */\n    int* a, b;\n\nwould be flagged by the gnu89 compiler, for example.\n\n"},{"id":"435965","messageId":"CAPUEspiY=V3y10nc-xUpv7AXwtTv1e1jyRC6FvRrcS3H_ASNnw@mail.gmail.com","threadId":"56405","inReplyTo":"e595dbc6c843fdb01c333fd20031d59716804b88.1631213265.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/19] Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Carlo Arenas","fromEmail":"carenas@gmail.com","sentAt":"2021-09-15T07:34:44Z","receivedAt":"2021-09-15T07:34:58Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"On Thu, Sep 9, 2021 at 11:47 AM Han-Wen Nienhuys via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> diff --git a/config.mak.uname b/config.mak.uname\n> index 76516aaa9a5..9beacda4434 100644\n> --- a/config.mak.uname\n> +++ b/config.mak.uname\n> @@ -258,6 +258,11 @@ ifeq ($(uname_S),FreeBSD)\n>         FILENO_IS_A_MACRO = UnfortunatelyYes\n>  endif\n>  ifeq ($(uname_S),OpenBSD)\n> +       NO_UNCOMPRESS2 = YesPlease\n\nThis first line is no longer needed and is counter productive\nThe logic in the next 4 sets this variable correctly, otherwise\n\n> +       # Versions < 7.0 need compatibility layer\n> +       ifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n> +               NO_UNCOMPRESS2 = UnfortunatelyYes\n> +       endif\n>         NO_STRCASESTR = YesPlease\n>         NO_MEMMEM = YesPlease\n>         USE_ST_TIMESPEC = YesPlease\n\nCarlo\n"},{"id":"436943","messageId":"87wnn62nhp.fsf@evledraar.gmail.com","threadId":"56405","inReplyTo":"97f7ee04886d573f6b0f2e0d54a853cf016e7d74.1631213265.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 08/19] reftable: reading/writing blocks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-09-24T11:52:28Z","receivedAt":"2021-09-24T11:59:19Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Sep 09 2021, Han-Wen Nienhuys via GitGitGadget wrote:\n\n> From: Han-Wen Nienhuys <hanwen@google.com>\n>\n> The reftable format is structured as a sequence of block. Within a block,\n> records are prefix compressed, with an index of offsets for fully expand keys to\n> enable binary search within blocks.\n> [...]\n> +\tif (typ == BLOCK_TYPE_LOG) {\n> +\t\tint block_header_skip = 4 + header_off;\n> +\t\tuLongf dst_len = sz - block_header_skip; /* total size of dest\n> +\t\t\t\t\t\t\t    buffer. */\n> +\t\tuLongf src_len = block->len - block_header_skip;\n> +\t\t/* Log blocks specify the *uncompressed* size in their header.\n> +\t\t */\n> +\t\tuint8_t *uncompressed = reftable_malloc(sz);\n> +\n> +\t\t/* Copy over the block header verbatim. It's not compressed. */\n> +\t\tmemcpy(uncompressed, block->data, block_header_skip);\n> +\n> +\t\t/* Uncompress */\n> +\t\tif (Z_OK !=\n> +\t\t    uncompress2(uncompressed + block_header_skip, &dst_len,\n> +\t\t\t\tblock->data + block_header_skip, &src_len)) {\n> +\t\t\treftable_free(uncompressed);\n> +\t\t\treturn REFTABLE_ZLIB_ERROR;\n> +\t\t}\n> +\n> +\t\tif (dst_len + block_header_skip != sz)\n> +\t\t\treturn REFTABLE_FORMAT_ERROR;\n> +\n> +\t\t/* We're done with the input data. */\n> +\t\treftable_block_done(block);\n> +\t\tblock->data = uncompressed;\n> +\t\tblock->len = sz;\n> +\t\tblock->source = malloc_block_source();\n> +\t\tfull_block_size = src_len + block_header_skip;\n\nI haven't tried to re-implement this, but in 07/19 we're adding an\nuncompress2() fallaback function, and here's the only place where it'll\nget used.\n\nLooking at the uncompress2() implementation isn't this a rather trivial\nwrapper for something we'd get from git.git's zlib.c, i.e. you seem to\njust need the \"avail_in\" here, which we're checking in various other\nparts of the codebase.\n\nI suspect that the reason for this is that this code is trying to keep\nat arms length from the rest of git.git, but if so some ifdef here if\nit's compiling in git.git seems better than having a compat interface in\ngit.git just for doing something its zlib.c API provides.\n\nAnyway, re Junio's ask if this is ready for \"next\" in the latest What's\nCooking I don't think this should be a blocker, just wondering if\nanyone's interested in some post-cleanup at some point, and whether I'm\nmissing something about uncompress2() v.s. zlib.c.\n"},{"id":"437307","messageId":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v2.git.git.1631213264.gitgitgadget@gmail.com","subject":"[PATCH v3 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:09:57Z","receivedAt":"2021-09-28T15:10:26Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"The reftable format is described in Documentation/technical/reftable.txt.\n\nThis is a fully reentrant implementation of reading and writing the reftable\nfile format, and should be suitable for embedding in libgit2 too. It does\nnot hook the code up to git to function as a ref storage backend yet.\n\nv2:\n\n * fold in OpenBSD fixes by Carlo Belón.\n\nHan-Wen Nienhuys (19):\n  hash.h: provide constants for the hash IDs\n  reftable: RFC: add LICENSE\n  reftable: add error related functionality\n  reftable: utility functions\n  reftable: add blocksource, an abstraction for random access reads\n  reftable: (de)serialization for the polymorphic record type.\n  Provide zlib's uncompress2 from compat/zlib-compat.c\n  reftable: reading/writing blocks\n  reftable: a generic binary tree implementation\n  reftable: write reftable files\n  reftable: generic interface to tables\n  reftable: read reftable files\n  reftable: reftable file level tests\n  reftable: add a heap-based priority queue for reftable records\n  reftable: add merged table view\n  reftable: implement refname validation\n  reftable: implement stack, a mutable database of reftable files.\n  reftable: add dump utility\n  Add \"test-tool dump-reftable\" command.\n\n Makefile                                   |   53 +-\n ci/lib.sh                                  |    1 +\n compat/.gitattributes                      |    1 +\n compat/zlib-uncompress2.c                  |   95 ++\n config.mak.uname                           |    4 +\n configure.ac                               |   13 +\n contrib/buildsystems/CMakeLists.txt        |   14 +-\n contrib/buildsystems/Generators/Vcxproj.pm |   11 +-\n hash.h                                     |    6 +\n object-file.c                              |    7 +-\n reftable/LICENSE                           |   31 +\n reftable/basics.c                          |  128 ++\n reftable/basics.h                          |   60 +\n reftable/basics_test.c                     |   98 ++\n reftable/block.c                           |  439 ++++++\n reftable/block.h                           |  127 ++\n reftable/block_test.c                      |  120 ++\n reftable/blocksource.c                     |  148 +++\n reftable/blocksource.h                     |   22 +\n reftable/constants.h                       |   21 +\n reftable/dump.c                            |  107 ++\n reftable/error.c                           |   41 +\n reftable/generic.c                         |  169 +++\n reftable/generic.h                         |   32 +\n reftable/iter.c                            |  194 +++\n reftable/iter.h                            |   69 +\n reftable/merged.c                          |  362 +++++\n reftable/merged.h                          |   35 +\n reftable/merged_test.c                     |  292 ++++\n reftable/pq.c                              |  105 ++\n reftable/pq.h                              |   33 +\n reftable/pq_test.c                         |   82 ++\n reftable/publicbasics.c                    |   58 +\n reftable/reader.c                          |  801 +++++++++++\n reftable/reader.h                          |   66 +\n reftable/readwrite_test.c                  |  652 +++++++++\n reftable/record.c                          | 1212 +++++++++++++++++\n reftable/record.h                          |  139 ++\n reftable/record_test.c                     |  412 ++++++\n reftable/refname.c                         |  209 +++\n reftable/refname.h                         |   29 +\n reftable/refname_test.c                    |  102 ++\n reftable/reftable-blocksource.h            |   49 +\n reftable/reftable-error.h                  |   62 +\n reftable/reftable-generic.h                |   47 +\n reftable/reftable-iterator.h               |   39 +\n reftable/reftable-malloc.h                 |   18 +\n reftable/reftable-merged.h                 |   72 +\n reftable/reftable-reader.h                 |  101 ++\n reftable/reftable-record.h                 |  114 ++\n reftable/reftable-stack.h                  |  128 ++\n reftable/reftable-tests.h                  |   23 +\n reftable/reftable-writer.h                 |  148 +++\n reftable/reftable.c                        |  115 ++\n reftable/stack.c                           | 1396 ++++++++++++++++++++\n reftable/stack.h                           |   41 +\n reftable/stack_test.c                      |  949 +++++++++++++\n reftable/system.h                          |   32 +\n reftable/test_framework.c                  |   23 +\n reftable/test_framework.h                  |   53 +\n reftable/tree.c                            |   63 +\n reftable/tree.h                            |   34 +\n reftable/tree_test.c                       |   61 +\n reftable/writer.c                          |  690 ++++++++++\n reftable/writer.h                          |   50 +\n t/helper/test-reftable.c                   |   21 +\n t/helper/test-tool.c                       |    4 +-\n t/helper/test-tool.h                       |    2 +\n t/t0032-reftable-unittest.sh               |   15 +\n 69 files changed, 10938 insertions(+), 12 deletions(-)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n create mode 100644 reftable/LICENSE\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/dump.c\n create mode 100644 reftable/error.c\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/readwrite_test.c\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n create mode 100644 reftable/reftable-blocksource.h\n create mode 100644 reftable/reftable-error.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-merged.h\n create mode 100644 reftable/reftable-reader.h\n create mode 100644 reftable/reftable-record.h\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/reftable.c\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\n\nbase-commit: ddb1055343948e0d0bc81f8d20245f1ada6430a0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1081%2Fhanwen%2Fjust-library-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1081/hanwen/just-library-v3\nPull-Request: https://github.com/git/git/pull/1081\n\nRange-diff vs v2:\n\n  1:  8534c69cf84 =  1:  b2f8dc32bfc hash.h: provide constants for the hash IDs\n  2:  52968d8e83f =  2:  2597ddfeae1 reftable: RFC: add LICENSE\n  3:  84c8b396df5 =  3:  41b1ff1293f reftable: add error related functionality\n  4:  fa58f3d73e7 =  4:  c03043c5e63 reftable: utility functions\n  5:  e9206532126 =  5:  317a6aaa357 reftable: add blocksource, an abstraction for random access reads\n  6:  651e195fe77 =  6:  55e66b7a74f reftable: (de)serialization for the polymorphic record type.\n  7:  e595dbc6c84 !  7:  4f6ecd3646a Provide zlib's uncompress2 from compat/zlib-compat.c\n     @@ config.mak.uname: ifeq ($(uname_S),FreeBSD)\n       \tFILENO_IS_A_MACRO = UnfortunatelyYes\n       endif\n       ifeq ($(uname_S),OpenBSD)\n     -+\tNO_UNCOMPRESS2 = YesPlease\n      +\t# Versions < 7.0 need compatibility layer\n      +\tifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n      +\t\tNO_UNCOMPRESS2 = UnfortunatelyYes\n  8:  97f7ee04886 =  8:  04f2d74df51 reftable: reading/writing blocks\n  9:  8d2e8be5795 =  9:  f6c2da61208 reftable: a generic binary tree implementation\n 10:  965af6ec065 = 10:  277a1f662e3 reftable: write reftable files\n 11:  4e259380fda = 11:  f97182dec1f reftable: generic interface to tables\n 12:  a43d3512164 = 12:  13de5d03a71 reftable: read reftable files\n 13:  f0e159cd853 = 13:  8efc3c2b6ab reftable: reftable file level tests\n 14:  5a5c108d601 = 14:  e89b16a37e6 reftable: add a heap-based priority queue for reftable records\n 15:  905ffa58f36 = 15:  40a91b14be8 reftable: add merged table view\n 16:  e9598889af2 = 16:  46c2ddf07cf reftable: implement refname validation\n 17:  8b0ee68c636 ! 17:  3db887bc078 reftable: implement stack, a mutable database of reftable files.\n     @@ reftable/stack.c (new)\n      +\n      +int read_lines(const char *filename, char ***namesp)\n      +{\n     -+\tint fd = open(filename, O_RDONLY, 0644);\n     ++\tint fd = open(filename, O_RDONLY);\n      +\tint err = 0;\n      +\tif (fd < 0) {\n      +\t\tif (errno == ENOENT) {\n 18:  a9bebe45bde = 18:  795a15000fb reftable: add dump utility\n 19:  104fbc7502f = 19:  7b3215aef39 Add \"test-tool dump-reftable\" command.\n\n-- \ngitgitgadget\n"},{"id":"437305","messageId":"2597ddfeae1d9da8695151f62379c85621ad0fcb.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 02/19] reftable: RFC: add LICENSE","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:09:59Z","receivedAt":"2021-09-28T15:10:27Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe objective of this code is to be usable as a C library, so it can be reused\nin libgit2.\n\nThis is currently using a BSD license as it is the liberal license I could find,\nbut this could be changed to whatever fits the stated goal above.\n\nThis code is currently imported from github.com/hanwen/reftable. Once this code\nlands in git.git, the C code will be removed from github.com/hanwen/reftable,\nand the git.git code will be the source of truth.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/LICENSE | 31 +++++++++++++++++++++++++++++++\n 1 file changed, 31 insertions(+)\n create mode 100644 reftable/LICENSE\n\ndiff --git a/reftable/LICENSE b/reftable/LICENSE\nnew file mode 100644\nindex 00000000000..402e0f9356b\n--- /dev/null\n+++ b/reftable/LICENSE\n@@ -0,0 +1,31 @@\n+BSD License\n+\n+Copyright (c) 2020, Google LLC\n+All rights reserved.\n+\n+Redistribution and use in source and binary forms, with or without\n+modification, are permitted provided that the following conditions are\n+met:\n+\n+* Redistributions of source code must retain the above copyright notice,\n+this list of conditions and the following disclaimer.\n+\n+* Redistributions in binary form must reproduce the above copyright\n+notice, this list of conditions and the following disclaimer in the\n+documentation and/or other materials provided with the distribution.\n+\n+* Neither the name of Google LLC nor the names of its contributors may\n+be used to endorse or promote products derived from this software\n+without specific prior written permission.\n+\n+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n-- \ngitgitgadget\n\n"},{"id":"437306","messageId":"41b1ff1293fee7b2ddfe694ca535265eb2f918b2.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 03/19] reftable: add error related functionality","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:00Z","receivedAt":"2021-09-28T15:10:29Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable/ directory is structured as a library, so it cannot\ncrash on misuse. Instead, it returns an error codes.\n\nIn addition, the error code can be used to signal conditions from lower levels\nof the library to be handled by higher levels of the library. For example, a\ntransaction might legitimately write an empty reftable file, but in that case,\nwe'd want to shortcut the transaction overhead.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/error.c          | 41 ++++++++++++++++++++++++++\n reftable/reftable-error.h | 62 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 103 insertions(+)\n create mode 100644 reftable/error.c\n create mode 100644 reftable/reftable-error.h\n\ndiff --git a/reftable/error.c b/reftable/error.c\nnew file mode 100644\nindex 00000000000..f6f16def921\n--- /dev/null\n+++ b/reftable/error.c\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-error.h\"\n+\n+#include <stdio.h>\n+\n+const char *reftable_error_str(int err)\n+{\n+\tstatic char buf[250];\n+\tswitch (err) {\n+\tcase REFTABLE_IO_ERROR:\n+\t\treturn \"I/O error\";\n+\tcase REFTABLE_FORMAT_ERROR:\n+\t\treturn \"corrupt reftable file\";\n+\tcase REFTABLE_NOT_EXIST_ERROR:\n+\t\treturn \"file does not exist\";\n+\tcase REFTABLE_LOCK_ERROR:\n+\t\treturn \"data is outdated\";\n+\tcase REFTABLE_API_ERROR:\n+\t\treturn \"misuse of the reftable API\";\n+\tcase REFTABLE_ZLIB_ERROR:\n+\t\treturn \"zlib failure\";\n+\tcase REFTABLE_NAME_CONFLICT:\n+\t\treturn \"file/directory conflict\";\n+\tcase REFTABLE_EMPTY_TABLE_ERROR:\n+\t\treturn \"wrote empty table\";\n+\tcase REFTABLE_REFNAME_ERROR:\n+\t\treturn \"invalid refname\";\n+\tcase -1:\n+\t\treturn \"general error\";\n+\tdefault:\n+\t\tsnprintf(buf, sizeof(buf), \"unknown error code %d\", err);\n+\t\treturn buf;\n+\t}\n+}\ndiff --git a/reftable/reftable-error.h b/reftable/reftable-error.h\nnew file mode 100644\nindex 00000000000..6f89bedf1a5\n--- /dev/null\n+++ b/reftable/reftable-error.h\n@@ -0,0 +1,62 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ERROR_H\n+#define REFTABLE_ERROR_H\n+\n+/*\n+ * Errors in reftable calls are signaled with negative integer return values. 0\n+ * means success.\n+ */\n+enum reftable_error {\n+\t/* Unexpected file system behavior */\n+\tREFTABLE_IO_ERROR = -2,\n+\n+\t/* Format inconsistency on reading data */\n+\tREFTABLE_FORMAT_ERROR = -3,\n+\n+\t/* File does not exist. Returned from block_source_from_file(), because\n+\t * it needs special handling in stack.\n+\t */\n+\tREFTABLE_NOT_EXIST_ERROR = -4,\n+\n+\t/* Trying to write out-of-date data. */\n+\tREFTABLE_LOCK_ERROR = -5,\n+\n+\t/* Misuse of the API:\n+\t *  - on writing a record with NULL refname.\n+\t *  - on writing a reftable_ref_record outside the table limits\n+\t *  - on writing a ref or log record before the stack's\n+\t * next_update_inde*x\n+\t *  - on writing a log record with multiline message with\n+\t *  exact_log_message unset\n+\t *  - on reading a reftable_ref_record from log iterator, or vice versa.\n+\t *\n+\t * When a call misuses the API, the internal state of the library is\n+\t * kept unchanged.\n+\t */\n+\tREFTABLE_API_ERROR = -6,\n+\n+\t/* Decompression error */\n+\tREFTABLE_ZLIB_ERROR = -7,\n+\n+\t/* Wrote a table without blocks. */\n+\tREFTABLE_EMPTY_TABLE_ERROR = -8,\n+\n+\t/* Dir/file conflict. */\n+\tREFTABLE_NAME_CONFLICT = -9,\n+\n+\t/* Invalid ref name. */\n+\tREFTABLE_REFNAME_ERROR = -10,\n+};\n+\n+/* convert the numeric error code to a string. The string should not be\n+ * deallocated. */\n+const char *reftable_error_str(int err);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"437308","messageId":"b2f8dc32bfcea771b8a6c10973cad6380b7a3f7a.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 01/19] hash.h: provide constants for the hash IDs","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:09:58Z","receivedAt":"2021-09-28T15:10:30Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will simplify referencing them from code that is not deeply integrated with\nGit, in particular, the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n hash.h        | 6 ++++++\n object-file.c | 7 ++-----\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/hash.h b/hash.h\nindex 9e25c40e9ac..5d40368f18a 100644\n--- a/hash.h\n+++ b/hash.h\n@@ -95,12 +95,18 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s\n /* Number of algorithms supported (including unknown). */\n #define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)\n \n+/* \"sha1\", big-endian */\n+#define GIT_SHA1_FORMAT_ID 0x73686131\n+\n /* The length in bytes and in hex digits of an object name (SHA-1 value). */\n #define GIT_SHA1_RAWSZ 20\n #define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)\n /* The block size of SHA-1. */\n #define GIT_SHA1_BLKSZ 64\n \n+/* \"s256\", big-endian */\n+#define GIT_SHA256_FORMAT_ID 0x73323536\n+\n /* The length in bytes and in hex digits of an object name (SHA-256 value). */\n #define GIT_SHA256_RAWSZ 32\n #define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)\ndiff --git a/object-file.c b/object-file.c\nindex be4f94ecf3b..0ed2a35de73 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -165,7 +165,6 @@ static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx)\n \tBUG(\"trying to finalize unknown hash\");\n }\n \n-\n const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t{\n \t\tNULL,\n@@ -184,8 +183,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha1\",\n-\t\t/* \"sha1\", big-endian */\n-\t\t0x73686131,\n+\t\tGIT_SHA1_FORMAT_ID,\n \t\tGIT_SHA1_RAWSZ,\n \t\tGIT_SHA1_HEXSZ,\n \t\tGIT_SHA1_BLKSZ,\n@@ -200,8 +198,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha256\",\n-\t\t/* \"s256\", big-endian */\n-\t\t0x73323536,\n+\t\tGIT_SHA256_FORMAT_ID,\n \t\tGIT_SHA256_RAWSZ,\n \t\tGIT_SHA256_HEXSZ,\n \t\tGIT_SHA256_BLKSZ,\n-- \ngitgitgadget\n\n"},{"id":"437309","messageId":"c03043c5e631fba352ea5e09ad9f2336a1f832c1.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 04/19] reftable: utility functions","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:01Z","receivedAt":"2021-09-28T15:10:32Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis commit provides basic utility classes for the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                                   |  25 +++-\n contrib/buildsystems/CMakeLists.txt        |  14 ++-\n contrib/buildsystems/Generators/Vcxproj.pm |  11 +-\n reftable/basics.c                          | 128 +++++++++++++++++++++\n reftable/basics.h                          |  60 ++++++++++\n reftable/basics_test.c                     |  98 ++++++++++++++++\n reftable/publicbasics.c                    |  58 ++++++++++\n reftable/reftable-malloc.h                 |  18 +++\n reftable/reftable-tests.h                  |  22 ++++\n reftable/system.h                          |  32 ++++++\n reftable/test_framework.c                  |  23 ++++\n reftable/test_framework.h                  |  53 +++++++++\n t/helper/test-reftable.c                   |   9 ++\n t/helper/test-tool.c                       |   3 +-\n t/helper/test-tool.h                       |   1 +\n t/t0032-reftable-unittest.sh               |  15 +++\n 16 files changed, 563 insertions(+), 7 deletions(-)\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\ndiff --git a/Makefile b/Makefile\nindex a9f9b689f0c..cb48d44b0da 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -736,6 +736,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o\n TEST_BUILTINS_OBJS += test-read-graph.o\n TEST_BUILTINS_OBJS += test-read-midx.o\n TEST_BUILTINS_OBJS += test-ref-store.o\n+TEST_BUILTINS_OBJS += test-reftable.o\n TEST_BUILTINS_OBJS += test-regex.o\n TEST_BUILTINS_OBJS += test-repository.o\n TEST_BUILTINS_OBJS += test-revision-walking.o\n@@ -814,6 +815,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n XDIFF_LIB = xdiff/lib.a\n+REFTABLE_LIB = reftable/libreftable.a\n+REFTABLE_TEST_LIB = reftable/libreftable_test.a\n \n GENERATED_H += command-list.h\n GENERATED_H += config-list.h\n@@ -1188,7 +1191,7 @@ THIRD_PARTY_SOURCES += compat/regex/%\n THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)\n+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)\n EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2424,7 +2427,15 @@ XDIFF_OBJS += xdiff/xutils.o\n .PHONY: xdiff-objs\n xdiff-objs: $(XDIFF_OBJS)\n \n+REFTABLE_OBJS += reftable/basics.o\n+REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/publicbasics.o\n+\n+REFTABLE_TEST_OBJS += reftable/test_framework.o\n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n+\n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n+\n .PHONY: test-objs\n test-objs: $(TEST_OBJS)\n \n@@ -2440,6 +2451,8 @@ OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n OBJECTS += $(XDIFF_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n+OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n+\n ifndef NO_CURL\n \tOBJECTS += http.o http-walker.o remote-curl.o\n endif\n@@ -2590,6 +2603,12 @@ $(LIB_FILE): $(LIB_OBJS)\n $(XDIFF_LIB): $(XDIFF_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n+$(REFTABLE_LIB): $(REFTABLE_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n export DEFAULT_EDITOR DEFAULT_PAGER\n \n Documentation/GIT-EXCLUDED-PROGRAMS: FORCE\n@@ -2888,7 +2907,7 @@ perf: all\n \n t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \n-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)\n+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n \n check-sha1:: t/helper/test-tool$X\n@@ -3223,7 +3242,7 @@ cocciclean:\n clean: profile-clean coverage-clean cocciclean\n \t$(RM) *.res\n \t$(RM) $(OBJECTS)\n-\t$(RM) $(LIB_FILE) $(XDIFF_LIB)\n+\t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 171b4124afe..c2bf5bdffc6 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -640,6 +640,12 @@ parse_makefile_for_sources(libxdiff_SOURCES \"XDIFF_OBJS\")\n list(TRANSFORM libxdiff_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n add_library(xdiff STATIC ${libxdiff_SOURCES})\n \n+#reftable\n+parse_makefile_for_sources(reftable_SOURCES \"REFTABLE_OBJS\")\n+\n+list(TRANSFORM reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+add_library(reftable STATIC ${reftable_SOURCES})\n+\n if(WIN32)\n \tif(NOT MSVC)#use windres when compiling with gcc and clang\n \t\tadd_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res\n@@ -662,7 +668,7 @@ endif()\n #link all required libraries to common-main\n add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)\n \n-target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})\n+target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})\n if(Intl_FOUND)\n \ttarget_link_libraries(common-main ${Intl_LIBRARIES})\n endif()\n@@ -902,11 +908,15 @@ if(BUILD_TESTING)\n add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)\n target_link_libraries(test-fake-ssh common-main)\n \n+#reftable-tests\n+parse_makefile_for_sources(test-reftable_SOURCES \"REFTABLE_TEST_OBJS\")\n+list(TRANSFORM test-reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+\n #test-tool\n parse_makefile_for_sources(test-tool_SOURCES \"TEST_BUILTINS_OBJS\")\n \n list(TRANSFORM test-tool_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/t/helper/\")\n-add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})\n+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})\n target_link_libraries(test-tool common-main)\n \n set_target_properties(test-fake-ssh test-tool\ndiff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm\nindex d2584450ba1..1a25789d285 100644\n--- a/contrib/buildsystems/Generators/Vcxproj.pm\n+++ b/contrib/buildsystems/Generators/Vcxproj.pm\n@@ -77,7 +77,7 @@ sub createProject {\n     my $libs_release = \"\\n    \";\n     my $libs_debug = \"\\n    \";\n     if (!$static_library) {\n-      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n+      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib|reftable\\/libreftable\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n       $libs_debug = $libs_release;\n       $libs_debug =~ s/zlib\\.lib/zlibd\\.lib/g;\n       $libs_debug =~ s/libexpat\\.lib/libexpatd\\.lib/g;\n@@ -232,6 +232,7 @@ EOM\n EOM\n     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {\n       my $uuid_libgit = $$build_structure{\"LIBS_libgit_GUID\"};\n+      my $uuid_libreftable = $$build_structure{\"LIBS_reftable/libreftable_GUID\"};\n       my $uuid_xdiff_lib = $$build_structure{\"LIBS_xdiff/lib_GUID\"};\n \n       print F << \"EOM\";\n@@ -241,6 +242,14 @@ EOM\n       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n     </ProjectReference>\n EOM\n+      if (!($name =~ /xdiff|libreftable/)) {\n+        print F << \"EOM\";\n+    <ProjectReference Include=\"$cdup\\\\reftable\\\\libreftable\\\\libreftable.vcxproj\">\n+      <Project>$uuid_libreftable</Project>\n+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n+    </ProjectReference>\n+EOM\n+      }\n       if (!($name =~ 'xdiff')) {\n         print F << \"EOM\";\n     <ProjectReference Include=\"$cdup\\\\xdiff\\\\lib\\\\xdiff_lib.vcxproj\">\ndiff --git a/reftable/basics.c b/reftable/basics.c\nnew file mode 100644\nindex 00000000000..f761e48028c\n--- /dev/null\n+++ b/reftable/basics.c\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+\n+void put_be24(uint8_t *out, uint32_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 16) & 0xff);\n+\tout[1] = (uint8_t)((i >> 8) & 0xff);\n+\tout[2] = (uint8_t)(i & 0xff);\n+}\n+\n+uint32_t get_be24(uint8_t *in)\n+{\n+\treturn (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |\n+\t       (uint32_t)(in[2]);\n+}\n+\n+void put_be16(uint8_t *out, uint16_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 8) & 0xff);\n+\tout[1] = (uint8_t)(i & 0xff);\n+}\n+\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)\n+{\n+\tsize_t lo = 0;\n+\tsize_t hi = sz;\n+\n+\t/* Invariants:\n+\t *\n+\t *  (hi == sz) || f(hi) == true\n+\t *  (lo == 0 && f(0) == true) || fi(lo) == false\n+\t */\n+\twhile (hi - lo > 1) {\n+\t\tsize_t mid = lo + (hi - lo) / 2;\n+\n+\t\tif (f(mid, args))\n+\t\t\thi = mid;\n+\t\telse\n+\t\t\tlo = mid;\n+\t}\n+\n+\tif (lo)\n+\t\treturn hi;\n+\n+\treturn f(0, args) ? 0 : 1;\n+}\n+\n+void free_names(char **a)\n+{\n+\tchar **p;\n+\tif (!a) {\n+\t\treturn;\n+\t}\n+\tfor (p = a; *p; p++) {\n+\t\treftable_free(*p);\n+\t}\n+\treftable_free(a);\n+}\n+\n+int names_length(char **names)\n+{\n+\tchar **p = names;\n+\tfor (; *p; p++) {\n+\t\t/* empty */\n+\t}\n+\treturn p - names;\n+}\n+\n+void parse_names(char *buf, int size, char ***namesp)\n+{\n+\tchar **names = NULL;\n+\tsize_t names_cap = 0;\n+\tsize_t names_len = 0;\n+\n+\tchar *p = buf;\n+\tchar *end = buf + size;\n+\twhile (p < end) {\n+\t\tchar *next = strchr(p, '\\n');\n+\t\tif (next && next < end) {\n+\t\t\t*next = 0;\n+\t\t} else {\n+\t\t\tnext = end;\n+\t\t}\n+\t\tif (p < next) {\n+\t\t\tif (names_len == names_cap) {\n+\t\t\t\tnames_cap = 2 * names_cap + 1;\n+\t\t\t\tnames = reftable_realloc(\n+\t\t\t\t\tnames, names_cap * sizeof(*names));\n+\t\t\t}\n+\t\t\tnames[names_len++] = xstrdup(p);\n+\t\t}\n+\t\tp = next + 1;\n+\t}\n+\n+\tnames = reftable_realloc(names, (names_len + 1) * sizeof(*names));\n+\tnames[names_len] = NULL;\n+\t*namesp = names;\n+}\n+\n+int names_equal(char **a, char **b)\n+{\n+\tint i = 0;\n+\tfor (; a[i] && b[i]; i++) {\n+\t\tif (strcmp(a[i], b[i])) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn a[i] == b[i];\n+}\n+\n+int common_prefix_size(struct strbuf *a, struct strbuf *b)\n+{\n+\tint p = 0;\n+\tfor (; p < a->len && p < b->len; p++) {\n+\t\tif (a->buf[p] != b->buf[p])\n+\t\t\tbreak;\n+\t}\n+\n+\treturn p;\n+}\ndiff --git a/reftable/basics.h b/reftable/basics.h\nnew file mode 100644\nindex 00000000000..096b36862b9\n--- /dev/null\n+++ b/reftable/basics.h\n@@ -0,0 +1,60 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BASICS_H\n+#define BASICS_H\n+\n+/*\n+ * miscellaneous utilities that are not provided by Git.\n+ */\n+\n+#include \"system.h\"\n+\n+/* Bigendian en/decoding of integers */\n+\n+void put_be24(uint8_t *out, uint32_t i);\n+uint32_t get_be24(uint8_t *in);\n+void put_be16(uint8_t *out, uint16_t i);\n+\n+/*\n+ * find smallest index i in [0, sz) at which f(i) is true, assuming\n+ * that f is ascending. Return sz if f(i) is false for all indices.\n+ *\n+ * Contrary to bsearch(3), this returns something useful if the argument is not\n+ * found.\n+ */\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);\n+\n+/*\n+ * Frees a NULL terminated array of malloced strings. The array itself is also\n+ * freed.\n+ */\n+void free_names(char **a);\n+\n+/* parse a newline separated list of names. `size` is the length of the buffer,\n+ * without terminating '\\0'. Empty names are discarded. */\n+void parse_names(char *buf, int size, char ***namesp);\n+\n+/* compares two NULL-terminated arrays of strings. */\n+int names_equal(char **a, char **b);\n+\n+/* returns the array size of a NULL-terminated array of strings. */\n+int names_length(char **names);\n+\n+/* Allocation routines; they invoke the functions set through\n+ * reftable_set_alloc() */\n+void *reftable_malloc(size_t sz);\n+void *reftable_realloc(void *p, size_t sz);\n+void reftable_free(void *p);\n+void *reftable_calloc(size_t sz);\n+\n+/* Find the longest shared prefix size of `a` and `b` */\n+struct strbuf;\n+int common_prefix_size(struct strbuf *a, struct strbuf *b);\n+\n+#endif\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nnew file mode 100644\nindex 00000000000..1fcd2297256\n--- /dev/null\n+++ b/reftable/basics_test.c\n@@ -0,0 +1,98 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct binsearch_args {\n+\tint key;\n+\tint *arr;\n+};\n+\n+static int binsearch_func(size_t i, void *void_args)\n+{\n+\tstruct binsearch_args *args = void_args;\n+\n+\treturn args->key < args->arr[i];\n+}\n+\n+static void test_binsearch(void)\n+{\n+\tint arr[] = { 2, 4, 6, 8, 10 };\n+\tsize_t sz = ARRAY_SIZE(arr);\n+\tstruct binsearch_args args = {\n+\t\t.arr = arr,\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 1; i < 11; i++) {\n+\t\tint res;\n+\t\targs.key = i;\n+\t\tres = binsearch(sz, &binsearch_func, &args);\n+\n+\t\tif (res < sz) {\n+\t\t\tEXPECT(args.key < arr[res]);\n+\t\t\tif (res > 0) {\n+\t\t\t\tEXPECT(args.key >= arr[res - 1]);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tEXPECT(args.key == 10 || args.key == 11);\n+\t\t}\n+\t}\n+}\n+\n+static void test_names_length(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(a) == 2);\n+}\n+\n+static void test_parse_names_normal(void)\n+{\n+\tchar in[] = \"a\\nb\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!strcmp(out[1], \"b\"));\n+\tEXPECT(!out[2]);\n+\tfree_names(out);\n+}\n+\n+static void test_parse_names_drop_empty(void)\n+{\n+\tchar in[] = \"a\\n\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!out[1]);\n+\tfree_names(out);\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct strbuf s1 = STRBUF_INIT;\n+\tstruct strbuf s2 = STRBUF_INIT;\n+\tstrbuf_addstr(&s1, \"abcdef\");\n+\tstrbuf_addstr(&s2, \"abc\");\n+\tEXPECT(common_prefix_size(&s1, &s2) == 3);\n+\tstrbuf_release(&s1);\n+\tstrbuf_release(&s2);\n+}\n+\n+int basics_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_parse_names_normal);\n+\tRUN_TEST(test_parse_names_drop_empty);\n+\tRUN_TEST(test_binsearch);\n+\tRUN_TEST(test_names_length);\n+\treturn 0;\n+}\ndiff --git a/reftable/publicbasics.c b/reftable/publicbasics.c\nnew file mode 100644\nindex 00000000000..bd0a02d3f68\n--- /dev/null\n+++ b/reftable/publicbasics.c\n@@ -0,0 +1,58 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-malloc.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;\n+static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;\n+static void (*reftable_free_ptr)(void *) = &free;\n+\n+void *reftable_malloc(size_t sz)\n+{\n+\treturn (*reftable_malloc_ptr)(sz);\n+}\n+\n+void *reftable_realloc(void *p, size_t sz)\n+{\n+\treturn (*reftable_realloc_ptr)(p, sz);\n+}\n+\n+void reftable_free(void *p)\n+{\n+\treftable_free_ptr(p);\n+}\n+\n+void *reftable_calloc(size_t sz)\n+{\n+\tvoid *p = reftable_malloc(sz);\n+\tmemset(p, 0, sz);\n+\treturn p;\n+}\n+\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *))\n+{\n+\treftable_malloc_ptr = malloc;\n+\treftable_realloc_ptr = realloc;\n+\treftable_free_ptr = free;\n+}\n+\n+int hash_size(uint32_t id)\n+{\n+\tswitch (id) {\n+\tcase 0:\n+\tcase GIT_SHA1_FORMAT_ID:\n+\t\treturn GIT_SHA1_RAWSZ;\n+\tcase GIT_SHA256_FORMAT_ID:\n+\t\treturn GIT_SHA256_RAWSZ;\n+\t}\n+\tabort();\n+}\ndiff --git a/reftable/reftable-malloc.h b/reftable/reftable-malloc.h\nnew file mode 100644\nindex 00000000000..5f2185f1f34\n--- /dev/null\n+++ b/reftable/reftable-malloc.h\n@@ -0,0 +1,18 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_H\n+#define REFTABLE_H\n+\n+#include <stddef.h>\n+\n+/* Overrides the functions to use for memory management. */\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *));\n+\n+#endif\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nnew file mode 100644\nindex 00000000000..5e7698ae654\n--- /dev/null\n+++ b/reftable/reftable-tests.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_TESTS_H\n+#define REFTABLE_TESTS_H\n+\n+int basics_test_main(int argc, const char **argv);\n+int block_test_main(int argc, const char **argv);\n+int merged_test_main(int argc, const char **argv);\n+int record_test_main(int argc, const char **argv);\n+int refname_test_main(int argc, const char **argv);\n+int reftable_test_main(int argc, const char **argv);\n+int stack_test_main(int argc, const char **argv);\n+int tree_test_main(int argc, const char **argv);\n+int reftable_dump_main(int argc, char *const *argv);\n+\n+#endif\ndiff --git a/reftable/system.h b/reftable/system.h\nnew file mode 100644\nindex 00000000000..4907306c0c5\n--- /dev/null\n+++ b/reftable/system.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef SYSTEM_H\n+#define SYSTEM_H\n+\n+/* This header glues the reftable library to the rest of Git */\n+\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"hash.h\" /* hash ID, sizes.*/\n+#include \"dir.h\" /* remove_dir_recursively, for tests.*/\n+\n+#include <zlib.h>\n+\n+#ifdef NO_UNCOMPRESS2\n+/*\n+ * This is uncompress2, which is only available in zlib >= 1.2.9\n+ * (released as of early 2017)\n+ */\n+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n+\t\tuLong *sourceLen);\n+#endif\n+\n+int hash_size(uint32_t id);\n+\n+#endif\ndiff --git a/reftable/test_framework.c b/reftable/test_framework.c\nnew file mode 100644\nindex 00000000000..84ac972cad0\n--- /dev/null\n+++ b/reftable/test_framework.c\n@@ -0,0 +1,23 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"test_framework.h\"\n+\n+#include \"basics.h\"\n+\n+void set_test_hash(uint8_t *p, int i)\n+{\n+\tmemset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID));\n+}\n+\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz)\n+{\n+\tstrbuf_add(b, data, sz);\n+\treturn sz;\n+}\ndiff --git a/reftable/test_framework.h b/reftable/test_framework.h\nnew file mode 100644\nindex 00000000000..774cb275bf6\n--- /dev/null\n+++ b/reftable/test_framework.h\n@@ -0,0 +1,53 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TEST_FRAMEWORK_H\n+#define TEST_FRAMEWORK_H\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+\n+#define EXPECT_ERR(c)                                                  \\\n+\tif (c != 0) {                                                  \\\n+\t\tfflush(stderr);                                        \\\n+\t\tfflush(stdout);                                        \\\n+\t\tfprintf(stderr, \"%s: %d: error == %d (%s), want 0\\n\",  \\\n+\t\t\t__FILE__, __LINE__, c, reftable_error_str(c)); \\\n+\t\tabort();                                               \\\n+\t}\n+\n+#define EXPECT_STREQ(a, b)                                               \\\n+\tif (strcmp(a, b)) {                                              \\\n+\t\tfflush(stderr);                                          \\\n+\t\tfflush(stdout);                                          \\\n+\t\tfprintf(stderr, \"%s:%d: %s (%s) != %s (%s)\\n\", __FILE__, \\\n+\t\t\t__LINE__, #a, a, #b, b);                         \\\n+\t\tabort();                                                 \\\n+\t}\n+\n+#define EXPECT(c)                                                          \\\n+\tif (!(c)) {                                                        \\\n+\t\tfflush(stderr);                                            \\\n+\t\tfflush(stdout);                                            \\\n+\t\tfprintf(stderr, \"%s: %d: failed assertion %s\\n\", __FILE__, \\\n+\t\t\t__LINE__, #c);                                     \\\n+\t\tabort();                                                   \\\n+\t}\n+\n+#define RUN_TEST(f)                          \\\n+\tfprintf(stderr, \"running %s\\n\", #f); \\\n+\tfflush(stderr);                      \\\n+\tf();\n+\n+void set_test_hash(uint8_t *p, int i);\n+\n+/* Like strbuf_add, but suitable for passing to reftable_new_writer\n+ */\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nnew file mode 100644\nindex 00000000000..3b58e423e7b\n--- /dev/null\n+++ b/t/helper/test-reftable.c\n@@ -0,0 +1,9 @@\n+#include \"reftable/reftable-tests.h\"\n+#include \"test-tool.h\"\n+\n+int cmd__reftable(int argc, const char **argv)\n+{\n+\tbasics_test_main(argc, argv);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 3ce5585e53a..f7c888ffda7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -53,13 +53,14 @@ static struct test_cmd cmds[] = {\n \t{ \"pcre2-config\", cmd__pcre2_config },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n-\t{ \"proc-receive\", cmd__proc_receive},\n+\t{ \"proc-receive\", cmd__proc_receive },\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\n \t{ \"read-graph\", cmd__read_graph },\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n+\t{ \"reftable\", cmd__reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 9f0f5228508..25f77469146 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -49,6 +49,7 @@ int cmd__read_cache(int argc, const char **argv);\n int cmd__read_graph(int argc, const char **argv);\n int cmd__read_midx(int argc, const char **argv);\n int cmd__ref_store(int argc, const char **argv);\n+int cmd__reftable(int argc, const char **argv);\n int cmd__regex(int argc, const char **argv);\n int cmd__repository(int argc, const char **argv);\n int cmd__revision_walking(int argc, const char **argv);\ndiff --git a/t/t0032-reftable-unittest.sh b/t/t0032-reftable-unittest.sh\nnew file mode 100755\nindex 00000000000..0ed14971a58\n--- /dev/null\n+++ b/t/t0032-reftable-unittest.sh\n@@ -0,0 +1,15 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Google LLC\n+#\n+\n+test_description='reftable unittests'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'unittests' '\n+\tTMPDIR=$(pwd) && export TMPDIR &&\n+\ttest-tool reftable\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"437310","messageId":"317a6aaa3577ea446a7cfb5b97a93f21cb3bd28f.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 05/19] reftable: add blocksource, an abstraction for random access reads","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:02Z","receivedAt":"2021-09-28T15:10:36Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is usually used with files for storage. However, we abstract\naway this using the blocksource data structure. This has two advantages:\n\n* log blocks are zlib compressed, and handling them is simplified if we can\n  discard byte segments from within the block layer.\n\n* for unittests, it is useful to read and write in-memory. The blocksource\n  allows us to abstract the data away from on-disk files.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                        |   1 +\n reftable/blocksource.c          | 148 ++++++++++++++++++++++++++++++++\n reftable/blocksource.h          |  22 +++++\n reftable/reftable-blocksource.h |  49 +++++++++++\n 4 files changed, 220 insertions(+)\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/reftable-blocksource.h\n\ndiff --git a/Makefile b/Makefile\nindex cb48d44b0da..eaca0da79a0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2429,6 +2429,7 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n \n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/blocksource.c b/reftable/blocksource.c\nnew file mode 100644\nindex 00000000000..0044eecd9aa\n--- /dev/null\n+++ b/reftable/blocksource.c\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+\n+static void strbuf_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void strbuf_close(void *b)\n+{\n+}\n+\n+static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t     uint32_t size)\n+{\n+\tstruct strbuf *b = v;\n+\tassert(off + size <= b->len);\n+\tdest->data = reftable_calloc(size);\n+\tmemcpy(dest->data, b->buf + off, size);\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static uint64_t strbuf_size(void *b)\n+{\n+\treturn ((struct strbuf *)b)->len;\n+}\n+\n+static struct reftable_block_source_vtable strbuf_vtable = {\n+\t.size = &strbuf_size,\n+\t.read_block = &strbuf_read_block,\n+\t.return_block = &strbuf_return_block,\n+\t.close = &strbuf_close,\n+};\n+\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf)\n+{\n+\tassert(!bs->ops);\n+\tbs->ops = &strbuf_vtable;\n+\tbs->arg = buf;\n+}\n+\n+static void malloc_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static struct reftable_block_source_vtable malloc_vtable = {\n+\t.return_block = &malloc_return_block,\n+};\n+\n+static struct reftable_block_source malloc_block_source_instance = {\n+\t.ops = &malloc_vtable,\n+};\n+\n+struct reftable_block_source malloc_block_source(void)\n+{\n+\treturn malloc_block_source_instance;\n+}\n+\n+struct file_block_source {\n+\tint fd;\n+\tuint64_t size;\n+};\n+\n+static uint64_t file_size(void *b)\n+{\n+\treturn ((struct file_block_source *)b)->size;\n+}\n+\n+static void file_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void file_close(void *b)\n+{\n+\tint fd = ((struct file_block_source *)b)->fd;\n+\tif (fd > 0) {\n+\t\tclose(fd);\n+\t\t((struct file_block_source *)b)->fd = 0;\n+\t}\n+\n+\treftable_free(b);\n+}\n+\n+static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t   uint32_t size)\n+{\n+\tstruct file_block_source *b = v;\n+\tassert(off + size <= b->size);\n+\tdest->data = reftable_malloc(size);\n+\tif (pread(b->fd, dest->data, size, off) != size)\n+\t\treturn -1;\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static struct reftable_block_source_vtable file_vtable = {\n+\t.size = &file_size,\n+\t.read_block = &file_read_block,\n+\t.return_block = &file_return_block,\n+\t.close = &file_close,\n+};\n+\n+int reftable_block_source_from_file(struct reftable_block_source *bs,\n+\t\t\t\t    const char *name)\n+{\n+\tstruct stat st = { 0 };\n+\tint err = 0;\n+\tint fd = open(name, O_RDONLY);\n+\tstruct file_block_source *p = NULL;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\treturn REFTABLE_NOT_EXIST_ERROR;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\terr = fstat(fd, &st);\n+\tif (err < 0)\n+\t\treturn -1;\n+\n+\tp = reftable_calloc(sizeof(struct file_block_source));\n+\tp->size = st.st_size;\n+\tp->fd = fd;\n+\n+\tassert(!bs->ops);\n+\tbs->ops = &file_vtable;\n+\tbs->arg = p;\n+\treturn 0;\n+}\ndiff --git a/reftable/blocksource.h b/reftable/blocksource.h\nnew file mode 100644\nindex 00000000000..072e2727ad2\n--- /dev/null\n+++ b/reftable/blocksource.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCKSOURCE_H\n+#define BLOCKSOURCE_H\n+\n+#include \"system.h\"\n+\n+struct reftable_block_source;\n+\n+/* Create an in-memory block source for reading reftables */\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf);\n+\n+struct reftable_block_source malloc_block_source(void);\n+\n+#endif\ndiff --git a/reftable/reftable-blocksource.h b/reftable/reftable-blocksource.h\nnew file mode 100644\nindex 00000000000..5aa3990a573\n--- /dev/null\n+++ b/reftable/reftable-blocksource.h\n@@ -0,0 +1,49 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_BLOCKSOURCE_H\n+#define REFTABLE_BLOCKSOURCE_H\n+\n+#include <stdint.h>\n+\n+/* block_source is a generic wrapper for a seekable readable file.\n+ */\n+struct reftable_block_source {\n+\tstruct reftable_block_source_vtable *ops;\n+\tvoid *arg;\n+};\n+\n+/* a contiguous segment of bytes. It keeps track of its generating block_source\n+ * so it can return itself into the pool. */\n+struct reftable_block {\n+\tuint8_t *data;\n+\tint len;\n+\tstruct reftable_block_source source;\n+};\n+\n+/* block_source_vtable are the operations that make up block_source */\n+struct reftable_block_source_vtable {\n+\t/* returns the size of a block source */\n+\tuint64_t (*size)(void *source);\n+\n+\t/* reads a segment from the block source. It is an error to read\n+\t   beyond the end of the block */\n+\tint (*read_block)(void *source, struct reftable_block *dest,\n+\t\t\t  uint64_t off, uint32_t size);\n+\t/* mark the block as read; may return the data back to malloc */\n+\tvoid (*return_block)(void *source, struct reftable_block *blockp);\n+\n+\t/* release all resources associated with the block source */\n+\tvoid (*close)(void *source);\n+};\n+\n+/* opens a file on the file system as a block_source */\n+int reftable_block_source_from_file(struct reftable_block_source *block_src,\n+\t\t\t\t    const char *name);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"437311","messageId":"55e66b7a74fcb6861ed5f27bb82cfceefa4c7b2a.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 06/19] reftable: (de)serialization for the polymorphic record type.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:03Z","receivedAt":"2021-09-28T15:10:38Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of blocks, and each block\ncontains a sequence of prefix-compressed key-value records. There are 4 types of\nrecords, and they have similarities in how they must be handled. This is\nachieved by introducing a polymorphic 'record' type that encapsulates ref, log,\nindex and object records.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |    2 +\n reftable/constants.h       |   21 +\n reftable/record.c          | 1212 ++++++++++++++++++++++++++++++++++++\n reftable/record.h          |  139 +++++\n reftable/record_test.c     |  412 ++++++++++++\n reftable/reftable-record.h |  114 ++++\n t/helper/test-reftable.c   |    2 +-\n 7 files changed, 1901 insertions(+), 1 deletion(-)\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/reftable-record.h\n\ndiff --git a/Makefile b/Makefile\nindex eaca0da79a0..7cbc8f95c8b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2431,7 +2431,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n \ndiff --git a/reftable/constants.h b/reftable/constants.h\nnew file mode 100644\nindex 00000000000..5eee72c4c11\n--- /dev/null\n+++ b/reftable/constants.h\n@@ -0,0 +1,21 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef CONSTANTS_H\n+#define CONSTANTS_H\n+\n+#define BLOCK_TYPE_LOG 'g'\n+#define BLOCK_TYPE_INDEX 'i'\n+#define BLOCK_TYPE_REF 'r'\n+#define BLOCK_TYPE_OBJ 'o'\n+#define BLOCK_TYPE_ANY 0\n+\n+#define MAX_RESTARTS ((1 << 16) - 1)\n+#define DEFAULT_BLOCK_SIZE 4096\n+\n+#endif\ndiff --git a/reftable/record.c b/reftable/record.c\nnew file mode 100644\nindex 00000000000..6a5dac32dc6\n--- /dev/null\n+++ b/reftable/record.c\n@@ -0,0 +1,1212 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+/* record.c - methods for different types of records. */\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"constants.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+\n+int get_var_int(uint64_t *dest, struct string_view *in)\n+{\n+\tint ptr = 0;\n+\tuint64_t val;\n+\n+\tif (in->len == 0)\n+\t\treturn -1;\n+\tval = in->buf[ptr] & 0x7f;\n+\n+\twhile (in->buf[ptr] & 0x80) {\n+\t\tptr++;\n+\t\tif (ptr > in->len) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tval = (val + 1) << 7 | (uint64_t)(in->buf[ptr] & 0x7f);\n+\t}\n+\n+\t*dest = val;\n+\treturn ptr + 1;\n+}\n+\n+int put_var_int(struct string_view *dest, uint64_t val)\n+{\n+\tuint8_t buf[10] = { 0 };\n+\tint i = 9;\n+\tint n = 0;\n+\tbuf[i] = (uint8_t)(val & 0x7f);\n+\ti--;\n+\twhile (1) {\n+\t\tval >>= 7;\n+\t\tif (!val) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tval--;\n+\t\tbuf[i] = 0x80 | (uint8_t)(val & 0x7f);\n+\t\ti--;\n+\t}\n+\n+\tn = sizeof(buf) - i - 1;\n+\tif (dest->len < n)\n+\t\treturn -1;\n+\tmemcpy(dest->buf, &buf[i + 1], n);\n+\treturn n;\n+}\n+\n+int reftable_is_block_type(uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\tcase BLOCK_TYPE_LOG:\n+\tcase BLOCK_TYPE_OBJ:\n+\tcase BLOCK_TYPE_INDEX:\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn rec->value.val1;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.target_value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static int decode_string(struct strbuf *dest, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t tsize = 0;\n+\tint n = get_var_int(&tsize, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\tif (in.len < tsize)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, in.buf, tsize);\n+\tstring_view_consume(&in, tsize);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static int encode_string(char *str, struct string_view s)\n+{\n+\tstruct string_view start = s;\n+\tint l = strlen(str);\n+\tint n = put_var_int(&s, l);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\tif (s.len < l)\n+\t\treturn -1;\n+\tmemcpy(s.buf, str, l);\n+\tstring_view_consume(&s, l);\n+\n+\treturn start.len - s.len;\n+}\n+\n+int reftable_encode_key(int *restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra)\n+{\n+\tstruct string_view start = dest;\n+\tint prefix_len = common_prefix_size(&prev_key, &key);\n+\tuint64_t suffix_len = key.len - prefix_len;\n+\tint n = put_var_int(&dest, (uint64_t)prefix_len);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\t*restart = (prefix_len == 0);\n+\n+\tn = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\tif (dest.len < suffix_len)\n+\t\treturn -1;\n+\tmemcpy(dest.buf, key.buf + prefix_len, suffix_len);\n+\tstring_view_consume(&dest, suffix_len);\n+\n+\treturn start.len - dest.len;\n+}\n+\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t prefix_len = 0;\n+\tuint64_t suffix_len = 0;\n+\tint n = get_var_int(&prefix_len, &in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tif (prefix_len > last_key.len)\n+\t\treturn -1;\n+\n+\tn = get_var_int(&suffix_len, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\t*extra = (uint8_t)(suffix_len & 0x7);\n+\tsuffix_len >>= 3;\n+\n+\tif (in.len < suffix_len)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(key);\n+\tstrbuf_add(key, last_key.buf, prefix_len);\n+\tstrbuf_add(key, in.buf, suffix_len);\n+\tstring_view_consume(&in, suffix_len);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static void reftable_ref_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_ref_record *rec =\n+\t\t(const struct reftable_ref_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, rec->refname);\n+}\n+\n+static void reftable_ref_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_ref_record *ref = rec;\n+\tconst struct reftable_ref_record *src = src_rec;\n+\tassert(hash_size > 0);\n+\n+\t/* This is simple and correct, but we could probably reuse the hash\n+\t * fields. */\n+\treftable_ref_record_release(ref);\n+\tif (src->refname) {\n+\t\tref->refname = xstrdup(src->refname);\n+\t}\n+\tref->update_index = src->update_index;\n+\tref->value_type = src->value_type;\n+\tswitch (src->value_type) {\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tref->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val1, src->value.val1, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tref->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.value, src->value.val2.value, hash_size);\n+\t\tref->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.target_value,\n+\t\t       src->value.val2.target_value, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tref->value.symref = xstrdup(src->value.symref);\n+\t\tbreak;\n+\t}\n+}\n+\n+static char hexdigit(int c)\n+{\n+\tif (c <= 9)\n+\t\treturn '0' + c;\n+\treturn 'a' + (c - 10);\n+}\n+\n+static void hex_format(char *dest, uint8_t *src, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (src) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < hash_size; i++) {\n+\t\t\tdest[2 * i] = hexdigit(src[i] >> 4);\n+\t\t\tdest[2 * i + 1] = hexdigit(src[i] & 0xf);\n+\t\t}\n+\t\tdest[2 * hash_size] = 0;\n+\t}\n+}\n+\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */\n+\tprintf(\"ref{%s(%\" PRIu64 \") \", ref->refname, ref->update_index);\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tprintf(\"=> %s\", ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\thex_format(hex, ref->value.val2.value, hash_size(hash_id));\n+\t\tprintf(\"val 2 %s\", hex);\n+\t\thex_format(hex, ref->value.val2.target_value,\n+\t\t\t   hash_size(hash_id));\n+\t\tprintf(\"(T %s)\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\thex_format(hex, ref->value.val1, hash_size(hash_id));\n+\t\tprintf(\"val 1 %s\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tprintf(\"delete\");\n+\t\tbreak;\n+\t}\n+\tprintf(\"}\\n\");\n+}\n+\n+static void reftable_ref_record_release_void(void *rec)\n+{\n+\treftable_ref_record_release(rec);\n+}\n+\n+void reftable_ref_record_release(struct reftable_ref_record *ref)\n+{\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treftable_free(ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treftable_free(ref->value.val2.target_value);\n+\t\treftable_free(ref->value.val2.value);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\treftable_free(ref->value.val1);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treftable_free(ref->refname);\n+\tmemset(ref, 0, sizeof(struct reftable_ref_record));\n+}\n+\n+static uint8_t reftable_ref_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\treturn r->value_type;\n+}\n+\n+static int reftable_ref_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\tstruct string_view start = s;\n+\tint n = put_var_int(&s, r->update_index);\n+\tassert(hash_size > 0);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tn = encode_string(r->value.symref, s);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (s.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val2.value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tmemcpy(s.buf, r->value.val2.target_value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (s.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val1, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_ref_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct reftable_ref_record *r = rec;\n+\tstruct string_view start = in;\n+\tuint64_t update_index = 0;\n+\tint n = get_var_int(&update_index, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\treftable_ref_record_release(r);\n+\n+\tassert(hash_size > 0);\n+\n+\tr->refname = reftable_realloc(r->refname, key.len + 1);\n+\tmemcpy(r->refname, key.buf, key.len);\n+\tr->update_index = update_index;\n+\tr->refname[key.len] = 0;\n+\tr->value_type = val_type;\n+\tswitch (val_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (in.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val1, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (in.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\n+\t\tr->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.target_value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_SYMREF: {\n+\t\tstruct strbuf dest = STRBUF_INIT;\n+\t\tint n = decode_string(&dest, in);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\t\tr->value.symref = dest.buf;\n+\t} break;\n+\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t\tbreak;\n+\t}\n+\n+\treturn start.len - in.len;\n+}\n+\n+static int reftable_ref_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_ref_record_is_deletion(\n+\t\t(const struct reftable_ref_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_ref_record_vtable = {\n+\t.key = &reftable_ref_record_key,\n+\t.type = BLOCK_TYPE_REF,\n+\t.copy_from = &reftable_ref_record_copy_from,\n+\t.val_type = &reftable_ref_record_val_type,\n+\t.encode = &reftable_ref_record_encode,\n+\t.decode = &reftable_ref_record_decode,\n+\t.release = &reftable_ref_record_release_void,\n+\t.is_deletion = &reftable_ref_record_is_deletion_void,\n+};\n+\n+static void reftable_obj_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_obj_record *rec =\n+\t\t(const struct reftable_obj_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len);\n+}\n+\n+static void reftable_obj_record_release(void *rec)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tFREE_AND_NULL(obj->hash_prefix);\n+\tFREE_AND_NULL(obj->offsets);\n+\tmemset(obj, 0, sizeof(struct reftable_obj_record));\n+}\n+\n+static void reftable_obj_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tconst struct reftable_obj_record *src =\n+\t\t(const struct reftable_obj_record *)src_rec;\n+\n+\treftable_obj_record_release(obj);\n+\t*obj = *src;\n+\tobj->hash_prefix = reftable_malloc(obj->hash_prefix_len);\n+\tmemcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);\n+\n+\tobj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));\n+\tCOPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);\n+}\n+\n+static uint8_t reftable_obj_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tif (r->offset_len > 0 && r->offset_len < 8)\n+\t\treturn r->offset_len;\n+\treturn 0;\n+}\n+\n+static int reftable_obj_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tstruct string_view start = s;\n+\tint i = 0;\n+\tint n = 0;\n+\tuint64_t last = 0;\n+\tif (r->offset_len == 0 || r->offset_len >= 8) {\n+\t\tn = put_var_int(&s, r->offset_len);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t}\n+\tif (r->offset_len == 0)\n+\t\treturn start.len - s.len;\n+\tn = put_var_int(&s, r->offsets[0]);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tlast = r->offsets[0];\n+\tfor (i = 1; i < r->offset_len; i++) {\n+\t\tint n = put_var_int(&s, r->offsets[i] - last);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tlast = r->offsets[i];\n+\t}\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_obj_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_obj_record *r = rec;\n+\tuint64_t count = val_type;\n+\tint n = 0;\n+\tuint64_t last;\n+\tint j;\n+\tr->hash_prefix = reftable_malloc(key.len);\n+\tmemcpy(r->hash_prefix, key.buf, key.len);\n+\tr->hash_prefix_len = key.len;\n+\n+\tif (val_type == 0) {\n+\t\tn = get_var_int(&count, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\n+\t\tstring_view_consume(&in, n);\n+\t}\n+\n+\tr->offsets = NULL;\n+\tr->offset_len = 0;\n+\tif (count == 0)\n+\t\treturn start.len - in.len;\n+\n+\tr->offsets = reftable_malloc(count * sizeof(uint64_t));\n+\tr->offset_len = count;\n+\n+\tn = get_var_int(&r->offsets[0], &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\tlast = r->offsets[0];\n+\tj = 1;\n+\twhile (j < count) {\n+\t\tuint64_t delta = 0;\n+\t\tint n = get_var_int(&delta, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\n+\t\tlast = r->offsets[j] = (delta + last);\n+\t\tj++;\n+\t}\n+\treturn start.len - in.len;\n+}\n+\n+static int not_a_deletion(const void *p)\n+{\n+\treturn 0;\n+}\n+\n+static struct reftable_record_vtable reftable_obj_record_vtable = {\n+\t.key = &reftable_obj_record_key,\n+\t.type = BLOCK_TYPE_OBJ,\n+\t.copy_from = &reftable_obj_record_copy_from,\n+\t.val_type = &reftable_obj_record_val_type,\n+\t.encode = &reftable_obj_record_encode,\n+\t.decode = &reftable_obj_record_decode,\n+\t.release = &reftable_obj_record_release,\n+\t.is_deletion = not_a_deletion,\n+};\n+\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[GIT_SHA256_RAWSZ + 1] = { 0 };\n+\n+\tswitch (log->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") delete\", log->refname,\n+\t\t       log->update_index);\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") %s <%s> %\" PRIu64 \" %04d\\n\",\n+\t\t       log->refname, log->update_index, log->value.update.name,\n+\t\t       log->value.update.email, log->value.update.time,\n+\t\t       log->value.update.tz_offset);\n+\t\thex_format(hex, log->value.update.old_hash, hash_size(hash_id));\n+\t\tprintf(\"%s => \", hex);\n+\t\thex_format(hex, log->value.update.new_hash, hash_size(hash_id));\n+\t\tprintf(\"%s\\n\\n%s\\n}\\n\", hex, log->value.update.message);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_log_record *rec =\n+\t\t(const struct reftable_log_record *)r;\n+\tint len = strlen(rec->refname);\n+\tuint8_t i64[8];\n+\tuint64_t ts = 0;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, (uint8_t *)rec->refname, len + 1);\n+\n+\tts = (~ts) - rec->update_index;\n+\tput_be64(&i64[0], ts);\n+\tstrbuf_add(dest, i64, sizeof(i64));\n+}\n+\n+static void reftable_log_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_log_record *dst = rec;\n+\tconst struct reftable_log_record *src =\n+\t\t(const struct reftable_log_record *)src_rec;\n+\n+\treftable_log_record_release(dst);\n+\t*dst = *src;\n+\tif (dst->refname) {\n+\t\tdst->refname = xstrdup(dst->refname);\n+\t}\n+\tswitch (dst->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tif (dst->value.update.email) {\n+\t\t\tdst->value.update.email =\n+\t\t\t\txstrdup(dst->value.update.email);\n+\t\t}\n+\t\tif (dst->value.update.name) {\n+\t\t\tdst->value.update.name =\n+\t\t\t\txstrdup(dst->value.update.name);\n+\t\t}\n+\t\tif (dst->value.update.message) {\n+\t\t\tdst->value.update.message =\n+\t\t\t\txstrdup(dst->value.update.message);\n+\t\t}\n+\n+\t\tif (dst->value.update.new_hash) {\n+\t\t\tdst->value.update.new_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.new_hash,\n+\t\t\t       src->value.update.new_hash, hash_size);\n+\t\t}\n+\t\tif (dst->value.update.old_hash) {\n+\t\t\tdst->value.update.old_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.old_hash,\n+\t\t\t       src->value.update.old_hash, hash_size);\n+\t\t}\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_release_void(void *rec)\n+{\n+\tstruct reftable_log_record *r = rec;\n+\treftable_log_record_release(r);\n+}\n+\n+void reftable_log_record_release(struct reftable_log_record *r)\n+{\n+\treftable_free(r->refname);\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treftable_free(r->value.update.new_hash);\n+\t\treftable_free(r->value.update.old_hash);\n+\t\treftable_free(r->value.update.name);\n+\t\treftable_free(r->value.update.email);\n+\t\treftable_free(r->value.update.message);\n+\t\tbreak;\n+\t}\n+\tmemset(r, 0, sizeof(struct reftable_log_record));\n+}\n+\n+static uint8_t reftable_log_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_log_record *log =\n+\t\t(const struct reftable_log_record *)rec;\n+\n+\treturn reftable_log_record_is_deletion(log) ? 0 : 1;\n+}\n+\n+static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 };\n+\n+static int reftable_log_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_log_record *r = rec;\n+\tstruct string_view start = s;\n+\tint n = 0;\n+\tuint8_t *oldh = NULL;\n+\tuint8_t *newh = NULL;\n+\tif (reftable_log_record_is_deletion(r))\n+\t\treturn 0;\n+\n+\toldh = r->value.update.old_hash;\n+\tnewh = r->value.update.new_hash;\n+\tif (!oldh) {\n+\t\toldh = zero;\n+\t}\n+\tif (!newh) {\n+\t\tnewh = zero;\n+\t}\n+\n+\tif (s.len < 2 * hash_size)\n+\t\treturn -1;\n+\n+\tmemcpy(s.buf, oldh, hash_size);\n+\tmemcpy(s.buf + hash_size, newh, hash_size);\n+\tstring_view_consume(&s, 2 * hash_size);\n+\n+\tn = encode_string(r->value.update.name ? r->value.update.name : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = encode_string(r->value.update.email ? r->value.update.email : \"\",\n+\t\t\t  s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = put_var_int(&s, r->value.update.time);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tif (s.len < 2)\n+\t\treturn -1;\n+\n+\tput_be16(s.buf, r->value.update.tz_offset);\n+\tstring_view_consume(&s, 2);\n+\n+\tn = encode_string(\n+\t\tr->value.update.message ? r->value.update.message : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_log_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_log_record *r = rec;\n+\tuint64_t max = 0;\n+\tuint64_t ts = 0;\n+\tstruct strbuf dest = STRBUF_INIT;\n+\tint n;\n+\n+\tif (key.len <= 9 || key.buf[key.len - 9] != 0)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->refname = reftable_realloc(r->refname, key.len - 8);\n+\tmemcpy(r->refname, key.buf, key.len - 8);\n+\tts = get_be64(key.buf + key.len - 8);\n+\n+\tr->update_index = (~max) - ts;\n+\n+\tif (val_type != r->value_type) {\n+\t\tswitch (r->value_type) {\n+\t\tcase REFTABLE_LOG_UPDATE:\n+\t\t\tFREE_AND_NULL(r->value.update.old_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.new_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.message);\n+\t\t\tFREE_AND_NULL(r->value.update.email);\n+\t\t\tFREE_AND_NULL(r->value.update.name);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_LOG_DELETION:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tr->value_type = val_type;\n+\tif (val_type == REFTABLE_LOG_DELETION)\n+\t\treturn 0;\n+\n+\tif (in.len < 2 * hash_size)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->value.update.old_hash =\n+\t\treftable_realloc(r->value.update.old_hash, hash_size);\n+\tr->value.update.new_hash =\n+\t\treftable_realloc(r->value.update.new_hash, hash_size);\n+\n+\tmemcpy(r->value.update.old_hash, in.buf, hash_size);\n+\tmemcpy(r->value.update.new_hash, in.buf + hash_size, hash_size);\n+\n+\tstring_view_consume(&in, 2 * hash_size);\n+\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.name =\n+\t\treftable_realloc(r->value.update.name, dest.len + 1);\n+\tmemcpy(r->value.update.name, dest.buf, dest.len);\n+\tr->value.update.name[dest.len] = 0;\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.email =\n+\t\treftable_realloc(r->value.update.email, dest.len + 1);\n+\tmemcpy(r->value.update.email, dest.buf, dest.len);\n+\tr->value.update.email[dest.len] = 0;\n+\n+\tts = 0;\n+\tn = get_var_int(&ts, &in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\tr->value.update.time = ts;\n+\tif (in.len < 2)\n+\t\tgoto done;\n+\n+\tr->value.update.tz_offset = get_be16(in.buf);\n+\tstring_view_consume(&in, 2);\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.message =\n+\t\treftable_realloc(r->value.update.message, dest.len + 1);\n+\tmemcpy(r->value.update.message, dest.buf, dest.len);\n+\tr->value.update.message[dest.len] = 0;\n+\n+\tstrbuf_release(&dest);\n+\treturn start.len - in.len;\n+\n+done:\n+\tstrbuf_release(&dest);\n+\treturn REFTABLE_FORMAT_ERROR;\n+}\n+\n+static int null_streq(char *a, char *b)\n+{\n+\tchar *empty = \"\";\n+\tif (!a)\n+\t\ta = empty;\n+\n+\tif (!b)\n+\t\tb = empty;\n+\n+\treturn 0 == strcmp(a, b);\n+}\n+\n+static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)\n+{\n+\tif (!a)\n+\t\ta = zero;\n+\n+\tif (!b)\n+\t\tb = zero;\n+\n+\treturn !memcmp(a, b, sz);\n+}\n+\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size)\n+{\n+\tif (!(null_streq(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\treturn 1;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treturn null_streq(a->value.update.name, b->value.update.name) &&\n+\t\t       a->value.update.time == b->value.update.time &&\n+\t\t       a->value.update.tz_offset == b->value.update.tz_offset &&\n+\t\t       null_streq(a->value.update.email,\n+\t\t\t\t  b->value.update.email) &&\n+\t\t       null_streq(a->value.update.message,\n+\t\t\t\t  b->value.update.message) &&\n+\t\t       zero_hash_eq(a->value.update.old_hash,\n+\t\t\t\t    b->value.update.old_hash, hash_size) &&\n+\t\t       zero_hash_eq(a->value.update.new_hash,\n+\t\t\t\t    b->value.update.new_hash, hash_size);\n+\t}\n+\n+\tabort();\n+}\n+\n+static int reftable_log_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_log_record_is_deletion(\n+\t\t(const struct reftable_log_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_log_record_vtable = {\n+\t.key = &reftable_log_record_key,\n+\t.type = BLOCK_TYPE_LOG,\n+\t.copy_from = &reftable_log_record_copy_from,\n+\t.val_type = &reftable_log_record_val_type,\n+\t.encode = &reftable_log_record_encode,\n+\t.decode = &reftable_log_record_decode,\n+\t.release = &reftable_log_record_release_void,\n+\t.is_deletion = &reftable_log_record_is_deletion_void,\n+};\n+\n+struct reftable_record reftable_new_record(uint8_t typ)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF: {\n+\t\tstruct reftable_ref_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_ref_record));\n+\t\treftable_record_from_ref(&rec, r);\n+\t\treturn rec;\n+\t}\n+\n+\tcase BLOCK_TYPE_OBJ: {\n+\t\tstruct reftable_obj_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_obj_record));\n+\t\treftable_record_from_obj(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_LOG: {\n+\t\tstruct reftable_log_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_log_record));\n+\t\treftable_record_from_log(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_INDEX: {\n+\t\tstruct reftable_index_record empty = { .last_key =\n+\t\t\t\t\t\t\t       STRBUF_INIT };\n+\t\tstruct reftable_index_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_index_record));\n+\t\t*r = empty;\n+\t\treftable_record_from_index(&rec, r);\n+\t\treturn rec;\n+\t}\n+\t}\n+\tabort();\n+\treturn rec;\n+}\n+\n+/* clear out the record, yielding the reftable_record data that was\n+ * encapsulated. */\n+static void *reftable_record_yield(struct reftable_record *rec)\n+{\n+\tvoid *p = rec->data;\n+\trec->data = NULL;\n+\treturn p;\n+}\n+\n+void reftable_record_destroy(struct reftable_record *rec)\n+{\n+\treftable_record_release(rec);\n+\treftable_free(reftable_record_yield(rec));\n+}\n+\n+static void reftable_index_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_index_record *rec = r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addbuf(dest, &rec->last_key);\n+}\n+\n+static void reftable_index_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t    int hash_size)\n+{\n+\tstruct reftable_index_record *dst = rec;\n+\tconst struct reftable_index_record *src = src_rec;\n+\n+\tstrbuf_reset(&dst->last_key);\n+\tstrbuf_addbuf(&dst->last_key, &src->last_key);\n+\tdst->offset = src->offset;\n+}\n+\n+static void reftable_index_record_release(void *rec)\n+{\n+\tstruct reftable_index_record *idx = rec;\n+\tstrbuf_release(&idx->last_key);\n+}\n+\n+static uint8_t reftable_index_record_val_type(const void *rec)\n+{\n+\treturn 0;\n+}\n+\n+static int reftable_index_record_encode(const void *rec, struct string_view out,\n+\t\t\t\t\tint hash_size)\n+{\n+\tconst struct reftable_index_record *r =\n+\t\t(const struct reftable_index_record *)rec;\n+\tstruct string_view start = out;\n+\n+\tint n = put_var_int(&out, r->offset);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&out, n);\n+\n+\treturn start.len - out.len;\n+}\n+\n+static int reftable_index_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t\tuint8_t val_type, struct string_view in,\n+\t\t\t\t\tint hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_index_record *r = rec;\n+\tint n = 0;\n+\n+\tstrbuf_reset(&r->last_key);\n+\tstrbuf_addbuf(&r->last_key, &key);\n+\n+\tn = get_var_int(&r->offset, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&in, n);\n+\treturn start.len - in.len;\n+}\n+\n+static struct reftable_record_vtable reftable_index_record_vtable = {\n+\t.key = &reftable_index_record_key,\n+\t.type = BLOCK_TYPE_INDEX,\n+\t.copy_from = &reftable_index_record_copy_from,\n+\t.val_type = &reftable_index_record_val_type,\n+\t.encode = &reftable_index_record_encode,\n+\t.decode = &reftable_index_record_decode,\n+\t.release = &reftable_index_record_release,\n+\t.is_deletion = &not_a_deletion,\n+};\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)\n+{\n+\trec->ops->key(rec->data, dest);\n+}\n+\n+uint8_t reftable_record_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->type;\n+}\n+\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size)\n+{\n+\treturn rec->ops->encode(rec->data, dest, hash_size);\n+}\n+\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size)\n+{\n+\tassert(src->ops->type == rec->ops->type);\n+\n+\trec->ops->copy_from(rec->data, src->data, hash_size);\n+}\n+\n+uint8_t reftable_record_val_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->val_type(rec->data);\n+}\n+\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src, int hash_size)\n+{\n+\treturn rec->ops->decode(rec->data, key, extra, src, hash_size);\n+}\n+\n+void reftable_record_release(struct reftable_record *rec)\n+{\n+\trec->ops->release(rec->data);\n+}\n+\n+int reftable_record_is_deletion(struct reftable_record *rec)\n+{\n+\treturn rec->ops->is_deletion(rec->data);\n+}\n+\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *ref_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = ref_rec;\n+\trec->ops = &reftable_ref_record_vtable;\n+}\n+\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *obj_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = obj_rec;\n+\trec->ops = &reftable_obj_record_vtable;\n+}\n+\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *index_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = index_rec;\n+\trec->ops = &reftable_index_record_vtable;\n+}\n+\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *log_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = log_rec;\n+\trec->ops = &reftable_log_record_vtable;\n+}\n+\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_REF);\n+\treturn rec->data;\n+}\n+\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_LOG);\n+\treturn rec->data;\n+}\n+\n+static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)\n+{\n+\tif (a && b)\n+\t\treturn !memcmp(a, b, hash_size);\n+\n+\treturn a == b;\n+}\n+\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (!(0 == strcmp(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treturn !strcmp(a->value.symref, b->value.symref);\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn hash_equal(a->value.val2.value, b->value.val2.value,\n+\t\t\t\t  hash_size) &&\n+\t\t       hash_equal(a->value.val2.target_value,\n+\t\t\t\t  b->value.val2.target_value, hash_size);\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn hash_equal(a->value.val1, b->value.val1, hash_size);\n+\tcase REFTABLE_REF_DELETION:\n+\t\treturn 1;\n+\tdefault:\n+\t\tabort();\n+\t}\n+}\n+\n+int reftable_ref_record_compare_name(const void *a, const void *b)\n+{\n+\treturn strcmp(((struct reftable_ref_record *)a)->refname,\n+\t\t      ((struct reftable_ref_record *)b)->refname);\n+}\n+\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref)\n+{\n+\treturn ref->value_type == REFTABLE_REF_DELETION;\n+}\n+\n+int reftable_log_record_compare_key(const void *a, const void *b)\n+{\n+\tconst struct reftable_log_record *la = a;\n+\tconst struct reftable_log_record *lb = b;\n+\n+\tint cmp = strcmp(la->refname, lb->refname);\n+\tif (cmp)\n+\t\treturn cmp;\n+\tif (la->update_index > lb->update_index)\n+\t\treturn -1;\n+\treturn (la->update_index < lb->update_index) ? 1 : 0;\n+}\n+\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log)\n+{\n+\treturn (log->value_type == REFTABLE_LOG_DELETION);\n+}\n+\n+void string_view_consume(struct string_view *s, int n)\n+{\n+\ts->buf += n;\n+\ts->len -= n;\n+}\ndiff --git a/reftable/record.h b/reftable/record.h\nnew file mode 100644\nindex 00000000000..498e8c50bf4\n--- /dev/null\n+++ b/reftable/record.h\n@@ -0,0 +1,139 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef RECORD_H\n+#define RECORD_H\n+\n+#include \"system.h\"\n+\n+#include <stdint.h>\n+\n+#include \"reftable-record.h\"\n+\n+/*\n+ * A substring of existing string data. This structure takes no responsibility\n+ * for the lifetime of the data it points to.\n+ */\n+struct string_view {\n+\tuint8_t *buf;\n+\tsize_t len;\n+};\n+\n+/* Advance `s.buf` by `n`, and decrease length. */\n+void string_view_consume(struct string_view *s, int n);\n+\n+/* utilities for de/encoding varints */\n+\n+int get_var_int(uint64_t *dest, struct string_view *in);\n+int put_var_int(struct string_view *dest, uint64_t val);\n+\n+/* Methods for records. */\n+struct reftable_record_vtable {\n+\t/* encode the key of to a uint8_t strbuf. */\n+\tvoid (*key)(const void *rec, struct strbuf *dest);\n+\n+\t/* The record type of ('r' for ref). */\n+\tuint8_t type;\n+\n+\tvoid (*copy_from)(void *dest, const void *src, int hash_size);\n+\n+\t/* a value of [0..7], indicating record subvariants (eg. ref vs. symref\n+\t * vs ref deletion) */\n+\tuint8_t (*val_type)(const void *rec);\n+\n+\t/* encodes rec into dest, returning how much space was used. */\n+\tint (*encode)(const void *rec, struct string_view dest, int hash_size);\n+\n+\t/* decode data from `src` into the record. */\n+\tint (*decode)(void *rec, struct strbuf key, uint8_t extra,\n+\t\t      struct string_view src, int hash_size);\n+\n+\t/* deallocate and null the record. */\n+\tvoid (*release)(void *rec);\n+\n+\t/* is this a tombstone? */\n+\tint (*is_deletion)(const void *rec);\n+};\n+\n+/* record is a generic wrapper for different types of records. */\n+struct reftable_record {\n+\tvoid *data;\n+\tstruct reftable_record_vtable *ops;\n+};\n+\n+/* returns true for recognized block types. Block start with the block type. */\n+int reftable_is_block_type(uint8_t typ);\n+\n+/* creates a malloced record of the given type. Dispose with record_destroy */\n+struct reftable_record reftable_new_record(uint8_t typ);\n+\n+/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns\n+ * number of bytes written. */\n+int reftable_encode_key(int *is_restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra);\n+\n+/* Decode into `key` and `extra` from `in` */\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in);\n+\n+/* reftable_index_record are used internally to speed up lookups. */\n+struct reftable_index_record {\n+\tuint64_t offset; /* Offset of block */\n+\tstruct strbuf last_key; /* Last key of the block. */\n+};\n+\n+/* reftable_obj_record stores an object ID => ref mapping. */\n+struct reftable_obj_record {\n+\tuint8_t *hash_prefix; /* leading bytes of the object ID */\n+\tint hash_prefix_len; /* number of leading bytes. Constant\n+\t\t\t      * across a single table. */\n+\tuint64_t *offsets; /* a vector of file offsets. */\n+\tint offset_len;\n+};\n+\n+/* see struct record_vtable */\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);\n+uint8_t reftable_record_type(struct reftable_record *rec);\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size);\n+uint8_t reftable_record_val_type(struct reftable_record *rec);\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size);\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src,\n+\t\t\t   int hash_size);\n+int reftable_record_is_deletion(struct reftable_record *rec);\n+\n+/* zeroes out the embedded record */\n+void reftable_record_release(struct reftable_record *rec);\n+\n+/* clear and deallocate embedded record, and zero `rec`. */\n+void reftable_record_destroy(struct reftable_record *rec);\n+\n+/* initialize generic records from concrete records. The generic record should\n+ * be zeroed out. */\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *objrec);\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *idxrec);\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *refrec);\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *logrec);\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);\n+\n+/* for qsort. */\n+int reftable_ref_record_compare_name(const void *a, const void *b);\n+\n+/* for qsort. */\n+int reftable_log_record_compare_key(const void *a, const void *b);\n+\n+#endif\ndiff --git a/reftable/record_test.c b/reftable/record_test.c\nnew file mode 100644\nindex 00000000000..f4ad7cace41\n--- /dev/null\n+++ b/reftable/record_test.c\n@@ -0,0 +1,412 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_copy(struct reftable_record *rec)\n+{\n+\tstruct reftable_record copy =\n+\t\treftable_new_record(reftable_record_type(rec));\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\t/* do it twice to catch memory leaks */\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\tswitch (reftable_record_type(&copy)) {\n+\tcase BLOCK_TYPE_REF:\n+\t\tEXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),\n+\t\t\t\t\t\t reftable_record_as_ref(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\tcase BLOCK_TYPE_LOG:\n+\t\tEXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),\n+\t\t\t\t\t\t reftable_record_as_log(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\t}\n+\treftable_record_destroy(&copy);\n+}\n+\n+static void test_varint_roundtrip(void)\n+{\n+\tuint64_t inputs[] = { 0,\n+\t\t\t      1,\n+\t\t\t      27,\n+\t\t\t      127,\n+\t\t\t      128,\n+\t\t\t      257,\n+\t\t\t      4096,\n+\t\t\t      ((uint64_t)1 << 63),\n+\t\t\t      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(inputs); i++) {\n+\t\tuint8_t dest[10];\n+\n+\t\tstruct string_view out = {\n+\t\t\t.buf = dest,\n+\t\t\t.len = sizeof(dest),\n+\t\t};\n+\t\tuint64_t in = inputs[i];\n+\t\tint n = put_var_int(&out, in);\n+\t\tuint64_t got = 0;\n+\n+\t\tEXPECT(n > 0);\n+\t\tout.len = n;\n+\t\tn = get_var_int(&got, &out);\n+\t\tEXPECT(n > 0);\n+\n+\t\tEXPECT(got == in);\n+\t}\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct {\n+\t\tconst char *a, *b;\n+\t\tint want;\n+\t} cases[] = {\n+\t\t{ \"abc\", \"ab\", 2 },\n+\t\t{ \"\", \"abc\", 0 },\n+\t\t{ \"abc\", \"abd\", 2 },\n+\t\t{ \"abc\", \"pqr\", 0 },\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\tEXPECT(common_prefix_size(&a, &b) == cases[i].want);\n+\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n+}\n+\n+static void set_hash(uint8_t *h, int j)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < hash_size(GIT_SHA1_FORMAT_ID); i++) {\n+\t\th[i] = (j >> i) & 0xff;\n+\t}\n+}\n+\n+static void test_reftable_ref_record_roundtrip(void)\n+{\n+\tint i = 0;\n+\n+\tfor (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {\n+\t\tstruct reftable_ref_record in = { NULL };\n+\t\tstruct reftable_ref_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\n+\t\tint n, m;\n+\n+\t\tin.value_type = i;\n+\t\tswitch (i) {\n+\t\tcase REFTABLE_REF_DELETION:\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL1:\n+\t\t\tin.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val1, 1);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL2:\n+\t\t\tin.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.value, 1);\n+\t\t\tin.value.val2.target_value =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.target_value, 2);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_SYMREF:\n+\t\t\tin.value.symref = xstrdup(\"target\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tin.refname = xstrdup(\"refs/heads/master\");\n+\n+\t\treftable_record_from_ref(&rec, &in);\n+\t\ttest_copy(&rec);\n+\n+\t\tEXPECT(reftable_record_val_type(&rec) == i);\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\n+\t\t/* decode into a non-zero reftable_record to test for leaks. */\n+\n+\t\treftable_record_from_ref(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, i, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));\n+\t\treftable_record_release(&rec_out);\n+\n+\t\tstrbuf_release(&key);\n+\t\treftable_ref_record_release(&in);\n+\t}\n+}\n+\n+static void test_reftable_log_record_equal(void)\n+{\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t}\n+\t};\n+\n+\tEXPECT(!reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\tin[1].update_index = in[0].update_index;\n+\tEXPECT(reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\treftable_log_record_release(&in[0]);\n+\treftable_log_record_release(&in[1]);\n+}\n+\n+static void test_reftable_log_record_roundtrip(void)\n+{\n+\tint i;\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.old_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.new_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"han-wen\"),\n+\t\t\t\t\t.email = xstrdup(\"hanwen@google.com\"),\n+\t\t\t\t\t.message = xstrdup(\"test\"),\n+\t\t\t\t\t.time = 1577123507,\n+\t\t\t\t\t.tz_offset = 100,\n+\t\t\t\t},\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t}\n+\t};\n+\tset_test_hash(in[0].value.update.new_hash, 1);\n+\tset_test_hash(in[0].value.update.old_hash, 2);\n+\tfor (i = 0; i < ARRAY_SIZE(in); i++) {\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\t/* populate out, to check for leaks. */\n+\t\tstruct reftable_log_record out = {\n+\t\t\t.refname = xstrdup(\"old name\"),\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"old name\"),\n+\t\t\t\t\t.email = xstrdup(\"old@email\"),\n+\t\t\t\t\t.message = xstrdup(\"old message\"),\n+\t\t\t\t},\n+\t\t\t},\n+\t\t};\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m, valtype;\n+\n+\t\treftable_record_from_log(&rec, &in[i]);\n+\n+\t\ttest_copy(&rec);\n+\n+\t\treftable_record_key(&rec, &key);\n+\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n >= 0);\n+\t\treftable_record_from_log(&rec_out, &out);\n+\t\tvaltype = reftable_record_val_type(&rec);\n+\t\tm = reftable_record_decode(&rec_out, key, valtype, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&in[i]);\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_u24_roundtrip(void)\n+{\n+\tuint32_t in = 0x112233;\n+\tuint8_t dest[3];\n+\tuint32_t out;\n+\tput_be24(dest, in);\n+\tout = get_be24(dest);\n+\tEXPECT(in == out);\n+}\n+\n+static void test_key_roundtrip(void)\n+{\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct strbuf roundtrip = STRBUF_INIT;\n+\tint restart;\n+\tuint8_t extra;\n+\tint n, m;\n+\tuint8_t rt_extra;\n+\n+\tstrbuf_addstr(&last_key, \"refs/heads/master\");\n+\tstrbuf_addstr(&key, \"refs/tags/bla\");\n+\textra = 6;\n+\tn = reftable_encode_key(&restart, dest, last_key, key, extra);\n+\tEXPECT(!restart);\n+\tEXPECT(n > 0);\n+\n+\tm = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);\n+\tEXPECT(n == m);\n+\tEXPECT(0 == strbuf_cmp(&key, &roundtrip));\n+\tEXPECT(rt_extra == extra);\n+\n+\tstrbuf_release(&last_key);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&roundtrip);\n+}\n+\n+static void test_reftable_obj_record_roundtrip(void)\n+{\n+\tuint8_t testHash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 4, 0 };\n+\tuint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };\n+\tstruct reftable_obj_record recs[3] = { {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 3,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 9,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t       } };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(recs); i++) {\n+\t\tstruct reftable_obj_record in = recs[i];\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_obj_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m;\n+\t\tuint8_t extra;\n+\n+\t\treftable_record_from_obj(&rec, &in);\n+\t\ttest_copy(&rec);\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\t\textra = reftable_record_val_type(&rec);\n+\t\treftable_record_from_obj(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, extra, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(in.hash_prefix_len == out.hash_prefix_len);\n+\t\tEXPECT(in.offset_len == out.offset_len);\n+\n+\t\tEXPECT(!memcmp(in.hash_prefix, out.hash_prefix,\n+\t\t\t       in.hash_prefix_len));\n+\t\tEXPECT(0 == memcmp(in.offsets, out.offsets,\n+\t\t\t\t   sizeof(uint64_t) * in.offset_len));\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_reftable_index_record_roundtrip(void)\n+{\n+\tstruct reftable_index_record in = {\n+\t\t.offset = 42,\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_index_record out = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record out_rec = { NULL };\n+\tint n, m;\n+\tuint8_t extra;\n+\n+\tstrbuf_addstr(&in.last_key, \"refs/heads/master\");\n+\treftable_record_from_index(&rec, &in);\n+\treftable_record_key(&rec, &key);\n+\ttest_copy(&rec);\n+\n+\tEXPECT(0 == strbuf_cmp(&key, &in.last_key));\n+\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(n > 0);\n+\n+\textra = reftable_record_val_type(&rec);\n+\treftable_record_from_index(&out_rec, &out);\n+\tm = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(m == n);\n+\n+\tEXPECT(in.offset == out.offset);\n+\n+\treftable_record_release(&out_rec);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&in.last_key);\n+}\n+\n+int record_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_reftable_log_record_equal);\n+\tRUN_TEST(test_reftable_log_record_roundtrip);\n+\tRUN_TEST(test_reftable_ref_record_roundtrip);\n+\tRUN_TEST(test_varint_roundtrip);\n+\tRUN_TEST(test_key_roundtrip);\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_reftable_obj_record_roundtrip);\n+\tRUN_TEST(test_reftable_index_record_roundtrip);\n+\tRUN_TEST(test_u24_roundtrip);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-record.h b/reftable/reftable-record.h\nnew file mode 100644\nindex 00000000000..5370d2288c7\n--- /dev/null\n+++ b/reftable/reftable-record.h\n@@ -0,0 +1,114 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_RECORD_H\n+#define REFTABLE_RECORD_H\n+\n+#include <stdint.h>\n+\n+/*\n+ * Basic data types\n+ *\n+ * Reftables store the state of each ref in struct reftable_ref_record, and they\n+ * store a sequence of reflog updates in struct reftable_log_record.\n+ */\n+\n+/* reftable_ref_record holds a ref database entry target_value */\n+struct reftable_ref_record {\n+\tchar *refname; /* Name of the ref, malloced. */\n+\tuint64_t update_index; /* Logical timestamp at which this value is\n+\t\t\t\t* written */\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_REF_DELETION = 0x0,\n+\n+\t\t/* a simple ref */\n+\t\tREFTABLE_REF_VAL1 = 0x1,\n+\t\t/* a tag, plus its peeled hash */\n+\t\tREFTABLE_REF_VAL2 = 0x2,\n+\n+\t\t/* a symbolic reference */\n+\t\tREFTABLE_REF_SYMREF = 0x3,\n+#define REFTABLE_NR_REF_VALUETYPES 4\n+\t} value_type;\n+\tunion {\n+\t\tuint8_t *val1; /* malloced hash. */\n+\t\tstruct {\n+\t\t\tuint8_t *value; /* first value, malloced hash  */\n+\t\t\tuint8_t *target_value; /* second value, malloced hash */\n+\t\t} val2;\n+\t\tchar *symref; /* referent, malloced 0-terminated string */\n+\t} value;\n+};\n+\n+/* Returns the first hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);\n+\n+/* Returns the second hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);\n+\n+/* returns whether 'ref' represents a deletion */\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);\n+\n+/* prints a reftable_ref_record onto stdout. Useful for debugging. */\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id);\n+\n+/* frees and nulls all pointer values inside `ref`. */\n+void reftable_ref_record_release(struct reftable_ref_record *ref);\n+\n+/* returns whether two reftable_ref_records are the same. Useful for testing. */\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size);\n+\n+/* reftable_log_record holds a reflog entry */\n+struct reftable_log_record {\n+\tchar *refname;\n+\tuint64_t update_index; /* logical timestamp of a transactional update.\n+\t\t\t\t*/\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_LOG_DELETION = 0x0,\n+\n+\t\t/* a simple update */\n+\t\tREFTABLE_LOG_UPDATE = 0x1,\n+#define REFTABLE_NR_LOG_VALUETYPES 2\n+\t} value_type;\n+\n+\tunion {\n+\t\tstruct {\n+\t\t\tuint8_t *new_hash;\n+\t\t\tuint8_t *old_hash;\n+\t\t\tchar *name;\n+\t\t\tchar *email;\n+\t\t\tuint64_t time;\n+\t\t\tint16_t tz_offset;\n+\t\t\tchar *message;\n+\t\t} update;\n+\t} value;\n+};\n+\n+/* returns whether 'ref' represents the deletion of a log record. */\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log);\n+\n+/* frees and nulls all pointer values. */\n+void reftable_log_record_release(struct reftable_log_record *log);\n+\n+/* returns whether two records are equal. Useful for testing. */\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size);\n+\n+/* dumps a reftable_log_record on stdout, for debugging/testing. */\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 3b58e423e7b..09d4b83ef9b 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,6 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n-\n+\trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"437312","messageId":"4f6ecd3646affde086114c2551e7e5c7266a3d40.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 07/19] Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:04Z","receivedAt":"2021-09-28T15:10:40Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will be needed for reading reflog blocks in reftable.\n\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |  7 +++\n ci/lib.sh                 |  1 +\n compat/.gitattributes     |  1 +\n compat/zlib-uncompress2.c | 95 +++++++++++++++++++++++++++++++++++++++\n config.mak.uname          |  4 ++\n configure.ac              | 13 ++++++\n 6 files changed, 121 insertions(+)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n\ndiff --git a/Makefile b/Makefile\nindex 7cbc8f95c8b..39e250c7e8c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -256,6 +256,8 @@ all::\n #\n # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.\n #\n+# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.\n+#\n # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,\n # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)\n #\n@@ -1716,6 +1718,11 @@ ifdef NO_DEFLATE_BOUND\n \tBASIC_CFLAGS += -DNO_DEFLATE_BOUND\n endif\n \n+ifdef NO_UNCOMPRESS2\n+\tBASIC_CFLAGS += -DNO_UNCOMPRESS2\n+\tREFTABLE_OBJS += compat/zlib-uncompress2.o\n+endif\n+\n ifdef NO_POSIX_GOODIES\n \tBASIC_CFLAGS += -DNO_POSIX_GOODIES\n endif\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex 476c3f369f5..5711c63979d 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -224,6 +224,7 @@ linux-gcc-default)\n \t;;\n Linux32)\n \tCC=gcc\n+\tMAKEFLAGS=\"$MAKEFLAGS NO_UNCOMPRESS2=1\"\n \t;;\n linux-musl)\n \tCC=gcc\ndiff --git a/compat/.gitattributes b/compat/.gitattributes\nnew file mode 100644\nindex 00000000000..40dbfb170da\n--- /dev/null\n+++ b/compat/.gitattributes\n@@ -0,0 +1 @@\n+/zlib-uncompress2.c\twhitespace=-indent-with-non-tab,-trailing-space\ndiff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c\nnew file mode 100644\nindex 00000000000..3dcb90b2839\n--- /dev/null\n+++ b/compat/zlib-uncompress2.c\n@@ -0,0 +1,95 @@\n+/* taken from zlib's uncompr.c\n+\n+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f\n+   Author: Mark Adler <madler@alumni.caltech.edu>\n+   Date:   Sun Jan 15 09:18:46 2017 -0800\n+\n+       zlib 1.2.11\n+\n+*/\n+\n+#include \"../reftable/system.h\"\n+#define z_const\n+\n+/*\n+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler\n+ * For conditions of distribution and use, see copyright notice in zlib.h\n+ */\n+\n+#include <zlib.h>\n+\n+/* clang-format off */\n+\n+/* ===========================================================================\n+     Decompresses the source buffer into the destination buffer.  *sourceLen is\n+   the byte length of the source buffer. Upon entry, *destLen is the total size\n+   of the destination buffer, which must be large enough to hold the entire\n+   uncompressed data. (The size of the uncompressed data must have been saved\n+   previously by the compressor and transmitted to the decompressor by some\n+   mechanism outside the scope of this compression library.) Upon exit,\n+   *destLen is the size of the decompressed data and *sourceLen is the number\n+   of source bytes consumed. Upon return, source + *sourceLen points to the\n+   first unused input byte.\n+\n+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough\n+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or\n+   Z_DATA_ERROR if the input data was corrupted, including if the input data is\n+   an incomplete zlib stream.\n+*/\n+int ZEXPORT uncompress2 (\n+    Bytef *dest,\n+    uLongf *destLen,\n+    const Bytef *source,\n+    uLong *sourceLen) {\n+    z_stream stream;\n+    int err;\n+    const uInt max = (uInt)-1;\n+    uLong len, left;\n+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */\n+\n+    len = *sourceLen;\n+    if (*destLen) {\n+        left = *destLen;\n+        *destLen = 0;\n+    }\n+    else {\n+        left = 1;\n+        dest = buf;\n+    }\n+\n+    stream.next_in = (z_const Bytef *)source;\n+    stream.avail_in = 0;\n+    stream.zalloc = (alloc_func)0;\n+    stream.zfree = (free_func)0;\n+    stream.opaque = (voidpf)0;\n+\n+    err = inflateInit(&stream);\n+    if (err != Z_OK) return err;\n+\n+    stream.next_out = dest;\n+    stream.avail_out = 0;\n+\n+    do {\n+        if (stream.avail_out == 0) {\n+            stream.avail_out = left > (uLong)max ? max : (uInt)left;\n+            left -= stream.avail_out;\n+        }\n+        if (stream.avail_in == 0) {\n+            stream.avail_in = len > (uLong)max ? max : (uInt)len;\n+            len -= stream.avail_in;\n+        }\n+        err = inflate(&stream, Z_NO_FLUSH);\n+    } while (err == Z_OK);\n+\n+    *sourceLen -= len + stream.avail_in;\n+    if (dest != buf)\n+        *destLen = stream.total_out;\n+    else if (stream.total_out && err == Z_BUF_ERROR)\n+        left = 1;\n+\n+    inflateEnd(&stream);\n+    return err == Z_STREAM_END ? Z_OK :\n+           err == Z_NEED_DICT ? Z_DATA_ERROR  :\n+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :\n+           err;\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 76516aaa9a5..4acc3e3609b 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -258,6 +258,10 @@ ifeq ($(uname_S),FreeBSD)\n \tFILENO_IS_A_MACRO = UnfortunatelyYes\n endif\n ifeq ($(uname_S),OpenBSD)\n+\t# Versions < 7.0 need compatibility layer\n+\tifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n+\t\tNO_UNCOMPRESS2 = UnfortunatelyYes\n+\tendif\n \tNO_STRCASESTR = YesPlease\n \tNO_MEMMEM = YesPlease\n \tUSE_ST_TIMESPEC = YesPlease\ndiff --git a/configure.ac b/configure.ac\nindex 031e8d3fee8..c3a913103d0 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -672,9 +672,22 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],\n \tNO_DEFLATE_BOUND=yes])\n LIBS=\"$old_LIBS\"\n \n+AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [\n+AC_LANG_PROGRAM([#include <zlib.h>],\n+ [uncompress2(NULL,NULL,NULL,NULL);])])\n+AC_MSG_CHECKING([for uncompress2 in -lz])\n+old_LIBS=\"$LIBS\"\n+LIBS=\"$LIBS -lz\"\n+AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],\n+\t[AC_MSG_RESULT([yes])],\n+\t[AC_MSG_RESULT([no])\n+\tNO_UNCOMPRESS2=yes])\n+LIBS=\"$old_LIBS\"\n+\n GIT_UNSTASH_FLAGS($ZLIB_PATH)\n \n GIT_CONF_SUBST([NO_DEFLATE_BOUND])\n+GIT_CONF_SUBST([NO_UNCOMPRESS2])\n \n #\n # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,\n-- \ngitgitgadget\n\n"},{"id":"437313","messageId":"f6c2da61208ce539710d79bd44e3256fd916cb68.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 09/19] reftable: a generic binary tree implementation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:06Z","receivedAt":"2021-09-28T15:10:44Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format includes support for an (OID => ref) map. This map can speed\nup visibility and reachability checks. In particular, various operations along\nthe fetch/push path within Gerrit have ben sped up by using this structure.\n\nThe map is constructed with help of a binary tree. Object IDs are hashes, so\nthey are uniformly distributed. Hence, the tree does not attempt forced\nrebalancing.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |  4 ++-\n reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++\n reftable/tree.h          | 34 ++++++++++++++++++++++\n reftable/tree_test.c     | 61 ++++++++++++++++++++++++++++++++++++++\n t/helper/test-reftable.c |  1 +\n 5 files changed, 162 insertions(+), 1 deletion(-)\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n\ndiff --git a/Makefile b/Makefile\nindex f4528fdbaf2..339a44f9271 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2440,11 +2440,13 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/tree.o\n \n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n-REFTABLE_TEST_OBJS += reftable/basics_test.o\n+REFTABLE_TEST_OBJS += reftable/tree_test.o\n \n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \ndiff --git a/reftable/tree.c b/reftable/tree.c\nnew file mode 100644\nindex 00000000000..82db7995dd6\n--- /dev/null\n+++ b/reftable/tree.c\n@@ -0,0 +1,63 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert)\n+{\n+\tint res;\n+\tif (*rootp == NULL) {\n+\t\tif (!insert) {\n+\t\t\treturn NULL;\n+\t\t} else {\n+\t\t\tstruct tree_node *n =\n+\t\t\t\treftable_calloc(sizeof(struct tree_node));\n+\t\t\tn->key = key;\n+\t\t\t*rootp = n;\n+\t\t\treturn *rootp;\n+\t\t}\n+\t}\n+\n+\tres = compare(key, (*rootp)->key);\n+\tif (res < 0)\n+\t\treturn tree_search(key, &(*rootp)->left, compare, insert);\n+\telse if (res > 0)\n+\t\treturn tree_search(key, &(*rootp)->right, compare, insert);\n+\treturn *rootp;\n+}\n+\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg)\n+{\n+\tif (t->left) {\n+\t\tinfix_walk(t->left, action, arg);\n+\t}\n+\taction(arg, t->key);\n+\tif (t->right) {\n+\t\tinfix_walk(t->right, action, arg);\n+\t}\n+}\n+\n+void tree_free(struct tree_node *t)\n+{\n+\tif (t == NULL) {\n+\t\treturn;\n+\t}\n+\tif (t->left) {\n+\t\ttree_free(t->left);\n+\t}\n+\tif (t->right) {\n+\t\ttree_free(t->right);\n+\t}\n+\treftable_free(t);\n+}\ndiff --git a/reftable/tree.h b/reftable/tree.h\nnew file mode 100644\nindex 00000000000..fbdd002e23a\n--- /dev/null\n+++ b/reftable/tree.h\n@@ -0,0 +1,34 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TREE_H\n+#define TREE_H\n+\n+/* tree_node is a generic binary search tree. */\n+struct tree_node {\n+\tvoid *key;\n+\tstruct tree_node *left, *right;\n+};\n+\n+/* looks for `key` in `rootp` using `compare` as comparison function. If insert\n+ * is set, insert the key if it's not found. Else, return NULL.\n+ */\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert);\n+\n+/* performs an infix walk of the tree. */\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg);\n+\n+/*\n+ * deallocates the tree nodes recursively. Keys should be deallocated separately\n+ * by walking over the tree. */\n+void tree_free(struct tree_node *t);\n+\n+#endif\ndiff --git a/reftable/tree_test.c b/reftable/tree_test.c\nnew file mode 100644\nindex 00000000000..cbff1255886\n--- /dev/null\n+++ b/reftable/tree_test.c\n@@ -0,0 +1,61 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static int test_compare(const void *a, const void *b)\n+{\n+\treturn (char *)a - (char *)b;\n+}\n+\n+struct curry {\n+\tvoid *last;\n+};\n+\n+static void check_increasing(void *arg, void *key)\n+{\n+\tstruct curry *c = arg;\n+\tif (c->last) {\n+\t\tEXPECT(test_compare(c->last, key) < 0);\n+\t}\n+\tc->last = key;\n+}\n+\n+static void test_tree(void)\n+{\n+\tstruct tree_node *root = NULL;\n+\n+\tvoid *values[11] = { NULL };\n+\tstruct tree_node *nodes[11] = { NULL };\n+\tint i = 1;\n+\tstruct curry c = { NULL };\n+\tdo {\n+\t\tnodes[i] = tree_search(values + i, &root, &test_compare, 1);\n+\t\ti = (i * 7) % 11;\n+\t} while (i != 1);\n+\n+\tfor (i = 1; i < ARRAY_SIZE(nodes); i++) {\n+\t\tEXPECT(values + i == nodes[i]->key);\n+\t\tEXPECT(nodes[i] ==\n+\t\t       tree_search(values + i, &root, &test_compare, 0));\n+\t}\n+\n+\tinfix_walk(root, check_increasing, &c);\n+\ttree_free(root);\n+}\n+\n+int tree_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_tree);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c9deeaf08c7..050551fa698 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"437314","messageId":"04f2d74df5189f473bc7a6395b9e194a1929f623.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 08/19] reftable: reading/writing blocks","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:05Z","receivedAt":"2021-09-28T15:10:44Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of block. Within a block,\nrecords are prefix compressed, with an index of offsets for fully expand keys to\nenable binary search within blocks.\n\nThis commit provides the logic to read and write these blocks.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   2 +\n reftable/block.c         | 439 +++++++++++++++++++++++++++++++++++++++\n reftable/block.h         | 127 +++++++++++\n reftable/block_test.c    | 120 +++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 689 insertions(+)\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 39e250c7e8c..f4528fdbaf2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2436,10 +2436,12 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\ndiff --git a/reftable/block.c b/reftable/block.c\nnew file mode 100644\nindex 00000000000..11387b260aa\n--- /dev/null\n+++ b/reftable/block.c\n@@ -0,0 +1,439 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+#include <zlib.h>\n+\n+int header_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 24;\n+\tcase 2:\n+\t\treturn 28;\n+\t}\n+\tabort();\n+}\n+\n+int footer_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 68;\n+\tcase 2:\n+\t\treturn 72;\n+\t}\n+\tabort();\n+}\n+\n+static int block_writer_register_restart(struct block_writer *w, int n,\n+\t\t\t\t\t int is_restart, struct strbuf *key)\n+{\n+\tint rlen = w->restart_len;\n+\tif (rlen >= MAX_RESTARTS) {\n+\t\tis_restart = 0;\n+\t}\n+\n+\tif (is_restart) {\n+\t\trlen++;\n+\t}\n+\tif (2 + 3 * rlen + n > w->block_size - w->next)\n+\t\treturn -1;\n+\tif (is_restart) {\n+\t\tif (w->restart_len == w->restart_cap) {\n+\t\t\tw->restart_cap = w->restart_cap * 2 + 1;\n+\t\t\tw->restarts = reftable_realloc(\n+\t\t\t\tw->restarts, sizeof(uint32_t) * w->restart_cap);\n+\t\t}\n+\n+\t\tw->restarts[w->restart_len++] = w->next;\n+\t}\n+\n+\tw->next += n;\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, key);\n+\tw->entries++;\n+\treturn 0;\n+}\n+\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size)\n+{\n+\tbw->buf = buf;\n+\tbw->hash_size = hash_size;\n+\tbw->block_size = block_size;\n+\tbw->header_off = header_off;\n+\tbw->buf[header_off] = typ;\n+\tbw->next = header_off + 4;\n+\tbw->restart_interval = 16;\n+\tbw->entries = 0;\n+\tbw->restart_len = 0;\n+\tbw->last_key.len = 0;\n+}\n+\n+uint8_t block_writer_type(struct block_writer *bw)\n+{\n+\treturn bw->buf[bw->header_off];\n+}\n+\n+/* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on\n+   success */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tstruct strbuf last =\n+\t\tw->entries % w->restart_interval == 0 ? empty : w->last_key;\n+\tstruct string_view out = {\n+\t\t.buf = w->buf + w->next,\n+\t\t.len = w->block_size - w->next,\n+\t};\n+\n+\tstruct string_view start = out;\n+\n+\tint is_restart = 0;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint n = 0;\n+\n+\treftable_record_key(rec, &key);\n+\tn = reftable_encode_key(&is_restart, out, last, key,\n+\t\t\t\treftable_record_val_type(rec));\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tn = reftable_record_encode(rec, out, w->hash_size);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tif (block_writer_register_restart(w, start.len - out.len, is_restart,\n+\t\t\t\t\t  &key) < 0)\n+\t\tgoto done;\n+\n+\tstrbuf_release(&key);\n+\treturn 0;\n+\n+done:\n+\tstrbuf_release(&key);\n+\treturn -1;\n+}\n+\n+int block_writer_finish(struct block_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->restart_len; i++) {\n+\t\tput_be24(w->buf + w->next, w->restarts[i]);\n+\t\tw->next += 3;\n+\t}\n+\n+\tput_be16(w->buf + w->next, w->restart_len);\n+\tw->next += 2;\n+\tput_be24(w->buf + 1 + w->header_off, w->next);\n+\n+\tif (block_writer_type(w) == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + w->header_off;\n+\t\tuint8_t *compressed = NULL;\n+\t\tint zresult = 0;\n+\t\tuLongf src_len = w->next - block_header_skip;\n+\t\tsize_t dest_cap = src_len;\n+\n+\t\tcompressed = reftable_malloc(dest_cap);\n+\t\twhile (1) {\n+\t\t\tuLongf out_dest_len = dest_cap;\n+\n+\t\t\tzresult = compress2(compressed, &out_dest_len,\n+\t\t\t\t\t    w->buf + block_header_skip, src_len,\n+\t\t\t\t\t    9);\n+\t\t\tif (zresult == Z_BUF_ERROR) {\n+\t\t\t\tdest_cap *= 2;\n+\t\t\t\tcompressed =\n+\t\t\t\t\treftable_realloc(compressed, dest_cap);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (Z_OK != zresult) {\n+\t\t\t\treftable_free(compressed);\n+\t\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t\t}\n+\n+\t\t\tmemcpy(w->buf + block_header_skip, compressed,\n+\t\t\t       out_dest_len);\n+\t\t\tw->next = out_dest_len + block_header_skip;\n+\t\t\treftable_free(compressed);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn w->next;\n+}\n+\n+uint8_t block_reader_type(struct block_reader *r)\n+{\n+\treturn r->block.data[r->header_off];\n+}\n+\n+int block_reader_init(struct block_reader *br, struct reftable_block *block,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size)\n+{\n+\tuint32_t full_block_size = table_block_size;\n+\tuint8_t typ = block->data[header_off];\n+\tuint32_t sz = get_be24(block->data + header_off + 1);\n+\n+\tuint16_t restart_count = 0;\n+\tuint32_t restart_start = 0;\n+\tuint8_t *restart_bytes = NULL;\n+\n+\tif (!reftable_is_block_type(typ))\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tif (typ == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + header_off;\n+\t\tuLongf dst_len = sz - block_header_skip; /* total size of dest\n+\t\t\t\t\t\t\t    buffer. */\n+\t\tuLongf src_len = block->len - block_header_skip;\n+\t\t/* Log blocks specify the *uncompressed* size in their header.\n+\t\t */\n+\t\tuint8_t *uncompressed = reftable_malloc(sz);\n+\n+\t\t/* Copy over the block header verbatim. It's not compressed. */\n+\t\tmemcpy(uncompressed, block->data, block_header_skip);\n+\n+\t\t/* Uncompress */\n+\t\tif (Z_OK !=\n+\t\t    uncompress2(uncompressed + block_header_skip, &dst_len,\n+\t\t\t\tblock->data + block_header_skip, &src_len)) {\n+\t\t\treftable_free(uncompressed);\n+\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t}\n+\n+\t\tif (dst_len + block_header_skip != sz)\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\t\t/* We're done with the input data. */\n+\t\treftable_block_done(block);\n+\t\tblock->data = uncompressed;\n+\t\tblock->len = sz;\n+\t\tblock->source = malloc_block_source();\n+\t\tfull_block_size = src_len + block_header_skip;\n+\t} else if (full_block_size == 0) {\n+\t\tfull_block_size = sz;\n+\t} else if (sz < full_block_size && sz < block->len &&\n+\t\t   block->data[sz] != 0) {\n+\t\t/* If the block is smaller than the full block size, it is\n+\t\t   padded (data followed by '\\0') or the next block is\n+\t\t   unaligned. */\n+\t\tfull_block_size = sz;\n+\t}\n+\n+\trestart_count = get_be16(block->data + sz - 2);\n+\trestart_start = sz - 2 - 3 * restart_count;\n+\trestart_bytes = block->data + restart_start;\n+\n+\t/* transfer ownership. */\n+\tbr->block = *block;\n+\tblock->data = NULL;\n+\tblock->len = 0;\n+\n+\tbr->hash_size = hash_size;\n+\tbr->block_len = restart_start;\n+\tbr->full_block_size = full_block_size;\n+\tbr->header_off = header_off;\n+\tbr->restart_count = restart_count;\n+\tbr->restart_bytes = restart_bytes;\n+\n+\treturn 0;\n+}\n+\n+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)\n+{\n+\treturn get_be24(br->restart_bytes + 3 * i);\n+}\n+\n+void block_reader_start(struct block_reader *br, struct block_iter *it)\n+{\n+\tit->br = br;\n+\tstrbuf_reset(&it->last_key);\n+\tit->next_off = br->header_off + 4;\n+}\n+\n+struct restart_find_args {\n+\tint error;\n+\tstruct strbuf key;\n+\tstruct block_reader *r;\n+};\n+\n+static int restart_key_less(size_t idx, void *args)\n+{\n+\tstruct restart_find_args *a = args;\n+\tuint32_t off = block_reader_restart_offset(a->r, idx);\n+\tstruct string_view in = {\n+\t\t.buf = a->r->block.data + off,\n+\t\t.len = a->r->block_len - off,\n+\t};\n+\n+\t/* the restart key is verbatim in the block, so this could avoid the\n+\t   alloc for decoding the key */\n+\tstruct strbuf rkey = STRBUF_INIT;\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tuint8_t unused_extra;\n+\tint n = reftable_decode_key(&rkey, &unused_extra, last_key, in);\n+\tint result;\n+\tif (n < 0) {\n+\t\ta->error = 1;\n+\t\treturn -1;\n+\t}\n+\n+\tresult = strbuf_cmp(&a->key, &rkey);\n+\tstrbuf_release(&rkey);\n+\treturn result;\n+}\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)\n+{\n+\tdest->br = src->br;\n+\tdest->next_off = src->next_off;\n+\tstrbuf_reset(&dest->last_key);\n+\tstrbuf_addbuf(&dest->last_key, &src->last_key);\n+}\n+\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec)\n+{\n+\tstruct string_view in = {\n+\t\t.buf = it->br->block.data + it->next_off,\n+\t\t.len = it->br->block_len - it->next_off,\n+\t};\n+\tstruct string_view start = in;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tuint8_t extra = 0;\n+\tint n = 0;\n+\n+\tif (it->next_off >= it->br->block_len)\n+\t\treturn 1;\n+\n+\tn = reftable_decode_key(&key, &extra, it->last_key, in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\n+\tstring_view_consume(&in, n);\n+\tn = reftable_record_decode(rec, key, extra, in, it->br->hash_size);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tstrbuf_reset(&it->last_key);\n+\tstrbuf_addbuf(&it->last_key, &key);\n+\tit->next_off += start.len - in.len;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tint off = br->header_off + 4;\n+\tstruct string_view in = {\n+\t\t.buf = br->block.data + off,\n+\t\t.len = br->block_len - off,\n+\t};\n+\n+\tuint8_t extra = 0;\n+\tint n = reftable_decode_key(key, &extra, empty, in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\treturn 0;\n+}\n+\n+int block_iter_seek(struct block_iter *it, struct strbuf *want)\n+{\n+\treturn block_reader_seek(it->br, it, want);\n+}\n+\n+void block_iter_close(struct block_iter *it)\n+{\n+\tstrbuf_release(&it->last_key);\n+}\n+\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want)\n+{\n+\tstruct restart_find_args args = {\n+\t\t.key = *want,\n+\t\t.r = br,\n+\t};\n+\tstruct reftable_record rec = reftable_new_record(block_reader_type(br));\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = 0;\n+\tstruct block_iter next = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\n+\tint i = binsearch(br->restart_count, &restart_key_less, &args);\n+\tif (args.error) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tit->br = br;\n+\tif (i > 0) {\n+\t\ti--;\n+\t\tit->next_off = block_reader_restart_offset(br, i);\n+\t} else {\n+\t\tit->next_off = br->header_off + 4;\n+\t}\n+\n+\t/* We're looking for the last entry less/equal than the wanted key, so\n+\t   we have to go one entry too far and then back up.\n+\t*/\n+\twhile (1) {\n+\t\tblock_iter_copy_from(&next, it);\n+\t\terr = block_iter_next(&next, &rec);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tif (err > 0 || strbuf_cmp(&key, want) >= 0) {\n+\t\t\terr = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tblock_iter_copy_from(it, &next);\n+\t}\n+\n+done:\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&next.last_key);\n+\treftable_record_destroy(&rec);\n+\n+\treturn err;\n+}\n+\n+void block_writer_release(struct block_writer *bw)\n+{\n+\tFREE_AND_NULL(bw->restarts);\n+\tstrbuf_release(&bw->last_key);\n+\t/* the block is not owned. */\n+}\n+\n+void reftable_block_done(struct reftable_block *blockp)\n+{\n+\tstruct reftable_block_source source = blockp->source;\n+\tif (blockp && source.ops)\n+\t\tsource.ops->return_block(source.arg, blockp);\n+\tblockp->data = NULL;\n+\tblockp->len = 0;\n+\tblockp->source.ops = NULL;\n+\tblockp->source.arg = NULL;\n+}\ndiff --git a/reftable/block.h b/reftable/block.h\nnew file mode 100644\nindex 00000000000..e207706a644\n--- /dev/null\n+++ b/reftable/block.h\n@@ -0,0 +1,127 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCK_H\n+#define BLOCK_H\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Writes reftable blocks. The block_writer is reused across blocks to minimize\n+ * allocation overhead.\n+ */\n+struct block_writer {\n+\tuint8_t *buf;\n+\tuint32_t block_size;\n+\n+\t/* Offset ofof the global header. Nonzero in the first block only. */\n+\tuint32_t header_off;\n+\n+\t/* How often to restart keys. */\n+\tint restart_interval;\n+\tint hash_size;\n+\n+\t/* Offset of next uint8_t to write. */\n+\tuint32_t next;\n+\tuint32_t *restarts;\n+\tuint32_t restart_len;\n+\tuint32_t restart_cap;\n+\n+\tstruct strbuf last_key;\n+\tint entries;\n+};\n+\n+/*\n+ * initializes the blockwriter to write `typ` entries, using `buf` as temporary\n+ * storage. `buf` is not owned by the block_writer. */\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size);\n+\n+/* returns the block type (eg. 'r' for ref records. */\n+uint8_t block_writer_type(struct block_writer *bw);\n+\n+/* appends the record, or -1 if it doesn't fit. */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec);\n+\n+/* appends the key restarts, and compress the block if necessary. */\n+int block_writer_finish(struct block_writer *w);\n+\n+/* clears out internally allocated block_writer members. */\n+void block_writer_release(struct block_writer *bw);\n+\n+/* Read a block. */\n+struct block_reader {\n+\t/* offset of the block header; nonzero for the first block in a\n+\t * reftable. */\n+\tuint32_t header_off;\n+\n+\t/* the memory block */\n+\tstruct reftable_block block;\n+\tint hash_size;\n+\n+\t/* size of the data, excluding restart data. */\n+\tuint32_t block_len;\n+\tuint8_t *restart_bytes;\n+\tuint16_t restart_count;\n+\n+\t/* size of the data in the file. For log blocks, this is the compressed\n+\t * size. */\n+\tuint32_t full_block_size;\n+};\n+\n+/* Iterate over entries in a block */\n+struct block_iter {\n+\t/* offset within the block of the next entry to read. */\n+\tuint32_t next_off;\n+\tstruct block_reader *br;\n+\n+\t/* key for last entry we read. */\n+\tstruct strbuf last_key;\n+};\n+\n+/* initializes a block reader. */\n+int block_reader_init(struct block_reader *br, struct reftable_block *bl,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size);\n+\n+/* Position `it` at start of the block */\n+void block_reader_start(struct block_reader *br, struct block_iter *it);\n+\n+/* Position `it` to the `want` key in the block */\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want);\n+\n+/* Returns the block type (eg. 'r' for refs) */\n+uint8_t block_reader_type(struct block_reader *r);\n+\n+/* Decodes the first key in the block */\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key);\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);\n+\n+/* return < 0 for error, 0 for OK, > 0 for EOF. */\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec);\n+\n+/* Seek to `want` with in the block pointed to by `it` */\n+int block_iter_seek(struct block_iter *it, struct strbuf *want);\n+\n+/* deallocate memory for `it`. The block reader and its block is left intact. */\n+void block_iter_close(struct block_iter *it);\n+\n+/* size of file header, depending on format version */\n+int header_size(int version);\n+\n+/* size of file footer, depending on format version */\n+int footer_size(int version);\n+\n+/* returns a block to its source. */\n+void reftable_block_done(struct reftable_block *ret);\n+\n+#endif\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nnew file mode 100644\nindex 00000000000..4b3ea262dcb\n--- /dev/null\n+++ b/reftable/block_test.c\n@@ -0,0 +1,120 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"system.h\"\n+#include \"blocksource.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_block_read_write(void)\n+{\n+\tconst int header_off = 21; /* random */\n+\tchar *names[30];\n+\tconst int N = ARRAY_SIZE(names);\n+\tconst int block_size = 1024;\n+\tstruct reftable_block block = { NULL };\n+\tstruct block_writer bw = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_record rec = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\tint j = 0;\n+\tstruct strbuf want = STRBUF_INIT;\n+\n+\tblock.data = reftable_calloc(block_size);\n+\tblock.len = block_size;\n+\tblock.source = malloc_block_source();\n+\tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n+\t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n+\treftable_record_from_ref(&rec, &ref);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tsnprintf(name, sizeof(name), \"branch%02d\", i);\n+\t\tmemset(hash, i, sizeof(hash));\n+\n+\t\tref.refname = name;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\n+\t\tnames[i] = xstrdup(name);\n+\t\tn = block_writer_add(&bw, &rec);\n+\t\tref.refname = NULL;\n+\t\tref.value_type = REFTABLE_REF_DELETION;\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = block_writer_finish(&bw);\n+\tEXPECT(n > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);\n+\n+\tblock_reader_start(&br, &it);\n+\n+\twhile (1) {\n+\t\tint r = block_iter_next(&it, &rec);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT_STREQ(names[j], ref.refname);\n+\t\tj++;\n+\t}\n+\n+\treftable_record_release(&rec);\n+\tblock_iter_close(&it);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\t\tstrbuf_reset(&want);\n+\t\tstrbuf_addstr(&want, names[i]);\n+\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\n+\t\tEXPECT_STREQ(names[i], ref.refname);\n+\n+\t\twant.len--;\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT_STREQ(names[10 * (i / 10)], ref.refname);\n+\n+\t\tblock_iter_close(&it);\n+\t}\n+\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\tstrbuf_release(&want);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+}\n+\n+int block_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_block_read_write);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 09d4b83ef9b..c9deeaf08c7 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,7 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n+\tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"437315","messageId":"277a1f662e3c64c9ce18fc6784c992754ab82670.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 10/19] reftable: write reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:07Z","receivedAt":"2021-09-28T15:10:47Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   1 +\n reftable/reftable-writer.h | 148 ++++++++\n reftable/writer.c          | 690 +++++++++++++++++++++++++++++++++++++\n reftable/writer.h          |  50 +++\n 4 files changed, 889 insertions(+)\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n\ndiff --git a/Makefile b/Makefile\nindex 339a44f9271..38aa8d5400f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2441,6 +2441,7 @@ REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/tree.o\n+REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\ndiff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h\nnew file mode 100644\nindex 00000000000..af36462ced5\n--- /dev/null\n+++ b/reftable/reftable-writer.h\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_WRITER_H\n+#define REFTABLE_WRITER_H\n+\n+#include \"reftable-record.h\"\n+\n+#include <stdint.h>\n+#include <unistd.h> /* ssize_t */\n+\n+/* Writing single reftables */\n+\n+/* reftable_write_options sets options for writing a single reftable. */\n+struct reftable_write_options {\n+\t/* boolean: do not pad out blocks to block size. */\n+\tunsigned unpadded : 1;\n+\n+\t/* the blocksize. Should be less than 2^24. */\n+\tuint32_t block_size;\n+\n+\t/* boolean: do not generate a SHA1 => ref index. */\n+\tunsigned skip_index_objects : 1;\n+\n+\t/* how often to write complete keys in each block. */\n+\tint restart_interval;\n+\n+\t/* 4-byte identifier (\"sha1\", \"s256\") of the hash.\n+\t * Defaults to SHA1 if unset\n+\t */\n+\tuint32_t hash_id;\n+\n+\t/* boolean: do not check ref names for validity or dir/file conflicts.\n+\t */\n+\tunsigned skip_name_check : 1;\n+\n+\t/* boolean: copy log messages exactly. If unset, check that the message\n+\t *   is a single line, and add '\\n' if missing.\n+\t */\n+\tunsigned exact_log_message : 1;\n+};\n+\n+/* reftable_block_stats holds statistics for a single block type */\n+struct reftable_block_stats {\n+\t/* total number of entries written */\n+\tint entries;\n+\t/* total number of key restarts */\n+\tint restarts;\n+\t/* total number of blocks */\n+\tint blocks;\n+\t/* total number of index blocks */\n+\tint index_blocks;\n+\t/* depth of the index */\n+\tint max_index_level;\n+\n+\t/* offset of the first block for this type */\n+\tuint64_t offset;\n+\t/* offset of the top level index block for this type, or 0 if not\n+\t * present */\n+\tuint64_t index_offset;\n+};\n+\n+/* stats holds overall statistics for a single reftable */\n+struct reftable_stats {\n+\t/* total number of blocks written. */\n+\tint blocks;\n+\t/* stats for ref data */\n+\tstruct reftable_block_stats ref_stats;\n+\t/* stats for the SHA1 to ref map. */\n+\tstruct reftable_block_stats obj_stats;\n+\t/* stats for index blocks */\n+\tstruct reftable_block_stats idx_stats;\n+\t/* stats for log blocks */\n+\tstruct reftable_block_stats log_stats;\n+\n+\t/* disambiguation length of shortened object IDs. */\n+\tint object_id_len;\n+};\n+\n+/* reftable_new_writer creates a new writer */\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts);\n+\n+/* Set the range of update indices for the records we will add. When writing a\n+   table into a stack, the min should be at least\n+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.\n+\n+   For transactional updates to a stack, typically min==max, and the\n+   update_index can be obtained by inspeciting the stack. When converting an\n+   existing ref database into a single reftable, this would be a range of\n+   update-index timestamps.\n+ */\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max);\n+\n+/*\n+  Add a reftable_ref_record. The record should have names that come after\n+  already added records.\n+\n+  The update_index must be within the limits set by\n+  reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an\n+  REFTABLE_API_ERROR error to write a ref record after a log record.\n+*/\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/*\n+  Convenience function to add multiple reftable_ref_records; the function sorts\n+  the records before adding them, reordering the records array passed in.\n+*/\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n);\n+\n+/*\n+  adds reftable_log_records. Log records are keyed by (refname, decreasing\n+  update_index). The key for the record added must come after the already added\n+  log records.\n+*/\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/*\n+  Convenience function to add multiple reftable_log_records; the function sorts\n+  the records before adding them, reordering records array passed in.\n+*/\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n);\n+\n+/* reftable_writer_close finalizes the reftable. The writer is retained so\n+ * statistics can be inspected. */\n+int reftable_writer_close(struct reftable_writer *w);\n+\n+/* writer_stats returns the statistics on the reftable being written.\n+\n+   This struct becomes invalid when the writer is freed.\n+ */\n+const struct reftable_stats *writer_stats(struct reftable_writer *w);\n+\n+/* reftable_writer_free deallocates memory for the writer */\n+void reftable_writer_free(struct reftable_writer *w);\n+\n+#endif\ndiff --git a/reftable/writer.c b/reftable/writer.c\nnew file mode 100644\nindex 00000000000..3ca721e9f64\n--- /dev/null\n+++ b/reftable/writer.c\n@@ -0,0 +1,690 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"writer.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"tree.h\"\n+#include \"reftable-error.h\"\n+\n+/* finishes a block, and writes it to storage */\n+static int writer_flush_block(struct reftable_writer *w);\n+\n+/* deallocates memory related to the index */\n+static void writer_clear_index(struct reftable_writer *w);\n+\n+/* finishes writing a 'r' (refs) or 'g' (reflogs) section */\n+static int writer_finish_public_section(struct reftable_writer *w);\n+\n+static struct reftable_block_stats *\n+writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase 'r':\n+\t\treturn &w->stats.ref_stats;\n+\tcase 'o':\n+\t\treturn &w->stats.obj_stats;\n+\tcase 'i':\n+\t\treturn &w->stats.idx_stats;\n+\tcase 'g':\n+\t\treturn &w->stats.log_stats;\n+\t}\n+\tabort();\n+\treturn NULL;\n+}\n+\n+/* write data, queuing the padding for the next write. Returns negative for\n+ * error. */\n+static int padded_write(struct reftable_writer *w, uint8_t *data, size_t len,\n+\t\t\tint padding)\n+{\n+\tint n = 0;\n+\tif (w->pending_padding > 0) {\n+\t\tuint8_t *zeroed = reftable_calloc(w->pending_padding);\n+\t\tint n = w->write(w->write_arg, zeroed, w->pending_padding);\n+\t\tif (n < 0)\n+\t\t\treturn n;\n+\n+\t\tw->pending_padding = 0;\n+\t\treftable_free(zeroed);\n+\t}\n+\n+\tw->pending_padding = padding;\n+\tn = w->write(w->write_arg, data, len);\n+\tif (n < 0)\n+\t\treturn n;\n+\tn += padding;\n+\treturn 0;\n+}\n+\n+static void options_set_defaults(struct reftable_write_options *opts)\n+{\n+\tif (opts->restart_interval == 0) {\n+\t\topts->restart_interval = 16;\n+\t}\n+\n+\tif (opts->hash_id == 0) {\n+\t\topts->hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\tif (opts->block_size == 0) {\n+\t\topts->block_size = DEFAULT_BLOCK_SIZE;\n+\t}\n+}\n+\n+static int writer_version(struct reftable_writer *w)\n+{\n+\treturn (w->opts.hash_id == 0 || w->opts.hash_id == GIT_SHA1_FORMAT_ID) ?\n+\t\t\t     1 :\n+\t\t\t     2;\n+}\n+\n+static int writer_write_header(struct reftable_writer *w, uint8_t *dest)\n+{\n+\tmemcpy(dest, \"REFT\", 4);\n+\n+\tdest[4] = writer_version(w);\n+\n+\tput_be24(dest + 5, w->opts.block_size);\n+\tput_be64(dest + 8, w->min_update_index);\n+\tput_be64(dest + 16, w->max_update_index);\n+\tif (writer_version(w) == 2) {\n+\t\tput_be32(dest + 24, w->opts.hash_id);\n+\t}\n+\treturn header_size(writer_version(w));\n+}\n+\n+static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)\n+{\n+\tint block_start = 0;\n+\tif (w->next == 0) {\n+\t\tblock_start = header_size(writer_version(w));\n+\t}\n+\n+\tstrbuf_release(&w->last_key);\n+\tblock_writer_init(&w->block_writer_data, typ, w->block,\n+\t\t\t  w->opts.block_size, block_start,\n+\t\t\t  hash_size(w->opts.hash_id));\n+\tw->block_writer = &w->block_writer_data;\n+\tw->block_writer->restart_interval = w->opts.restart_interval;\n+}\n+\n+static struct strbuf reftable_empty_strbuf = STRBUF_INIT;\n+\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts)\n+{\n+\tstruct reftable_writer *wp =\n+\t\treftable_calloc(sizeof(struct reftable_writer));\n+\tstrbuf_init(&wp->block_writer_data.last_key, 0);\n+\toptions_set_defaults(opts);\n+\tif (opts->block_size >= (1 << 24)) {\n+\t\t/* TODO - error return? */\n+\t\tabort();\n+\t}\n+\twp->last_key = reftable_empty_strbuf;\n+\twp->block = reftable_calloc(opts->block_size);\n+\twp->write = writer_func;\n+\twp->write_arg = writer_arg;\n+\twp->opts = *opts;\n+\twriter_reinit_block_writer(wp, BLOCK_TYPE_REF);\n+\n+\treturn wp;\n+}\n+\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max)\n+{\n+\tw->min_update_index = min;\n+\tw->max_update_index = max;\n+}\n+\n+void reftable_writer_free(struct reftable_writer *w)\n+{\n+\treftable_free(w->block);\n+\treftable_free(w);\n+}\n+\n+struct obj_index_tree_node {\n+\tstruct strbuf hash;\n+\tuint64_t *offsets;\n+\tsize_t offset_len;\n+\tsize_t offset_cap;\n+};\n+\n+#define OBJ_INDEX_TREE_NODE_INIT    \\\n+\t{                           \\\n+\t\t.hash = STRBUF_INIT \\\n+\t}\n+\n+static int obj_index_tree_node_compare(const void *a, const void *b)\n+{\n+\treturn strbuf_cmp(&((const struct obj_index_tree_node *)a)->hash,\n+\t\t\t  &((const struct obj_index_tree_node *)b)->hash);\n+}\n+\n+static void writer_index_hash(struct reftable_writer *w, struct strbuf *hash)\n+{\n+\tuint64_t off = w->next;\n+\n+\tstruct obj_index_tree_node want = { .hash = *hash };\n+\n+\tstruct tree_node *node = tree_search(&want, &w->obj_index_tree,\n+\t\t\t\t\t     &obj_index_tree_node_compare, 0);\n+\tstruct obj_index_tree_node *key = NULL;\n+\tif (node == NULL) {\n+\t\tstruct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;\n+\t\tkey = reftable_malloc(sizeof(struct obj_index_tree_node));\n+\t\t*key = empty;\n+\n+\t\tstrbuf_reset(&key->hash);\n+\t\tstrbuf_addbuf(&key->hash, hash);\n+\t\ttree_search((void *)key, &w->obj_index_tree,\n+\t\t\t    &obj_index_tree_node_compare, 1);\n+\t} else {\n+\t\tkey = node->key;\n+\t}\n+\n+\tif (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {\n+\t\treturn;\n+\t}\n+\n+\tif (key->offset_len == key->offset_cap) {\n+\t\tkey->offset_cap = 2 * key->offset_cap + 1;\n+\t\tkey->offsets = reftable_realloc(\n+\t\t\tkey->offsets, sizeof(uint64_t) * key->offset_cap);\n+\t}\n+\n+\tkey->offsets[key->offset_len++] = off;\n+}\n+\n+static int writer_add_record(struct reftable_writer *w,\n+\t\t\t     struct reftable_record *rec)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = -1;\n+\treftable_record_key(rec, &key);\n+\tif (strbuf_cmp(&w->last_key, &key) >= 0) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, &key);\n+\tif (w->block_writer == NULL) {\n+\t\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\t}\n+\n+\tassert(block_writer_type(w->block_writer) == reftable_record_type(rec));\n+\n+\tif (block_writer_add(w->block_writer, rec) == 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\terr = block_writer_add(w->block_writer, rec);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = 0;\n+done:\n+\tstrbuf_release(&key);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_ref_record copy = *ref;\n+\tint err = 0;\n+\n+\tif (ref->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\tif (ref->update_index < w->min_update_index ||\n+\t    ref->update_index > w->max_update_index)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\treftable_record_from_ref(&rec, &copy);\n+\tcopy.update_index -= w->min_update_index;\n+\n+\terr = writer_add_record(w, &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, (char *)reftable_ref_record_val1(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, reftable_ref_record_val2(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(refs, n, reftable_ref_record_compare_name);\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_ref(w, &refs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int reftable_writer_add_log_verbatim(struct reftable_writer *w,\n+\t\t\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tif (w->block_writer &&\n+\t    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {\n+\t\tint err = writer_finish_public_section(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tw->next -= w->pending_padding;\n+\tw->pending_padding = 0;\n+\n+\treftable_record_from_log(&rec, log);\n+\treturn writer_add_record(w, &rec);\n+}\n+\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tchar *input_log_message = NULL;\n+\tstruct strbuf cleaned_message = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (log->value_type == REFTABLE_LOG_DELETION)\n+\t\treturn reftable_writer_add_log_verbatim(w, log);\n+\n+\tif (log->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\tinput_log_message = log->value.update.message;\n+\tif (!w->opts.exact_log_message && log->value.update.message) {\n+\t\tstrbuf_addstr(&cleaned_message, log->value.update.message);\n+\t\twhile (cleaned_message.len &&\n+\t\t       cleaned_message.buf[cleaned_message.len - 1] == '\\n')\n+\t\t\tstrbuf_setlen(&cleaned_message,\n+\t\t\t\t      cleaned_message.len - 1);\n+\t\tif (strchr(cleaned_message.buf, '\\n')) {\n+\t\t\t/* multiple lines not allowed. */\n+\t\t\terr = REFTABLE_API_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tstrbuf_addstr(&cleaned_message, \"\\n\");\n+\t\tlog->value.update.message = cleaned_message.buf;\n+\t}\n+\n+\terr = reftable_writer_add_log_verbatim(w, log);\n+\tlog->value.update.message = input_log_message;\n+done:\n+\tstrbuf_release(&cleaned_message);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(logs, n, reftable_log_record_compare_key);\n+\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_log(w, &logs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int writer_finish_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tuint64_t index_start = 0;\n+\tint max_level = 0;\n+\tint threshold = w->opts.unpadded ? 1 : 3;\n+\tint before_blocks = w->stats.idx_stats.blocks;\n+\tint err = writer_flush_block(w);\n+\tint i = 0;\n+\tstruct reftable_block_stats *bstats = NULL;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\twhile (w->index_len > threshold) {\n+\t\tstruct reftable_index_record *idx = NULL;\n+\t\tint idx_len = 0;\n+\n+\t\tmax_level++;\n+\t\tindex_start = w->next;\n+\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\tidx = w->index;\n+\t\tidx_len = w->index_len;\n+\n+\t\tw->index = NULL;\n+\t\tw->index_len = 0;\n+\t\tw->index_cap = 0;\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstruct reftable_record rec = { NULL };\n+\t\t\treftable_record_from_index(&rec, idx + i);\n+\t\t\tif (block_writer_add(w->block_writer, &rec) == 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\terr = writer_flush_block(w);\n+\t\t\tif (err < 0)\n+\t\t\t\treturn err;\n+\n+\t\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\t\terr = block_writer_add(w->block_writer, &rec);\n+\t\t\tif (err != 0) {\n+\t\t\t\t/* write into fresh block should always succeed\n+\t\t\t\t */\n+\t\t\t\tabort();\n+\t\t\t}\n+\t\t}\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstrbuf_release(&idx[i].last_key);\n+\t\t}\n+\t\treftable_free(idx);\n+\t}\n+\n+\twriter_clear_index(w);\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tbstats = writer_reftable_block_stats(w, typ);\n+\tbstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;\n+\tbstats->index_offset = index_start;\n+\tbstats->max_index_level = max_level;\n+\n+\t/* Reinit lastKey, as the next section can start with any key. */\n+\tw->last_key.len = 0;\n+\n+\treturn 0;\n+}\n+\n+struct common_prefix_arg {\n+\tstruct strbuf *last;\n+\tint max;\n+};\n+\n+static void update_common(void *void_arg, void *key)\n+{\n+\tstruct common_prefix_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tif (arg->last) {\n+\t\tint n = common_prefix_size(&entry->hash, arg->last);\n+\t\tif (n > arg->max) {\n+\t\t\targ->max = n;\n+\t\t}\n+\t}\n+\targ->last = &entry->hash;\n+}\n+\n+struct write_record_arg {\n+\tstruct reftable_writer *w;\n+\tint err;\n+};\n+\n+static void write_object_record(void *void_arg, void *key)\n+{\n+\tstruct write_record_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tstruct reftable_obj_record obj_rec = {\n+\t\t.hash_prefix = (uint8_t *)entry->hash.buf,\n+\t\t.hash_prefix_len = arg->w->stats.object_id_len,\n+\t\t.offsets = entry->offsets,\n+\t\t.offset_len = entry->offset_len,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\treftable_record_from_obj(&rec, &obj_rec);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\n+\targ->err = writer_flush_block(arg->w);\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\twriter_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\tobj_rec.offset_len = 0;\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\n+\t/* Should be able to write into a fresh block. */\n+\tassert(arg->err == 0);\n+\n+done:;\n+}\n+\n+static void object_record_free(void *void_arg, void *key)\n+{\n+\tstruct obj_index_tree_node *entry = key;\n+\n+\tFREE_AND_NULL(entry->offsets);\n+\tstrbuf_release(&entry->hash);\n+\treftable_free(entry);\n+}\n+\n+static int writer_dump_object_index(struct reftable_writer *w)\n+{\n+\tstruct write_record_arg closure = { .w = w };\n+\tstruct common_prefix_arg common = { NULL };\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &update_common, &common);\n+\t}\n+\tw->stats.object_id_len = common.max + 1;\n+\n+\twriter_reinit_block_writer(w, BLOCK_TYPE_OBJ);\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &write_object_record, &closure);\n+\t}\n+\n+\tif (closure.err < 0)\n+\t\treturn closure.err;\n+\treturn writer_finish_section(w);\n+}\n+\n+static int writer_finish_public_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = 0;\n+\tint err = 0;\n+\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\n+\ttyp = block_writer_type(w->block_writer);\n+\terr = writer_finish_section(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\tif (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&\n+\t    w->stats.ref_stats.index_blocks > 0) {\n+\t\terr = writer_dump_object_index(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &object_record_free, NULL);\n+\t\ttree_free(w->obj_index_tree);\n+\t\tw->obj_index_tree = NULL;\n+\t}\n+\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+int reftable_writer_close(struct reftable_writer *w)\n+{\n+\tuint8_t footer[72];\n+\tuint8_t *p = footer;\n+\tint err = writer_finish_public_section(w);\n+\tint empty_table = w->next == 0;\n+\tif (err != 0)\n+\t\tgoto done;\n+\tw->pending_padding = 0;\n+\tif (empty_table) {\n+\t\t/* Empty tables need a header anyway. */\n+\t\tuint8_t header[28];\n+\t\tint n = writer_write_header(w, header);\n+\t\terr = padded_write(w, header, n, 0);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\tp += writer_write_header(w, footer);\n+\tput_be64(p, w->stats.ref_stats.index_offset);\n+\tp += 8;\n+\tput_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);\n+\tp += 8;\n+\tput_be64(p, w->stats.obj_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be64(p, w->stats.log_stats.offset);\n+\tp += 8;\n+\tput_be64(p, w->stats.log_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be32(p, crc32(0, footer, p - footer));\n+\tp += 4;\n+\n+\terr = padded_write(w, footer, footer_size(writer_version(w)), 0);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (empty_table) {\n+\t\terr = REFTABLE_EMPTY_TABLE_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\t/* free up memory. */\n+\tblock_writer_release(&w->block_writer_data);\n+\twriter_clear_index(w);\n+\tstrbuf_release(&w->last_key);\n+\treturn err;\n+}\n+\n+static void writer_clear_index(struct reftable_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->index_len; i++) {\n+\t\tstrbuf_release(&w->index[i].last_key);\n+\t}\n+\n+\tFREE_AND_NULL(w->index);\n+\tw->index_len = 0;\n+\tw->index_cap = 0;\n+}\n+\n+static const int debug = 0;\n+\n+static int writer_flush_nonempty_block(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tstruct reftable_block_stats *bstats =\n+\t\twriter_reftable_block_stats(w, typ);\n+\tuint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;\n+\tint raw_bytes = block_writer_finish(w->block_writer);\n+\tint padding = 0;\n+\tint err = 0;\n+\tstruct reftable_index_record ir = { .last_key = STRBUF_INIT };\n+\tif (raw_bytes < 0)\n+\t\treturn raw_bytes;\n+\n+\tif (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {\n+\t\tpadding = w->opts.block_size - raw_bytes;\n+\t}\n+\n+\tif (block_typ_off > 0) {\n+\t\tbstats->offset = block_typ_off;\n+\t}\n+\n+\tbstats->entries += w->block_writer->entries;\n+\tbstats->restarts += w->block_writer->restart_len;\n+\tbstats->blocks++;\n+\tw->stats.blocks++;\n+\n+\tif (debug) {\n+\t\tfprintf(stderr, \"block %c off %\" PRIu64 \" sz %d (%d)\\n\", typ,\n+\t\t\tw->next, raw_bytes,\n+\t\t\tget_be24(w->block + w->block_writer->header_off + 1));\n+\t}\n+\n+\tif (w->next == 0) {\n+\t\twriter_write_header(w, w->block);\n+\t}\n+\n+\terr = padded_write(w, w->block, raw_bytes, padding);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (w->index_cap == w->index_len) {\n+\t\tw->index_cap = 2 * w->index_cap + 1;\n+\t\tw->index = reftable_realloc(\n+\t\t\tw->index,\n+\t\t\tsizeof(struct reftable_index_record) * w->index_cap);\n+\t}\n+\n+\tir.offset = w->next;\n+\tstrbuf_reset(&ir.last_key);\n+\tstrbuf_addbuf(&ir.last_key, &w->block_writer->last_key);\n+\tw->index[w->index_len] = ir;\n+\n+\tw->index_len++;\n+\tw->next += padding + raw_bytes;\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+static int writer_flush_block(struct reftable_writer *w)\n+{\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\tif (w->block_writer->entries == 0)\n+\t\treturn 0;\n+\treturn writer_flush_nonempty_block(w);\n+}\n+\n+const struct reftable_stats *writer_stats(struct reftable_writer *w)\n+{\n+\treturn &w->stats;\n+}\ndiff --git a/reftable/writer.h b/reftable/writer.h\nnew file mode 100644\nindex 00000000000..09b88673d97\n--- /dev/null\n+++ b/reftable/writer.h\n@@ -0,0 +1,50 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef WRITER_H\n+#define WRITER_H\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"tree.h\"\n+#include \"reftable-writer.h\"\n+\n+struct reftable_writer {\n+\tssize_t (*write)(void *, const void *, size_t);\n+\tvoid *write_arg;\n+\tint pending_padding;\n+\tstruct strbuf last_key;\n+\n+\t/* offset of next block to write. */\n+\tuint64_t next;\n+\tuint64_t min_update_index, max_update_index;\n+\tstruct reftable_write_options opts;\n+\n+\t/* memory buffer for writing */\n+\tuint8_t *block;\n+\n+\t/* writer for the current section. NULL or points to\n+\t * block_writer_data */\n+\tstruct block_writer *block_writer;\n+\n+\tstruct block_writer block_writer_data;\n+\n+\t/* pending index records for the current section */\n+\tstruct reftable_index_record *index;\n+\tsize_t index_len;\n+\tsize_t index_cap;\n+\n+\t/*\n+\t * tree for use with tsearch; used to populate the 'o' inverse OID\n+\t * map */\n+\tstruct tree_node *obj_index_tree;\n+\n+\tstruct reftable_stats stats;\n+};\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"437316","messageId":"f97182dec1fe41490335d5e6267a89b2e4ae6f46.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 11/19] reftable: generic interface to tables","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:08Z","receivedAt":"2021-09-28T15:10:49Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                     |   3 +\n reftable/generic.c           | 169 +++++++++++++++++++++++++++++++++++\n reftable/generic.h           |  32 +++++++\n reftable/reftable-generic.h  |  47 ++++++++++\n reftable/reftable-iterator.h |  39 ++++++++\n reftable/reftable.c          | 115 ++++++++++++++++++++++++\n 6 files changed, 405 insertions(+)\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable.c\n\ndiff --git a/Makefile b/Makefile\nindex 38aa8d5400f..c6bbf4d4a2f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2440,6 +2440,9 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/refname.o\n+REFTABLE_OBJS += reftable/generic.o\n+REFTABLE_OBJS += reftable/stack.o\n REFTABLE_OBJS += reftable/tree.o\n REFTABLE_OBJS += reftable/writer.o\n \ndiff --git a/reftable/generic.c b/reftable/generic.c\nnew file mode 100644\nindex 00000000000..7a8a738d860\n--- /dev/null\n+++ b/reftable/generic.c\n@@ -0,0 +1,169 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = ~((uint64_t)0),\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+int reftable_table_print(struct reftable_table *tab) {\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\tuint32_t hash_id = reftable_table_hash_id(tab);\n+\tint err = reftable_table_seek_ref(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_ref_record_print(&ref, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_table_seek_log(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_log_record_print(&log, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_log_record_release(&log);\n+\treturn 0;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\ndiff --git a/reftable/generic.h b/reftable/generic.h\nnew file mode 100644\nindex 00000000000..98886a06402\n--- /dev/null\n+++ b/reftable/generic.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef GENERIC_H\n+#define GENERIC_H\n+\n+#include \"record.h\"\n+#include \"reftable-generic.h\"\n+\n+/* generic interface to reftables */\n+struct reftable_table_vtable {\n+\tint (*seek_record)(void *tab, struct reftable_iterator *it,\n+\t\t\t   struct reftable_record *);\n+\tuint32_t (*hash_id)(void *tab);\n+\tuint64_t (*min_update_index)(void *tab);\n+\tuint64_t (*max_update_index)(void *tab);\n+};\n+\n+struct reftable_iterator_vtable {\n+\tint (*next)(void *iter_arg, struct reftable_record *rec);\n+\tvoid (*close)(void *iter_arg);\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it);\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec);\n+\n+#endif\ndiff --git a/reftable/reftable-generic.h b/reftable/reftable-generic.h\nnew file mode 100644\nindex 00000000000..d239751a778\n--- /dev/null\n+++ b/reftable/reftable-generic.h\n@@ -0,0 +1,47 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_GENERIC_H\n+#define REFTABLE_GENERIC_H\n+\n+#include \"reftable-iterator.h\"\n+\n+struct reftable_table_vtable;\n+\n+/*\n+ * Provides a unified API for reading tables, either merged tables, or single\n+ * readers. */\n+struct reftable_table {\n+\tstruct reftable_table_vtable *ops;\n+\tvoid *table_arg;\n+};\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID from a generic reftable_table */\n+uint32_t reftable_table_hash_id(struct reftable_table *tab);\n+\n+/* returns the max update_index covered by this table. */\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab);\n+\n+/* returns the min update_index covered by this table. */\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0\n+   for success, and 1 if ref not found. */\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* dump table contents onto stdout for debugging */\n+int reftable_table_print(struct reftable_table *tab);\n+\n+#endif\ndiff --git a/reftable/reftable-iterator.h b/reftable/reftable-iterator.h\nnew file mode 100644\nindex 00000000000..d3eee7af357\n--- /dev/null\n+++ b/reftable/reftable-iterator.h\n@@ -0,0 +1,39 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ITERATOR_H\n+#define REFTABLE_ITERATOR_H\n+\n+#include \"reftable-record.h\"\n+\n+struct reftable_iterator_vtable;\n+\n+/* iterator is the generic interface for walking over data stored in a\n+ * reftable.\n+ */\n+struct reftable_iterator {\n+\tstruct reftable_iterator_vtable *ops;\n+\tvoid *iter_arg;\n+};\n+\n+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref);\n+\n+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log);\n+\n+/* releases resources associated with an iterator. */\n+void reftable_iterator_destroy(struct reftable_iterator *it);\n+\n+#endif\ndiff --git a/reftable/reftable.c b/reftable/reftable.c\nnew file mode 100644\nindex 00000000000..0e4607a7cd6\n--- /dev/null\n+++ b/reftable/reftable.c\n@@ -0,0 +1,115 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\n-- \ngitgitgadget\n\n"},{"id":"437317","messageId":"13de5d03a71c6ab8400989cd9c27398df8203f35.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 12/19] reftable: read reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:09Z","receivedAt":"2021-09-28T15:10:52Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis supports reading a single reftable file.\n\nThe commit introduces an abstract iterator type, which captures the usecases\nboth of reading individual refs, and iterating over a segment of the ref\nnamespace.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/iter.c            | 194 +++++++++\n reftable/iter.h            |  69 ++++\n reftable/reader.c          | 801 +++++++++++++++++++++++++++++++++++++\n reftable/reader.h          |  66 +++\n reftable/reftable-reader.h | 101 +++++\n 6 files changed, 1233 insertions(+)\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/reftable-reader.h\n\ndiff --git a/Makefile b/Makefile\nindex c6bbf4d4a2f..8a058df33b8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2438,7 +2438,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n+REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n REFTABLE_OBJS += reftable/generic.o\ndiff --git a/reftable/iter.c b/reftable/iter.c\nnew file mode 100644\nindex 00000000000..93d04f735b8\n--- /dev/null\n+++ b/reftable/iter.c\n@@ -0,0 +1,194 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"iter.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"generic.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"reftable-error.h\"\n+\n+int iterator_is_null(struct reftable_iterator *it)\n+{\n+\treturn !it->ops;\n+}\n+\n+static void filtering_ref_iterator_close(void *iter_arg)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstrbuf_release(&fri->oid);\n+\treftable_iterator_destroy(&fri->it);\n+}\n+\n+static int filtering_ref_iterator_next(void *iter_arg,\n+\t\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\tint err = 0;\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&fri->it, ref);\n+\t\tif (err != 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (fri->double_check) {\n+\t\t\tstruct reftable_iterator it = { NULL };\n+\n+\t\t\terr = reftable_table_seek_ref(&fri->tab, &it,\n+\t\t\t\t\t\t      ref->refname);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = reftable_iterator_next_ref(&it, ref);\n+\t\t\t}\n+\n+\t\t\treftable_iterator_destroy(&it);\n+\n+\t\t\tif (err < 0) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (err > 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL2 &&\n+\t\t    (!memcmp(fri->oid.buf, ref->value.val2.target_value,\n+\t\t\t     fri->oid.len) ||\n+\t\t     !memcmp(fri->oid.buf, ref->value.val2.value,\n+\t\t\t     fri->oid.len)))\n+\t\t\treturn 0;\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL1 &&\n+\t\t    !memcmp(fri->oid.buf, ref->value.val1, fri->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treftable_ref_record_release(ref);\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable filtering_ref_iterator_vtable = {\n+\t.next = &filtering_ref_iterator_next,\n+\t.close = &filtering_ref_iterator_close,\n+};\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *it,\n+\t\t\t\t\t  struct filtering_ref_iterator *fri)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = fri;\n+\tit->ops = &filtering_ref_iterator_vtable;\n+}\n+\n+static void indexed_table_ref_iter_close(void *p)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tblock_iter_close(&it->cur);\n+\treftable_block_done(&it->block_reader.block);\n+\treftable_free(it->offsets);\n+\tstrbuf_release(&it->oid);\n+}\n+\n+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)\n+{\n+\tuint64_t off;\n+\tint err = 0;\n+\tif (it->offset_idx == it->offset_len) {\n+\t\tit->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\n+\treftable_block_done(&it->block_reader.block);\n+\n+\toff = it->offsets[it->offset_idx++];\n+\terr = reader_init_block_reader(it->r, &it->block_reader, off,\n+\t\t\t\t       BLOCK_TYPE_REF);\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\tif (err > 0) {\n+\t\t/* indexed block does not exist. */\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\t}\n+\tblock_reader_start(&it->block_reader, &it->cur);\n+\treturn 0;\n+}\n+\n+static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\n+\twhile (1) {\n+\t\tint err = block_iter_next(&it->cur, rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\terr = indexed_table_ref_iter_next_block(it);\n+\t\t\tif (err < 0) {\n+\t\t\t\treturn err;\n+\t\t\t}\n+\n+\t\t\tif (it->is_finished) {\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\t/* BUG */\n+\t\tif (!memcmp(it->oid.buf, ref->value.val2.target_value,\n+\t\t\t    it->oid.len) ||\n+\t\t    !memcmp(it->oid.buf, ref->value.val2.value, it->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+}\n+\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len)\n+{\n+\tstruct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT;\n+\tstruct indexed_table_ref_iter *itr =\n+\t\treftable_calloc(sizeof(struct indexed_table_ref_iter));\n+\tint err = 0;\n+\n+\t*itr = empty;\n+\titr->r = r;\n+\tstrbuf_add(&itr->oid, oid, oid_len);\n+\n+\titr->offsets = offsets;\n+\titr->offset_len = offset_len;\n+\n+\terr = indexed_table_ref_iter_next_block(itr);\n+\tif (err < 0) {\n+\t\treftable_free(itr);\n+\t} else {\n+\t\t*dest = itr;\n+\t}\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {\n+\t.next = &indexed_table_ref_iter_next,\n+\t.close = &indexed_table_ref_iter_close,\n+};\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = itr;\n+\tit->ops = &indexed_table_ref_iter_vtable;\n+}\ndiff --git a/reftable/iter.h b/reftable/iter.h\nnew file mode 100644\nindex 00000000000..09eb0cbfa59\n--- /dev/null\n+++ b/reftable/iter.h\n@@ -0,0 +1,69 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef ITER_H\n+#define ITER_H\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"record.h\"\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+/* Returns true for a zeroed out iterator, such as the one returned from\n+ * iterator_destroy. */\n+int iterator_is_null(struct reftable_iterator *it);\n+\n+/* iterator that produces only ref records that point to `oid` */\n+struct filtering_ref_iterator {\n+\tint double_check;\n+\tstruct reftable_table tab;\n+\tstruct strbuf oid;\n+\tstruct reftable_iterator it;\n+};\n+#define FILTERING_REF_ITERATOR_INIT \\\n+\t{                           \\\n+\t\t.oid = STRBUF_INIT  \\\n+\t}\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *,\n+\t\t\t\t\t  struct filtering_ref_iterator *);\n+\n+/* iterator that produces only ref records that point to `oid`,\n+ * but using the object index.\n+ */\n+struct indexed_table_ref_iter {\n+\tstruct reftable_reader *r;\n+\tstruct strbuf oid;\n+\n+\t/* mutable */\n+\tuint64_t *offsets;\n+\n+\t/* Points to the next offset to read. */\n+\tint offset_idx;\n+\tint offset_len;\n+\tstruct block_reader block_reader;\n+\tstruct block_iter cur;\n+\tint is_finished;\n+};\n+\n+#define INDEXED_TABLE_REF_ITER_INIT                                     \\\n+\t{                                                               \\\n+\t\t.cur = { .last_key = STRBUF_INIT }, .oid = STRBUF_INIT, \\\n+\t}\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr);\n+\n+/* Takes ownership of `offsets` */\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len);\n+\n+#endif\ndiff --git a/reftable/reader.c b/reftable/reader.c\nnew file mode 100644\nindex 00000000000..ab7a981eb8e\n--- /dev/null\n+++ b/reftable/reader.c\n@@ -0,0 +1,801 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reader.h\"\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"generic.h\"\n+#include \"iter.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-generic.h\"\n+#include \"tree.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source)\n+{\n+\treturn source->ops->size(source->arg);\n+}\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size)\n+{\n+\tint result = source->ops->read_block(source->arg, dest, off, size);\n+\tdest->source = *source;\n+\treturn result;\n+}\n+\n+void block_source_close(struct reftable_block_source *source)\n+{\n+\tif (!source->ops) {\n+\t\treturn;\n+\t}\n+\n+\tsource->ops->close(source->arg);\n+\tsource->ops = NULL;\n+}\n+\n+static struct reftable_reader_offsets *\n+reader_offsets_for(struct reftable_reader *r, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\t\treturn &r->ref_offsets;\n+\tcase BLOCK_TYPE_LOG:\n+\t\treturn &r->log_offsets;\n+\tcase BLOCK_TYPE_OBJ:\n+\t\treturn &r->obj_offsets;\n+\t}\n+\tabort();\n+}\n+\n+static int reader_get_block(struct reftable_reader *r,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t sz)\n+{\n+\tif (off >= r->size)\n+\t\treturn 0;\n+\n+\tif (off + sz > r->size) {\n+\t\tsz = r->size - off;\n+\t}\n+\n+\treturn block_source_read_block(&r->source, dest, off, sz);\n+}\n+\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r)\n+{\n+\treturn r->hash_id;\n+}\n+\n+const char *reader_name(struct reftable_reader *r)\n+{\n+\treturn r->name;\n+}\n+\n+static int parse_footer(struct reftable_reader *r, uint8_t *footer,\n+\t\t\tuint8_t *header)\n+{\n+\tuint8_t *f = footer;\n+\tuint8_t first_block_typ;\n+\tint err = 0;\n+\tuint32_t computed_crc;\n+\tuint32_t file_crc;\n+\n+\tif (memcmp(f, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tf += 4;\n+\n+\tif (memcmp(footer, header, header_size(r->version))) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tf++;\n+\tr->block_size = get_be24(f);\n+\n+\tf += 3;\n+\tr->min_update_index = get_be64(f);\n+\tf += 8;\n+\tr->max_update_index = get_be64(f);\n+\tf += 8;\n+\n+\tif (r->version == 1) {\n+\t\tr->hash_id = GIT_SHA1_FORMAT_ID;\n+\t} else {\n+\t\tr->hash_id = get_be32(f);\n+\t\tswitch (r->hash_id) {\n+\t\tcase GIT_SHA1_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tcase GIT_SHA256_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tf += 4;\n+\t}\n+\n+\tr->ref_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->obj_offsets.offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);\n+\tr->obj_offsets.offset >>= 5;\n+\n+\tr->obj_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tcomputed_crc = crc32(0, footer, f - footer);\n+\tfile_crc = get_be32(f);\n+\tf += 4;\n+\tif (computed_crc != file_crc) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tfirst_block_typ = header[header_size(r->version)];\n+\tr->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF);\n+\tr->ref_offsets.offset = 0;\n+\tr->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG ||\n+\t\t\t\t     r->log_offsets.offset > 0);\n+\tr->obj_offsets.is_present = r->obj_offsets.offset > 0;\n+\terr = 0;\n+done:\n+\treturn err;\n+}\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name)\n+{\n+\tstruct reftable_block footer = { NULL };\n+\tstruct reftable_block header = { NULL };\n+\tint err = 0;\n+\tuint64_t file_size = block_source_size(source);\n+\n+\t/* Need +1 to read type of first block. */\n+\tuint32_t read_size = header_size(2) + 1; /* read v2 because it's larger.  */\n+\tmemset(r, 0, sizeof(struct reftable_reader));\n+\n+\tif (read_size > file_size) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = block_source_read_block(source, &header, 0, read_size);\n+\tif (err != read_size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tif (memcmp(header.data, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tr->version = header.data[4];\n+\tif (r->version != 1 && r->version != 2) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tr->size = file_size - footer_size(r->version);\n+\tr->source = *source;\n+\tr->name = xstrdup(name);\n+\tr->hash_id = 0;\n+\n+\terr = block_source_read_block(source, &footer, r->size,\n+\t\t\t\t      footer_size(r->version));\n+\tif (err != footer_size(r->version)) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = parse_footer(r, footer.data, header.data);\n+done:\n+\treftable_block_done(&footer);\n+\treftable_block_done(&header);\n+\treturn err;\n+}\n+\n+struct table_iter {\n+\tstruct reftable_reader *r;\n+\tuint8_t typ;\n+\tuint64_t block_off;\n+\tstruct block_iter bi;\n+\tint is_finished;\n+};\n+#define TABLE_ITER_INIT                          \\\n+\t{                                        \\\n+\t\t.bi = {.last_key = STRBUF_INIT } \\\n+\t}\n+\n+static void table_iter_copy_from(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = src->block_off;\n+\tdest->is_finished = src->is_finished;\n+\tblock_iter_copy_from(&dest->bi, &src->bi);\n+}\n+\n+static int table_iter_next_in_block(struct table_iter *ti,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tint res = block_iter_next(&ti->bi, rec);\n+\tif (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {\n+\t\t((struct reftable_ref_record *)rec->data)->update_index +=\n+\t\t\tti->r->min_update_index;\n+\t}\n+\n+\treturn res;\n+}\n+\n+static void table_iter_block_done(struct table_iter *ti)\n+{\n+\tif (!ti->bi.br) {\n+\t\treturn;\n+\t}\n+\treftable_block_done(&ti->bi.br->block);\n+\tFREE_AND_NULL(ti->bi.br);\n+\n+\tti->bi.last_key.len = 0;\n+\tti->bi.next_off = 0;\n+}\n+\n+static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off,\n+\t\t\t\t  int version)\n+{\n+\tint32_t result = 0;\n+\n+\tif (off == 0) {\n+\t\tdata += header_size(version);\n+\t}\n+\n+\t*typ = data[0];\n+\tif (reftable_is_block_type(*typ)) {\n+\t\tresult = get_be24(data + 1);\n+\t}\n+\treturn result;\n+}\n+\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ)\n+{\n+\tint32_t guess_block_size = r->block_size ? r->block_size :\n+\t\t\t\t\t\t\t DEFAULT_BLOCK_SIZE;\n+\tstruct reftable_block block = { NULL };\n+\tuint8_t block_typ = 0;\n+\tint err = 0;\n+\tuint32_t header_off = next_off ? 0 : header_size(r->version);\n+\tint32_t block_size = 0;\n+\n+\tif (next_off >= r->size)\n+\t\treturn 1;\n+\n+\terr = reader_get_block(r, &block, next_off, guess_block_size);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tblock_size = extract_block_size(block.data, &block_typ, next_off,\n+\t\t\t\t\tr->version);\n+\tif (block_size < 0)\n+\t\treturn block_size;\n+\n+\tif (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {\n+\t\treftable_block_done(&block);\n+\t\treturn 1;\n+\t}\n+\n+\tif (block_size > guess_block_size) {\n+\t\treftable_block_done(&block);\n+\t\terr = reader_get_block(r, &block, next_off, block_size);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn block_reader_init(br, &block, header_off, r->block_size,\n+\t\t\t\t hash_size(r->hash_id));\n+}\n+\n+static int table_iter_next_block(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tuint64_t next_block_off = src->block_off + src->bi.br->full_block_size;\n+\tstruct block_reader br = { 0 };\n+\tint err = 0;\n+\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = next_block_off;\n+\n+\terr = reader_init_block_reader(src->r, &br, next_block_off, src->typ);\n+\tif (err > 0) {\n+\t\tdest->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\tif (err != 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct block_reader *brp =\n+\t\t\treftable_malloc(sizeof(struct block_reader));\n+\t\t*brp = br;\n+\n+\t\tdest->is_finished = 0;\n+\t\tblock_reader_start(brp, &dest->bi);\n+\t}\n+\treturn 0;\n+}\n+\n+static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)\n+{\n+\tif (reftable_record_type(rec) != ti->typ)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\twhile (1) {\n+\t\tstruct table_iter next = TABLE_ITER_INIT;\n+\t\tint err = 0;\n+\t\tif (ti->is_finished) {\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\terr = table_iter_next_in_block(ti, rec);\n+\t\tif (err <= 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err != 0) {\n+\t\t\tti->is_finished = 1;\n+\t\t}\n+\t\ttable_iter_block_done(ti);\n+\t\tif (err != 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\ttable_iter_copy_from(ti, &next);\n+\t\tblock_iter_close(&next.bi);\n+\t}\n+}\n+\n+static int table_iter_next_void(void *ti, struct reftable_record *rec)\n+{\n+\treturn table_iter_next(ti, rec);\n+}\n+\n+static void table_iter_close(void *p)\n+{\n+\tstruct table_iter *ti = p;\n+\ttable_iter_block_done(ti);\n+\tblock_iter_close(&ti->bi);\n+}\n+\n+static struct reftable_iterator_vtable table_iter_vtable = {\n+\t.next = &table_iter_next_void,\n+\t.close = &table_iter_close,\n+};\n+\n+static void iterator_from_table_iter(struct reftable_iterator *it,\n+\t\t\t\t     struct table_iter *ti)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = ti;\n+\tit->ops = &table_iter_vtable;\n+}\n+\n+static int reader_table_iter_at(struct reftable_reader *r,\n+\t\t\t\tstruct table_iter *ti, uint64_t off,\n+\t\t\t\tuint8_t typ)\n+{\n+\tstruct block_reader br = { 0 };\n+\tstruct block_reader *brp = NULL;\n+\n+\tint err = reader_init_block_reader(r, &br, off, typ);\n+\tif (err != 0)\n+\t\treturn err;\n+\n+\tbrp = reftable_malloc(sizeof(struct block_reader));\n+\t*brp = br;\n+\tti->r = r;\n+\tti->typ = block_reader_type(brp);\n+\tti->block_off = off;\n+\tblock_reader_start(brp, &ti->bi);\n+\treturn 0;\n+}\n+\n+static int reader_start(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\tuint8_t typ, int index)\n+{\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tuint64_t off = offs->offset;\n+\tif (index) {\n+\t\toff = offs->index_offset;\n+\t\tif (off == 0) {\n+\t\t\treturn 1;\n+\t\t}\n+\t\ttyp = BLOCK_TYPE_INDEX;\n+\t}\n+\n+\treturn reader_table_iter_at(r, ti, off, typ);\n+}\n+\n+static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\t      struct reftable_record *want)\n+{\n+\tstruct reftable_record rec =\n+\t\treftable_new_record(reftable_record_type(want));\n+\tstruct strbuf want_key = STRBUF_INIT;\n+\tstruct strbuf got_key = STRBUF_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = -1;\n+\n+\treftable_record_key(want, &want_key);\n+\n+\twhile (1) {\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = block_reader_first_key(next.bi.br, &got_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (strbuf_cmp(&got_key, &want_key) > 0) {\n+\t\t\ttable_iter_block_done(&next);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_block_done(ti);\n+\t\ttable_iter_copy_from(ti, &next);\n+\t}\n+\n+\terr = block_iter_seek(&ti->bi, &want_key);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = 0;\n+\n+done:\n+\tblock_iter_close(&next.bi);\n+\treftable_record_destroy(&rec);\n+\tstrbuf_release(&want_key);\n+\tstrbuf_release(&got_key);\n+\treturn err;\n+}\n+\n+static int reader_seek_indexed(struct reftable_reader *r,\n+\t\t\t       struct reftable_iterator *it,\n+\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct reftable_index_record want_index = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record want_index_rec = { NULL };\n+\tstruct reftable_index_record index_result = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record index_result_rec = { NULL };\n+\tstruct table_iter index_iter = TABLE_ITER_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = 0;\n+\n+\treftable_record_key(rec, &want_index.last_key);\n+\treftable_record_from_index(&want_index_rec, &want_index);\n+\treftable_record_from_index(&index_result_rec, &index_result);\n+\n+\terr = reader_start(r, &index_iter, reftable_record_type(rec), 1);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reader_seek_linear(r, &index_iter, &want_index_rec);\n+\twhile (1) {\n+\t\terr = table_iter_next(&index_iter, &index_result_rec);\n+\t\ttable_iter_block_done(&index_iter);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = reader_table_iter_at(r, &next, index_result.offset, 0);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = block_iter_seek(&next.bi, &want_index.last_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (next.typ == reftable_record_type(rec)) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (next.typ != BLOCK_TYPE_INDEX) {\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_copy_from(&index_iter, &next);\n+\t}\n+\n+\tif (err == 0) {\n+\t\tstruct table_iter empty = TABLE_ITER_INIT;\n+\t\tstruct table_iter *malloced =\n+\t\t\treftable_calloc(sizeof(struct table_iter));\n+\t\t*malloced = empty;\n+\t\ttable_iter_copy_from(malloced, &next);\n+\t\titerator_from_table_iter(it, malloced);\n+\t}\n+done:\n+\tblock_iter_close(&next.bi);\n+\ttable_iter_close(&index_iter);\n+\treftable_record_release(&want_index_rec);\n+\treftable_record_release(&index_result_rec);\n+\treturn err;\n+}\n+\n+static int reader_seek_internal(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it,\n+\t\t\t\tstruct reftable_record *rec)\n+{\n+\tstruct reftable_reader_offsets *offs =\n+\t\treader_offsets_for(r, reftable_record_type(rec));\n+\tuint64_t idx = offs->index_offset;\n+\tstruct table_iter ti = TABLE_ITER_INIT;\n+\tint err = 0;\n+\tif (idx > 0)\n+\t\treturn reader_seek_indexed(r, it, rec);\n+\n+\terr = reader_start(r, &ti, reftable_record_type(rec), 0);\n+\tif (err < 0)\n+\t\treturn err;\n+\terr = reader_seek_linear(r, &ti, rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct table_iter *p =\n+\t\t\treftable_malloc(sizeof(struct table_iter));\n+\t\t*p = ti;\n+\t\titerator_from_table_iter(it, p);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\tstruct reftable_record *rec)\n+{\n+\tuint8_t typ = reftable_record_type(rec);\n+\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tif (!offs->is_present) {\n+\t\titerator_set_empty(it);\n+\t\treturn 0;\n+\t}\n+\n+\treturn reader_seek_internal(r, it, rec);\n+}\n+\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_reader_seek_log_at(r, it, name, max);\n+}\n+\n+void reader_close(struct reftable_reader *r)\n+{\n+\tblock_source_close(&r->source);\n+\tFREE_AND_NULL(r->name);\n+}\n+\n+int reftable_new_reader(struct reftable_reader **p,\n+\t\t\tstruct reftable_block_source *src, char const *name)\n+{\n+\tstruct reftable_reader *rd =\n+\t\treftable_calloc(sizeof(struct reftable_reader));\n+\tint err = init_reader(rd, src, name);\n+\tif (err == 0) {\n+\t\t*p = rd;\n+\t} else {\n+\t\tblock_source_close(src);\n+\t\treftable_free(rd);\n+\t}\n+\treturn err;\n+}\n+\n+void reftable_reader_free(struct reftable_reader *r)\n+{\n+\treader_close(r);\n+\treftable_free(r);\n+}\n+\n+static int reftable_reader_refs_for_indexed(struct reftable_reader *r,\n+\t\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t\t    uint8_t *oid)\n+{\n+\tstruct reftable_obj_record want = {\n+\t\t.hash_prefix = oid,\n+\t\t.hash_prefix_len = r->object_id_len,\n+\t};\n+\tstruct reftable_record want_rec = { NULL };\n+\tstruct reftable_iterator oit = { NULL };\n+\tstruct reftable_obj_record got = { NULL };\n+\tstruct reftable_record got_rec = { NULL };\n+\tint err = 0;\n+\tstruct indexed_table_ref_iter *itr = NULL;\n+\n+\t/* Look through the reverse index. */\n+\treftable_record_from_obj(&want_rec, &want);\n+\terr = reader_seek(r, &oit, &want_rec);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\t/* read out the reftable_obj_record */\n+\treftable_record_from_obj(&got_rec, &got);\n+\terr = iterator_next(&oit, &got_rec);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 0 ||\n+\t    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {\n+\t\t/* didn't find it; return empty iterator */\n+\t\titerator_set_empty(it);\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),\n+\t\t\t\t\t got.offsets, got.offset_len);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tgot.offsets = NULL;\n+\titerator_from_indexed_table_ref_iter(it, itr);\n+\n+done:\n+\treftable_iterator_destroy(&oit);\n+\treftable_record_release(&got_rec);\n+\treturn err;\n+}\n+\n+static int reftable_reader_refs_for_unindexed(struct reftable_reader *r,\n+\t\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t\t      uint8_t *oid)\n+{\n+\tstruct table_iter ti_empty = TABLE_ITER_INIT;\n+\tstruct table_iter *ti = reftable_calloc(sizeof(struct table_iter));\n+\tstruct filtering_ref_iterator *filter = NULL;\n+\tstruct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT;\n+\tint oid_len = hash_size(r->hash_id);\n+\tint err;\n+\n+\t*ti = ti_empty;\n+\terr = reader_start(r, ti, BLOCK_TYPE_REF, 0);\n+\tif (err < 0) {\n+\t\treftable_free(ti);\n+\t\treturn err;\n+\t}\n+\n+\tfilter = reftable_malloc(sizeof(struct filtering_ref_iterator));\n+\t*filter = empty;\n+\n+\tstrbuf_add(&filter->oid, oid, oid_len);\n+\treftable_table_from_reader(&filter->tab, r);\n+\tfilter->double_check = 0;\n+\titerator_from_table_iter(&filter->it, ti);\n+\n+\titerator_from_filtering_ref_iterator(it, filter);\n+\treturn 0;\n+}\n+\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid)\n+{\n+\tif (r->obj_offsets.is_present)\n+\t\treturn reftable_reader_refs_for_indexed(r, it, oid);\n+\treturn reftable_reader_refs_for_unindexed(r, it, oid);\n+}\n+\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r)\n+{\n+\treturn r->max_update_index;\n+}\n+\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r)\n+{\n+\treturn r->min_update_index;\n+}\n+\n+/* generic table interface. */\n+\n+static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,\n+\t\t\t\t     struct reftable_record *rec)\n+{\n+\treturn reader_seek(tab, it, rec);\n+}\n+\n+static uint32_t reftable_reader_hash_id_void(void *tab)\n+{\n+\treturn reftable_reader_hash_id(tab);\n+}\n+\n+static uint64_t reftable_reader_min_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_reader_max_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable reader_vtable = {\n+\t.seek_record = reftable_reader_seek_void,\n+\t.hash_id = reftable_reader_hash_id_void,\n+\t.min_update_index = reftable_reader_min_update_index_void,\n+\t.max_update_index = reftable_reader_max_update_index_void,\n+};\n+\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &reader_vtable;\n+\ttab->table_arg = reader;\n+}\n+\n+\n+int reftable_reader_print_file(const char *tablename)\n+{\n+\tstruct reftable_block_source src = { NULL };\n+\tint err = reftable_block_source_from_file(&src, tablename);\n+\tstruct reftable_reader *r = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&r, &src, tablename);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\treftable_table_from_reader(&tab, r);\n+\terr = reftable_table_print(&tab);\n+done:\n+\treftable_reader_free(r);\n+\treturn err;\n+}\ndiff --git a/reftable/reader.h b/reftable/reader.h\nnew file mode 100644\nindex 00000000000..39583e5dbcd\n--- /dev/null\n+++ b/reftable/reader.h\n@@ -0,0 +1,66 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef READER_H\n+#define READER_H\n+\n+#include \"block.h\"\n+#include \"record.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source);\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size);\n+void block_source_close(struct reftable_block_source *source);\n+\n+/* metadata for a block type */\n+struct reftable_reader_offsets {\n+\tint is_present;\n+\tuint64_t offset;\n+\tuint64_t index_offset;\n+};\n+\n+/* The state for reading a reftable file. */\n+struct reftable_reader {\n+\t/* for convience, associate a name with the instance. */\n+\tchar *name;\n+\tstruct reftable_block_source source;\n+\n+\t/* Size of the file, excluding the footer. */\n+\tuint64_t size;\n+\n+\t/* 'sha1' for SHA1, 's256' for SHA-256 */\n+\tuint32_t hash_id;\n+\n+\tuint32_t block_size;\n+\tuint64_t min_update_index;\n+\tuint64_t max_update_index;\n+\t/* Length of the OID keys in the 'o' section */\n+\tint object_id_len;\n+\tint version;\n+\n+\tstruct reftable_reader_offsets ref_offsets;\n+\tstruct reftable_reader_offsets obj_offsets;\n+\tstruct reftable_reader_offsets log_offsets;\n+};\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name);\n+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\tstruct reftable_record *rec);\n+void reader_close(struct reftable_reader *r);\n+const char *reader_name(struct reftable_reader *r);\n+\n+/* initialize a block reader to read from `r` */\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ);\n+\n+#endif\ndiff --git a/reftable/reftable-reader.h b/reftable/reftable-reader.h\nnew file mode 100644\nindex 00000000000..4a4bc2fdf85\n--- /dev/null\n+++ b/reftable/reftable-reader.h\n@@ -0,0 +1,101 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_READER_H\n+#define REFTABLE_READER_H\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Reading single tables\n+ *\n+ * The follow routines are for reading single files. For an\n+ * application-level interface, skip ahead to struct\n+ * reftable_merged_table and struct reftable_stack.\n+ */\n+\n+/* The reader struct is a handle to an open reftable file. */\n+struct reftable_reader;\n+\n+/* Generic table. */\n+struct reftable_table;\n+\n+/* reftable_new_reader opens a reftable for reading. If successful,\n+ * returns 0 code and sets pp. The name is used for creating a\n+ * stack. Typically, it is the basename of the file. The block source\n+ * `src` is owned by the reader, and is closed on calling\n+ * reftable_reader_destroy(). On error, the block source `src` is\n+ * closed as well.\n+ */\n+int reftable_new_reader(struct reftable_reader **pp,\n+\t\t\tstruct reftable_block_source *src, const char *name);\n+\n+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted\n+   in the table.  To seek to the start of the table, use name = \"\".\n+\n+   example:\n+\n+   struct reftable_reader *r = NULL;\n+   int err = reftable_new_reader(&r, &src, \"filename\");\n+   if (err < 0) { ... }\n+   struct reftable_iterator it  = {0};\n+   err = reftable_reader_seek_ref(r, &it, \"refs/heads/master\");\n+   if (err < 0) { ... }\n+   struct reftable_ref_record ref  = {0};\n+   while (1) {\n+   err = reftable_iterator_next_ref(&it, &ref);\n+   if (err > 0) {\n+   break;\n+   }\n+   if (err < 0) {\n+   ..error handling..\n+   }\n+   ..found..\n+   }\n+   reftable_iterator_destroy(&it);\n+   reftable_ref_record_release(&ref);\n+*/\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID used in this table. */\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r);\n+\n+/* seek to logs for the given name, older than update_index. To seek to the\n+   start of the table, use name = \"\".\n+*/\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index);\n+\n+/* seek to newest log entry for given name. */\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* closes and deallocates a reader. */\n+void reftable_reader_free(struct reftable_reader *);\n+\n+/* return an iterator for the refs pointing to `oid`. */\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid);\n+\n+/* return the max_update_index for a table */\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);\n+\n+/* return the min_update_index for a table */\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);\n+\n+/* creates a generic table from a file reader. */\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader);\n+\n+/* print table onto stdout for debugging. */\n+int reftable_reader_print_file(const char *tablename);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"437318","messageId":"40a91b14be89a53e6501cf34a4ca5dcf3c874dc6.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 15/19] reftable: add merged table view","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:12Z","receivedAt":"2021-09-28T15:10:54Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis adds an abstract, read-only interface to the ref database.\n\nThis primitive is used to construct the read view of the ref database\n(the read view is constructed by merging several *.ref files). It also\nprovides the mechanism to provide a unified view of the refs in the main\nrepository and the per-worktree refs.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/merged.c          | 362 +++++++++++++++++++++++++++++++++++++\n reftable/merged.h          |  35 ++++\n reftable/merged_test.c     | 292 ++++++++++++++++++++++++++++++\n reftable/reftable-merged.h |  72 ++++++++\n t/helper/test-reftable.c   |   1 +\n 6 files changed, 764 insertions(+)\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/reftable-merged.h\n\ndiff --git a/Makefile b/Makefile\nindex 86509141558..d90aa25f764 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2440,6 +2440,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/merged.o\n REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n@@ -2451,6 +2452,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\ndiff --git a/reftable/merged.c b/reftable/merged.c\nnew file mode 100644\nindex 00000000000..e5b53da6db3\n--- /dev/null\n+++ b/reftable/merged.c\n@@ -0,0 +1,362 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"constants.h\"\n+#include \"iter.h\"\n+#include \"pq.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+\n+static int merged_iter_init(struct merged_iter *mi)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\t\tint err = iterator_next(&mi->stack[i], &rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t\t\treftable_record_destroy(&rec);\n+\t\t} else {\n+\t\t\tstruct pq_entry e = {\n+\t\t\t\t.rec = rec,\n+\t\t\t\t.index = i,\n+\t\t\t};\n+\t\t\tmerged_iter_pqueue_add(&mi->pq, e);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merged_iter_close(void *p)\n+{\n+\tstruct merged_iter *mi = p;\n+\tint i = 0;\n+\tmerged_iter_pqueue_release(&mi->pq);\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t}\n+\treftable_free(mi->stack);\n+}\n+\n+static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,\n+\t\t\t\t\t       size_t idx)\n+{\n+\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\tstruct pq_entry e = {\n+\t\t.rec = rec,\n+\t\t.index = idx,\n+\t};\n+\tint err = iterator_next(&mi->stack[idx], &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (err > 0) {\n+\t\treftable_iterator_destroy(&mi->stack[idx]);\n+\t\treftable_record_destroy(&rec);\n+\t\treturn 0;\n+\t}\n+\n+\tmerged_iter_pqueue_add(&mi->pq, e);\n+\treturn 0;\n+}\n+\n+static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)\n+{\n+\tif (iterator_is_null(&mi->stack[idx]))\n+\t\treturn 0;\n+\treturn merged_iter_advance_nonnull_subiter(mi, idx);\n+}\n+\n+static int merged_iter_next_entry(struct merged_iter *mi,\n+\t\t\t\t  struct reftable_record *rec)\n+{\n+\tstruct strbuf entry_key = STRBUF_INIT;\n+\tstruct pq_entry entry = { 0 };\n+\tint err = 0;\n+\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\tentry = merged_iter_pqueue_remove(&mi->pq);\n+\terr = merged_iter_advance_subiter(mi, entry.index);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\t/*\n+\t  One can also use reftable as datacenter-local storage, where the ref\n+\t  database is maintained in globally consistent database (eg.\n+\t  CockroachDB or Spanner). In this scenario, replication delays together\n+\t  with compaction may cause newer tables to contain older entries. In\n+\t  such a deployment, the loop below must be changed to collect all\n+\t  entries for the same key, and return new the newest one.\n+\t*/\n+\treftable_record_key(&entry.rec, &entry_key);\n+\twhile (!merged_iter_pqueue_is_empty(mi->pq)) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(mi->pq);\n+\t\tstruct strbuf k = STRBUF_INIT;\n+\t\tint err = 0, cmp = 0;\n+\n+\t\treftable_record_key(&top.rec, &k);\n+\n+\t\tcmp = strbuf_cmp(&k, &entry_key);\n+\t\tstrbuf_release(&k);\n+\n+\t\tif (cmp > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmerged_iter_pqueue_remove(&mi->pq);\n+\t\terr = merged_iter_advance_subiter(mi, top.index);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_record_destroy(&top.rec);\n+\t}\n+\n+\treftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));\n+\treftable_record_destroy(&entry.rec);\n+\tstrbuf_release(&entry_key);\n+\treturn 0;\n+}\n+\n+static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)\n+{\n+\twhile (1) {\n+\t\tint err = merged_iter_next_entry(mi, rec);\n+\t\tif (err == 0 && mi->suppress_deletions &&\n+\t\t    reftable_record_is_deletion(rec)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treturn err;\n+\t}\n+}\n+\n+static int merged_iter_next_void(void *p, struct reftable_record *rec)\n+{\n+\tstruct merged_iter *mi = p;\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\treturn merged_iter_next(mi, rec);\n+}\n+\n+static struct reftable_iterator_vtable merged_iter_vtable = {\n+\t.next = &merged_iter_next_void,\n+\t.close = &merged_iter_close,\n+};\n+\n+static void iterator_from_merged_iter(struct reftable_iterator *it,\n+\t\t\t\t      struct merged_iter *mi)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = mi;\n+\tit->ops = &merged_iter_vtable;\n+}\n+\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id)\n+{\n+\tstruct reftable_merged_table *m = NULL;\n+\tuint64_t last_max = 0;\n+\tuint64_t first_min = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t min = reftable_table_min_update_index(&stack[i]);\n+\t\tuint64_t max = reftable_table_max_update_index(&stack[i]);\n+\n+\t\tif (reftable_table_hash_id(&stack[i]) != hash_id) {\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\t\t}\n+\t\tif (i == 0 || min < first_min) {\n+\t\t\tfirst_min = min;\n+\t\t}\n+\t\tif (i == 0 || max > last_max) {\n+\t\t\tlast_max = max;\n+\t\t}\n+\t}\n+\n+\tm = reftable_calloc(sizeof(struct reftable_merged_table));\n+\tm->stack = stack;\n+\tm->stack_len = n;\n+\tm->min = first_min;\n+\tm->max = last_max;\n+\tm->hash_id = hash_id;\n+\t*dest = m;\n+\treturn 0;\n+}\n+\n+/* clears the list of subtable, without affecting the readers themselves. */\n+void merged_table_release(struct reftable_merged_table *mt)\n+{\n+\tFREE_AND_NULL(mt->stack);\n+\tmt->stack_len = 0;\n+}\n+\n+void reftable_merged_table_free(struct reftable_merged_table *mt)\n+{\n+\tif (!mt) {\n+\t\treturn;\n+\t}\n+\tmerged_table_release(mt);\n+\treftable_free(mt);\n+}\n+\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->max;\n+}\n+\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->min;\n+}\n+\n+static int reftable_table_seek_record(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      struct reftable_record *rec)\n+{\n+\treturn tab->ops->seek_record(tab->table_arg, it, rec);\n+}\n+\n+static int merged_table_seek_record(struct reftable_merged_table *mt,\n+\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tstruct reftable_iterator *iters = reftable_calloc(\n+\t\tsizeof(struct reftable_iterator) * mt->stack_len);\n+\tstruct merged_iter merged = {\n+\t\t.stack = iters,\n+\t\t.typ = reftable_record_type(rec),\n+\t\t.hash_id = mt->hash_id,\n+\t\t.suppress_deletions = mt->suppress_deletions,\n+\t};\n+\tint n = 0;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < mt->stack_len && err == 0; i++) {\n+\t\tint e = reftable_table_seek_record(&mt->stack[i], &iters[n],\n+\t\t\t\t\t\t   rec);\n+\t\tif (e < 0) {\n+\t\t\terr = e;\n+\t\t}\n+\t\tif (e == 0) {\n+\t\t\tn++;\n+\t\t}\n+\t}\n+\tif (err < 0) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < n; i++) {\n+\t\t\treftable_iterator_destroy(&iters[i]);\n+\t\t}\n+\t\treftable_free(iters);\n+\t\treturn err;\n+\t}\n+\n+\tmerged.stack_len = n;\n+\terr = merged_iter_init(&merged);\n+\tif (err < 0) {\n+\t\tmerged_iter_close(&merged);\n+\t\treturn err;\n+\t} else {\n+\t\tstruct merged_iter *p =\n+\t\t\treftable_malloc(sizeof(struct merged_iter));\n+\t\t*p = merged;\n+\t\titerator_from_merged_iter(it, p);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_merged_table_seek_log_at(mt, it, name, max);\n+}\n+\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt)\n+{\n+\treturn mt->hash_id;\n+}\n+\n+static int reftable_merged_table_seek_void(void *tab,\n+\t\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t\t   struct reftable_record *rec)\n+{\n+\treturn merged_table_seek_record(tab, it, rec);\n+}\n+\n+static uint32_t reftable_merged_table_hash_id_void(void *tab)\n+{\n+\treturn reftable_merged_table_hash_id(tab);\n+}\n+\n+static uint64_t reftable_merged_table_min_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_merged_table_max_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable merged_table_vtable = {\n+\t.seek_record = reftable_merged_table_seek_void,\n+\t.hash_id = reftable_merged_table_hash_id_void,\n+\t.min_update_index = reftable_merged_table_min_update_index_void,\n+\t.max_update_index = reftable_merged_table_max_update_index_void,\n+};\n+\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *merged)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &merged_table_vtable;\n+\ttab->table_arg = merged;\n+}\ndiff --git a/reftable/merged.h b/reftable/merged.h\nnew file mode 100644\nindex 00000000000..8c4d4d58d77\n--- /dev/null\n+++ b/reftable/merged.h\n@@ -0,0 +1,35 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef MERGED_H\n+#define MERGED_H\n+\n+#include \"pq.h\"\n+\n+struct reftable_merged_table {\n+\tstruct reftable_table *stack;\n+\tsize_t stack_len;\n+\tuint32_t hash_id;\n+\tint suppress_deletions;\n+\n+\tuint64_t min;\n+\tuint64_t max;\n+};\n+\n+struct merged_iter {\n+\tstruct reftable_iterator *stack;\n+\tuint32_t hash_id;\n+\tsize_t stack_len;\n+\tuint8_t typ;\n+\tint suppress_deletions;\n+\tstruct merged_iter_pqueue pq;\n+};\n+\n+void merged_table_release(struct reftable_merged_table *mt);\n+\n+#endif\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nnew file mode 100644\nindex 00000000000..da607401141\n--- /dev/null\n+++ b/reftable/merged_test.c\n@@ -0,0 +1,292 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-generic.h\"\n+#include \"reftable-writer.h\"\n+\n+static void write_test_table(struct strbuf *buf,\n+\t\t\t     struct reftable_ref_record refs[], int n)\n+{\n+\tint min = 0xffffffff;\n+\tint max = 0;\n+\tint i = 0;\n+\tint err;\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_writer *w = NULL;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t ui = refs[i].update_index;\n+\t\tif (ui > max) {\n+\t\t\tmax = ui;\n+\t\t}\n+\t\tif (ui < min) {\n+\t\t\tmin = ui;\n+\t\t}\n+\t}\n+\n+\tw = reftable_new_writer(&strbuf_add_void, buf, &opts);\n+\treftable_writer_set_limits(w, min, max);\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t before = refs[i].update_index;\n+\t\tint n = reftable_writer_add_ref(w, &refs[i]);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT(before == refs[i].update_index);\n+\t}\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\n+\treftable_writer_free(w);\n+}\n+\n+static struct reftable_merged_table *\n+merged_table_from_records(struct reftable_ref_record **refs,\n+\t\t\t  struct reftable_block_source **source,\n+\t\t\t  struct reftable_reader ***readers, int *sizes,\n+\t\t\t  struct strbuf *buf, int n)\n+{\n+\tint i = 0;\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err;\n+\tstruct reftable_table *tabs =\n+\t\treftable_calloc(n * sizeof(struct reftable_table));\n+\t*readers = reftable_calloc(n * sizeof(struct reftable_reader *));\n+\t*source = reftable_calloc(n * sizeof(**source));\n+\tfor (i = 0; i < n; i++) {\n+\t\twrite_test_table(&buf[i], refs[i], sizes[i]);\n+\t\tblock_source_from_strbuf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_new_reader(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tEXPECT_ERR(err);\n+\t\treftable_table_from_reader(&tabs[i], (*readers)[i]);\n+\t}\n+\n+\terr = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\treturn mt;\n+}\n+\n+static void readers_destroy(struct reftable_reader **readers, size_t n)\n+{\n+\tint i = 0;\n+\tfor (; i < n; i++)\n+\t\treftable_reader_free(readers[i]);\n+\treftable_free(readers);\n+}\n+\n+static void test_merged_between(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };\n+\n+\tstruct reftable_ref_record r1[] = { {\n+\t\t.refname = \"b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = hash1,\n+\t} };\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2 };\n+\tint sizes[] = { 1, 1 };\n+\tstruct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);\n+\tint i;\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\tEXPECT(ref.update_index == 2);\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treaders_destroy(readers, 2);\n+\treftable_merged_table_free(mt);\n+\tfor (i = 0; i < ARRAY_SIZE(bufs); i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treftable_free(bs);\n+}\n+\n+static void test_merged(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };\n+\tuint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\tstruct reftable_ref_record r3[] = {\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash2,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"d\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t};\n+\n+\tstruct reftable_ref_record want[] = {\n+\t\tr2[0],\n+\t\tr1[1],\n+\t\tr3[0],\n+\t\tr3[1],\n+\t};\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n+\tint sizes[3] = { 3, 1, 2 };\n+\tstruct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tstruct reftable_ref_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tint i = 0;\n+\n+\tEXPECT_ERR(err);\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (len == cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\tout = reftable_realloc(\n+\t\t\t\tout, sizeof(struct reftable_ref_record) * cap);\n+\t\t}\n+\t\tout[len++] = ref;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tEXPECT(ARRAY_SIZE(want) == len);\n+\tfor (i = 0; i < len; i++) {\n+\t\tEXPECT(reftable_ref_record_equal(&want[i], &out[i],\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t}\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&out[i]);\n+\t}\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+static void test_default_write_opts(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"master\",\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);\n+\tuint32_t hash_id;\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_merged_table *merged = NULL;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\thash_id = reftable_reader_hash_id(rd);\n+\tEXPECT(hash_id == GIT_SHA1_FORMAT_ID);\n+\n+\treftable_table_from_reader(&tab[0], rd);\n+\terr = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\treftable_reader_free(rd);\n+\treftable_merged_table_free(merged);\n+\tstrbuf_release(&buf);\n+}\n+\n+/* XXX test refs_for(oid) */\n+\n+int merged_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_merged_between);\n+\tRUN_TEST(test_merged);\n+\tRUN_TEST(test_default_write_opts);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-merged.h b/reftable/reftable-merged.h\nnew file mode 100644\nindex 00000000000..1a6d16915ab\n--- /dev/null\n+++ b/reftable/reftable-merged.h\n@@ -0,0 +1,72 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_MERGED_H\n+#define REFTABLE_MERGED_H\n+\n+#include \"reftable-iterator.h\"\n+\n+/*\n+ * Merged tables\n+ *\n+ * A ref database kept in a sequence of table files. The merged_table presents a\n+ * unified view to reading (seeking, iterating) a sequence of immutable tables.\n+ *\n+ * The merged tables are on purpose kept disconnected from their actual storage\n+ * (eg. files on disk), because it is useful to merge tables aren't files. For\n+ * example, the per-workspace and global ref namespace can be implemented as a\n+ * merged table of two stacks of file-backed reftables.\n+ */\n+\n+/* A merged table is implements seeking/iterating over a stack of tables. */\n+struct reftable_merged_table;\n+\n+/* A generic reftable; see below. */\n+struct reftable_table;\n+\n+/* reftable_new_merged_table creates a new merged table. It takes ownership of\n+   the stack array.\n+*/\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id);\n+\n+/* returns an iterator positioned just before 'name' */\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns an iterator for log entry, at given update_index */\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index);\n+\n+/* like reftable_merged_table_seek_log_at but look for the newest entry. */\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns the max update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);\n+\n+/* returns the min update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);\n+\n+/* releases memory for the merged_table */\n+void reftable_merged_table_free(struct reftable_merged_table *m);\n+\n+/* return the hash ID of the merged table. */\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);\n+\n+/* create a generic table from reftable_merged_table */\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *table);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 0b5a1701df1..8087f2da4e6 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"437319","messageId":"8efc3c2b6ab494a94107bf01de6ada2a0fa76ff3.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 13/19] reftable: reftable file level tests","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:10Z","receivedAt":"2021-09-28T15:10:55Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nWith support for reading and writing files in place, we can construct files (in\nmemory) and attempt to read them back.\n\nBecause some sections of the format are optional (eg. indices, log entries), we\nhave to exercise this code using multiple sizes of input data\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   1 +\n reftable/readwrite_test.c | 652 ++++++++++++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   2 +-\n t/helper/test-reftable.c  |   1 +\n 4 files changed, 655 insertions(+), 1 deletion(-)\n create mode 100644 reftable/readwrite_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 8a058df33b8..a4551bdf562 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2451,6 +2451,7 @@ REFTABLE_OBJS += reftable/writer.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n+REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nnew file mode 100644\nindex 00000000000..5f6bcc2f775\n--- /dev/null\n+++ b/reftable/readwrite_test.c\n@@ -0,0 +1,652 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+\n+static const int update_index = 5;\n+\n+static void test_buffer(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_block out = { NULL };\n+\tint n;\n+\tuint8_t in[] = \"hello\";\n+\tstrbuf_add(&buf, in, sizeof(in));\n+\tblock_source_from_strbuf(&source, &buf);\n+\tEXPECT(block_source_size(&source) == 6);\n+\tn = block_source_read_block(&source, &out, 0, sizeof(in));\n+\tEXPECT(n == sizeof(in));\n+\tEXPECT(!memcmp(in, out.data, n));\n+\treftable_block_done(&out);\n+\n+\tn = block_source_read_block(&source, &out, 1, 2);\n+\tEXPECT(n == 2);\n+\tEXPECT(!memcmp(out.data, \"el\", 2));\n+\n+\treftable_block_done(&out);\n+\tblock_source_close(&source);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void write_table(char ***names, struct strbuf *buf, int N,\n+\t\t\tint block_size, uint32_t hash_id)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = block_size,\n+\t\t.hash_id = hash_id,\n+\t};\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, buf, &opts);\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0, n;\n+\tstruct reftable_log_record log = { NULL };\n+\tconst struct reftable_stats *stats = NULL;\n+\t*names = reftable_calloc(sizeof(char *) * (N + 1));\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tref.refname = name;\n+\t\tref.update_index = update_index;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\t\t(*names)[i] = xstrdup(name);\n+\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tlog.refname = name;\n+\t\tlog.update_index = update_index;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.new_hash = hash;\n+\t\tlog.value.update.message = \"message\";\n+\n+\t\tn = reftable_writer_add_log(w, &log);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tfor (i = 0; i < stats->ref_stats.blocks; i++) {\n+\t\tint off = i * opts.block_size;\n+\t\tif (off == 0) {\n+\t\t\toff = header_size(\n+\t\t\t\t(hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1);\n+\t\t}\n+\t\tEXPECT(buf->buf[off] == 'r');\n+\t}\n+\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+}\n+\n+static void test_log_buffer_size(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 4096,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record\n+\t\tlog = { .refname = \"refs/heads/master\",\n+\t\t\t.update_index = 0xa,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = { .update = {\n+\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .tz_offset = 100,\n+\t\t\t\t\t   .time = 0x5e430672,\n+\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t   } } };\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\t/* This tests buffer extension for log compression. Must use a random\n+\t   hash, to ensure that the compressed part is larger than the original.\n+\t*/\n+\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\tfor (i = 0; i < GIT_SHA1_RAWSZ; i++) {\n+\t\thash1[i] = (uint8_t)(rand() % 256);\n+\t\thash2[i] = (uint8_t)(rand() % 256);\n+\t}\n+\tlog.value.update.old_hash = hash1;\n+\tlog.value.update.new_hash = hash2;\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\terr = reftable_writer_add_log(w, &log);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_log_write_read(void)\n+{\n+\tint N = 2;\n+\tchar **names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint err;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tstruct reftable_log_record log = { NULL };\n+\tint n;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tconst struct reftable_stats *stats = NULL;\n+\treftable_writer_set_limits(w, 0, N);\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[256];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tsnprintf(name, sizeof(name), \"b%02d%0*d\", i, 130, 7);\n+\t\tnames[i] = xstrdup(name);\n+\t\tref.refname = name;\n+\t\tref.update_index = i;\n+\n+\t\terr = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_log_record log = { NULL };\n+\t\tset_test_hash(hash1, i);\n+\t\tset_test_hash(hash2, i + 1);\n+\n+\t\tlog.refname = names[i];\n+\t\tlog.update_index = i;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.old_hash = hash1;\n+\t\tlog.value.update.new_hash = hash2;\n+\n+\t\terr = reftable_writer_add_log(w, &log);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[N - 1]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* end of iteration. */\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT(0 < err);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_reader_seek_log(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\ti = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT_STREQ(names[i], log.refname);\n+\t\tEXPECT(i == log.update_index);\n+\t\ti++;\n+\t\treftable_log_record_release(&log);\n+\t}\n+\n+\tEXPECT(i == N);\n+\treftable_iterator_destroy(&it);\n+\n+\t/* cleanup. */\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_sequential(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err = 0;\n+\tint j = 0;\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint r = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT(0 == strcmp(names[j], ref.refname));\n+\t\tEXPECT(update_index == ref.update_index);\n+\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == N);\n+\treftable_iterator_destroy(&it);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\n+\treader_close(&rd);\n+}\n+\n+static void test_table_write_small_table(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 1;\n+\twrite_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);\n+\tEXPECT(buf.len < 200);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+}\n+\n+static void test_table_read_api(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record log = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[0]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_log(&it, &log);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_free(names);\n+\treader_close(&rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_table_read_write_seek(int index, int hash_id)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i = 0;\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct strbuf pastLast = STRBUF_INIT;\n+\tstruct reftable_ref_record ref = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, hash_id);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tEXPECT(hash_id == reftable_reader_hash_id(&rd));\n+\n+\tif (!index) {\n+\t\trd.ref_offsets.index_offset = 0;\n+\t} else {\n+\t\tEXPECT(rd.ref_offsets.index_offset > 0);\n+\t}\n+\n+\tfor (i = 1; i < N; i++) {\n+\t\tint err = reftable_reader_seek_ref(&rd, &it, names[i]);\n+\t\tEXPECT_ERR(err);\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(0 == strcmp(names[i], ref.refname));\n+\t\tEXPECT(REFTABLE_REF_VAL1 == ref.value_type);\n+\t\tEXPECT(i == ref.value.val1[0]);\n+\n+\t\treftable_ref_record_release(&ref);\n+\t\treftable_iterator_destroy(&it);\n+\t}\n+\n+\tstrbuf_addstr(&pastLast, names[N - 1]);\n+\tstrbuf_addstr(&pastLast, \"/\");\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, pastLast.buf);\n+\tif (err == 0) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err > 0);\n+\t} else {\n+\t\tEXPECT(err > 0);\n+\t}\n+\n+\tstrbuf_release(&pastLast);\n+\treftable_iterator_destroy(&it);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_free(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_seek_linear(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_linear_sha256(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_index(void)\n+{\n+\ttest_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_refs_for(int indexed)\n+{\n+\tint N = 50;\n+\tchar **want_names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint want_names_len = 0;\n+\tuint8_t want_hash[GIT_SHA1_RAWSZ];\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tint err;\n+\tstruct reftable_reader rd;\n+\tstruct reftable_block_source source = { NULL };\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint j;\n+\n+\tset_test_hash(want_hash, 4);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tchar fill[51] = { 0 };\n+\t\tchar name[100];\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ];\n+\t\tuint8_t hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\n+\t\tmemset(hash, i, sizeof(hash));\n+\t\tmemset(fill, 'x', 50);\n+\t\t/* Put the variable part in the start */\n+\t\tsnprintf(name, sizeof(name), \"br%02d%s\", i, fill);\n+\t\tname[40] = 0;\n+\t\tref.refname = name;\n+\n+\t\tset_test_hash(hash1, i / 4);\n+\t\tset_test_hash(hash2, 3 + i / 4);\n+\t\tref.value_type = REFTABLE_REF_VAL2;\n+\t\tref.value.val2.value = hash1;\n+\t\tref.value.val2.target_value = hash2;\n+\n+\t\t/* 80 bytes / entry, so 3 entries per block. Yields 17\n+\t\t */\n+\t\t/* blocks. */\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\n+\t\tif (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||\n+\t\t    !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {\n+\t\t\twant_names[want_names_len++] = xstrdup(name);\n+\t\t}\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tif (!indexed) {\n+\t\trd.obj_offsets.is_present = 0;\n+\t}\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_reader_refs_for(&rd, &it, want_hash);\n+\tEXPECT_ERR(err);\n+\n+\tj = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err >= 0);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT(j < want_names_len);\n+\t\tEXPECT(0 == strcmp(ref.refname, want_names[j]));\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == want_names_len);\n+\n+\tstrbuf_release(&buf);\n+\tfree_names(want_names);\n+\treftable_iterator_destroy(&it);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_refs_for_no_index(void)\n+{\n+\ttest_table_refs_for(0);\n+}\n+\n+static void test_table_refs_for_obj_index(void)\n+{\n+\ttest_table_refs_for(1);\n+}\n+\n+static void test_write_empty_table(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_ref_record rec = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);\n+\treftable_writer_free(w);\n+\n+\tEXPECT(buf.len == header_size(1) + footer_size(1));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &rec);\n+\tEXPECT(err > 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_write_key_order(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record refs[2] = {\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}, {\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}\n+\t};\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\terr = reftable_writer_add_ref(w, &refs[0]);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_add_ref(w, &refs[1]);\n+\tprintf(\"%d\\n\", err);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_writer_close(w);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_corrupt_table_empty(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+}\n+\n+static void test_corrupt_table(void)\n+{\n+\tuint8_t zeros[1024] = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\tstrbuf_add(&buf, zeros, sizeof(zeros));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\tstrbuf_release(&buf);\n+}\n+\n+int readwrite_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_corrupt_table);\n+\tRUN_TEST(test_corrupt_table_empty);\n+\tRUN_TEST(test_log_write_read);\n+\tRUN_TEST(test_write_key_order);\n+\tRUN_TEST(test_table_read_write_seek_linear_sha256);\n+\tRUN_TEST(test_log_buffer_size);\n+\tRUN_TEST(test_table_write_small_table);\n+\tRUN_TEST(test_buffer);\n+\tRUN_TEST(test_table_read_api);\n+\tRUN_TEST(test_table_read_write_sequential);\n+\tRUN_TEST(test_table_read_write_seek_linear);\n+\tRUN_TEST(test_table_read_write_seek_index);\n+\tRUN_TEST(test_table_refs_for_no_index);\n+\tRUN_TEST(test_table_refs_for_obj_index);\n+\tRUN_TEST(test_write_empty_table);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 5e7698ae654..3d541fa5c0c 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -14,7 +14,7 @@ int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n-int reftable_test_main(int argc, const char **argv);\n+int readwrite_test_main(int argc, const char **argv);\n int stack_test_main(int argc, const char **argv);\n int tree_test_main(int argc, const char **argv);\n int reftable_dump_main(int argc, char *const *argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 050551fa698..898aba836fd 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,6 +6,7 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"437320","messageId":"e89b16a37e67f1f8af550485632776e7ed9189f3.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 14/19] reftable: add a heap-based priority queue for reftable records","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:11Z","receivedAt":"2021-09-28T15:11:02Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis is needed to create a merged view multiple reftables\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   2 +\n reftable/pq.c             | 105 ++++++++++++++++++++++++++++++++++++++\n reftable/pq.h             |  33 ++++++++++++\n reftable/pq_test.c        |  82 +++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   1 +\n t/helper/test-reftable.c  |   1 +\n 6 files changed, 224 insertions(+)\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n\ndiff --git a/Makefile b/Makefile\nindex a4551bdf562..86509141558 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2440,6 +2440,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n@@ -2450,6 +2451,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/pq.c b/reftable/pq.c\nnew file mode 100644\nindex 00000000000..efc474017a2\n--- /dev/null\n+++ b/reftable/pq.c\n@@ -0,0 +1,105 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"pq.h\"\n+\n+#include \"reftable-record.h\"\n+#include \"system.h\"\n+#include \"basics.h\"\n+\n+int pq_less(struct pq_entry *a, struct pq_entry *b)\n+{\n+\tstruct strbuf ak = STRBUF_INIT;\n+\tstruct strbuf bk = STRBUF_INIT;\n+\tint cmp = 0;\n+\treftable_record_key(&a->rec, &ak);\n+\treftable_record_key(&b->rec, &bk);\n+\n+\tcmp = strbuf_cmp(&ak, &bk);\n+\n+\tstrbuf_release(&ak);\n+\tstrbuf_release(&bk);\n+\n+\tif (cmp == 0)\n+\t\treturn a->index > b->index;\n+\n+\treturn cmp < 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.heap[0];\n+}\n+\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.len == 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tstruct pq_entry e = pq->heap[0];\n+\tpq->heap[0] = pq->heap[pq->len - 1];\n+\tpq->len--;\n+\n+\ti = 0;\n+\twhile (i < pq->len) {\n+\t\tint min = i;\n+\t\tint j = 2 * i + 1;\n+\t\tint k = 2 * i + 2;\n+\t\tif (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) {\n+\t\t\tmin = j;\n+\t\t}\n+\t\tif (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) {\n+\t\t\tmin = k;\n+\t\t}\n+\n+\t\tif (min == i) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[i], pq->heap[min]);\n+\t\ti = min;\n+\t}\n+\n+\treturn e;\n+}\n+\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)\n+{\n+\tint i = 0;\n+\tif (pq->len == pq->cap) {\n+\t\tpq->cap = 2 * pq->cap + 1;\n+\t\tpq->heap = reftable_realloc(pq->heap,\n+\t\t\t\t\t    pq->cap * sizeof(struct pq_entry));\n+\t}\n+\n+\tpq->heap[pq->len++] = e;\n+\ti = pq->len - 1;\n+\twhile (i > 0) {\n+\t\tint j = (i - 1) / 2;\n+\t\tif (pq_less(&pq->heap[j], &pq->heap[i])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[j], pq->heap[i]);\n+\n+\t\ti = j;\n+\t}\n+}\n+\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < pq->len; i++) {\n+\t\treftable_record_destroy(&pq->heap[i].rec);\n+\t}\n+\tFREE_AND_NULL(pq->heap);\n+\tpq->len = pq->cap = 0;\n+}\ndiff --git a/reftable/pq.h b/reftable/pq.h\nnew file mode 100644\nindex 00000000000..56fc1b6d873\n--- /dev/null\n+++ b/reftable/pq.h\n@@ -0,0 +1,33 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef PQ_H\n+#define PQ_H\n+\n+#include \"record.h\"\n+\n+struct pq_entry {\n+\tint index;\n+\tstruct reftable_record rec;\n+};\n+\n+struct merged_iter_pqueue {\n+\tstruct pq_entry *heap;\n+\tsize_t len;\n+\tsize_t cap;\n+};\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);\n+int pq_less(struct pq_entry *a, struct pq_entry *b);\n+\n+#endif\ndiff --git a/reftable/pq_test.c b/reftable/pq_test.c\nnew file mode 100644\nindex 00000000000..aaa86d12247\n--- /dev/null\n+++ b/reftable/pq_test.c\n@@ -0,0 +1,82 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"pq.h\"\n+#include \"record.h\"\n+#include \"reftable-tests.h\"\n+#include \"test_framework.h\"\n+\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n+{\n+\tint i = 0;\n+\tfor (i = 1; i < pq.len; i++) {\n+\t\tint parent = (i - 1) / 2;\n+\n+\t\tassert(pq_less(&pq.heap[parent], &pq.heap[i]));\n+\t}\n+}\n+\n+static void test_pq(void)\n+{\n+\tchar *names[54] = { NULL };\n+\tint N = ARRAY_SIZE(names) - 1;\n+\n+\tstruct merged_iter_pqueue pq = { NULL };\n+\tconst char *last = NULL;\n+\n+\tint i = 0;\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tsnprintf(name, sizeof(name), \"%02d\", i);\n+\t\tnames[i] = xstrdup(name);\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct reftable_record rec =\n+\t\t\treftable_new_record(BLOCK_TYPE_REF);\n+\t\tstruct pq_entry e = { 0 };\n+\n+\t\treftable_record_as_ref(&rec)->refname = names[i];\n+\t\te.rec = rec;\n+\t\tmerged_iter_pqueue_add(&pq, e);\n+\t\tmerged_iter_pqueue_check(pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\twhile (!merged_iter_pqueue_is_empty(pq)) {\n+\t\tstruct pq_entry e = merged_iter_pqueue_remove(&pq);\n+\t\tstruct reftable_ref_record *ref =\n+\t\t\treftable_record_as_ref(&e.rec);\n+\n+\t\tmerged_iter_pqueue_check(pq);\n+\n+\t\tif (last) {\n+\t\t\tEXPECT(strcmp(last, ref->refname) < 0);\n+\t\t}\n+\t\tlast = ref->refname;\n+\t\tref->refname = NULL;\n+\t\treftable_free(ref);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\n+\tmerged_iter_pqueue_release(&pq);\n+}\n+\n+int pq_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_pq);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 3d541fa5c0c..0019cbcfa49 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -12,6 +12,7 @@ https://developers.google.com/open-source/licenses/bsd\n int basics_test_main(int argc, const char **argv);\n int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n+int pq_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n int readwrite_test_main(int argc, const char **argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 898aba836fd..0b5a1701df1 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"437321","messageId":"46c2ddf07cfd776b4965e8ceaa38b04045a2fc80.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 16/19] reftable: implement refname validation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:13Z","receivedAt":"2021-09-28T15:11:07Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe packed/loose format has restrictions on refnames: a and a/b cannot\ncoexist. This limitation does not apply to reftable per se, but must be\nmaintained for interoperability. This code adds validation routines to\nabort transactions that are trying to add invalid names.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   1 +\n reftable/refname.c       | 209 +++++++++++++++++++++++++++++++++++++++\n reftable/refname.h       |  29 ++++++\n reftable/refname_test.c  | 102 +++++++++++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 342 insertions(+)\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n\ndiff --git a/Makefile b/Makefile\nindex d90aa25f764..226012f5426 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2456,6 +2456,7 @@ REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n+REFTABLE_TEST_OBJS += reftable/refname_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/refname.c b/reftable/refname.c\nnew file mode 100644\nindex 00000000000..95734969324\n--- /dev/null\n+++ b/reftable/refname.c\n@@ -0,0 +1,209 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+#include \"refname.h\"\n+#include \"reftable-iterator.h\"\n+\n+struct find_arg {\n+\tchar **names;\n+\tconst char *want;\n+};\n+\n+static int find_name(size_t k, void *arg)\n+{\n+\tstruct find_arg *f_arg = arg;\n+\treturn strcmp(f_arg->names[k], f_arg->want) >= 0;\n+}\n+\n+static int modification_has_ref(struct modification *mod, const char *name)\n+{\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len && !strcmp(mod->add[idx], name)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (mod->del_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->del,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\tif (idx < mod->del_len && !strcmp(mod->del[idx], name)) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\terr = reftable_table_read_ref(&mod->tab, name, &ref);\n+\treftable_ref_record_release(&ref);\n+\treturn err;\n+}\n+\n+static void modification_release(struct modification *mod)\n+{\n+\t/* don't delete the strings themselves; they're owned by ref records.\n+\t */\n+\tFREE_AND_NULL(mod->add);\n+\tFREE_AND_NULL(mod->del);\n+\tmod->add_len = 0;\n+\tmod->del_len = 0;\n+}\n+\n+static int modification_has_ref_with_prefix(struct modification *mod,\n+\t\t\t\t\t    const char *prefix)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = prefix,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len &&\n+\t\t    !strncmp(prefix, mod->add[idx], strlen(prefix)))\n+\t\t\tgoto done;\n+\t}\n+\terr = reftable_table_seek_ref(&mod->tab, &it, prefix);\n+\tif (err)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\n+\t\tif (mod->del_len > 0) {\n+\t\t\tstruct find_arg arg = {\n+\t\t\t\t.names = mod->del,\n+\t\t\t\t.want = ref.refname,\n+\t\t\t};\n+\t\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\t\tif (idx < mod->del_len &&\n+\t\t\t    !strcmp(ref.refname, mod->del[idx])) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (strncmp(ref.refname, prefix, strlen(prefix))) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int validate_refname(const char *name)\n+{\n+\twhile (1) {\n+\t\tchar *next = strchr(name, '/');\n+\t\tif (!*name) {\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\t}\n+\t\tif (!next) {\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (next - name == 0 || (next - name == 1 && *name == '.') ||\n+\t\t    (next - name == 2 && name[0] == '.' && name[1] == '.'))\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\tname = next + 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz)\n+{\n+\tstruct modification mod = {\n+\t\t.tab = tab,\n+\t\t.add = reftable_calloc(sizeof(char *) * sz),\n+\t\t.del = reftable_calloc(sizeof(char *) * sz),\n+\t};\n+\tint i = 0;\n+\tint err = 0;\n+\tfor (; i < sz; i++) {\n+\t\tif (reftable_ref_record_is_deletion(&recs[i])) {\n+\t\t\tmod.del[mod.del_len++] = recs[i].refname;\n+\t\t} else {\n+\t\t\tmod.add[mod.add_len++] = recs[i].refname;\n+\t\t}\n+\t}\n+\n+\terr = modification_validate(&mod);\n+\tmodification_release(&mod);\n+\treturn err;\n+}\n+\n+static void strbuf_trim_component(struct strbuf *sl)\n+{\n+\twhile (sl->len > 0) {\n+\t\tint is_slash = (sl->buf[sl->len - 1] == '/');\n+\t\tstrbuf_setlen(sl, sl->len - 1);\n+\t\tif (is_slash)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+int modification_validate(struct modification *mod)\n+{\n+\tstruct strbuf slashed = STRBUF_INIT;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (; i < mod->add_len; i++) {\n+\t\terr = validate_refname(mod->add[i]);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\tstrbuf_addstr(&slashed, \"/\");\n+\n+\t\terr = modification_has_ref_with_prefix(mod, slashed.buf);\n+\t\tif (err == 0) {\n+\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\twhile (slashed.len) {\n+\t\t\tstrbuf_trim_component(&slashed);\n+\t\t\terr = modification_has_ref(mod, slashed.buf);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\t}\n+\terr = 0;\n+done:\n+\tstrbuf_release(&slashed);\n+\treturn err;\n+}\ndiff --git a/reftable/refname.h b/reftable/refname.h\nnew file mode 100644\nindex 00000000000..a24b40fcb42\n--- /dev/null\n+++ b/reftable/refname.h\n@@ -0,0 +1,29 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+#ifndef REFNAME_H\n+#define REFNAME_H\n+\n+#include \"reftable-record.h\"\n+#include \"reftable-generic.h\"\n+\n+struct modification {\n+\tstruct reftable_table tab;\n+\n+\tchar **add;\n+\tsize_t add_len;\n+\n+\tchar **del;\n+\tsize_t del_len;\n+};\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz);\n+\n+int modification_validate(struct modification *mod);\n+\n+#endif\ndiff --git a/reftable/refname_test.c b/reftable/refname_test.c\nnew file mode 100644\nindex 00000000000..8645cd93bbd\n--- /dev/null\n+++ b/reftable/refname_test.c\n@@ -0,0 +1,102 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-writer.h\"\n+#include \"system.h\"\n+\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct testcase {\n+\tchar *add;\n+\tchar *del;\n+\tint error_code;\n+};\n+\n+static void test_conflict(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"a/b\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"destination\", /* make sure it's not a symref.\n+\t\t\t\t\t\t*/\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct testcase cases[] = {\n+\t\t{ \"a/b/c\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"b\", NULL, 0 },\n+\t\t{ \"a\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"a\", \"a/b\", 0 },\n+\n+\t\t{ \"p/\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p//q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/./q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/../q\", NULL, REFTABLE_REFNAME_ERROR },\n+\n+\t\t{ \"a/b/c\", \"a/b\", 0 },\n+\t\t{ NULL, \"a//b\", 0 },\n+\t};\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\treftable_table_from_reader(&tab, rd);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct modification mod = {\n+\t\t\t.tab = tab,\n+\t\t};\n+\n+\t\tif (cases[i].add) {\n+\t\t\tmod.add = &cases[i].add;\n+\t\t\tmod.add_len = 1;\n+\t\t}\n+\t\tif (cases[i].del) {\n+\t\t\tmod.del = &cases[i].del;\n+\t\t\tmod.del_len = 1;\n+\t\t}\n+\n+\t\terr = modification_validate(&mod);\n+\t\tEXPECT(err == cases[i].error_code);\n+\t}\n+\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+int refname_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_conflict);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 8087f2da4e6..c8db6852c35 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -8,6 +8,7 @@ int cmd__reftable(int argc, const char **argv)\n \tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"437323","messageId":"3db887bc0784a781b435d7a77fb2ee1a9a037ee0.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 17/19] reftable: implement stack, a mutable database of reftable files.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:14Z","receivedAt":"2021-09-28T15:11:17Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |    1 +\n reftable/reftable-stack.h |  128 ++++\n reftable/stack.c          | 1396 +++++++++++++++++++++++++++++++++++++\n reftable/stack.h          |   41 ++\n reftable/stack_test.c     |  949 +++++++++++++++++++++++++\n t/helper/test-reftable.c  |    1 +\n 6 files changed, 2516 insertions(+)\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 226012f5426..53c13c90a5e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2457,6 +2457,7 @@ REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/refname_test.o\n+REFTABLE_TEST_OBJS += reftable/stack_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h\nnew file mode 100644\nindex 00000000000..1b602dda58a\n--- /dev/null\n+++ b/reftable/reftable-stack.h\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_STACK_H\n+#define REFTABLE_STACK_H\n+\n+#include \"reftable-writer.h\"\n+\n+/*\n+ * The stack presents an interface to a mutable sequence of reftables.\n+\n+ * A stack can be mutated by pushing a table to the top of the stack.\n+\n+ * The reftable_stack automatically compacts files on disk to ensure good\n+ * amortized performance.\n+ *\n+ * For windows and other platforms that cannot have open files as rename\n+ * destinations, concurrent access from multiple processes needs the rand()\n+ * random seed to be randomized.\n+ */\n+struct reftable_stack;\n+\n+/* open a new reftable stack. The tables along with the table list will be\n+ *  stored in 'dir'. Typically, this should be .git/reftables.\n+ */\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config);\n+\n+/* returns the update_index at which a next table should be written. */\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);\n+\n+/* holds a transaction to add tables at the top of a stack. */\n+struct reftable_addition;\n+\n+/*\n+ * returns a new transaction to add reftables to the given stack. As a side\n+ * effect, the ref database is locked.\n+ */\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st);\n+\n+/* Adds a reftable to transaction. */\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg);\n+\n+/* Commits the transaction, releasing the lock. After calling this,\n+ * reftable_addition_destroy should still be called.\n+ */\n+int reftable_addition_commit(struct reftable_addition *add);\n+\n+/* Release all non-committed data from the transaction, and deallocate the\n+ * transaction. Releases the lock if held. */\n+void reftable_addition_destroy(struct reftable_addition *add);\n+\n+/* add a new table to the stack. The write_table function must call\n+ * reftable_writer_set_limits, add refs and return an error value. */\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t  void *write_arg),\n+\t\t       void *write_arg);\n+\n+/* returns the merged_table for seeking. This table is valid until the\n+ * next write or reload, and should not be closed or deleted.\n+ */\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st);\n+\n+/* frees all resources associated with the stack. */\n+void reftable_stack_destroy(struct reftable_stack *st);\n+\n+/* Reloads the stack if necessary. This is very cheap to run if the stack was up\n+ * to date */\n+int reftable_stack_reload(struct reftable_stack *st);\n+\n+/* Policy for expiring reflog entries. */\n+struct reftable_log_expiry_config {\n+\t/* Drop entries older than this timestamp */\n+\tuint64_t time;\n+\n+\t/* Drop older entries */\n+\tuint64_t min_update_index;\n+};\n+\n+/* compacts all reftables into a giant table. Expire reflog entries if config is\n+ * non-NULL */\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+\n+/* heuristically compact unbalanced table stack. */\n+int reftable_stack_auto_compact(struct reftable_stack *st);\n+\n+/* delete stale .ref tables. */\n+int reftable_stack_clean(struct reftable_stack *st);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* convenience function to read a single log. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/* statistics on past compactions. */\n+struct reftable_compaction_stats {\n+\tuint64_t bytes; /* total number of bytes written */\n+\tuint64_t entries_written; /* total number of entries written, including\n+\t\t\t\t     failures. */\n+\tint attempts; /* how often we tried to compact */\n+\tint failures; /* failures happen on concurrent updates */\n+};\n+\n+/* return statistics for compaction up till now. */\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st);\n+\n+/* print the entire stack represented by the directory */\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);\n+\n+#endif\ndiff --git a/reftable/stack.c b/reftable/stack.c\nnew file mode 100644\nindex 00000000000..df5021ebf08\n--- /dev/null\n+++ b/reftable/stack.c\n@@ -0,0 +1,1396 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+#include \"merged.h\"\n+#include \"reader.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-merged.h\"\n+#include \"writer.h\"\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg);\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name);\n+static void reftable_addition_close(struct reftable_addition *add);\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open);\n+\n+static void stack_filename(struct strbuf *dest, struct reftable_stack *st,\n+\t\t\t   const char *name)\n+{\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, st->reftable_dir);\n+\tstrbuf_addstr(dest, \"/\");\n+\tstrbuf_addstr(dest, name);\n+}\n+\n+static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)\n+{\n+\tint *fdp = (int *)arg;\n+\treturn write(*fdp, data, sz);\n+}\n+\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config)\n+{\n+\tstruct reftable_stack *p =\n+\t\treftable_calloc(sizeof(struct reftable_stack));\n+\tstruct strbuf list_file_name = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (config.hash_id == 0) {\n+\t\tconfig.hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\n+\t*dest = NULL;\n+\n+\tstrbuf_reset(&list_file_name);\n+\tstrbuf_addstr(&list_file_name, dir);\n+\tstrbuf_addstr(&list_file_name, \"/tables.list\");\n+\n+\tp->list_file = strbuf_detach(&list_file_name, NULL);\n+\tp->reftable_dir = xstrdup(dir);\n+\tp->config = config;\n+\n+\terr = reftable_stack_reload_maybe_reuse(p, 1);\n+\tif (err < 0) {\n+\t\treftable_stack_destroy(p);\n+\t} else {\n+\t\t*dest = p;\n+\t}\n+\treturn err;\n+}\n+\n+static int fd_read_lines(int fd, char ***namesp)\n+{\n+\toff_t size = lseek(fd, 0, SEEK_END);\n+\tchar *buf = NULL;\n+\tint err = 0;\n+\tif (size < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\terr = lseek(fd, 0, SEEK_SET);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tbuf = reftable_malloc(size + 1);\n+\tif (read(fd, buf, size) != size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\tbuf[size] = 0;\n+\n+\tparse_names(buf, size, namesp);\n+\n+done:\n+\treftable_free(buf);\n+\treturn err;\n+}\n+\n+int read_lines(const char *filename, char ***namesp)\n+{\n+\tint fd = open(filename, O_RDONLY);\n+\tint err = 0;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\t*namesp = reftable_calloc(sizeof(char *));\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\terr = fd_read_lines(fd, namesp);\n+\tclose(fd);\n+\treturn err;\n+}\n+\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st)\n+{\n+\treturn st->merged;\n+}\n+\n+static int has_name(char **names, const char *name)\n+{\n+\twhile (*names) {\n+\t\tif (!strcmp(*names, name))\n+\t\t\treturn 1;\n+\t\tnames++;\n+\t}\n+\treturn 0;\n+}\n+\n+/* Close and free the stack */\n+void reftable_stack_destroy(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = 0;\n+\tif (st->merged) {\n+\t\treftable_merged_table_free(st->merged);\n+\t\tst->merged = NULL;\n+\t}\n+\n+\terr = read_lines(st->list_file, &names);\n+\tif (err < 0) {\n+\t\tFREE_AND_NULL(names);\n+\t}\n+\n+\tif (st->readers) {\n+\t\tint i = 0;\n+\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\tfor (i = 0; i < st->readers_len; i++) {\n+\t\t\tconst char *name = reader_name(st->readers[i]);\n+\t\t\tstrbuf_reset(&filename);\n+\t\t\tif (names && !has_name(names, name)) {\n+\t\t\t\tstack_filename(&filename, st, name);\n+\t\t\t}\n+\t\t\treftable_reader_free(st->readers[i]);\n+\n+\t\t\tif (filename.len) {\n+\t\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\t\tunlink(filename.buf);\n+\t\t\t}\n+\t\t}\n+\t\tstrbuf_release(&filename);\n+\t\tst->readers_len = 0;\n+\t\tFREE_AND_NULL(st->readers);\n+\t}\n+\tFREE_AND_NULL(st->list_file);\n+\tFREE_AND_NULL(st->reftable_dir);\n+\treftable_free(st);\n+\tfree_names(names);\n+}\n+\n+static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n+\t\t\t\t\t\t   int cur_len)\n+{\n+\tstruct reftable_reader **cur =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * cur_len);\n+\tint i = 0;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tcur[i] = st->readers[i];\n+\t}\n+\treturn cur;\n+}\n+\n+static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+\t\t\t\t      int reuse_open)\n+{\n+\tint cur_len = !st->merged ? 0 : st->merged->stack_len;\n+\tstruct reftable_reader **cur = stack_copy_readers(st, cur_len);\n+\tint err = 0;\n+\tint names_len = names_length(names);\n+\tstruct reftable_reader **new_readers =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * names_len);\n+\tstruct reftable_table *new_tables =\n+\t\treftable_calloc(sizeof(struct reftable_table) * names_len);\n+\tint new_readers_len = 0;\n+\tstruct reftable_merged_table *new_merged = NULL;\n+\tint i;\n+\n+\twhile (*names) {\n+\t\tstruct reftable_reader *rd = NULL;\n+\t\tchar *name = *names++;\n+\n+\t\t/* this is linear; we assume compaction keeps the number of\n+\t\t   tables under control so this is not quadratic. */\n+\t\tint j = 0;\n+\t\tfor (j = 0; reuse_open && j < cur_len; j++) {\n+\t\t\tif (cur[j] && 0 == strcmp(cur[j]->name, name)) {\n+\t\t\t\trd = cur[j];\n+\t\t\t\tcur[j] = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!rd) {\n+\t\t\tstruct reftable_block_source src = { NULL };\n+\t\t\tstruct strbuf table_path = STRBUF_INIT;\n+\t\t\tstack_filename(&table_path, st, name);\n+\n+\t\t\terr = reftable_block_source_from_file(&src,\n+\t\t\t\t\t\t\t      table_path.buf);\n+\t\t\tstrbuf_release(&table_path);\n+\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\n+\t\t\terr = reftable_new_reader(&rd, &src, name);\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\n+\t\tnew_readers[new_readers_len] = rd;\n+\t\treftable_table_from_reader(&new_tables[new_readers_len], rd);\n+\t\tnew_readers_len++;\n+\t}\n+\n+\t/* success! */\n+\terr = reftable_new_merged_table(&new_merged, new_tables,\n+\t\t\t\t\tnew_readers_len, st->config.hash_id);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tnew_tables = NULL;\n+\tst->readers_len = new_readers_len;\n+\tif (st->merged) {\n+\t\tmerged_table_release(st->merged);\n+\t\treftable_merged_table_free(st->merged);\n+\t}\n+\tif (st->readers) {\n+\t\treftable_free(st->readers);\n+\t}\n+\tst->readers = new_readers;\n+\tnew_readers = NULL;\n+\tnew_readers_len = 0;\n+\n+\tnew_merged->suppress_deletions = 1;\n+\tst->merged = new_merged;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tif (cur[i]) {\n+\t\t\tconst char *name = reader_name(cur[i]);\n+\t\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\t\tstack_filename(&filename, st, name);\n+\n+\t\t\treader_close(cur[i]);\n+\t\t\treftable_reader_free(cur[i]);\n+\n+\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\tunlink(filename.buf);\n+\n+\t\t\tstrbuf_release(&filename);\n+\t\t}\n+\t}\n+\n+done:\n+\tfor (i = 0; i < new_readers_len; i++) {\n+\t\treader_close(new_readers[i]);\n+\t\treftable_reader_free(new_readers[i]);\n+\t}\n+\treftable_free(new_readers);\n+\treftable_free(new_tables);\n+\treftable_free(cur);\n+\treturn err;\n+}\n+\n+/* return negative if a before b. */\n+static int tv_cmp(struct timeval *a, struct timeval *b)\n+{\n+\ttime_t diff = a->tv_sec - b->tv_sec;\n+\tint udiff = a->tv_usec - b->tv_usec;\n+\n+\tif (diff != 0)\n+\t\treturn diff;\n+\n+\treturn udiff;\n+}\n+\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open)\n+{\n+\tstruct timeval deadline = { 0 };\n+\tint err = gettimeofday(&deadline, NULL);\n+\tint64_t delay = 0;\n+\tint tries = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tdeadline.tv_sec += 3;\n+\twhile (1) {\n+\t\tchar **names = NULL;\n+\t\tchar **names_after = NULL;\n+\t\tstruct timeval now = { 0 };\n+\t\tint err = gettimeofday(&now, NULL);\n+\t\tint err2 = 0;\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* Only look at deadlines after the first few times. This\n+\t\t   simplifies debugging in GDB */\n+\t\ttries++;\n+\t\tif (tries > 3 && tv_cmp(&now, &deadline) >= 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = read_lines(st->list_file, &names);\n+\t\tif (err < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\tif (err == 0) {\n+\t\t\tfree_names(names);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err != REFTABLE_NOT_EXIST_ERROR) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent\n+\t\t   writer. Check if there was one by checking if the name list\n+\t\t   changed.\n+\t\t*/\n+\t\terr2 = read_lines(st->list_file, &names_after);\n+\t\tif (err2 < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err2;\n+\t\t}\n+\n+\t\tif (names_equal(names_after, names)) {\n+\t\t\tfree_names(names);\n+\t\t\tfree_names(names_after);\n+\t\t\treturn err;\n+\t\t}\n+\t\tfree_names(names);\n+\t\tfree_names(names_after);\n+\n+\t\tdelay = delay + (delay * rand()) / RAND_MAX + 1;\n+\t\tsleep_millisec(delay);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* -1 = error\n+ 0 = up to date\n+ 1 = changed. */\n+static int stack_uptodate(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = read_lines(st->list_file, &names);\n+\tint i = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tfor (i = 0; i < st->readers_len; i++) {\n+\t\tif (!names[i]) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (strcmp(st->readers[i]->name, names[i])) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tif (names[st->merged->stack_len]) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tfree_names(names);\n+\treturn err;\n+}\n+\n+int reftable_stack_reload(struct reftable_stack *st)\n+{\n+\tint err = stack_uptodate(st);\n+\tif (err > 0)\n+\t\treturn reftable_stack_reload_maybe_reuse(st, 1);\n+\treturn err;\n+}\n+\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write)(struct reftable_writer *wr, void *arg),\n+\t\t       void *arg)\n+{\n+\tint err = stack_try_add(st, write, arg);\n+\tif (err < 0) {\n+\t\tif (err == REFTABLE_LOCK_ERROR) {\n+\t\t\t/* Ignore error return, we want to propagate\n+\t\t\t   REFTABLE_LOCK_ERROR.\n+\t\t\t*/\n+\t\t\treftable_stack_reload(st);\n+\t\t}\n+\t\treturn err;\n+\t}\n+\n+\tif (!st->disable_auto_compact)\n+\t\treturn reftable_stack_auto_compact(st);\n+\n+\treturn 0;\n+}\n+\n+static void format_name(struct strbuf *dest, uint64_t min, uint64_t max)\n+{\n+\tchar buf[100];\n+\tuint32_t rnd = (uint32_t)rand();\n+\tsnprintf(buf, sizeof(buf), \"0x%012\" PRIx64 \"-0x%012\" PRIx64 \"-%08x\",\n+\t\t min, max, rnd);\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, buf);\n+}\n+\n+struct reftable_addition {\n+\tint lock_file_fd;\n+\tstruct strbuf lock_file_name;\n+\tstruct reftable_stack *stack;\n+\n+\tchar **new_tables;\n+\tint new_tables_len;\n+\tuint64_t next_update_index;\n+};\n+\n+#define REFTABLE_ADDITION_INIT                \\\n+\t{                                     \\\n+\t\t.lock_file_name = STRBUF_INIT \\\n+\t}\n+\n+static int reftable_stack_init_addition(struct reftable_addition *add,\n+\t\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tadd->stack = st;\n+\n+\tstrbuf_reset(&add->lock_file_name);\n+\tstrbuf_addstr(&add->lock_file_name, st->list_file);\n+\tstrbuf_addstr(&add->lock_file_name, \".lock\");\n+\n+\tadd->lock_file_fd = open(add->lock_file_name.buf,\n+\t\t\t\t O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (add->lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = REFTABLE_LOCK_ERROR;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\terr = stack_uptodate(st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 1) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->next_update_index = reftable_stack_next_update_index(st);\n+done:\n+\tif (err) {\n+\t\treftable_addition_close(add);\n+\t}\n+\treturn err;\n+}\n+\n+static void reftable_addition_close(struct reftable_addition *add)\n+{\n+\tint i = 0;\n+\tstruct strbuf nm = STRBUF_INIT;\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstack_filename(&nm, add->stack, add->new_tables[i]);\n+\t\tunlink(nm.buf);\n+\t\treftable_free(add->new_tables[i]);\n+\t\tadd->new_tables[i] = NULL;\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\tif (add->lock_file_fd > 0) {\n+\t\tclose(add->lock_file_fd);\n+\t\tadd->lock_file_fd = 0;\n+\t}\n+\tif (add->lock_file_name.len > 0) {\n+\t\tunlink(add->lock_file_name.buf);\n+\t\tstrbuf_release(&add->lock_file_name);\n+\t}\n+\n+\tstrbuf_release(&nm);\n+}\n+\n+void reftable_addition_destroy(struct reftable_addition *add)\n+{\n+\tif (!add) {\n+\t\treturn;\n+\t}\n+\treftable_addition_close(add);\n+\treftable_free(add);\n+}\n+\n+int reftable_addition_commit(struct reftable_addition *add)\n+{\n+\tstruct strbuf table_list = STRBUF_INIT;\n+\tint i = 0;\n+\tint err = 0;\n+\tif (add->new_tables_len == 0)\n+\t\tgoto done;\n+\n+\tfor (i = 0; i < add->stack->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->stack->readers[i]->name);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->new_tables[i]);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\n+\terr = write(add->lock_file_fd, table_list.buf, table_list.len);\n+\tstrbuf_release(&table_list);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = close(add->lock_file_fd);\n+\tadd->lock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(add->lock_file_name.buf, add->stack->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\t/* success, no more state to clean up. */\n+\tstrbuf_release(&add->lock_file_name);\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\treftable_free(add->new_tables[i]);\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\terr = reftable_stack_reload(add->stack);\n+done:\n+\treftable_addition_close(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tstruct reftable_addition empty = REFTABLE_ADDITION_INIT;\n+\t*dest = reftable_calloc(sizeof(**dest));\n+\t**dest = empty;\n+\terr = reftable_stack_init_addition(*dest, st);\n+\tif (err) {\n+\t\treftable_free(*dest);\n+\t\t*dest = NULL;\n+\t}\n+\treturn err;\n+}\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg)\n+{\n+\tstruct reftable_addition add = REFTABLE_ADDITION_INIT;\n+\tint err = reftable_stack_init_addition(&add, st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tif (err > 0) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_addition_add(&add, write_table, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_addition_commit(&add);\n+done:\n+\treftable_addition_close(&add);\n+\treturn err;\n+}\n+\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf tab_file_name = STRBUF_INIT;\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\tint tab_fd = 0;\n+\n+\tstrbuf_reset(&next_name);\n+\tformat_name(&next_name, add->next_update_index, add->next_update_index);\n+\n+\tstack_filename(&temp_tab_file_name, add->stack, next_name.buf);\n+\tstrbuf_addstr(&temp_tab_file_name, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab_file_name.buf);\n+\tif (tab_fd < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd,\n+\t\t\t\t &add->stack->config);\n+\terr = write_table(wr, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_writer_close(wr);\n+\tif (err == REFTABLE_EMPTY_TABLE_ERROR) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = stack_check_addition(add->stack, temp_tab_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (wr->min_update_index < add->next_update_index) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tformat_name(&next_name, wr->min_update_index, wr->max_update_index);\n+\tstrbuf_addstr(&next_name, \".ref\");\n+\n+\tstack_filename(&tab_file_name, add->stack, next_name.buf);\n+\n+\t/*\n+\t  On windows, this relies on rand() picking a unique destination name.\n+\t  Maybe we should do retry loop as well?\n+\t */\n+\terr = rename(temp_tab_file_name.buf, tab_file_name.buf);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->new_tables = reftable_realloc(add->new_tables,\n+\t\t\t\t\t   sizeof(*add->new_tables) *\n+\t\t\t\t\t\t   (add->new_tables_len + 1));\n+\tadd->new_tables[add->new_tables_len] = strbuf_detach(&next_name, NULL);\n+\tadd->new_tables_len++;\n+done:\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (temp_tab_file_name.len > 0) {\n+\t\tunlink(temp_tab_file_name.buf);\n+\t}\n+\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&tab_file_name);\n+\tstrbuf_release(&next_name);\n+\treftable_writer_free(wr);\n+\treturn err;\n+}\n+\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st)\n+{\n+\tint sz = st->merged->stack_len;\n+\tif (sz > 0)\n+\t\treturn reftable_reader_max_update_index(st->readers[sz - 1]) +\n+\t\t       1;\n+\treturn 1;\n+}\n+\n+static int stack_compact_locked(struct reftable_stack *st, int first, int last,\n+\t\t\t\tstruct strbuf *temp_tab,\n+\t\t\t\tstruct reftable_log_expiry_config *config)\n+{\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tint tab_fd = -1;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\n+\tformat_name(&next_name,\n+\t\t    reftable_reader_min_update_index(st->readers[first]),\n+\t\t    reftable_reader_max_update_index(st->readers[last]));\n+\n+\tstack_filename(temp_tab, st, next_name.buf);\n+\tstrbuf_addstr(temp_tab, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab->buf);\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);\n+\n+\terr = stack_write_compact(st, wr, first, last, config);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = reftable_writer_close(wr);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\n+done:\n+\treftable_writer_free(wr);\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (err != 0 && temp_tab->len > 0) {\n+\t\tunlink(temp_tab->buf);\n+\t\tstrbuf_release(temp_tab);\n+\t}\n+\tstrbuf_release(&next_name);\n+\treturn err;\n+}\n+\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\tint subtabs_len = last - first + 1;\n+\tstruct reftable_table *subtabs = reftable_calloc(\n+\t\tsizeof(struct reftable_table) * (last - first + 1));\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err = 0;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\n+\tuint64_t entries = 0;\n+\n+\tint i = 0, j = 0;\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct reftable_reader *t = st->readers[i];\n+\t\treftable_table_from_reader(&subtabs[j++], t);\n+\t\tst->stats.bytes += t->size;\n+\t}\n+\treftable_writer_set_limits(wr, st->readers[first]->min_update_index,\n+\t\t\t\t   st->readers[last]->max_update_index);\n+\n+\terr = reftable_new_merged_table(&mt, subtabs, subtabs_len,\n+\t\t\t\t\tst->config.hash_id);\n+\tif (err < 0) {\n+\t\treftable_free(subtabs);\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_merged_table_seek_ref(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (first == 0 && reftable_ref_record_is_deletion(&ref)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_ref(wr, &ref);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_merged_table_seek_log(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (first == 0 && reftable_log_record_is_deletion(&log)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->min_update_index > 0 &&\n+\t\t    log.update_index < config->min_update_index) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->time > 0 &&\n+\t\t    log.value.update.time < config->time) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_log(wr, &log);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\tif (mt) {\n+\t\tmerged_table_release(mt);\n+\t\treftable_merged_table_free(mt);\n+\t}\n+\treftable_ref_record_release(&ref);\n+\treftable_log_record_release(&log);\n+\tst->stats.entries_written += entries;\n+\treturn err;\n+}\n+\n+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */\n+static int stack_compact_range(struct reftable_stack *st, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *expiry)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf new_table_name = STRBUF_INIT;\n+\tstruct strbuf lock_file_name = STRBUF_INIT;\n+\tstruct strbuf ref_list_contents = STRBUF_INIT;\n+\tstruct strbuf new_table_path = STRBUF_INIT;\n+\tint err = 0;\n+\tint have_lock = 0;\n+\tint lock_file_fd = 0;\n+\tint compact_count = last - first + 1;\n+\tchar **listp = NULL;\n+\tchar **delete_on_success =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tchar **subtable_locks =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tint i = 0;\n+\tint j = 0;\n+\tint is_empty_table = 0;\n+\n+\tif (first > last || (!expiry && first == last)) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\tst->stats.attempts++;\n+\n+\tstrbuf_reset(&lock_file_name);\n+\tstrbuf_addstr(&lock_file_name, st->list_file);\n+\tstrbuf_addstr(&lock_file_name, \".lock\");\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\t/* Don't want to write to the lock for now.  */\n+\tclose(lock_file_fd);\n+\tlock_file_fd = 0;\n+\n+\thave_lock = 1;\n+\terr = stack_uptodate(st);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct strbuf subtab_file_name = STRBUF_INIT;\n+\t\tstruct strbuf subtab_lock = STRBUF_INIT;\n+\t\tint sublock_file_fd = -1;\n+\n+\t\tstack_filename(&subtab_file_name, st,\n+\t\t\t       reader_name(st->readers[i]));\n+\n+\t\tstrbuf_reset(&subtab_lock);\n+\t\tstrbuf_addbuf(&subtab_lock, &subtab_file_name);\n+\t\tstrbuf_addstr(&subtab_lock, \".lock\");\n+\n+\t\tsublock_file_fd = open(subtab_lock.buf,\n+\t\t\t\t       O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\t\tif (sublock_file_fd > 0) {\n+\t\t\tclose(sublock_file_fd);\n+\t\t} else if (sublock_file_fd < 0) {\n+\t\t\tif (errno == EEXIST) {\n+\t\t\t\terr = 1;\n+\t\t\t} else {\n+\t\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\t}\n+\t\t}\n+\n+\t\tsubtable_locks[j] = subtab_lock.buf;\n+\t\tdelete_on_success[j] = subtab_file_name.buf;\n+\t\tj++;\n+\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\terr = unlink(lock_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\thave_lock = 0;\n+\n+\terr = stack_compact_locked(st, first, last, &temp_tab_file_name,\n+\t\t\t\t   expiry);\n+\t/* Compaction + tombstones can create an empty table out of non-empty\n+\t * tables. */\n+\tis_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);\n+\tif (is_empty_table) {\n+\t\terr = 0;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\thave_lock = 1;\n+\n+\tformat_name(&new_table_name, st->readers[first]->min_update_index,\n+\t\t    st->readers[last]->max_update_index);\n+\tstrbuf_addstr(&new_table_name, \".ref\");\n+\n+\tstack_filename(&new_table_path, st, new_table_name.buf);\n+\n+\tif (!is_empty_table) {\n+\t\t/* retry? */\n+\t\terr = rename(temp_tab_file_name.buf, new_table_path.buf);\n+\t\tif (err < 0) {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < first; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tif (!is_empty_table) {\n+\t\tstrbuf_addbuf(&ref_list_contents, &new_table_name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tfor (i = last + 1; i < st->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\n+\terr = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\terr = close(lock_file_fd);\n+\tlock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(lock_file_name.buf, st->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\thave_lock = 0;\n+\n+\t/* Reload the stack before deleting. On windows, we can only delete the\n+\t   files after we closed them.\n+\t*/\n+\terr = reftable_stack_reload_maybe_reuse(st, first < last);\n+\n+\tlistp = delete_on_success;\n+\twhile (*listp) {\n+\t\tif (strcmp(*listp, new_table_path.buf)) {\n+\t\t\tunlink(*listp);\n+\t\t}\n+\t\tlistp++;\n+\t}\n+\n+done:\n+\tfree_names(delete_on_success);\n+\n+\tlistp = subtable_locks;\n+\twhile (*listp) {\n+\t\tunlink(*listp);\n+\t\tlistp++;\n+\t}\n+\tfree_names(subtable_locks);\n+\tif (lock_file_fd > 0) {\n+\t\tclose(lock_file_fd);\n+\t\tlock_file_fd = 0;\n+\t}\n+\tif (have_lock) {\n+\t\tunlink(lock_file_name.buf);\n+\t}\n+\tstrbuf_release(&new_table_name);\n+\tstrbuf_release(&new_table_path);\n+\tstrbuf_release(&ref_list_contents);\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&lock_file_name);\n+\treturn err;\n+}\n+\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\treturn stack_compact_range(st, 0, st->merged->stack_len - 1, config);\n+}\n+\n+static int stack_compact_range_stats(struct reftable_stack *st, int first,\n+\t\t\t\t     int last,\n+\t\t\t\t     struct reftable_log_expiry_config *config)\n+{\n+\tint err = stack_compact_range(st, first, last, config);\n+\tif (err > 0) {\n+\t\tst->stats.failures++;\n+\t}\n+\treturn err;\n+}\n+\n+static int segment_size(struct segment *s)\n+{\n+\treturn s->end - s->start;\n+}\n+\n+int fastlog2(uint64_t sz)\n+{\n+\tint l = 0;\n+\tif (sz == 0)\n+\t\treturn 0;\n+\tfor (; sz; sz /= 2) {\n+\t\tl++;\n+\t}\n+\treturn l - 1;\n+}\n+\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)\n+{\n+\tstruct segment *segs = reftable_calloc(sizeof(struct segment) * n);\n+\tint next = 0;\n+\tstruct segment cur = { 0 };\n+\tint i = 0;\n+\n+\tif (n == 0) {\n+\t\t*seglen = 0;\n+\t\treturn segs;\n+\t}\n+\tfor (i = 0; i < n; i++) {\n+\t\tint log = fastlog2(sizes[i]);\n+\t\tif (cur.log != log && cur.bytes > 0) {\n+\t\t\tstruct segment fresh = {\n+\t\t\t\t.start = i,\n+\t\t\t};\n+\n+\t\t\tsegs[next++] = cur;\n+\t\t\tcur = fresh;\n+\t\t}\n+\n+\t\tcur.log = log;\n+\t\tcur.end = i + 1;\n+\t\tcur.bytes += sizes[i];\n+\t}\n+\tsegs[next++] = cur;\n+\t*seglen = next;\n+\treturn segs;\n+}\n+\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, sizes, n);\n+\tstruct segment min_seg = {\n+\t\t.log = 64,\n+\t};\n+\tint i = 0;\n+\tfor (i = 0; i < seglen; i++) {\n+\t\tif (segment_size(&segs[i]) == 1) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (segs[i].log < min_seg.log) {\n+\t\t\tmin_seg = segs[i];\n+\t\t}\n+\t}\n+\n+\twhile (min_seg.start > 0) {\n+\t\tint prev = min_seg.start - 1;\n+\t\tif (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmin_seg.start = prev;\n+\t\tmin_seg.bytes += sizes[prev];\n+\t}\n+\n+\treftable_free(segs);\n+\treturn min_seg;\n+}\n+\n+static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes =\n+\t\treftable_calloc(sizeof(uint64_t) * st->merged->stack_len);\n+\tint version = (st->config.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2;\n+\tint overhead = header_size(version) - 1;\n+\tint i = 0;\n+\tfor (i = 0; i < st->merged->stack_len; i++) {\n+\t\tsizes[i] = st->readers[i]->size - overhead;\n+\t}\n+\treturn sizes;\n+}\n+\n+int reftable_stack_auto_compact(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes = stack_table_sizes_for_compaction(st);\n+\tstruct segment seg =\n+\t\tsuggest_compaction_segment(sizes, st->merged->stack_len);\n+\treftable_free(sizes);\n+\tif (segment_size(&seg) > 0)\n+\t\treturn stack_compact_range_stats(st, seg.start, seg.end - 1,\n+\t\t\t\t\t\t NULL);\n+\n+\treturn 0;\n+}\n+\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st)\n+{\n+\treturn &st->stats;\n+}\n+\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_table tab = { NULL };\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\treturn reftable_table_read_ref(&tab, refname, ref);\n+}\n+\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_merged_table *mt = reftable_stack_merged_table(st);\n+\tint err = reftable_merged_table_seek_log(mt, &it, refname);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_log(&it, log);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(log->refname, refname) ||\n+\t    reftable_log_record_is_deletion(log)) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tif (err) {\n+\t\treftable_log_record_release(log);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name)\n+{\n+\tint err = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct reftable_ref_record *refs = NULL;\n+\tstruct reftable_iterator it = { NULL };\n+\tint cap = 0;\n+\tint len = 0;\n+\tint i = 0;\n+\n+\tif (st->config.skip_name_check)\n+\t\treturn 0;\n+\n+\terr = reftable_block_source_from_file(&src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tif (err > 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (len >= cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\trefs = reftable_realloc(refs, cap * sizeof(refs[0]));\n+\t\t}\n+\n+\t\trefs[len++] = ref;\n+\t}\n+\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\n+\terr = validate_ref_record_addition(tab, refs, len);\n+\n+done:\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t}\n+\n+\tfree(refs);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\treturn err;\n+}\n+\n+static int is_table_name(const char *s)\n+{\n+\tconst char *dot = strrchr(s, '.');\n+\treturn dot && !strcmp(dot, \".ref\");\n+}\n+\n+static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max,\n+\t\t\t\t     const char *name)\n+{\n+\tint err = 0;\n+\tuint64_t update_idx = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct strbuf table_path = STRBUF_INIT;\n+\tstack_filename(&table_path, st, name);\n+\n+\terr = reftable_block_source_from_file(&src, table_path.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tupdate_idx = reftable_reader_max_update_index(rd);\n+\treftable_reader_free(rd);\n+\n+\tif (update_idx <= max) {\n+\t\tunlink(table_path.buf);\n+\t}\n+done:\n+\tstrbuf_release(&table_path);\n+}\n+\n+static int reftable_stack_clean_locked(struct reftable_stack *st)\n+{\n+\tuint64_t max = reftable_merged_table_max_update_index(\n+\t\treftable_stack_merged_table(st));\n+\tDIR *dir = opendir(st->reftable_dir);\n+\tstruct dirent *d = NULL;\n+\tif (!dir) {\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tint i = 0;\n+\t\tint found = 0;\n+\t\tif (!is_table_name(d->d_name))\n+\t\t\tcontinue;\n+\n+\t\tfor (i = 0; !found && i < st->readers_len; i++) {\n+\t\t\tfound = !strcmp(reader_name(st->readers[i]), d->d_name);\n+\t\t}\n+\t\tif (found)\n+\t\t\tcontinue;\n+\n+\t\tremove_maybe_stale_table(st, max, d->d_name);\n+\t}\n+\n+\tclosedir(dir);\n+\treturn 0;\n+}\n+\n+int reftable_stack_clean(struct reftable_stack *st)\n+{\n+\tstruct reftable_addition *add = NULL;\n+\tint err = reftable_stack_new_addition(&add, st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_reload(st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_clean_locked(st);\n+\n+done:\n+\treftable_addition_destroy(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { .hash_id = hash_id };\n+\tstruct reftable_merged_table *merged = NULL;\n+\tstruct reftable_table table = { NULL };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tmerged = reftable_stack_merged_table(stack);\n+\treftable_table_from_merged_table(&table, merged);\n+\terr = reftable_table_print(&table);\n+done:\n+\tif (stack)\n+\t\treftable_stack_destroy(stack);\n+\treturn err;\n+}\ndiff --git a/reftable/stack.h b/reftable/stack.h\nnew file mode 100644\nindex 00000000000..f57005846e5\n--- /dev/null\n+++ b/reftable/stack.h\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef STACK_H\n+#define STACK_H\n+\n+#include \"system.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-stack.h\"\n+\n+struct reftable_stack {\n+\tchar *list_file;\n+\tchar *reftable_dir;\n+\tint disable_auto_compact;\n+\n+\tstruct reftable_write_options config;\n+\n+\tstruct reftable_reader **readers;\n+\tsize_t readers_len;\n+\tstruct reftable_merged_table *merged;\n+\tstruct reftable_compaction_stats stats;\n+};\n+\n+int read_lines(const char *filename, char ***lines);\n+\n+struct segment {\n+\tint start, end;\n+\tint log;\n+\tuint64_t bytes;\n+};\n+\n+int fastlog2(uint64_t sz);\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n);\n+\n+#endif\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nnew file mode 100644\nindex 00000000000..7a4641ab601\n--- /dev/null\n+++ b/reftable/stack_test.c\n@@ -0,0 +1,949 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+\n+#include \"reftable-reader.h\"\n+#include \"merged.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+#include <sys/types.h>\n+#include <dirent.h>\n+\n+static void clear_dir(const char *dirname)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, dirname);\n+\tremove_dir_recursively(&path, 0);\n+\tstrbuf_release(&path);\n+}\n+\n+static int count_dir_entries(const char *dirname)\n+{\n+\tDIR *dir = opendir(dirname);\n+\tint len = 0;\n+\tstruct dirent *d;\n+\tif (dir == NULL)\n+\t\treturn 0;\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tif (!strcmp(d->d_name, \"..\") || !strcmp(d->d_name, \".\"))\n+\t\t\tcontinue;\n+\t\tlen++;\n+\t}\n+\tclosedir(dir);\n+\treturn len;\n+}\n+\n+// Work linenumber into the tempdir, so we can see which tests forget to\n+// cleanup.\n+static char *get_tmp_template(int linenumber)\n+{\n+\tconst char *tmp = getenv(\"TMPDIR\");\n+\tstatic char template[1024];\n+\tsnprintf(template, sizeof(template) - 1, \"%s/stack_test-%d.XXXXXX\",\n+\t\t tmp ? tmp : \"/tmp\", linenumber);\n+\treturn template;\n+}\n+\n+static char *get_tmp_dir(int linenumber)\n+{\n+\tchar *dir = get_tmp_template(linenumber);\n+\tEXPECT(mkdtemp(dir));\n+\treturn dir;\n+}\n+\n+static void test_read_file(void)\n+{\n+\tchar *fn = get_tmp_template(__LINE__);\n+\tint fd = mkstemp(fn);\n+\tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n+\tint n, err;\n+\tchar **names = NULL;\n+\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tint i = 0;\n+\n+\tEXPECT(fd > 0);\n+\tn = write(fd, out, strlen(out));\n+\tEXPECT(n == strlen(out));\n+\terr = close(fd);\n+\tEXPECT(err >= 0);\n+\n+\terr = read_lines(fn, &names);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; names[i]; i++) {\n+\t\tEXPECT(0 == strcmp(want[i], names[i]));\n+\t}\n+\tfree_names(names);\n+\tremove(fn);\n+}\n+\n+static void test_parse_names(void)\n+{\n+\tchar buf[] = \"line\\n\";\n+\tchar **names = NULL;\n+\tparse_names(buf, strlen(buf), &names);\n+\n+\tEXPECT(NULL != names[0]);\n+\tEXPECT(0 == strcmp(names[0], \"line\"));\n+\tEXPECT(NULL == names[1]);\n+\tfree_names(names);\n+}\n+\n+static void test_names_equal(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tchar *c[] = { \"a\", \"b\", NULL };\n+\n+\tEXPECT(names_equal(a, a));\n+\tEXPECT(!names_equal(a, b));\n+\tEXPECT(!names_equal(a, c));\n+}\n+\n+static int write_test_ref(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct reftable_ref_record *ref = arg;\n+\treftable_writer_set_limits(wr, ref->update_index, ref->update_index);\n+\treturn reftable_writer_add_ref(wr, ref);\n+}\n+\n+struct write_log_arg {\n+\tstruct reftable_log_record *log;\n+\tuint64_t update_index;\n+};\n+\n+static int write_test_log(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct write_log_arg *wla = arg;\n+\n+\treftable_writer_set_limits(wr, wla->update_index, wla->update_index);\n+\treturn reftable_writer_add_log(wr, wla->log);\n+}\n+\n+static void test_reftable_stack_add_one(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\tprintf(\"testing print functionality:\\n\");\n+\terr = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_uptodate(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL;\n+\tstruct reftable_stack *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"branch2\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\n+\t/* simulate multi-process access to the same stack\n+\t   by creating two stacks for the same directory.\n+\t */\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st1, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_LOCK_ERROR);\n+\n+\terr = reftable_stack_reload(st2);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT_ERR(err);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_transaction_api(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_addition *add = NULL;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_new_addition(&add, st);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_add(add, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_commit(add);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(REFTABLE_REF_SYMREF == dest.value_type);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_validate_refname(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint i;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"a/b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tchar *additions[] = { \"a\", \"a/b/c\" };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(additions); i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = additions[i],\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT(err == REFTABLE_NAME_CONFLICT);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static int write_error(struct reftable_writer *wr, void *arg)\n+{\n+\treturn *((int *)arg);\n+}\n+\n+static void test_reftable_stack_update_index_check(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"name1\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"name2\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_lock_failure(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err, i;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {\n+\t\terr = reftable_stack_add(st, &write_error, &i);\n+\t\tEXPECT(err == i);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_add(void)\n+{\n+\tint i = 0;\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t.exact_log_message = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tst->disable_auto_compact = 1;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tset_test_hash(refs[i].value.val1, i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = N + i + 1;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_ref_record dest = { NULL };\n+\n+\t\tint err = reftable_stack_read_ref(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_ref_record_equal(&dest, refs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_ref_record_release(&dest);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_log_record dest = { NULL };\n+\t\tint err = reftable_stack_read_log(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_log_record_equal(&dest, logs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&dest);\n+\t}\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_log_normalize(void)\n+{\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t0,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tuint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };\n+\n+\tstruct reftable_log_record input = { .refname = \"branch\",\n+\t\t\t\t\t     .update_index = 1,\n+\t\t\t\t\t     .value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t\t\t     .value = { .update = {\n+\t\t\t\t\t\t\t\t.new_hash = h1,\n+\t\t\t\t\t\t\t\t.old_hash = h2,\n+\t\t\t\t\t\t\t} } };\n+\tstruct reftable_log_record dest = {\n+\t\t.update_index = 0,\n+\t};\n+\tstruct write_log_arg arg = {\n+\t\t.log = &input,\n+\t\t.update_index = 1,\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tinput.value.update.message = \"one\\ntwo\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tinput.value.update.message = \"one\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n+\n+\tinput.value.update.message = \"two\\n\";\n+\targ.update_index = 2;\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"two\\n\"));\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\treftable_log_record_release(&dest);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_tombstone(void)\n+{\n+\tint i = 0;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\tstruct reftable_ref_record dest = { NULL };\n+\tstruct reftable_log_record log_dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\t/* even entries add the refs, odd entries delete them. */\n+\tfor (i = 0; i < N; i++) {\n+\t\tconst char *buf = \"branch\";\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\tif (i % 2 == 0) {\n+\t\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(refs[i].value.val1, i);\n+\t\t}\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\t/* update_index is part of the key. */\n+\t\tlogs[i].update_index = 42;\n+\t\tif (i % 2 == 0) {\n+\t\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\t\tlogs[i].value.update.new_hash =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t\t\tlogs[i].value.update.email =\n+\t\t\t\txstrdup(\"identity@invalid\");\n+\t\t}\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_log_record_release(&log_dest);\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\treftable_log_record_release(&log_dest);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_hash_id(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"master\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"target\",\n+\t\t.update_index = 1,\n+\t};\n+\tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n+\tstruct reftable_stack *st32 = NULL;\n+\tstruct reftable_write_options cfg_default = { 0 };\n+\tstruct reftable_stack *st_default = NULL;\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* can't read it with the wrong hash ID. */\n+\terr = reftable_new_stack(&st32, dir, cfg32);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\t/* check that we can read it back with default config too. */\n+\terr = reftable_new_stack(&st_default, dir, cfg_default);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st_default, \"master\", &dest);\n+\tEXPECT_ERR(err);\n+\n+\tEXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st_default);\n+\tclear_dir(dir);\n+}\n+\n+static void test_log2(void)\n+{\n+\tEXPECT(1 == fastlog2(3));\n+\tEXPECT(2 == fastlog2(4));\n+\tEXPECT(2 == fastlog2(5));\n+}\n+\n+static void test_sizes_to_segments(void)\n+{\n+\tuint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };\n+\t/* .................0  1  2  3  4  5 */\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(segs[2].log == 3);\n+\tEXPECT(segs[2].start == 5);\n+\tEXPECT(segs[2].end == 6);\n+\n+\tEXPECT(segs[1].log == 2);\n+\tEXPECT(segs[1].start == 2);\n+\tEXPECT(segs[1].end == 5);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_empty(void)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, NULL, 0);\n+\tEXPECT(seglen == 0);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_all_equal(void)\n+{\n+\tuint64_t sizes[] = { 5, 5 };\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(seglen == 1);\n+\tEXPECT(segs[0].start == 0);\n+\tEXPECT(segs[0].end == 2);\n+\treftable_free(segs);\n+}\n+\n+static void test_suggest_compaction_segment(void)\n+{\n+\tuint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };\n+\t/* .................0    1    2  3   4  5  6 */\n+\tstruct segment min =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(min.start == 2);\n+\tEXPECT(min.end == 7);\n+}\n+\n+static void test_suggest_compaction_segment_nothing(void)\n+{\n+\tuint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };\n+\tstruct segment result =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(result.start == result.end);\n+}\n+\n+static void test_reflog_expire(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_log_record logs[20] = { { NULL } };\n+\tint N = ARRAY_SIZE(logs) - 1;\n+\tint i = 0;\n+\tint err;\n+\tstruct reftable_log_expiry_config expiry = {\n+\t\t.time = 10,\n+\t};\n+\tstruct reftable_log_record log = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = i;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.time = i;\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[9].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[11].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\texpiry.min_update_index = 15;\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[14].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[16].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i <= N; i++) {\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+\treftable_log_record_release(&log);\n+}\n+\n+static int write_nothing(struct reftable_writer *wr, void *arg)\n+{\n+\treftable_writer_set_limits(wr, 1, 1);\n+\treturn 0;\n+}\n+\n+static void test_empty_add(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_stack *st2 = NULL;\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_nothing, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tclear_dir(dir);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st2);\n+}\n+\n+static void test_reftable_stack_auto_compaction(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 100;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\n+\t\tEXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));\n+\t}\n+\n+\tEXPECT(reftable_stack_compaction_stats(st)->entries_written <\n+\t       (uint64_t)(N * fastlog2(N)));\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\tclear_dir(dir);\n+}\n+\n+static void unclean_stack_close(struct reftable_stack *st)\n+{\n+\t// break abstraction boundary to simulate unclean shutdown.\n+\tint i = 0;\n+\tfor (; i < st->readers_len; i++) {\n+\t\treftable_reader_free(st->readers[i]);\n+\t}\n+\tst->readers_len = 0;\n+\tFREE_AND_NULL(st->readers);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent_clean(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tunclean_stack_close(st1);\n+\tunclean_stack_close(st2);\n+\n+\terr = reftable_new_stack(&st3, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_clean(st3);\n+\tEXPECT_ERR(err);\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\treftable_stack_destroy(st3);\n+\n+\tclear_dir(dir);\n+}\n+\n+int stack_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_empty_add);\n+\tRUN_TEST(test_log2);\n+\tRUN_TEST(test_names_equal);\n+\tRUN_TEST(test_parse_names);\n+\tRUN_TEST(test_read_file);\n+\tRUN_TEST(test_reflog_expire);\n+\tRUN_TEST(test_reftable_stack_add);\n+\tRUN_TEST(test_reftable_stack_add_one);\n+\tRUN_TEST(test_reftable_stack_auto_compaction);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent_clean);\n+\tRUN_TEST(test_reftable_stack_hash_id);\n+\tRUN_TEST(test_reftable_stack_lock_failure);\n+\tRUN_TEST(test_reftable_stack_log_normalize);\n+\tRUN_TEST(test_reftable_stack_tombstone);\n+\tRUN_TEST(test_reftable_stack_transaction_api);\n+\tRUN_TEST(test_reftable_stack_update_index_check);\n+\tRUN_TEST(test_reftable_stack_uptodate);\n+\tRUN_TEST(test_reftable_stack_validate_refname);\n+\tRUN_TEST(test_sizes_to_segments);\n+\tRUN_TEST(test_sizes_to_segments_all_equal);\n+\tRUN_TEST(test_sizes_to_segments_empty);\n+\tRUN_TEST(test_suggest_compaction_segment);\n+\tRUN_TEST(test_suggest_compaction_segment_nothing);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c8db6852c35..996da85f7b5 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -10,6 +10,7 @@ int cmd__reftable(int argc, const char **argv)\n \trecord_test_main(argc, argv);\n \trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n+\tstack_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"437322","messageId":"795a15000fbf526b614719dcf33085c2b089f431.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 18/19] reftable: add dump utility","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:15Z","receivedAt":"2021-09-28T15:11:19Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nprovide a command-line utility for inspecting individual tables, and\ninspecting a complete ref database\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/dump.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 107 insertions(+)\n create mode 100644 reftable/dump.c\n\ndiff --git a/reftable/dump.c b/reftable/dump.c\nnew file mode 100644\nindex 00000000000..155953d1b82\n--- /dev/null\n+++ b/reftable/dump.c\n@@ -0,0 +1,107 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"git-compat-util.h\"\n+#include \"hash.h\"\n+\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+#include \"reftable-stack.h\"\n+#include \"reftable-generic.h\"\n+\n+#include <stddef.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <unistd.h>\n+#include <string.h>\n+\n+static int compact_stack(const char *stackdir)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { 0 };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_stack_compact_all(stack, NULL);\n+\tif (err < 0)\n+\t\tgoto done;\n+done:\n+\tif (stack) {\n+\t\treftable_stack_destroy(stack);\n+\t}\n+\treturn err;\n+}\n+\n+static void print_help(void)\n+{\n+\tprintf(\"usage: dump [-cst] arg\\n\\n\"\n+\t       \"options: \\n\"\n+\t       \"  -c compact\\n\"\n+\t       \"  -t dump table\\n\"\n+\t       \"  -s dump stack\\n\"\n+\t       \"  -6 sha256 hash format\\n\"\n+\t       \"  -h this help\\n\"\n+\t       \"\\n\");\n+}\n+\n+int reftable_dump_main(int argc, char *const *argv)\n+{\n+\tint err = 0;\n+\tint opt_dump_table = 0;\n+\tint opt_dump_stack = 0;\n+\tint opt_compact = 0;\n+\tuint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;\n+\tconst char *arg = NULL, *argv0 = argv[0];\n+\n+\tfor (; argc > 1; argv++, argc--)\n+\t\tif (*argv[1] != '-')\n+\t\t\tbreak;\n+\t\telse if (!strcmp(\"-t\", argv[1]))\n+\t\t\topt_dump_table = 1;\n+\t\telse if (!strcmp(\"-6\", argv[1]))\n+\t\t\topt_hash_id = GIT_SHA256_FORMAT_ID;\n+\t\telse if (!strcmp(\"-s\", argv[1]))\n+\t\t\topt_dump_stack = 1;\n+\t\telse if (!strcmp(\"-c\", argv[1]))\n+\t\t\topt_compact = 1;\n+\t\telse if (!strcmp(\"-?\", argv[1]) || !strcmp(\"-h\", argv[1])) {\n+\t\t\tprint_help();\n+\t\t\treturn 2;\n+\t\t}\n+\n+\tif (argc != 2) {\n+\t\tfprintf(stderr, \"need argument\\n\");\n+\t\tprint_help();\n+\t\treturn 2;\n+\t}\n+\n+\targ = argv[1];\n+\n+\tif (opt_dump_table) {\n+\t\terr = reftable_reader_print_file(arg);\n+\t} else if (opt_dump_stack) {\n+\t\terr = reftable_stack_print_directory(arg, opt_hash_id);\n+\t} else if (opt_compact) {\n+\t\terr = compact_stack(arg);\n+\t}\n+\n+\tif (err < 0) {\n+\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv0, arg,\n+\t\t\treftable_error_str(err));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n-- \ngitgitgadget\n\n"},{"id":"437324","messageId":"7b3215aef390d64e1064c89e8a566b62a1ab24b5.1632841817.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v3 19/19] Add \"test-tool dump-reftable\" command.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-28T15:10:16Z","receivedAt":"2021-09-28T15:11:22Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis command dumps individual tables or a stack of of tables.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 | 1 +\n t/helper/test-reftable.c | 5 +++++\n t/helper/test-tool.c     | 1 +\n t/helper/test-tool.h     | 1 +\n 4 files changed, 8 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 53c13c90a5e..21834ace4cf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2452,6 +2452,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/dump.o\n REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 996da85f7b5..26b03d7b789 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -14,3 +14,8 @@ int cmd__reftable(int argc, const char **argv)\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n+\n+int cmd__dump_reftable(int argc, const char **argv)\n+{\n+\treturn reftable_dump_main(argc, (char *const *)argv);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f7c888ffda7..338a57b104d 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -61,6 +61,7 @@ static struct test_cmd cmds[] = {\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n \t{ \"reftable\", cmd__reftable },\n+\t{ \"dump-reftable\", cmd__dump_reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 25f77469146..48cee1f4a2d 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -19,6 +19,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);\n int cmd__dump_fsmonitor(int argc, const char **argv);\n int cmd__dump_split_index(int argc, const char **argv);\n int cmd__dump_untracked_cache(int argc, const char **argv);\n+int cmd__dump_reftable(int argc, const char **argv);\n int cmd__example_decorate(int argc, const char **argv);\n int cmd__fast_rebase(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n-- \ngitgitgadget\n"},{"id":"437342","messageId":"xmqqtui4k1j5.fsf@gitster.g","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-28T18:17:34Z","receivedAt":"2021-09-28T18:17:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Han-Wen Nienhuys via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> The reftable format is described in Documentation/technical/reftable.txt.\n>\n> This is a fully reentrant implementation of reading and writing the reftable\n> file format, and should be suitable for embedding in libgit2 too. It does\n> not hook the code up to git to function as a ref storage backend yet.\n\nElsewhere you solicited comments on the \"RFC - add LICENSE\" step.\n\nAs long as the copyright holders of this library are happy with BSD,\nI do not see a problem with your plan in which the project borrows\nthis code under the license and then owns it, giving access to\nothers under the same license.\n\nIf somebody sees problems with it, please raise your hands ;-)\n\nThanks.\n"},{"id":"437497","messageId":"20210930054032.16867-1-carenas@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"hn/reftable \"fixes\"","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-30T05:40:28Z","receivedAt":"2021-09-30T05:40:47Z","isPatch":false,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"Patch 1, was discussed before and might be solved in a better way as an\nalternative.\n\nPatch 2 and 3 are \"nice to have\" for portability and hopefully not controversial\nbut could be dropped, if someone feels strongly against it.\n\nPatch 4 is not something I'd found failing anywhere, but the fact that Microsoft\nmentions it is only supported as an extension and it needs to be supported by\nthe dynamic linker and I couldn't find anything clear about it in POSIX means,\nthat is probably safer this way.\n\n  [PATCH 1/4] fixup! reftable: add a heap-based priority queue for\n  [PATCH 2/4] fixup! reftable: implement stack, a mutable database of\n  [PATCH 3/4] config.mak.uname: last release and snapshots of Minix\n  [PATCH 4/4] reftable: avoid non portable compile time pointer to\n\n"},{"id":"437498","messageId":"20210930054032.16867-2-carenas@gmail.com","threadId":"56405","inReplyTo":"20210930054032.16867-1-carenas@gmail.com","subject":"[PATCH 1/4] fixup! reftable: add a heap-based priority queue for reftable records","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-30T05:40:29Z","receivedAt":"2021-09-30T05:40:49Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"originally discussed as part of the -DNDEBUG thread[1], but still likely\nto break the build with the parent variable set but not used when assert\ngets compiled out.\n\nmoving to BUG or DIE still a better option otherwise, but this fixup will\nat least address that specific problem (even if the function then is a\nNOOP).\n\n[1] https://lore.kernel.org/git/20210902053023.44006-4-carenas@gmail.com/\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/pq_test.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/reftable/pq_test.c b/reftable/pq_test.c\nindex aaa86d1224..a678848743 100644\n--- a/reftable/pq_test.c\n+++ b/reftable/pq_test.c\n@@ -17,11 +17,9 @@ license that can be found in the LICENSE file or at\n \n void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n {\n-\tint i = 0;\n+\tint i;\n \tfor (i = 1; i < pq.len; i++) {\n-\t\tint parent = (i - 1) / 2;\n-\n-\t\tassert(pq_less(&pq.heap[parent], &pq.heap[i]));\n+\t\tassert(pq_less(&pq.heap[(i - 1)/2], &pq.heap[i]));\n \t}\n }\n \n-- \n2.33.0.955.gee03ddbf0e\n\n"},{"id":"437499","messageId":"20210930054032.16867-3-carenas@gmail.com","threadId":"56405","inReplyTo":"20210930054032.16867-1-carenas@gmail.com","subject":"[PATCH 2/4] fixup! reftable: implement stack, a mutable database of reftable files.","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-30T05:40:30Z","receivedAt":"2021-09-30T05:40:50Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"Avoid C++ style comments, that are not allowed in C89.\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/stack_test.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7a4641ab60..f907215d3c 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -46,8 +46,10 @@ static int count_dir_entries(const char *dirname)\n \treturn len;\n }\n \n-// Work linenumber into the tempdir, so we can see which tests forget to\n-// cleanup.\n+/*\n+ * Work linenumber into the tempdir, so we can see which tests forget to\n+ * cleanup.\n+ */\n static char *get_tmp_template(int linenumber)\n {\n \tconst char *tmp = getenv(\"TMPDIR\");\n@@ -861,7 +863,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n \n static void unclean_stack_close(struct reftable_stack *st)\n {\n-\t// break abstraction boundary to simulate unclean shutdown.\n+\t/* break abstraction boundary to simulate unclean shutdown */\n \tint i = 0;\n \tfor (; i < st->readers_len; i++) {\n \t\treftable_reader_free(st->readers[i]);\n-- \n2.33.0.955.gee03ddbf0e\n\n"},{"id":"437500","messageId":"20210930054032.16867-4-carenas@gmail.com","threadId":"56405","inReplyTo":"20210930054032.16867-1-carenas@gmail.com","subject":"[PATCH 3/4] config.mak.uname: last release and snapshots of Minix still use zlib 1.2.3","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-30T05:40:31Z","receivedAt":"2021-09-30T05:40:51Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"Minix 3 uses a BSD userspace from NetBSD, but might had synced last before\n8.x got branched.\n\nWhile Minix itself doesn't have much activity, this is still affecting the\nlast release (and subsequent snapshots).\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n config.mak.uname | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 4acc3e3609..9399247cbb 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -517,6 +517,7 @@ ifeq ($(uname_S),Interix)\n \tendif\n endif\n ifeq ($(uname_S),Minix)\n+\tNO_UNCOMPRESS2 = YesPlease\n \tNO_IPV6 = YesPlease\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n \tNO_NSEC = YesPlease\n-- \n2.33.0.955.gee03ddbf0e\n\n"},{"id":"437501","messageId":"20210930054032.16867-5-carenas@gmail.com","threadId":"56405","inReplyTo":"20210930054032.16867-1-carenas@gmail.com","subject":"[PATCH 4/4] reftable: avoid non portable compile time pointer to function","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-30T05:40:32Z","receivedAt":"2021-09-30T05:40:52Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"While GNU compilers (and even MSVC) allow as an extension setting\nat compile time the address of a dynamic function into a static\nvariables, it trigger C4232[1] with a warning for portability.\n\nAvoid it by resolving at runtime the address that will be used\non each case, and not relying on the pointer initialization\nto be honour at compile time.\n\n[1] https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4232\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/publicbasics.c | 21 +++++++++++++++------\n 1 file changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/reftable/publicbasics.c b/reftable/publicbasics.c\nindex bd0a02d3f6..b5767bccc5 100644\n--- a/reftable/publicbasics.c\n+++ b/reftable/publicbasics.c\n@@ -11,23 +11,32 @@ license that can be found in the LICENSE file or at\n #include \"basics.h\"\n #include \"system.h\"\n \n-static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;\n-static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;\n-static void (*reftable_free_ptr)(void *) = &free;\n+static void *(*reftable_malloc_ptr)(size_t sz);\n+static void *(*reftable_realloc_ptr)(void *, size_t);\n+static void (*reftable_free_ptr)(void *);\n \n void *reftable_malloc(size_t sz)\n {\n-\treturn (*reftable_malloc_ptr)(sz);\n+\tif (reftable_malloc_ptr)\n+\t\treturn reftable_malloc_ptr(sz);\n+\telse\n+\t\treturn malloc(sz);\n }\n \n void *reftable_realloc(void *p, size_t sz)\n {\n-\treturn (*reftable_realloc_ptr)(p, sz);\n+\tif (reftable_realloc_ptr)\n+\t\treturn reftable_realloc_ptr(p, sz);\n+\telse\n+\t\treturn realloc(p, sz);\n }\n \n void reftable_free(void *p)\n {\n-\treftable_free_ptr(p);\n+\tif (reftable_free_ptr)\n+\t\treftable_free_ptr(p);\n+\telse\n+\t\tfree(p);\n }\n \n void *reftable_calloc(size_t sz)\n-- \n2.33.0.955.gee03ddbf0e\n\n"},{"id":"437518","messageId":"20210930122352.26505-1-carenas@gmail.com","threadId":"56405","inReplyTo":"04f2d74df5189f473bc7a6395b9e194a1929f623.1632841817.git.gitgitgadget@gmail.com","subject":"[PATCH] squash! reftable: reading/writing blocks","fromName":"Carlo Marcelo Arenas Belón","fromEmail":"carenas@gmail.com","sentAt":"2021-09-30T12:23:52Z","receivedAt":"2021-09-30T12:24:39Z","isPatch":true,"sender":{"key":"carenas@gmail.com","avatar":"https://avatars.githubusercontent.com/u/76036?v=4"},"body":"Avoid a possible infinite loop in platforms where size_t > long\nby using instead the required type and checking for overflow\nbefore doubling.\n\nIncrease slightly the initial buffer based on the zlib(1)\nrecommendation to reduce the likelihood of this code triggering,\nand add some error checking to the memory allocation.\n\n[1] https://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/zlib-compress2-1.html\n\nSigned-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/block.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/reftable/block.c b/reftable/block.c\nindex 11387b260a..4029df18ac 100644\n--- a/reftable/block.c\n+++ b/reftable/block.c\n@@ -132,7 +132,7 @@ int block_writer_add(struct block_writer *w, struct reftable_record *rec)\n \n int block_writer_finish(struct block_writer *w)\n {\n-\tint i = 0;\n+\tint i;\n \tfor (i = 0; i < w->restart_len; i++) {\n \t\tput_be24(w->buf + w->next, w->restarts[i]);\n \t\tw->next += 3;\n@@ -144,23 +144,24 @@ int block_writer_finish(struct block_writer *w)\n \n \tif (block_writer_type(w) == BLOCK_TYPE_LOG) {\n \t\tint block_header_skip = 4 + w->header_off;\n-\t\tuint8_t *compressed = NULL;\n-\t\tint zresult = 0;\n+\t\tuint8_t *compressed;\n \t\tuLongf src_len = w->next - block_header_skip;\n-\t\tsize_t dest_cap = src_len;\n+\t\tuLongf dest_cap = src_len * 1.001 + 12;\n \n \t\tcompressed = reftable_malloc(dest_cap);\n \t\twhile (1) {\n+\t\t\tint zresult;\n \t\t\tuLongf out_dest_len = dest_cap;\n \n \t\t\tzresult = compress2(compressed, &out_dest_len,\n \t\t\t\t\t    w->buf + block_header_skip, src_len,\n \t\t\t\t\t    9);\n-\t\t\tif (zresult == Z_BUF_ERROR) {\n+\t\t\tif (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) {\n \t\t\t\tdest_cap *= 2;\n \t\t\t\tcompressed =\n \t\t\t\t\treftable_realloc(compressed, dest_cap);\n-\t\t\t\tcontinue;\n+\t\t\t\tif (compressed)\n+\t\t\t\t\tcontinue;\n \t\t\t}\n \n \t\t\tif (Z_OK != zresult) {\n-- \n2.33.0.955.gee03ddbf0e\n\n"},{"id":"437576","messageId":"xmqq1r55by3r.fsf@gitster.g","threadId":"56405","inReplyTo":"20210930054032.16867-1-carenas@gmail.com","subject":"Re: hn/reftable \"fixes\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-30T20:35:36Z","receivedAt":"2021-09-30T20:35:42Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carlo Marcelo Arenas Belón  <carenas@gmail.com> writes:\n\n> Patch 1, was discussed before and might be solved in a better way as an\n> alternative.\n>\n> Patch 2 and 3 are \"nice to have\" for portability and hopefully not controversial\n> but could be dropped, if someone feels strongly against it.\n>\n> Patch 4 is not something I'd found failing anywhere, but the fact that Microsoft\n> mentions it is only supported as an extension and it needs to be supported by\n> the dynamic linker and I couldn't find anything clear about it in POSIX means,\n> that is probably safer this way.\n\nAll of them look sensible.  Thanks.\n"},{"id":"437709","messageId":"87wnmwpwyf.fsf@evledraar.gmail.com","threadId":"56405","inReplyTo":"20210930054032.16867-3-carenas@gmail.com","subject":"C++(C99)-style comments in git.git","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-01T15:37:40Z","receivedAt":"2021-10-01T15:49:19Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Sep 29 2021, Carlo Marcelo Arenas Belón wrote:\n\n> Avoid C++ style comments, that are not allowed in C89.\n>\n> Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n> ---\n>  reftable/stack_test.c | 8 +++++---\n>  1 file changed, 5 insertions(+), 3 deletions(-)\n>\n> diff --git a/reftable/stack_test.c b/reftable/stack_test.c\n> index 7a4641ab60..f907215d3c 100644\n> --- a/reftable/stack_test.c\n> +++ b/reftable/stack_test.c\n> @@ -46,8 +46,10 @@ static int count_dir_entries(const char *dirname)\n>  \treturn len;\n>  }\n>  \n> -// Work linenumber into the tempdir, so we can see which tests forget to\n> -// cleanup.\n> +/*\n> + * Work linenumber into the tempdir, so we can see which tests forget to\n> + * cleanup.\n> + */\n>  static char *get_tmp_template(int linenumber)\n>  {\n>  \tconst char *tmp = getenv(\"TMPDIR\");\n> @@ -861,7 +863,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n>  \n>  static void unclean_stack_close(struct reftable_stack *st)\n>  {\n> -\t// break abstraction boundary to simulate unclean shutdown.\n> +\t/* break abstraction boundary to simulate unclean shutdown */\n>  \tint i = 0;\n>  \tfor (; i < st->readers_len; i++) {\n>  \t\treftable_reader_free(st->readers[i]);\n\nThis change looks fine, but FYI we had a C++ comment in git.git from\nv2.21.0 with d73019feb44 (http: add support selecting http version,\n2018-11-08) until now.\n\nIt'll be removed in v2.34.0 with my 2a7f64616a3 (http: correct version\ncheck for CURL_HTTP_VERSION_2, 2021-09-13) (not the purpose of that\nchange).\n\nNow, granted it's \"only\" compiled if you don't have NO_CURL=Y, and it's\non a \"#\" pre-processor line, not in the \"main\" code.\n\nSo it's possible (but I think not likely) that someone's cpp accepts it,\nbut not their cc. Or that some platform has nobody compiling with curl\n(not very likely).\n\nI'm not advocating for using \"//\" comments, but I think it's fair to say\ngiven the above that they're purely an asthetic coding style preference\nin the context of git.git, and not a matter of C89 portability that\nanyone cares about when building or porting git.\n\nWe already have a hard dependency on a few C99-only features, I'd think\nit's very unlikely that someone has say implemented designated\ninitializers, but not \"//\" comment support. So for our purposes support\nfor it should be universal.\n"},{"id":"437760","messageId":"875yufpynd.fsf@evledraar.gmail.com","threadId":"56405","inReplyTo":"xmqqtui4k1j5.fsf@gitster.g","subject":"Re: [PATCH v3 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-02T09:20:29Z","receivedAt":"2021-10-02T09:24:42Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Sep 28 2021, Junio C Hamano wrote:\n\n> \"Han-Wen Nienhuys via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> The reftable format is described in Documentation/technical/reftable.txt.\n>>\n>> This is a fully reentrant implementation of reading and writing the reftable\n>> file format, and should be suitable for embedding in libgit2 too. It does\n>> not hook the code up to git to function as a ref storage backend yet.\n>\n> Elsewhere you solicited comments on the \"RFC - add LICENSE\" step.\n>\n> As long as the copyright holders of this library are happy with BSD,\n> I do not see a problem with your plan in which the project borrows\n> this code under the license and then owns it, giving access to\n> others under the same license.\n>\n> If somebody sees problems with it, please raise your hands ;-)\n\nNo objection, but as noted in previous discussions about it I think we\nshould have some clarity about what it means for contributors. I.e. that\nwhen they're contributing to reftable/ it's BSD license, but for the\nrest of the codebase it's GPLv2.\n\nI've just submitted a series to address that more generally:\nhttps://lore.kernel.org/git/cover-0.5-00000000000-20211002T091212Z-avarab@gmail.com/\n\nI think with that clarification of what these in-tree subdirectory\n\"COPYING\" files mean the only thing left is for the \"RFC\" label to be\ndropped here.\n\nFrom what I as not-a-lawyer think I know about how licenses work the\nconcept that we can have BSD licensed content in-tree that can be\nextracted and legally used by anyone outside of it seems rather dubious.\n\nI.e. if I author a cross-tree where \"reftable/\" uses some updated API,\nisn't the result a derived work and now GPLv2 and not \"cleanly\" BSD\nlicensed? But that's a separate question, and ultimately a worry for any\nout-of-tree users. So I don't really care. I also believe that we've had\nthat situation before with (I think) libgit2 taking snapshots of our\n\"xdiff\", so maybe it's already deemed to be a complete non-issue.\n\nAll I'd like is for contributors to git.git not to be confused, which I\nthink with my series above won't be the case.\n"},{"id":"438178","messageId":"CAFQ2z_PoRTOZDmZyWObeL3CC4Y5SvJhaVAMfkV-nT4NnMdL6zQ@mail.gmail.com","threadId":"56405","inReplyTo":"20210930122352.26505-1-carenas@gmail.com","subject":"Re: [PATCH] squash! reftable: reading/writing blocks","fromName":"Han-Wen Nienhuys","fromEmail":"hanwen@google.com","sentAt":"2021-10-07T16:34:31Z","receivedAt":"2021-10-07T16:34:45Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"On Thu, Sep 30, 2021 at 2:24 PM Carlo Marcelo Arenas Belón\n<carenas@gmail.com> wrote:\n> Avoid a possible infinite loop in platforms where size_t > long\n> by using instead the required type and checking for overflow\n> before doubling.\n\nThanks for your attention to detail. Folded in.\n\nI think the loop is rather hypothetical, though: the block size is\nlimited to 2²⁴, so I think you can´t make blocks that large.\n\n-- \nHan-Wen Nienhuys - Google Munich\nI work 80%. Don't expect answers from me on Fridays.\n--\nGoogle Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich\nRegistergericht und -nummer: Hamburg, HRB 86891\nSitz der Gesellschaft: Hamburg\nGeschäftsführer: Paul Manicle, Halimah DeLaine Prado\n"},{"id":"438201","messageId":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v3.git.git.1632841817.gitgitgadget@gmail.com","subject":"[PATCH v4 00/19] Adds reftable library code from https://github.com/hanwen/reftable.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:24:56Z","receivedAt":"2021-10-07T20:25:21Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"The reftable format is described in Documentation/technical/reftable.txt.\n\nThis is a fully reentrant implementation of reading and writing the reftable\nfile format, and should be suitable for embedding in libgit2 too. It does\nnot hook the code up to git to function as a ref storage backend yet.\n\nv4:\n\n * fixes by Carlo Belón.\n * remove RFC from LICENSE commit.\n\nHan-Wen Nienhuys (19):\n  hash.h: provide constants for the hash IDs\n  reftable: add LICENSE\n  reftable: add error related functionality\n  reftable: utility functions\n  reftable: add blocksource, an abstraction for random access reads\n  reftable: (de)serialization for the polymorphic record type.\n  Provide zlib's uncompress2 from compat/zlib-compat.c\n  reftable: reading/writing blocks\n  reftable: a generic binary tree implementation\n  reftable: write reftable files\n  reftable: generic interface to tables\n  reftable: read reftable files\n  reftable: reftable file level tests\n  reftable: add a heap-based priority queue for reftable records\n  reftable: add merged table view\n  reftable: implement refname validation\n  reftable: implement stack, a mutable database of reftable files.\n  reftable: add dump utility\n  Add \"test-tool dump-reftable\" command.\n\n Makefile                                   |   53 +-\n ci/lib.sh                                  |    1 +\n compat/.gitattributes                      |    1 +\n compat/zlib-uncompress2.c                  |   95 ++\n config.mak.uname                           |    5 +\n configure.ac                               |   13 +\n contrib/buildsystems/CMakeLists.txt        |   14 +-\n contrib/buildsystems/Generators/Vcxproj.pm |   11 +-\n hash.h                                     |    6 +\n object-file.c                              |    7 +-\n reftable/LICENSE                           |   31 +\n reftable/basics.c                          |  128 ++\n reftable/basics.h                          |   60 +\n reftable/basics_test.c                     |   98 ++\n reftable/block.c                           |  437 ++++++\n reftable/block.h                           |  127 ++\n reftable/block_test.c                      |  120 ++\n reftable/blocksource.c                     |  148 +++\n reftable/blocksource.h                     |   22 +\n reftable/constants.h                       |   21 +\n reftable/dump.c                            |  107 ++\n reftable/error.c                           |   41 +\n reftable/generic.c                         |  169 +++\n reftable/generic.h                         |   32 +\n reftable/iter.c                            |  194 +++\n reftable/iter.h                            |   69 +\n reftable/merged.c                          |  362 +++++\n reftable/merged.h                          |   38 +\n reftable/merged_test.c                     |  468 +++++++\n reftable/pq.c                              |  105 ++\n reftable/pq.h                              |   33 +\n reftable/pq_test.c                         |   82 ++\n reftable/publicbasics.c                    |   65 +\n reftable/reader.c                          |  801 +++++++++++\n reftable/reader.h                          |   64 +\n reftable/readwrite_test.c                  |  652 +++++++++\n reftable/record.c                          | 1212 +++++++++++++++++\n reftable/record.h                          |  139 ++\n reftable/record_test.c                     |  412 ++++++\n reftable/refname.c                         |  209 +++\n reftable/refname.h                         |   29 +\n reftable/refname_test.c                    |  102 ++\n reftable/reftable-blocksource.h            |   49 +\n reftable/reftable-error.h                  |   62 +\n reftable/reftable-generic.h                |   47 +\n reftable/reftable-iterator.h               |   39 +\n reftable/reftable-malloc.h                 |   18 +\n reftable/reftable-merged.h                 |   72 +\n reftable/reftable-reader.h                 |  101 ++\n reftable/reftable-record.h                 |  114 ++\n reftable/reftable-stack.h                  |  128 ++\n reftable/reftable-tests.h                  |   23 +\n reftable/reftable-writer.h                 |  148 +++\n reftable/reftable.c                        |  115 ++\n reftable/stack.c                           | 1396 ++++++++++++++++++++\n reftable/stack.h                           |   41 +\n reftable/stack_test.c                      |  953 +++++++++++++\n reftable/system.h                          |   32 +\n reftable/test_framework.c                  |   23 +\n reftable/test_framework.h                  |   53 +\n reftable/tree.c                            |   63 +\n reftable/tree.h                            |   34 +\n reftable/tree_test.c                       |   61 +\n reftable/writer.c                          |  690 ++++++++++\n reftable/writer.h                          |   50 +\n t/helper/test-reftable.c                   |   21 +\n t/helper/test-tool.c                       |    4 +-\n t/helper/test-tool.h                       |    2 +\n t/t0032-reftable-unittest.sh               |   15 +\n 69 files changed, 11125 insertions(+), 12 deletions(-)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n create mode 100644 reftable/LICENSE\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/dump.c\n create mode 100644 reftable/error.c\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/readwrite_test.c\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n create mode 100644 reftable/reftable-blocksource.h\n create mode 100644 reftable/reftable-error.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-merged.h\n create mode 100644 reftable/reftable-reader.h\n create mode 100644 reftable/reftable-record.h\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/reftable.c\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\n\nbase-commit: 106298f7f9cca4158a980de149ef217751e1f943\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1081%2Fhanwen%2Fjust-library-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1081/hanwen/just-library-v4\nPull-Request: https://github.com/git/git/pull/1081\n\nRange-diff vs v3:\n\n  1:  b2f8dc32bfc =  1:  9cc44f78377 hash.h: provide constants for the hash IDs\n  2:  2597ddfeae1 !  2:  6a5ee5db472 reftable: RFC: add LICENSE\n     @@ Metadata\n      Author: Han-Wen Nienhuys <hanwen@google.com>\n      \n       ## Commit message ##\n     -    reftable: RFC: add LICENSE\n     +    reftable: add LICENSE\n      \n          The objective of this code is to be usable as a C library, so it can be reused\n          in libgit2.\n  3:  41b1ff1293f !  3:  bf51bc9ab0c reftable: add error related functionality\n     @@ Commit message\n          reftable: add error related functionality\n      \n          The reftable/ directory is structured as a library, so it cannot\n     -    crash on misuse. Instead, it returns an error codes.\n     +    crash on misuse. Instead, it returns an error code.\n      \n     -    In addition, the error code can be used to signal conditions from lower levels\n     -    of the library to be handled by higher levels of the library. For example, a\n     -    transaction might legitimately write an empty reftable file, but in that case,\n     -    we'd want to shortcut the transaction overhead.\n     +    In addition to signaling errors, the error code can be used to signal\n     +    conditions from lower levels of the library to be handled by higher\n     +    levels of the library. For example, in a transaction we might\n     +    legitimately write an empty reftable file, but in that case, we want to\n     +    shortcut the transaction.\n      \n          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>\n      \n  4:  c03043c5e63 !  4:  318e7ad41e5 reftable: utility functions\n     @@ reftable/publicbasics.c (new)\n      +#include \"basics.h\"\n      +#include \"system.h\"\n      +\n     -+static void *(*reftable_malloc_ptr)(size_t sz) = &malloc;\n     -+static void *(*reftable_realloc_ptr)(void *, size_t) = &realloc;\n     -+static void (*reftable_free_ptr)(void *) = &free;\n     ++static void *(*reftable_malloc_ptr)(size_t sz);\n     ++static void *(*reftable_realloc_ptr)(void *, size_t);\n     ++static void (*reftable_free_ptr)(void *);\n      +\n      +void *reftable_malloc(size_t sz)\n      +{\n     -+\treturn (*reftable_malloc_ptr)(sz);\n     ++\tif (reftable_malloc_ptr)\n     ++\t\treturn (*reftable_malloc_ptr)(sz);\n     ++\treturn malloc(sz);\n      +}\n      +\n      +void *reftable_realloc(void *p, size_t sz)\n      +{\n     -+\treturn (*reftable_realloc_ptr)(p, sz);\n     ++\tif (reftable_realloc_ptr)\n     ++\t\treturn (*reftable_realloc_ptr)(p, sz);\n     ++\treturn realloc(p, sz);\n      +}\n      +\n      +void reftable_free(void *p)\n      +{\n     -+\treftable_free_ptr(p);\n     ++\tif (reftable_free_ptr)\n     ++\t\treftable_free_ptr(p);\n     ++\telse\n     ++\t\tfree(p);\n      +}\n      +\n      +void *reftable_calloc(size_t sz)\n  5:  317a6aaa357 =  5:  80036f93e2e reftable: add blocksource, an abstraction for random access reads\n  6:  55e66b7a74f =  6:  f2cd2876265 reftable: (de)serialization for the polymorphic record type.\n  7:  4f6ecd3646a !  7:  6cbc2f758df Provide zlib's uncompress2 from compat/zlib-compat.c\n     @@ config.mak.uname: ifeq ($(uname_S),FreeBSD)\n       \tNO_STRCASESTR = YesPlease\n       \tNO_MEMMEM = YesPlease\n       \tUSE_ST_TIMESPEC = YesPlease\n     +@@ config.mak.uname: ifeq ($(uname_S),Interix)\n     + \tendif\n     + endif\n     + ifeq ($(uname_S),Minix)\n     ++\tNO_UNCOMPRESS2 = YesPlease\n     + \tNO_IPV6 = YesPlease\n     + \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n     + \tNO_NSEC = YesPlease\n      \n       ## configure.ac ##\n      @@ configure.ac: AC_LINK_IFELSE([ZLIBTEST_SRC],\n  8:  04f2d74df51 !  8:  1002d6f526c reftable: reading/writing blocks\n     @@ Commit message\n      \n          This commit provides the logic to read and write these blocks.\n      \n     +    Helped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>\n      \n       ## Makefile ##\n     @@ reftable/block.c (new)\n      +\n      +int block_writer_finish(struct block_writer *w)\n      +{\n     -+\tint i = 0;\n     ++\tint i;\n      +\tfor (i = 0; i < w->restart_len; i++) {\n      +\t\tput_be24(w->buf + w->next, w->restarts[i]);\n      +\t\tw->next += 3;\n     @@ reftable/block.c (new)\n      +\n      +\tif (block_writer_type(w) == BLOCK_TYPE_LOG) {\n      +\t\tint block_header_skip = 4 + w->header_off;\n     -+\t\tuint8_t *compressed = NULL;\n     -+\t\tint zresult = 0;\n      +\t\tuLongf src_len = w->next - block_header_skip;\n     -+\t\tsize_t dest_cap = src_len;\n     ++\t\tuLongf dest_cap = src_len * 1.001 + 12;\n      +\n     -+\t\tcompressed = reftable_malloc(dest_cap);\n     ++\t\tuint8_t *compressed = reftable_malloc(dest_cap);\n      +\t\twhile (1) {\n      +\t\t\tuLongf out_dest_len = dest_cap;\n     -+\n     -+\t\t\tzresult = compress2(compressed, &out_dest_len,\n     -+\t\t\t\t\t    w->buf + block_header_skip, src_len,\n     -+\t\t\t\t\t    9);\n     -+\t\t\tif (zresult == Z_BUF_ERROR) {\n     ++\t\t\tint zresult = compress2(compressed, &out_dest_len,\n     ++\t\t\t\t\t\tw->buf + block_header_skip,\n     ++\t\t\t\t\t\tsrc_len, 9);\n     ++\t\t\tif (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) {\n      +\t\t\t\tdest_cap *= 2;\n      +\t\t\t\tcompressed =\n      +\t\t\t\t\treftable_realloc(compressed, dest_cap);\n     -+\t\t\t\tcontinue;\n     ++\t\t\t\tif (compressed)\n     ++\t\t\t\t\tcontinue;\n      +\t\t\t}\n      +\n      +\t\t\tif (Z_OK != zresult) {\n  9:  f6c2da61208 =  9:  96e490ac32c reftable: a generic binary tree implementation\n 10:  277a1f662e3 = 10:  daf9cc897d1 reftable: write reftable files\n 11:  f97182dec1f = 11:  c9a5813b211 reftable: generic interface to tables\n 12:  13de5d03a71 ! 12:  854b30268b6 reftable: read reftable files\n     @@ reftable/reader.c (new)\n      +\treturn 0;\n      +}\n      +\n     -+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n     -+\t\tstruct reftable_record *rec)\n     ++static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n     ++\t\t       struct reftable_record *rec)\n      +{\n      +\tuint8_t typ = reftable_record_type(rec);\n      +\n     @@ reftable/reader.h (new)\n      +\n      +int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n      +\t\tconst char *name);\n     -+int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n     -+\t\tstruct reftable_record *rec);\n      +void reader_close(struct reftable_reader *r);\n      +const char *reader_name(struct reftable_reader *r);\n      +\n 13:  8efc3c2b6ab = 13:  c0d0427d52c reftable: reftable file level tests\n 14:  e89b16a37e6 ! 14:  6e5ee043083 reftable: add a heap-based priority queue for reftable records\n     @@ reftable/pq_test.c (new)\n      +\n      +void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n      +{\n     -+\tint i = 0;\n     ++\tint i;\n      +\tfor (i = 1; i < pq.len; i++) {\n      +\t\tint parent = (i - 1) / 2;\n      +\n     -+\t\tassert(pq_less(&pq.heap[parent], &pq.heap[i]));\n     ++\t\tEXPECT(pq_less(&pq.heap[parent], &pq.heap[i]));\n      +\t}\n      +}\n      +\n 15:  40a91b14be8 ! 15:  0b86f6c2327 reftable: add merged table view\n     @@ reftable/merged.h (new)\n      +\tstruct reftable_table *stack;\n      +\tsize_t stack_len;\n      +\tuint32_t hash_id;\n     ++\n     ++\t/* If unset, produce deletions. This is useful for compaction. For the\n     ++\t * full stack, deletions should be produced. */\n      +\tint suppress_deletions;\n      +\n      +\tuint64_t min;\n     @@ reftable/merged_test.c (new)\n      +\treftable_writer_free(w);\n      +}\n      +\n     ++static void write_test_log_table(struct strbuf *buf,\n     ++\t\t\t\t struct reftable_log_record logs[], int n,\n     ++\t\t\t\t uint64_t update_index)\n     ++{\n     ++\tint i = 0;\n     ++\tint err;\n     ++\n     ++\tstruct reftable_write_options opts = {\n     ++\t\t.block_size = 256,\n     ++\t\t.exact_log_message = 1,\n     ++\t};\n     ++\tstruct reftable_writer *w = NULL;\n     ++\tw = reftable_new_writer(&strbuf_add_void, buf, &opts);\n     ++\treftable_writer_set_limits(w, update_index, update_index);\n     ++\n     ++\tfor (i = 0; i < n; i++) {\n     ++\t\tint err = reftable_writer_add_log(w, &logs[i]);\n     ++\t\tEXPECT_ERR(err);\n     ++\t}\n     ++\n     ++\terr = reftable_writer_close(w);\n     ++\tEXPECT_ERR(err);\n     ++\n     ++\treftable_writer_free(w);\n     ++}\n     ++\n      +static struct reftable_merged_table *\n      +merged_table_from_records(struct reftable_ref_record **refs,\n      +\t\t\t  struct reftable_block_source **source,\n     @@ reftable/merged_test.c (new)\n      +\tint i = 0;\n      +\n      +\tEXPECT_ERR(err);\n     ++\tEXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);\n     ++\tEXPECT(reftable_merged_table_min_update_index(mt) == 1);\n     ++\n      +\twhile (len < 100) { /* cap loops/recursion. */\n      +\t\tstruct reftable_ref_record ref = { NULL };\n      +\t\tint err = reftable_iterator_next_ref(&it, &ref);\n     @@ reftable/merged_test.c (new)\n      +\treftable_free(bs);\n      +}\n      +\n     ++static struct reftable_merged_table *\n     ++merged_table_from_log_records(struct reftable_log_record **logs,\n     ++\t\t\t      struct reftable_block_source **source,\n     ++\t\t\t      struct reftable_reader ***readers, int *sizes,\n     ++\t\t\t      struct strbuf *buf, int n)\n     ++{\n     ++\tint i = 0;\n     ++\tstruct reftable_merged_table *mt = NULL;\n     ++\tint err;\n     ++\tstruct reftable_table *tabs =\n     ++\t\treftable_calloc(n * sizeof(struct reftable_table));\n     ++\t*readers = reftable_calloc(n * sizeof(struct reftable_reader *));\n     ++\t*source = reftable_calloc(n * sizeof(**source));\n     ++\tfor (i = 0; i < n; i++) {\n     ++\t\twrite_test_log_table(&buf[i], logs[i], sizes[i], i + 1);\n     ++\t\tblock_source_from_strbuf(&(*source)[i], &buf[i]);\n     ++\n     ++\t\terr = reftable_new_reader(&(*readers)[i], &(*source)[i],\n     ++\t\t\t\t\t  \"name\");\n     ++\t\tEXPECT_ERR(err);\n     ++\t\treftable_table_from_reader(&tabs[i], (*readers)[i]);\n     ++\t}\n     ++\n     ++\terr = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);\n     ++\tEXPECT_ERR(err);\n     ++\treturn mt;\n     ++}\n     ++\n     ++static void test_merged_logs(void)\n     ++{\n     ++\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };\n     ++\tuint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };\n     ++\tuint8_t hash3[GIT_SHA1_RAWSZ] = { 3 };\n     ++\tstruct reftable_log_record r1[] = {\n     ++\t\t{\n     ++\t\t\t.refname = \"a\",\n     ++\t\t\t.update_index = 2,\n     ++\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n     ++\t\t\t.value.update = {\n     ++\t\t\t\t.old_hash = hash2,\n     ++\t\t\t\t/* deletion */\n     ++\t\t\t\t.name = \"jane doe\",\n     ++\t\t\t\t.email = \"jane@invalid\",\n     ++\t\t\t\t.message = \"message2\",\n     ++\t\t\t}\n     ++\t\t},\n     ++\t\t{\n     ++\t\t\t.refname = \"a\",\n     ++\t\t\t.update_index = 1,\n     ++\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n     ++\t\t\t.value.update = {\n     ++\t\t\t\t.old_hash = hash1,\n     ++\t\t\t\t.new_hash = hash2,\n     ++\t\t\t\t.name = \"jane doe\",\n     ++\t\t\t\t.email = \"jane@invalid\",\n     ++\t\t\t\t.message = \"message1\",\n     ++\t\t\t}\n     ++\t\t},\n     ++\t};\n     ++\tstruct reftable_log_record r2[] = {\n     ++\t\t{\n     ++\t\t\t.refname = \"a\",\n     ++\t\t\t.update_index = 3,\n     ++\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n     ++\t\t\t.value.update = {\n     ++\t\t\t\t.new_hash = hash3,\n     ++\t\t\t\t.name = \"jane doe\",\n     ++\t\t\t\t.email = \"jane@invalid\",\n     ++\t\t\t\t.message = \"message3\",\n     ++\t\t\t}\n     ++\t\t},\n     ++\t};\n     ++\tstruct reftable_log_record r3[] = {\n     ++\t\t{\n     ++\t\t\t.refname = \"a\",\n     ++\t\t\t.update_index = 2,\n     ++\t\t\t.value_type = REFTABLE_LOG_DELETION,\n     ++\t\t},\n     ++\t};\n     ++\tstruct reftable_log_record want[] = {\n     ++\t\tr2[0],\n     ++\t\tr3[0],\n     ++\t\tr1[1],\n     ++\t};\n     ++\n     ++\tstruct reftable_log_record *logs[] = { r1, r2, r3 };\n     ++\tint sizes[3] = { 2, 1, 1 };\n     ++\tstruct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };\n     ++\tstruct reftable_block_source *bs = NULL;\n     ++\tstruct reftable_reader **readers = NULL;\n     ++\tstruct reftable_merged_table *mt = merged_table_from_log_records(\n     ++\t\tlogs, &bs, &readers, sizes, bufs, 3);\n     ++\n     ++\tstruct reftable_iterator it = { NULL };\n     ++\tint err = reftable_merged_table_seek_log(mt, &it, \"a\");\n     ++\tstruct reftable_log_record *out = NULL;\n     ++\tsize_t len = 0;\n     ++\tsize_t cap = 0;\n     ++\tint i = 0;\n     ++\n     ++\tEXPECT_ERR(err);\n     ++\tEXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);\n     ++\tEXPECT(reftable_merged_table_min_update_index(mt) == 1);\n     ++\n     ++\twhile (len < 100) { /* cap loops/recursion. */\n     ++\t\tstruct reftable_log_record log = { NULL };\n     ++\t\tint err = reftable_iterator_next_log(&it, &log);\n     ++\t\tif (err > 0) {\n     ++\t\t\tbreak;\n     ++\t\t}\n     ++\t\tif (len == cap) {\n     ++\t\t\tcap = 2 * cap + 1;\n     ++\t\t\tout = reftable_realloc(\n     ++\t\t\t\tout, sizeof(struct reftable_log_record) * cap);\n     ++\t\t}\n     ++\t\tout[len++] = log;\n     ++\t}\n     ++\treftable_iterator_destroy(&it);\n     ++\n     ++\tEXPECT(ARRAY_SIZE(want) == len);\n     ++\tfor (i = 0; i < len; i++) {\n     ++\t\tEXPECT(reftable_log_record_equal(&want[i], &out[i],\n     ++\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n     ++\t}\n     ++\n     ++\terr = reftable_merged_table_seek_log_at(mt, &it, \"a\", 2);\n     ++\tEXPECT_ERR(err);\n     ++\treftable_log_record_release(&out[0]);\n     ++\terr = reftable_iterator_next_log(&it, &out[0]);\n     ++\tEXPECT_ERR(err);\n     ++\tEXPECT(reftable_log_record_equal(&out[0], &r3[0], GIT_SHA1_RAWSZ));\n     ++\treftable_iterator_destroy(&it);\n     ++\n     ++\tfor (i = 0; i < len; i++) {\n     ++\t\treftable_log_record_release(&out[i]);\n     ++\t}\n     ++\treftable_free(out);\n     ++\n     ++\tfor (i = 0; i < 3; i++) {\n     ++\t\tstrbuf_release(&bufs[i]);\n     ++\t}\n     ++\treaders_destroy(readers, 3);\n     ++\treftable_merged_table_free(mt);\n     ++\treftable_free(bs);\n     ++}\n     ++\n      +static void test_default_write_opts(void)\n      +{\n      +\tstruct reftable_write_options opts = { 0 };\n     @@ reftable/merged_test.c (new)\n      +\n      +int merged_test_main(int argc, const char *argv[])\n      +{\n     ++\tRUN_TEST(test_merged_logs);\n      +\tRUN_TEST(test_merged_between);\n      +\tRUN_TEST(test_merged);\n      +\tRUN_TEST(test_default_write_opts);\n 16:  46c2ddf07cf = 16:  c600e1f975c reftable: implement refname validation\n 17:  3db887bc078 ! 17:  55dacc6760f reftable: implement stack, a mutable database of reftable files.\n     @@ reftable/stack_test.c (new)\n      +\treturn len;\n      +}\n      +\n     -+// Work linenumber into the tempdir, so we can see which tests forget to\n     -+// cleanup.\n     ++/*\n     ++ * Work linenumber into the tempdir, so we can see which tests forget to\n     ++ * cleanup.\n     ++ */\n      +static char *get_tmp_template(int linenumber)\n      +{\n      +\tconst char *tmp = getenv(\"TMPDIR\");\n     @@ reftable/stack_test.c (new)\n      +\terr = reftable_new_stack(&st, dir, cfg);\n      +\tEXPECT_ERR(err);\n      +\n     ++\tst->disable_auto_compact = 1; /* call manually below for coverage. */\n      +\tfor (i = 0; i < N; i++) {\n      +\t\tchar name[100];\n      +\t\tstruct reftable_ref_record ref = {\n     @@ reftable/stack_test.c (new)\n      +\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n      +\t\tEXPECT_ERR(err);\n      +\n     ++\t\terr = reftable_stack_auto_compact(st);\n      +\t\tEXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));\n      +\t}\n      +\n     @@ reftable/stack_test.c (new)\n      +\n      +static void unclean_stack_close(struct reftable_stack *st)\n      +{\n     -+\t// break abstraction boundary to simulate unclean shutdown.\n     ++\t/* break abstraction boundary to simulate unclean shutdown. */\n      +\tint i = 0;\n      +\tfor (; i < st->readers_len; i++) {\n      +\t\treftable_reader_free(st->readers[i]);\n 18:  795a15000fb = 18:  2f825cdb2d4 reftable: add dump utility\n 19:  7b3215aef39 = 19:  c8428cb0df8 Add \"test-tool dump-reftable\" command.\n\n-- \ngitgitgadget\n"},{"id":"438200","messageId":"9cc44f7837793c8da376eb34de90506696416682.1633638315.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 01/19] hash.h: provide constants for the hash IDs","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:24:57Z","receivedAt":"2021-10-07T20:25:23Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will simplify referencing them from code that is not deeply integrated with\nGit, in particular, the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n hash.h        | 6 ++++++\n object-file.c | 7 ++-----\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/hash.h b/hash.h\nindex 9e25c40e9ac..5d40368f18a 100644\n--- a/hash.h\n+++ b/hash.h\n@@ -95,12 +95,18 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s\n /* Number of algorithms supported (including unknown). */\n #define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)\n \n+/* \"sha1\", big-endian */\n+#define GIT_SHA1_FORMAT_ID 0x73686131\n+\n /* The length in bytes and in hex digits of an object name (SHA-1 value). */\n #define GIT_SHA1_RAWSZ 20\n #define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)\n /* The block size of SHA-1. */\n #define GIT_SHA1_BLKSZ 64\n \n+/* \"s256\", big-endian */\n+#define GIT_SHA256_FORMAT_ID 0x73323536\n+\n /* The length in bytes and in hex digits of an object name (SHA-256 value). */\n #define GIT_SHA256_RAWSZ 32\n #define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)\ndiff --git a/object-file.c b/object-file.c\nindex 112d9b4badc..b79ab75979f 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -165,7 +165,6 @@ static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx)\n \tBUG(\"trying to finalize unknown hash\");\n }\n \n-\n const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t{\n \t\tNULL,\n@@ -184,8 +183,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha1\",\n-\t\t/* \"sha1\", big-endian */\n-\t\t0x73686131,\n+\t\tGIT_SHA1_FORMAT_ID,\n \t\tGIT_SHA1_RAWSZ,\n \t\tGIT_SHA1_HEXSZ,\n \t\tGIT_SHA1_BLKSZ,\n@@ -200,8 +198,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {\n \t},\n \t{\n \t\t\"sha256\",\n-\t\t/* \"s256\", big-endian */\n-\t\t0x73323536,\n+\t\tGIT_SHA256_FORMAT_ID,\n \t\tGIT_SHA256_RAWSZ,\n \t\tGIT_SHA256_HEXSZ,\n \t\tGIT_SHA256_BLKSZ,\n-- \ngitgitgadget\n\n"},{"id":"438202","messageId":"6a5ee5db472ac33e8a113b348354e57cea6ec94d.1633638315.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 02/19] reftable: add LICENSE","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:24:58Z","receivedAt":"2021-10-07T20:25:25Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe objective of this code is to be usable as a C library, so it can be reused\nin libgit2.\n\nThis is currently using a BSD license as it is the liberal license I could find,\nbut this could be changed to whatever fits the stated goal above.\n\nThis code is currently imported from github.com/hanwen/reftable. Once this code\nlands in git.git, the C code will be removed from github.com/hanwen/reftable,\nand the git.git code will be the source of truth.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/LICENSE | 31 +++++++++++++++++++++++++++++++\n 1 file changed, 31 insertions(+)\n create mode 100644 reftable/LICENSE\n\ndiff --git a/reftable/LICENSE b/reftable/LICENSE\nnew file mode 100644\nindex 00000000000..402e0f9356b\n--- /dev/null\n+++ b/reftable/LICENSE\n@@ -0,0 +1,31 @@\n+BSD License\n+\n+Copyright (c) 2020, Google LLC\n+All rights reserved.\n+\n+Redistribution and use in source and binary forms, with or without\n+modification, are permitted provided that the following conditions are\n+met:\n+\n+* Redistributions of source code must retain the above copyright notice,\n+this list of conditions and the following disclaimer.\n+\n+* Redistributions in binary form must reproduce the above copyright\n+notice, this list of conditions and the following disclaimer in the\n+documentation and/or other materials provided with the distribution.\n+\n+* Neither the name of Google LLC nor the names of its contributors may\n+be used to endorse or promote products derived from this software\n+without specific prior written permission.\n+\n+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n-- \ngitgitgadget\n\n"},{"id":"438203","messageId":"bf51bc9ab0c724600806085ed387eb743161e250.1633638315.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 03/19] reftable: add error related functionality","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:24:59Z","receivedAt":"2021-10-07T20:25:28Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable/ directory is structured as a library, so it cannot\ncrash on misuse. Instead, it returns an error code.\n\nIn addition to signaling errors, the error code can be used to signal\nconditions from lower levels of the library to be handled by higher\nlevels of the library. For example, in a transaction we might\nlegitimately write an empty reftable file, but in that case, we want to\nshortcut the transaction.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n reftable/error.c          | 41 ++++++++++++++++++++++++++\n reftable/reftable-error.h | 62 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 103 insertions(+)\n create mode 100644 reftable/error.c\n create mode 100644 reftable/reftable-error.h\n\ndiff --git a/reftable/error.c b/reftable/error.c\nnew file mode 100644\nindex 00000000000..f6f16def921\n--- /dev/null\n+++ b/reftable/error.c\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-error.h\"\n+\n+#include <stdio.h>\n+\n+const char *reftable_error_str(int err)\n+{\n+\tstatic char buf[250];\n+\tswitch (err) {\n+\tcase REFTABLE_IO_ERROR:\n+\t\treturn \"I/O error\";\n+\tcase REFTABLE_FORMAT_ERROR:\n+\t\treturn \"corrupt reftable file\";\n+\tcase REFTABLE_NOT_EXIST_ERROR:\n+\t\treturn \"file does not exist\";\n+\tcase REFTABLE_LOCK_ERROR:\n+\t\treturn \"data is outdated\";\n+\tcase REFTABLE_API_ERROR:\n+\t\treturn \"misuse of the reftable API\";\n+\tcase REFTABLE_ZLIB_ERROR:\n+\t\treturn \"zlib failure\";\n+\tcase REFTABLE_NAME_CONFLICT:\n+\t\treturn \"file/directory conflict\";\n+\tcase REFTABLE_EMPTY_TABLE_ERROR:\n+\t\treturn \"wrote empty table\";\n+\tcase REFTABLE_REFNAME_ERROR:\n+\t\treturn \"invalid refname\";\n+\tcase -1:\n+\t\treturn \"general error\";\n+\tdefault:\n+\t\tsnprintf(buf, sizeof(buf), \"unknown error code %d\", err);\n+\t\treturn buf;\n+\t}\n+}\ndiff --git a/reftable/reftable-error.h b/reftable/reftable-error.h\nnew file mode 100644\nindex 00000000000..6f89bedf1a5\n--- /dev/null\n+++ b/reftable/reftable-error.h\n@@ -0,0 +1,62 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ERROR_H\n+#define REFTABLE_ERROR_H\n+\n+/*\n+ * Errors in reftable calls are signaled with negative integer return values. 0\n+ * means success.\n+ */\n+enum reftable_error {\n+\t/* Unexpected file system behavior */\n+\tREFTABLE_IO_ERROR = -2,\n+\n+\t/* Format inconsistency on reading data */\n+\tREFTABLE_FORMAT_ERROR = -3,\n+\n+\t/* File does not exist. Returned from block_source_from_file(), because\n+\t * it needs special handling in stack.\n+\t */\n+\tREFTABLE_NOT_EXIST_ERROR = -4,\n+\n+\t/* Trying to write out-of-date data. */\n+\tREFTABLE_LOCK_ERROR = -5,\n+\n+\t/* Misuse of the API:\n+\t *  - on writing a record with NULL refname.\n+\t *  - on writing a reftable_ref_record outside the table limits\n+\t *  - on writing a ref or log record before the stack's\n+\t * next_update_inde*x\n+\t *  - on writing a log record with multiline message with\n+\t *  exact_log_message unset\n+\t *  - on reading a reftable_ref_record from log iterator, or vice versa.\n+\t *\n+\t * When a call misuses the API, the internal state of the library is\n+\t * kept unchanged.\n+\t */\n+\tREFTABLE_API_ERROR = -6,\n+\n+\t/* Decompression error */\n+\tREFTABLE_ZLIB_ERROR = -7,\n+\n+\t/* Wrote a table without blocks. */\n+\tREFTABLE_EMPTY_TABLE_ERROR = -8,\n+\n+\t/* Dir/file conflict. */\n+\tREFTABLE_NAME_CONFLICT = -9,\n+\n+\t/* Invalid ref name. */\n+\tREFTABLE_REFNAME_ERROR = -10,\n+};\n+\n+/* convert the numeric error code to a string. The string should not be\n+ * deallocated. */\n+const char *reftable_error_str(int err);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"438204","messageId":"318e7ad41e50aba1f12a3e3f220606aebd77be3d.1633638315.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 04/19] reftable: utility functions","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:00Z","receivedAt":"2021-10-07T20:25:30Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis commit provides basic utility classes for the reftable library.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                                   |  25 +++-\n contrib/buildsystems/CMakeLists.txt        |  14 ++-\n contrib/buildsystems/Generators/Vcxproj.pm |  11 +-\n reftable/basics.c                          | 128 +++++++++++++++++++++\n reftable/basics.h                          |  60 ++++++++++\n reftable/basics_test.c                     |  98 ++++++++++++++++\n reftable/publicbasics.c                    |  65 +++++++++++\n reftable/reftable-malloc.h                 |  18 +++\n reftable/reftable-tests.h                  |  22 ++++\n reftable/system.h                          |  32 ++++++\n reftable/test_framework.c                  |  23 ++++\n reftable/test_framework.h                  |  53 +++++++++\n t/helper/test-reftable.c                   |   9 ++\n t/helper/test-tool.c                       |   3 +-\n t/helper/test-tool.h                       |   1 +\n t/t0032-reftable-unittest.sh               |  15 +++\n 16 files changed, 570 insertions(+), 7 deletions(-)\n create mode 100644 reftable/basics.c\n create mode 100644 reftable/basics.h\n create mode 100644 reftable/basics_test.c\n create mode 100644 reftable/publicbasics.c\n create mode 100644 reftable/reftable-malloc.h\n create mode 100644 reftable/reftable-tests.h\n create mode 100644 reftable/system.h\n create mode 100644 reftable/test_framework.c\n create mode 100644 reftable/test_framework.h\n create mode 100644 t/helper/test-reftable.c\n create mode 100755 t/t0032-reftable-unittest.sh\n\ndiff --git a/Makefile b/Makefile\nindex e04c942f58f..aa5ac56cde6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -736,6 +736,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o\n TEST_BUILTINS_OBJS += test-read-graph.o\n TEST_BUILTINS_OBJS += test-read-midx.o\n TEST_BUILTINS_OBJS += test-ref-store.o\n+TEST_BUILTINS_OBJS += test-reftable.o\n TEST_BUILTINS_OBJS += test-regex.o\n TEST_BUILTINS_OBJS += test-repository.o\n TEST_BUILTINS_OBJS += test-revision-walking.o\n@@ -814,6 +815,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n XDIFF_LIB = xdiff/lib.a\n+REFTABLE_LIB = reftable/libreftable.a\n+REFTABLE_TEST_LIB = reftable/libreftable_test.a\n \n GENERATED_H += command-list.h\n GENERATED_H += config-list.h\n@@ -1188,7 +1191,7 @@ THIRD_PARTY_SOURCES += compat/regex/%\n THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)\n+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)\n EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2426,7 +2429,15 @@ XDIFF_OBJS += xdiff/xutils.o\n .PHONY: xdiff-objs\n xdiff-objs: $(XDIFF_OBJS)\n \n+REFTABLE_OBJS += reftable/basics.o\n+REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/publicbasics.o\n+\n+REFTABLE_TEST_OBJS += reftable/test_framework.o\n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n+\n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n+\n .PHONY: test-objs\n test-objs: $(TEST_OBJS)\n \n@@ -2442,6 +2453,8 @@ OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n OBJECTS += $(XDIFF_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n+OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n+\n ifndef NO_CURL\n \tOBJECTS += http.o http-walker.o remote-curl.o\n endif\n@@ -2591,6 +2604,12 @@ $(LIB_FILE): $(LIB_OBJS)\n $(XDIFF_LIB): $(XDIFF_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n+$(REFTABLE_LIB): $(REFTABLE_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n export DEFAULT_EDITOR DEFAULT_PAGER\n \n Documentation/GIT-EXCLUDED-PROGRAMS: FORCE\n@@ -2889,7 +2908,7 @@ perf: all\n \n t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \n-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)\n+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n \n check-sha1:: t/helper/test-tool$X\n@@ -3224,7 +3243,7 @@ cocciclean:\n clean: profile-clean coverage-clean cocciclean\n \t$(RM) *.res\n \t$(RM) $(OBJECTS)\n-\t$(RM) $(LIB_FILE) $(XDIFF_LIB)\n+\t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 171b4124afe..c2bf5bdffc6 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -640,6 +640,12 @@ parse_makefile_for_sources(libxdiff_SOURCES \"XDIFF_OBJS\")\n list(TRANSFORM libxdiff_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n add_library(xdiff STATIC ${libxdiff_SOURCES})\n \n+#reftable\n+parse_makefile_for_sources(reftable_SOURCES \"REFTABLE_OBJS\")\n+\n+list(TRANSFORM reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+add_library(reftable STATIC ${reftable_SOURCES})\n+\n if(WIN32)\n \tif(NOT MSVC)#use windres when compiling with gcc and clang\n \t\tadd_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res\n@@ -662,7 +668,7 @@ endif()\n #link all required libraries to common-main\n add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)\n \n-target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})\n+target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})\n if(Intl_FOUND)\n \ttarget_link_libraries(common-main ${Intl_LIBRARIES})\n endif()\n@@ -902,11 +908,15 @@ if(BUILD_TESTING)\n add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)\n target_link_libraries(test-fake-ssh common-main)\n \n+#reftable-tests\n+parse_makefile_for_sources(test-reftable_SOURCES \"REFTABLE_TEST_OBJS\")\n+list(TRANSFORM test-reftable_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/\")\n+\n #test-tool\n parse_makefile_for_sources(test-tool_SOURCES \"TEST_BUILTINS_OBJS\")\n \n list(TRANSFORM test-tool_SOURCES PREPEND \"${CMAKE_SOURCE_DIR}/t/helper/\")\n-add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})\n+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})\n target_link_libraries(test-tool common-main)\n \n set_target_properties(test-fake-ssh test-tool\ndiff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm\nindex d2584450ba1..1a25789d285 100644\n--- a/contrib/buildsystems/Generators/Vcxproj.pm\n+++ b/contrib/buildsystems/Generators/Vcxproj.pm\n@@ -77,7 +77,7 @@ sub createProject {\n     my $libs_release = \"\\n    \";\n     my $libs_debug = \"\\n    \";\n     if (!$static_library) {\n-      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n+      $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib|reftable\\/libreftable\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n       $libs_debug = $libs_release;\n       $libs_debug =~ s/zlib\\.lib/zlibd\\.lib/g;\n       $libs_debug =~ s/libexpat\\.lib/libexpatd\\.lib/g;\n@@ -232,6 +232,7 @@ EOM\n EOM\n     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {\n       my $uuid_libgit = $$build_structure{\"LIBS_libgit_GUID\"};\n+      my $uuid_libreftable = $$build_structure{\"LIBS_reftable/libreftable_GUID\"};\n       my $uuid_xdiff_lib = $$build_structure{\"LIBS_xdiff/lib_GUID\"};\n \n       print F << \"EOM\";\n@@ -241,6 +242,14 @@ EOM\n       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n     </ProjectReference>\n EOM\n+      if (!($name =~ /xdiff|libreftable/)) {\n+        print F << \"EOM\";\n+    <ProjectReference Include=\"$cdup\\\\reftable\\\\libreftable\\\\libreftable.vcxproj\">\n+      <Project>$uuid_libreftable</Project>\n+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n+    </ProjectReference>\n+EOM\n+      }\n       if (!($name =~ 'xdiff')) {\n         print F << \"EOM\";\n     <ProjectReference Include=\"$cdup\\\\xdiff\\\\lib\\\\xdiff_lib.vcxproj\">\ndiff --git a/reftable/basics.c b/reftable/basics.c\nnew file mode 100644\nindex 00000000000..f761e48028c\n--- /dev/null\n+++ b/reftable/basics.c\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+\n+void put_be24(uint8_t *out, uint32_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 16) & 0xff);\n+\tout[1] = (uint8_t)((i >> 8) & 0xff);\n+\tout[2] = (uint8_t)(i & 0xff);\n+}\n+\n+uint32_t get_be24(uint8_t *in)\n+{\n+\treturn (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |\n+\t       (uint32_t)(in[2]);\n+}\n+\n+void put_be16(uint8_t *out, uint16_t i)\n+{\n+\tout[0] = (uint8_t)((i >> 8) & 0xff);\n+\tout[1] = (uint8_t)(i & 0xff);\n+}\n+\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)\n+{\n+\tsize_t lo = 0;\n+\tsize_t hi = sz;\n+\n+\t/* Invariants:\n+\t *\n+\t *  (hi == sz) || f(hi) == true\n+\t *  (lo == 0 && f(0) == true) || fi(lo) == false\n+\t */\n+\twhile (hi - lo > 1) {\n+\t\tsize_t mid = lo + (hi - lo) / 2;\n+\n+\t\tif (f(mid, args))\n+\t\t\thi = mid;\n+\t\telse\n+\t\t\tlo = mid;\n+\t}\n+\n+\tif (lo)\n+\t\treturn hi;\n+\n+\treturn f(0, args) ? 0 : 1;\n+}\n+\n+void free_names(char **a)\n+{\n+\tchar **p;\n+\tif (!a) {\n+\t\treturn;\n+\t}\n+\tfor (p = a; *p; p++) {\n+\t\treftable_free(*p);\n+\t}\n+\treftable_free(a);\n+}\n+\n+int names_length(char **names)\n+{\n+\tchar **p = names;\n+\tfor (; *p; p++) {\n+\t\t/* empty */\n+\t}\n+\treturn p - names;\n+}\n+\n+void parse_names(char *buf, int size, char ***namesp)\n+{\n+\tchar **names = NULL;\n+\tsize_t names_cap = 0;\n+\tsize_t names_len = 0;\n+\n+\tchar *p = buf;\n+\tchar *end = buf + size;\n+\twhile (p < end) {\n+\t\tchar *next = strchr(p, '\\n');\n+\t\tif (next && next < end) {\n+\t\t\t*next = 0;\n+\t\t} else {\n+\t\t\tnext = end;\n+\t\t}\n+\t\tif (p < next) {\n+\t\t\tif (names_len == names_cap) {\n+\t\t\t\tnames_cap = 2 * names_cap + 1;\n+\t\t\t\tnames = reftable_realloc(\n+\t\t\t\t\tnames, names_cap * sizeof(*names));\n+\t\t\t}\n+\t\t\tnames[names_len++] = xstrdup(p);\n+\t\t}\n+\t\tp = next + 1;\n+\t}\n+\n+\tnames = reftable_realloc(names, (names_len + 1) * sizeof(*names));\n+\tnames[names_len] = NULL;\n+\t*namesp = names;\n+}\n+\n+int names_equal(char **a, char **b)\n+{\n+\tint i = 0;\n+\tfor (; a[i] && b[i]; i++) {\n+\t\tif (strcmp(a[i], b[i])) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn a[i] == b[i];\n+}\n+\n+int common_prefix_size(struct strbuf *a, struct strbuf *b)\n+{\n+\tint p = 0;\n+\tfor (; p < a->len && p < b->len; p++) {\n+\t\tif (a->buf[p] != b->buf[p])\n+\t\t\tbreak;\n+\t}\n+\n+\treturn p;\n+}\ndiff --git a/reftable/basics.h b/reftable/basics.h\nnew file mode 100644\nindex 00000000000..096b36862b9\n--- /dev/null\n+++ b/reftable/basics.h\n@@ -0,0 +1,60 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BASICS_H\n+#define BASICS_H\n+\n+/*\n+ * miscellaneous utilities that are not provided by Git.\n+ */\n+\n+#include \"system.h\"\n+\n+/* Bigendian en/decoding of integers */\n+\n+void put_be24(uint8_t *out, uint32_t i);\n+uint32_t get_be24(uint8_t *in);\n+void put_be16(uint8_t *out, uint16_t i);\n+\n+/*\n+ * find smallest index i in [0, sz) at which f(i) is true, assuming\n+ * that f is ascending. Return sz if f(i) is false for all indices.\n+ *\n+ * Contrary to bsearch(3), this returns something useful if the argument is not\n+ * found.\n+ */\n+int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);\n+\n+/*\n+ * Frees a NULL terminated array of malloced strings. The array itself is also\n+ * freed.\n+ */\n+void free_names(char **a);\n+\n+/* parse a newline separated list of names. `size` is the length of the buffer,\n+ * without terminating '\\0'. Empty names are discarded. */\n+void parse_names(char *buf, int size, char ***namesp);\n+\n+/* compares two NULL-terminated arrays of strings. */\n+int names_equal(char **a, char **b);\n+\n+/* returns the array size of a NULL-terminated array of strings. */\n+int names_length(char **names);\n+\n+/* Allocation routines; they invoke the functions set through\n+ * reftable_set_alloc() */\n+void *reftable_malloc(size_t sz);\n+void *reftable_realloc(void *p, size_t sz);\n+void reftable_free(void *p);\n+void *reftable_calloc(size_t sz);\n+\n+/* Find the longest shared prefix size of `a` and `b` */\n+struct strbuf;\n+int common_prefix_size(struct strbuf *a, struct strbuf *b);\n+\n+#endif\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nnew file mode 100644\nindex 00000000000..1fcd2297256\n--- /dev/null\n+++ b/reftable/basics_test.c\n@@ -0,0 +1,98 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct binsearch_args {\n+\tint key;\n+\tint *arr;\n+};\n+\n+static int binsearch_func(size_t i, void *void_args)\n+{\n+\tstruct binsearch_args *args = void_args;\n+\n+\treturn args->key < args->arr[i];\n+}\n+\n+static void test_binsearch(void)\n+{\n+\tint arr[] = { 2, 4, 6, 8, 10 };\n+\tsize_t sz = ARRAY_SIZE(arr);\n+\tstruct binsearch_args args = {\n+\t\t.arr = arr,\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 1; i < 11; i++) {\n+\t\tint res;\n+\t\targs.key = i;\n+\t\tres = binsearch(sz, &binsearch_func, &args);\n+\n+\t\tif (res < sz) {\n+\t\t\tEXPECT(args.key < arr[res]);\n+\t\t\tif (res > 0) {\n+\t\t\t\tEXPECT(args.key >= arr[res - 1]);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tEXPECT(args.key == 10 || args.key == 11);\n+\t\t}\n+\t}\n+}\n+\n+static void test_names_length(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(a) == 2);\n+}\n+\n+static void test_parse_names_normal(void)\n+{\n+\tchar in[] = \"a\\nb\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!strcmp(out[1], \"b\"));\n+\tEXPECT(!out[2]);\n+\tfree_names(out);\n+}\n+\n+static void test_parse_names_drop_empty(void)\n+{\n+\tchar in[] = \"a\\n\\n\";\n+\tchar **out = NULL;\n+\tparse_names(in, strlen(in), &out);\n+\tEXPECT(!strcmp(out[0], \"a\"));\n+\tEXPECT(!out[1]);\n+\tfree_names(out);\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct strbuf s1 = STRBUF_INIT;\n+\tstruct strbuf s2 = STRBUF_INIT;\n+\tstrbuf_addstr(&s1, \"abcdef\");\n+\tstrbuf_addstr(&s2, \"abc\");\n+\tEXPECT(common_prefix_size(&s1, &s2) == 3);\n+\tstrbuf_release(&s1);\n+\tstrbuf_release(&s2);\n+}\n+\n+int basics_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_parse_names_normal);\n+\tRUN_TEST(test_parse_names_drop_empty);\n+\tRUN_TEST(test_binsearch);\n+\tRUN_TEST(test_names_length);\n+\treturn 0;\n+}\ndiff --git a/reftable/publicbasics.c b/reftable/publicbasics.c\nnew file mode 100644\nindex 00000000000..0ad7d5c0ff2\n--- /dev/null\n+++ b/reftable/publicbasics.c\n@@ -0,0 +1,65 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reftable-malloc.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+static void *(*reftable_malloc_ptr)(size_t sz);\n+static void *(*reftable_realloc_ptr)(void *, size_t);\n+static void (*reftable_free_ptr)(void *);\n+\n+void *reftable_malloc(size_t sz)\n+{\n+\tif (reftable_malloc_ptr)\n+\t\treturn (*reftable_malloc_ptr)(sz);\n+\treturn malloc(sz);\n+}\n+\n+void *reftable_realloc(void *p, size_t sz)\n+{\n+\tif (reftable_realloc_ptr)\n+\t\treturn (*reftable_realloc_ptr)(p, sz);\n+\treturn realloc(p, sz);\n+}\n+\n+void reftable_free(void *p)\n+{\n+\tif (reftable_free_ptr)\n+\t\treftable_free_ptr(p);\n+\telse\n+\t\tfree(p);\n+}\n+\n+void *reftable_calloc(size_t sz)\n+{\n+\tvoid *p = reftable_malloc(sz);\n+\tmemset(p, 0, sz);\n+\treturn p;\n+}\n+\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *))\n+{\n+\treftable_malloc_ptr = malloc;\n+\treftable_realloc_ptr = realloc;\n+\treftable_free_ptr = free;\n+}\n+\n+int hash_size(uint32_t id)\n+{\n+\tswitch (id) {\n+\tcase 0:\n+\tcase GIT_SHA1_FORMAT_ID:\n+\t\treturn GIT_SHA1_RAWSZ;\n+\tcase GIT_SHA256_FORMAT_ID:\n+\t\treturn GIT_SHA256_RAWSZ;\n+\t}\n+\tabort();\n+}\ndiff --git a/reftable/reftable-malloc.h b/reftable/reftable-malloc.h\nnew file mode 100644\nindex 00000000000..5f2185f1f34\n--- /dev/null\n+++ b/reftable/reftable-malloc.h\n@@ -0,0 +1,18 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_H\n+#define REFTABLE_H\n+\n+#include <stddef.h>\n+\n+/* Overrides the functions to use for memory management. */\n+void reftable_set_alloc(void *(*malloc)(size_t),\n+\t\t\tvoid *(*realloc)(void *, size_t), void (*free)(void *));\n+\n+#endif\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nnew file mode 100644\nindex 00000000000..5e7698ae654\n--- /dev/null\n+++ b/reftable/reftable-tests.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_TESTS_H\n+#define REFTABLE_TESTS_H\n+\n+int basics_test_main(int argc, const char **argv);\n+int block_test_main(int argc, const char **argv);\n+int merged_test_main(int argc, const char **argv);\n+int record_test_main(int argc, const char **argv);\n+int refname_test_main(int argc, const char **argv);\n+int reftable_test_main(int argc, const char **argv);\n+int stack_test_main(int argc, const char **argv);\n+int tree_test_main(int argc, const char **argv);\n+int reftable_dump_main(int argc, char *const *argv);\n+\n+#endif\ndiff --git a/reftable/system.h b/reftable/system.h\nnew file mode 100644\nindex 00000000000..4907306c0c5\n--- /dev/null\n+++ b/reftable/system.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef SYSTEM_H\n+#define SYSTEM_H\n+\n+/* This header glues the reftable library to the rest of Git */\n+\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"hash.h\" /* hash ID, sizes.*/\n+#include \"dir.h\" /* remove_dir_recursively, for tests.*/\n+\n+#include <zlib.h>\n+\n+#ifdef NO_UNCOMPRESS2\n+/*\n+ * This is uncompress2, which is only available in zlib >= 1.2.9\n+ * (released as of early 2017)\n+ */\n+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,\n+\t\tuLong *sourceLen);\n+#endif\n+\n+int hash_size(uint32_t id);\n+\n+#endif\ndiff --git a/reftable/test_framework.c b/reftable/test_framework.c\nnew file mode 100644\nindex 00000000000..84ac972cad0\n--- /dev/null\n+++ b/reftable/test_framework.c\n@@ -0,0 +1,23 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"test_framework.h\"\n+\n+#include \"basics.h\"\n+\n+void set_test_hash(uint8_t *p, int i)\n+{\n+\tmemset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID));\n+}\n+\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz)\n+{\n+\tstrbuf_add(b, data, sz);\n+\treturn sz;\n+}\ndiff --git a/reftable/test_framework.h b/reftable/test_framework.h\nnew file mode 100644\nindex 00000000000..774cb275bf6\n--- /dev/null\n+++ b/reftable/test_framework.h\n@@ -0,0 +1,53 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TEST_FRAMEWORK_H\n+#define TEST_FRAMEWORK_H\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+\n+#define EXPECT_ERR(c)                                                  \\\n+\tif (c != 0) {                                                  \\\n+\t\tfflush(stderr);                                        \\\n+\t\tfflush(stdout);                                        \\\n+\t\tfprintf(stderr, \"%s: %d: error == %d (%s), want 0\\n\",  \\\n+\t\t\t__FILE__, __LINE__, c, reftable_error_str(c)); \\\n+\t\tabort();                                               \\\n+\t}\n+\n+#define EXPECT_STREQ(a, b)                                               \\\n+\tif (strcmp(a, b)) {                                              \\\n+\t\tfflush(stderr);                                          \\\n+\t\tfflush(stdout);                                          \\\n+\t\tfprintf(stderr, \"%s:%d: %s (%s) != %s (%s)\\n\", __FILE__, \\\n+\t\t\t__LINE__, #a, a, #b, b);                         \\\n+\t\tabort();                                                 \\\n+\t}\n+\n+#define EXPECT(c)                                                          \\\n+\tif (!(c)) {                                                        \\\n+\t\tfflush(stderr);                                            \\\n+\t\tfflush(stdout);                                            \\\n+\t\tfprintf(stderr, \"%s: %d: failed assertion %s\\n\", __FILE__, \\\n+\t\t\t__LINE__, #c);                                     \\\n+\t\tabort();                                                   \\\n+\t}\n+\n+#define RUN_TEST(f)                          \\\n+\tfprintf(stderr, \"running %s\\n\", #f); \\\n+\tfflush(stderr);                      \\\n+\tf();\n+\n+void set_test_hash(uint8_t *p, int i);\n+\n+/* Like strbuf_add, but suitable for passing to reftable_new_writer\n+ */\n+ssize_t strbuf_add_void(void *b, const void *data, size_t sz);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nnew file mode 100644\nindex 00000000000..3b58e423e7b\n--- /dev/null\n+++ b/t/helper/test-reftable.c\n@@ -0,0 +1,9 @@\n+#include \"reftable/reftable-tests.h\"\n+#include \"test-tool.h\"\n+\n+int cmd__reftable(int argc, const char **argv)\n+{\n+\tbasics_test_main(argc, argv);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 3ce5585e53a..f7c888ffda7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -53,13 +53,14 @@ static struct test_cmd cmds[] = {\n \t{ \"pcre2-config\", cmd__pcre2_config },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n-\t{ \"proc-receive\", cmd__proc_receive},\n+\t{ \"proc-receive\", cmd__proc_receive },\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\n \t{ \"read-graph\", cmd__read_graph },\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n+\t{ \"reftable\", cmd__reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 9f0f5228508..25f77469146 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -49,6 +49,7 @@ int cmd__read_cache(int argc, const char **argv);\n int cmd__read_graph(int argc, const char **argv);\n int cmd__read_midx(int argc, const char **argv);\n int cmd__ref_store(int argc, const char **argv);\n+int cmd__reftable(int argc, const char **argv);\n int cmd__regex(int argc, const char **argv);\n int cmd__repository(int argc, const char **argv);\n int cmd__revision_walking(int argc, const char **argv);\ndiff --git a/t/t0032-reftable-unittest.sh b/t/t0032-reftable-unittest.sh\nnew file mode 100755\nindex 00000000000..0ed14971a58\n--- /dev/null\n+++ b/t/t0032-reftable-unittest.sh\n@@ -0,0 +1,15 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Google LLC\n+#\n+\n+test_description='reftable unittests'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'unittests' '\n+\tTMPDIR=$(pwd) && export TMPDIR &&\n+\ttest-tool reftable\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"438205","messageId":"80036f93e2e9b7823e49ee2764344f2db55ed768.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 05/19] reftable: add blocksource, an abstraction for random access reads","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:01Z","receivedAt":"2021-10-07T20:25:31Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is usually used with files for storage. However, we abstract\naway this using the blocksource data structure. This has two advantages:\n\n* log blocks are zlib compressed, and handling them is simplified if we can\n  discard byte segments from within the block layer.\n\n* for unittests, it is useful to read and write in-memory. The blocksource\n  allows us to abstract the data away from on-disk files.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                        |   1 +\n reftable/blocksource.c          | 148 ++++++++++++++++++++++++++++++++\n reftable/blocksource.h          |  22 +++++\n reftable/reftable-blocksource.h |  49 +++++++++++\n 4 files changed, 220 insertions(+)\n create mode 100644 reftable/blocksource.c\n create mode 100644 reftable/blocksource.h\n create mode 100644 reftable/reftable-blocksource.h\n\ndiff --git a/Makefile b/Makefile\nindex aa5ac56cde6..bfdac1e04c4 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2431,6 +2431,7 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n \n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/blocksource.c b/reftable/blocksource.c\nnew file mode 100644\nindex 00000000000..0044eecd9aa\n--- /dev/null\n+++ b/reftable/blocksource.c\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+\n+static void strbuf_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void strbuf_close(void *b)\n+{\n+}\n+\n+static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t     uint32_t size)\n+{\n+\tstruct strbuf *b = v;\n+\tassert(off + size <= b->len);\n+\tdest->data = reftable_calloc(size);\n+\tmemcpy(dest->data, b->buf + off, size);\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static uint64_t strbuf_size(void *b)\n+{\n+\treturn ((struct strbuf *)b)->len;\n+}\n+\n+static struct reftable_block_source_vtable strbuf_vtable = {\n+\t.size = &strbuf_size,\n+\t.read_block = &strbuf_read_block,\n+\t.return_block = &strbuf_return_block,\n+\t.close = &strbuf_close,\n+};\n+\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf)\n+{\n+\tassert(!bs->ops);\n+\tbs->ops = &strbuf_vtable;\n+\tbs->arg = buf;\n+}\n+\n+static void malloc_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static struct reftable_block_source_vtable malloc_vtable = {\n+\t.return_block = &malloc_return_block,\n+};\n+\n+static struct reftable_block_source malloc_block_source_instance = {\n+\t.ops = &malloc_vtable,\n+};\n+\n+struct reftable_block_source malloc_block_source(void)\n+{\n+\treturn malloc_block_source_instance;\n+}\n+\n+struct file_block_source {\n+\tint fd;\n+\tuint64_t size;\n+};\n+\n+static uint64_t file_size(void *b)\n+{\n+\treturn ((struct file_block_source *)b)->size;\n+}\n+\n+static void file_return_block(void *b, struct reftable_block *dest)\n+{\n+\tmemset(dest->data, 0xff, dest->len);\n+\treftable_free(dest->data);\n+}\n+\n+static void file_close(void *b)\n+{\n+\tint fd = ((struct file_block_source *)b)->fd;\n+\tif (fd > 0) {\n+\t\tclose(fd);\n+\t\t((struct file_block_source *)b)->fd = 0;\n+\t}\n+\n+\treftable_free(b);\n+}\n+\n+static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,\n+\t\t\t   uint32_t size)\n+{\n+\tstruct file_block_source *b = v;\n+\tassert(off + size <= b->size);\n+\tdest->data = reftable_malloc(size);\n+\tif (pread(b->fd, dest->data, size, off) != size)\n+\t\treturn -1;\n+\tdest->len = size;\n+\treturn size;\n+}\n+\n+static struct reftable_block_source_vtable file_vtable = {\n+\t.size = &file_size,\n+\t.read_block = &file_read_block,\n+\t.return_block = &file_return_block,\n+\t.close = &file_close,\n+};\n+\n+int reftable_block_source_from_file(struct reftable_block_source *bs,\n+\t\t\t\t    const char *name)\n+{\n+\tstruct stat st = { 0 };\n+\tint err = 0;\n+\tint fd = open(name, O_RDONLY);\n+\tstruct file_block_source *p = NULL;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\treturn REFTABLE_NOT_EXIST_ERROR;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\terr = fstat(fd, &st);\n+\tif (err < 0)\n+\t\treturn -1;\n+\n+\tp = reftable_calloc(sizeof(struct file_block_source));\n+\tp->size = st.st_size;\n+\tp->fd = fd;\n+\n+\tassert(!bs->ops);\n+\tbs->ops = &file_vtable;\n+\tbs->arg = p;\n+\treturn 0;\n+}\ndiff --git a/reftable/blocksource.h b/reftable/blocksource.h\nnew file mode 100644\nindex 00000000000..072e2727ad2\n--- /dev/null\n+++ b/reftable/blocksource.h\n@@ -0,0 +1,22 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCKSOURCE_H\n+#define BLOCKSOURCE_H\n+\n+#include \"system.h\"\n+\n+struct reftable_block_source;\n+\n+/* Create an in-memory block source for reading reftables */\n+void block_source_from_strbuf(struct reftable_block_source *bs,\n+\t\t\t      struct strbuf *buf);\n+\n+struct reftable_block_source malloc_block_source(void);\n+\n+#endif\ndiff --git a/reftable/reftable-blocksource.h b/reftable/reftable-blocksource.h\nnew file mode 100644\nindex 00000000000..5aa3990a573\n--- /dev/null\n+++ b/reftable/reftable-blocksource.h\n@@ -0,0 +1,49 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_BLOCKSOURCE_H\n+#define REFTABLE_BLOCKSOURCE_H\n+\n+#include <stdint.h>\n+\n+/* block_source is a generic wrapper for a seekable readable file.\n+ */\n+struct reftable_block_source {\n+\tstruct reftable_block_source_vtable *ops;\n+\tvoid *arg;\n+};\n+\n+/* a contiguous segment of bytes. It keeps track of its generating block_source\n+ * so it can return itself into the pool. */\n+struct reftable_block {\n+\tuint8_t *data;\n+\tint len;\n+\tstruct reftable_block_source source;\n+};\n+\n+/* block_source_vtable are the operations that make up block_source */\n+struct reftable_block_source_vtable {\n+\t/* returns the size of a block source */\n+\tuint64_t (*size)(void *source);\n+\n+\t/* reads a segment from the block source. It is an error to read\n+\t   beyond the end of the block */\n+\tint (*read_block)(void *source, struct reftable_block *dest,\n+\t\t\t  uint64_t off, uint32_t size);\n+\t/* mark the block as read; may return the data back to malloc */\n+\tvoid (*return_block)(void *source, struct reftable_block *blockp);\n+\n+\t/* release all resources associated with the block source */\n+\tvoid (*close)(void *source);\n+};\n+\n+/* opens a file on the file system as a block_source */\n+int reftable_block_source_from_file(struct reftable_block_source *block_src,\n+\t\t\t\t    const char *name);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"438206","messageId":"96e490ac32ccde5c9a4eae5f344ecd7f7c941923.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 09/19] reftable: a generic binary tree implementation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:05Z","receivedAt":"2021-10-07T20:25:32Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format includes support for an (OID => ref) map. This map can speed\nup visibility and reachability checks. In particular, various operations along\nthe fetch/push path within Gerrit have ben sped up by using this structure.\n\nThe map is constructed with help of a binary tree. Object IDs are hashes, so\nthey are uniformly distributed. Hence, the tree does not attempt forced\nrebalancing.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |  4 ++-\n reftable/tree.c          | 63 ++++++++++++++++++++++++++++++++++++++++\n reftable/tree.h          | 34 ++++++++++++++++++++++\n reftable/tree_test.c     | 61 ++++++++++++++++++++++++++++++++++++++\n t/helper/test-reftable.c |  1 +\n 5 files changed, 162 insertions(+), 1 deletion(-)\n create mode 100644 reftable/tree.c\n create mode 100644 reftable/tree.h\n create mode 100644 reftable/tree_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 153db9dff11..d8d4bb0143b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2442,11 +2442,13 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/tree.o\n \n+REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n-REFTABLE_TEST_OBJS += reftable/basics_test.o\n+REFTABLE_TEST_OBJS += reftable/tree_test.o\n \n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \ndiff --git a/reftable/tree.c b/reftable/tree.c\nnew file mode 100644\nindex 00000000000..82db7995dd6\n--- /dev/null\n+++ b/reftable/tree.c\n@@ -0,0 +1,63 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"system.h\"\n+\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert)\n+{\n+\tint res;\n+\tif (*rootp == NULL) {\n+\t\tif (!insert) {\n+\t\t\treturn NULL;\n+\t\t} else {\n+\t\t\tstruct tree_node *n =\n+\t\t\t\treftable_calloc(sizeof(struct tree_node));\n+\t\t\tn->key = key;\n+\t\t\t*rootp = n;\n+\t\t\treturn *rootp;\n+\t\t}\n+\t}\n+\n+\tres = compare(key, (*rootp)->key);\n+\tif (res < 0)\n+\t\treturn tree_search(key, &(*rootp)->left, compare, insert);\n+\telse if (res > 0)\n+\t\treturn tree_search(key, &(*rootp)->right, compare, insert);\n+\treturn *rootp;\n+}\n+\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg)\n+{\n+\tif (t->left) {\n+\t\tinfix_walk(t->left, action, arg);\n+\t}\n+\taction(arg, t->key);\n+\tif (t->right) {\n+\t\tinfix_walk(t->right, action, arg);\n+\t}\n+}\n+\n+void tree_free(struct tree_node *t)\n+{\n+\tif (t == NULL) {\n+\t\treturn;\n+\t}\n+\tif (t->left) {\n+\t\ttree_free(t->left);\n+\t}\n+\tif (t->right) {\n+\t\ttree_free(t->right);\n+\t}\n+\treftable_free(t);\n+}\ndiff --git a/reftable/tree.h b/reftable/tree.h\nnew file mode 100644\nindex 00000000000..fbdd002e23a\n--- /dev/null\n+++ b/reftable/tree.h\n@@ -0,0 +1,34 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef TREE_H\n+#define TREE_H\n+\n+/* tree_node is a generic binary search tree. */\n+struct tree_node {\n+\tvoid *key;\n+\tstruct tree_node *left, *right;\n+};\n+\n+/* looks for `key` in `rootp` using `compare` as comparison function. If insert\n+ * is set, insert the key if it's not found. Else, return NULL.\n+ */\n+struct tree_node *tree_search(void *key, struct tree_node **rootp,\n+\t\t\t      int (*compare)(const void *, const void *),\n+\t\t\t      int insert);\n+\n+/* performs an infix walk of the tree. */\n+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),\n+\t\tvoid *arg);\n+\n+/*\n+ * deallocates the tree nodes recursively. Keys should be deallocated separately\n+ * by walking over the tree. */\n+void tree_free(struct tree_node *t);\n+\n+#endif\ndiff --git a/reftable/tree_test.c b/reftable/tree_test.c\nnew file mode 100644\nindex 00000000000..cbff1255886\n--- /dev/null\n+++ b/reftable/tree_test.c\n@@ -0,0 +1,61 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"tree.h\"\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static int test_compare(const void *a, const void *b)\n+{\n+\treturn (char *)a - (char *)b;\n+}\n+\n+struct curry {\n+\tvoid *last;\n+};\n+\n+static void check_increasing(void *arg, void *key)\n+{\n+\tstruct curry *c = arg;\n+\tif (c->last) {\n+\t\tEXPECT(test_compare(c->last, key) < 0);\n+\t}\n+\tc->last = key;\n+}\n+\n+static void test_tree(void)\n+{\n+\tstruct tree_node *root = NULL;\n+\n+\tvoid *values[11] = { NULL };\n+\tstruct tree_node *nodes[11] = { NULL };\n+\tint i = 1;\n+\tstruct curry c = { NULL };\n+\tdo {\n+\t\tnodes[i] = tree_search(values + i, &root, &test_compare, 1);\n+\t\ti = (i * 7) % 11;\n+\t} while (i != 1);\n+\n+\tfor (i = 1; i < ARRAY_SIZE(nodes); i++) {\n+\t\tEXPECT(values + i == nodes[i]->key);\n+\t\tEXPECT(nodes[i] ==\n+\t\t       tree_search(values + i, &root, &test_compare, 0));\n+\t}\n+\n+\tinfix_walk(root, check_increasing, &c);\n+\ttree_free(root);\n+}\n+\n+int tree_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_tree);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c9deeaf08c7..050551fa698 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,5 +6,6 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"438208","messageId":"daf9cc897d125453fafec17d512231fd5de4b50b.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 10/19] reftable: write reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:06Z","receivedAt":"2021-10-07T20:25:36Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   1 +\n reftable/reftable-writer.h | 148 ++++++++\n reftable/writer.c          | 690 +++++++++++++++++++++++++++++++++++++\n reftable/writer.h          |  50 +++\n 4 files changed, 889 insertions(+)\n create mode 100644 reftable/reftable-writer.h\n create mode 100644 reftable/writer.c\n create mode 100644 reftable/writer.h\n\ndiff --git a/Makefile b/Makefile\nindex d8d4bb0143b..a7803690b94 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2443,6 +2443,7 @@ REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/tree.o\n+REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\ndiff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h\nnew file mode 100644\nindex 00000000000..af36462ced5\n--- /dev/null\n+++ b/reftable/reftable-writer.h\n@@ -0,0 +1,148 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_WRITER_H\n+#define REFTABLE_WRITER_H\n+\n+#include \"reftable-record.h\"\n+\n+#include <stdint.h>\n+#include <unistd.h> /* ssize_t */\n+\n+/* Writing single reftables */\n+\n+/* reftable_write_options sets options for writing a single reftable. */\n+struct reftable_write_options {\n+\t/* boolean: do not pad out blocks to block size. */\n+\tunsigned unpadded : 1;\n+\n+\t/* the blocksize. Should be less than 2^24. */\n+\tuint32_t block_size;\n+\n+\t/* boolean: do not generate a SHA1 => ref index. */\n+\tunsigned skip_index_objects : 1;\n+\n+\t/* how often to write complete keys in each block. */\n+\tint restart_interval;\n+\n+\t/* 4-byte identifier (\"sha1\", \"s256\") of the hash.\n+\t * Defaults to SHA1 if unset\n+\t */\n+\tuint32_t hash_id;\n+\n+\t/* boolean: do not check ref names for validity or dir/file conflicts.\n+\t */\n+\tunsigned skip_name_check : 1;\n+\n+\t/* boolean: copy log messages exactly. If unset, check that the message\n+\t *   is a single line, and add '\\n' if missing.\n+\t */\n+\tunsigned exact_log_message : 1;\n+};\n+\n+/* reftable_block_stats holds statistics for a single block type */\n+struct reftable_block_stats {\n+\t/* total number of entries written */\n+\tint entries;\n+\t/* total number of key restarts */\n+\tint restarts;\n+\t/* total number of blocks */\n+\tint blocks;\n+\t/* total number of index blocks */\n+\tint index_blocks;\n+\t/* depth of the index */\n+\tint max_index_level;\n+\n+\t/* offset of the first block for this type */\n+\tuint64_t offset;\n+\t/* offset of the top level index block for this type, or 0 if not\n+\t * present */\n+\tuint64_t index_offset;\n+};\n+\n+/* stats holds overall statistics for a single reftable */\n+struct reftable_stats {\n+\t/* total number of blocks written. */\n+\tint blocks;\n+\t/* stats for ref data */\n+\tstruct reftable_block_stats ref_stats;\n+\t/* stats for the SHA1 to ref map. */\n+\tstruct reftable_block_stats obj_stats;\n+\t/* stats for index blocks */\n+\tstruct reftable_block_stats idx_stats;\n+\t/* stats for log blocks */\n+\tstruct reftable_block_stats log_stats;\n+\n+\t/* disambiguation length of shortened object IDs. */\n+\tint object_id_len;\n+};\n+\n+/* reftable_new_writer creates a new writer */\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts);\n+\n+/* Set the range of update indices for the records we will add. When writing a\n+   table into a stack, the min should be at least\n+   reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.\n+\n+   For transactional updates to a stack, typically min==max, and the\n+   update_index can be obtained by inspeciting the stack. When converting an\n+   existing ref database into a single reftable, this would be a range of\n+   update-index timestamps.\n+ */\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max);\n+\n+/*\n+  Add a reftable_ref_record. The record should have names that come after\n+  already added records.\n+\n+  The update_index must be within the limits set by\n+  reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an\n+  REFTABLE_API_ERROR error to write a ref record after a log record.\n+*/\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/*\n+  Convenience function to add multiple reftable_ref_records; the function sorts\n+  the records before adding them, reordering the records array passed in.\n+*/\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n);\n+\n+/*\n+  adds reftable_log_records. Log records are keyed by (refname, decreasing\n+  update_index). The key for the record added must come after the already added\n+  log records.\n+*/\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/*\n+  Convenience function to add multiple reftable_log_records; the function sorts\n+  the records before adding them, reordering records array passed in.\n+*/\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n);\n+\n+/* reftable_writer_close finalizes the reftable. The writer is retained so\n+ * statistics can be inspected. */\n+int reftable_writer_close(struct reftable_writer *w);\n+\n+/* writer_stats returns the statistics on the reftable being written.\n+\n+   This struct becomes invalid when the writer is freed.\n+ */\n+const struct reftable_stats *writer_stats(struct reftable_writer *w);\n+\n+/* reftable_writer_free deallocates memory for the writer */\n+void reftable_writer_free(struct reftable_writer *w);\n+\n+#endif\ndiff --git a/reftable/writer.c b/reftable/writer.c\nnew file mode 100644\nindex 00000000000..3ca721e9f64\n--- /dev/null\n+++ b/reftable/writer.c\n@@ -0,0 +1,690 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"writer.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"tree.h\"\n+#include \"reftable-error.h\"\n+\n+/* finishes a block, and writes it to storage */\n+static int writer_flush_block(struct reftable_writer *w);\n+\n+/* deallocates memory related to the index */\n+static void writer_clear_index(struct reftable_writer *w);\n+\n+/* finishes writing a 'r' (refs) or 'g' (reflogs) section */\n+static int writer_finish_public_section(struct reftable_writer *w);\n+\n+static struct reftable_block_stats *\n+writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase 'r':\n+\t\treturn &w->stats.ref_stats;\n+\tcase 'o':\n+\t\treturn &w->stats.obj_stats;\n+\tcase 'i':\n+\t\treturn &w->stats.idx_stats;\n+\tcase 'g':\n+\t\treturn &w->stats.log_stats;\n+\t}\n+\tabort();\n+\treturn NULL;\n+}\n+\n+/* write data, queuing the padding for the next write. Returns negative for\n+ * error. */\n+static int padded_write(struct reftable_writer *w, uint8_t *data, size_t len,\n+\t\t\tint padding)\n+{\n+\tint n = 0;\n+\tif (w->pending_padding > 0) {\n+\t\tuint8_t *zeroed = reftable_calloc(w->pending_padding);\n+\t\tint n = w->write(w->write_arg, zeroed, w->pending_padding);\n+\t\tif (n < 0)\n+\t\t\treturn n;\n+\n+\t\tw->pending_padding = 0;\n+\t\treftable_free(zeroed);\n+\t}\n+\n+\tw->pending_padding = padding;\n+\tn = w->write(w->write_arg, data, len);\n+\tif (n < 0)\n+\t\treturn n;\n+\tn += padding;\n+\treturn 0;\n+}\n+\n+static void options_set_defaults(struct reftable_write_options *opts)\n+{\n+\tif (opts->restart_interval == 0) {\n+\t\topts->restart_interval = 16;\n+\t}\n+\n+\tif (opts->hash_id == 0) {\n+\t\topts->hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\tif (opts->block_size == 0) {\n+\t\topts->block_size = DEFAULT_BLOCK_SIZE;\n+\t}\n+}\n+\n+static int writer_version(struct reftable_writer *w)\n+{\n+\treturn (w->opts.hash_id == 0 || w->opts.hash_id == GIT_SHA1_FORMAT_ID) ?\n+\t\t\t     1 :\n+\t\t\t     2;\n+}\n+\n+static int writer_write_header(struct reftable_writer *w, uint8_t *dest)\n+{\n+\tmemcpy(dest, \"REFT\", 4);\n+\n+\tdest[4] = writer_version(w);\n+\n+\tput_be24(dest + 5, w->opts.block_size);\n+\tput_be64(dest + 8, w->min_update_index);\n+\tput_be64(dest + 16, w->max_update_index);\n+\tif (writer_version(w) == 2) {\n+\t\tput_be32(dest + 24, w->opts.hash_id);\n+\t}\n+\treturn header_size(writer_version(w));\n+}\n+\n+static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)\n+{\n+\tint block_start = 0;\n+\tif (w->next == 0) {\n+\t\tblock_start = header_size(writer_version(w));\n+\t}\n+\n+\tstrbuf_release(&w->last_key);\n+\tblock_writer_init(&w->block_writer_data, typ, w->block,\n+\t\t\t  w->opts.block_size, block_start,\n+\t\t\t  hash_size(w->opts.hash_id));\n+\tw->block_writer = &w->block_writer_data;\n+\tw->block_writer->restart_interval = w->opts.restart_interval;\n+}\n+\n+static struct strbuf reftable_empty_strbuf = STRBUF_INIT;\n+\n+struct reftable_writer *\n+reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),\n+\t\t    void *writer_arg, struct reftable_write_options *opts)\n+{\n+\tstruct reftable_writer *wp =\n+\t\treftable_calloc(sizeof(struct reftable_writer));\n+\tstrbuf_init(&wp->block_writer_data.last_key, 0);\n+\toptions_set_defaults(opts);\n+\tif (opts->block_size >= (1 << 24)) {\n+\t\t/* TODO - error return? */\n+\t\tabort();\n+\t}\n+\twp->last_key = reftable_empty_strbuf;\n+\twp->block = reftable_calloc(opts->block_size);\n+\twp->write = writer_func;\n+\twp->write_arg = writer_arg;\n+\twp->opts = *opts;\n+\twriter_reinit_block_writer(wp, BLOCK_TYPE_REF);\n+\n+\treturn wp;\n+}\n+\n+void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,\n+\t\t\t\tuint64_t max)\n+{\n+\tw->min_update_index = min;\n+\tw->max_update_index = max;\n+}\n+\n+void reftable_writer_free(struct reftable_writer *w)\n+{\n+\treftable_free(w->block);\n+\treftable_free(w);\n+}\n+\n+struct obj_index_tree_node {\n+\tstruct strbuf hash;\n+\tuint64_t *offsets;\n+\tsize_t offset_len;\n+\tsize_t offset_cap;\n+};\n+\n+#define OBJ_INDEX_TREE_NODE_INIT    \\\n+\t{                           \\\n+\t\t.hash = STRBUF_INIT \\\n+\t}\n+\n+static int obj_index_tree_node_compare(const void *a, const void *b)\n+{\n+\treturn strbuf_cmp(&((const struct obj_index_tree_node *)a)->hash,\n+\t\t\t  &((const struct obj_index_tree_node *)b)->hash);\n+}\n+\n+static void writer_index_hash(struct reftable_writer *w, struct strbuf *hash)\n+{\n+\tuint64_t off = w->next;\n+\n+\tstruct obj_index_tree_node want = { .hash = *hash };\n+\n+\tstruct tree_node *node = tree_search(&want, &w->obj_index_tree,\n+\t\t\t\t\t     &obj_index_tree_node_compare, 0);\n+\tstruct obj_index_tree_node *key = NULL;\n+\tif (node == NULL) {\n+\t\tstruct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;\n+\t\tkey = reftable_malloc(sizeof(struct obj_index_tree_node));\n+\t\t*key = empty;\n+\n+\t\tstrbuf_reset(&key->hash);\n+\t\tstrbuf_addbuf(&key->hash, hash);\n+\t\ttree_search((void *)key, &w->obj_index_tree,\n+\t\t\t    &obj_index_tree_node_compare, 1);\n+\t} else {\n+\t\tkey = node->key;\n+\t}\n+\n+\tif (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {\n+\t\treturn;\n+\t}\n+\n+\tif (key->offset_len == key->offset_cap) {\n+\t\tkey->offset_cap = 2 * key->offset_cap + 1;\n+\t\tkey->offsets = reftable_realloc(\n+\t\t\tkey->offsets, sizeof(uint64_t) * key->offset_cap);\n+\t}\n+\n+\tkey->offsets[key->offset_len++] = off;\n+}\n+\n+static int writer_add_record(struct reftable_writer *w,\n+\t\t\t     struct reftable_record *rec)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = -1;\n+\treftable_record_key(rec, &key);\n+\tif (strbuf_cmp(&w->last_key, &key) >= 0) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, &key);\n+\tif (w->block_writer == NULL) {\n+\t\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\t}\n+\n+\tassert(block_writer_type(w->block_writer) == reftable_record_type(rec));\n+\n+\tif (block_writer_add(w->block_writer, rec) == 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\twriter_reinit_block_writer(w, reftable_record_type(rec));\n+\terr = block_writer_add(w->block_writer, rec);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = 0;\n+done:\n+\tstrbuf_release(&key);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_ref(struct reftable_writer *w,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_ref_record copy = *ref;\n+\tint err = 0;\n+\n+\tif (ref->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\tif (ref->update_index < w->min_update_index ||\n+\t    ref->update_index > w->max_update_index)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\treftable_record_from_ref(&rec, &copy);\n+\tcopy.update_index -= w->min_update_index;\n+\n+\terr = writer_add_record(w, &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, (char *)reftable_ref_record_val1(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\n+\tif (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) {\n+\t\tstruct strbuf h = STRBUF_INIT;\n+\t\tstrbuf_add(&h, reftable_ref_record_val2(ref),\n+\t\t\t   hash_size(w->opts.hash_id));\n+\t\twriter_index_hash(w, &h);\n+\t\tstrbuf_release(&h);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_writer_add_refs(struct reftable_writer *w,\n+\t\t\t     struct reftable_ref_record *refs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(refs, n, reftable_ref_record_compare_name);\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_ref(w, &refs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int reftable_writer_add_log_verbatim(struct reftable_writer *w,\n+\t\t\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tif (w->block_writer &&\n+\t    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {\n+\t\tint err = writer_finish_public_section(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tw->next -= w->pending_padding;\n+\tw->pending_padding = 0;\n+\n+\treftable_record_from_log(&rec, log);\n+\treturn writer_add_record(w, &rec);\n+}\n+\n+int reftable_writer_add_log(struct reftable_writer *w,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tchar *input_log_message = NULL;\n+\tstruct strbuf cleaned_message = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (log->value_type == REFTABLE_LOG_DELETION)\n+\t\treturn reftable_writer_add_log_verbatim(w, log);\n+\n+\tif (log->refname == NULL)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\tinput_log_message = log->value.update.message;\n+\tif (!w->opts.exact_log_message && log->value.update.message) {\n+\t\tstrbuf_addstr(&cleaned_message, log->value.update.message);\n+\t\twhile (cleaned_message.len &&\n+\t\t       cleaned_message.buf[cleaned_message.len - 1] == '\\n')\n+\t\t\tstrbuf_setlen(&cleaned_message,\n+\t\t\t\t      cleaned_message.len - 1);\n+\t\tif (strchr(cleaned_message.buf, '\\n')) {\n+\t\t\t/* multiple lines not allowed. */\n+\t\t\terr = REFTABLE_API_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tstrbuf_addstr(&cleaned_message, \"\\n\");\n+\t\tlog->value.update.message = cleaned_message.buf;\n+\t}\n+\n+\terr = reftable_writer_add_log_verbatim(w, log);\n+\tlog->value.update.message = input_log_message;\n+done:\n+\tstrbuf_release(&cleaned_message);\n+\treturn err;\n+}\n+\n+int reftable_writer_add_logs(struct reftable_writer *w,\n+\t\t\t     struct reftable_log_record *logs, int n)\n+{\n+\tint err = 0;\n+\tint i = 0;\n+\tQSORT(logs, n, reftable_log_record_compare_key);\n+\n+\tfor (i = 0; err == 0 && i < n; i++) {\n+\t\terr = reftable_writer_add_log(w, &logs[i]);\n+\t}\n+\treturn err;\n+}\n+\n+static int writer_finish_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tuint64_t index_start = 0;\n+\tint max_level = 0;\n+\tint threshold = w->opts.unpadded ? 1 : 3;\n+\tint before_blocks = w->stats.idx_stats.blocks;\n+\tint err = writer_flush_block(w);\n+\tint i = 0;\n+\tstruct reftable_block_stats *bstats = NULL;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\twhile (w->index_len > threshold) {\n+\t\tstruct reftable_index_record *idx = NULL;\n+\t\tint idx_len = 0;\n+\n+\t\tmax_level++;\n+\t\tindex_start = w->next;\n+\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\tidx = w->index;\n+\t\tidx_len = w->index_len;\n+\n+\t\tw->index = NULL;\n+\t\tw->index_len = 0;\n+\t\tw->index_cap = 0;\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstruct reftable_record rec = { NULL };\n+\t\t\treftable_record_from_index(&rec, idx + i);\n+\t\t\tif (block_writer_add(w->block_writer, &rec) == 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\terr = writer_flush_block(w);\n+\t\t\tif (err < 0)\n+\t\t\t\treturn err;\n+\n+\t\t\twriter_reinit_block_writer(w, BLOCK_TYPE_INDEX);\n+\n+\t\t\terr = block_writer_add(w->block_writer, &rec);\n+\t\t\tif (err != 0) {\n+\t\t\t\t/* write into fresh block should always succeed\n+\t\t\t\t */\n+\t\t\t\tabort();\n+\t\t\t}\n+\t\t}\n+\t\tfor (i = 0; i < idx_len; i++) {\n+\t\t\tstrbuf_release(&idx[i].last_key);\n+\t\t}\n+\t\treftable_free(idx);\n+\t}\n+\n+\twriter_clear_index(w);\n+\n+\terr = writer_flush_block(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tbstats = writer_reftable_block_stats(w, typ);\n+\tbstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;\n+\tbstats->index_offset = index_start;\n+\tbstats->max_index_level = max_level;\n+\n+\t/* Reinit lastKey, as the next section can start with any key. */\n+\tw->last_key.len = 0;\n+\n+\treturn 0;\n+}\n+\n+struct common_prefix_arg {\n+\tstruct strbuf *last;\n+\tint max;\n+};\n+\n+static void update_common(void *void_arg, void *key)\n+{\n+\tstruct common_prefix_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tif (arg->last) {\n+\t\tint n = common_prefix_size(&entry->hash, arg->last);\n+\t\tif (n > arg->max) {\n+\t\t\targ->max = n;\n+\t\t}\n+\t}\n+\targ->last = &entry->hash;\n+}\n+\n+struct write_record_arg {\n+\tstruct reftable_writer *w;\n+\tint err;\n+};\n+\n+static void write_object_record(void *void_arg, void *key)\n+{\n+\tstruct write_record_arg *arg = void_arg;\n+\tstruct obj_index_tree_node *entry = key;\n+\tstruct reftable_obj_record obj_rec = {\n+\t\t.hash_prefix = (uint8_t *)entry->hash.buf,\n+\t\t.hash_prefix_len = arg->w->stats.object_id_len,\n+\t\t.offsets = entry->offsets,\n+\t\t.offset_len = entry->offset_len,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\treftable_record_from_obj(&rec, &obj_rec);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\n+\targ->err = writer_flush_block(arg->w);\n+\tif (arg->err < 0)\n+\t\tgoto done;\n+\n+\twriter_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\tif (arg->err == 0)\n+\t\tgoto done;\n+\tobj_rec.offset_len = 0;\n+\targ->err = block_writer_add(arg->w->block_writer, &rec);\n+\n+\t/* Should be able to write into a fresh block. */\n+\tassert(arg->err == 0);\n+\n+done:;\n+}\n+\n+static void object_record_free(void *void_arg, void *key)\n+{\n+\tstruct obj_index_tree_node *entry = key;\n+\n+\tFREE_AND_NULL(entry->offsets);\n+\tstrbuf_release(&entry->hash);\n+\treftable_free(entry);\n+}\n+\n+static int writer_dump_object_index(struct reftable_writer *w)\n+{\n+\tstruct write_record_arg closure = { .w = w };\n+\tstruct common_prefix_arg common = { NULL };\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &update_common, &common);\n+\t}\n+\tw->stats.object_id_len = common.max + 1;\n+\n+\twriter_reinit_block_writer(w, BLOCK_TYPE_OBJ);\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &write_object_record, &closure);\n+\t}\n+\n+\tif (closure.err < 0)\n+\t\treturn closure.err;\n+\treturn writer_finish_section(w);\n+}\n+\n+static int writer_finish_public_section(struct reftable_writer *w)\n+{\n+\tuint8_t typ = 0;\n+\tint err = 0;\n+\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\n+\ttyp = block_writer_type(w->block_writer);\n+\terr = writer_finish_section(w);\n+\tif (err < 0)\n+\t\treturn err;\n+\tif (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&\n+\t    w->stats.ref_stats.index_blocks > 0) {\n+\t\terr = writer_dump_object_index(w);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (w->obj_index_tree) {\n+\t\tinfix_walk(w->obj_index_tree, &object_record_free, NULL);\n+\t\ttree_free(w->obj_index_tree);\n+\t\tw->obj_index_tree = NULL;\n+\t}\n+\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+int reftable_writer_close(struct reftable_writer *w)\n+{\n+\tuint8_t footer[72];\n+\tuint8_t *p = footer;\n+\tint err = writer_finish_public_section(w);\n+\tint empty_table = w->next == 0;\n+\tif (err != 0)\n+\t\tgoto done;\n+\tw->pending_padding = 0;\n+\tif (empty_table) {\n+\t\t/* Empty tables need a header anyway. */\n+\t\tuint8_t header[28];\n+\t\tint n = writer_write_header(w, header);\n+\t\terr = padded_write(w, header, n, 0);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\tp += writer_write_header(w, footer);\n+\tput_be64(p, w->stats.ref_stats.index_offset);\n+\tp += 8;\n+\tput_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);\n+\tp += 8;\n+\tput_be64(p, w->stats.obj_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be64(p, w->stats.log_stats.offset);\n+\tp += 8;\n+\tput_be64(p, w->stats.log_stats.index_offset);\n+\tp += 8;\n+\n+\tput_be32(p, crc32(0, footer, p - footer));\n+\tp += 4;\n+\n+\terr = padded_write(w, footer, footer_size(writer_version(w)), 0);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (empty_table) {\n+\t\terr = REFTABLE_EMPTY_TABLE_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\t/* free up memory. */\n+\tblock_writer_release(&w->block_writer_data);\n+\twriter_clear_index(w);\n+\tstrbuf_release(&w->last_key);\n+\treturn err;\n+}\n+\n+static void writer_clear_index(struct reftable_writer *w)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < w->index_len; i++) {\n+\t\tstrbuf_release(&w->index[i].last_key);\n+\t}\n+\n+\tFREE_AND_NULL(w->index);\n+\tw->index_len = 0;\n+\tw->index_cap = 0;\n+}\n+\n+static const int debug = 0;\n+\n+static int writer_flush_nonempty_block(struct reftable_writer *w)\n+{\n+\tuint8_t typ = block_writer_type(w->block_writer);\n+\tstruct reftable_block_stats *bstats =\n+\t\twriter_reftable_block_stats(w, typ);\n+\tuint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;\n+\tint raw_bytes = block_writer_finish(w->block_writer);\n+\tint padding = 0;\n+\tint err = 0;\n+\tstruct reftable_index_record ir = { .last_key = STRBUF_INIT };\n+\tif (raw_bytes < 0)\n+\t\treturn raw_bytes;\n+\n+\tif (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {\n+\t\tpadding = w->opts.block_size - raw_bytes;\n+\t}\n+\n+\tif (block_typ_off > 0) {\n+\t\tbstats->offset = block_typ_off;\n+\t}\n+\n+\tbstats->entries += w->block_writer->entries;\n+\tbstats->restarts += w->block_writer->restart_len;\n+\tbstats->blocks++;\n+\tw->stats.blocks++;\n+\n+\tif (debug) {\n+\t\tfprintf(stderr, \"block %c off %\" PRIu64 \" sz %d (%d)\\n\", typ,\n+\t\t\tw->next, raw_bytes,\n+\t\t\tget_be24(w->block + w->block_writer->header_off + 1));\n+\t}\n+\n+\tif (w->next == 0) {\n+\t\twriter_write_header(w, w->block);\n+\t}\n+\n+\terr = padded_write(w, w->block, raw_bytes, padding);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (w->index_cap == w->index_len) {\n+\t\tw->index_cap = 2 * w->index_cap + 1;\n+\t\tw->index = reftable_realloc(\n+\t\t\tw->index,\n+\t\t\tsizeof(struct reftable_index_record) * w->index_cap);\n+\t}\n+\n+\tir.offset = w->next;\n+\tstrbuf_reset(&ir.last_key);\n+\tstrbuf_addbuf(&ir.last_key, &w->block_writer->last_key);\n+\tw->index[w->index_len] = ir;\n+\n+\tw->index_len++;\n+\tw->next += padding + raw_bytes;\n+\tw->block_writer = NULL;\n+\treturn 0;\n+}\n+\n+static int writer_flush_block(struct reftable_writer *w)\n+{\n+\tif (w->block_writer == NULL)\n+\t\treturn 0;\n+\tif (w->block_writer->entries == 0)\n+\t\treturn 0;\n+\treturn writer_flush_nonempty_block(w);\n+}\n+\n+const struct reftable_stats *writer_stats(struct reftable_writer *w)\n+{\n+\treturn &w->stats;\n+}\ndiff --git a/reftable/writer.h b/reftable/writer.h\nnew file mode 100644\nindex 00000000000..09b88673d97\n--- /dev/null\n+++ b/reftable/writer.h\n@@ -0,0 +1,50 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef WRITER_H\n+#define WRITER_H\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"tree.h\"\n+#include \"reftable-writer.h\"\n+\n+struct reftable_writer {\n+\tssize_t (*write)(void *, const void *, size_t);\n+\tvoid *write_arg;\n+\tint pending_padding;\n+\tstruct strbuf last_key;\n+\n+\t/* offset of next block to write. */\n+\tuint64_t next;\n+\tuint64_t min_update_index, max_update_index;\n+\tstruct reftable_write_options opts;\n+\n+\t/* memory buffer for writing */\n+\tuint8_t *block;\n+\n+\t/* writer for the current section. NULL or points to\n+\t * block_writer_data */\n+\tstruct block_writer *block_writer;\n+\n+\tstruct block_writer block_writer_data;\n+\n+\t/* pending index records for the current section */\n+\tstruct reftable_index_record *index;\n+\tsize_t index_len;\n+\tsize_t index_cap;\n+\n+\t/*\n+\t * tree for use with tsearch; used to populate the 'o' inverse OID\n+\t * map */\n+\tstruct tree_node *obj_index_tree;\n+\n+\tstruct reftable_stats stats;\n+};\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"438207","messageId":"c9a5813b21114b3c99a20669800c56af90e19baa.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 11/19] reftable: generic interface to tables","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:07Z","receivedAt":"2021-10-07T20:25:37Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                     |   3 +\n reftable/generic.c           | 169 +++++++++++++++++++++++++++++++++++\n reftable/generic.h           |  32 +++++++\n reftable/reftable-generic.h  |  47 ++++++++++\n reftable/reftable-iterator.h |  39 ++++++++\n reftable/reftable.c          | 115 ++++++++++++++++++++++++\n 6 files changed, 405 insertions(+)\n create mode 100644 reftable/generic.c\n create mode 100644 reftable/generic.h\n create mode 100644 reftable/reftable-generic.h\n create mode 100644 reftable/reftable-iterator.h\n create mode 100644 reftable/reftable.c\n\ndiff --git a/Makefile b/Makefile\nindex a7803690b94..abf1faf778a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2442,6 +2442,9 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n+REFTABLE_OBJS += reftable/refname.o\n+REFTABLE_OBJS += reftable/generic.o\n+REFTABLE_OBJS += reftable/stack.o\n REFTABLE_OBJS += reftable/tree.o\n REFTABLE_OBJS += reftable/writer.o\n \ndiff --git a/reftable/generic.c b/reftable/generic.c\nnew file mode 100644\nindex 00000000000..7a8a738d860\n--- /dev/null\n+++ b/reftable/generic.c\n@@ -0,0 +1,169 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = ~((uint64_t)0),\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+int reftable_table_print(struct reftable_table *tab) {\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\tuint32_t hash_id = reftable_table_hash_id(tab);\n+\tint err = reftable_table_seek_ref(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_ref_record_print(&ref, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_table_seek_log(tab, &it, \"\");\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_log_record_print(&log, hash_id);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_log_record_release(&log);\n+\treturn 0;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\ndiff --git a/reftable/generic.h b/reftable/generic.h\nnew file mode 100644\nindex 00000000000..98886a06402\n--- /dev/null\n+++ b/reftable/generic.h\n@@ -0,0 +1,32 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef GENERIC_H\n+#define GENERIC_H\n+\n+#include \"record.h\"\n+#include \"reftable-generic.h\"\n+\n+/* generic interface to reftables */\n+struct reftable_table_vtable {\n+\tint (*seek_record)(void *tab, struct reftable_iterator *it,\n+\t\t\t   struct reftable_record *);\n+\tuint32_t (*hash_id)(void *tab);\n+\tuint64_t (*min_update_index)(void *tab);\n+\tuint64_t (*max_update_index)(void *tab);\n+};\n+\n+struct reftable_iterator_vtable {\n+\tint (*next)(void *iter_arg, struct reftable_record *rec);\n+\tvoid (*close)(void *iter_arg);\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it);\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec);\n+\n+#endif\ndiff --git a/reftable/reftable-generic.h b/reftable/reftable-generic.h\nnew file mode 100644\nindex 00000000000..d239751a778\n--- /dev/null\n+++ b/reftable/reftable-generic.h\n@@ -0,0 +1,47 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_GENERIC_H\n+#define REFTABLE_GENERIC_H\n+\n+#include \"reftable-iterator.h\"\n+\n+struct reftable_table_vtable;\n+\n+/*\n+ * Provides a unified API for reading tables, either merged tables, or single\n+ * readers. */\n+struct reftable_table {\n+\tstruct reftable_table_vtable *ops;\n+\tvoid *table_arg;\n+};\n+\n+int reftable_table_seek_log(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID from a generic reftable_table */\n+uint32_t reftable_table_hash_id(struct reftable_table *tab);\n+\n+/* returns the max update_index covered by this table. */\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab);\n+\n+/* returns the min update_index covered by this table. */\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0\n+   for success, and 1 if ref not found. */\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* dump table contents onto stdout for debugging */\n+int reftable_table_print(struct reftable_table *tab);\n+\n+#endif\ndiff --git a/reftable/reftable-iterator.h b/reftable/reftable-iterator.h\nnew file mode 100644\nindex 00000000000..d3eee7af357\n--- /dev/null\n+++ b/reftable/reftable-iterator.h\n@@ -0,0 +1,39 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_ITERATOR_H\n+#define REFTABLE_ITERATOR_H\n+\n+#include \"reftable-record.h\"\n+\n+struct reftable_iterator_vtable;\n+\n+/* iterator is the generic interface for walking over data stored in a\n+ * reftable.\n+ */\n+struct reftable_iterator {\n+\tstruct reftable_iterator_vtable *ops;\n+\tvoid *iter_arg;\n+};\n+\n+/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref);\n+\n+/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:\n+ * end of iteration.\n+ */\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log);\n+\n+/* releases resources associated with an iterator. */\n+void reftable_iterator_destroy(struct reftable_iterator *it);\n+\n+#endif\ndiff --git a/reftable/reftable.c b/reftable/reftable.c\nnew file mode 100644\nindex 00000000000..0e4607a7cd6\n--- /dev/null\n+++ b/reftable/reftable.c\n@@ -0,0 +1,115 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+int reftable_table_seek_ref(struct reftable_table *tab,\n+\t\t\t    struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn tab->ops->seek_record(tab->table_arg, it, &rec);\n+}\n+\n+int reftable_table_read_ref(struct reftable_table *tab, const char *name,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_table_seek_ref(tab, &it, name);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_ref(&it, ref);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(ref->refname, name) ||\n+\t    reftable_ref_record_is_deletion(ref)) {\n+\t\treftable_ref_record_release(ref);\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+uint64_t reftable_table_max_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->max_update_index(tab->table_arg);\n+}\n+\n+uint64_t reftable_table_min_update_index(struct reftable_table *tab)\n+{\n+\treturn tab->ops->min_update_index(tab->table_arg);\n+}\n+\n+uint32_t reftable_table_hash_id(struct reftable_table *tab)\n+{\n+\treturn tab->ops->hash_id(tab->table_arg);\n+}\n+\n+void reftable_iterator_destroy(struct reftable_iterator *it)\n+{\n+\tif (!it->ops) {\n+\t\treturn;\n+\t}\n+\tit->ops->close(it->iter_arg);\n+\tit->ops = NULL;\n+\tFREE_AND_NULL(it->iter_arg);\n+}\n+\n+int reftable_iterator_next_ref(struct reftable_iterator *it,\n+\t\t\t       struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, ref);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int reftable_iterator_next_log(struct reftable_iterator *it,\n+\t\t\t       struct reftable_log_record *log)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, log);\n+\treturn iterator_next(it, &rec);\n+}\n+\n+int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)\n+{\n+\treturn it->ops->next(it->iter_arg, rec);\n+}\n+\n+static int empty_iterator_next(void *arg, struct reftable_record *rec)\n+{\n+\treturn 1;\n+}\n+\n+static void empty_iterator_close(void *arg)\n+{\n+}\n+\n+static struct reftable_iterator_vtable empty_vtable = {\n+\t.next = &empty_iterator_next,\n+\t.close = &empty_iterator_close,\n+};\n+\n+void iterator_set_empty(struct reftable_iterator *it)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = NULL;\n+\tit->ops = &empty_vtable;\n+}\n-- \ngitgitgadget\n\n"},{"id":"438209","messageId":"1002d6f526c8c4f829ee367cbb65b84cd15c5233.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 08/19] reftable: reading/writing blocks","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:04Z","receivedAt":"2021-10-07T20:25:39Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of block. Within a block,\nrecords are prefix compressed, with an index of offsets for fully expand keys to\nenable binary search within blocks.\n\nThis commit provides the logic to read and write these blocks.\n\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   2 +\n reftable/block.c         | 437 +++++++++++++++++++++++++++++++++++++++\n reftable/block.h         | 127 ++++++++++++\n reftable/block_test.c    | 120 +++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 687 insertions(+)\n create mode 100644 reftable/block.c\n create mode 100644 reftable/block.h\n create mode 100644 reftable/block_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 9dfab70f79b..153db9dff11 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2438,10 +2438,12 @@ xdiff-objs: $(XDIFF_OBJS)\n \n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n+REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\ndiff --git a/reftable/block.c b/reftable/block.c\nnew file mode 100644\nindex 00000000000..855e3f5c947\n--- /dev/null\n+++ b/reftable/block.c\n@@ -0,0 +1,437 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+#include <zlib.h>\n+\n+int header_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 24;\n+\tcase 2:\n+\t\treturn 28;\n+\t}\n+\tabort();\n+}\n+\n+int footer_size(int version)\n+{\n+\tswitch (version) {\n+\tcase 1:\n+\t\treturn 68;\n+\tcase 2:\n+\t\treturn 72;\n+\t}\n+\tabort();\n+}\n+\n+static int block_writer_register_restart(struct block_writer *w, int n,\n+\t\t\t\t\t int is_restart, struct strbuf *key)\n+{\n+\tint rlen = w->restart_len;\n+\tif (rlen >= MAX_RESTARTS) {\n+\t\tis_restart = 0;\n+\t}\n+\n+\tif (is_restart) {\n+\t\trlen++;\n+\t}\n+\tif (2 + 3 * rlen + n > w->block_size - w->next)\n+\t\treturn -1;\n+\tif (is_restart) {\n+\t\tif (w->restart_len == w->restart_cap) {\n+\t\t\tw->restart_cap = w->restart_cap * 2 + 1;\n+\t\t\tw->restarts = reftable_realloc(\n+\t\t\t\tw->restarts, sizeof(uint32_t) * w->restart_cap);\n+\t\t}\n+\n+\t\tw->restarts[w->restart_len++] = w->next;\n+\t}\n+\n+\tw->next += n;\n+\n+\tstrbuf_reset(&w->last_key);\n+\tstrbuf_addbuf(&w->last_key, key);\n+\tw->entries++;\n+\treturn 0;\n+}\n+\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size)\n+{\n+\tbw->buf = buf;\n+\tbw->hash_size = hash_size;\n+\tbw->block_size = block_size;\n+\tbw->header_off = header_off;\n+\tbw->buf[header_off] = typ;\n+\tbw->next = header_off + 4;\n+\tbw->restart_interval = 16;\n+\tbw->entries = 0;\n+\tbw->restart_len = 0;\n+\tbw->last_key.len = 0;\n+}\n+\n+uint8_t block_writer_type(struct block_writer *bw)\n+{\n+\treturn bw->buf[bw->header_off];\n+}\n+\n+/* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on\n+   success */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tstruct strbuf last =\n+\t\tw->entries % w->restart_interval == 0 ? empty : w->last_key;\n+\tstruct string_view out = {\n+\t\t.buf = w->buf + w->next,\n+\t\t.len = w->block_size - w->next,\n+\t};\n+\n+\tstruct string_view start = out;\n+\n+\tint is_restart = 0;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint n = 0;\n+\n+\treftable_record_key(rec, &key);\n+\tn = reftable_encode_key(&is_restart, out, last, key,\n+\t\t\t\treftable_record_val_type(rec));\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tn = reftable_record_encode(rec, out, w->hash_size);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&out, n);\n+\n+\tif (block_writer_register_restart(w, start.len - out.len, is_restart,\n+\t\t\t\t\t  &key) < 0)\n+\t\tgoto done;\n+\n+\tstrbuf_release(&key);\n+\treturn 0;\n+\n+done:\n+\tstrbuf_release(&key);\n+\treturn -1;\n+}\n+\n+int block_writer_finish(struct block_writer *w)\n+{\n+\tint i;\n+\tfor (i = 0; i < w->restart_len; i++) {\n+\t\tput_be24(w->buf + w->next, w->restarts[i]);\n+\t\tw->next += 3;\n+\t}\n+\n+\tput_be16(w->buf + w->next, w->restart_len);\n+\tw->next += 2;\n+\tput_be24(w->buf + 1 + w->header_off, w->next);\n+\n+\tif (block_writer_type(w) == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + w->header_off;\n+\t\tuLongf src_len = w->next - block_header_skip;\n+\t\tuLongf dest_cap = src_len * 1.001 + 12;\n+\n+\t\tuint8_t *compressed = reftable_malloc(dest_cap);\n+\t\twhile (1) {\n+\t\t\tuLongf out_dest_len = dest_cap;\n+\t\t\tint zresult = compress2(compressed, &out_dest_len,\n+\t\t\t\t\t\tw->buf + block_header_skip,\n+\t\t\t\t\t\tsrc_len, 9);\n+\t\t\tif (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) {\n+\t\t\t\tdest_cap *= 2;\n+\t\t\t\tcompressed =\n+\t\t\t\t\treftable_realloc(compressed, dest_cap);\n+\t\t\t\tif (compressed)\n+\t\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (Z_OK != zresult) {\n+\t\t\t\treftable_free(compressed);\n+\t\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t\t}\n+\n+\t\t\tmemcpy(w->buf + block_header_skip, compressed,\n+\t\t\t       out_dest_len);\n+\t\t\tw->next = out_dest_len + block_header_skip;\n+\t\t\treftable_free(compressed);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn w->next;\n+}\n+\n+uint8_t block_reader_type(struct block_reader *r)\n+{\n+\treturn r->block.data[r->header_off];\n+}\n+\n+int block_reader_init(struct block_reader *br, struct reftable_block *block,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size)\n+{\n+\tuint32_t full_block_size = table_block_size;\n+\tuint8_t typ = block->data[header_off];\n+\tuint32_t sz = get_be24(block->data + header_off + 1);\n+\n+\tuint16_t restart_count = 0;\n+\tuint32_t restart_start = 0;\n+\tuint8_t *restart_bytes = NULL;\n+\n+\tif (!reftable_is_block_type(typ))\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tif (typ == BLOCK_TYPE_LOG) {\n+\t\tint block_header_skip = 4 + header_off;\n+\t\tuLongf dst_len = sz - block_header_skip; /* total size of dest\n+\t\t\t\t\t\t\t    buffer. */\n+\t\tuLongf src_len = block->len - block_header_skip;\n+\t\t/* Log blocks specify the *uncompressed* size in their header.\n+\t\t */\n+\t\tuint8_t *uncompressed = reftable_malloc(sz);\n+\n+\t\t/* Copy over the block header verbatim. It's not compressed. */\n+\t\tmemcpy(uncompressed, block->data, block_header_skip);\n+\n+\t\t/* Uncompress */\n+\t\tif (Z_OK !=\n+\t\t    uncompress2(uncompressed + block_header_skip, &dst_len,\n+\t\t\t\tblock->data + block_header_skip, &src_len)) {\n+\t\t\treftable_free(uncompressed);\n+\t\t\treturn REFTABLE_ZLIB_ERROR;\n+\t\t}\n+\n+\t\tif (dst_len + block_header_skip != sz)\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\t\t/* We're done with the input data. */\n+\t\treftable_block_done(block);\n+\t\tblock->data = uncompressed;\n+\t\tblock->len = sz;\n+\t\tblock->source = malloc_block_source();\n+\t\tfull_block_size = src_len + block_header_skip;\n+\t} else if (full_block_size == 0) {\n+\t\tfull_block_size = sz;\n+\t} else if (sz < full_block_size && sz < block->len &&\n+\t\t   block->data[sz] != 0) {\n+\t\t/* If the block is smaller than the full block size, it is\n+\t\t   padded (data followed by '\\0') or the next block is\n+\t\t   unaligned. */\n+\t\tfull_block_size = sz;\n+\t}\n+\n+\trestart_count = get_be16(block->data + sz - 2);\n+\trestart_start = sz - 2 - 3 * restart_count;\n+\trestart_bytes = block->data + restart_start;\n+\n+\t/* transfer ownership. */\n+\tbr->block = *block;\n+\tblock->data = NULL;\n+\tblock->len = 0;\n+\n+\tbr->hash_size = hash_size;\n+\tbr->block_len = restart_start;\n+\tbr->full_block_size = full_block_size;\n+\tbr->header_off = header_off;\n+\tbr->restart_count = restart_count;\n+\tbr->restart_bytes = restart_bytes;\n+\n+\treturn 0;\n+}\n+\n+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)\n+{\n+\treturn get_be24(br->restart_bytes + 3 * i);\n+}\n+\n+void block_reader_start(struct block_reader *br, struct block_iter *it)\n+{\n+\tit->br = br;\n+\tstrbuf_reset(&it->last_key);\n+\tit->next_off = br->header_off + 4;\n+}\n+\n+struct restart_find_args {\n+\tint error;\n+\tstruct strbuf key;\n+\tstruct block_reader *r;\n+};\n+\n+static int restart_key_less(size_t idx, void *args)\n+{\n+\tstruct restart_find_args *a = args;\n+\tuint32_t off = block_reader_restart_offset(a->r, idx);\n+\tstruct string_view in = {\n+\t\t.buf = a->r->block.data + off,\n+\t\t.len = a->r->block_len - off,\n+\t};\n+\n+\t/* the restart key is verbatim in the block, so this could avoid the\n+\t   alloc for decoding the key */\n+\tstruct strbuf rkey = STRBUF_INIT;\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tuint8_t unused_extra;\n+\tint n = reftable_decode_key(&rkey, &unused_extra, last_key, in);\n+\tint result;\n+\tif (n < 0) {\n+\t\ta->error = 1;\n+\t\treturn -1;\n+\t}\n+\n+\tresult = strbuf_cmp(&a->key, &rkey);\n+\tstrbuf_release(&rkey);\n+\treturn result;\n+}\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)\n+{\n+\tdest->br = src->br;\n+\tdest->next_off = src->next_off;\n+\tstrbuf_reset(&dest->last_key);\n+\tstrbuf_addbuf(&dest->last_key, &src->last_key);\n+}\n+\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec)\n+{\n+\tstruct string_view in = {\n+\t\t.buf = it->br->block.data + it->next_off,\n+\t\t.len = it->br->block_len - it->next_off,\n+\t};\n+\tstruct string_view start = in;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tuint8_t extra = 0;\n+\tint n = 0;\n+\n+\tif (it->next_off >= it->br->block_len)\n+\t\treturn 1;\n+\n+\tn = reftable_decode_key(&key, &extra, it->last_key, in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\n+\tstring_view_consume(&in, n);\n+\tn = reftable_record_decode(rec, key, extra, in, it->br->hash_size);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tstrbuf_reset(&it->last_key);\n+\tstrbuf_addbuf(&it->last_key, &key);\n+\tit->next_off += start.len - in.len;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key)\n+{\n+\tstruct strbuf empty = STRBUF_INIT;\n+\tint off = br->header_off + 4;\n+\tstruct string_view in = {\n+\t\t.buf = br->block.data + off,\n+\t\t.len = br->block_len - off,\n+\t};\n+\n+\tuint8_t extra = 0;\n+\tint n = reftable_decode_key(key, &extra, empty, in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\treturn 0;\n+}\n+\n+int block_iter_seek(struct block_iter *it, struct strbuf *want)\n+{\n+\treturn block_reader_seek(it->br, it, want);\n+}\n+\n+void block_iter_close(struct block_iter *it)\n+{\n+\tstrbuf_release(&it->last_key);\n+}\n+\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want)\n+{\n+\tstruct restart_find_args args = {\n+\t\t.key = *want,\n+\t\t.r = br,\n+\t};\n+\tstruct reftable_record rec = reftable_new_record(block_reader_type(br));\n+\tstruct strbuf key = STRBUF_INIT;\n+\tint err = 0;\n+\tstruct block_iter next = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\n+\tint i = binsearch(br->restart_count, &restart_key_less, &args);\n+\tif (args.error) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tit->br = br;\n+\tif (i > 0) {\n+\t\ti--;\n+\t\tit->next_off = block_reader_restart_offset(br, i);\n+\t} else {\n+\t\tit->next_off = br->header_off + 4;\n+\t}\n+\n+\t/* We're looking for the last entry less/equal than the wanted key, so\n+\t   we have to go one entry too far and then back up.\n+\t*/\n+\twhile (1) {\n+\t\tblock_iter_copy_from(&next, it);\n+\t\terr = block_iter_next(&next, &rec);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tif (err > 0 || strbuf_cmp(&key, want) >= 0) {\n+\t\t\terr = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tblock_iter_copy_from(it, &next);\n+\t}\n+\n+done:\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&next.last_key);\n+\treftable_record_destroy(&rec);\n+\n+\treturn err;\n+}\n+\n+void block_writer_release(struct block_writer *bw)\n+{\n+\tFREE_AND_NULL(bw->restarts);\n+\tstrbuf_release(&bw->last_key);\n+\t/* the block is not owned. */\n+}\n+\n+void reftable_block_done(struct reftable_block *blockp)\n+{\n+\tstruct reftable_block_source source = blockp->source;\n+\tif (blockp && source.ops)\n+\t\tsource.ops->return_block(source.arg, blockp);\n+\tblockp->data = NULL;\n+\tblockp->len = 0;\n+\tblockp->source.ops = NULL;\n+\tblockp->source.arg = NULL;\n+}\ndiff --git a/reftable/block.h b/reftable/block.h\nnew file mode 100644\nindex 00000000000..e207706a644\n--- /dev/null\n+++ b/reftable/block.h\n@@ -0,0 +1,127 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef BLOCK_H\n+#define BLOCK_H\n+\n+#include \"basics.h\"\n+#include \"record.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Writes reftable blocks. The block_writer is reused across blocks to minimize\n+ * allocation overhead.\n+ */\n+struct block_writer {\n+\tuint8_t *buf;\n+\tuint32_t block_size;\n+\n+\t/* Offset ofof the global header. Nonzero in the first block only. */\n+\tuint32_t header_off;\n+\n+\t/* How often to restart keys. */\n+\tint restart_interval;\n+\tint hash_size;\n+\n+\t/* Offset of next uint8_t to write. */\n+\tuint32_t next;\n+\tuint32_t *restarts;\n+\tuint32_t restart_len;\n+\tuint32_t restart_cap;\n+\n+\tstruct strbuf last_key;\n+\tint entries;\n+};\n+\n+/*\n+ * initializes the blockwriter to write `typ` entries, using `buf` as temporary\n+ * storage. `buf` is not owned by the block_writer. */\n+void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,\n+\t\t       uint32_t block_size, uint32_t header_off, int hash_size);\n+\n+/* returns the block type (eg. 'r' for ref records. */\n+uint8_t block_writer_type(struct block_writer *bw);\n+\n+/* appends the record, or -1 if it doesn't fit. */\n+int block_writer_add(struct block_writer *w, struct reftable_record *rec);\n+\n+/* appends the key restarts, and compress the block if necessary. */\n+int block_writer_finish(struct block_writer *w);\n+\n+/* clears out internally allocated block_writer members. */\n+void block_writer_release(struct block_writer *bw);\n+\n+/* Read a block. */\n+struct block_reader {\n+\t/* offset of the block header; nonzero for the first block in a\n+\t * reftable. */\n+\tuint32_t header_off;\n+\n+\t/* the memory block */\n+\tstruct reftable_block block;\n+\tint hash_size;\n+\n+\t/* size of the data, excluding restart data. */\n+\tuint32_t block_len;\n+\tuint8_t *restart_bytes;\n+\tuint16_t restart_count;\n+\n+\t/* size of the data in the file. For log blocks, this is the compressed\n+\t * size. */\n+\tuint32_t full_block_size;\n+};\n+\n+/* Iterate over entries in a block */\n+struct block_iter {\n+\t/* offset within the block of the next entry to read. */\n+\tuint32_t next_off;\n+\tstruct block_reader *br;\n+\n+\t/* key for last entry we read. */\n+\tstruct strbuf last_key;\n+};\n+\n+/* initializes a block reader. */\n+int block_reader_init(struct block_reader *br, struct reftable_block *bl,\n+\t\t      uint32_t header_off, uint32_t table_block_size,\n+\t\t      int hash_size);\n+\n+/* Position `it` at start of the block */\n+void block_reader_start(struct block_reader *br, struct block_iter *it);\n+\n+/* Position `it` to the `want` key in the block */\n+int block_reader_seek(struct block_reader *br, struct block_iter *it,\n+\t\t      struct strbuf *want);\n+\n+/* Returns the block type (eg. 'r' for refs) */\n+uint8_t block_reader_type(struct block_reader *r);\n+\n+/* Decodes the first key in the block */\n+int block_reader_first_key(struct block_reader *br, struct strbuf *key);\n+\n+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);\n+\n+/* return < 0 for error, 0 for OK, > 0 for EOF. */\n+int block_iter_next(struct block_iter *it, struct reftable_record *rec);\n+\n+/* Seek to `want` with in the block pointed to by `it` */\n+int block_iter_seek(struct block_iter *it, struct strbuf *want);\n+\n+/* deallocate memory for `it`. The block reader and its block is left intact. */\n+void block_iter_close(struct block_iter *it);\n+\n+/* size of file header, depending on format version */\n+int header_size(int version);\n+\n+/* size of file footer, depending on format version */\n+int footer_size(int version);\n+\n+/* returns a block to its source. */\n+void reftable_block_done(struct reftable_block *ret);\n+\n+#endif\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nnew file mode 100644\nindex 00000000000..4b3ea262dcb\n--- /dev/null\n+++ b/reftable/block_test.c\n@@ -0,0 +1,120 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"block.h\"\n+\n+#include \"system.h\"\n+#include \"blocksource.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_block_read_write(void)\n+{\n+\tconst int header_off = 21; /* random */\n+\tchar *names[30];\n+\tconst int N = ARRAY_SIZE(names);\n+\tconst int block_size = 1024;\n+\tstruct reftable_block block = { NULL };\n+\tstruct block_writer bw = {\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_record rec = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\tint j = 0;\n+\tstruct strbuf want = STRBUF_INIT;\n+\n+\tblock.data = reftable_calloc(block_size);\n+\tblock.len = block_size;\n+\tblock.source = malloc_block_source();\n+\tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n+\t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n+\treftable_record_from_ref(&rec, &ref);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tsnprintf(name, sizeof(name), \"branch%02d\", i);\n+\t\tmemset(hash, i, sizeof(hash));\n+\n+\t\tref.refname = name;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\n+\t\tnames[i] = xstrdup(name);\n+\t\tn = block_writer_add(&bw, &rec);\n+\t\tref.refname = NULL;\n+\t\tref.value_type = REFTABLE_REF_DELETION;\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = block_writer_finish(&bw);\n+\tEXPECT(n > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);\n+\n+\tblock_reader_start(&br, &it);\n+\n+\twhile (1) {\n+\t\tint r = block_iter_next(&it, &rec);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT_STREQ(names[j], ref.refname);\n+\t\tj++;\n+\t}\n+\n+\treftable_record_release(&rec);\n+\tblock_iter_close(&it);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct block_iter it = { .last_key = STRBUF_INIT };\n+\t\tstrbuf_reset(&want);\n+\t\tstrbuf_addstr(&want, names[i]);\n+\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\n+\t\tEXPECT_STREQ(names[i], ref.refname);\n+\n+\t\twant.len--;\n+\t\tn = block_reader_seek(&br, &it, &want);\n+\t\tEXPECT(n == 0);\n+\n+\t\tn = block_iter_next(&it, &rec);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT_STREQ(names[10 * (i / 10)], ref.refname);\n+\n+\t\tblock_iter_close(&it);\n+\t}\n+\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\tstrbuf_release(&want);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+}\n+\n+int block_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_block_read_write);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 09d4b83ef9b..c9deeaf08c7 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,7 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n+\tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"438210","messageId":"854b30268b6dd7fcf0afe66d0a1c31de8ba46c09.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 12/19] reftable: read reftable files","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:08Z","receivedAt":"2021-10-07T20:25:41Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis supports reading a single reftable file.\n\nThe commit introduces an abstract iterator type, which captures the usecases\nboth of reading individual refs, and iterating over a segment of the ref\nnamespace.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/iter.c            | 194 +++++++++\n reftable/iter.h            |  69 ++++\n reftable/reader.c          | 801 +++++++++++++++++++++++++++++++++++++\n reftable/reader.h          |  64 +++\n reftable/reftable-reader.h | 101 +++++\n 6 files changed, 1231 insertions(+)\n create mode 100644 reftable/iter.c\n create mode 100644 reftable/iter.h\n create mode 100644 reftable/reader.c\n create mode 100644 reftable/reader.h\n create mode 100644 reftable/reftable-reader.h\n\ndiff --git a/Makefile b/Makefile\nindex abf1faf778a..f6b344cd8ac 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2440,7 +2440,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n+REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n REFTABLE_OBJS += reftable/generic.o\ndiff --git a/reftable/iter.c b/reftable/iter.c\nnew file mode 100644\nindex 00000000000..93d04f735b8\n--- /dev/null\n+++ b/reftable/iter.c\n@@ -0,0 +1,194 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"iter.h\"\n+\n+#include \"system.h\"\n+\n+#include \"block.h\"\n+#include \"generic.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"reftable-error.h\"\n+\n+int iterator_is_null(struct reftable_iterator *it)\n+{\n+\treturn !it->ops;\n+}\n+\n+static void filtering_ref_iterator_close(void *iter_arg)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstrbuf_release(&fri->oid);\n+\treftable_iterator_destroy(&fri->it);\n+}\n+\n+static int filtering_ref_iterator_next(void *iter_arg,\n+\t\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct filtering_ref_iterator *fri = iter_arg;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\tint err = 0;\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&fri->it, ref);\n+\t\tif (err != 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (fri->double_check) {\n+\t\t\tstruct reftable_iterator it = { NULL };\n+\n+\t\t\terr = reftable_table_seek_ref(&fri->tab, &it,\n+\t\t\t\t\t\t      ref->refname);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = reftable_iterator_next_ref(&it, ref);\n+\t\t\t}\n+\n+\t\t\treftable_iterator_destroy(&it);\n+\n+\t\t\tif (err < 0) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (err > 0) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL2 &&\n+\t\t    (!memcmp(fri->oid.buf, ref->value.val2.target_value,\n+\t\t\t     fri->oid.len) ||\n+\t\t     !memcmp(fri->oid.buf, ref->value.val2.value,\n+\t\t\t     fri->oid.len)))\n+\t\t\treturn 0;\n+\n+\t\tif (ref->value_type == REFTABLE_REF_VAL1 &&\n+\t\t    !memcmp(fri->oid.buf, ref->value.val1, fri->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treftable_ref_record_release(ref);\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable filtering_ref_iterator_vtable = {\n+\t.next = &filtering_ref_iterator_next,\n+\t.close = &filtering_ref_iterator_close,\n+};\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *it,\n+\t\t\t\t\t  struct filtering_ref_iterator *fri)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = fri;\n+\tit->ops = &filtering_ref_iterator_vtable;\n+}\n+\n+static void indexed_table_ref_iter_close(void *p)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tblock_iter_close(&it->cur);\n+\treftable_block_done(&it->block_reader.block);\n+\treftable_free(it->offsets);\n+\tstrbuf_release(&it->oid);\n+}\n+\n+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)\n+{\n+\tuint64_t off;\n+\tint err = 0;\n+\tif (it->offset_idx == it->offset_len) {\n+\t\tit->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\n+\treftable_block_done(&it->block_reader.block);\n+\n+\toff = it->offsets[it->offset_idx++];\n+\terr = reader_init_block_reader(it->r, &it->block_reader, off,\n+\t\t\t\t       BLOCK_TYPE_REF);\n+\tif (err < 0) {\n+\t\treturn err;\n+\t}\n+\tif (err > 0) {\n+\t\t/* indexed block does not exist. */\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\t}\n+\tblock_reader_start(&it->block_reader, &it->cur);\n+\treturn 0;\n+}\n+\n+static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)\n+{\n+\tstruct indexed_table_ref_iter *it = p;\n+\tstruct reftable_ref_record *ref = rec->data;\n+\n+\twhile (1) {\n+\t\tint err = block_iter_next(&it->cur, rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\terr = indexed_table_ref_iter_next_block(it);\n+\t\t\tif (err < 0) {\n+\t\t\t\treturn err;\n+\t\t\t}\n+\n+\t\t\tif (it->is_finished) {\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\t/* BUG */\n+\t\tif (!memcmp(it->oid.buf, ref->value.val2.target_value,\n+\t\t\t    it->oid.len) ||\n+\t\t    !memcmp(it->oid.buf, ref->value.val2.value, it->oid.len)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+}\n+\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len)\n+{\n+\tstruct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT;\n+\tstruct indexed_table_ref_iter *itr =\n+\t\treftable_calloc(sizeof(struct indexed_table_ref_iter));\n+\tint err = 0;\n+\n+\t*itr = empty;\n+\titr->r = r;\n+\tstrbuf_add(&itr->oid, oid, oid_len);\n+\n+\titr->offsets = offsets;\n+\titr->offset_len = offset_len;\n+\n+\terr = indexed_table_ref_iter_next_block(itr);\n+\tif (err < 0) {\n+\t\treftable_free(itr);\n+\t} else {\n+\t\t*dest = itr;\n+\t}\n+\treturn err;\n+}\n+\n+static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {\n+\t.next = &indexed_table_ref_iter_next,\n+\t.close = &indexed_table_ref_iter_close,\n+};\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = itr;\n+\tit->ops = &indexed_table_ref_iter_vtable;\n+}\ndiff --git a/reftable/iter.h b/reftable/iter.h\nnew file mode 100644\nindex 00000000000..09eb0cbfa59\n--- /dev/null\n+++ b/reftable/iter.h\n@@ -0,0 +1,69 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef ITER_H\n+#define ITER_H\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"record.h\"\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-generic.h\"\n+\n+/* Returns true for a zeroed out iterator, such as the one returned from\n+ * iterator_destroy. */\n+int iterator_is_null(struct reftable_iterator *it);\n+\n+/* iterator that produces only ref records that point to `oid` */\n+struct filtering_ref_iterator {\n+\tint double_check;\n+\tstruct reftable_table tab;\n+\tstruct strbuf oid;\n+\tstruct reftable_iterator it;\n+};\n+#define FILTERING_REF_ITERATOR_INIT \\\n+\t{                           \\\n+\t\t.oid = STRBUF_INIT  \\\n+\t}\n+\n+void iterator_from_filtering_ref_iterator(struct reftable_iterator *,\n+\t\t\t\t\t  struct filtering_ref_iterator *);\n+\n+/* iterator that produces only ref records that point to `oid`,\n+ * but using the object index.\n+ */\n+struct indexed_table_ref_iter {\n+\tstruct reftable_reader *r;\n+\tstruct strbuf oid;\n+\n+\t/* mutable */\n+\tuint64_t *offsets;\n+\n+\t/* Points to the next offset to read. */\n+\tint offset_idx;\n+\tint offset_len;\n+\tstruct block_reader block_reader;\n+\tstruct block_iter cur;\n+\tint is_finished;\n+};\n+\n+#define INDEXED_TABLE_REF_ITER_INIT                                     \\\n+\t{                                                               \\\n+\t\t.cur = { .last_key = STRBUF_INIT }, .oid = STRBUF_INIT, \\\n+\t}\n+\n+void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,\n+\t\t\t\t\t  struct indexed_table_ref_iter *itr);\n+\n+/* Takes ownership of `offsets` */\n+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,\n+\t\t\t       struct reftable_reader *r, uint8_t *oid,\n+\t\t\t       int oid_len, uint64_t *offsets, int offset_len);\n+\n+#endif\ndiff --git a/reftable/reader.c b/reftable/reader.c\nnew file mode 100644\nindex 00000000000..006709a645a\n--- /dev/null\n+++ b/reftable/reader.c\n@@ -0,0 +1,801 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"reader.h\"\n+\n+#include \"system.h\"\n+#include \"block.h\"\n+#include \"constants.h\"\n+#include \"generic.h\"\n+#include \"iter.h\"\n+#include \"record.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-generic.h\"\n+#include \"tree.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source)\n+{\n+\treturn source->ops->size(source->arg);\n+}\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size)\n+{\n+\tint result = source->ops->read_block(source->arg, dest, off, size);\n+\tdest->source = *source;\n+\treturn result;\n+}\n+\n+void block_source_close(struct reftable_block_source *source)\n+{\n+\tif (!source->ops) {\n+\t\treturn;\n+\t}\n+\n+\tsource->ops->close(source->arg);\n+\tsource->ops = NULL;\n+}\n+\n+static struct reftable_reader_offsets *\n+reader_offsets_for(struct reftable_reader *r, uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\t\treturn &r->ref_offsets;\n+\tcase BLOCK_TYPE_LOG:\n+\t\treturn &r->log_offsets;\n+\tcase BLOCK_TYPE_OBJ:\n+\t\treturn &r->obj_offsets;\n+\t}\n+\tabort();\n+}\n+\n+static int reader_get_block(struct reftable_reader *r,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t sz)\n+{\n+\tif (off >= r->size)\n+\t\treturn 0;\n+\n+\tif (off + sz > r->size) {\n+\t\tsz = r->size - off;\n+\t}\n+\n+\treturn block_source_read_block(&r->source, dest, off, sz);\n+}\n+\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r)\n+{\n+\treturn r->hash_id;\n+}\n+\n+const char *reader_name(struct reftable_reader *r)\n+{\n+\treturn r->name;\n+}\n+\n+static int parse_footer(struct reftable_reader *r, uint8_t *footer,\n+\t\t\tuint8_t *header)\n+{\n+\tuint8_t *f = footer;\n+\tuint8_t first_block_typ;\n+\tint err = 0;\n+\tuint32_t computed_crc;\n+\tuint32_t file_crc;\n+\n+\tif (memcmp(f, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tf += 4;\n+\n+\tif (memcmp(footer, header, header_size(r->version))) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tf++;\n+\tr->block_size = get_be24(f);\n+\n+\tf += 3;\n+\tr->min_update_index = get_be64(f);\n+\tf += 8;\n+\tr->max_update_index = get_be64(f);\n+\tf += 8;\n+\n+\tif (r->version == 1) {\n+\t\tr->hash_id = GIT_SHA1_FORMAT_ID;\n+\t} else {\n+\t\tr->hash_id = get_be32(f);\n+\t\tswitch (r->hash_id) {\n+\t\tcase GIT_SHA1_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tcase GIT_SHA256_FORMAT_ID:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tf += 4;\n+\t}\n+\n+\tr->ref_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->obj_offsets.offset = get_be64(f);\n+\tf += 8;\n+\n+\tr->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);\n+\tr->obj_offsets.offset >>= 5;\n+\n+\tr->obj_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.offset = get_be64(f);\n+\tf += 8;\n+\tr->log_offsets.index_offset = get_be64(f);\n+\tf += 8;\n+\n+\tcomputed_crc = crc32(0, footer, f - footer);\n+\tfile_crc = get_be32(f);\n+\tf += 4;\n+\tif (computed_crc != file_crc) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tfirst_block_typ = header[header_size(r->version)];\n+\tr->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF);\n+\tr->ref_offsets.offset = 0;\n+\tr->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG ||\n+\t\t\t\t     r->log_offsets.offset > 0);\n+\tr->obj_offsets.is_present = r->obj_offsets.offset > 0;\n+\terr = 0;\n+done:\n+\treturn err;\n+}\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name)\n+{\n+\tstruct reftable_block footer = { NULL };\n+\tstruct reftable_block header = { NULL };\n+\tint err = 0;\n+\tuint64_t file_size = block_source_size(source);\n+\n+\t/* Need +1 to read type of first block. */\n+\tuint32_t read_size = header_size(2) + 1; /* read v2 because it's larger.  */\n+\tmemset(r, 0, sizeof(struct reftable_reader));\n+\n+\tif (read_size > file_size) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = block_source_read_block(source, &header, 0, read_size);\n+\tif (err != read_size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tif (memcmp(header.data, \"REFT\", 4)) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\tr->version = header.data[4];\n+\tif (r->version != 1 && r->version != 2) {\n+\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tr->size = file_size - footer_size(r->version);\n+\tr->source = *source;\n+\tr->name = xstrdup(name);\n+\tr->hash_id = 0;\n+\n+\terr = block_source_read_block(source, &footer, r->size,\n+\t\t\t\t      footer_size(r->version));\n+\tif (err != footer_size(r->version)) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = parse_footer(r, footer.data, header.data);\n+done:\n+\treftable_block_done(&footer);\n+\treftable_block_done(&header);\n+\treturn err;\n+}\n+\n+struct table_iter {\n+\tstruct reftable_reader *r;\n+\tuint8_t typ;\n+\tuint64_t block_off;\n+\tstruct block_iter bi;\n+\tint is_finished;\n+};\n+#define TABLE_ITER_INIT                          \\\n+\t{                                        \\\n+\t\t.bi = {.last_key = STRBUF_INIT } \\\n+\t}\n+\n+static void table_iter_copy_from(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = src->block_off;\n+\tdest->is_finished = src->is_finished;\n+\tblock_iter_copy_from(&dest->bi, &src->bi);\n+}\n+\n+static int table_iter_next_in_block(struct table_iter *ti,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tint res = block_iter_next(&ti->bi, rec);\n+\tif (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {\n+\t\t((struct reftable_ref_record *)rec->data)->update_index +=\n+\t\t\tti->r->min_update_index;\n+\t}\n+\n+\treturn res;\n+}\n+\n+static void table_iter_block_done(struct table_iter *ti)\n+{\n+\tif (!ti->bi.br) {\n+\t\treturn;\n+\t}\n+\treftable_block_done(&ti->bi.br->block);\n+\tFREE_AND_NULL(ti->bi.br);\n+\n+\tti->bi.last_key.len = 0;\n+\tti->bi.next_off = 0;\n+}\n+\n+static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off,\n+\t\t\t\t  int version)\n+{\n+\tint32_t result = 0;\n+\n+\tif (off == 0) {\n+\t\tdata += header_size(version);\n+\t}\n+\n+\t*typ = data[0];\n+\tif (reftable_is_block_type(*typ)) {\n+\t\tresult = get_be24(data + 1);\n+\t}\n+\treturn result;\n+}\n+\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ)\n+{\n+\tint32_t guess_block_size = r->block_size ? r->block_size :\n+\t\t\t\t\t\t\t DEFAULT_BLOCK_SIZE;\n+\tstruct reftable_block block = { NULL };\n+\tuint8_t block_typ = 0;\n+\tint err = 0;\n+\tuint32_t header_off = next_off ? 0 : header_size(r->version);\n+\tint32_t block_size = 0;\n+\n+\tif (next_off >= r->size)\n+\t\treturn 1;\n+\n+\terr = reader_get_block(r, &block, next_off, guess_block_size);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tblock_size = extract_block_size(block.data, &block_typ, next_off,\n+\t\t\t\t\tr->version);\n+\tif (block_size < 0)\n+\t\treturn block_size;\n+\n+\tif (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {\n+\t\treftable_block_done(&block);\n+\t\treturn 1;\n+\t}\n+\n+\tif (block_size > guess_block_size) {\n+\t\treftable_block_done(&block);\n+\t\terr = reader_get_block(r, &block, next_off, block_size);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn block_reader_init(br, &block, header_off, r->block_size,\n+\t\t\t\t hash_size(r->hash_id));\n+}\n+\n+static int table_iter_next_block(struct table_iter *dest,\n+\t\t\t\t struct table_iter *src)\n+{\n+\tuint64_t next_block_off = src->block_off + src->bi.br->full_block_size;\n+\tstruct block_reader br = { 0 };\n+\tint err = 0;\n+\n+\tdest->r = src->r;\n+\tdest->typ = src->typ;\n+\tdest->block_off = next_block_off;\n+\n+\terr = reader_init_block_reader(src->r, &br, next_block_off, src->typ);\n+\tif (err > 0) {\n+\t\tdest->is_finished = 1;\n+\t\treturn 1;\n+\t}\n+\tif (err != 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct block_reader *brp =\n+\t\t\treftable_malloc(sizeof(struct block_reader));\n+\t\t*brp = br;\n+\n+\t\tdest->is_finished = 0;\n+\t\tblock_reader_start(brp, &dest->bi);\n+\t}\n+\treturn 0;\n+}\n+\n+static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)\n+{\n+\tif (reftable_record_type(rec) != ti->typ)\n+\t\treturn REFTABLE_API_ERROR;\n+\n+\twhile (1) {\n+\t\tstruct table_iter next = TABLE_ITER_INIT;\n+\t\tint err = 0;\n+\t\tif (ti->is_finished) {\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\terr = table_iter_next_in_block(ti, rec);\n+\t\tif (err <= 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err != 0) {\n+\t\t\tti->is_finished = 1;\n+\t\t}\n+\t\ttable_iter_block_done(ti);\n+\t\tif (err != 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\ttable_iter_copy_from(ti, &next);\n+\t\tblock_iter_close(&next.bi);\n+\t}\n+}\n+\n+static int table_iter_next_void(void *ti, struct reftable_record *rec)\n+{\n+\treturn table_iter_next(ti, rec);\n+}\n+\n+static void table_iter_close(void *p)\n+{\n+\tstruct table_iter *ti = p;\n+\ttable_iter_block_done(ti);\n+\tblock_iter_close(&ti->bi);\n+}\n+\n+static struct reftable_iterator_vtable table_iter_vtable = {\n+\t.next = &table_iter_next_void,\n+\t.close = &table_iter_close,\n+};\n+\n+static void iterator_from_table_iter(struct reftable_iterator *it,\n+\t\t\t\t     struct table_iter *ti)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = ti;\n+\tit->ops = &table_iter_vtable;\n+}\n+\n+static int reader_table_iter_at(struct reftable_reader *r,\n+\t\t\t\tstruct table_iter *ti, uint64_t off,\n+\t\t\t\tuint8_t typ)\n+{\n+\tstruct block_reader br = { 0 };\n+\tstruct block_reader *brp = NULL;\n+\n+\tint err = reader_init_block_reader(r, &br, off, typ);\n+\tif (err != 0)\n+\t\treturn err;\n+\n+\tbrp = reftable_malloc(sizeof(struct block_reader));\n+\t*brp = br;\n+\tti->r = r;\n+\tti->typ = block_reader_type(brp);\n+\tti->block_off = off;\n+\tblock_reader_start(brp, &ti->bi);\n+\treturn 0;\n+}\n+\n+static int reader_start(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\tuint8_t typ, int index)\n+{\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tuint64_t off = offs->offset;\n+\tif (index) {\n+\t\toff = offs->index_offset;\n+\t\tif (off == 0) {\n+\t\t\treturn 1;\n+\t\t}\n+\t\ttyp = BLOCK_TYPE_INDEX;\n+\t}\n+\n+\treturn reader_table_iter_at(r, ti, off, typ);\n+}\n+\n+static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,\n+\t\t\t      struct reftable_record *want)\n+{\n+\tstruct reftable_record rec =\n+\t\treftable_new_record(reftable_record_type(want));\n+\tstruct strbuf want_key = STRBUF_INIT;\n+\tstruct strbuf got_key = STRBUF_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = -1;\n+\n+\treftable_record_key(want, &want_key);\n+\n+\twhile (1) {\n+\t\terr = table_iter_next_block(&next, ti);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = block_reader_first_key(next.bi.br, &got_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (strbuf_cmp(&got_key, &want_key) > 0) {\n+\t\t\ttable_iter_block_done(&next);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_block_done(ti);\n+\t\ttable_iter_copy_from(ti, &next);\n+\t}\n+\n+\terr = block_iter_seek(&ti->bi, &want_key);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = 0;\n+\n+done:\n+\tblock_iter_close(&next.bi);\n+\treftable_record_destroy(&rec);\n+\tstrbuf_release(&want_key);\n+\tstrbuf_release(&got_key);\n+\treturn err;\n+}\n+\n+static int reader_seek_indexed(struct reftable_reader *r,\n+\t\t\t       struct reftable_iterator *it,\n+\t\t\t       struct reftable_record *rec)\n+{\n+\tstruct reftable_index_record want_index = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record want_index_rec = { NULL };\n+\tstruct reftable_index_record index_result = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record index_result_rec = { NULL };\n+\tstruct table_iter index_iter = TABLE_ITER_INIT;\n+\tstruct table_iter next = TABLE_ITER_INIT;\n+\tint err = 0;\n+\n+\treftable_record_key(rec, &want_index.last_key);\n+\treftable_record_from_index(&want_index_rec, &want_index);\n+\treftable_record_from_index(&index_result_rec, &index_result);\n+\n+\terr = reader_start(r, &index_iter, reftable_record_type(rec), 1);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reader_seek_linear(r, &index_iter, &want_index_rec);\n+\twhile (1) {\n+\t\terr = table_iter_next(&index_iter, &index_result_rec);\n+\t\ttable_iter_block_done(&index_iter);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = reader_table_iter_at(r, &next, index_result.offset, 0);\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\n+\t\terr = block_iter_seek(&next.bi, &want_index.last_key);\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (next.typ == reftable_record_type(rec)) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (next.typ != BLOCK_TYPE_INDEX) {\n+\t\t\terr = REFTABLE_FORMAT_ERROR;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\ttable_iter_copy_from(&index_iter, &next);\n+\t}\n+\n+\tif (err == 0) {\n+\t\tstruct table_iter empty = TABLE_ITER_INIT;\n+\t\tstruct table_iter *malloced =\n+\t\t\treftable_calloc(sizeof(struct table_iter));\n+\t\t*malloced = empty;\n+\t\ttable_iter_copy_from(malloced, &next);\n+\t\titerator_from_table_iter(it, malloced);\n+\t}\n+done:\n+\tblock_iter_close(&next.bi);\n+\ttable_iter_close(&index_iter);\n+\treftable_record_release(&want_index_rec);\n+\treftable_record_release(&index_result_rec);\n+\treturn err;\n+}\n+\n+static int reader_seek_internal(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it,\n+\t\t\t\tstruct reftable_record *rec)\n+{\n+\tstruct reftable_reader_offsets *offs =\n+\t\treader_offsets_for(r, reftable_record_type(rec));\n+\tuint64_t idx = offs->index_offset;\n+\tstruct table_iter ti = TABLE_ITER_INIT;\n+\tint err = 0;\n+\tif (idx > 0)\n+\t\treturn reader_seek_indexed(r, it, rec);\n+\n+\terr = reader_start(r, &ti, reftable_record_type(rec), 0);\n+\tif (err < 0)\n+\t\treturn err;\n+\terr = reader_seek_linear(r, &ti, rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\telse {\n+\t\tstruct table_iter *p =\n+\t\t\treftable_malloc(sizeof(struct table_iter));\n+\t\t*p = ti;\n+\t\titerator_from_table_iter(it, p);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,\n+\t\t       struct reftable_record *rec)\n+{\n+\tuint8_t typ = reftable_record_type(rec);\n+\n+\tstruct reftable_reader_offsets *offs = reader_offsets_for(r, typ);\n+\tif (!offs->is_present) {\n+\t\titerator_set_empty(it);\n+\t\treturn 0;\n+\t}\n+\n+\treturn reader_seek_internal(r, it, rec);\n+}\n+\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn reader_seek(r, it, &rec);\n+}\n+\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_reader_seek_log_at(r, it, name, max);\n+}\n+\n+void reader_close(struct reftable_reader *r)\n+{\n+\tblock_source_close(&r->source);\n+\tFREE_AND_NULL(r->name);\n+}\n+\n+int reftable_new_reader(struct reftable_reader **p,\n+\t\t\tstruct reftable_block_source *src, char const *name)\n+{\n+\tstruct reftable_reader *rd =\n+\t\treftable_calloc(sizeof(struct reftable_reader));\n+\tint err = init_reader(rd, src, name);\n+\tif (err == 0) {\n+\t\t*p = rd;\n+\t} else {\n+\t\tblock_source_close(src);\n+\t\treftable_free(rd);\n+\t}\n+\treturn err;\n+}\n+\n+void reftable_reader_free(struct reftable_reader *r)\n+{\n+\treader_close(r);\n+\treftable_free(r);\n+}\n+\n+static int reftable_reader_refs_for_indexed(struct reftable_reader *r,\n+\t\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t\t    uint8_t *oid)\n+{\n+\tstruct reftable_obj_record want = {\n+\t\t.hash_prefix = oid,\n+\t\t.hash_prefix_len = r->object_id_len,\n+\t};\n+\tstruct reftable_record want_rec = { NULL };\n+\tstruct reftable_iterator oit = { NULL };\n+\tstruct reftable_obj_record got = { NULL };\n+\tstruct reftable_record got_rec = { NULL };\n+\tint err = 0;\n+\tstruct indexed_table_ref_iter *itr = NULL;\n+\n+\t/* Look through the reverse index. */\n+\treftable_record_from_obj(&want_rec, &want);\n+\terr = reader_seek(r, &oit, &want_rec);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\t/* read out the reftable_obj_record */\n+\treftable_record_from_obj(&got_rec, &got);\n+\terr = iterator_next(&oit, &got_rec);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 0 ||\n+\t    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {\n+\t\t/* didn't find it; return empty iterator */\n+\t\titerator_set_empty(it);\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\terr = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),\n+\t\t\t\t\t got.offsets, got.offset_len);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tgot.offsets = NULL;\n+\titerator_from_indexed_table_ref_iter(it, itr);\n+\n+done:\n+\treftable_iterator_destroy(&oit);\n+\treftable_record_release(&got_rec);\n+\treturn err;\n+}\n+\n+static int reftable_reader_refs_for_unindexed(struct reftable_reader *r,\n+\t\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t\t      uint8_t *oid)\n+{\n+\tstruct table_iter ti_empty = TABLE_ITER_INIT;\n+\tstruct table_iter *ti = reftable_calloc(sizeof(struct table_iter));\n+\tstruct filtering_ref_iterator *filter = NULL;\n+\tstruct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT;\n+\tint oid_len = hash_size(r->hash_id);\n+\tint err;\n+\n+\t*ti = ti_empty;\n+\terr = reader_start(r, ti, BLOCK_TYPE_REF, 0);\n+\tif (err < 0) {\n+\t\treftable_free(ti);\n+\t\treturn err;\n+\t}\n+\n+\tfilter = reftable_malloc(sizeof(struct filtering_ref_iterator));\n+\t*filter = empty;\n+\n+\tstrbuf_add(&filter->oid, oid, oid_len);\n+\treftable_table_from_reader(&filter->tab, r);\n+\tfilter->double_check = 0;\n+\titerator_from_table_iter(&filter->it, ti);\n+\n+\titerator_from_filtering_ref_iterator(it, filter);\n+\treturn 0;\n+}\n+\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid)\n+{\n+\tif (r->obj_offsets.is_present)\n+\t\treturn reftable_reader_refs_for_indexed(r, it, oid);\n+\treturn reftable_reader_refs_for_unindexed(r, it, oid);\n+}\n+\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r)\n+{\n+\treturn r->max_update_index;\n+}\n+\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r)\n+{\n+\treturn r->min_update_index;\n+}\n+\n+/* generic table interface. */\n+\n+static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,\n+\t\t\t\t     struct reftable_record *rec)\n+{\n+\treturn reader_seek(tab, it, rec);\n+}\n+\n+static uint32_t reftable_reader_hash_id_void(void *tab)\n+{\n+\treturn reftable_reader_hash_id(tab);\n+}\n+\n+static uint64_t reftable_reader_min_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_reader_max_update_index_void(void *tab)\n+{\n+\treturn reftable_reader_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable reader_vtable = {\n+\t.seek_record = reftable_reader_seek_void,\n+\t.hash_id = reftable_reader_hash_id_void,\n+\t.min_update_index = reftable_reader_min_update_index_void,\n+\t.max_update_index = reftable_reader_max_update_index_void,\n+};\n+\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &reader_vtable;\n+\ttab->table_arg = reader;\n+}\n+\n+\n+int reftable_reader_print_file(const char *tablename)\n+{\n+\tstruct reftable_block_source src = { NULL };\n+\tint err = reftable_block_source_from_file(&src, tablename);\n+\tstruct reftable_reader *r = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&r, &src, tablename);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\treftable_table_from_reader(&tab, r);\n+\terr = reftable_table_print(&tab);\n+done:\n+\treftable_reader_free(r);\n+\treturn err;\n+}\ndiff --git a/reftable/reader.h b/reftable/reader.h\nnew file mode 100644\nindex 00000000000..e869165f234\n--- /dev/null\n+++ b/reftable/reader.h\n@@ -0,0 +1,64 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef READER_H\n+#define READER_H\n+\n+#include \"block.h\"\n+#include \"record.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+\n+uint64_t block_source_size(struct reftable_block_source *source);\n+\n+int block_source_read_block(struct reftable_block_source *source,\n+\t\t\t    struct reftable_block *dest, uint64_t off,\n+\t\t\t    uint32_t size);\n+void block_source_close(struct reftable_block_source *source);\n+\n+/* metadata for a block type */\n+struct reftable_reader_offsets {\n+\tint is_present;\n+\tuint64_t offset;\n+\tuint64_t index_offset;\n+};\n+\n+/* The state for reading a reftable file. */\n+struct reftable_reader {\n+\t/* for convience, associate a name with the instance. */\n+\tchar *name;\n+\tstruct reftable_block_source source;\n+\n+\t/* Size of the file, excluding the footer. */\n+\tuint64_t size;\n+\n+\t/* 'sha1' for SHA1, 's256' for SHA-256 */\n+\tuint32_t hash_id;\n+\n+\tuint32_t block_size;\n+\tuint64_t min_update_index;\n+\tuint64_t max_update_index;\n+\t/* Length of the OID keys in the 'o' section */\n+\tint object_id_len;\n+\tint version;\n+\n+\tstruct reftable_reader_offsets ref_offsets;\n+\tstruct reftable_reader_offsets obj_offsets;\n+\tstruct reftable_reader_offsets log_offsets;\n+};\n+\n+int init_reader(struct reftable_reader *r, struct reftable_block_source *source,\n+\t\tconst char *name);\n+void reader_close(struct reftable_reader *r);\n+const char *reader_name(struct reftable_reader *r);\n+\n+/* initialize a block reader to read from `r` */\n+int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,\n+\t\t\t     uint64_t next_off, uint8_t want_typ);\n+\n+#endif\ndiff --git a/reftable/reftable-reader.h b/reftable/reftable-reader.h\nnew file mode 100644\nindex 00000000000..4a4bc2fdf85\n--- /dev/null\n+++ b/reftable/reftable-reader.h\n@@ -0,0 +1,101 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_READER_H\n+#define REFTABLE_READER_H\n+\n+#include \"reftable-iterator.h\"\n+#include \"reftable-blocksource.h\"\n+\n+/*\n+ * Reading single tables\n+ *\n+ * The follow routines are for reading single files. For an\n+ * application-level interface, skip ahead to struct\n+ * reftable_merged_table and struct reftable_stack.\n+ */\n+\n+/* The reader struct is a handle to an open reftable file. */\n+struct reftable_reader;\n+\n+/* Generic table. */\n+struct reftable_table;\n+\n+/* reftable_new_reader opens a reftable for reading. If successful,\n+ * returns 0 code and sets pp. The name is used for creating a\n+ * stack. Typically, it is the basename of the file. The block source\n+ * `src` is owned by the reader, and is closed on calling\n+ * reftable_reader_destroy(). On error, the block source `src` is\n+ * closed as well.\n+ */\n+int reftable_new_reader(struct reftable_reader **pp,\n+\t\t\tstruct reftable_block_source *src, const char *name);\n+\n+/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted\n+   in the table.  To seek to the start of the table, use name = \"\".\n+\n+   example:\n+\n+   struct reftable_reader *r = NULL;\n+   int err = reftable_new_reader(&r, &src, \"filename\");\n+   if (err < 0) { ... }\n+   struct reftable_iterator it  = {0};\n+   err = reftable_reader_seek_ref(r, &it, \"refs/heads/master\");\n+   if (err < 0) { ... }\n+   struct reftable_ref_record ref  = {0};\n+   while (1) {\n+   err = reftable_iterator_next_ref(&it, &ref);\n+   if (err > 0) {\n+   break;\n+   }\n+   if (err < 0) {\n+   ..error handling..\n+   }\n+   ..found..\n+   }\n+   reftable_iterator_destroy(&it);\n+   reftable_ref_record_release(&ref);\n+*/\n+int reftable_reader_seek_ref(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* returns the hash ID used in this table. */\n+uint32_t reftable_reader_hash_id(struct reftable_reader *r);\n+\n+/* seek to logs for the given name, older than update_index. To seek to the\n+   start of the table, use name = \"\".\n+*/\n+int reftable_reader_seek_log_at(struct reftable_reader *r,\n+\t\t\t\tstruct reftable_iterator *it, const char *name,\n+\t\t\t\tuint64_t update_index);\n+\n+/* seek to newest log entry for given name. */\n+int reftable_reader_seek_log(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, const char *name);\n+\n+/* closes and deallocates a reader. */\n+void reftable_reader_free(struct reftable_reader *);\n+\n+/* return an iterator for the refs pointing to `oid`. */\n+int reftable_reader_refs_for(struct reftable_reader *r,\n+\t\t\t     struct reftable_iterator *it, uint8_t *oid);\n+\n+/* return the max_update_index for a table */\n+uint64_t reftable_reader_max_update_index(struct reftable_reader *r);\n+\n+/* return the min_update_index for a table */\n+uint64_t reftable_reader_min_update_index(struct reftable_reader *r);\n+\n+/* creates a generic table from a file reader. */\n+void reftable_table_from_reader(struct reftable_table *tab,\n+\t\t\t\tstruct reftable_reader *reader);\n+\n+/* print table onto stdout for debugging. */\n+int reftable_reader_print_file(const char *tablename);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"438211","messageId":"f2cd28762651c3640287b6ef0ae0b52c60bcf68d.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 06/19] reftable: (de)serialization for the polymorphic record type.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:02Z","receivedAt":"2021-10-07T20:25:42Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe reftable format is structured as a sequence of blocks, and each block\ncontains a sequence of prefix-compressed key-value records. There are 4 types of\nrecords, and they have similarities in how they must be handled. This is\nachieved by introducing a polymorphic 'record' type that encapsulates ref, log,\nindex and object records.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |    2 +\n reftable/constants.h       |   21 +\n reftable/record.c          | 1212 ++++++++++++++++++++++++++++++++++++\n reftable/record.h          |  139 +++++\n reftable/record_test.c     |  412 ++++++++++++\n reftable/reftable-record.h |  114 ++++\n t/helper/test-reftable.c   |    2 +-\n 7 files changed, 1901 insertions(+), 1 deletion(-)\n create mode 100644 reftable/constants.h\n create mode 100644 reftable/record.c\n create mode 100644 reftable/record.h\n create mode 100644 reftable/record_test.c\n create mode 100644 reftable/reftable-record.h\n\ndiff --git a/Makefile b/Makefile\nindex bfdac1e04c4..b57b2b0e78e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2433,7 +2433,9 @@ REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/record.o\n \n+REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n \ndiff --git a/reftable/constants.h b/reftable/constants.h\nnew file mode 100644\nindex 00000000000..5eee72c4c11\n--- /dev/null\n+++ b/reftable/constants.h\n@@ -0,0 +1,21 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef CONSTANTS_H\n+#define CONSTANTS_H\n+\n+#define BLOCK_TYPE_LOG 'g'\n+#define BLOCK_TYPE_INDEX 'i'\n+#define BLOCK_TYPE_REF 'r'\n+#define BLOCK_TYPE_OBJ 'o'\n+#define BLOCK_TYPE_ANY 0\n+\n+#define MAX_RESTARTS ((1 << 16) - 1)\n+#define DEFAULT_BLOCK_SIZE 4096\n+\n+#endif\ndiff --git a/reftable/record.c b/reftable/record.c\nnew file mode 100644\nindex 00000000000..6a5dac32dc6\n--- /dev/null\n+++ b/reftable/record.c\n@@ -0,0 +1,1212 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+/* record.c - methods for different types of records. */\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"constants.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+\n+int get_var_int(uint64_t *dest, struct string_view *in)\n+{\n+\tint ptr = 0;\n+\tuint64_t val;\n+\n+\tif (in->len == 0)\n+\t\treturn -1;\n+\tval = in->buf[ptr] & 0x7f;\n+\n+\twhile (in->buf[ptr] & 0x80) {\n+\t\tptr++;\n+\t\tif (ptr > in->len) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tval = (val + 1) << 7 | (uint64_t)(in->buf[ptr] & 0x7f);\n+\t}\n+\n+\t*dest = val;\n+\treturn ptr + 1;\n+}\n+\n+int put_var_int(struct string_view *dest, uint64_t val)\n+{\n+\tuint8_t buf[10] = { 0 };\n+\tint i = 9;\n+\tint n = 0;\n+\tbuf[i] = (uint8_t)(val & 0x7f);\n+\ti--;\n+\twhile (1) {\n+\t\tval >>= 7;\n+\t\tif (!val) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tval--;\n+\t\tbuf[i] = 0x80 | (uint8_t)(val & 0x7f);\n+\t\ti--;\n+\t}\n+\n+\tn = sizeof(buf) - i - 1;\n+\tif (dest->len < n)\n+\t\treturn -1;\n+\tmemcpy(dest->buf, &buf[i + 1], n);\n+\treturn n;\n+}\n+\n+int reftable_is_block_type(uint8_t typ)\n+{\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF:\n+\tcase BLOCK_TYPE_LOG:\n+\tcase BLOCK_TYPE_OBJ:\n+\tcase BLOCK_TYPE_INDEX:\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn rec->value.val1;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)\n+{\n+\tswitch (rec->value_type) {\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn rec->value.val2.target_value;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static int decode_string(struct strbuf *dest, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t tsize = 0;\n+\tint n = get_var_int(&tsize, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\tif (in.len < tsize)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, in.buf, tsize);\n+\tstring_view_consume(&in, tsize);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static int encode_string(char *str, struct string_view s)\n+{\n+\tstruct string_view start = s;\n+\tint l = strlen(str);\n+\tint n = put_var_int(&s, l);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\tif (s.len < l)\n+\t\treturn -1;\n+\tmemcpy(s.buf, str, l);\n+\tstring_view_consume(&s, l);\n+\n+\treturn start.len - s.len;\n+}\n+\n+int reftable_encode_key(int *restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra)\n+{\n+\tstruct string_view start = dest;\n+\tint prefix_len = common_prefix_size(&prev_key, &key);\n+\tuint64_t suffix_len = key.len - prefix_len;\n+\tint n = put_var_int(&dest, (uint64_t)prefix_len);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\t*restart = (prefix_len == 0);\n+\n+\tn = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&dest, n);\n+\n+\tif (dest.len < suffix_len)\n+\t\treturn -1;\n+\tmemcpy(dest.buf, key.buf + prefix_len, suffix_len);\n+\tstring_view_consume(&dest, suffix_len);\n+\n+\treturn start.len - dest.len;\n+}\n+\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in)\n+{\n+\tint start_len = in.len;\n+\tuint64_t prefix_len = 0;\n+\tuint64_t suffix_len = 0;\n+\tint n = get_var_int(&prefix_len, &in);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\tif (prefix_len > last_key.len)\n+\t\treturn -1;\n+\n+\tn = get_var_int(&suffix_len, &in);\n+\tif (n <= 0)\n+\t\treturn -1;\n+\tstring_view_consume(&in, n);\n+\n+\t*extra = (uint8_t)(suffix_len & 0x7);\n+\tsuffix_len >>= 3;\n+\n+\tif (in.len < suffix_len)\n+\t\treturn -1;\n+\n+\tstrbuf_reset(key);\n+\tstrbuf_add(key, last_key.buf, prefix_len);\n+\tstrbuf_add(key, in.buf, suffix_len);\n+\tstring_view_consume(&in, suffix_len);\n+\n+\treturn start_len - in.len;\n+}\n+\n+static void reftable_ref_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_ref_record *rec =\n+\t\t(const struct reftable_ref_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, rec->refname);\n+}\n+\n+static void reftable_ref_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_ref_record *ref = rec;\n+\tconst struct reftable_ref_record *src = src_rec;\n+\tassert(hash_size > 0);\n+\n+\t/* This is simple and correct, but we could probably reuse the hash\n+\t * fields. */\n+\treftable_ref_record_release(ref);\n+\tif (src->refname) {\n+\t\tref->refname = xstrdup(src->refname);\n+\t}\n+\tref->update_index = src->update_index;\n+\tref->value_type = src->value_type;\n+\tswitch (src->value_type) {\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tref->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val1, src->value.val1, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tref->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.value, src->value.val2.value, hash_size);\n+\t\tref->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(ref->value.val2.target_value,\n+\t\t       src->value.val2.target_value, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tref->value.symref = xstrdup(src->value.symref);\n+\t\tbreak;\n+\t}\n+}\n+\n+static char hexdigit(int c)\n+{\n+\tif (c <= 9)\n+\t\treturn '0' + c;\n+\treturn 'a' + (c - 10);\n+}\n+\n+static void hex_format(char *dest, uint8_t *src, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (src) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < hash_size; i++) {\n+\t\t\tdest[2 * i] = hexdigit(src[i] >> 4);\n+\t\t\tdest[2 * i + 1] = hexdigit(src[i] & 0xf);\n+\t\t}\n+\t\tdest[2 * hash_size] = 0;\n+\t}\n+}\n+\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */\n+\tprintf(\"ref{%s(%\" PRIu64 \") \", ref->refname, ref->update_index);\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tprintf(\"=> %s\", ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\thex_format(hex, ref->value.val2.value, hash_size(hash_id));\n+\t\tprintf(\"val 2 %s\", hex);\n+\t\thex_format(hex, ref->value.val2.target_value,\n+\t\t\t   hash_size(hash_id));\n+\t\tprintf(\"(T %s)\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\thex_format(hex, ref->value.val1, hash_size(hash_id));\n+\t\tprintf(\"val 1 %s\", hex);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tprintf(\"delete\");\n+\t\tbreak;\n+\t}\n+\tprintf(\"}\\n\");\n+}\n+\n+static void reftable_ref_record_release_void(void *rec)\n+{\n+\treftable_ref_record_release(rec);\n+}\n+\n+void reftable_ref_record_release(struct reftable_ref_record *ref)\n+{\n+\tswitch (ref->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treftable_free(ref->value.symref);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\treftable_free(ref->value.val2.target_value);\n+\t\treftable_free(ref->value.val2.value);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\treftable_free(ref->value.val1);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treftable_free(ref->refname);\n+\tmemset(ref, 0, sizeof(struct reftable_ref_record));\n+}\n+\n+static uint8_t reftable_ref_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\treturn r->value_type;\n+}\n+\n+static int reftable_ref_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_ref_record *r =\n+\t\t(const struct reftable_ref_record *)rec;\n+\tstruct string_view start = s;\n+\tint n = put_var_int(&s, r->update_index);\n+\tassert(hash_size > 0);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\tn = encode_string(r->value.symref, s);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (s.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val2.value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tmemcpy(s.buf, r->value.val2.target_value, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (s.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tmemcpy(s.buf, r->value.val1, hash_size);\n+\t\tstring_view_consume(&s, hash_size);\n+\t\tbreak;\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t}\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_ref_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct reftable_ref_record *r = rec;\n+\tstruct string_view start = in;\n+\tuint64_t update_index = 0;\n+\tint n = get_var_int(&update_index, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\treftable_ref_record_release(r);\n+\n+\tassert(hash_size > 0);\n+\n+\tr->refname = reftable_realloc(r->refname, key.len + 1);\n+\tmemcpy(r->refname, key.buf, key.len);\n+\tr->update_index = update_index;\n+\tr->refname[key.len] = 0;\n+\tr->value_type = val_type;\n+\tswitch (val_type) {\n+\tcase REFTABLE_REF_VAL1:\n+\t\tif (in.len < hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val1 = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val1, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_VAL2:\n+\t\tif (in.len < 2 * hash_size) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tr->value.val2.value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\n+\t\tr->value.val2.target_value = reftable_malloc(hash_size);\n+\t\tmemcpy(r->value.val2.target_value, in.buf, hash_size);\n+\t\tstring_view_consume(&in, hash_size);\n+\t\tbreak;\n+\n+\tcase REFTABLE_REF_SYMREF: {\n+\t\tstruct strbuf dest = STRBUF_INIT;\n+\t\tint n = decode_string(&dest, in);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\t\tr->value.symref = dest.buf;\n+\t} break;\n+\n+\tcase REFTABLE_REF_DELETION:\n+\t\tbreak;\n+\tdefault:\n+\t\tabort();\n+\t\tbreak;\n+\t}\n+\n+\treturn start.len - in.len;\n+}\n+\n+static int reftable_ref_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_ref_record_is_deletion(\n+\t\t(const struct reftable_ref_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_ref_record_vtable = {\n+\t.key = &reftable_ref_record_key,\n+\t.type = BLOCK_TYPE_REF,\n+\t.copy_from = &reftable_ref_record_copy_from,\n+\t.val_type = &reftable_ref_record_val_type,\n+\t.encode = &reftable_ref_record_encode,\n+\t.decode = &reftable_ref_record_decode,\n+\t.release = &reftable_ref_record_release_void,\n+\t.is_deletion = &reftable_ref_record_is_deletion_void,\n+};\n+\n+static void reftable_obj_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_obj_record *rec =\n+\t\t(const struct reftable_obj_record *)r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len);\n+}\n+\n+static void reftable_obj_record_release(void *rec)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tFREE_AND_NULL(obj->hash_prefix);\n+\tFREE_AND_NULL(obj->offsets);\n+\tmemset(obj, 0, sizeof(struct reftable_obj_record));\n+}\n+\n+static void reftable_obj_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_obj_record *obj = rec;\n+\tconst struct reftable_obj_record *src =\n+\t\t(const struct reftable_obj_record *)src_rec;\n+\n+\treftable_obj_record_release(obj);\n+\t*obj = *src;\n+\tobj->hash_prefix = reftable_malloc(obj->hash_prefix_len);\n+\tmemcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);\n+\n+\tobj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));\n+\tCOPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);\n+}\n+\n+static uint8_t reftable_obj_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tif (r->offset_len > 0 && r->offset_len < 8)\n+\t\treturn r->offset_len;\n+\treturn 0;\n+}\n+\n+static int reftable_obj_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_obj_record *r = rec;\n+\tstruct string_view start = s;\n+\tint i = 0;\n+\tint n = 0;\n+\tuint64_t last = 0;\n+\tif (r->offset_len == 0 || r->offset_len >= 8) {\n+\t\tn = put_var_int(&s, r->offset_len);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t}\n+\tif (r->offset_len == 0)\n+\t\treturn start.len - s.len;\n+\tn = put_var_int(&s, r->offsets[0]);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tlast = r->offsets[0];\n+\tfor (i = 1; i < r->offset_len; i++) {\n+\t\tint n = put_var_int(&s, r->offsets[i] - last);\n+\t\tif (n < 0) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tstring_view_consume(&s, n);\n+\t\tlast = r->offsets[i];\n+\t}\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_obj_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_obj_record *r = rec;\n+\tuint64_t count = val_type;\n+\tint n = 0;\n+\tuint64_t last;\n+\tint j;\n+\tr->hash_prefix = reftable_malloc(key.len);\n+\tmemcpy(r->hash_prefix, key.buf, key.len);\n+\tr->hash_prefix_len = key.len;\n+\n+\tif (val_type == 0) {\n+\t\tn = get_var_int(&count, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\n+\t\tstring_view_consume(&in, n);\n+\t}\n+\n+\tr->offsets = NULL;\n+\tr->offset_len = 0;\n+\tif (count == 0)\n+\t\treturn start.len - in.len;\n+\n+\tr->offsets = reftable_malloc(count * sizeof(uint64_t));\n+\tr->offset_len = count;\n+\n+\tn = get_var_int(&r->offsets[0], &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\tstring_view_consume(&in, n);\n+\n+\tlast = r->offsets[0];\n+\tj = 1;\n+\twhile (j < count) {\n+\t\tuint64_t delta = 0;\n+\t\tint n = get_var_int(&delta, &in);\n+\t\tif (n < 0) {\n+\t\t\treturn n;\n+\t\t}\n+\t\tstring_view_consume(&in, n);\n+\n+\t\tlast = r->offsets[j] = (delta + last);\n+\t\tj++;\n+\t}\n+\treturn start.len - in.len;\n+}\n+\n+static int not_a_deletion(const void *p)\n+{\n+\treturn 0;\n+}\n+\n+static struct reftable_record_vtable reftable_obj_record_vtable = {\n+\t.key = &reftable_obj_record_key,\n+\t.type = BLOCK_TYPE_OBJ,\n+\t.copy_from = &reftable_obj_record_copy_from,\n+\t.val_type = &reftable_obj_record_val_type,\n+\t.encode = &reftable_obj_record_encode,\n+\t.decode = &reftable_obj_record_decode,\n+\t.release = &reftable_obj_record_release,\n+\t.is_deletion = not_a_deletion,\n+};\n+\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id)\n+{\n+\tchar hex[GIT_SHA256_RAWSZ + 1] = { 0 };\n+\n+\tswitch (log->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") delete\", log->refname,\n+\t\t       log->update_index);\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tprintf(\"log{%s(%\" PRIu64 \") %s <%s> %\" PRIu64 \" %04d\\n\",\n+\t\t       log->refname, log->update_index, log->value.update.name,\n+\t\t       log->value.update.email, log->value.update.time,\n+\t\t       log->value.update.tz_offset);\n+\t\thex_format(hex, log->value.update.old_hash, hash_size(hash_id));\n+\t\tprintf(\"%s => \", hex);\n+\t\thex_format(hex, log->value.update.new_hash, hash_size(hash_id));\n+\t\tprintf(\"%s\\n\\n%s\\n}\\n\", hex, log->value.update.message);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_log_record *rec =\n+\t\t(const struct reftable_log_record *)r;\n+\tint len = strlen(rec->refname);\n+\tuint8_t i64[8];\n+\tuint64_t ts = 0;\n+\tstrbuf_reset(dest);\n+\tstrbuf_add(dest, (uint8_t *)rec->refname, len + 1);\n+\n+\tts = (~ts) - rec->update_index;\n+\tput_be64(&i64[0], ts);\n+\tstrbuf_add(dest, i64, sizeof(i64));\n+}\n+\n+static void reftable_log_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t  int hash_size)\n+{\n+\tstruct reftable_log_record *dst = rec;\n+\tconst struct reftable_log_record *src =\n+\t\t(const struct reftable_log_record *)src_rec;\n+\n+\treftable_log_record_release(dst);\n+\t*dst = *src;\n+\tif (dst->refname) {\n+\t\tdst->refname = xstrdup(dst->refname);\n+\t}\n+\tswitch (dst->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\tif (dst->value.update.email) {\n+\t\t\tdst->value.update.email =\n+\t\t\t\txstrdup(dst->value.update.email);\n+\t\t}\n+\t\tif (dst->value.update.name) {\n+\t\t\tdst->value.update.name =\n+\t\t\t\txstrdup(dst->value.update.name);\n+\t\t}\n+\t\tif (dst->value.update.message) {\n+\t\t\tdst->value.update.message =\n+\t\t\t\txstrdup(dst->value.update.message);\n+\t\t}\n+\n+\t\tif (dst->value.update.new_hash) {\n+\t\t\tdst->value.update.new_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.new_hash,\n+\t\t\t       src->value.update.new_hash, hash_size);\n+\t\t}\n+\t\tif (dst->value.update.old_hash) {\n+\t\t\tdst->value.update.old_hash = reftable_malloc(hash_size);\n+\t\t\tmemcpy(dst->value.update.old_hash,\n+\t\t\t       src->value.update.old_hash, hash_size);\n+\t\t}\n+\t\tbreak;\n+\t}\n+}\n+\n+static void reftable_log_record_release_void(void *rec)\n+{\n+\tstruct reftable_log_record *r = rec;\n+\treftable_log_record_release(r);\n+}\n+\n+void reftable_log_record_release(struct reftable_log_record *r)\n+{\n+\treftable_free(r->refname);\n+\tswitch (r->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\tbreak;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treftable_free(r->value.update.new_hash);\n+\t\treftable_free(r->value.update.old_hash);\n+\t\treftable_free(r->value.update.name);\n+\t\treftable_free(r->value.update.email);\n+\t\treftable_free(r->value.update.message);\n+\t\tbreak;\n+\t}\n+\tmemset(r, 0, sizeof(struct reftable_log_record));\n+}\n+\n+static uint8_t reftable_log_record_val_type(const void *rec)\n+{\n+\tconst struct reftable_log_record *log =\n+\t\t(const struct reftable_log_record *)rec;\n+\n+\treturn reftable_log_record_is_deletion(log) ? 0 : 1;\n+}\n+\n+static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 };\n+\n+static int reftable_log_record_encode(const void *rec, struct string_view s,\n+\t\t\t\t      int hash_size)\n+{\n+\tconst struct reftable_log_record *r = rec;\n+\tstruct string_view start = s;\n+\tint n = 0;\n+\tuint8_t *oldh = NULL;\n+\tuint8_t *newh = NULL;\n+\tif (reftable_log_record_is_deletion(r))\n+\t\treturn 0;\n+\n+\toldh = r->value.update.old_hash;\n+\tnewh = r->value.update.new_hash;\n+\tif (!oldh) {\n+\t\toldh = zero;\n+\t}\n+\tif (!newh) {\n+\t\tnewh = zero;\n+\t}\n+\n+\tif (s.len < 2 * hash_size)\n+\t\treturn -1;\n+\n+\tmemcpy(s.buf, oldh, hash_size);\n+\tmemcpy(s.buf + hash_size, newh, hash_size);\n+\tstring_view_consume(&s, 2 * hash_size);\n+\n+\tn = encode_string(r->value.update.name ? r->value.update.name : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = encode_string(r->value.update.email ? r->value.update.email : \"\",\n+\t\t\t  s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tn = put_var_int(&s, r->value.update.time);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\tif (s.len < 2)\n+\t\treturn -1;\n+\n+\tput_be16(s.buf, r->value.update.tz_offset);\n+\tstring_view_consume(&s, 2);\n+\n+\tn = encode_string(\n+\t\tr->value.update.message ? r->value.update.message : \"\", s);\n+\tif (n < 0)\n+\t\treturn -1;\n+\tstring_view_consume(&s, n);\n+\n+\treturn start.len - s.len;\n+}\n+\n+static int reftable_log_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t      uint8_t val_type, struct string_view in,\n+\t\t\t\t      int hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_log_record *r = rec;\n+\tuint64_t max = 0;\n+\tuint64_t ts = 0;\n+\tstruct strbuf dest = STRBUF_INIT;\n+\tint n;\n+\n+\tif (key.len <= 9 || key.buf[key.len - 9] != 0)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->refname = reftable_realloc(r->refname, key.len - 8);\n+\tmemcpy(r->refname, key.buf, key.len - 8);\n+\tts = get_be64(key.buf + key.len - 8);\n+\n+\tr->update_index = (~max) - ts;\n+\n+\tif (val_type != r->value_type) {\n+\t\tswitch (r->value_type) {\n+\t\tcase REFTABLE_LOG_UPDATE:\n+\t\t\tFREE_AND_NULL(r->value.update.old_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.new_hash);\n+\t\t\tFREE_AND_NULL(r->value.update.message);\n+\t\t\tFREE_AND_NULL(r->value.update.email);\n+\t\t\tFREE_AND_NULL(r->value.update.name);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_LOG_DELETION:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tr->value_type = val_type;\n+\tif (val_type == REFTABLE_LOG_DELETION)\n+\t\treturn 0;\n+\n+\tif (in.len < 2 * hash_size)\n+\t\treturn REFTABLE_FORMAT_ERROR;\n+\n+\tr->value.update.old_hash =\n+\t\treftable_realloc(r->value.update.old_hash, hash_size);\n+\tr->value.update.new_hash =\n+\t\treftable_realloc(r->value.update.new_hash, hash_size);\n+\n+\tmemcpy(r->value.update.old_hash, in.buf, hash_size);\n+\tmemcpy(r->value.update.new_hash, in.buf + hash_size, hash_size);\n+\n+\tstring_view_consume(&in, 2 * hash_size);\n+\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.name =\n+\t\treftable_realloc(r->value.update.name, dest.len + 1);\n+\tmemcpy(r->value.update.name, dest.buf, dest.len);\n+\tr->value.update.name[dest.len] = 0;\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.email =\n+\t\treftable_realloc(r->value.update.email, dest.len + 1);\n+\tmemcpy(r->value.update.email, dest.buf, dest.len);\n+\tr->value.update.email[dest.len] = 0;\n+\n+\tts = 0;\n+\tn = get_var_int(&ts, &in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\tr->value.update.time = ts;\n+\tif (in.len < 2)\n+\t\tgoto done;\n+\n+\tr->value.update.tz_offset = get_be16(in.buf);\n+\tstring_view_consume(&in, 2);\n+\n+\tstrbuf_reset(&dest);\n+\tn = decode_string(&dest, in);\n+\tif (n < 0)\n+\t\tgoto done;\n+\tstring_view_consume(&in, n);\n+\n+\tr->value.update.message =\n+\t\treftable_realloc(r->value.update.message, dest.len + 1);\n+\tmemcpy(r->value.update.message, dest.buf, dest.len);\n+\tr->value.update.message[dest.len] = 0;\n+\n+\tstrbuf_release(&dest);\n+\treturn start.len - in.len;\n+\n+done:\n+\tstrbuf_release(&dest);\n+\treturn REFTABLE_FORMAT_ERROR;\n+}\n+\n+static int null_streq(char *a, char *b)\n+{\n+\tchar *empty = \"\";\n+\tif (!a)\n+\t\ta = empty;\n+\n+\tif (!b)\n+\t\tb = empty;\n+\n+\treturn 0 == strcmp(a, b);\n+}\n+\n+static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)\n+{\n+\tif (!a)\n+\t\ta = zero;\n+\n+\tif (!b)\n+\t\tb = zero;\n+\n+\treturn !memcmp(a, b, sz);\n+}\n+\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size)\n+{\n+\tif (!(null_streq(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_LOG_DELETION:\n+\t\treturn 1;\n+\tcase REFTABLE_LOG_UPDATE:\n+\t\treturn null_streq(a->value.update.name, b->value.update.name) &&\n+\t\t       a->value.update.time == b->value.update.time &&\n+\t\t       a->value.update.tz_offset == b->value.update.tz_offset &&\n+\t\t       null_streq(a->value.update.email,\n+\t\t\t\t  b->value.update.email) &&\n+\t\t       null_streq(a->value.update.message,\n+\t\t\t\t  b->value.update.message) &&\n+\t\t       zero_hash_eq(a->value.update.old_hash,\n+\t\t\t\t    b->value.update.old_hash, hash_size) &&\n+\t\t       zero_hash_eq(a->value.update.new_hash,\n+\t\t\t\t    b->value.update.new_hash, hash_size);\n+\t}\n+\n+\tabort();\n+}\n+\n+static int reftable_log_record_is_deletion_void(const void *p)\n+{\n+\treturn reftable_log_record_is_deletion(\n+\t\t(const struct reftable_log_record *)p);\n+}\n+\n+static struct reftable_record_vtable reftable_log_record_vtable = {\n+\t.key = &reftable_log_record_key,\n+\t.type = BLOCK_TYPE_LOG,\n+\t.copy_from = &reftable_log_record_copy_from,\n+\t.val_type = &reftable_log_record_val_type,\n+\t.encode = &reftable_log_record_encode,\n+\t.decode = &reftable_log_record_decode,\n+\t.release = &reftable_log_record_release_void,\n+\t.is_deletion = &reftable_log_record_is_deletion_void,\n+};\n+\n+struct reftable_record reftable_new_record(uint8_t typ)\n+{\n+\tstruct reftable_record rec = { NULL };\n+\tswitch (typ) {\n+\tcase BLOCK_TYPE_REF: {\n+\t\tstruct reftable_ref_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_ref_record));\n+\t\treftable_record_from_ref(&rec, r);\n+\t\treturn rec;\n+\t}\n+\n+\tcase BLOCK_TYPE_OBJ: {\n+\t\tstruct reftable_obj_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_obj_record));\n+\t\treftable_record_from_obj(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_LOG: {\n+\t\tstruct reftable_log_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_log_record));\n+\t\treftable_record_from_log(&rec, r);\n+\t\treturn rec;\n+\t}\n+\tcase BLOCK_TYPE_INDEX: {\n+\t\tstruct reftable_index_record empty = { .last_key =\n+\t\t\t\t\t\t\t       STRBUF_INIT };\n+\t\tstruct reftable_index_record *r =\n+\t\t\treftable_calloc(sizeof(struct reftable_index_record));\n+\t\t*r = empty;\n+\t\treftable_record_from_index(&rec, r);\n+\t\treturn rec;\n+\t}\n+\t}\n+\tabort();\n+\treturn rec;\n+}\n+\n+/* clear out the record, yielding the reftable_record data that was\n+ * encapsulated. */\n+static void *reftable_record_yield(struct reftable_record *rec)\n+{\n+\tvoid *p = rec->data;\n+\trec->data = NULL;\n+\treturn p;\n+}\n+\n+void reftable_record_destroy(struct reftable_record *rec)\n+{\n+\treftable_record_release(rec);\n+\treftable_free(reftable_record_yield(rec));\n+}\n+\n+static void reftable_index_record_key(const void *r, struct strbuf *dest)\n+{\n+\tconst struct reftable_index_record *rec = r;\n+\tstrbuf_reset(dest);\n+\tstrbuf_addbuf(dest, &rec->last_key);\n+}\n+\n+static void reftable_index_record_copy_from(void *rec, const void *src_rec,\n+\t\t\t\t\t    int hash_size)\n+{\n+\tstruct reftable_index_record *dst = rec;\n+\tconst struct reftable_index_record *src = src_rec;\n+\n+\tstrbuf_reset(&dst->last_key);\n+\tstrbuf_addbuf(&dst->last_key, &src->last_key);\n+\tdst->offset = src->offset;\n+}\n+\n+static void reftable_index_record_release(void *rec)\n+{\n+\tstruct reftable_index_record *idx = rec;\n+\tstrbuf_release(&idx->last_key);\n+}\n+\n+static uint8_t reftable_index_record_val_type(const void *rec)\n+{\n+\treturn 0;\n+}\n+\n+static int reftable_index_record_encode(const void *rec, struct string_view out,\n+\t\t\t\t\tint hash_size)\n+{\n+\tconst struct reftable_index_record *r =\n+\t\t(const struct reftable_index_record *)rec;\n+\tstruct string_view start = out;\n+\n+\tint n = put_var_int(&out, r->offset);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&out, n);\n+\n+\treturn start.len - out.len;\n+}\n+\n+static int reftable_index_record_decode(void *rec, struct strbuf key,\n+\t\t\t\t\tuint8_t val_type, struct string_view in,\n+\t\t\t\t\tint hash_size)\n+{\n+\tstruct string_view start = in;\n+\tstruct reftable_index_record *r = rec;\n+\tint n = 0;\n+\n+\tstrbuf_reset(&r->last_key);\n+\tstrbuf_addbuf(&r->last_key, &key);\n+\n+\tn = get_var_int(&r->offset, &in);\n+\tif (n < 0)\n+\t\treturn n;\n+\n+\tstring_view_consume(&in, n);\n+\treturn start.len - in.len;\n+}\n+\n+static struct reftable_record_vtable reftable_index_record_vtable = {\n+\t.key = &reftable_index_record_key,\n+\t.type = BLOCK_TYPE_INDEX,\n+\t.copy_from = &reftable_index_record_copy_from,\n+\t.val_type = &reftable_index_record_val_type,\n+\t.encode = &reftable_index_record_encode,\n+\t.decode = &reftable_index_record_decode,\n+\t.release = &reftable_index_record_release,\n+\t.is_deletion = &not_a_deletion,\n+};\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)\n+{\n+\trec->ops->key(rec->data, dest);\n+}\n+\n+uint8_t reftable_record_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->type;\n+}\n+\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size)\n+{\n+\treturn rec->ops->encode(rec->data, dest, hash_size);\n+}\n+\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size)\n+{\n+\tassert(src->ops->type == rec->ops->type);\n+\n+\trec->ops->copy_from(rec->data, src->data, hash_size);\n+}\n+\n+uint8_t reftable_record_val_type(struct reftable_record *rec)\n+{\n+\treturn rec->ops->val_type(rec->data);\n+}\n+\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src, int hash_size)\n+{\n+\treturn rec->ops->decode(rec->data, key, extra, src, hash_size);\n+}\n+\n+void reftable_record_release(struct reftable_record *rec)\n+{\n+\trec->ops->release(rec->data);\n+}\n+\n+int reftable_record_is_deletion(struct reftable_record *rec)\n+{\n+\treturn rec->ops->is_deletion(rec->data);\n+}\n+\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *ref_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = ref_rec;\n+\trec->ops = &reftable_ref_record_vtable;\n+}\n+\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *obj_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = obj_rec;\n+\trec->ops = &reftable_obj_record_vtable;\n+}\n+\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *index_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = index_rec;\n+\trec->ops = &reftable_index_record_vtable;\n+}\n+\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *log_rec)\n+{\n+\tassert(!rec->ops);\n+\trec->data = log_rec;\n+\trec->ops = &reftable_log_record_vtable;\n+}\n+\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_REF);\n+\treturn rec->data;\n+}\n+\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)\n+{\n+\tassert(reftable_record_type(rec) == BLOCK_TYPE_LOG);\n+\treturn rec->data;\n+}\n+\n+static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)\n+{\n+\tif (a && b)\n+\t\treturn !memcmp(a, b, hash_size);\n+\n+\treturn a == b;\n+}\n+\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size)\n+{\n+\tassert(hash_size > 0);\n+\tif (!(0 == strcmp(a->refname, b->refname) &&\n+\t      a->update_index == b->update_index &&\n+\t      a->value_type == b->value_type))\n+\t\treturn 0;\n+\n+\tswitch (a->value_type) {\n+\tcase REFTABLE_REF_SYMREF:\n+\t\treturn !strcmp(a->value.symref, b->value.symref);\n+\tcase REFTABLE_REF_VAL2:\n+\t\treturn hash_equal(a->value.val2.value, b->value.val2.value,\n+\t\t\t\t  hash_size) &&\n+\t\t       hash_equal(a->value.val2.target_value,\n+\t\t\t\t  b->value.val2.target_value, hash_size);\n+\tcase REFTABLE_REF_VAL1:\n+\t\treturn hash_equal(a->value.val1, b->value.val1, hash_size);\n+\tcase REFTABLE_REF_DELETION:\n+\t\treturn 1;\n+\tdefault:\n+\t\tabort();\n+\t}\n+}\n+\n+int reftable_ref_record_compare_name(const void *a, const void *b)\n+{\n+\treturn strcmp(((struct reftable_ref_record *)a)->refname,\n+\t\t      ((struct reftable_ref_record *)b)->refname);\n+}\n+\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref)\n+{\n+\treturn ref->value_type == REFTABLE_REF_DELETION;\n+}\n+\n+int reftable_log_record_compare_key(const void *a, const void *b)\n+{\n+\tconst struct reftable_log_record *la = a;\n+\tconst struct reftable_log_record *lb = b;\n+\n+\tint cmp = strcmp(la->refname, lb->refname);\n+\tif (cmp)\n+\t\treturn cmp;\n+\tif (la->update_index > lb->update_index)\n+\t\treturn -1;\n+\treturn (la->update_index < lb->update_index) ? 1 : 0;\n+}\n+\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log)\n+{\n+\treturn (log->value_type == REFTABLE_LOG_DELETION);\n+}\n+\n+void string_view_consume(struct string_view *s, int n)\n+{\n+\ts->buf += n;\n+\ts->len -= n;\n+}\ndiff --git a/reftable/record.h b/reftable/record.h\nnew file mode 100644\nindex 00000000000..498e8c50bf4\n--- /dev/null\n+++ b/reftable/record.h\n@@ -0,0 +1,139 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef RECORD_H\n+#define RECORD_H\n+\n+#include \"system.h\"\n+\n+#include <stdint.h>\n+\n+#include \"reftable-record.h\"\n+\n+/*\n+ * A substring of existing string data. This structure takes no responsibility\n+ * for the lifetime of the data it points to.\n+ */\n+struct string_view {\n+\tuint8_t *buf;\n+\tsize_t len;\n+};\n+\n+/* Advance `s.buf` by `n`, and decrease length. */\n+void string_view_consume(struct string_view *s, int n);\n+\n+/* utilities for de/encoding varints */\n+\n+int get_var_int(uint64_t *dest, struct string_view *in);\n+int put_var_int(struct string_view *dest, uint64_t val);\n+\n+/* Methods for records. */\n+struct reftable_record_vtable {\n+\t/* encode the key of to a uint8_t strbuf. */\n+\tvoid (*key)(const void *rec, struct strbuf *dest);\n+\n+\t/* The record type of ('r' for ref). */\n+\tuint8_t type;\n+\n+\tvoid (*copy_from)(void *dest, const void *src, int hash_size);\n+\n+\t/* a value of [0..7], indicating record subvariants (eg. ref vs. symref\n+\t * vs ref deletion) */\n+\tuint8_t (*val_type)(const void *rec);\n+\n+\t/* encodes rec into dest, returning how much space was used. */\n+\tint (*encode)(const void *rec, struct string_view dest, int hash_size);\n+\n+\t/* decode data from `src` into the record. */\n+\tint (*decode)(void *rec, struct strbuf key, uint8_t extra,\n+\t\t      struct string_view src, int hash_size);\n+\n+\t/* deallocate and null the record. */\n+\tvoid (*release)(void *rec);\n+\n+\t/* is this a tombstone? */\n+\tint (*is_deletion)(const void *rec);\n+};\n+\n+/* record is a generic wrapper for different types of records. */\n+struct reftable_record {\n+\tvoid *data;\n+\tstruct reftable_record_vtable *ops;\n+};\n+\n+/* returns true for recognized block types. Block start with the block type. */\n+int reftable_is_block_type(uint8_t typ);\n+\n+/* creates a malloced record of the given type. Dispose with record_destroy */\n+struct reftable_record reftable_new_record(uint8_t typ);\n+\n+/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns\n+ * number of bytes written. */\n+int reftable_encode_key(int *is_restart, struct string_view dest,\n+\t\t\tstruct strbuf prev_key, struct strbuf key,\n+\t\t\tuint8_t extra);\n+\n+/* Decode into `key` and `extra` from `in` */\n+int reftable_decode_key(struct strbuf *key, uint8_t *extra,\n+\t\t\tstruct strbuf last_key, struct string_view in);\n+\n+/* reftable_index_record are used internally to speed up lookups. */\n+struct reftable_index_record {\n+\tuint64_t offset; /* Offset of block */\n+\tstruct strbuf last_key; /* Last key of the block. */\n+};\n+\n+/* reftable_obj_record stores an object ID => ref mapping. */\n+struct reftable_obj_record {\n+\tuint8_t *hash_prefix; /* leading bytes of the object ID */\n+\tint hash_prefix_len; /* number of leading bytes. Constant\n+\t\t\t      * across a single table. */\n+\tuint64_t *offsets; /* a vector of file offsets. */\n+\tint offset_len;\n+};\n+\n+/* see struct record_vtable */\n+\n+void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);\n+uint8_t reftable_record_type(struct reftable_record *rec);\n+void reftable_record_copy_from(struct reftable_record *rec,\n+\t\t\t       struct reftable_record *src, int hash_size);\n+uint8_t reftable_record_val_type(struct reftable_record *rec);\n+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,\n+\t\t\t   int hash_size);\n+int reftable_record_decode(struct reftable_record *rec, struct strbuf key,\n+\t\t\t   uint8_t extra, struct string_view src,\n+\t\t\t   int hash_size);\n+int reftable_record_is_deletion(struct reftable_record *rec);\n+\n+/* zeroes out the embedded record */\n+void reftable_record_release(struct reftable_record *rec);\n+\n+/* clear and deallocate embedded record, and zero `rec`. */\n+void reftable_record_destroy(struct reftable_record *rec);\n+\n+/* initialize generic records from concrete records. The generic record should\n+ * be zeroed out. */\n+void reftable_record_from_obj(struct reftable_record *rec,\n+\t\t\t      struct reftable_obj_record *objrec);\n+void reftable_record_from_index(struct reftable_record *rec,\n+\t\t\t\tstruct reftable_index_record *idxrec);\n+void reftable_record_from_ref(struct reftable_record *rec,\n+\t\t\t      struct reftable_ref_record *refrec);\n+void reftable_record_from_log(struct reftable_record *rec,\n+\t\t\t      struct reftable_log_record *logrec);\n+struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);\n+struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);\n+\n+/* for qsort. */\n+int reftable_ref_record_compare_name(const void *a, const void *b);\n+\n+/* for qsort. */\n+int reftable_log_record_compare_key(const void *a, const void *b);\n+\n+#endif\ndiff --git a/reftable/record_test.c b/reftable/record_test.c\nnew file mode 100644\nindex 00000000000..f4ad7cace41\n--- /dev/null\n+++ b/reftable/record_test.c\n@@ -0,0 +1,412 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"record.h\"\n+\n+#include \"system.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+static void test_copy(struct reftable_record *rec)\n+{\n+\tstruct reftable_record copy =\n+\t\treftable_new_record(reftable_record_type(rec));\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\t/* do it twice to catch memory leaks */\n+\treftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);\n+\tswitch (reftable_record_type(&copy)) {\n+\tcase BLOCK_TYPE_REF:\n+\t\tEXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),\n+\t\t\t\t\t\t reftable_record_as_ref(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\tcase BLOCK_TYPE_LOG:\n+\t\tEXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),\n+\t\t\t\t\t\t reftable_record_as_log(rec),\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\tbreak;\n+\t}\n+\treftable_record_destroy(&copy);\n+}\n+\n+static void test_varint_roundtrip(void)\n+{\n+\tuint64_t inputs[] = { 0,\n+\t\t\t      1,\n+\t\t\t      27,\n+\t\t\t      127,\n+\t\t\t      128,\n+\t\t\t      257,\n+\t\t\t      4096,\n+\t\t\t      ((uint64_t)1 << 63),\n+\t\t\t      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(inputs); i++) {\n+\t\tuint8_t dest[10];\n+\n+\t\tstruct string_view out = {\n+\t\t\t.buf = dest,\n+\t\t\t.len = sizeof(dest),\n+\t\t};\n+\t\tuint64_t in = inputs[i];\n+\t\tint n = put_var_int(&out, in);\n+\t\tuint64_t got = 0;\n+\n+\t\tEXPECT(n > 0);\n+\t\tout.len = n;\n+\t\tn = get_var_int(&got, &out);\n+\t\tEXPECT(n > 0);\n+\n+\t\tEXPECT(got == in);\n+\t}\n+}\n+\n+static void test_common_prefix(void)\n+{\n+\tstruct {\n+\t\tconst char *a, *b;\n+\t\tint want;\n+\t} cases[] = {\n+\t\t{ \"abc\", \"ab\", 2 },\n+\t\t{ \"\", \"abc\", 0 },\n+\t\t{ \"abc\", \"abd\", 2 },\n+\t\t{ \"abc\", \"pqr\", 0 },\n+\t};\n+\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\tEXPECT(common_prefix_size(&a, &b) == cases[i].want);\n+\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n+}\n+\n+static void set_hash(uint8_t *h, int j)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < hash_size(GIT_SHA1_FORMAT_ID); i++) {\n+\t\th[i] = (j >> i) & 0xff;\n+\t}\n+}\n+\n+static void test_reftable_ref_record_roundtrip(void)\n+{\n+\tint i = 0;\n+\n+\tfor (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {\n+\t\tstruct reftable_ref_record in = { NULL };\n+\t\tstruct reftable_ref_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\n+\t\tint n, m;\n+\n+\t\tin.value_type = i;\n+\t\tswitch (i) {\n+\t\tcase REFTABLE_REF_DELETION:\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL1:\n+\t\t\tin.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val1, 1);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL2:\n+\t\t\tin.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.value, 1);\n+\t\t\tin.value.val2.target_value =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_hash(in.value.val2.target_value, 2);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_SYMREF:\n+\t\t\tin.value.symref = xstrdup(\"target\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tin.refname = xstrdup(\"refs/heads/master\");\n+\n+\t\treftable_record_from_ref(&rec, &in);\n+\t\ttest_copy(&rec);\n+\n+\t\tEXPECT(reftable_record_val_type(&rec) == i);\n+\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\n+\t\t/* decode into a non-zero reftable_record to test for leaks. */\n+\n+\t\treftable_record_from_ref(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, i, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));\n+\t\treftable_record_release(&rec_out);\n+\n+\t\tstrbuf_release(&key);\n+\t\treftable_ref_record_release(&in);\n+\t}\n+}\n+\n+static void test_reftable_log_record_equal(void)\n+{\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t}\n+\t};\n+\n+\tEXPECT(!reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\tin[1].update_index = in[0].update_index;\n+\tEXPECT(reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));\n+\treftable_log_record_release(&in[0]);\n+\treftable_log_record_release(&in[1]);\n+}\n+\n+static void test_reftable_log_record_roundtrip(void)\n+{\n+\tint i;\n+\tstruct reftable_log_record in[2] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.old_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.new_hash = reftable_malloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"han-wen\"),\n+\t\t\t\t\t.email = xstrdup(\"hanwen@google.com\"),\n+\t\t\t\t\t.message = xstrdup(\"test\"),\n+\t\t\t\t\t.time = 1577123507,\n+\t\t\t\t\t.tz_offset = 100,\n+\t\t\t\t},\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t}\n+\t};\n+\tset_test_hash(in[0].value.update.new_hash, 1);\n+\tset_test_hash(in[0].value.update.old_hash, 2);\n+\tfor (i = 0; i < ARRAY_SIZE(in); i++) {\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\t/* populate out, to check for leaks. */\n+\t\tstruct reftable_log_record out = {\n+\t\t\t.refname = xstrdup(\"old name\"),\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),\n+\t\t\t\t\t.name = xstrdup(\"old name\"),\n+\t\t\t\t\t.email = xstrdup(\"old@email\"),\n+\t\t\t\t\t.message = xstrdup(\"old message\"),\n+\t\t\t\t},\n+\t\t\t},\n+\t\t};\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m, valtype;\n+\n+\t\treftable_record_from_log(&rec, &in[i]);\n+\n+\t\ttest_copy(&rec);\n+\n+\t\treftable_record_key(&rec, &key);\n+\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n >= 0);\n+\t\treftable_record_from_log(&rec_out, &out);\n+\t\tvaltype = reftable_record_val_type(&rec);\n+\t\tm = reftable_record_decode(&rec_out, key, valtype, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&in[i]);\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_u24_roundtrip(void)\n+{\n+\tuint32_t in = 0x112233;\n+\tuint8_t dest[3];\n+\tuint32_t out;\n+\tput_be24(dest, in);\n+\tout = get_be24(dest);\n+\tEXPECT(in == out);\n+}\n+\n+static void test_key_roundtrip(void)\n+{\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf last_key = STRBUF_INIT;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct strbuf roundtrip = STRBUF_INIT;\n+\tint restart;\n+\tuint8_t extra;\n+\tint n, m;\n+\tuint8_t rt_extra;\n+\n+\tstrbuf_addstr(&last_key, \"refs/heads/master\");\n+\tstrbuf_addstr(&key, \"refs/tags/bla\");\n+\textra = 6;\n+\tn = reftable_encode_key(&restart, dest, last_key, key, extra);\n+\tEXPECT(!restart);\n+\tEXPECT(n > 0);\n+\n+\tm = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);\n+\tEXPECT(n == m);\n+\tEXPECT(0 == strbuf_cmp(&key, &roundtrip));\n+\tEXPECT(rt_extra == extra);\n+\n+\tstrbuf_release(&last_key);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&roundtrip);\n+}\n+\n+static void test_reftable_obj_record_roundtrip(void)\n+{\n+\tuint8_t testHash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 4, 0 };\n+\tuint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };\n+\tstruct reftable_obj_record recs[3] = { {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 3,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t\t       .offsets = till9,\n+\t\t\t\t\t\t       .offset_len = 9,\n+\t\t\t\t\t       },\n+\t\t\t\t\t       {\n+\t\t\t\t\t\t       .hash_prefix = testHash1,\n+\t\t\t\t\t\t       .hash_prefix_len = 5,\n+\t\t\t\t\t       } };\n+\tint i = 0;\n+\tfor (i = 0; i < ARRAY_SIZE(recs); i++) {\n+\t\tstruct reftable_obj_record in = recs[i];\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\tstruct reftable_record rec = { NULL };\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct reftable_obj_record out = { NULL };\n+\t\tstruct reftable_record rec_out = { NULL };\n+\t\tint n, m;\n+\t\tuint8_t extra;\n+\n+\t\treftable_record_from_obj(&rec, &in);\n+\t\ttest_copy(&rec);\n+\t\treftable_record_key(&rec, &key);\n+\t\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n > 0);\n+\t\textra = reftable_record_val_type(&rec);\n+\t\treftable_record_from_obj(&rec_out, &out);\n+\t\tm = reftable_record_decode(&rec_out, key, extra, dest,\n+\t\t\t\t\t   GIT_SHA1_RAWSZ);\n+\t\tEXPECT(n == m);\n+\n+\t\tEXPECT(in.hash_prefix_len == out.hash_prefix_len);\n+\t\tEXPECT(in.offset_len == out.offset_len);\n+\n+\t\tEXPECT(!memcmp(in.hash_prefix, out.hash_prefix,\n+\t\t\t       in.hash_prefix_len));\n+\t\tEXPECT(0 == memcmp(in.offsets, out.offsets,\n+\t\t\t\t   sizeof(uint64_t) * in.offset_len));\n+\t\tstrbuf_release(&key);\n+\t\treftable_record_release(&rec_out);\n+\t}\n+}\n+\n+static void test_reftable_index_record_roundtrip(void)\n+{\n+\tstruct reftable_index_record in = {\n+\t\t.offset = 42,\n+\t\t.last_key = STRBUF_INIT,\n+\t};\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstruct reftable_record rec = { NULL };\n+\tstruct reftable_index_record out = { .last_key = STRBUF_INIT };\n+\tstruct reftable_record out_rec = { NULL };\n+\tint n, m;\n+\tuint8_t extra;\n+\n+\tstrbuf_addstr(&in.last_key, \"refs/heads/master\");\n+\treftable_record_from_index(&rec, &in);\n+\treftable_record_key(&rec, &key);\n+\ttest_copy(&rec);\n+\n+\tEXPECT(0 == strbuf_cmp(&key, &in.last_key));\n+\tn = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(n > 0);\n+\n+\textra = reftable_record_val_type(&rec);\n+\treftable_record_from_index(&out_rec, &out);\n+\tm = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);\n+\tEXPECT(m == n);\n+\n+\tEXPECT(in.offset == out.offset);\n+\n+\treftable_record_release(&out_rec);\n+\tstrbuf_release(&key);\n+\tstrbuf_release(&in.last_key);\n+}\n+\n+int record_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_reftable_log_record_equal);\n+\tRUN_TEST(test_reftable_log_record_roundtrip);\n+\tRUN_TEST(test_reftable_ref_record_roundtrip);\n+\tRUN_TEST(test_varint_roundtrip);\n+\tRUN_TEST(test_key_roundtrip);\n+\tRUN_TEST(test_common_prefix);\n+\tRUN_TEST(test_reftable_obj_record_roundtrip);\n+\tRUN_TEST(test_reftable_index_record_roundtrip);\n+\tRUN_TEST(test_u24_roundtrip);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-record.h b/reftable/reftable-record.h\nnew file mode 100644\nindex 00000000000..5370d2288c7\n--- /dev/null\n+++ b/reftable/reftable-record.h\n@@ -0,0 +1,114 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_RECORD_H\n+#define REFTABLE_RECORD_H\n+\n+#include <stdint.h>\n+\n+/*\n+ * Basic data types\n+ *\n+ * Reftables store the state of each ref in struct reftable_ref_record, and they\n+ * store a sequence of reflog updates in struct reftable_log_record.\n+ */\n+\n+/* reftable_ref_record holds a ref database entry target_value */\n+struct reftable_ref_record {\n+\tchar *refname; /* Name of the ref, malloced. */\n+\tuint64_t update_index; /* Logical timestamp at which this value is\n+\t\t\t\t* written */\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_REF_DELETION = 0x0,\n+\n+\t\t/* a simple ref */\n+\t\tREFTABLE_REF_VAL1 = 0x1,\n+\t\t/* a tag, plus its peeled hash */\n+\t\tREFTABLE_REF_VAL2 = 0x2,\n+\n+\t\t/* a symbolic reference */\n+\t\tREFTABLE_REF_SYMREF = 0x3,\n+#define REFTABLE_NR_REF_VALUETYPES 4\n+\t} value_type;\n+\tunion {\n+\t\tuint8_t *val1; /* malloced hash. */\n+\t\tstruct {\n+\t\t\tuint8_t *value; /* first value, malloced hash  */\n+\t\t\tuint8_t *target_value; /* second value, malloced hash */\n+\t\t} val2;\n+\t\tchar *symref; /* referent, malloced 0-terminated string */\n+\t} value;\n+};\n+\n+/* Returns the first hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);\n+\n+/* Returns the second hash, or NULL if `rec` is not of type\n+ * REFTABLE_REF_VAL2. */\n+uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);\n+\n+/* returns whether 'ref' represents a deletion */\n+int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);\n+\n+/* prints a reftable_ref_record onto stdout. Useful for debugging. */\n+void reftable_ref_record_print(struct reftable_ref_record *ref,\n+\t\t\t       uint32_t hash_id);\n+\n+/* frees and nulls all pointer values inside `ref`. */\n+void reftable_ref_record_release(struct reftable_ref_record *ref);\n+\n+/* returns whether two reftable_ref_records are the same. Useful for testing. */\n+int reftable_ref_record_equal(struct reftable_ref_record *a,\n+\t\t\t      struct reftable_ref_record *b, int hash_size);\n+\n+/* reftable_log_record holds a reflog entry */\n+struct reftable_log_record {\n+\tchar *refname;\n+\tuint64_t update_index; /* logical timestamp of a transactional update.\n+\t\t\t\t*/\n+\n+\tenum {\n+\t\t/* tombstone to hide deletions from earlier tables */\n+\t\tREFTABLE_LOG_DELETION = 0x0,\n+\n+\t\t/* a simple update */\n+\t\tREFTABLE_LOG_UPDATE = 0x1,\n+#define REFTABLE_NR_LOG_VALUETYPES 2\n+\t} value_type;\n+\n+\tunion {\n+\t\tstruct {\n+\t\t\tuint8_t *new_hash;\n+\t\t\tuint8_t *old_hash;\n+\t\t\tchar *name;\n+\t\t\tchar *email;\n+\t\t\tuint64_t time;\n+\t\t\tint16_t tz_offset;\n+\t\t\tchar *message;\n+\t\t} update;\n+\t} value;\n+};\n+\n+/* returns whether 'ref' represents the deletion of a log record. */\n+int reftable_log_record_is_deletion(const struct reftable_log_record *log);\n+\n+/* frees and nulls all pointer values. */\n+void reftable_log_record_release(struct reftable_log_record *log);\n+\n+/* returns whether two records are equal. Useful for testing. */\n+int reftable_log_record_equal(struct reftable_log_record *a,\n+\t\t\t      struct reftable_log_record *b, int hash_size);\n+\n+/* dumps a reftable_log_record on stdout, for debugging/testing. */\n+void reftable_log_record_print(struct reftable_log_record *log,\n+\t\t\t       uint32_t hash_id);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 3b58e423e7b..09d4b83ef9b 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -4,6 +4,6 @@\n int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n-\n+\trecord_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"438213","messageId":"6cbc2f758dfbfba4363fbdbf1a7a8102c52842ad.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 07/19] Provide zlib's uncompress2 from compat/zlib-compat.c","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:03Z","receivedAt":"2021-10-07T20:25:44Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis will be needed for reading reflog blocks in reftable.\n\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |  7 +++\n ci/lib.sh                 |  1 +\n compat/.gitattributes     |  1 +\n compat/zlib-uncompress2.c | 95 +++++++++++++++++++++++++++++++++++++++\n config.mak.uname          |  5 +++\n configure.ac              | 13 ++++++\n 6 files changed, 122 insertions(+)\n create mode 100644 compat/.gitattributes\n create mode 100644 compat/zlib-uncompress2.c\n\ndiff --git a/Makefile b/Makefile\nindex b57b2b0e78e..9dfab70f79b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -256,6 +256,8 @@ all::\n #\n # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.\n #\n+# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.\n+#\n # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,\n # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)\n #\n@@ -1718,6 +1720,11 @@ ifdef NO_DEFLATE_BOUND\n \tBASIC_CFLAGS += -DNO_DEFLATE_BOUND\n endif\n \n+ifdef NO_UNCOMPRESS2\n+\tBASIC_CFLAGS += -DNO_UNCOMPRESS2\n+\tREFTABLE_OBJS += compat/zlib-uncompress2.o\n+endif\n+\n ifdef NO_POSIX_GOODIES\n \tBASIC_CFLAGS += -DNO_POSIX_GOODIES\n endif\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex 476c3f369f5..5711c63979d 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -224,6 +224,7 @@ linux-gcc-default)\n \t;;\n Linux32)\n \tCC=gcc\n+\tMAKEFLAGS=\"$MAKEFLAGS NO_UNCOMPRESS2=1\"\n \t;;\n linux-musl)\n \tCC=gcc\ndiff --git a/compat/.gitattributes b/compat/.gitattributes\nnew file mode 100644\nindex 00000000000..40dbfb170da\n--- /dev/null\n+++ b/compat/.gitattributes\n@@ -0,0 +1 @@\n+/zlib-uncompress2.c\twhitespace=-indent-with-non-tab,-trailing-space\ndiff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c\nnew file mode 100644\nindex 00000000000..3dcb90b2839\n--- /dev/null\n+++ b/compat/zlib-uncompress2.c\n@@ -0,0 +1,95 @@\n+/* taken from zlib's uncompr.c\n+\n+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f\n+   Author: Mark Adler <madler@alumni.caltech.edu>\n+   Date:   Sun Jan 15 09:18:46 2017 -0800\n+\n+       zlib 1.2.11\n+\n+*/\n+\n+#include \"../reftable/system.h\"\n+#define z_const\n+\n+/*\n+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler\n+ * For conditions of distribution and use, see copyright notice in zlib.h\n+ */\n+\n+#include <zlib.h>\n+\n+/* clang-format off */\n+\n+/* ===========================================================================\n+     Decompresses the source buffer into the destination buffer.  *sourceLen is\n+   the byte length of the source buffer. Upon entry, *destLen is the total size\n+   of the destination buffer, which must be large enough to hold the entire\n+   uncompressed data. (The size of the uncompressed data must have been saved\n+   previously by the compressor and transmitted to the decompressor by some\n+   mechanism outside the scope of this compression library.) Upon exit,\n+   *destLen is the size of the decompressed data and *sourceLen is the number\n+   of source bytes consumed. Upon return, source + *sourceLen points to the\n+   first unused input byte.\n+\n+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough\n+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or\n+   Z_DATA_ERROR if the input data was corrupted, including if the input data is\n+   an incomplete zlib stream.\n+*/\n+int ZEXPORT uncompress2 (\n+    Bytef *dest,\n+    uLongf *destLen,\n+    const Bytef *source,\n+    uLong *sourceLen) {\n+    z_stream stream;\n+    int err;\n+    const uInt max = (uInt)-1;\n+    uLong len, left;\n+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */\n+\n+    len = *sourceLen;\n+    if (*destLen) {\n+        left = *destLen;\n+        *destLen = 0;\n+    }\n+    else {\n+        left = 1;\n+        dest = buf;\n+    }\n+\n+    stream.next_in = (z_const Bytef *)source;\n+    stream.avail_in = 0;\n+    stream.zalloc = (alloc_func)0;\n+    stream.zfree = (free_func)0;\n+    stream.opaque = (voidpf)0;\n+\n+    err = inflateInit(&stream);\n+    if (err != Z_OK) return err;\n+\n+    stream.next_out = dest;\n+    stream.avail_out = 0;\n+\n+    do {\n+        if (stream.avail_out == 0) {\n+            stream.avail_out = left > (uLong)max ? max : (uInt)left;\n+            left -= stream.avail_out;\n+        }\n+        if (stream.avail_in == 0) {\n+            stream.avail_in = len > (uLong)max ? max : (uInt)len;\n+            len -= stream.avail_in;\n+        }\n+        err = inflate(&stream, Z_NO_FLUSH);\n+    } while (err == Z_OK);\n+\n+    *sourceLen -= len + stream.avail_in;\n+    if (dest != buf)\n+        *destLen = stream.total_out;\n+    else if (stream.total_out && err == Z_BUF_ERROR)\n+        left = 1;\n+\n+    inflateEnd(&stream);\n+    return err == Z_STREAM_END ? Z_OK :\n+           err == Z_NEED_DICT ? Z_DATA_ERROR  :\n+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :\n+           err;\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 76516aaa9a5..9399247cbb3 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -258,6 +258,10 @@ ifeq ($(uname_S),FreeBSD)\n \tFILENO_IS_A_MACRO = UnfortunatelyYes\n endif\n ifeq ($(uname_S),OpenBSD)\n+\t# Versions < 7.0 need compatibility layer\n+\tifeq ($(shell expr \"$(uname_R)\" : \"[1-6]\\.\"),2)\n+\t\tNO_UNCOMPRESS2 = UnfortunatelyYes\n+\tendif\n \tNO_STRCASESTR = YesPlease\n \tNO_MEMMEM = YesPlease\n \tUSE_ST_TIMESPEC = YesPlease\n@@ -513,6 +517,7 @@ ifeq ($(uname_S),Interix)\n \tendif\n endif\n ifeq ($(uname_S),Minix)\n+\tNO_UNCOMPRESS2 = YesPlease\n \tNO_IPV6 = YesPlease\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n \tNO_NSEC = YesPlease\ndiff --git a/configure.ac b/configure.ac\nindex 031e8d3fee8..c3a913103d0 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -672,9 +672,22 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],\n \tNO_DEFLATE_BOUND=yes])\n LIBS=\"$old_LIBS\"\n \n+AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [\n+AC_LANG_PROGRAM([#include <zlib.h>],\n+ [uncompress2(NULL,NULL,NULL,NULL);])])\n+AC_MSG_CHECKING([for uncompress2 in -lz])\n+old_LIBS=\"$LIBS\"\n+LIBS=\"$LIBS -lz\"\n+AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],\n+\t[AC_MSG_RESULT([yes])],\n+\t[AC_MSG_RESULT([no])\n+\tNO_UNCOMPRESS2=yes])\n+LIBS=\"$old_LIBS\"\n+\n GIT_UNSTASH_FLAGS($ZLIB_PATH)\n \n GIT_CONF_SUBST([NO_DEFLATE_BOUND])\n+GIT_CONF_SUBST([NO_UNCOMPRESS2])\n \n #\n # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,\n-- \ngitgitgadget\n\n"},{"id":"438212","messageId":"c0d0427d52ce6a7f7f1e75840045f61a0a95ec55.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 13/19] reftable: reftable file level tests","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:09Z","receivedAt":"2021-10-07T20:25:46Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nWith support for reading and writing files in place, we can construct files (in\nmemory) and attempt to read them back.\n\nBecause some sections of the format are optional (eg. indices, log entries), we\nhave to exercise this code using multiple sizes of input data\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   1 +\n reftable/readwrite_test.c | 652 ++++++++++++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   2 +-\n t/helper/test-reftable.c  |   1 +\n 4 files changed, 655 insertions(+), 1 deletion(-)\n create mode 100644 reftable/readwrite_test.c\n\ndiff --git a/Makefile b/Makefile\nindex f6b344cd8ac..65d3856aaea 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2453,6 +2453,7 @@ REFTABLE_OBJS += reftable/writer.o\n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n+REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nnew file mode 100644\nindex 00000000000..5f6bcc2f775\n--- /dev/null\n+++ b/reftable/readwrite_test.c\n@@ -0,0 +1,652 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+\n+static const int update_index = 5;\n+\n+static void test_buffer(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_block out = { NULL };\n+\tint n;\n+\tuint8_t in[] = \"hello\";\n+\tstrbuf_add(&buf, in, sizeof(in));\n+\tblock_source_from_strbuf(&source, &buf);\n+\tEXPECT(block_source_size(&source) == 6);\n+\tn = block_source_read_block(&source, &out, 0, sizeof(in));\n+\tEXPECT(n == sizeof(in));\n+\tEXPECT(!memcmp(in, out.data, n));\n+\treftable_block_done(&out);\n+\n+\tn = block_source_read_block(&source, &out, 1, 2);\n+\tEXPECT(n == 2);\n+\tEXPECT(!memcmp(out.data, \"el\", 2));\n+\n+\treftable_block_done(&out);\n+\tblock_source_close(&source);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void write_table(char ***names, struct strbuf *buf, int N,\n+\t\t\tint block_size, uint32_t hash_id)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = block_size,\n+\t\t.hash_id = hash_id,\n+\t};\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, buf, &opts);\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0, n;\n+\tstruct reftable_log_record log = { NULL };\n+\tconst struct reftable_stats *stats = NULL;\n+\t*names = reftable_calloc(sizeof(char *) * (N + 1));\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tref.refname = name;\n+\t\tref.update_index = update_index;\n+\t\tref.value_type = REFTABLE_REF_VAL1;\n+\t\tref.value.val1 = hash;\n+\t\t(*names)[i] = xstrdup(name);\n+\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA256_RAWSZ] = { 0 };\n+\t\tchar name[100];\n+\t\tint n;\n+\n+\t\tset_test_hash(hash, i);\n+\n+\t\tsnprintf(name, sizeof(name), \"refs/heads/branch%02d\", i);\n+\n+\t\tlog.refname = name;\n+\t\tlog.update_index = update_index;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.new_hash = hash;\n+\t\tlog.value.update.message = \"message\";\n+\n+\t\tn = reftable_writer_add_log(w, &log);\n+\t\tEXPECT(n == 0);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tfor (i = 0; i < stats->ref_stats.blocks; i++) {\n+\t\tint off = i * opts.block_size;\n+\t\tif (off == 0) {\n+\t\t\toff = header_size(\n+\t\t\t\t(hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1);\n+\t\t}\n+\t\tEXPECT(buf->buf[off] == 'r');\n+\t}\n+\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+}\n+\n+static void test_log_buffer_size(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 4096,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record\n+\t\tlog = { .refname = \"refs/heads/master\",\n+\t\t\t.update_index = 0xa,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = { .update = {\n+\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .tz_offset = 100,\n+\t\t\t\t\t   .time = 0x5e430672,\n+\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t   } } };\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\t/* This tests buffer extension for log compression. Must use a random\n+\t   hash, to ensure that the compressed part is larger than the original.\n+\t*/\n+\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\tfor (i = 0; i < GIT_SHA1_RAWSZ; i++) {\n+\t\thash1[i] = (uint8_t)(rand() % 256);\n+\t\thash2[i] = (uint8_t)(rand() % 256);\n+\t}\n+\tlog.value.update.old_hash = hash1;\n+\tlog.value.update.new_hash = hash2;\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\terr = reftable_writer_add_log(w, &log);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_log_write_read(void)\n+{\n+\tint N = 2;\n+\tchar **names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint err;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tstruct reftable_log_record log = { NULL };\n+\tint n;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tconst struct reftable_stats *stats = NULL;\n+\treftable_writer_set_limits(w, 0, N);\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[256];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tsnprintf(name, sizeof(name), \"b%02d%0*d\", i, 130, 7);\n+\t\tnames[i] = xstrdup(name);\n+\t\tref.refname = name;\n+\t\tref.update_index = i;\n+\n+\t\terr = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_log_record log = { NULL };\n+\t\tset_test_hash(hash1, i);\n+\t\tset_test_hash(hash2, i + 1);\n+\n+\t\tlog.refname = names[i];\n+\t\tlog.update_index = i;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tlog.value.update.old_hash = hash1;\n+\t\tlog.value.update.new_hash = hash2;\n+\n+\t\terr = reftable_writer_add_log(w, &log);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\tstats = writer_stats(w);\n+\tEXPECT(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[N - 1]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* end of iteration. */\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT(0 < err);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\terr = reftable_reader_seek_log(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\ti = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT_STREQ(names[i], log.refname);\n+\t\tEXPECT(i == log.update_index);\n+\t\ti++;\n+\t\treftable_log_record_release(&log);\n+\t}\n+\n+\tEXPECT(i == N);\n+\treftable_iterator_destroy(&it);\n+\n+\t/* cleanup. */\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_sequential(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err = 0;\n+\tint j = 0;\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint r = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(r >= 0);\n+\t\tif (r > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tEXPECT(0 == strcmp(names[j], ref.refname));\n+\t\tEXPECT(update_index == ref.update_index);\n+\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == N);\n+\treftable_iterator_destroy(&it);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+\n+\treader_close(&rd);\n+}\n+\n+static void test_table_write_small_table(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 1;\n+\twrite_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);\n+\tEXPECT(buf.len < 200);\n+\tstrbuf_release(&buf);\n+\tfree_names(names);\n+}\n+\n+static void test_table_read_api(void)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i;\n+\tstruct reftable_log_record log = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, names[0]);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_log(&it, &log);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treftable_free(names);\n+\treader_close(&rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_table_read_write_seek(int index, int hash_id)\n+{\n+\tchar **names;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader rd = { NULL };\n+\tstruct reftable_block_source source = { NULL };\n+\tint err;\n+\tint i = 0;\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct strbuf pastLast = STRBUF_INIT;\n+\tstruct reftable_ref_record ref = { NULL };\n+\n+\twrite_table(&names, &buf, N, 256, hash_id);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tEXPECT(hash_id == reftable_reader_hash_id(&rd));\n+\n+\tif (!index) {\n+\t\trd.ref_offsets.index_offset = 0;\n+\t} else {\n+\t\tEXPECT(rd.ref_offsets.index_offset > 0);\n+\t}\n+\n+\tfor (i = 1; i < N; i++) {\n+\t\tint err = reftable_reader_seek_ref(&rd, &it, names[i]);\n+\t\tEXPECT_ERR(err);\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(0 == strcmp(names[i], ref.refname));\n+\t\tEXPECT(REFTABLE_REF_VAL1 == ref.value_type);\n+\t\tEXPECT(i == ref.value.val1[0]);\n+\n+\t\treftable_ref_record_release(&ref);\n+\t\treftable_iterator_destroy(&it);\n+\t}\n+\n+\tstrbuf_addstr(&pastLast, names[N - 1]);\n+\tstrbuf_addstr(&pastLast, \"/\");\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, pastLast.buf);\n+\tif (err == 0) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err > 0);\n+\t} else {\n+\t\tEXPECT(err > 0);\n+\t}\n+\n+\tstrbuf_release(&pastLast);\n+\treftable_iterator_destroy(&it);\n+\n+\tstrbuf_release(&buf);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\treftable_free(names);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_read_write_seek_linear(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_linear_sha256(void)\n+{\n+\ttest_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);\n+}\n+\n+static void test_table_read_write_seek_index(void)\n+{\n+\ttest_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);\n+}\n+\n+static void test_table_refs_for(int indexed)\n+{\n+\tint N = 50;\n+\tchar **want_names = reftable_calloc(sizeof(char *) * (N + 1));\n+\tint want_names_len = 0;\n+\tuint8_t want_hash[GIT_SHA1_RAWSZ];\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint i = 0;\n+\tint n;\n+\tint err;\n+\tstruct reftable_reader rd;\n+\tstruct reftable_block_source source = { NULL };\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint j;\n+\n+\tset_test_hash(want_hash, 4);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[GIT_SHA1_RAWSZ];\n+\t\tchar fill[51] = { 0 };\n+\t\tchar name[100];\n+\t\tuint8_t hash1[GIT_SHA1_RAWSZ];\n+\t\tuint8_t hash2[GIT_SHA1_RAWSZ];\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\n+\t\tmemset(hash, i, sizeof(hash));\n+\t\tmemset(fill, 'x', 50);\n+\t\t/* Put the variable part in the start */\n+\t\tsnprintf(name, sizeof(name), \"br%02d%s\", i, fill);\n+\t\tname[40] = 0;\n+\t\tref.refname = name;\n+\n+\t\tset_test_hash(hash1, i / 4);\n+\t\tset_test_hash(hash2, 3 + i / 4);\n+\t\tref.value_type = REFTABLE_REF_VAL2;\n+\t\tref.value.val2.value = hash1;\n+\t\tref.value.val2.target_value = hash2;\n+\n+\t\t/* 80 bytes / entry, so 3 entries per block. Yields 17\n+\t\t */\n+\t\t/* blocks. */\n+\t\tn = reftable_writer_add_ref(w, &ref);\n+\t\tEXPECT(n == 0);\n+\n+\t\tif (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||\n+\t\t    !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {\n+\t\t\twant_names[want_names_len++] = xstrdup(name);\n+\t\t}\n+\t}\n+\n+\tn = reftable_writer_close(w);\n+\tEXPECT(n == 0);\n+\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = init_reader(&rd, &source, \"file.ref\");\n+\tEXPECT_ERR(err);\n+\tif (!indexed) {\n+\t\trd.obj_offsets.is_present = 0;\n+\t}\n+\n+\terr = reftable_reader_seek_ref(&rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_reader_refs_for(&rd, &it, want_hash);\n+\tEXPECT_ERR(err);\n+\n+\tj = 0;\n+\twhile (1) {\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tEXPECT(err >= 0);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tEXPECT(j < want_names_len);\n+\t\tEXPECT(0 == strcmp(ref.refname, want_names[j]));\n+\t\tj++;\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tEXPECT(j == want_names_len);\n+\n+\tstrbuf_release(&buf);\n+\tfree_names(want_names);\n+\treftable_iterator_destroy(&it);\n+\treader_close(&rd);\n+}\n+\n+static void test_table_refs_for_no_index(void)\n+{\n+\ttest_table_refs_for(0);\n+}\n+\n+static void test_table_refs_for_obj_index(void)\n+{\n+\ttest_table_refs_for(1);\n+}\n+\n+static void test_write_empty_table(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_ref_record rec = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);\n+\treftable_writer_free(w);\n+\n+\tEXPECT(buf.len == header_size(1) + footer_size(1));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &rec);\n+\tEXPECT(err > 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_write_key_order(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record refs[2] = {\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}, {\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = \"target\",\n+\t\t\t},\n+\t\t}\n+\t};\n+\tint err;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\terr = reftable_writer_add_ref(w, &refs[0]);\n+\tEXPECT_ERR(err);\n+\terr = reftable_writer_add_ref(w, &refs[1]);\n+\tprintf(\"%d\\n\", err);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_writer_close(w);\n+\treftable_writer_free(w);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void test_corrupt_table_empty(void)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+}\n+\n+static void test_corrupt_table(void)\n+{\n+\tuint8_t zeros[1024] = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader rd = { NULL };\n+\tint err;\n+\tstrbuf_add(&buf, zeros, sizeof(zeros));\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = init_reader(&rd, &source, \"file.log\");\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\tstrbuf_release(&buf);\n+}\n+\n+int readwrite_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_corrupt_table);\n+\tRUN_TEST(test_corrupt_table_empty);\n+\tRUN_TEST(test_log_write_read);\n+\tRUN_TEST(test_write_key_order);\n+\tRUN_TEST(test_table_read_write_seek_linear_sha256);\n+\tRUN_TEST(test_log_buffer_size);\n+\tRUN_TEST(test_table_write_small_table);\n+\tRUN_TEST(test_buffer);\n+\tRUN_TEST(test_table_read_api);\n+\tRUN_TEST(test_table_read_write_sequential);\n+\tRUN_TEST(test_table_read_write_seek_linear);\n+\tRUN_TEST(test_table_read_write_seek_index);\n+\tRUN_TEST(test_table_refs_for_no_index);\n+\tRUN_TEST(test_table_refs_for_obj_index);\n+\tRUN_TEST(test_write_empty_table);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 5e7698ae654..3d541fa5c0c 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -14,7 +14,7 @@ int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n-int reftable_test_main(int argc, const char **argv);\n+int readwrite_test_main(int argc, const char **argv);\n int stack_test_main(int argc, const char **argv);\n int tree_test_main(int argc, const char **argv);\n int reftable_dump_main(int argc, char *const *argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 050551fa698..898aba836fd 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -6,6 +6,7 @@ int cmd__reftable(int argc, const char **argv)\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"438214","messageId":"6e5ee04308370885e280fa17c19b64c6d34b4f6e.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 14/19] reftable: add a heap-based priority queue for reftable records","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:10Z","receivedAt":"2021-10-07T20:25:47Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis is needed to create a merged view multiple reftables\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |   2 +\n reftable/pq.c             | 105 ++++++++++++++++++++++++++++++++++++++\n reftable/pq.h             |  33 ++++++++++++\n reftable/pq_test.c        |  82 +++++++++++++++++++++++++++++\n reftable/reftable-tests.h |   1 +\n t/helper/test-reftable.c  |   1 +\n 6 files changed, 224 insertions(+)\n create mode 100644 reftable/pq.c\n create mode 100644 reftable/pq.h\n create mode 100644 reftable/pq_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 65d3856aaea..cdfde13b584 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2442,6 +2442,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n REFTABLE_OBJS += reftable/refname.o\n@@ -2452,6 +2453,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\ndiff --git a/reftable/pq.c b/reftable/pq.c\nnew file mode 100644\nindex 00000000000..efc474017a2\n--- /dev/null\n+++ b/reftable/pq.c\n@@ -0,0 +1,105 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"pq.h\"\n+\n+#include \"reftable-record.h\"\n+#include \"system.h\"\n+#include \"basics.h\"\n+\n+int pq_less(struct pq_entry *a, struct pq_entry *b)\n+{\n+\tstruct strbuf ak = STRBUF_INIT;\n+\tstruct strbuf bk = STRBUF_INIT;\n+\tint cmp = 0;\n+\treftable_record_key(&a->rec, &ak);\n+\treftable_record_key(&b->rec, &bk);\n+\n+\tcmp = strbuf_cmp(&ak, &bk);\n+\n+\tstrbuf_release(&ak);\n+\tstrbuf_release(&bk);\n+\n+\tif (cmp == 0)\n+\t\treturn a->index > b->index;\n+\n+\treturn cmp < 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.heap[0];\n+}\n+\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)\n+{\n+\treturn pq.len == 0;\n+}\n+\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tstruct pq_entry e = pq->heap[0];\n+\tpq->heap[0] = pq->heap[pq->len - 1];\n+\tpq->len--;\n+\n+\ti = 0;\n+\twhile (i < pq->len) {\n+\t\tint min = i;\n+\t\tint j = 2 * i + 1;\n+\t\tint k = 2 * i + 2;\n+\t\tif (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) {\n+\t\t\tmin = j;\n+\t\t}\n+\t\tif (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) {\n+\t\t\tmin = k;\n+\t\t}\n+\n+\t\tif (min == i) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[i], pq->heap[min]);\n+\t\ti = min;\n+\t}\n+\n+\treturn e;\n+}\n+\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)\n+{\n+\tint i = 0;\n+\tif (pq->len == pq->cap) {\n+\t\tpq->cap = 2 * pq->cap + 1;\n+\t\tpq->heap = reftable_realloc(pq->heap,\n+\t\t\t\t\t    pq->cap * sizeof(struct pq_entry));\n+\t}\n+\n+\tpq->heap[pq->len++] = e;\n+\ti = pq->len - 1;\n+\twhile (i > 0) {\n+\t\tint j = (i - 1) / 2;\n+\t\tif (pq_less(&pq->heap[j], &pq->heap[i])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tSWAP(pq->heap[j], pq->heap[i]);\n+\n+\t\ti = j;\n+\t}\n+}\n+\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < pq->len; i++) {\n+\t\treftable_record_destroy(&pq->heap[i].rec);\n+\t}\n+\tFREE_AND_NULL(pq->heap);\n+\tpq->len = pq->cap = 0;\n+}\ndiff --git a/reftable/pq.h b/reftable/pq.h\nnew file mode 100644\nindex 00000000000..56fc1b6d873\n--- /dev/null\n+++ b/reftable/pq.h\n@@ -0,0 +1,33 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef PQ_H\n+#define PQ_H\n+\n+#include \"record.h\"\n+\n+struct pq_entry {\n+\tint index;\n+\tstruct reftable_record rec;\n+};\n+\n+struct merged_iter_pqueue {\n+\tstruct pq_entry *heap;\n+\tsize_t len;\n+\tsize_t cap;\n+};\n+\n+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);\n+int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);\n+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);\n+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);\n+void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);\n+int pq_less(struct pq_entry *a, struct pq_entry *b);\n+\n+#endif\ndiff --git a/reftable/pq_test.c b/reftable/pq_test.c\nnew file mode 100644\nindex 00000000000..c9bb05e37b7\n--- /dev/null\n+++ b/reftable/pq_test.c\n@@ -0,0 +1,82 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"pq.h\"\n+#include \"record.h\"\n+#include \"reftable-tests.h\"\n+#include \"test_framework.h\"\n+\n+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)\n+{\n+\tint i;\n+\tfor (i = 1; i < pq.len; i++) {\n+\t\tint parent = (i - 1) / 2;\n+\n+\t\tEXPECT(pq_less(&pq.heap[parent], &pq.heap[i]));\n+\t}\n+}\n+\n+static void test_pq(void)\n+{\n+\tchar *names[54] = { NULL };\n+\tint N = ARRAY_SIZE(names) - 1;\n+\n+\tstruct merged_iter_pqueue pq = { NULL };\n+\tconst char *last = NULL;\n+\n+\tint i = 0;\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tsnprintf(name, sizeof(name), \"%02d\", i);\n+\t\tnames[i] = xstrdup(name);\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct reftable_record rec =\n+\t\t\treftable_new_record(BLOCK_TYPE_REF);\n+\t\tstruct pq_entry e = { 0 };\n+\n+\t\treftable_record_as_ref(&rec)->refname = names[i];\n+\t\te.rec = rec;\n+\t\tmerged_iter_pqueue_add(&pq, e);\n+\t\tmerged_iter_pqueue_check(pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\twhile (!merged_iter_pqueue_is_empty(pq)) {\n+\t\tstruct pq_entry e = merged_iter_pqueue_remove(&pq);\n+\t\tstruct reftable_ref_record *ref =\n+\t\t\treftable_record_as_ref(&e.rec);\n+\n+\t\tmerged_iter_pqueue_check(pq);\n+\n+\t\tif (last) {\n+\t\t\tEXPECT(strcmp(last, ref->refname) < 0);\n+\t\t}\n+\t\tlast = ref->refname;\n+\t\tref->refname = NULL;\n+\t\treftable_free(ref);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_free(names[i]);\n+\t}\n+\n+\tmerged_iter_pqueue_release(&pq);\n+}\n+\n+int pq_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_pq);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h\nindex 3d541fa5c0c..0019cbcfa49 100644\n--- a/reftable/reftable-tests.h\n+++ b/reftable/reftable-tests.h\n@@ -12,6 +12,7 @@ https://developers.google.com/open-source/licenses/bsd\n int basics_test_main(int argc, const char **argv);\n int block_test_main(int argc, const char **argv);\n int merged_test_main(int argc, const char **argv);\n+int pq_test_main(int argc, const char **argv);\n int record_test_main(int argc, const char **argv);\n int refname_test_main(int argc, const char **argv);\n int readwrite_test_main(int argc, const char **argv);\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 898aba836fd..0b5a1701df1 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"438215","messageId":"0b86f6c2327b88886ad8667d28e0fa8382791499.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 15/19] reftable: add merged table view","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:11Z","receivedAt":"2021-10-07T20:25:48Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis adds an abstract, read-only interface to the ref database.\n\nThis primitive is used to construct the read view of the ref database\n(the read view is constructed by merging several *.ref files). It also\nprovides the mechanism to provide a unified view of the refs in the main\nrepository and the per-worktree refs.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                   |   2 +\n reftable/merged.c          | 362 ++++++++++++++++++++++++++++\n reftable/merged.h          |  38 +++\n reftable/merged_test.c     | 468 +++++++++++++++++++++++++++++++++++++\n reftable/reftable-merged.h |  72 ++++++\n t/helper/test-reftable.c   |   1 +\n 6 files changed, 943 insertions(+)\n create mode 100644 reftable/merged.c\n create mode 100644 reftable/merged.h\n create mode 100644 reftable/merged_test.c\n create mode 100644 reftable/reftable-merged.h\n\ndiff --git a/Makefile b/Makefile\nindex cdfde13b584..b81e3e73354 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2442,6 +2442,7 @@ REFTABLE_OBJS += reftable/block.o\n REFTABLE_OBJS += reftable/blocksource.o\n REFTABLE_OBJS += reftable/iter.o\n REFTABLE_OBJS += reftable/publicbasics.o\n+REFTABLE_OBJS += reftable/merged.o\n REFTABLE_OBJS += reftable/pq.o\n REFTABLE_OBJS += reftable/reader.o\n REFTABLE_OBJS += reftable/record.o\n@@ -2453,6 +2454,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\ndiff --git a/reftable/merged.c b/reftable/merged.c\nnew file mode 100644\nindex 00000000000..e5b53da6db3\n--- /dev/null\n+++ b/reftable/merged.c\n@@ -0,0 +1,362 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"constants.h\"\n+#include \"iter.h\"\n+#include \"pq.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"generic.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-error.h\"\n+#include \"system.h\"\n+\n+static int merged_iter_init(struct merged_iter *mi)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\t\tint err = iterator_next(&mi->stack[i], &rec);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tif (err > 0) {\n+\t\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t\t\treftable_record_destroy(&rec);\n+\t\t} else {\n+\t\t\tstruct pq_entry e = {\n+\t\t\t\t.rec = rec,\n+\t\t\t\t.index = i,\n+\t\t\t};\n+\t\t\tmerged_iter_pqueue_add(&mi->pq, e);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merged_iter_close(void *p)\n+{\n+\tstruct merged_iter *mi = p;\n+\tint i = 0;\n+\tmerged_iter_pqueue_release(&mi->pq);\n+\tfor (i = 0; i < mi->stack_len; i++) {\n+\t\treftable_iterator_destroy(&mi->stack[i]);\n+\t}\n+\treftable_free(mi->stack);\n+}\n+\n+static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,\n+\t\t\t\t\t       size_t idx)\n+{\n+\tstruct reftable_record rec = reftable_new_record(mi->typ);\n+\tstruct pq_entry e = {\n+\t\t.rec = rec,\n+\t\t.index = idx,\n+\t};\n+\tint err = iterator_next(&mi->stack[idx], &rec);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tif (err > 0) {\n+\t\treftable_iterator_destroy(&mi->stack[idx]);\n+\t\treftable_record_destroy(&rec);\n+\t\treturn 0;\n+\t}\n+\n+\tmerged_iter_pqueue_add(&mi->pq, e);\n+\treturn 0;\n+}\n+\n+static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)\n+{\n+\tif (iterator_is_null(&mi->stack[idx]))\n+\t\treturn 0;\n+\treturn merged_iter_advance_nonnull_subiter(mi, idx);\n+}\n+\n+static int merged_iter_next_entry(struct merged_iter *mi,\n+\t\t\t\t  struct reftable_record *rec)\n+{\n+\tstruct strbuf entry_key = STRBUF_INIT;\n+\tstruct pq_entry entry = { 0 };\n+\tint err = 0;\n+\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\tentry = merged_iter_pqueue_remove(&mi->pq);\n+\terr = merged_iter_advance_subiter(mi, entry.index);\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\t/*\n+\t  One can also use reftable as datacenter-local storage, where the ref\n+\t  database is maintained in globally consistent database (eg.\n+\t  CockroachDB or Spanner). In this scenario, replication delays together\n+\t  with compaction may cause newer tables to contain older entries. In\n+\t  such a deployment, the loop below must be changed to collect all\n+\t  entries for the same key, and return new the newest one.\n+\t*/\n+\treftable_record_key(&entry.rec, &entry_key);\n+\twhile (!merged_iter_pqueue_is_empty(mi->pq)) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(mi->pq);\n+\t\tstruct strbuf k = STRBUF_INIT;\n+\t\tint err = 0, cmp = 0;\n+\n+\t\treftable_record_key(&top.rec, &k);\n+\n+\t\tcmp = strbuf_cmp(&k, &entry_key);\n+\t\tstrbuf_release(&k);\n+\n+\t\tif (cmp > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmerged_iter_pqueue_remove(&mi->pq);\n+\t\terr = merged_iter_advance_subiter(mi, top.index);\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\t\treftable_record_destroy(&top.rec);\n+\t}\n+\n+\treftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));\n+\treftable_record_destroy(&entry.rec);\n+\tstrbuf_release(&entry_key);\n+\treturn 0;\n+}\n+\n+static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)\n+{\n+\twhile (1) {\n+\t\tint err = merged_iter_next_entry(mi, rec);\n+\t\tif (err == 0 && mi->suppress_deletions &&\n+\t\t    reftable_record_is_deletion(rec)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treturn err;\n+\t}\n+}\n+\n+static int merged_iter_next_void(void *p, struct reftable_record *rec)\n+{\n+\tstruct merged_iter *mi = p;\n+\tif (merged_iter_pqueue_is_empty(mi->pq))\n+\t\treturn 1;\n+\n+\treturn merged_iter_next(mi, rec);\n+}\n+\n+static struct reftable_iterator_vtable merged_iter_vtable = {\n+\t.next = &merged_iter_next_void,\n+\t.close = &merged_iter_close,\n+};\n+\n+static void iterator_from_merged_iter(struct reftable_iterator *it,\n+\t\t\t\t      struct merged_iter *mi)\n+{\n+\tassert(!it->ops);\n+\tit->iter_arg = mi;\n+\tit->ops = &merged_iter_vtable;\n+}\n+\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id)\n+{\n+\tstruct reftable_merged_table *m = NULL;\n+\tuint64_t last_max = 0;\n+\tuint64_t first_min = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t min = reftable_table_min_update_index(&stack[i]);\n+\t\tuint64_t max = reftable_table_max_update_index(&stack[i]);\n+\n+\t\tif (reftable_table_hash_id(&stack[i]) != hash_id) {\n+\t\t\treturn REFTABLE_FORMAT_ERROR;\n+\t\t}\n+\t\tif (i == 0 || min < first_min) {\n+\t\t\tfirst_min = min;\n+\t\t}\n+\t\tif (i == 0 || max > last_max) {\n+\t\t\tlast_max = max;\n+\t\t}\n+\t}\n+\n+\tm = reftable_calloc(sizeof(struct reftable_merged_table));\n+\tm->stack = stack;\n+\tm->stack_len = n;\n+\tm->min = first_min;\n+\tm->max = last_max;\n+\tm->hash_id = hash_id;\n+\t*dest = m;\n+\treturn 0;\n+}\n+\n+/* clears the list of subtable, without affecting the readers themselves. */\n+void merged_table_release(struct reftable_merged_table *mt)\n+{\n+\tFREE_AND_NULL(mt->stack);\n+\tmt->stack_len = 0;\n+}\n+\n+void reftable_merged_table_free(struct reftable_merged_table *mt)\n+{\n+\tif (!mt) {\n+\t\treturn;\n+\t}\n+\tmerged_table_release(mt);\n+\treftable_free(mt);\n+}\n+\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->max;\n+}\n+\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt)\n+{\n+\treturn mt->min;\n+}\n+\n+static int reftable_table_seek_record(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      struct reftable_record *rec)\n+{\n+\treturn tab->ops->seek_record(tab->table_arg, it, rec);\n+}\n+\n+static int merged_table_seek_record(struct reftable_merged_table *mt,\n+\t\t\t\t    struct reftable_iterator *it,\n+\t\t\t\t    struct reftable_record *rec)\n+{\n+\tstruct reftable_iterator *iters = reftable_calloc(\n+\t\tsizeof(struct reftable_iterator) * mt->stack_len);\n+\tstruct merged_iter merged = {\n+\t\t.stack = iters,\n+\t\t.typ = reftable_record_type(rec),\n+\t\t.hash_id = mt->hash_id,\n+\t\t.suppress_deletions = mt->suppress_deletions,\n+\t};\n+\tint n = 0;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (i = 0; i < mt->stack_len && err == 0; i++) {\n+\t\tint e = reftable_table_seek_record(&mt->stack[i], &iters[n],\n+\t\t\t\t\t\t   rec);\n+\t\tif (e < 0) {\n+\t\t\terr = e;\n+\t\t}\n+\t\tif (e == 0) {\n+\t\t\tn++;\n+\t\t}\n+\t}\n+\tif (err < 0) {\n+\t\tint i = 0;\n+\t\tfor (i = 0; i < n; i++) {\n+\t\t\treftable_iterator_destroy(&iters[i]);\n+\t\t}\n+\t\treftable_free(iters);\n+\t\treturn err;\n+\t}\n+\n+\tmerged.stack_len = n;\n+\terr = merged_iter_init(&merged);\n+\tif (err < 0) {\n+\t\tmerged_iter_close(&merged);\n+\t\treturn err;\n+\t} else {\n+\t\tstruct merged_iter *p =\n+\t\t\treftable_malloc(sizeof(struct merged_iter));\n+\t\t*p = merged;\n+\t\titerator_from_merged_iter(it, p);\n+\t}\n+\treturn 0;\n+}\n+\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)name,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_ref(&rec, &ref);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index)\n+{\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)name,\n+\t\t.update_index = update_index,\n+\t};\n+\tstruct reftable_record rec = { NULL };\n+\treftable_record_from_log(&rec, &log);\n+\treturn merged_table_seek_record(mt, it, &rec);\n+}\n+\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name)\n+{\n+\tuint64_t max = ~((uint64_t)0);\n+\treturn reftable_merged_table_seek_log_at(mt, it, name, max);\n+}\n+\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt)\n+{\n+\treturn mt->hash_id;\n+}\n+\n+static int reftable_merged_table_seek_void(void *tab,\n+\t\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t\t   struct reftable_record *rec)\n+{\n+\treturn merged_table_seek_record(tab, it, rec);\n+}\n+\n+static uint32_t reftable_merged_table_hash_id_void(void *tab)\n+{\n+\treturn reftable_merged_table_hash_id(tab);\n+}\n+\n+static uint64_t reftable_merged_table_min_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_min_update_index(tab);\n+}\n+\n+static uint64_t reftable_merged_table_max_update_index_void(void *tab)\n+{\n+\treturn reftable_merged_table_max_update_index(tab);\n+}\n+\n+static struct reftable_table_vtable merged_table_vtable = {\n+\t.seek_record = reftable_merged_table_seek_void,\n+\t.hash_id = reftable_merged_table_hash_id_void,\n+\t.min_update_index = reftable_merged_table_min_update_index_void,\n+\t.max_update_index = reftable_merged_table_max_update_index_void,\n+};\n+\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *merged)\n+{\n+\tassert(!tab->ops);\n+\ttab->ops = &merged_table_vtable;\n+\ttab->table_arg = merged;\n+}\ndiff --git a/reftable/merged.h b/reftable/merged.h\nnew file mode 100644\nindex 00000000000..7d9f95d27ed\n--- /dev/null\n+++ b/reftable/merged.h\n@@ -0,0 +1,38 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef MERGED_H\n+#define MERGED_H\n+\n+#include \"pq.h\"\n+\n+struct reftable_merged_table {\n+\tstruct reftable_table *stack;\n+\tsize_t stack_len;\n+\tuint32_t hash_id;\n+\n+\t/* If unset, produce deletions. This is useful for compaction. For the\n+\t * full stack, deletions should be produced. */\n+\tint suppress_deletions;\n+\n+\tuint64_t min;\n+\tuint64_t max;\n+};\n+\n+struct merged_iter {\n+\tstruct reftable_iterator *stack;\n+\tuint32_t hash_id;\n+\tsize_t stack_len;\n+\tuint8_t typ;\n+\tint suppress_deletions;\n+\tstruct merged_iter_pqueue pq;\n+};\n+\n+void merged_table_release(struct reftable_merged_table *mt);\n+\n+#endif\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nnew file mode 100644\nindex 00000000000..24461e8a802\n--- /dev/null\n+++ b/reftable/merged_test.c\n@@ -0,0 +1,468 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"merged.h\"\n+\n+#include \"system.h\"\n+\n+#include \"basics.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-generic.h\"\n+#include \"reftable-writer.h\"\n+\n+static void write_test_table(struct strbuf *buf,\n+\t\t\t     struct reftable_ref_record refs[], int n)\n+{\n+\tint min = 0xffffffff;\n+\tint max = 0;\n+\tint i = 0;\n+\tint err;\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_writer *w = NULL;\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t ui = refs[i].update_index;\n+\t\tif (ui > max) {\n+\t\t\tmax = ui;\n+\t\t}\n+\t\tif (ui < min) {\n+\t\t\tmin = ui;\n+\t\t}\n+\t}\n+\n+\tw = reftable_new_writer(&strbuf_add_void, buf, &opts);\n+\treftable_writer_set_limits(w, min, max);\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tuint64_t before = refs[i].update_index;\n+\t\tint n = reftable_writer_add_ref(w, &refs[i]);\n+\t\tEXPECT(n == 0);\n+\t\tEXPECT(before == refs[i].update_index);\n+\t}\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\n+\treftable_writer_free(w);\n+}\n+\n+static void write_test_log_table(struct strbuf *buf,\n+\t\t\t\t struct reftable_log_record logs[], int n,\n+\t\t\t\t uint64_t update_index)\n+{\n+\tint i = 0;\n+\tint err;\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t\t.exact_log_message = 1,\n+\t};\n+\tstruct reftable_writer *w = NULL;\n+\tw = reftable_new_writer(&strbuf_add_void, buf, &opts);\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tint err = reftable_writer_add_log(w, &logs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\n+\treftable_writer_free(w);\n+}\n+\n+static struct reftable_merged_table *\n+merged_table_from_records(struct reftable_ref_record **refs,\n+\t\t\t  struct reftable_block_source **source,\n+\t\t\t  struct reftable_reader ***readers, int *sizes,\n+\t\t\t  struct strbuf *buf, int n)\n+{\n+\tint i = 0;\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err;\n+\tstruct reftable_table *tabs =\n+\t\treftable_calloc(n * sizeof(struct reftable_table));\n+\t*readers = reftable_calloc(n * sizeof(struct reftable_reader *));\n+\t*source = reftable_calloc(n * sizeof(**source));\n+\tfor (i = 0; i < n; i++) {\n+\t\twrite_test_table(&buf[i], refs[i], sizes[i]);\n+\t\tblock_source_from_strbuf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_new_reader(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tEXPECT_ERR(err);\n+\t\treftable_table_from_reader(&tabs[i], (*readers)[i]);\n+\t}\n+\n+\terr = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\treturn mt;\n+}\n+\n+static void readers_destroy(struct reftable_reader **readers, size_t n)\n+{\n+\tint i = 0;\n+\tfor (; i < n; i++)\n+\t\treftable_reader_free(readers[i]);\n+\treftable_free(readers);\n+}\n+\n+static void test_merged_between(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };\n+\n+\tstruct reftable_ref_record r1[] = { {\n+\t\t.refname = \"b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = hash1,\n+\t} };\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2 };\n+\tint sizes[] = { 1, 1 };\n+\tstruct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);\n+\tint i;\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tEXPECT_ERR(err);\n+\tEXPECT(ref.update_index == 2);\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treaders_destroy(readers, 2);\n+\treftable_merged_table_free(mt);\n+\tfor (i = 0; i < ARRAY_SIZE(bufs); i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treftable_free(bs);\n+}\n+\n+static void test_merged(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };\n+\tuint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\tstruct reftable_ref_record r3[] = {\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash2,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"d\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t};\n+\n+\tstruct reftable_ref_record want[] = {\n+\t\tr2[0],\n+\t\tr1[1],\n+\t\tr3[0],\n+\t\tr3[1],\n+\t};\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n+\tint sizes[3] = { 3, 1, 2 };\n+\tstruct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_ref(mt, &it, \"a\");\n+\tstruct reftable_ref_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tint i = 0;\n+\n+\tEXPECT_ERR(err);\n+\tEXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);\n+\tEXPECT(reftable_merged_table_min_update_index(mt) == 1);\n+\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (len == cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\tout = reftable_realloc(\n+\t\t\t\tout, sizeof(struct reftable_ref_record) * cap);\n+\t\t}\n+\t\tout[len++] = ref;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tEXPECT(ARRAY_SIZE(want) == len);\n+\tfor (i = 0; i < len; i++) {\n+\t\tEXPECT(reftable_ref_record_equal(&want[i], &out[i],\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t}\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&out[i]);\n+\t}\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+static struct reftable_merged_table *\n+merged_table_from_log_records(struct reftable_log_record **logs,\n+\t\t\t      struct reftable_block_source **source,\n+\t\t\t      struct reftable_reader ***readers, int *sizes,\n+\t\t\t      struct strbuf *buf, int n)\n+{\n+\tint i = 0;\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err;\n+\tstruct reftable_table *tabs =\n+\t\treftable_calloc(n * sizeof(struct reftable_table));\n+\t*readers = reftable_calloc(n * sizeof(struct reftable_reader *));\n+\t*source = reftable_calloc(n * sizeof(**source));\n+\tfor (i = 0; i < n; i++) {\n+\t\twrite_test_log_table(&buf[i], logs[i], sizes[i], i + 1);\n+\t\tblock_source_from_strbuf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_new_reader(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tEXPECT_ERR(err);\n+\t\treftable_table_from_reader(&tabs[i], (*readers)[i]);\n+\t}\n+\n+\terr = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\treturn mt;\n+}\n+\n+static void test_merged_logs(void)\n+{\n+\tuint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };\n+\tuint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };\n+\tuint8_t hash3[GIT_SHA1_RAWSZ] = { 3 };\n+\tstruct reftable_log_record r1[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.old_hash = hash2,\n+\t\t\t\t/* deletion */\n+\t\t\t\t.name = \"jane doe\",\n+\t\t\t\t.email = \"jane@invalid\",\n+\t\t\t\t.message = \"message2\",\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.old_hash = hash1,\n+\t\t\t\t.new_hash = hash2,\n+\t\t\t\t.name = \"jane doe\",\n+\t\t\t\t.email = \"jane@invalid\",\n+\t\t\t\t.message = \"message1\",\n+\t\t\t}\n+\t\t},\n+\t};\n+\tstruct reftable_log_record r2[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.new_hash = hash3,\n+\t\t\t\t.name = \"jane doe\",\n+\t\t\t\t.email = \"jane@invalid\",\n+\t\t\t\t.message = \"message3\",\n+\t\t\t}\n+\t\t},\n+\t};\n+\tstruct reftable_log_record r3[] = {\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t},\n+\t};\n+\tstruct reftable_log_record want[] = {\n+\t\tr2[0],\n+\t\tr3[0],\n+\t\tr1[1],\n+\t};\n+\n+\tstruct reftable_log_record *logs[] = { r1, r2, r3 };\n+\tint sizes[3] = { 2, 1, 1 };\n+\tstruct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt = merged_table_from_log_records(\n+\t\tlogs, &bs, &readers, sizes, bufs, 3);\n+\n+\tstruct reftable_iterator it = { NULL };\n+\tint err = reftable_merged_table_seek_log(mt, &it, \"a\");\n+\tstruct reftable_log_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tint i = 0;\n+\n+\tEXPECT_ERR(err);\n+\tEXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);\n+\tEXPECT(reftable_merged_table_min_update_index(mt) == 1);\n+\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_log_record log = { NULL };\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (len == cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\tout = reftable_realloc(\n+\t\t\t\tout, sizeof(struct reftable_log_record) * cap);\n+\t\t}\n+\t\tout[len++] = log;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tEXPECT(ARRAY_SIZE(want) == len);\n+\tfor (i = 0; i < len; i++) {\n+\t\tEXPECT(reftable_log_record_equal(&want[i], &out[i],\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t}\n+\n+\terr = reftable_merged_table_seek_log_at(mt, &it, \"a\", 2);\n+\tEXPECT_ERR(err);\n+\treftable_log_record_release(&out[0]);\n+\terr = reftable_iterator_next_log(&it, &out[0]);\n+\tEXPECT_ERR(err);\n+\tEXPECT(reftable_log_record_equal(&out[0], &r3[0], GIT_SHA1_RAWSZ));\n+\treftable_iterator_destroy(&it);\n+\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_log_record_release(&out[i]);\n+\t}\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tstrbuf_release(&bufs[i]);\n+\t}\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+static void test_default_write_opts(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"master\",\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);\n+\tuint32_t hash_id;\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_merged_table *merged = NULL;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\thash_id = reftable_reader_hash_id(rd);\n+\tEXPECT(hash_id == GIT_SHA1_FORMAT_ID);\n+\n+\treftable_table_from_reader(&tab[0], rd);\n+\terr = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\treftable_reader_free(rd);\n+\treftable_merged_table_free(merged);\n+\tstrbuf_release(&buf);\n+}\n+\n+/* XXX test refs_for(oid) */\n+\n+int merged_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_merged_logs);\n+\tRUN_TEST(test_merged_between);\n+\tRUN_TEST(test_merged);\n+\tRUN_TEST(test_default_write_opts);\n+\treturn 0;\n+}\ndiff --git a/reftable/reftable-merged.h b/reftable/reftable-merged.h\nnew file mode 100644\nindex 00000000000..1a6d16915ab\n--- /dev/null\n+++ b/reftable/reftable-merged.h\n@@ -0,0 +1,72 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_MERGED_H\n+#define REFTABLE_MERGED_H\n+\n+#include \"reftable-iterator.h\"\n+\n+/*\n+ * Merged tables\n+ *\n+ * A ref database kept in a sequence of table files. The merged_table presents a\n+ * unified view to reading (seeking, iterating) a sequence of immutable tables.\n+ *\n+ * The merged tables are on purpose kept disconnected from their actual storage\n+ * (eg. files on disk), because it is useful to merge tables aren't files. For\n+ * example, the per-workspace and global ref namespace can be implemented as a\n+ * merged table of two stacks of file-backed reftables.\n+ */\n+\n+/* A merged table is implements seeking/iterating over a stack of tables. */\n+struct reftable_merged_table;\n+\n+/* A generic reftable; see below. */\n+struct reftable_table;\n+\n+/* reftable_new_merged_table creates a new merged table. It takes ownership of\n+   the stack array.\n+*/\n+int reftable_new_merged_table(struct reftable_merged_table **dest,\n+\t\t\t      struct reftable_table *stack, int n,\n+\t\t\t      uint32_t hash_id);\n+\n+/* returns an iterator positioned just before 'name' */\n+int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns an iterator for log entry, at given update_index */\n+int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,\n+\t\t\t\t      struct reftable_iterator *it,\n+\t\t\t\t      const char *name, uint64_t update_index);\n+\n+/* like reftable_merged_table_seek_log_at but look for the newest entry. */\n+int reftable_merged_table_seek_log(struct reftable_merged_table *mt,\n+\t\t\t\t   struct reftable_iterator *it,\n+\t\t\t\t   const char *name);\n+\n+/* returns the max update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_max_update_index(struct reftable_merged_table *mt);\n+\n+/* returns the min update_index covered by this merged table. */\n+uint64_t\n+reftable_merged_table_min_update_index(struct reftable_merged_table *mt);\n+\n+/* releases memory for the merged_table */\n+void reftable_merged_table_free(struct reftable_merged_table *m);\n+\n+/* return the hash ID of the merged table. */\n+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);\n+\n+/* create a generic table from reftable_merged_table */\n+void reftable_table_from_merged_table(struct reftable_table *tab,\n+\t\t\t\t      struct reftable_merged_table *table);\n+\n+#endif\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 0b5a1701df1..8087f2da4e6 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)\n {\n \tbasics_test_main(argc, argv);\n \tblock_test_main(argc, argv);\n+\tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n-- \ngitgitgadget\n\n"},{"id":"438216","messageId":"c600e1f975c4be636527105696b1a7f245ab97df.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 16/19] reftable: implement refname validation","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:12Z","receivedAt":"2021-10-07T20:25:50Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThe packed/loose format has restrictions on refnames: a and a/b cannot\ncoexist. This limitation does not apply to reftable per se, but must be\nmaintained for interoperability. This code adds validation routines to\nabort transactions that are trying to add invalid names.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 |   1 +\n reftable/refname.c       | 209 +++++++++++++++++++++++++++++++++++++++\n reftable/refname.h       |  29 ++++++\n reftable/refname_test.c  | 102 +++++++++++++++++++\n t/helper/test-reftable.c |   1 +\n 5 files changed, 342 insertions(+)\n create mode 100644 reftable/refname.c\n create mode 100644 reftable/refname.h\n create mode 100644 reftable/refname_test.c\n\ndiff --git a/Makefile b/Makefile\nindex b81e3e73354..6c652fcacbd 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2458,6 +2458,7 @@ REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n+REFTABLE_TEST_OBJS += reftable/refname_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/refname.c b/reftable/refname.c\nnew file mode 100644\nindex 00000000000..95734969324\n--- /dev/null\n+++ b/reftable/refname.c\n@@ -0,0 +1,209 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"system.h\"\n+#include \"reftable-error.h\"\n+#include \"basics.h\"\n+#include \"refname.h\"\n+#include \"reftable-iterator.h\"\n+\n+struct find_arg {\n+\tchar **names;\n+\tconst char *want;\n+};\n+\n+static int find_name(size_t k, void *arg)\n+{\n+\tstruct find_arg *f_arg = arg;\n+\treturn strcmp(f_arg->names[k], f_arg->want) >= 0;\n+}\n+\n+static int modification_has_ref(struct modification *mod, const char *name)\n+{\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len && !strcmp(mod->add[idx], name)) {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (mod->del_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->del,\n+\t\t\t.want = name,\n+\t\t};\n+\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\tif (idx < mod->del_len && !strcmp(mod->del[idx], name)) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\terr = reftable_table_read_ref(&mod->tab, name, &ref);\n+\treftable_ref_record_release(&ref);\n+\treturn err;\n+}\n+\n+static void modification_release(struct modification *mod)\n+{\n+\t/* don't delete the strings themselves; they're owned by ref records.\n+\t */\n+\tFREE_AND_NULL(mod->add);\n+\tFREE_AND_NULL(mod->del);\n+\tmod->add_len = 0;\n+\tmod->del_len = 0;\n+}\n+\n+static int modification_has_ref_with_prefix(struct modification *mod,\n+\t\t\t\t\t    const char *prefix)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tint err = 0;\n+\n+\tif (mod->add_len > 0) {\n+\t\tstruct find_arg arg = {\n+\t\t\t.names = mod->add,\n+\t\t\t.want = prefix,\n+\t\t};\n+\t\tint idx = binsearch(mod->add_len, find_name, &arg);\n+\t\tif (idx < mod->add_len &&\n+\t\t    !strncmp(prefix, mod->add[idx], strlen(prefix)))\n+\t\t\tgoto done;\n+\t}\n+\terr = reftable_table_seek_ref(&mod->tab, &it, prefix);\n+\tif (err)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\n+\t\tif (mod->del_len > 0) {\n+\t\t\tstruct find_arg arg = {\n+\t\t\t\t.names = mod->del,\n+\t\t\t\t.want = ref.refname,\n+\t\t\t};\n+\t\t\tint idx = binsearch(mod->del_len, find_name, &arg);\n+\t\t\tif (idx < mod->del_len &&\n+\t\t\t    !strcmp(ref.refname, mod->del[idx])) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (strncmp(ref.refname, prefix, strlen(prefix))) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int validate_refname(const char *name)\n+{\n+\twhile (1) {\n+\t\tchar *next = strchr(name, '/');\n+\t\tif (!*name) {\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\t}\n+\t\tif (!next) {\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (next - name == 0 || (next - name == 1 && *name == '.') ||\n+\t\t    (next - name == 2 && name[0] == '.' && name[1] == '.'))\n+\t\t\treturn REFTABLE_REFNAME_ERROR;\n+\t\tname = next + 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz)\n+{\n+\tstruct modification mod = {\n+\t\t.tab = tab,\n+\t\t.add = reftable_calloc(sizeof(char *) * sz),\n+\t\t.del = reftable_calloc(sizeof(char *) * sz),\n+\t};\n+\tint i = 0;\n+\tint err = 0;\n+\tfor (; i < sz; i++) {\n+\t\tif (reftable_ref_record_is_deletion(&recs[i])) {\n+\t\t\tmod.del[mod.del_len++] = recs[i].refname;\n+\t\t} else {\n+\t\t\tmod.add[mod.add_len++] = recs[i].refname;\n+\t\t}\n+\t}\n+\n+\terr = modification_validate(&mod);\n+\tmodification_release(&mod);\n+\treturn err;\n+}\n+\n+static void strbuf_trim_component(struct strbuf *sl)\n+{\n+\twhile (sl->len > 0) {\n+\t\tint is_slash = (sl->buf[sl->len - 1] == '/');\n+\t\tstrbuf_setlen(sl, sl->len - 1);\n+\t\tif (is_slash)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+int modification_validate(struct modification *mod)\n+{\n+\tstruct strbuf slashed = STRBUF_INIT;\n+\tint err = 0;\n+\tint i = 0;\n+\tfor (; i < mod->add_len; i++) {\n+\t\terr = validate_refname(mod->add[i]);\n+\t\tif (err)\n+\t\t\tgoto done;\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\tstrbuf_addstr(&slashed, \"/\");\n+\n+\t\terr = modification_has_ref_with_prefix(mod, slashed.buf);\n+\t\tif (err == 0) {\n+\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tstrbuf_reset(&slashed);\n+\t\tstrbuf_addstr(&slashed, mod->add[i]);\n+\t\twhile (slashed.len) {\n+\t\t\tstrbuf_trim_component(&slashed);\n+\t\t\terr = modification_has_ref(mod, slashed.buf);\n+\t\t\tif (err == 0) {\n+\t\t\t\terr = REFTABLE_NAME_CONFLICT;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\t}\n+\terr = 0;\n+done:\n+\tstrbuf_release(&slashed);\n+\treturn err;\n+}\ndiff --git a/reftable/refname.h b/reftable/refname.h\nnew file mode 100644\nindex 00000000000..a24b40fcb42\n--- /dev/null\n+++ b/reftable/refname.h\n@@ -0,0 +1,29 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+#ifndef REFNAME_H\n+#define REFNAME_H\n+\n+#include \"reftable-record.h\"\n+#include \"reftable-generic.h\"\n+\n+struct modification {\n+\tstruct reftable_table tab;\n+\n+\tchar **add;\n+\tsize_t add_len;\n+\n+\tchar **del;\n+\tsize_t del_len;\n+};\n+\n+int validate_ref_record_addition(struct reftable_table tab,\n+\t\t\t\t struct reftable_ref_record *recs, size_t sz);\n+\n+int modification_validate(struct modification *mod);\n+\n+#endif\ndiff --git a/reftable/refname_test.c b/reftable/refname_test.c\nnew file mode 100644\nindex 00000000000..8645cd93bbd\n--- /dev/null\n+++ b/reftable/refname_test.c\n@@ -0,0 +1,102 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"basics.h\"\n+#include \"block.h\"\n+#include \"blocksource.h\"\n+#include \"constants.h\"\n+#include \"reader.h\"\n+#include \"record.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-writer.h\"\n+#include \"system.h\"\n+\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+struct testcase {\n+\tchar *add;\n+\tchar *del;\n+\tint error_code;\n+};\n+\n+static void test_conflict(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct reftable_writer *w =\n+\t\treftable_new_writer(&strbuf_add_void, &buf, &opts);\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = \"a/b\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"destination\", /* make sure it's not a symref.\n+\t\t\t\t\t\t*/\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tint i;\n+\tstruct reftable_block_source source = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct testcase cases[] = {\n+\t\t{ \"a/b/c\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"b\", NULL, 0 },\n+\t\t{ \"a\", NULL, REFTABLE_NAME_CONFLICT },\n+\t\t{ \"a\", \"a/b\", 0 },\n+\n+\t\t{ \"p/\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p//q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/./q\", NULL, REFTABLE_REFNAME_ERROR },\n+\t\t{ \"p/../q\", NULL, REFTABLE_REFNAME_ERROR },\n+\n+\t\t{ \"a/b/c\", \"a/b\", 0 },\n+\t\t{ NULL, \"a//b\", 0 },\n+\t};\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\terr = reftable_writer_add_ref(w, &rec);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_writer_close(w);\n+\tEXPECT_ERR(err);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_strbuf(&source, &buf);\n+\terr = reftable_new_reader(&rd, &source, \"filename\");\n+\tEXPECT_ERR(err);\n+\n+\treftable_table_from_reader(&tab, rd);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\tstruct modification mod = {\n+\t\t\t.tab = tab,\n+\t\t};\n+\n+\t\tif (cases[i].add) {\n+\t\t\tmod.add = &cases[i].add;\n+\t\t\tmod.add_len = 1;\n+\t\t}\n+\t\tif (cases[i].del) {\n+\t\t\tmod.del = &cases[i].del;\n+\t\t\tmod.del_len = 1;\n+\t\t}\n+\n+\t\terr = modification_validate(&mod);\n+\t\tEXPECT(err == cases[i].error_code);\n+\t}\n+\n+\treftable_reader_free(rd);\n+\tstrbuf_release(&buf);\n+}\n+\n+int refname_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_conflict);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 8087f2da4e6..c8db6852c35 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -8,6 +8,7 @@ int cmd__reftable(int argc, const char **argv)\n \tmerged_test_main(argc, argv);\n \tpq_test_main(argc, argv);\n \trecord_test_main(argc, argv);\n+\trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"438217","messageId":"2f825cdb2d46d4420e1830c9798d62ee3b3b2783.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 18/19] reftable: add dump utility","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:14Z","receivedAt":"2021-10-07T20:25:57Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nprovide a command-line utility for inspecting individual tables, and\ninspecting a complete ref database\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\n---\n reftable/dump.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 107 insertions(+)\n create mode 100644 reftable/dump.c\n\ndiff --git a/reftable/dump.c b/reftable/dump.c\nnew file mode 100644\nindex 00000000000..155953d1b82\n--- /dev/null\n+++ b/reftable/dump.c\n@@ -0,0 +1,107 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"git-compat-util.h\"\n+#include \"hash.h\"\n+\n+#include \"reftable-blocksource.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-merged.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-tests.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-iterator.h\"\n+#include \"reftable-reader.h\"\n+#include \"reftable-stack.h\"\n+#include \"reftable-generic.h\"\n+\n+#include <stddef.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <unistd.h>\n+#include <string.h>\n+\n+static int compact_stack(const char *stackdir)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { 0 };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_stack_compact_all(stack, NULL);\n+\tif (err < 0)\n+\t\tgoto done;\n+done:\n+\tif (stack) {\n+\t\treftable_stack_destroy(stack);\n+\t}\n+\treturn err;\n+}\n+\n+static void print_help(void)\n+{\n+\tprintf(\"usage: dump [-cst] arg\\n\\n\"\n+\t       \"options: \\n\"\n+\t       \"  -c compact\\n\"\n+\t       \"  -t dump table\\n\"\n+\t       \"  -s dump stack\\n\"\n+\t       \"  -6 sha256 hash format\\n\"\n+\t       \"  -h this help\\n\"\n+\t       \"\\n\");\n+}\n+\n+int reftable_dump_main(int argc, char *const *argv)\n+{\n+\tint err = 0;\n+\tint opt_dump_table = 0;\n+\tint opt_dump_stack = 0;\n+\tint opt_compact = 0;\n+\tuint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;\n+\tconst char *arg = NULL, *argv0 = argv[0];\n+\n+\tfor (; argc > 1; argv++, argc--)\n+\t\tif (*argv[1] != '-')\n+\t\t\tbreak;\n+\t\telse if (!strcmp(\"-t\", argv[1]))\n+\t\t\topt_dump_table = 1;\n+\t\telse if (!strcmp(\"-6\", argv[1]))\n+\t\t\topt_hash_id = GIT_SHA256_FORMAT_ID;\n+\t\telse if (!strcmp(\"-s\", argv[1]))\n+\t\t\topt_dump_stack = 1;\n+\t\telse if (!strcmp(\"-c\", argv[1]))\n+\t\t\topt_compact = 1;\n+\t\telse if (!strcmp(\"-?\", argv[1]) || !strcmp(\"-h\", argv[1])) {\n+\t\t\tprint_help();\n+\t\t\treturn 2;\n+\t\t}\n+\n+\tif (argc != 2) {\n+\t\tfprintf(stderr, \"need argument\\n\");\n+\t\tprint_help();\n+\t\treturn 2;\n+\t}\n+\n+\targ = argv[1];\n+\n+\tif (opt_dump_table) {\n+\t\terr = reftable_reader_print_file(arg);\n+\t} else if (opt_dump_stack) {\n+\t\terr = reftable_stack_print_directory(arg, opt_hash_id);\n+\t} else if (opt_compact) {\n+\t\terr = compact_stack(arg);\n+\t}\n+\n+\tif (err < 0) {\n+\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv0, arg,\n+\t\t\treftable_error_str(err));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n-- \ngitgitgadget\n\n"},{"id":"438219","messageId":"55dacc6760f9e7679957ed2055e664f1c8a0615f.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 17/19] reftable: implement stack, a mutable database of reftable files.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:13Z","receivedAt":"2021-10-07T20:26:07Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                  |    1 +\n reftable/reftable-stack.h |  128 ++++\n reftable/stack.c          | 1396 +++++++++++++++++++++++++++++++++++++\n reftable/stack.h          |   41 ++\n reftable/stack_test.c     |  953 +++++++++++++++++++++++++\n t/helper/test-reftable.c  |    1 +\n 6 files changed, 2520 insertions(+)\n create mode 100644 reftable/reftable-stack.h\n create mode 100644 reftable/stack.c\n create mode 100644 reftable/stack.h\n create mode 100644 reftable/stack_test.c\n\ndiff --git a/Makefile b/Makefile\nindex 6c652fcacbd..29344440715 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2459,6 +2459,7 @@ REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\n REFTABLE_TEST_OBJS += reftable/readwrite_test.o\n REFTABLE_TEST_OBJS += reftable/refname_test.o\n+REFTABLE_TEST_OBJS += reftable/stack_test.o\n REFTABLE_TEST_OBJS += reftable/test_framework.o\n REFTABLE_TEST_OBJS += reftable/tree_test.o\n \ndiff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h\nnew file mode 100644\nindex 00000000000..1b602dda58a\n--- /dev/null\n+++ b/reftable/reftable-stack.h\n@@ -0,0 +1,128 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef REFTABLE_STACK_H\n+#define REFTABLE_STACK_H\n+\n+#include \"reftable-writer.h\"\n+\n+/*\n+ * The stack presents an interface to a mutable sequence of reftables.\n+\n+ * A stack can be mutated by pushing a table to the top of the stack.\n+\n+ * The reftable_stack automatically compacts files on disk to ensure good\n+ * amortized performance.\n+ *\n+ * For windows and other platforms that cannot have open files as rename\n+ * destinations, concurrent access from multiple processes needs the rand()\n+ * random seed to be randomized.\n+ */\n+struct reftable_stack;\n+\n+/* open a new reftable stack. The tables along with the table list will be\n+ *  stored in 'dir'. Typically, this should be .git/reftables.\n+ */\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config);\n+\n+/* returns the update_index at which a next table should be written. */\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st);\n+\n+/* holds a transaction to add tables at the top of a stack. */\n+struct reftable_addition;\n+\n+/*\n+ * returns a new transaction to add reftables to the given stack. As a side\n+ * effect, the ref database is locked.\n+ */\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st);\n+\n+/* Adds a reftable to transaction. */\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg);\n+\n+/* Commits the transaction, releasing the lock. After calling this,\n+ * reftable_addition_destroy should still be called.\n+ */\n+int reftable_addition_commit(struct reftable_addition *add);\n+\n+/* Release all non-committed data from the transaction, and deallocate the\n+ * transaction. Releases the lock if held. */\n+void reftable_addition_destroy(struct reftable_addition *add);\n+\n+/* add a new table to the stack. The write_table function must call\n+ * reftable_writer_set_limits, add refs and return an error value. */\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t  void *write_arg),\n+\t\t       void *write_arg);\n+\n+/* returns the merged_table for seeking. This table is valid until the\n+ * next write or reload, and should not be closed or deleted.\n+ */\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st);\n+\n+/* frees all resources associated with the stack. */\n+void reftable_stack_destroy(struct reftable_stack *st);\n+\n+/* Reloads the stack if necessary. This is very cheap to run if the stack was up\n+ * to date */\n+int reftable_stack_reload(struct reftable_stack *st);\n+\n+/* Policy for expiring reflog entries. */\n+struct reftable_log_expiry_config {\n+\t/* Drop entries older than this timestamp */\n+\tuint64_t time;\n+\n+\t/* Drop older entries */\n+\tuint64_t min_update_index;\n+};\n+\n+/* compacts all reftables into a giant table. Expire reflog entries if config is\n+ * non-NULL */\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+\n+/* heuristically compact unbalanced table stack. */\n+int reftable_stack_auto_compact(struct reftable_stack *st);\n+\n+/* delete stale .ref tables. */\n+int reftable_stack_clean(struct reftable_stack *st);\n+\n+/* convenience function to read a single ref. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref);\n+\n+/* convenience function to read a single log. Returns < 0 for error, 0 for\n+ * success, and 1 if ref not found. */\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log);\n+\n+/* statistics on past compactions. */\n+struct reftable_compaction_stats {\n+\tuint64_t bytes; /* total number of bytes written */\n+\tuint64_t entries_written; /* total number of entries written, including\n+\t\t\t\t     failures. */\n+\tint attempts; /* how often we tried to compact */\n+\tint failures; /* failures happen on concurrent updates */\n+};\n+\n+/* return statistics for compaction up till now. */\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st);\n+\n+/* print the entire stack represented by the directory */\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);\n+\n+#endif\ndiff --git a/reftable/stack.c b/reftable/stack.c\nnew file mode 100644\nindex 00000000000..df5021ebf08\n--- /dev/null\n+++ b/reftable/stack.c\n@@ -0,0 +1,1396 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+#include \"merged.h\"\n+#include \"reader.h\"\n+#include \"refname.h\"\n+#include \"reftable-error.h\"\n+#include \"reftable-record.h\"\n+#include \"reftable-merged.h\"\n+#include \"writer.h\"\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg);\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config);\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name);\n+static void reftable_addition_close(struct reftable_addition *add);\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open);\n+\n+static void stack_filename(struct strbuf *dest, struct reftable_stack *st,\n+\t\t\t   const char *name)\n+{\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, st->reftable_dir);\n+\tstrbuf_addstr(dest, \"/\");\n+\tstrbuf_addstr(dest, name);\n+}\n+\n+static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)\n+{\n+\tint *fdp = (int *)arg;\n+\treturn write(*fdp, data, sz);\n+}\n+\n+int reftable_new_stack(struct reftable_stack **dest, const char *dir,\n+\t\t       struct reftable_write_options config)\n+{\n+\tstruct reftable_stack *p =\n+\t\treftable_calloc(sizeof(struct reftable_stack));\n+\tstruct strbuf list_file_name = STRBUF_INIT;\n+\tint err = 0;\n+\n+\tif (config.hash_id == 0) {\n+\t\tconfig.hash_id = GIT_SHA1_FORMAT_ID;\n+\t}\n+\n+\t*dest = NULL;\n+\n+\tstrbuf_reset(&list_file_name);\n+\tstrbuf_addstr(&list_file_name, dir);\n+\tstrbuf_addstr(&list_file_name, \"/tables.list\");\n+\n+\tp->list_file = strbuf_detach(&list_file_name, NULL);\n+\tp->reftable_dir = xstrdup(dir);\n+\tp->config = config;\n+\n+\terr = reftable_stack_reload_maybe_reuse(p, 1);\n+\tif (err < 0) {\n+\t\treftable_stack_destroy(p);\n+\t} else {\n+\t\t*dest = p;\n+\t}\n+\treturn err;\n+}\n+\n+static int fd_read_lines(int fd, char ***namesp)\n+{\n+\toff_t size = lseek(fd, 0, SEEK_END);\n+\tchar *buf = NULL;\n+\tint err = 0;\n+\tif (size < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\terr = lseek(fd, 0, SEEK_SET);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tbuf = reftable_malloc(size + 1);\n+\tif (read(fd, buf, size) != size) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\tbuf[size] = 0;\n+\n+\tparse_names(buf, size, namesp);\n+\n+done:\n+\treftable_free(buf);\n+\treturn err;\n+}\n+\n+int read_lines(const char *filename, char ***namesp)\n+{\n+\tint fd = open(filename, O_RDONLY);\n+\tint err = 0;\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT) {\n+\t\t\t*namesp = reftable_calloc(sizeof(char *));\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\terr = fd_read_lines(fd, namesp);\n+\tclose(fd);\n+\treturn err;\n+}\n+\n+struct reftable_merged_table *\n+reftable_stack_merged_table(struct reftable_stack *st)\n+{\n+\treturn st->merged;\n+}\n+\n+static int has_name(char **names, const char *name)\n+{\n+\twhile (*names) {\n+\t\tif (!strcmp(*names, name))\n+\t\t\treturn 1;\n+\t\tnames++;\n+\t}\n+\treturn 0;\n+}\n+\n+/* Close and free the stack */\n+void reftable_stack_destroy(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = 0;\n+\tif (st->merged) {\n+\t\treftable_merged_table_free(st->merged);\n+\t\tst->merged = NULL;\n+\t}\n+\n+\terr = read_lines(st->list_file, &names);\n+\tif (err < 0) {\n+\t\tFREE_AND_NULL(names);\n+\t}\n+\n+\tif (st->readers) {\n+\t\tint i = 0;\n+\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\tfor (i = 0; i < st->readers_len; i++) {\n+\t\t\tconst char *name = reader_name(st->readers[i]);\n+\t\t\tstrbuf_reset(&filename);\n+\t\t\tif (names && !has_name(names, name)) {\n+\t\t\t\tstack_filename(&filename, st, name);\n+\t\t\t}\n+\t\t\treftable_reader_free(st->readers[i]);\n+\n+\t\t\tif (filename.len) {\n+\t\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\t\tunlink(filename.buf);\n+\t\t\t}\n+\t\t}\n+\t\tstrbuf_release(&filename);\n+\t\tst->readers_len = 0;\n+\t\tFREE_AND_NULL(st->readers);\n+\t}\n+\tFREE_AND_NULL(st->list_file);\n+\tFREE_AND_NULL(st->reftable_dir);\n+\treftable_free(st);\n+\tfree_names(names);\n+}\n+\n+static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n+\t\t\t\t\t\t   int cur_len)\n+{\n+\tstruct reftable_reader **cur =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * cur_len);\n+\tint i = 0;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tcur[i] = st->readers[i];\n+\t}\n+\treturn cur;\n+}\n+\n+static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+\t\t\t\t      int reuse_open)\n+{\n+\tint cur_len = !st->merged ? 0 : st->merged->stack_len;\n+\tstruct reftable_reader **cur = stack_copy_readers(st, cur_len);\n+\tint err = 0;\n+\tint names_len = names_length(names);\n+\tstruct reftable_reader **new_readers =\n+\t\treftable_calloc(sizeof(struct reftable_reader *) * names_len);\n+\tstruct reftable_table *new_tables =\n+\t\treftable_calloc(sizeof(struct reftable_table) * names_len);\n+\tint new_readers_len = 0;\n+\tstruct reftable_merged_table *new_merged = NULL;\n+\tint i;\n+\n+\twhile (*names) {\n+\t\tstruct reftable_reader *rd = NULL;\n+\t\tchar *name = *names++;\n+\n+\t\t/* this is linear; we assume compaction keeps the number of\n+\t\t   tables under control so this is not quadratic. */\n+\t\tint j = 0;\n+\t\tfor (j = 0; reuse_open && j < cur_len; j++) {\n+\t\t\tif (cur[j] && 0 == strcmp(cur[j]->name, name)) {\n+\t\t\t\trd = cur[j];\n+\t\t\t\tcur[j] = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!rd) {\n+\t\t\tstruct reftable_block_source src = { NULL };\n+\t\t\tstruct strbuf table_path = STRBUF_INIT;\n+\t\t\tstack_filename(&table_path, st, name);\n+\n+\t\t\terr = reftable_block_source_from_file(&src,\n+\t\t\t\t\t\t\t      table_path.buf);\n+\t\t\tstrbuf_release(&table_path);\n+\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\n+\t\t\terr = reftable_new_reader(&rd, &src, name);\n+\t\t\tif (err < 0)\n+\t\t\t\tgoto done;\n+\t\t}\n+\n+\t\tnew_readers[new_readers_len] = rd;\n+\t\treftable_table_from_reader(&new_tables[new_readers_len], rd);\n+\t\tnew_readers_len++;\n+\t}\n+\n+\t/* success! */\n+\terr = reftable_new_merged_table(&new_merged, new_tables,\n+\t\t\t\t\tnew_readers_len, st->config.hash_id);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tnew_tables = NULL;\n+\tst->readers_len = new_readers_len;\n+\tif (st->merged) {\n+\t\tmerged_table_release(st->merged);\n+\t\treftable_merged_table_free(st->merged);\n+\t}\n+\tif (st->readers) {\n+\t\treftable_free(st->readers);\n+\t}\n+\tst->readers = new_readers;\n+\tnew_readers = NULL;\n+\tnew_readers_len = 0;\n+\n+\tnew_merged->suppress_deletions = 1;\n+\tst->merged = new_merged;\n+\tfor (i = 0; i < cur_len; i++) {\n+\t\tif (cur[i]) {\n+\t\t\tconst char *name = reader_name(cur[i]);\n+\t\t\tstruct strbuf filename = STRBUF_INIT;\n+\t\t\tstack_filename(&filename, st, name);\n+\n+\t\t\treader_close(cur[i]);\n+\t\t\treftable_reader_free(cur[i]);\n+\n+\t\t\t/* On Windows, can only unlink after closing. */\n+\t\t\tunlink(filename.buf);\n+\n+\t\t\tstrbuf_release(&filename);\n+\t\t}\n+\t}\n+\n+done:\n+\tfor (i = 0; i < new_readers_len; i++) {\n+\t\treader_close(new_readers[i]);\n+\t\treftable_reader_free(new_readers[i]);\n+\t}\n+\treftable_free(new_readers);\n+\treftable_free(new_tables);\n+\treftable_free(cur);\n+\treturn err;\n+}\n+\n+/* return negative if a before b. */\n+static int tv_cmp(struct timeval *a, struct timeval *b)\n+{\n+\ttime_t diff = a->tv_sec - b->tv_sec;\n+\tint udiff = a->tv_usec - b->tv_usec;\n+\n+\tif (diff != 0)\n+\t\treturn diff;\n+\n+\treturn udiff;\n+}\n+\n+static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n+\t\t\t\t\t     int reuse_open)\n+{\n+\tstruct timeval deadline = { 0 };\n+\tint err = gettimeofday(&deadline, NULL);\n+\tint64_t delay = 0;\n+\tint tries = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tdeadline.tv_sec += 3;\n+\twhile (1) {\n+\t\tchar **names = NULL;\n+\t\tchar **names_after = NULL;\n+\t\tstruct timeval now = { 0 };\n+\t\tint err = gettimeofday(&now, NULL);\n+\t\tint err2 = 0;\n+\t\tif (err < 0) {\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* Only look at deadlines after the first few times. This\n+\t\t   simplifies debugging in GDB */\n+\t\ttries++;\n+\t\tif (tries > 3 && tv_cmp(&now, &deadline) >= 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\terr = read_lines(st->list_file, &names);\n+\t\tif (err < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\tif (err == 0) {\n+\t\t\tfree_names(names);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err != REFTABLE_NOT_EXIST_ERROR) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\t/* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent\n+\t\t   writer. Check if there was one by checking if the name list\n+\t\t   changed.\n+\t\t*/\n+\t\terr2 = read_lines(st->list_file, &names_after);\n+\t\tif (err2 < 0) {\n+\t\t\tfree_names(names);\n+\t\t\treturn err2;\n+\t\t}\n+\n+\t\tif (names_equal(names_after, names)) {\n+\t\t\tfree_names(names);\n+\t\t\tfree_names(names_after);\n+\t\t\treturn err;\n+\t\t}\n+\t\tfree_names(names);\n+\t\tfree_names(names_after);\n+\n+\t\tdelay = delay + (delay * rand()) / RAND_MAX + 1;\n+\t\tsleep_millisec(delay);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* -1 = error\n+ 0 = up to date\n+ 1 = changed. */\n+static int stack_uptodate(struct reftable_stack *st)\n+{\n+\tchar **names = NULL;\n+\tint err = read_lines(st->list_file, &names);\n+\tint i = 0;\n+\tif (err < 0)\n+\t\treturn err;\n+\n+\tfor (i = 0; i < st->readers_len; i++) {\n+\t\tif (!names[i]) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (strcmp(st->readers[i]->name, names[i])) {\n+\t\t\terr = 1;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tif (names[st->merged->stack_len]) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tfree_names(names);\n+\treturn err;\n+}\n+\n+int reftable_stack_reload(struct reftable_stack *st)\n+{\n+\tint err = stack_uptodate(st);\n+\tif (err > 0)\n+\t\treturn reftable_stack_reload_maybe_reuse(st, 1);\n+\treturn err;\n+}\n+\n+int reftable_stack_add(struct reftable_stack *st,\n+\t\t       int (*write)(struct reftable_writer *wr, void *arg),\n+\t\t       void *arg)\n+{\n+\tint err = stack_try_add(st, write, arg);\n+\tif (err < 0) {\n+\t\tif (err == REFTABLE_LOCK_ERROR) {\n+\t\t\t/* Ignore error return, we want to propagate\n+\t\t\t   REFTABLE_LOCK_ERROR.\n+\t\t\t*/\n+\t\t\treftable_stack_reload(st);\n+\t\t}\n+\t\treturn err;\n+\t}\n+\n+\tif (!st->disable_auto_compact)\n+\t\treturn reftable_stack_auto_compact(st);\n+\n+\treturn 0;\n+}\n+\n+static void format_name(struct strbuf *dest, uint64_t min, uint64_t max)\n+{\n+\tchar buf[100];\n+\tuint32_t rnd = (uint32_t)rand();\n+\tsnprintf(buf, sizeof(buf), \"0x%012\" PRIx64 \"-0x%012\" PRIx64 \"-%08x\",\n+\t\t min, max, rnd);\n+\tstrbuf_reset(dest);\n+\tstrbuf_addstr(dest, buf);\n+}\n+\n+struct reftable_addition {\n+\tint lock_file_fd;\n+\tstruct strbuf lock_file_name;\n+\tstruct reftable_stack *stack;\n+\n+\tchar **new_tables;\n+\tint new_tables_len;\n+\tuint64_t next_update_index;\n+};\n+\n+#define REFTABLE_ADDITION_INIT                \\\n+\t{                                     \\\n+\t\t.lock_file_name = STRBUF_INIT \\\n+\t}\n+\n+static int reftable_stack_init_addition(struct reftable_addition *add,\n+\t\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tadd->stack = st;\n+\n+\tstrbuf_reset(&add->lock_file_name);\n+\tstrbuf_addstr(&add->lock_file_name, st->list_file);\n+\tstrbuf_addstr(&add->lock_file_name, \".lock\");\n+\n+\tadd->lock_file_fd = open(add->lock_file_name.buf,\n+\t\t\t\t O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (add->lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = REFTABLE_LOCK_ERROR;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\terr = stack_uptodate(st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (err > 1) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->next_update_index = reftable_stack_next_update_index(st);\n+done:\n+\tif (err) {\n+\t\treftable_addition_close(add);\n+\t}\n+\treturn err;\n+}\n+\n+static void reftable_addition_close(struct reftable_addition *add)\n+{\n+\tint i = 0;\n+\tstruct strbuf nm = STRBUF_INIT;\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstack_filename(&nm, add->stack, add->new_tables[i]);\n+\t\tunlink(nm.buf);\n+\t\treftable_free(add->new_tables[i]);\n+\t\tadd->new_tables[i] = NULL;\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\tif (add->lock_file_fd > 0) {\n+\t\tclose(add->lock_file_fd);\n+\t\tadd->lock_file_fd = 0;\n+\t}\n+\tif (add->lock_file_name.len > 0) {\n+\t\tunlink(add->lock_file_name.buf);\n+\t\tstrbuf_release(&add->lock_file_name);\n+\t}\n+\n+\tstrbuf_release(&nm);\n+}\n+\n+void reftable_addition_destroy(struct reftable_addition *add)\n+{\n+\tif (!add) {\n+\t\treturn;\n+\t}\n+\treftable_addition_close(add);\n+\treftable_free(add);\n+}\n+\n+int reftable_addition_commit(struct reftable_addition *add)\n+{\n+\tstruct strbuf table_list = STRBUF_INIT;\n+\tint i = 0;\n+\tint err = 0;\n+\tif (add->new_tables_len == 0)\n+\t\tgoto done;\n+\n+\tfor (i = 0; i < add->stack->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->stack->readers[i]->name);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\tstrbuf_addstr(&table_list, add->new_tables[i]);\n+\t\tstrbuf_addstr(&table_list, \"\\n\");\n+\t}\n+\n+\terr = write(add->lock_file_fd, table_list.buf, table_list.len);\n+\tstrbuf_release(&table_list);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = close(add->lock_file_fd);\n+\tadd->lock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(add->lock_file_name.buf, add->stack->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\t/* success, no more state to clean up. */\n+\tstrbuf_release(&add->lock_file_name);\n+\tfor (i = 0; i < add->new_tables_len; i++) {\n+\t\treftable_free(add->new_tables[i]);\n+\t}\n+\treftable_free(add->new_tables);\n+\tadd->new_tables = NULL;\n+\tadd->new_tables_len = 0;\n+\n+\terr = reftable_stack_reload(add->stack);\n+done:\n+\treftable_addition_close(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_new_addition(struct reftable_addition **dest,\n+\t\t\t\tstruct reftable_stack *st)\n+{\n+\tint err = 0;\n+\tstruct reftable_addition empty = REFTABLE_ADDITION_INIT;\n+\t*dest = reftable_calloc(sizeof(**dest));\n+\t**dest = empty;\n+\terr = reftable_stack_init_addition(*dest, st);\n+\tif (err) {\n+\t\treftable_free(*dest);\n+\t\t*dest = NULL;\n+\t}\n+\treturn err;\n+}\n+\n+static int stack_try_add(struct reftable_stack *st,\n+\t\t\t int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t    void *arg),\n+\t\t\t void *arg)\n+{\n+\tstruct reftable_addition add = REFTABLE_ADDITION_INIT;\n+\tint err = reftable_stack_init_addition(&add, st);\n+\tif (err < 0)\n+\t\tgoto done;\n+\tif (err > 0) {\n+\t\terr = REFTABLE_LOCK_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_addition_add(&add, write_table, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_addition_commit(&add);\n+done:\n+\treftable_addition_close(&add);\n+\treturn err;\n+}\n+\n+int reftable_addition_add(struct reftable_addition *add,\n+\t\t\t  int (*write_table)(struct reftable_writer *wr,\n+\t\t\t\t\t     void *arg),\n+\t\t\t  void *arg)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf tab_file_name = STRBUF_INIT;\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\tint tab_fd = 0;\n+\n+\tstrbuf_reset(&next_name);\n+\tformat_name(&next_name, add->next_update_index, add->next_update_index);\n+\n+\tstack_filename(&temp_tab_file_name, add->stack, next_name.buf);\n+\tstrbuf_addstr(&temp_tab_file_name, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab_file_name.buf);\n+\tif (tab_fd < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd,\n+\t\t\t\t &add->stack->config);\n+\terr = write_table(wr, arg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_writer_close(wr);\n+\tif (err == REFTABLE_EMPTY_TABLE_ERROR) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\terr = stack_check_addition(add->stack, temp_tab_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tif (wr->min_update_index < add->next_update_index) {\n+\t\terr = REFTABLE_API_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tformat_name(&next_name, wr->min_update_index, wr->max_update_index);\n+\tstrbuf_addstr(&next_name, \".ref\");\n+\n+\tstack_filename(&tab_file_name, add->stack, next_name.buf);\n+\n+\t/*\n+\t  On windows, this relies on rand() picking a unique destination name.\n+\t  Maybe we should do retry loop as well?\n+\t */\n+\terr = rename(temp_tab_file_name.buf, tab_file_name.buf);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tadd->new_tables = reftable_realloc(add->new_tables,\n+\t\t\t\t\t   sizeof(*add->new_tables) *\n+\t\t\t\t\t\t   (add->new_tables_len + 1));\n+\tadd->new_tables[add->new_tables_len] = strbuf_detach(&next_name, NULL);\n+\tadd->new_tables_len++;\n+done:\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (temp_tab_file_name.len > 0) {\n+\t\tunlink(temp_tab_file_name.buf);\n+\t}\n+\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&tab_file_name);\n+\tstrbuf_release(&next_name);\n+\treftable_writer_free(wr);\n+\treturn err;\n+}\n+\n+uint64_t reftable_stack_next_update_index(struct reftable_stack *st)\n+{\n+\tint sz = st->merged->stack_len;\n+\tif (sz > 0)\n+\t\treturn reftable_reader_max_update_index(st->readers[sz - 1]) +\n+\t\t       1;\n+\treturn 1;\n+}\n+\n+static int stack_compact_locked(struct reftable_stack *st, int first, int last,\n+\t\t\t\tstruct strbuf *temp_tab,\n+\t\t\t\tstruct reftable_log_expiry_config *config)\n+{\n+\tstruct strbuf next_name = STRBUF_INIT;\n+\tint tab_fd = -1;\n+\tstruct reftable_writer *wr = NULL;\n+\tint err = 0;\n+\n+\tformat_name(&next_name,\n+\t\t    reftable_reader_min_update_index(st->readers[first]),\n+\t\t    reftable_reader_max_update_index(st->readers[last]));\n+\n+\tstack_filename(temp_tab, st, next_name.buf);\n+\tstrbuf_addstr(temp_tab, \".temp.XXXXXX\");\n+\n+\ttab_fd = mkstemp(temp_tab->buf);\n+\twr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);\n+\n+\terr = stack_write_compact(st, wr, first, last, config);\n+\tif (err < 0)\n+\t\tgoto done;\n+\terr = reftable_writer_close(wr);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = close(tab_fd);\n+\ttab_fd = 0;\n+\n+done:\n+\treftable_writer_free(wr);\n+\tif (tab_fd > 0) {\n+\t\tclose(tab_fd);\n+\t\ttab_fd = 0;\n+\t}\n+\tif (err != 0 && temp_tab->len > 0) {\n+\t\tunlink(temp_tab->buf);\n+\t\tstrbuf_release(temp_tab);\n+\t}\n+\tstrbuf_release(&next_name);\n+\treturn err;\n+}\n+\n+static int stack_write_compact(struct reftable_stack *st,\n+\t\t\t       struct reftable_writer *wr, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\tint subtabs_len = last - first + 1;\n+\tstruct reftable_table *subtabs = reftable_calloc(\n+\t\tsizeof(struct reftable_table) * (last - first + 1));\n+\tstruct reftable_merged_table *mt = NULL;\n+\tint err = 0;\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_ref_record ref = { NULL };\n+\tstruct reftable_log_record log = { NULL };\n+\n+\tuint64_t entries = 0;\n+\n+\tint i = 0, j = 0;\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct reftable_reader *t = st->readers[i];\n+\t\treftable_table_from_reader(&subtabs[j++], t);\n+\t\tst->stats.bytes += t->size;\n+\t}\n+\treftable_writer_set_limits(wr, st->readers[first]->min_update_index,\n+\t\t\t\t   st->readers[last]->max_update_index);\n+\n+\terr = reftable_new_merged_table(&mt, subtabs, subtabs_len,\n+\t\t\t\t\tst->config.hash_id);\n+\tif (err < 0) {\n+\t\treftable_free(subtabs);\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_merged_table_seek_ref(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (first == 0 && reftable_ref_record_is_deletion(&ref)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_ref(wr, &ref);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\terr = reftable_merged_table_seek_log(mt, &it, \"\");\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (first == 0 && reftable_log_record_is_deletion(&log)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->min_update_index > 0 &&\n+\t\t    log.update_index < config->min_update_index) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (config && config->time > 0 &&\n+\t\t    log.value.update.time < config->time) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\terr = reftable_writer_add_log(wr, &log);\n+\t\tif (err < 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tentries++;\n+\t}\n+\n+done:\n+\treftable_iterator_destroy(&it);\n+\tif (mt) {\n+\t\tmerged_table_release(mt);\n+\t\treftable_merged_table_free(mt);\n+\t}\n+\treftable_ref_record_release(&ref);\n+\treftable_log_record_release(&log);\n+\tst->stats.entries_written += entries;\n+\treturn err;\n+}\n+\n+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */\n+static int stack_compact_range(struct reftable_stack *st, int first, int last,\n+\t\t\t       struct reftable_log_expiry_config *expiry)\n+{\n+\tstruct strbuf temp_tab_file_name = STRBUF_INIT;\n+\tstruct strbuf new_table_name = STRBUF_INIT;\n+\tstruct strbuf lock_file_name = STRBUF_INIT;\n+\tstruct strbuf ref_list_contents = STRBUF_INIT;\n+\tstruct strbuf new_table_path = STRBUF_INIT;\n+\tint err = 0;\n+\tint have_lock = 0;\n+\tint lock_file_fd = 0;\n+\tint compact_count = last - first + 1;\n+\tchar **listp = NULL;\n+\tchar **delete_on_success =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tchar **subtable_locks =\n+\t\treftable_calloc(sizeof(char *) * (compact_count + 1));\n+\tint i = 0;\n+\tint j = 0;\n+\tint is_empty_table = 0;\n+\n+\tif (first > last || (!expiry && first == last)) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\n+\tst->stats.attempts++;\n+\n+\tstrbuf_reset(&lock_file_name);\n+\tstrbuf_addstr(&lock_file_name, st->list_file);\n+\tstrbuf_addstr(&lock_file_name, \".lock\");\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\t/* Don't want to write to the lock for now.  */\n+\tclose(lock_file_fd);\n+\tlock_file_fd = 0;\n+\n+\thave_lock = 1;\n+\terr = stack_uptodate(st);\n+\tif (err != 0)\n+\t\tgoto done;\n+\n+\tfor (i = first, j = 0; i <= last; i++) {\n+\t\tstruct strbuf subtab_file_name = STRBUF_INIT;\n+\t\tstruct strbuf subtab_lock = STRBUF_INIT;\n+\t\tint sublock_file_fd = -1;\n+\n+\t\tstack_filename(&subtab_file_name, st,\n+\t\t\t       reader_name(st->readers[i]));\n+\n+\t\tstrbuf_reset(&subtab_lock);\n+\t\tstrbuf_addbuf(&subtab_lock, &subtab_file_name);\n+\t\tstrbuf_addstr(&subtab_lock, \".lock\");\n+\n+\t\tsublock_file_fd = open(subtab_lock.buf,\n+\t\t\t\t       O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\t\tif (sublock_file_fd > 0) {\n+\t\t\tclose(sublock_file_fd);\n+\t\t} else if (sublock_file_fd < 0) {\n+\t\t\tif (errno == EEXIST) {\n+\t\t\t\terr = 1;\n+\t\t\t} else {\n+\t\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\t}\n+\t\t}\n+\n+\t\tsubtable_locks[j] = subtab_lock.buf;\n+\t\tdelete_on_success[j] = subtab_file_name.buf;\n+\t\tj++;\n+\n+\t\tif (err != 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\terr = unlink(lock_file_name.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\thave_lock = 0;\n+\n+\terr = stack_compact_locked(st, first, last, &temp_tab_file_name,\n+\t\t\t\t   expiry);\n+\t/* Compaction + tombstones can create an empty table out of non-empty\n+\t * tables. */\n+\tis_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);\n+\tif (is_empty_table) {\n+\t\terr = 0;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tlock_file_fd =\n+\t\topen(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0644);\n+\tif (lock_file_fd < 0) {\n+\t\tif (errno == EEXIST) {\n+\t\t\terr = 1;\n+\t\t} else {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\thave_lock = 1;\n+\n+\tformat_name(&new_table_name, st->readers[first]->min_update_index,\n+\t\t    st->readers[last]->max_update_index);\n+\tstrbuf_addstr(&new_table_name, \".ref\");\n+\n+\tstack_filename(&new_table_path, st, new_table_name.buf);\n+\n+\tif (!is_empty_table) {\n+\t\t/* retry? */\n+\t\terr = rename(temp_tab_file_name.buf, new_table_path.buf);\n+\t\tif (err < 0) {\n+\t\t\terr = REFTABLE_IO_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < first; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tif (!is_empty_table) {\n+\t\tstrbuf_addbuf(&ref_list_contents, &new_table_name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\tfor (i = last + 1; i < st->merged->stack_len; i++) {\n+\t\tstrbuf_addstr(&ref_list_contents, st->readers[i]->name);\n+\t\tstrbuf_addstr(&ref_list_contents, \"\\n\");\n+\t}\n+\n+\terr = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\terr = close(lock_file_fd);\n+\tlock_file_fd = 0;\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\n+\terr = rename(lock_file_name.buf, st->list_file);\n+\tif (err < 0) {\n+\t\terr = REFTABLE_IO_ERROR;\n+\t\tunlink(new_table_path.buf);\n+\t\tgoto done;\n+\t}\n+\thave_lock = 0;\n+\n+\t/* Reload the stack before deleting. On windows, we can only delete the\n+\t   files after we closed them.\n+\t*/\n+\terr = reftable_stack_reload_maybe_reuse(st, first < last);\n+\n+\tlistp = delete_on_success;\n+\twhile (*listp) {\n+\t\tif (strcmp(*listp, new_table_path.buf)) {\n+\t\t\tunlink(*listp);\n+\t\t}\n+\t\tlistp++;\n+\t}\n+\n+done:\n+\tfree_names(delete_on_success);\n+\n+\tlistp = subtable_locks;\n+\twhile (*listp) {\n+\t\tunlink(*listp);\n+\t\tlistp++;\n+\t}\n+\tfree_names(subtable_locks);\n+\tif (lock_file_fd > 0) {\n+\t\tclose(lock_file_fd);\n+\t\tlock_file_fd = 0;\n+\t}\n+\tif (have_lock) {\n+\t\tunlink(lock_file_name.buf);\n+\t}\n+\tstrbuf_release(&new_table_name);\n+\tstrbuf_release(&new_table_path);\n+\tstrbuf_release(&ref_list_contents);\n+\tstrbuf_release(&temp_tab_file_name);\n+\tstrbuf_release(&lock_file_name);\n+\treturn err;\n+}\n+\n+int reftable_stack_compact_all(struct reftable_stack *st,\n+\t\t\t       struct reftable_log_expiry_config *config)\n+{\n+\treturn stack_compact_range(st, 0, st->merged->stack_len - 1, config);\n+}\n+\n+static int stack_compact_range_stats(struct reftable_stack *st, int first,\n+\t\t\t\t     int last,\n+\t\t\t\t     struct reftable_log_expiry_config *config)\n+{\n+\tint err = stack_compact_range(st, first, last, config);\n+\tif (err > 0) {\n+\t\tst->stats.failures++;\n+\t}\n+\treturn err;\n+}\n+\n+static int segment_size(struct segment *s)\n+{\n+\treturn s->end - s->start;\n+}\n+\n+int fastlog2(uint64_t sz)\n+{\n+\tint l = 0;\n+\tif (sz == 0)\n+\t\treturn 0;\n+\tfor (; sz; sz /= 2) {\n+\t\tl++;\n+\t}\n+\treturn l - 1;\n+}\n+\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)\n+{\n+\tstruct segment *segs = reftable_calloc(sizeof(struct segment) * n);\n+\tint next = 0;\n+\tstruct segment cur = { 0 };\n+\tint i = 0;\n+\n+\tif (n == 0) {\n+\t\t*seglen = 0;\n+\t\treturn segs;\n+\t}\n+\tfor (i = 0; i < n; i++) {\n+\t\tint log = fastlog2(sizes[i]);\n+\t\tif (cur.log != log && cur.bytes > 0) {\n+\t\t\tstruct segment fresh = {\n+\t\t\t\t.start = i,\n+\t\t\t};\n+\n+\t\t\tsegs[next++] = cur;\n+\t\t\tcur = fresh;\n+\t\t}\n+\n+\t\tcur.log = log;\n+\t\tcur.end = i + 1;\n+\t\tcur.bytes += sizes[i];\n+\t}\n+\tsegs[next++] = cur;\n+\t*seglen = next;\n+\treturn segs;\n+}\n+\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, sizes, n);\n+\tstruct segment min_seg = {\n+\t\t.log = 64,\n+\t};\n+\tint i = 0;\n+\tfor (i = 0; i < seglen; i++) {\n+\t\tif (segment_size(&segs[i]) == 1) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (segs[i].log < min_seg.log) {\n+\t\t\tmin_seg = segs[i];\n+\t\t}\n+\t}\n+\n+\twhile (min_seg.start > 0) {\n+\t\tint prev = min_seg.start - 1;\n+\t\tif (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmin_seg.start = prev;\n+\t\tmin_seg.bytes += sizes[prev];\n+\t}\n+\n+\treftable_free(segs);\n+\treturn min_seg;\n+}\n+\n+static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes =\n+\t\treftable_calloc(sizeof(uint64_t) * st->merged->stack_len);\n+\tint version = (st->config.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2;\n+\tint overhead = header_size(version) - 1;\n+\tint i = 0;\n+\tfor (i = 0; i < st->merged->stack_len; i++) {\n+\t\tsizes[i] = st->readers[i]->size - overhead;\n+\t}\n+\treturn sizes;\n+}\n+\n+int reftable_stack_auto_compact(struct reftable_stack *st)\n+{\n+\tuint64_t *sizes = stack_table_sizes_for_compaction(st);\n+\tstruct segment seg =\n+\t\tsuggest_compaction_segment(sizes, st->merged->stack_len);\n+\treftable_free(sizes);\n+\tif (segment_size(&seg) > 0)\n+\t\treturn stack_compact_range_stats(st, seg.start, seg.end - 1,\n+\t\t\t\t\t\t NULL);\n+\n+\treturn 0;\n+}\n+\n+struct reftable_compaction_stats *\n+reftable_stack_compaction_stats(struct reftable_stack *st)\n+{\n+\treturn &st->stats;\n+}\n+\n+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_ref_record *ref)\n+{\n+\tstruct reftable_table tab = { NULL };\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\treturn reftable_table_read_ref(&tab, refname, ref);\n+}\n+\n+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,\n+\t\t\t    struct reftable_log_record *log)\n+{\n+\tstruct reftable_iterator it = { NULL };\n+\tstruct reftable_merged_table *mt = reftable_stack_merged_table(st);\n+\tint err = reftable_merged_table_seek_log(mt, &it, refname);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = reftable_iterator_next_log(&it, log);\n+\tif (err)\n+\t\tgoto done;\n+\n+\tif (strcmp(log->refname, refname) ||\n+\t    reftable_log_record_is_deletion(log)) {\n+\t\terr = 1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tif (err) {\n+\t\treftable_log_record_release(log);\n+\t}\n+\treftable_iterator_destroy(&it);\n+\treturn err;\n+}\n+\n+static int stack_check_addition(struct reftable_stack *st,\n+\t\t\t\tconst char *new_tab_name)\n+{\n+\tint err = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_table tab = { NULL };\n+\tstruct reftable_ref_record *refs = NULL;\n+\tstruct reftable_iterator it = { NULL };\n+\tint cap = 0;\n+\tint len = 0;\n+\tint i = 0;\n+\n+\tif (st->config.skip_name_check)\n+\t\treturn 0;\n+\n+\terr = reftable_block_source_from_file(&src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, new_tab_name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_reader_seek_ref(rd, &it, \"\");\n+\tif (err > 0) {\n+\t\terr = 0;\n+\t\tgoto done;\n+\t}\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\twhile (1) {\n+\t\tstruct reftable_ref_record ref = { NULL };\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (err < 0)\n+\t\t\tgoto done;\n+\n+\t\tif (len >= cap) {\n+\t\t\tcap = 2 * cap + 1;\n+\t\t\trefs = reftable_realloc(refs, cap * sizeof(refs[0]));\n+\t\t}\n+\n+\t\trefs[len++] = ref;\n+\t}\n+\n+\treftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));\n+\n+\terr = validate_ref_record_addition(tab, refs, len);\n+\n+done:\n+\tfor (i = 0; i < len; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t}\n+\n+\tfree(refs);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_free(rd);\n+\treturn err;\n+}\n+\n+static int is_table_name(const char *s)\n+{\n+\tconst char *dot = strrchr(s, '.');\n+\treturn dot && !strcmp(dot, \".ref\");\n+}\n+\n+static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max,\n+\t\t\t\t     const char *name)\n+{\n+\tint err = 0;\n+\tuint64_t update_idx = 0;\n+\tstruct reftable_block_source src = { NULL };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct strbuf table_path = STRBUF_INIT;\n+\tstack_filename(&table_path, st, name);\n+\n+\terr = reftable_block_source_from_file(&src, table_path.buf);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\terr = reftable_new_reader(&rd, &src, name);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tupdate_idx = reftable_reader_max_update_index(rd);\n+\treftable_reader_free(rd);\n+\n+\tif (update_idx <= max) {\n+\t\tunlink(table_path.buf);\n+\t}\n+done:\n+\tstrbuf_release(&table_path);\n+}\n+\n+static int reftable_stack_clean_locked(struct reftable_stack *st)\n+{\n+\tuint64_t max = reftable_merged_table_max_update_index(\n+\t\treftable_stack_merged_table(st));\n+\tDIR *dir = opendir(st->reftable_dir);\n+\tstruct dirent *d = NULL;\n+\tif (!dir) {\n+\t\treturn REFTABLE_IO_ERROR;\n+\t}\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tint i = 0;\n+\t\tint found = 0;\n+\t\tif (!is_table_name(d->d_name))\n+\t\t\tcontinue;\n+\n+\t\tfor (i = 0; !found && i < st->readers_len; i++) {\n+\t\t\tfound = !strcmp(reader_name(st->readers[i]), d->d_name);\n+\t\t}\n+\t\tif (found)\n+\t\t\tcontinue;\n+\n+\t\tremove_maybe_stale_table(st, max, d->d_name);\n+\t}\n+\n+\tclosedir(dir);\n+\treturn 0;\n+}\n+\n+int reftable_stack_clean(struct reftable_stack *st)\n+{\n+\tstruct reftable_addition *add = NULL;\n+\tint err = reftable_stack_new_addition(&add, st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_reload(st);\n+\tif (err < 0) {\n+\t\tgoto done;\n+\t}\n+\n+\terr = reftable_stack_clean_locked(st);\n+\n+done:\n+\treftable_addition_destroy(add);\n+\treturn err;\n+}\n+\n+int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id)\n+{\n+\tstruct reftable_stack *stack = NULL;\n+\tstruct reftable_write_options cfg = { .hash_id = hash_id };\n+\tstruct reftable_merged_table *merged = NULL;\n+\tstruct reftable_table table = { NULL };\n+\n+\tint err = reftable_new_stack(&stack, stackdir, cfg);\n+\tif (err < 0)\n+\t\tgoto done;\n+\n+\tmerged = reftable_stack_merged_table(stack);\n+\treftable_table_from_merged_table(&table, merged);\n+\terr = reftable_table_print(&table);\n+done:\n+\tif (stack)\n+\t\treftable_stack_destroy(stack);\n+\treturn err;\n+}\ndiff --git a/reftable/stack.h b/reftable/stack.h\nnew file mode 100644\nindex 00000000000..f57005846e5\n--- /dev/null\n+++ b/reftable/stack.h\n@@ -0,0 +1,41 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#ifndef STACK_H\n+#define STACK_H\n+\n+#include \"system.h\"\n+#include \"reftable-writer.h\"\n+#include \"reftable-stack.h\"\n+\n+struct reftable_stack {\n+\tchar *list_file;\n+\tchar *reftable_dir;\n+\tint disable_auto_compact;\n+\n+\tstruct reftable_write_options config;\n+\n+\tstruct reftable_reader **readers;\n+\tsize_t readers_len;\n+\tstruct reftable_merged_table *merged;\n+\tstruct reftable_compaction_stats stats;\n+};\n+\n+int read_lines(const char *filename, char ***lines);\n+\n+struct segment {\n+\tint start, end;\n+\tint log;\n+\tuint64_t bytes;\n+};\n+\n+int fastlog2(uint64_t sz);\n+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);\n+struct segment suggest_compaction_segment(uint64_t *sizes, int n);\n+\n+#endif\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nnew file mode 100644\nindex 00000000000..eb0b7228b0c\n--- /dev/null\n+++ b/reftable/stack_test.c\n@@ -0,0 +1,953 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"stack.h\"\n+\n+#include \"system.h\"\n+\n+#include \"reftable-reader.h\"\n+#include \"merged.h\"\n+#include \"basics.h\"\n+#include \"constants.h\"\n+#include \"record.h\"\n+#include \"test_framework.h\"\n+#include \"reftable-tests.h\"\n+\n+#include <sys/types.h>\n+#include <dirent.h>\n+\n+static void clear_dir(const char *dirname)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, dirname);\n+\tremove_dir_recursively(&path, 0);\n+\tstrbuf_release(&path);\n+}\n+\n+static int count_dir_entries(const char *dirname)\n+{\n+\tDIR *dir = opendir(dirname);\n+\tint len = 0;\n+\tstruct dirent *d;\n+\tif (dir == NULL)\n+\t\treturn 0;\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tif (!strcmp(d->d_name, \"..\") || !strcmp(d->d_name, \".\"))\n+\t\t\tcontinue;\n+\t\tlen++;\n+\t}\n+\tclosedir(dir);\n+\treturn len;\n+}\n+\n+/*\n+ * Work linenumber into the tempdir, so we can see which tests forget to\n+ * cleanup.\n+ */\n+static char *get_tmp_template(int linenumber)\n+{\n+\tconst char *tmp = getenv(\"TMPDIR\");\n+\tstatic char template[1024];\n+\tsnprintf(template, sizeof(template) - 1, \"%s/stack_test-%d.XXXXXX\",\n+\t\t tmp ? tmp : \"/tmp\", linenumber);\n+\treturn template;\n+}\n+\n+static char *get_tmp_dir(int linenumber)\n+{\n+\tchar *dir = get_tmp_template(linenumber);\n+\tEXPECT(mkdtemp(dir));\n+\treturn dir;\n+}\n+\n+static void test_read_file(void)\n+{\n+\tchar *fn = get_tmp_template(__LINE__);\n+\tint fd = mkstemp(fn);\n+\tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n+\tint n, err;\n+\tchar **names = NULL;\n+\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tint i = 0;\n+\n+\tEXPECT(fd > 0);\n+\tn = write(fd, out, strlen(out));\n+\tEXPECT(n == strlen(out));\n+\terr = close(fd);\n+\tEXPECT(err >= 0);\n+\n+\terr = read_lines(fn, &names);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; names[i]; i++) {\n+\t\tEXPECT(0 == strcmp(want[i], names[i]));\n+\t}\n+\tfree_names(names);\n+\tremove(fn);\n+}\n+\n+static void test_parse_names(void)\n+{\n+\tchar buf[] = \"line\\n\";\n+\tchar **names = NULL;\n+\tparse_names(buf, strlen(buf), &names);\n+\n+\tEXPECT(NULL != names[0]);\n+\tEXPECT(0 == strcmp(names[0], \"line\"));\n+\tEXPECT(NULL == names[1]);\n+\tfree_names(names);\n+}\n+\n+static void test_names_equal(void)\n+{\n+\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tchar *c[] = { \"a\", \"b\", NULL };\n+\n+\tEXPECT(names_equal(a, a));\n+\tEXPECT(!names_equal(a, b));\n+\tEXPECT(!names_equal(a, c));\n+}\n+\n+static int write_test_ref(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct reftable_ref_record *ref = arg;\n+\treftable_writer_set_limits(wr, ref->update_index, ref->update_index);\n+\treturn reftable_writer_add_ref(wr, ref);\n+}\n+\n+struct write_log_arg {\n+\tstruct reftable_log_record *log;\n+\tuint64_t update_index;\n+};\n+\n+static int write_test_log(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct write_log_arg *wla = arg;\n+\n+\treftable_writer_set_limits(wr, wla->update_index, wla->update_index);\n+\treturn reftable_writer_add_log(wr, wla->log);\n+}\n+\n+static void test_reftable_stack_add_one(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\tprintf(\"testing print functionality:\\n\");\n+\terr = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_uptodate(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL;\n+\tstruct reftable_stack *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"branch2\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\n+\t/* simulate multi-process access to the same stack\n+\t   by creating two stacks for the same directory.\n+\t */\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st1, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_LOCK_ERROR);\n+\n+\terr = reftable_stack_reload(st2);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st2, &write_test_ref, &ref2);\n+\tEXPECT_ERR(err);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_transaction_api(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_addition *add = NULL;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_new_addition(&add, st);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_add(add, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_addition_commit(add);\n+\tEXPECT_ERR(err);\n+\n+\treftable_addition_destroy(add);\n+\n+\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(REFTABLE_REF_SYMREF == dest.value_type);\n+\tEXPECT(0 == strcmp(\"master\", dest.value.symref));\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_validate_refname(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint i;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"a/b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tchar *additions[] = { \"a\", \"a/b/c\" };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < ARRAY_SIZE(additions); i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = additions[i],\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT(err == REFTABLE_NAME_CONFLICT);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static int write_error(struct reftable_writer *wr, void *arg)\n+{\n+\treturn *((int *)arg);\n+}\n+\n+static void test_reftable_stack_update_index_check(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = \"name1\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = \"name2\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"master\",\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref1);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref2);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_lock_failure(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err, i;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {\n+\t\terr = reftable_stack_add(st, &write_error, &i);\n+\t\tEXPECT(err == i);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_add(void)\n+{\n+\tint i = 0;\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t.exact_log_message = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tst->disable_auto_compact = 1;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tset_test_hash(refs[i].value.val1, i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = N + i + 1;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_ref_record dest = { NULL };\n+\n+\t\tint err = reftable_stack_read_ref(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_ref_record_equal(&dest, refs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_ref_record_release(&dest);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_log_record dest = { NULL };\n+\t\tint err = reftable_stack_read_log(st, refs[i].refname, &dest);\n+\t\tEXPECT_ERR(err);\n+\t\tEXPECT(reftable_log_record_equal(&dest, logs + i,\n+\t\t\t\t\t\t GIT_SHA1_RAWSZ));\n+\t\treftable_log_record_release(&dest);\n+\t}\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_log_normalize(void)\n+{\n+\tint err = 0;\n+\tstruct reftable_write_options cfg = {\n+\t\t0,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tuint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };\n+\n+\tstruct reftable_log_record input = { .refname = \"branch\",\n+\t\t\t\t\t     .update_index = 1,\n+\t\t\t\t\t     .value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t\t\t     .value = { .update = {\n+\t\t\t\t\t\t\t\t.new_hash = h1,\n+\t\t\t\t\t\t\t\t.old_hash = h2,\n+\t\t\t\t\t\t\t} } };\n+\tstruct reftable_log_record dest = {\n+\t\t.update_index = 0,\n+\t};\n+\tstruct write_log_arg arg = {\n+\t\t.log = &input,\n+\t\t.update_index = 1,\n+\t};\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tinput.value.update.message = \"one\\ntwo\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT(err == REFTABLE_API_ERROR);\n+\n+\tinput.value.update.message = \"one\";\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n+\n+\tinput.value.update.message = \"two\\n\";\n+\targ.update_index = 2;\n+\terr = reftable_stack_add(st, &write_test_log, &arg);\n+\tEXPECT_ERR(err);\n+\terr = reftable_stack_read_log(st, input.refname, &dest);\n+\tEXPECT_ERR(err);\n+\tEXPECT(0 == strcmp(dest.value.update.message, \"two\\n\"));\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\treftable_log_record_release(&dest);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_tombstone(void)\n+{\n+\tint i = 0;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tstruct reftable_ref_record refs[2] = { { NULL } };\n+\tstruct reftable_log_record logs[2] = { { NULL } };\n+\tint N = ARRAY_SIZE(refs);\n+\tstruct reftable_ref_record dest = { NULL };\n+\tstruct reftable_log_record log_dest = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\t/* even entries add the refs, odd entries delete them. */\n+\tfor (i = 0; i < N; i++) {\n+\t\tconst char *buf = \"branch\";\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\tif (i % 2 == 0) {\n+\t\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\t\trefs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(refs[i].value.val1, i);\n+\t\t}\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\t/* update_index is part of the key. */\n+\t\tlogs[i].update_index = 42;\n+\t\tif (i % 2 == 0) {\n+\t\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\t\tlogs[i].value.update.new_hash =\n+\t\t\t\treftable_malloc(GIT_SHA1_RAWSZ);\n+\t\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t\t\tlogs[i].value.update.email =\n+\t\t\t\txstrdup(\"identity@invalid\");\n+\t\t}\n+\t}\n+\tfor (i = 0; i < N; i++) {\n+\t\tint err = reftable_stack_add(st, &write_test_ref, &refs[i]);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_log_record_release(&log_dest);\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n+\tEXPECT(err == 1);\n+\treftable_ref_record_release(&dest);\n+\treftable_log_record_release(&log_dest);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_hash_id(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = \"master\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = \"target\",\n+\t\t.update_index = 1,\n+\t};\n+\tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n+\tstruct reftable_stack *st32 = NULL;\n+\tstruct reftable_write_options cfg_default = { 0 };\n+\tstruct reftable_stack *st_default = NULL;\n+\tstruct reftable_ref_record dest = { NULL };\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\tEXPECT_ERR(err);\n+\n+\t/* can't read it with the wrong hash ID. */\n+\terr = reftable_new_stack(&st32, dir, cfg32);\n+\tEXPECT(err == REFTABLE_FORMAT_ERROR);\n+\n+\t/* check that we can read it back with default config too. */\n+\terr = reftable_new_stack(&st_default, dir, cfg_default);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_ref(st_default, \"master\", &dest);\n+\tEXPECT_ERR(err);\n+\n+\tEXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st_default);\n+\tclear_dir(dir);\n+}\n+\n+static void test_log2(void)\n+{\n+\tEXPECT(1 == fastlog2(3));\n+\tEXPECT(2 == fastlog2(4));\n+\tEXPECT(2 == fastlog2(5));\n+}\n+\n+static void test_sizes_to_segments(void)\n+{\n+\tuint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };\n+\t/* .................0  1  2  3  4  5 */\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(segs[2].log == 3);\n+\tEXPECT(segs[2].start == 5);\n+\tEXPECT(segs[2].end == 6);\n+\n+\tEXPECT(segs[1].log == 2);\n+\tEXPECT(segs[1].start == 2);\n+\tEXPECT(segs[1].end == 5);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_empty(void)\n+{\n+\tint seglen = 0;\n+\tstruct segment *segs = sizes_to_segments(&seglen, NULL, 0);\n+\tEXPECT(seglen == 0);\n+\treftable_free(segs);\n+}\n+\n+static void test_sizes_to_segments_all_equal(void)\n+{\n+\tuint64_t sizes[] = { 5, 5 };\n+\n+\tint seglen = 0;\n+\tstruct segment *segs =\n+\t\tsizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(seglen == 1);\n+\tEXPECT(segs[0].start == 0);\n+\tEXPECT(segs[0].end == 2);\n+\treftable_free(segs);\n+}\n+\n+static void test_suggest_compaction_segment(void)\n+{\n+\tuint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };\n+\t/* .................0    1    2  3   4  5  6 */\n+\tstruct segment min =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(min.start == 2);\n+\tEXPECT(min.end == 7);\n+}\n+\n+static void test_suggest_compaction_segment_nothing(void)\n+{\n+\tuint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };\n+\tstruct segment result =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes));\n+\tEXPECT(result.start == result.end);\n+}\n+\n+static void test_reflog_expire(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_log_record logs[20] = { { NULL } };\n+\tint N = ARRAY_SIZE(logs) - 1;\n+\tint i = 0;\n+\tint err;\n+\tstruct reftable_log_expiry_config expiry = {\n+\t\t.time = 10,\n+\t};\n+\tstruct reftable_log_record log = { NULL };\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02d\", i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = i;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.time = i;\n+\t\tlogs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tset_test_hash(logs[i].value.update.new_hash, i);\n+\t}\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tint err = reftable_stack_add(st, &write_test_log, &arg);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_stack_compact_all(st, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[9].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[11].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\texpiry.min_update_index = 15;\n+\terr = reftable_stack_compact_all(st, &expiry);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_read_log(st, logs[14].refname, &log);\n+\tEXPECT(err == 1);\n+\n+\terr = reftable_stack_read_log(st, logs[16].refname, &log);\n+\tEXPECT_ERR(err);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i <= N; i++) {\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+\treftable_log_record_release(&log);\n+}\n+\n+static int write_nothing(struct reftable_writer *wr, void *arg)\n+{\n+\treftable_writer_set_limits(wr, 1, 1);\n+\treturn 0;\n+}\n+\n+static void test_empty_add(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint err;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_stack *st2 = NULL;\n+\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_add(st, &write_nothing, NULL);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\tclear_dir(dir);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st2);\n+}\n+\n+static void test_reftable_stack_auto_compaction(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 100;\n+\n+\terr = reftable_new_stack(&st, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tst->disable_auto_compact = 1; /* call manually below for coverage. */\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\n+\t\terr = reftable_stack_auto_compact(st);\n+\t\tEXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));\n+\t}\n+\n+\tEXPECT(reftable_stack_compaction_stats(st)->entries_written <\n+\t       (uint64_t)(N * fastlog2(N)));\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\tclear_dir(dir);\n+}\n+\n+static void unclean_stack_close(struct reftable_stack *st)\n+{\n+\t/* break abstraction boundary to simulate unclean shutdown. */\n+\tint i = 0;\n+\tfor (; i < st->readers_len; i++) {\n+\t\treftable_reader_free(st->readers[i]);\n+\t}\n+\tst->readers_len = 0;\n+\tFREE_AND_NULL(st->readers);\n+}\n+\n+static void test_reftable_stack_compaction_concurrent_clean(void)\n+{\n+\tstruct reftable_write_options cfg = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tint err, i;\n+\tint N = 3;\n+\n+\terr = reftable_new_stack(&st1, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st1),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n+\n+\t\terr = reftable_stack_add(st1, &write_test_ref, &ref);\n+\t\tEXPECT_ERR(err);\n+\t}\n+\n+\terr = reftable_new_stack(&st2, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_compact_all(st1, NULL);\n+\tEXPECT_ERR(err);\n+\n+\tunclean_stack_close(st1);\n+\tunclean_stack_close(st2);\n+\n+\terr = reftable_new_stack(&st3, dir, cfg);\n+\tEXPECT_ERR(err);\n+\n+\terr = reftable_stack_clean(st3);\n+\tEXPECT_ERR(err);\n+\tEXPECT(count_dir_entries(dir) == 2);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\treftable_stack_destroy(st3);\n+\n+\tclear_dir(dir);\n+}\n+\n+int stack_test_main(int argc, const char *argv[])\n+{\n+\tRUN_TEST(test_empty_add);\n+\tRUN_TEST(test_log2);\n+\tRUN_TEST(test_names_equal);\n+\tRUN_TEST(test_parse_names);\n+\tRUN_TEST(test_read_file);\n+\tRUN_TEST(test_reflog_expire);\n+\tRUN_TEST(test_reftable_stack_add);\n+\tRUN_TEST(test_reftable_stack_add_one);\n+\tRUN_TEST(test_reftable_stack_auto_compaction);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent);\n+\tRUN_TEST(test_reftable_stack_compaction_concurrent_clean);\n+\tRUN_TEST(test_reftable_stack_hash_id);\n+\tRUN_TEST(test_reftable_stack_lock_failure);\n+\tRUN_TEST(test_reftable_stack_log_normalize);\n+\tRUN_TEST(test_reftable_stack_tombstone);\n+\tRUN_TEST(test_reftable_stack_transaction_api);\n+\tRUN_TEST(test_reftable_stack_update_index_check);\n+\tRUN_TEST(test_reftable_stack_uptodate);\n+\tRUN_TEST(test_reftable_stack_validate_refname);\n+\tRUN_TEST(test_sizes_to_segments);\n+\tRUN_TEST(test_sizes_to_segments_all_equal);\n+\tRUN_TEST(test_sizes_to_segments_empty);\n+\tRUN_TEST(test_suggest_compaction_segment);\n+\tRUN_TEST(test_suggest_compaction_segment_nothing);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex c8db6852c35..996da85f7b5 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -10,6 +10,7 @@ int cmd__reftable(int argc, const char **argv)\n \trecord_test_main(argc, argv);\n \trefname_test_main(argc, argv);\n \treadwrite_test_main(argc, argv);\n+\tstack_test_main(argc, argv);\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"438218","messageId":"c8428cb0df857fbc47c6cbc2813d4701eda36321.1633638316.git.gitgitgadget@gmail.com","threadId":"56405","inReplyTo":"pull.1081.v4.git.git.1633638315.gitgitgadget@gmail.com","subject":"[PATCH v4 19/19] Add \"test-tool dump-reftable\" command.","fromName":"Han-Wen Nienhuys via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-10-07T20:25:15Z","receivedAt":"2021-10-07T20:26:08Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"From: Han-Wen Nienhuys <hanwen@google.com>\n\nThis command dumps individual tables or a stack of of tables.\n\nSigned-off-by: Han-Wen Nienhuys <hanwen@google.com>\n---\n Makefile                 | 1 +\n t/helper/test-reftable.c | 5 +++++\n t/helper/test-tool.c     | 1 +\n t/helper/test-tool.h     | 1 +\n 4 files changed, 8 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 29344440715..0d189a72410 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2454,6 +2454,7 @@ REFTABLE_OBJS += reftable/writer.o\n \n REFTABLE_TEST_OBJS += reftable/basics_test.o\n REFTABLE_TEST_OBJS += reftable/block_test.o\n+REFTABLE_TEST_OBJS += reftable/dump.o\n REFTABLE_TEST_OBJS += reftable/merged_test.o\n REFTABLE_TEST_OBJS += reftable/pq_test.o\n REFTABLE_TEST_OBJS += reftable/record_test.o\ndiff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c\nindex 996da85f7b5..26b03d7b789 100644\n--- a/t/helper/test-reftable.c\n+++ b/t/helper/test-reftable.c\n@@ -14,3 +14,8 @@ int cmd__reftable(int argc, const char **argv)\n \ttree_test_main(argc, argv);\n \treturn 0;\n }\n+\n+int cmd__dump_reftable(int argc, const char **argv)\n+{\n+\treturn reftable_dump_main(argc, (char *const *)argv);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f7c888ffda7..338a57b104d 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -61,6 +61,7 @@ static struct test_cmd cmds[] = {\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\n \t{ \"reftable\", cmd__reftable },\n+\t{ \"dump-reftable\", cmd__dump_reftable },\n \t{ \"regex\", cmd__regex },\n \t{ \"repository\", cmd__repository },\n \t{ \"revision-walking\", cmd__revision_walking },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 25f77469146..48cee1f4a2d 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -19,6 +19,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);\n int cmd__dump_fsmonitor(int argc, const char **argv);\n int cmd__dump_split_index(int argc, const char **argv);\n int cmd__dump_untracked_cache(int argc, const char **argv);\n+int cmd__dump_reftable(int argc, const char **argv);\n int cmd__example_decorate(int argc, const char **argv);\n int cmd__fast_rebase(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n-- \ngitgitgadget\n"},{"id":"446118","messageId":"patch-1.1-7425b64c0a0-20220113T113821Z-avarab@gmail.com","threadId":"56405","inReplyTo":"0b86f6c2327b88886ad8667d28e0fa8382791499.1633638316.git.gitgitgadget@gmail.com","subject":"[PATCH] reftable tests: use C syntax compatible with old xlc","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-13T11:38:35Z","receivedAt":"2022-01-13T11:38:40Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change code added in 1ae2b8cda84 (reftable: add merged table view,\n2021-10-07) to be compatible with older versions of AIX's IBM xlc\ncompiler. Version V12.1 of it (on gcc111.fsffrance.org) will hard\nerror with:\n\n    \"reftable/merged_test.c\", line 211.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_ref_record\" is not allowed.\n    \"reftable/merged_test.c\", line 212.19: 1506-196 (S) Initialization between types \"unsigned long long\" and \"struct reftable_ref_record\" is not allowed.\n    \"reftable/merged_test.c\", line 213.19: 1506-196 (S) Initialization between types \"enum {...}\" and \"struct reftable_ref_record\" is not allowed.\n    \"reftable/merged_test.c\", line 214.19: 1506-196 (S) Initialization between types \"unsigned char*\" and \"struct reftable_ref_record\" is not allowed.\n    \"reftable/merged_test.c\", line 349.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_log_record\" is not allowed.\n    \"reftable/merged_test.c\", line 350.19: 1506-196 (S) Initialization between types \"unsigned long long\" and \"struct reftable_log_record\" is not allowed.\n    \"reftable/merged_test.c\", line 351.19: 1506-196 (S) Initialization between types \"enum {...}\" and \"struct reftable_log_record\" is not allowed.\n\nIts newer V13.1.3 sibling (on gcc119.fsffrance.org, a AIX 7.2 box)\nwill compile the pre-image without issues. Let's not make git's\nsources incompatible with this older AIX 7.1 compiler.\n\nPerhaps there's a better way to do this, but just duplicating the\nearlier struct values declared earlier in these functions works, and\nis probably the least bad solution.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n reftable/merged_test.c | 74 +++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 66 insertions(+), 8 deletions(-)\n\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 24461e8a802..bf231990a84 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -206,12 +206,38 @@ static void test_merged(void)\n \t\t\t.value.val1 = hash1,\n \t\t},\n \t};\n-\n+\t/*\n+\t * We don't use { r2[0], r3[0], ... } for compatibility with\n+\t * older IBM xlc.\n+\t */\n \tstruct reftable_ref_record want[] = {\n-\t\tr2[0],\n-\t\tr1[1],\n-\t\tr3[0],\n-\t\tr3[1],\n+\t\t/* Same as r2[0] */\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_REF_DELETION,\n+\t\t},\n+\t\t/* Same as r1[1] */\n+\t\t{\n+\n+\t\t\t.refname = \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n+\t\t/* Same as r3[0..1] */\n+\t\t{\n+\t\t\t.refname = \"c\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash2,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = \"d\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = hash1,\n+\t\t},\n \t};\n \n \tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n@@ -345,10 +371,42 @@ static void test_merged_logs(void)\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n \t};\n+\t/*\n+\t * We don't use { r2[0], r3[0], ... } for compatibility with\n+\t * older IBM xlc.\n+\t */\n \tstruct reftable_log_record want[] = {\n-\t\tr2[0],\n-\t\tr3[0],\n-\t\tr1[1],\n+\t\t/* Same as r2[0] */\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.new_hash = hash3,\n+\t\t\t\t.name = \"jane doe\",\n+\t\t\t\t.email = \"jane@invalid\",\n+\t\t\t\t.message = \"message3\",\n+\t\t\t}\n+\t\t},\n+\t\t/* Same as r3[0] */\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t},\n+\t\t/* Same as r1[1] */\n+\t\t{\n+\t\t\t.refname = \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.old_hash = hash1,\n+\t\t\t\t.new_hash = hash2,\n+\t\t\t\t.name = \"jane doe\",\n+\t\t\t\t.email = \"jane@invalid\",\n+\t\t\t\t.message = \"message1\",\n+\t\t\t}\n+\t\t},\n \t};\n \n \tstruct reftable_log_record *logs[] = { r1, r2, r3 };\n-- \n2.35.0.rc0.850.gcc6bf5af6b1\n\n"},{"id":"446153","messageId":"CAFQ2z_M2ZH-8RNS_zxShkdaXdO4x4Vr8EwrFo6atd0qzyy36oA@mail.gmail.com","threadId":"56405","inReplyTo":"patch-1.1-7425b64c0a0-20220113T113821Z-avarab@gmail.com","subject":"Re: [PATCH] reftable tests: use C syntax compatible with old xlc","fromName":"Han-Wen Nienhuys","fromEmail":"hanwen@google.com","sentAt":"2022-01-13T14:23:29Z","receivedAt":"2022-01-13T14:23:44Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"On Thu, Jan 13, 2022 at 12:38 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> Change code added in 1ae2b8cda84 (reftable: add merged table view,\n> 2021-10-07) to be compatible with older versions of AIX's IBM xlc\n> compiler. Version V12.1 of it (on gcc111.fsffrance.org) will hard\n> error with:\n>\n>     \"reftable/merged_test.c\", line 211.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_ref_record\" is not allowed.\n>     \"reftable/merged_test.c\", line 212.19: 1506-196 (S) Initialization between types \"unsigned long long\" and \"struct reftable_ref_record\" is not allowed.\n>     \"reftable/merged_test.c\", line 213.19: 1506-196 (S) Initialization between types \"enum {...}\" and \"struct reftable_ref_record\" is not allowed.\n>     \"reftable/merged_test.c\", line 214.19: 1506-196 (S) Initialization between types \"unsigned char*\" and \"struct reftable_ref_record\" is not allowed.\n>     \"reftable/merged_test.c\", line 349.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_log_record\" is not allowed.\n>     \"reftable/merged_test.c\", line 350.19: 1506-196 (S) Initialization between types \"unsigned long long\" and \"struct reftable_log_record\" is not allowed.\n>     \"reftable/merged_test.c\", line 351.19: 1506-196 (S) Initialization between types \"enum {...}\" and \"struct reftable_log_record\" is not allowed.\n\nWeird. What C standard does xlc implement?\n\n> Perhaps there's a better way to do this, but just duplicating the\n> earlier struct values declared earlier in these functions works, and\n> is probably the least bad solution.\n\nI'd rather not duplicate anything. Can't you do\n\n  struct foobar *want = { &r[0], &r[2] .. }\n\n-- \nHan-Wen Nienhuys - Google Munich\nI work 80%. Don't expect answers from me on Fridays.\n--\n\nGoogle Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich\n\nRegistergericht und -nummer: Hamburg, HRB 86891\n\nSitz der Gesellschaft: Hamburg\n\nGeschäftsführer: Paul Manicle, Halimah DeLaine Prado\n"},{"id":"446158","messageId":"220113.86v8yntxfb.gmgdl@evledraar.gmail.com","threadId":"56405","inReplyTo":"CAFQ2z_M2ZH-8RNS_zxShkdaXdO4x4Vr8EwrFo6atd0qzyy36oA@mail.gmail.com","subject":"Re: [PATCH] reftable tests: use C syntax compatible with old xlc","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-13T16:22:47Z","receivedAt":"2022-01-13T16:25:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 13 2022, Han-Wen Nienhuys wrote:\n\n> On Thu, Jan 13, 2022 at 12:38 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>> Change code added in 1ae2b8cda84 (reftable: add merged table view,\n>> 2021-10-07) to be compatible with older versions of AIX's IBM xlc\n>> compiler. Version V12.1 of it (on gcc111.fsffrance.org) will hard\n>> error with:\n>>\n>>     \"reftable/merged_test.c\", line 211.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_ref_record\" is not allowed.\n>>     \"reftable/merged_test.c\", line 212.19: 1506-196 (S) Initialization between types \"unsigned long long\" and \"struct reftable_ref_record\" is not allowed.\n>>     \"reftable/merged_test.c\", line 213.19: 1506-196 (S) Initialization between types \"enum {...}\" and \"struct reftable_ref_record\" is not allowed.\n>>     \"reftable/merged_test.c\", line 214.19: 1506-196 (S) Initialization between types \"unsigned char*\" and \"struct reftable_ref_record\" is not allowed.\n>>     \"reftable/merged_test.c\", line 349.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_log_record\" is not allowed.\n>>     \"reftable/merged_test.c\", line 350.19: 1506-196 (S) Initialization between types \"unsigned long long\" and \"struct reftable_log_record\" is not allowed.\n>>     \"reftable/merged_test.c\", line 351.19: 1506-196 (S) Initialization between types \"enum {...}\" and \"struct reftable_log_record\" is not allowed.\n>\n> Weird. What C standard does xlc implement?\n\nI don't know. Your guess (and searching through IBM's website) is as\ngood as mine.\n\nAFAICT it mostly implements the C99 semantics, but doesn't grok the\ninterpolation of structs-within-structs\n\n>> Perhaps there's a better way to do this, but just duplicating the\n>> earlier struct values declared earlier in these functions works, and\n>> is probably the least bad solution.\n>\n> I'd rather not duplicate anything. Can't you do\n>\n>   struct foobar *want = { &r[0], &r[2] .. }\n\nMaybe I'm just not understanding what you mean, but this:\n\n        struct reftable_ref_record *want = {\n                &r2[0],\n                &r1[1],\n                &r3[0],\n                &r3[1],\n        };\n\nGives us the predictable compiler error on gcc/clang, nevermind\nxlc. Turn that into:\n\n        struct reftable_ref_record want[] = {\n                &r2[0],\n                &r1[1],\n                &r3[0],\n                &r3[1],\n        };\n\nAnd you can get gcc/clang to emulate what that xlc version (mis)parses\nthat as. I.e. it takes it as a reference to the nth element in that\nstruct.\n"},{"id":"446177","messageId":"xmqq4k67ihba.fsf@gitster.g","threadId":"56405","inReplyTo":"CAFQ2z_M2ZH-8RNS_zxShkdaXdO4x4Vr8EwrFo6atd0qzyy36oA@mail.gmail.com","subject":"Re: [PATCH] reftable tests: use C syntax compatible with old xlc","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-13T19:09:13Z","receivedAt":"2022-01-13T19:09:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Han-Wen Nienhuys <hanwen@google.com> writes:\n\n> On Thu, Jan 13, 2022 at 12:38 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>> Change code added in 1ae2b8cda84 (reftable: add merged table view,\n>> 2021-10-07) to be compatible with older versions of AIX's IBM xlc\n>> compiler. Version V12.1 of it (on gcc111.fsffrance.org) will hard\n>> error with:\n>>\n>>     \"reftable/merged_test.c\", line 211.19: 1506-196 (S) Initialization between types \"char*\" and \"struct reftable_ref_record\" is not allowed.\n> ...\n> Weird. What C standard does xlc implement?\n\nInteresting.  It is complaining about this thing:\n\n\tstruct reftable_ref_record r2[] = { {\n\t\t.refname = \"a\",\n\t\t.update_index = 2,\n\t\t.value_type = REFTABLE_REF_DELETION,\n\t} };\n\t...\n\tstruct reftable_ref_record want[] = {\n\t\tr2[0],\n\t\tr1[1],\n\t\tr3[0],\n\t\tr3[1],\n\t};\n\nand somehow assuming that r2[0] corresponds to the first member of a\n\"struct reftable_ref_record\" structure, which is \"char *refname\".\nOf course, you cannot initialize a character pointer with a whole\nstruct, which is *not* what the code wants to do.  I would\nunderstand if it were written incorrectly like so\n\nBAD:\tstruct reftable_ref_record want[] = {\nBAD:\t\t{ r2[0] },\nBAD:\t\t{ r1[1] },\nBAD:\t\t{ r3[0] },\nBAD:\t\t{ r3[1] },\nBAD:\t};\n\nbut that is not what we fed the compiler.  But apparently the\ncompiler is confused.\n\nI wonder if it helps to use designated initializer on the latter, i.e.\n\n\tstruct reftable_ref_record want[] = {\n\t\t[0] = r2[0],\n\t\t[1] = r1[1],\n\t\t[2] = r3[0],\n\t\t[3] = r3[1],\n\t};\n\n"}]}