git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / Atom feed
From: "Han-Wen Nienhuys via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Han-Wen Nienhuys <hanwenn@gmail.com>
Subject: [PATCH v2 0/5] Reftable support git-core
Date: Mon, 27 Jan 2020 14:22:19 +0000
Message-ID: <pull.539.v2.git.1580134944.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.539.git.1579808479.gitgitgadget@gmail.com>

This adds the reftable library, and hooks it up as a ref backend.

At this point, I am mainly interested in feedback on the spots marked with
XXX in the Git source code, in particular, how to handle reflog expiry in
this backend.

v2

 * address Jun's nits.
 * address Dscho's portability comments
 * more background in commit messages.

Han-Wen Nienhuys (5):
  setup.c: enable repo detection for reftable
  create .git/refs in files-backend.c
  Document how ref iterators and symrefs interact
  Add reftable library
  Reftable support for git-core

 Makefile                  |   23 +-
 builtin/init-db.c         |    2 -
 refs.c                    |   18 +-
 refs.h                    |    2 +
 refs/files-backend.c      |    4 +
 refs/refs-internal.h      |    4 +
 refs/reftable-backend.c   |  812 +++++++++++++++++++++++++++
 reftable/LICENSE          |   31 ++
 reftable/README.md        |   17 +
 reftable/VERSION          |    5 +
 reftable/basics.c         |  196 +++++++
 reftable/basics.h         |   38 ++
 reftable/block.c          |  401 ++++++++++++++
 reftable/block.h          |   71 +++
 reftable/block_test.c     |  151 +++++
 reftable/blocksource.h    |   20 +
 reftable/bytes.c          |    0
 reftable/config.h         |    1 +
 reftable/constants.h      |   27 +
 reftable/dump.c           |   97 ++++
 reftable/file.c           |   97 ++++
 reftable/iter.c           |  230 ++++++++
 reftable/iter.h           |   56 ++
 reftable/merged.c         |  288 ++++++++++
 reftable/merged.h         |   34 ++
 reftable/merged_test.c    |  258 +++++++++
 reftable/pq.c             |  124 +++++
 reftable/pq.h             |   34 ++
 reftable/reader.c         |  710 ++++++++++++++++++++++++
 reftable/reader.h         |   52 ++
 reftable/record.c         | 1110 +++++++++++++++++++++++++++++++++++++
 reftable/record.h         |   79 +++
 reftable/record_test.c    |  332 +++++++++++
 reftable/reftable.h       |  399 +++++++++++++
 reftable/reftable_test.c  |  481 ++++++++++++++++
 reftable/slice.c          |  199 +++++++
 reftable/slice.h          |   39 ++
 reftable/slice_test.c     |   38 ++
 reftable/stack.c          |  985 ++++++++++++++++++++++++++++++++
 reftable/stack.h          |   40 ++
 reftable/stack_test.c     |  281 ++++++++++
 reftable/system.h         |   36 ++
 reftable/test_framework.c |   67 +++
 reftable/test_framework.h |   64 +++
 reftable/tree.c           |   66 +++
 reftable/tree.h           |   24 +
 reftable/tree_test.c      |   61 ++
 reftable/writer.c         |  624 +++++++++++++++++++++
 reftable/writer.h         |   46 ++
 setup.c                   |   20 +-
 50 files changed, 8782 insertions(+), 12 deletions(-)
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/slice_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h


base-commit: bc7a3d4dc04dd719e7c8c35ebd7a6e6651c5c5b6
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-539%2Fhanwen%2Freftable-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-539/hanwen/reftable-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/539

Range-diff vs v1:

 1:  fd2baf1628 = 1:  174b98f6db setup.c: enable repo detection for reftable
 2:  bc643d0b0c = 2:  d7d642dcf6 create .git/refs in files-backend.c
 3:  1a01e0b1b5 = 3:  9cf185b51f Document how ref iterators and symrefs interact
 4:  3c86bd1d7e ! 4:  2106ff286b Add reftable library
     @@ -2,23 +2,92 @@
      
          Add reftable library
      
     +    Reftable is a new format for storing the ref database. It provides the
     +    following benefits:
     +
     +     * Simple and fast atomic ref transactions, including multiple refs and reflogs.
     +     * Compact storage of ref data.
     +     * Fast look ups of ref data.
     +     * Case-sensitive ref names on Windows/OSX, regardless of file system
     +     * Eliminates file/directory conflicts in ref names
     +
     +    Further context and motivation can be found in background reading:
     +
     +    * Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md
     +
     +    * Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html
     +
     +    * First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/
     +
     +    * Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/
     +
     +    * First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/
     +
     +    * libgit2 support issue: https://github.com/libgit2/libgit2/issues
     +
     +    * GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6
     +
     +    * go-git support issue: https://github.com/src-d/go-git/issues/1059
     +
          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
          Change-Id: Id396ff42be8b42b9e11f194a32e2f95b8250c109
      
     + diff --git a/reftable/LICENSE b/reftable/LICENSE
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/LICENSE
     +@@
     ++BSD License
     ++
     ++Copyright (c) 2020, Google LLC
     ++All rights reserved.
     ++
     ++Redistribution and use in source and binary forms, with or without
     ++modification, are permitted provided that the following conditions are
     ++met:
     ++
     ++* Redistributions of source code must retain the above copyright notice,
     ++this list of conditions and the following disclaimer.
     ++
     ++* Redistributions in binary form must reproduce the above copyright
     ++notice, this list of conditions and the following disclaimer in the
     ++documentation and/or other materials provided with the distribution.
     ++
     ++* Neither the name of Google LLC nor the names of its contributors may
     ++be used to endorse or promote products derived from this software
     ++without specific prior written permission.
     ++
     ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     ++"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     ++LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     ++A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     ++OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     ++SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     ++LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     ++DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     ++THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     ++(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     ++OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     +
       diff --git a/reftable/README.md b/reftable/README.md
       new file mode 100644
       --- /dev/null
       +++ b/reftable/README.md
      @@
     -+The source code in this directory comes from https://github.com/google/reftable
     -+and can be updated by doing:
      +
     -+   rm -rf reftable-repo && \
     -+   git clone https://github.com/google/reftable reftable-repo && \
     ++The source code in this directory comes from https://github.com/google/reftable.
     ++
     ++The VERSION file keeps track of the current version of the reftable library.
     ++
     ++To update the library, do:
     ++
     ++   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
     ++    git clone https://github.com/google/reftable reftable-repo) && \
      +   cp reftable-repo/c/*.[ch] reftable/ && \
     -+   cp reftable-repo/LICENSE reftable/
     ++   cp reftable-repo/LICENSE reftable/ &&
      +   git --git-dir reftable-repo/.git show --no-patch origin/master \
     -+      > reftable/VERSION
     ++    > reftable/VERSION && \
     ++   echo '/* empty */' > reftable/config.h
      +
      +Bugfixes should be accompanied by a test and applied to upstream project at
      +https://github.com/google/reftable.
     @@ -28,299 +97,11 @@
       --- /dev/null
       +++ b/reftable/VERSION
      @@
     -+commit 887250932a9c39a15aee26aef59df2300c77c03f
     ++commit c616d53b88657c3a5fe4d2e7243a48effc34c626
      +Author: Han-Wen Nienhuys <hanwen@google.com>
     -+Date:   Wed Jan 22 19:50:16 2020 +0100
     -+
     -+    C: implement reflog expiry
     -+
     -+diff --git a/c/reftable.h b/c/reftable.h
     -+index 2f44973..d760af9 100644
     -+--- a/c/reftable.h
     -++++ b/c/reftable.h
     -+@@ -359,8 +359,17 @@ void stack_destroy(struct stack *st);
     -+ /* reloads the stack if necessary. */
     -+ int stack_reload(struct stack *st);
     -+ 
     -+-/* compacts all reftables into a giant table. */
     -+-int stack_compact_all(struct stack *st);
     -++/* Policy for expiring reflog entries. */
     -++struct log_expiry_config {
     -++  /* Drop entries older than this timestamp */
     -++  uint64_t time;
     -++
     -++  /* Drop older entries */
     -++  uint64_t min_update_index;
     -++};
     -++
     -++/* compacts all reftables into a giant table. Expire reflog entries if config is non-NULL */
     -++int stack_compact_all(struct stack *st, struct log_expiry_config *config);
     -+ 
     -+ /* heuristically compact unbalanced table stack. */
     -+ int stack_auto_compact(struct stack *st);
     -+diff --git a/c/stack.c b/c/stack.c
     -+index d67828f..641ac52 100644
     -+--- a/c/stack.c
     -++++ b/c/stack.c
     -+@@ -119,7 +119,7 @@ static struct reader **stack_copy_readers(struct stack *st, int cur_len) {
     -+   return cur;
     -+ }
     -+ 
     -+-static int stack_reload_once(struct stack *st, char **names) {
     -++static int stack_reload_once(struct stack *st, char **names, bool reuse_open) {
     -+   int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     -+   struct reader **cur = stack_copy_readers(st, cur_len);
     -+   int err = 0;
     -+@@ -136,7 +136,7 @@ static int stack_reload_once(struct stack *st, char **names) {
     -+ 
     -+     // this is linear; we assume compaction keeps the number of tables
     -+     // under control so this is not quadratic.
     -+-    for (int j = 0; j < cur_len; j++) {
     -++    for (int j = 0; reuse_open && j < cur_len; j++) {
     -+       if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
     -+         rd = cur[j];
     -+         cur[j] = NULL;
     -+@@ -207,7 +207,7 @@ static int tv_cmp(struct timeval *a, struct timeval *b) {
     -+   return udiff;
     -+ }
     -+ 
     -+-int stack_reload(struct stack *st) {
     -++static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open) {
     -+   struct timeval deadline = {};
     -+   int err = gettimeofday(&deadline, NULL);
     -+   int64_t delay = 0;
     -+@@ -238,7 +238,7 @@ int stack_reload(struct stack *st) {
     -+       free_names(names);
     -+       return err;
     -+     }
     -+-    err = stack_reload_once(st, names);
     -++    err = stack_reload_once(st, names, reuse_open);
     -+     if (err == 0) {
     -+       free_names(names);
     -+       break;
     -+@@ -269,6 +269,10 @@ int stack_reload(struct stack *st) {
     -+   return 0;
     -+ }
     -+ 
     -++int stack_reload(struct stack *st) {
     -++  return stack_reload_maybe_reuse(st, true);
     -++}
     -++
     -+ // -1 = error
     -+ // 0 = up to date
     -+ // 1 = changed.
     -+@@ -471,7 +475,7 @@ uint64_t stack_next_update_index(struct stack *st) {
     -+ }
     -+ 
     -+ static int stack_compact_locked(struct stack *st, int first, int last,
     -+-                                struct slice *temp_tab) {
     -++                                struct slice *temp_tab, struct log_expiry_config *config) {
     -+   struct slice next_name = {};
     -+   int tab_fd = -1;
     -+   struct writer *wr = NULL;
     -+@@ -488,7 +492,7 @@ static int stack_compact_locked(struct stack *st, int first, int last,
     -+   tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     -+   wr = new_writer(fd_writer, &tab_fd, &st->config);
     -+ 
     -+-  err = stack_write_compact(st, wr, first, last);
     -++  err = stack_write_compact(st, wr, first, last, config);
     -+   if (err < 0) {
     -+     goto exit;
     -+   }
     -+@@ -515,7 +519,7 @@ static int stack_compact_locked(struct stack *st, int first, int last,
     -+ }
     -+ 
     -+ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+-                        int last) {
     -++                        int last, struct log_expiry_config *config) {
     -+   int subtabs_len = last - first + 1;
     -+   struct reader **subtabs = calloc(sizeof(struct reader *), last - first + 1);
     -+   struct merged_table *mt = NULL;
     -+@@ -580,6 +584,16 @@ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+       continue;
     -+     }
     -+ 
     -++    // XXX collect stats?
     -++
     -++    if (config != NULL && config->time > 0 && log.time < config->time) {
     -++      continue;
     -++    }
     -++
     -++    if (config != NULL && config->min_update_index > 0 && log.update_index < config->min_update_index) {
     -++      continue;
     -++    }
     -++
     -+     err = writer_add_log(wr, &log);
     -+     if (err < 0) {
     -+       break;
     -+@@ -599,7 +613,7 @@ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+ }
     -+ 
     -+ // <  0: error. 0 == OK, > 0 attempt failed; could retry.
     -+-static int stack_compact_range(struct stack *st, int first, int last) {
     -++static int stack_compact_range(struct stack *st, int first, int last, struct log_expiry_config *expiry) {
     -+   struct slice temp_tab_name = {};
     -+   struct slice new_table_name = {};
     -+   struct slice lock_file_name = {};
     -+@@ -612,7 +626,7 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
     -+   char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
     -+ 
     -+-  if (first >= last) {
     -++  if (first > last || (expiry == NULL && first == last)) {
     -+     err = 0;
     -+     goto exit;
     -+   }
     -+@@ -676,7 +690,7 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   }
     -+   have_lock = false;
     -+ 
     -+-  err = stack_compact_locked(st, first, last, &temp_tab_name);
     -++  err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     -+   if (err < 0) {
     -+     goto exit;
     -+   }
     -+@@ -739,10 +753,12 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   have_lock = false;
     -+ 
     -+   for (char **p = delete_on_success; *p; p++) {
     -+-    unlink(*p);
     -++    if (0 != strcmp(*p, slice_as_string(&new_table_path))) {
     -++      unlink(*p);
     -++    }
     -+   }
     -+ 
     -+-  err = stack_reload(st);
     -++  err = stack_reload_maybe_reuse(st, first < last);
     -+ exit:
     -+   for (char **p = subtable_locks; *p; p++) {
     -+     unlink(*p);
     -+@@ -764,12 +780,12 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   return err;
     -+ }
     -+ 
     -+-int stack_compact_all(struct stack *st) {
     -+-  return stack_compact_range(st, 0, st->merged->stack_len - 1);
     -++int stack_compact_all(struct stack *st, struct log_expiry_config *config) {
     -++  return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     -+ }
     -+ 
     -+-static int stack_compact_range_stats(struct stack *st, int first, int last) {
     -+-  int err = stack_compact_range(st, first, last);
     -++static int stack_compact_range_stats(struct stack *st, int first, int last, struct log_expiry_config *config) {
     -++  int err = stack_compact_range(st, first, last, config);
     -+   if (err > 0) {
     -+     st->stats.failures++;
     -+   }
     -+@@ -856,7 +872,7 @@ int stack_auto_compact(struct stack *st) {
     -+   struct segment seg = suggest_compaction_segment(sizes, st->merged->stack_len);
     -+   free(sizes);
     -+   if (segment_size(&seg) > 0) {
     -+-    return stack_compact_range_stats(st, seg.start, seg.end - 1);
     -++    return stack_compact_range_stats(st, seg.start, seg.end - 1, NULL);
     -+   }
     -+ 
     -+   return 0;
     -+diff --git a/c/stack.h b/c/stack.h
     -+index 8cb2cb0..092a03b 100644
     -+--- a/c/stack.h
     -++++ b/c/stack.h
     -+@@ -25,7 +25,7 @@ int read_lines(const char *filename, char ***lines);
     -+ int stack_try_add(struct stack *st,
     -+                   int (*write_table)(struct writer *wr, void *arg), void *arg);
     -+ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+-                        int last);
     -++                        int last, struct log_expiry_config *config);
     -+ int fastlog2(uint64_t sz);
     -+ 
     -+ struct segment {
     -+diff --git a/c/stack_test.c b/c/stack_test.c
     -+index c49d8b8..8c0cde0 100644
     -+--- a/c/stack_test.c
     -++++ b/c/stack_test.c
     -+@@ -121,7 +121,7 @@ void test_stack_add(void) {
     -+     assert_err(err);
     -+   }
     -+ 
     -+-  err = stack_compact_all(st);
     -++  err = stack_compact_all(st, NULL);
     -+   assert_err(err);
     -+ 
     -+   for (int i = 0; i < N; i++) {
     -+@@ -186,7 +186,73 @@ void test_suggest_compaction_segment(void) {
     -+   }
     -+ }
     -+ 
     -++void test_reflog_expire(void) {
     -++  char dir[256] = "/tmp/stack.XXXXXX";
     -++  assert(mkdtemp(dir));
     -++  printf("%s\n", dir);
     -++  char fn[256] = "";
     -++  strcat(fn, dir);
     -++  strcat(fn, "/refs");
     -++
     -++  struct write_options cfg = {};
     -++  struct stack *st = NULL;
     -++  int err = new_stack(&st, dir, fn, cfg);
     -++  assert_err(err);
     -++
     -++  struct log_record logs[20] = {};
     -++  int N = ARRAYSIZE(logs) -1;
     -++  for (int i = 1; i <= N; i++) {
     -++    char buf[256];
     -++    sprintf(buf, "branch%02d", i);
     -++
     -++    logs[i].ref_name = strdup(buf);
     -++    logs[i].update_index = i;
     -++    logs[i].time = i;
     -++    logs[i].new_hash = malloc(SHA1_SIZE);
     -++    logs[i].email = strdup("identity@invalid");
     -++    set_test_hash(logs[i].new_hash, i);
     -++  }
     -++
     -++  for (int i = 1; i <= N; i++) {
     -++    int err = stack_add(st, &write_test_log, &logs[i]);
     -++    assert_err(err);
     -++  }
     -++
     -++  err = stack_compact_all(st, NULL);
     -++  assert_err(err);
     -++
     -++  struct log_expiry_config expiry = {
     -++    .time = 10,
     -++  };
     -++  err = stack_compact_all(st, &expiry);
     -++  assert_err(err);
     -++
     -++  struct log_record log = {};
     -++  err = stack_read_log(st, logs[9].ref_name, &log);
     -++  assert(err == 1);
     -++
     -++  err = stack_read_log(st, logs[11].ref_name, &log);
     -++  assert_err(err);
     -++
     -++  expiry.min_update_index = 15;
     -++  err = stack_compact_all(st, &expiry);
     -++  assert_err(err);
     -++
     -++  err = stack_read_log(st, logs[14].ref_name, &log);
     -++  assert(err == 1);
     -++
     -++  err = stack_read_log(st, logs[16].ref_name, &log);
     -++  assert_err(err);
     -++
     -++  // cleanup
     -++  stack_destroy(st);
     -++  for (int i = 0; i < N; i++) {
     -++    log_record_clear(&logs[i]);
     -++  }
     -++}
     -++
     -+ int main() {
     -++  add_test_case("test_reflog_expire", test_reflog_expire);
     -+   add_test_case("test_suggest_compaction_segment",
     -+                 &test_suggest_compaction_segment);
     -+   add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     ++Date:   Mon Jan 27 15:05:43 2020 +0100
     ++
     ++    C: ban // comments
      
       diff --git a/reftable/basics.c b/reftable/basics.c
       new file mode 100644
     @@ -337,175 +118,191 @@
      +
      +#include "basics.h"
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
     -+void put_u24(byte *out, uint32_t i) {
     -+  out[0] = (byte)((i >> 16) & 0xff);
     -+  out[1] = (byte)((i >> 8) & 0xff);
     -+  out[2] = (byte)((i)&0xff);
     ++void put_u24(byte *out, uint32_t i)
     ++{
     ++	out[0] = (byte)((i >> 16) & 0xff);
     ++	out[1] = (byte)((i >> 8) & 0xff);
     ++	out[2] = (byte)((i)&0xff);
      +}
      +
     -+uint32_t get_u24(byte *in) {
     -+  return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 | (uint32_t)(in[2]);
     ++uint32_t get_u24(byte *in)
     ++{
     ++	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
     ++	       (uint32_t)(in[2]);
      +}
      +
     -+void put_u32(byte *out, uint32_t i) {
     -+  out[0] = (byte)((i >> 24) & 0xff);
     -+  out[1] = (byte)((i >> 16) & 0xff);
     -+  out[2] = (byte)((i >> 8) & 0xff);
     -+  out[3] = (byte)((i)&0xff);
     ++void put_u32(byte *out, uint32_t i)
     ++{
     ++	out[0] = (byte)((i >> 24) & 0xff);
     ++	out[1] = (byte)((i >> 16) & 0xff);
     ++	out[2] = (byte)((i >> 8) & 0xff);
     ++	out[3] = (byte)((i)&0xff);
      +}
      +
     -+uint32_t get_u32(byte *in) {
     -+  return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     -+         (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
     ++uint32_t get_u32(byte *in)
     ++{
     ++	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     ++	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
      +}
      +
     -+void put_u64(byte *out, uint64_t v) {
     -+  for (int i = sizeof(uint64_t); i--;) {
     -+    out[i] = (byte)(v & 0xff);
     -+    v >>= 8;
     -+  }
     ++void put_u64(byte *out, uint64_t v)
     ++{
     ++	int i = 0;
     ++	for (i = sizeof(uint64_t); i--;) {
     ++		out[i] = (byte)(v & 0xff);
     ++		v >>= 8;
     ++	}
      +}
      +
     -+uint64_t get_u64(byte *out) {
     -+  uint64_t v = 0;
     -+  for (int i = 0; i < sizeof(uint64_t); i++) {
     -+    v = (v << 8) | (byte)(out[i] & 0xff);
     -+  }
     -+  return v;
     ++uint64_t get_u64(byte *out)
     ++{
     ++	uint64_t v = 0;
     ++	int i = 0;
     ++	for (i = 0; i < sizeof(uint64_t); i++) {
     ++		v = (v << 8) | (byte)(out[i] & 0xff);
     ++	}
     ++	return v;
      +}
      +
     -+void put_u16(byte *out, uint16_t i) {
     -+  out[0] = (byte)((i >> 8) & 0xff);
     -+  out[1] = (byte)((i)&0xff);
     ++void put_u16(byte *out, uint16_t i)
     ++{
     ++	out[0] = (byte)((i >> 8) & 0xff);
     ++	out[1] = (byte)((i)&0xff);
      +}
      +
     -+uint16_t get_u16(byte *in) {
     -+  return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
     ++uint16_t get_u16(byte *in)
     ++{
     ++	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
      +}
      +
      +/*
      +  find smallest index i in [0, sz) at which f(i) is true, assuming
      +  that f is ascending. Return sz if f(i) is false for all indices.
      +*/
     -+int binsearch(int sz, int (*f)(int k, void *args), void *args) {
     -+  int lo = 0;
     -+  int hi = sz;
     -+
     -+  /* invariant: (hi == sz) || f(hi) == true
     -+     (lo == 0 && f(0) == true) || fi(lo) == false
     -+   */
     -+  while (hi - lo > 1) {
     -+    int mid = lo + (hi - lo) / 2;
     -+
     -+    int val = f(mid, args);
     -+    if (val) {
     -+      hi = mid;
     -+    } else {
     -+      lo = mid;
     -+    }
     -+  }
     -+
     -+  if (lo == 0) {
     -+    if (f(0, args)) {
     -+      return 0;
     -+    } else {
     -+      return 1;
     -+    }
     -+  }
     -+
     -+  return hi;
     -+}
     -+
     -+void free_names(char **a) {
     -+  char **p = a;
     -+  if (p == NULL) {
     -+    return;
     -+  }
     -+  while (*p) {
     -+    free(*p);
     -+    p++;
     -+  }
     -+  free(a);
     -+}
     -+
     -+int names_length(char **names) {
     -+  int len = 0;
     -+  for (char **p = names; *p; p++) {
     -+    len++;
     -+  }
     -+  return len;
     ++int binsearch(int sz, int (*f)(int k, void *args), void *args)
     ++{
     ++	int lo = 0;
     ++	int hi = sz;
     ++
     ++	/* invariant: (hi == sz) || f(hi) == true
     ++	   (lo == 0 && f(0) == true) || fi(lo) == false
     ++	 */
     ++	while (hi - lo > 1) {
     ++		int mid = lo + (hi - lo) / 2;
     ++
     ++		int val = f(mid, args);
     ++		if (val) {
     ++			hi = mid;
     ++		} else {
     ++			lo = mid;
     ++		}
     ++	}
     ++
     ++	if (lo == 0) {
     ++		if (f(0, args)) {
     ++			return 0;
     ++		} else {
     ++			return 1;
     ++		}
     ++	}
     ++
     ++	return hi;
     ++}
     ++
     ++void free_names(char **a)
     ++{
     ++	char **p = a;
     ++	if (p == NULL) {
     ++		return;
     ++	}
     ++	while (*p) {
     ++		free(*p);
     ++		p++;
     ++	}
     ++	free(a);
     ++}
     ++
     ++int names_length(char **names)
     ++{
     ++	int len = 0;
     ++	for (char **p = names; *p; p++) {
     ++		len++;
     ++	}
     ++	return len;
      +}
      +
      +/* parse a newline separated list of names. Empty names are discarded. */
     -+void parse_names(char *buf, int size, char ***namesp) {
     -+  char **names = NULL;
     -+  int names_cap = 0;
     -+  int names_len = 0;
     -+
     -+  char *p = buf;
     -+  char *end = buf + size;
     -+  while (p < end) {
     -+    char *next = strchr(p, '\n');
     -+    if (next != NULL) {
     -+      *next = 0;
     -+    } else {
     -+      next = end;
     -+    }
     -+    if (p < next) {
     -+      if (names_len == names_cap) {
     -+        names_cap = 2 * names_cap + 1;
     -+        names = realloc(names, names_cap * sizeof(char *));
     -+      }
     -+      names[names_len++] = strdup(p);
     -+    }
     -+    p = next + 1;
     -+  }
     -+
     -+  if (names_len == names_cap) {
     -+    names_cap = 2 * names_cap + 1;
     -+    names = realloc(names, names_cap * sizeof(char *));
     -+  }
     -+
     -+  names[names_len] = NULL;
     -+  *namesp = names;
     -+}
     -+
     -+int names_equal(char **a, char **b) {
     -+  while (*a && *b) {
     -+    if (0 != strcmp(*a, *b)) {
     -+      return 0;
     -+    }
     -+
     -+    a++;
     -+    b++;
     -+  }
     -+
     -+  return *a == *b;
     -+}
     -+
     -+const char *error_str(int err) {
     -+  switch (err) {
     -+    case IO_ERROR:
     -+      return "I/O error";
     -+    case FORMAT_ERROR:
     -+      return "FORMAT_ERROR";
     -+    case NOT_EXIST_ERROR:
     -+      return "NOT_EXIST_ERROR";
     -+    case LOCK_ERROR:
     -+      return "LOCK_ERROR";
     -+    case API_ERROR:
     -+      return "API_ERROR";
     -+    case ZLIB_ERROR:
     -+      return "ZLIB_ERROR";
     -+    case -1:
     -+      return "general error";
     -+    default:
     -+      return "unknown error code";
     -+  }
     ++void parse_names(char *buf, int size, char ***namesp)
     ++{
     ++	char **names = NULL;
     ++	int names_cap = 0;
     ++	int names_len = 0;
     ++
     ++	char *p = buf;
     ++	char *end = buf + size;
     ++	while (p < end) {
     ++		char *next = strchr(p, '\n');
     ++		if (next != NULL) {
     ++			*next = 0;
     ++		} else {
     ++			next = end;
     ++		}
     ++		if (p < next) {
     ++			if (names_len == names_cap) {
     ++				names_cap = 2 * names_cap + 1;
     ++				names = realloc(names,
     ++						names_cap * sizeof(char *));
     ++			}
     ++			names[names_len++] = strdup(p);
     ++		}
     ++		p = next + 1;
     ++	}
     ++
     ++	if (names_len == names_cap) {
     ++		names_cap = 2 * names_cap + 1;
     ++		names = realloc(names, names_cap * sizeof(char *));
     ++	}
     ++
     ++	names[names_len] = NULL;
     ++	*namesp = names;
     ++}
     ++
     ++int names_equal(char **a, char **b)
     ++{
     ++	while (*a && *b) {
     ++		if (strcmp(*a, *b)) {
     ++			return 0;
     ++		}
     ++
     ++		a++;
     ++		b++;
     ++	}
     ++
     ++	return *a == *b;
     ++}
     ++
     ++const char *error_str(int err)
     ++{
     ++	switch (err) {
     ++	case IO_ERROR:
     ++		return "I/O error";
     ++	case FORMAT_ERROR:
     ++		return "FORMAT_ERROR";
     ++	case NOT_EXIST_ERROR:
     ++		return "NOT_EXIST_ERROR";
     ++	case LOCK_ERROR:
     ++		return "LOCK_ERROR";
     ++	case API_ERROR:
     ++		return "API_ERROR";
     ++	case ZLIB_ERROR:
     ++		return "ZLIB_ERROR";
     ++	case -1:
     ++		return "general error";
     ++	default:
     ++		return "unknown error code";
     ++	}
      +}
      
       diff --git a/reftable/basics.h b/reftable/basics.h
     @@ -524,7 +321,7 @@
      +#ifndef BASICS_H
      +#define BASICS_H
      +
     -+#include <stdint.h>
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     @@ -567,10 +364,7 @@
      +
      +#include "block.h"
      +
     -+#include <assert.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "blocksource.h"
      +#include "constants.h"
     @@ -579,365 +373,387 @@
      +#include "zlib.h"
      +
      +int block_writer_register_restart(struct block_writer *w, int n, bool restart,
     -+                                  struct slice key);
     ++				  struct slice key);
      +
      +void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
     -+                       uint32_t block_size, uint32_t header_off,
     -+                       int hash_size) {
     -+  bw->buf = buf;
     -+  bw->hash_size = hash_size;
     -+  bw->block_size = block_size;
     -+  bw->header_off = header_off;
     -+  bw->buf[header_off] = typ;
     -+  bw->next = header_off + 4;
     -+  bw->restart_interval = 16;
     -+  bw->entries = 0;
     ++		       uint32_t block_size, uint32_t header_off, int hash_size)
     ++{
     ++	bw->buf = buf;
     ++	bw->hash_size = hash_size;
     ++	bw->block_size = block_size;
     ++	bw->header_off = header_off;
     ++	bw->buf[header_off] = typ;
     ++	bw->next = header_off + 4;
     ++	bw->restart_interval = 16;
     ++	bw->entries = 0;
      +}
      +
     -+byte block_writer_type(struct block_writer *bw) {
     -+  return bw->buf[bw->header_off];
     ++byte block_writer_type(struct block_writer *bw)
     ++{
     ++	return bw->buf[bw->header_off];
      +}
      +
      +/* adds the record to the block. Returns -1 if it does not fit, 0 on
      +   success */
     -+int block_writer_add(struct block_writer *w, struct record rec) {
     -+  struct slice empty = {};
     -+  struct slice last =
     -+      w->entries % w->restart_interval == 0 ? empty : w->last_key;
     -+  struct slice out = {
     -+      .buf = w->buf + w->next,
     -+      .len = w->block_size - w->next,
     -+  };
     -+
     -+  struct slice start = out;
     -+
     -+  bool restart = false;
     -+  struct slice key = {};
     -+  int n = 0;
     -+
     -+  record_key(rec, &key);
     -+  n = encode_key(&restart, out, last, key, record_val_type(rec));
     -+  if (n < 0) {
     -+    goto err;
     -+  }
     -+  out.buf += n;
     -+  out.len -= n;
     -+
     -+  n = record_encode(rec, out, w->hash_size);
     -+  if (n < 0) {
     -+    goto err;
     -+  }
     -+
     -+  out.buf += n;
     -+  out.len -= n;
     -+
     -+  if (block_writer_register_restart(w, start.len - out.len, restart, key) < 0) {
     -+    goto err;
     -+  }
     -+
     -+  free(slice_yield(&key));
     -+  return 0;
     ++int block_writer_add(struct block_writer *w, struct record rec)
     ++{
     ++	struct slice empty = {};
     ++	struct slice last = w->entries % w->restart_interval == 0 ? empty :
     ++								    w->last_key;
     ++	struct slice out = {
     ++		.buf = w->buf + w->next,
     ++		.len = w->block_size - w->next,
     ++	};
     ++
     ++	struct slice start = out;
     ++
     ++	bool restart = false;
     ++	struct slice key = {};
     ++	int n = 0;
     ++
     ++	record_key(rec, &key);
     ++	n = encode_key(&restart, out, last, key, record_val_type(rec));
     ++	if (n < 0) {
     ++		goto err;
     ++	}
     ++	out.buf += n;
     ++	out.len -= n;
     ++
     ++	n = record_encode(rec, out, w->hash_size);
     ++	if (n < 0) {
     ++		goto err;
     ++	}
     ++
     ++	out.buf += n;
     ++	out.len -= n;
     ++
     ++	if (block_writer_register_restart(w, start.len - out.len, restart,
     ++					  key) < 0) {
     ++		goto err;
     ++	}
     ++
     ++	free(slice_yield(&key));
     ++	return 0;
      +
      +err:
     -+  free(slice_yield(&key));
     -+  return -1;
     ++	free(slice_yield(&key));
     ++	return -1;
      +}
      +
      +int block_writer_register_restart(struct block_writer *w, int n, bool restart,
     -+                                  struct slice key) {
     -+  int rlen = w->restart_len;
     -+  if (rlen >= MAX_RESTARTS) {
     -+    restart = false;
     -+  }
     -+
     -+  if (restart) {
     -+    rlen++;
     -+  }
     -+  if (2 + 3 * rlen + n > w->block_size - w->next) {
     -+    return -1;
     -+  }
     -+  if (restart) {
     -+    if (w->restart_len == w->restart_cap) {
     -+      w->restart_cap = w->restart_cap * 2 + 1;
     -+      w->restarts = realloc(w->restarts, sizeof(uint32_t) * w->restart_cap);
     -+    }
     -+
     -+    w->restarts[w->restart_len++] = w->next;
     -+  }
     -+
     -+  w->next += n;
     -+  slice_copy(&w->last_key, key);
     -+  w->entries++;
     -+  return 0;
     -+}
     -+
     -+int block_writer_finish(struct block_writer *w) {
     -+  for (int i = 0; i < w->restart_len; i++) {
     -+    put_u24(w->buf + w->next, w->restarts[i]);
     -+    w->next += 3;
     -+  }
     -+
     -+  put_u16(w->buf + w->next, w->restart_len);
     -+  w->next += 2;
     -+  put_u24(w->buf + 1 + w->header_off, w->next);
     -+
     -+  if (block_writer_type(w) == BLOCK_TYPE_LOG) {
     -+    int block_header_skip = 4 + w->header_off;
     -+    struct slice compressed = {};
     -+    uLongf dest_len = 0, src_len = 0;
     -+    slice_resize(&compressed, w->next - block_header_skip);
     -+
     -+    dest_len = compressed.len;
     -+    src_len = w->next - block_header_skip;
     -+
     -+    if (Z_OK != compress2(compressed.buf, &dest_len, w->buf + block_header_skip,
     -+                          src_len, 9)) {
     -+      free(slice_yield(&compressed));
     -+      return ZLIB_ERROR;
     -+    }
     -+    memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
     -+    w->next = dest_len + block_header_skip;
     -+  }
     -+  return w->next;
     -+}
     -+
     -+byte block_reader_type(struct block_reader *r) {
     -+  return r->block.data[r->header_off];
     ++				  struct slice key)
     ++{
     ++	int rlen = w->restart_len;
     ++	if (rlen >= MAX_RESTARTS) {
     ++		restart = false;
     ++	}
     ++
     ++	if (restart) {
     ++		rlen++;
     ++	}
     ++	if (2 + 3 * rlen + n > w->block_size - w->next) {
     ++		return -1;
     ++	}
     ++	if (restart) {
     ++		if (w->restart_len == w->restart_cap) {
     ++			w->restart_cap = w->restart_cap * 2 + 1;
     ++			w->restarts = realloc(
     ++				w->restarts, sizeof(uint32_t) * w->restart_cap);
     ++		}
     ++
     ++		w->restarts[w->restart_len++] = w->next;
     ++	}
     ++
     ++	w->next += n;
     ++	slice_copy(&w->last_key, key);
     ++	w->entries++;
     ++	return 0;
     ++}
     ++
     ++int block_writer_finish(struct block_writer *w)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < w->restart_len; i++) {
     ++		put_u24(w->buf + w->next, w->restarts[i]);
     ++		w->next += 3;
     ++	}
     ++
     ++	put_u16(w->buf + w->next, w->restart_len);
     ++	w->next += 2;
     ++	put_u24(w->buf + 1 + w->header_off, w->next);
     ++
     ++	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
     ++		int block_header_skip = 4 + w->header_off;
     ++		struct slice compressed = {};
     ++		uLongf dest_len = 0, src_len = 0;
     ++		slice_resize(&compressed, w->next - block_header_skip);
     ++
     ++		dest_len = compressed.len;
     ++		src_len = w->next - block_header_skip;
     ++
     ++		if (Z_OK != compress2(compressed.buf, &dest_len,
     ++				      w->buf + block_header_skip, src_len, 9)) {
     ++			free(slice_yield(&compressed));
     ++			return ZLIB_ERROR;
     ++		}
     ++		memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
     ++		w->next = dest_len + block_header_skip;
     ++	}
     ++	return w->next;
     ++}
     ++
     ++byte block_reader_type(struct block_reader *r)
     ++{
     ++	return r->block.data[r->header_off];
      +}
      +
      +int block_reader_init(struct block_reader *br, struct block *block,
     -+                      uint32_t header_off, uint32_t table_block_size,
     -+                      int hash_size) {
     -+  uint32_t full_block_size = table_block_size;
     -+  byte typ = block->data[header_off];
     -+  uint32_t sz = get_u24(block->data + header_off + 1);
     -+
     -+  if (!is_block_type(typ)) {
     -+    return FORMAT_ERROR;
     -+  }
     -+
     -+  if (typ == BLOCK_TYPE_LOG) {
     -+    struct slice uncompressed = {};
     -+    int block_header_skip = 4 + header_off;
     -+    uLongf dst_len = sz - block_header_skip;
     -+    uLongf src_len = block->len - block_header_skip;
     -+
     -+    slice_resize(&uncompressed, sz);
     -+    memcpy(uncompressed.buf, block->data, block_header_skip);
     -+
     -+    if (Z_OK != uncompress2(uncompressed.buf + block_header_skip, &dst_len,
     -+                            block->data + block_header_skip, &src_len)) {
     -+      free(slice_yield(&uncompressed));
     -+      return ZLIB_ERROR;
     -+    }
     -+
     -+    block_source_return_block(block->source, block);
     -+    block->data = uncompressed.buf;
     -+    block->len = dst_len; /* XXX: 4 bytes missing? */
     -+    block->source = malloc_block_source();
     -+    full_block_size = src_len + block_header_skip;
     -+  } else if (full_block_size == 0) {
     -+    full_block_size = sz;
     -+  } else if (sz < full_block_size && sz < block->len && block->data[sz] != 0) {
     -+    // If the block is smaller than the full block size,
     -+    // it is padded (data followed by '\0') or the next
     -+    // block is unaligned.
     -+    full_block_size = sz;
     -+  }
     -+
     -+  {
     -+    uint16_t restart_count = get_u16(block->data + sz - 2);
     -+    uint32_t restart_start = sz - 2 - 3 * restart_count;
     -+
     -+    byte *restart_bytes = block->data + restart_start;
     -+
     -+    // transfer ownership.
     -+    br->block = *block;
     -+    block->data = NULL;
     -+    block->len = 0;
     -+
     -+    br->hash_size = hash_size;
     -+    br->block_len = restart_start;
     -+    br->full_block_size = full_block_size;
     -+    br->header_off = header_off;
     -+    br->restart_count = restart_count;
     -+    br->restart_bytes = restart_bytes;
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+static uint32_t block_reader_restart_offset(struct block_reader *br, int i) {
     -+  return get_u24(br->restart_bytes + 3 * i);
     -+}
     -+
     -+void block_reader_start(struct block_reader *br, struct block_iter *it) {
     -+  it->br = br;
     -+  slice_resize(&it->last_key, 0);
     -+  it->next_off = br->header_off + 4;
     ++		      uint32_t header_off, uint32_t table_block_size,
     ++		      int hash_size)
     ++{
     ++	uint32_t full_block_size = table_block_size;
     ++	byte typ = block->data[header_off];
     ++	uint32_t sz = get_u24(block->data + header_off + 1);
     ++
     ++	if (!is_block_type(typ)) {
     ++		return FORMAT_ERROR;
     ++	}
     ++
     ++	if (typ == BLOCK_TYPE_LOG) {
     ++		struct slice uncompressed = {};
     ++		int block_header_skip = 4 + header_off;
     ++		uLongf dst_len = sz - block_header_skip;
     ++		uLongf src_len = block->len - block_header_skip;
     ++
     ++		slice_resize(&uncompressed, sz);
     ++		memcpy(uncompressed.buf, block->data, block_header_skip);
     ++
     ++		if (Z_OK !=
     ++		    uncompress2(uncompressed.buf + block_header_skip, &dst_len,
     ++				block->data + block_header_skip, &src_len)) {
     ++			free(slice_yield(&uncompressed));
     ++			return ZLIB_ERROR;
     ++		}
     ++
     ++		block_source_return_block(block->source, block);
     ++		block->data = uncompressed.buf;
     ++		block->len = dst_len; /* XXX: 4 bytes missing? */
     ++		block->source = malloc_block_source();
     ++		full_block_size = src_len + block_header_skip;
     ++	} else if (full_block_size == 0) {
     ++		full_block_size = sz;
     ++	} else if (sz < full_block_size && sz < block->len &&
     ++		   block->data[sz] != 0) {
     ++		/* If the block is smaller than the full block size, it is
     ++                   padded (data followed by '\0') or the next block is
     ++                   unaligned. */
     ++		full_block_size = sz;
     ++	}
     ++
     ++	{
     ++		uint16_t restart_count = get_u16(block->data + sz - 2);
     ++		uint32_t restart_start = sz - 2 - 3 * restart_count;
     ++
     ++		byte *restart_bytes = block->data + restart_start;
     ++
     ++		/* transfer ownership. */
     ++		br->block = *block;
     ++		block->data = NULL;
     ++		block->len = 0;
     ++
     ++		br->hash_size = hash_size;
     ++		br->block_len = restart_start;
     ++		br->full_block_size = full_block_size;
     ++		br->header_off = header_off;
     ++		br->restart_count = restart_count;
     ++		br->restart_bytes = restart_bytes;
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
     ++{
     ++	return get_u24(br->restart_bytes + 3 * i);
     ++}
     ++
     ++void block_reader_start(struct block_reader *br, struct block_iter *it)
     ++{
     ++	it->br = br;
     ++	slice_resize(&it->last_key, 0);
     ++	it->next_off = br->header_off + 4;
      +}
      +
      +struct restart_find_args {
     -+  struct slice key;
     -+  struct block_reader *r;
     -+  int error;
     ++	struct slice key;
     ++	struct block_reader *r;
     ++	int error;
      +};
      +
     -+static int restart_key_less(int idx, void *args) {
     -+  struct restart_find_args *a = (struct restart_find_args *)args;
     -+  uint32_t off = block_reader_restart_offset(a->r, idx);
     -+  struct slice in = {
     -+      .buf = a->r->block.data + off,
     -+      .len = a->r->block_len - off,
     -+  };
     -+
     -+  /* the restart key is verbatim in the block, so this could avoid the
     -+     alloc for decoding the key */
     -+  struct slice rkey = {};
     -+  struct slice last_key = {};
     -+  byte unused_extra;
     -+  int n = decode_key(&rkey, &unused_extra, last_key, in);
     -+  if (n < 0) {
     -+    a->error = 1;
     -+    return -1;
     -+  }
     -+
     -+  {
     -+    int result = slice_compare(a->key, rkey);
     -+    free(slice_yield(&rkey));
     -+    return result;
     -+  }
     -+}
     -+
     -+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src) {
     -+  dest->br = src->br;
     -+  dest->next_off = src->next_off;
     -+  slice_copy(&dest->last_key, src->last_key);
     -+}
     -+
     -+// return < 0 for error, 0 for OK, > 0 for EOF.
     -+int block_iter_next(struct block_iter *it, struct record rec) {
     -+  if (it->next_off >= it->br->block_len) {
     -+    return 1;
     -+  }
     -+
     -+  {
     -+    struct slice in = {
     -+        .buf = it->br->block.data + it->next_off,
     -+        .len = it->br->block_len - it->next_off,
     -+    };
     -+    struct slice start = in;
     -+    struct slice key = {};
     -+    byte extra;
     -+    int n = decode_key(&key, &extra, it->last_key, in);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+
     -+    in.buf += n;
     -+    in.len -= n;
     -+    n = record_decode(rec, key, extra, in, it->br->hash_size);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+    in.buf += n;
     -+    in.len -= n;
     -+
     -+    slice_copy(&it->last_key, key);
     -+    it->next_off += start.len - in.len;
     -+    free(slice_yield(&key));
     -+    return 0;
     -+  }
     -+}
     -+
     -+int block_reader_first_key(struct block_reader *br, struct slice *key) {
     -+  struct slice empty = {};
     -+  int off = br->header_off + 4;
     -+  struct slice in = {
     -+      .buf = br->block.data + off,
     -+      .len = br->block_len - off,
     -+  };
     -+
     -+  byte extra = 0;
     -+  int n = decode_key(key, &extra, empty, in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+  return 0;
     -+}
     -+
     -+int block_iter_seek(struct block_iter *it, struct slice want) {
     -+  return block_reader_seek(it->br, it, want);
     -+}
     -+
     -+void block_iter_close(struct block_iter *it) {
     -+  free(slice_yield(&it->last_key));
     ++static int restart_key_less(int idx, void *args)
     ++{
     ++	struct restart_find_args *a = (struct restart_find_args *)args;
     ++	uint32_t off = block_reader_restart_offset(a->r, idx);
     ++	struct slice in = {
     ++		.buf = a->r->block.data + off,
     ++		.len = a->r->block_len - off,
     ++	};
     ++
     ++	/* the restart key is verbatim in the block, so this could avoid the
     ++	   alloc for decoding the key */
     ++	struct slice rkey = {};
     ++	struct slice last_key = {};
     ++	byte unused_extra;
     ++	int n = decode_key(&rkey, &unused_extra, last_key, in);
     ++	if (n < 0) {
     ++		a->error = 1;
     ++		return -1;
     ++	}
     ++
     ++	{
     ++		int result = slice_compare(a->key, rkey);
     ++		free(slice_yield(&rkey));
     ++		return result;
     ++	}
     ++}
     ++
     ++void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
     ++{
     ++	dest->br = src->br;
     ++	dest->next_off = src->next_off;
     ++	slice_copy(&dest->last_key, src->last_key);
     ++}
     ++
     ++/* return < 0 for error, 0 for OK, > 0 for EOF. */
     ++int block_iter_next(struct block_iter *it, struct record rec)
     ++{
     ++	if (it->next_off >= it->br->block_len) {
     ++		return 1;
     ++	}
     ++
     ++	{
     ++		struct slice in = {
     ++			.buf = it->br->block.data + it->next_off,
     ++			.len = it->br->block_len - it->next_off,
     ++		};
     ++		struct slice start = in;
     ++		struct slice key = {};
     ++		byte extra;
     ++		int n = decode_key(&key, &extra, it->last_key, in);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++
     ++		in.buf += n;
     ++		in.len -= n;
     ++		n = record_decode(rec, key, extra, in, it->br->hash_size);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		in.buf += n;
     ++		in.len -= n;
     ++
     ++		slice_copy(&it->last_key, key);
     ++		it->next_off += start.len - in.len;
     ++		free(slice_yield(&key));
     ++		return 0;
     ++	}
     ++}
     ++
     ++int block_reader_first_key(struct block_reader *br, struct slice *key)
     ++{
     ++	struct slice empty = {};
     ++	int off = br->header_off + 4;
     ++	struct slice in = {
     ++		.buf = br->block.data + off,
     ++		.len = br->block_len - off,
     ++	};
     ++
     ++	byte extra = 0;
     ++	int n = decode_key(key, &extra, empty, in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++	return 0;
     ++}
     ++
     ++int block_iter_seek(struct block_iter *it, struct slice want)
     ++{
     ++	return block_reader_seek(it->br, it, want);
     ++}
     ++
     ++void block_iter_close(struct block_iter *it)
     ++{
     ++	free(slice_yield(&it->last_key));
      +}
      +
      +int block_reader_seek(struct block_reader *br, struct block_iter *it,
     -+                      struct slice want) {
     -+  struct restart_find_args args = {
     -+      .key = want,
     -+      .r = br,
     -+  };
     -+
     -+  int i = binsearch(br->restart_count, &restart_key_less, &args);
     -+  if (args.error) {
     -+    return -1;
     -+  }
     -+
     -+  it->br = br;
     -+  if (i > 0) {
     -+    i--;
     -+    it->next_off = block_reader_restart_offset(br, i);
     -+  } else {
     -+    it->next_off = br->header_off + 4;
     -+  }
     -+
     -+  {
     -+    struct record rec = new_record(block_reader_type(br));
     -+    struct slice key = {};
     -+    int result = 0;
     -+    int err = 0;
     -+    struct block_iter next = {};
     -+    while (true) {
     -+      block_iter_copy_from(&next, it);
     -+
     -+      err = block_iter_next(&next, rec);
     -+      if (err < 0) {
     -+        result = -1;
     -+        goto exit;
     -+      }
     -+
     -+      record_key(rec, &key);
     -+      if (err > 0 || slice_compare(key, want) >= 0) {
     -+        result = 0;
     -+        goto exit;
     -+      }
     -+
     -+      block_iter_copy_from(it, &next);
     -+    }
     -+
     -+  exit:
     -+    free(slice_yield(&key));
     -+    free(slice_yield(&next.last_key));
     -+    record_clear(rec);
     -+    free(record_yield(&rec));
     -+
     -+    return result;
     -+  }
     -+}
     -+
     -+void block_writer_reset(struct block_writer *bw) {
     -+  bw->restart_len = 0;
     -+  bw->last_key.len = 0;
     -+}
     -+
     -+void block_writer_clear(struct block_writer *bw) {
     -+  free(bw->restarts);
     -+  bw->restarts = NULL;
     -+  free(slice_yield(&bw->last_key));
     -+  // the block is not owned.
     ++		      struct slice want)
     ++{
     ++	struct restart_find_args args = {
     ++		.key = want,
     ++		.r = br,
     ++	};
     ++
     ++	int i = binsearch(br->restart_count, &restart_key_less, &args);
     ++	if (args.error) {
     ++		return -1;
     ++	}
     ++
     ++	it->br = br;
     ++	if (i > 0) {
     ++		i--;
     ++		it->next_off = block_reader_restart_offset(br, i);
     ++	} else {
     ++		it->next_off = br->header_off + 4;
     ++	}
     ++
     ++	{
     ++		struct record rec = new_record(block_reader_type(br));
     ++		struct slice key = {};
     ++		int result = 0;
     ++		int err = 0;
     ++		struct block_iter next = {};
     ++		while (true) {
     ++			block_iter_copy_from(&next, it);
     ++
     ++			err = block_iter_next(&next, rec);
     ++			if (err < 0) {
     ++				result = -1;
     ++				goto exit;
     ++			}
     ++
     ++			record_key(rec, &key);
     ++			if (err > 0 || slice_compare(key, want) >= 0) {
     ++				result = 0;
     ++				goto exit;
     ++			}
     ++
     ++			block_iter_copy_from(it, &next);
     ++		}
     ++
     ++	exit:
     ++		free(slice_yield(&key));
     ++		free(slice_yield(&next.last_key));
     ++		record_clear(rec);
     ++		free(record_yield(&rec));
     ++
     ++		return result;
     ++	}
     ++}
     ++
     ++void block_writer_reset(struct block_writer *bw)
     ++{
     ++	bw->restart_len = 0;
     ++	bw->last_key.len = 0;
     ++}
     ++
     ++void block_writer_clear(struct block_writer *bw)
     ++{
     ++	free(bw->restarts);
     ++	bw->restarts = NULL;
     ++	free(slice_yield(&bw->last_key));
     ++	/* the block is not owned. */
      +}
      
       diff --git a/reftable/block.h b/reftable/block.h
     @@ -961,22 +777,22 @@
      +#include "reftable.h"
      +
      +struct block_writer {
     -+  byte *buf;
     -+  uint32_t block_size;
     -+  uint32_t header_off;
     -+  int restart_interval;
     -+  int hash_size;
     -+
     -+  uint32_t next;
     -+  uint32_t *restarts;
     -+  uint32_t restart_len;
     -+  uint32_t restart_cap;
     -+  struct slice last_key;
     -+  int entries;
     ++	byte *buf;
     ++	uint32_t block_size;
     ++	uint32_t header_off;
     ++	int restart_interval;
     ++	int hash_size;
     ++
     ++	uint32_t next;
     ++	uint32_t *restarts;
     ++	uint32_t restart_len;
     ++	uint32_t restart_cap;
     ++	struct slice last_key;
     ++	int entries;
      +};
      +
      +void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
     -+                       uint32_t block_size, uint32_t header_off, int hash_size);
     ++		       uint32_t block_size, uint32_t header_off, int hash_size);
      +byte block_writer_type(struct block_writer *bw);
      +int block_writer_add(struct block_writer *w, struct record rec);
      +int block_writer_finish(struct block_writer *w);
     @@ -984,29 +800,29 @@
      +void block_writer_clear(struct block_writer *bw);
      +
      +struct block_reader {
     -+  uint32_t header_off;
     -+  struct block block;
     -+  int hash_size;
     -+
     -+  // size of the data, excluding restart data.
     -+  uint32_t block_len;
     -+  byte *restart_bytes;
     -+  uint32_t full_block_size;
     -+  uint16_t restart_count;
     ++	uint32_t header_off;
     ++	struct block block;
     ++	int hash_size;
     ++
     ++	/* size of the data, excluding restart data. */
     ++	uint32_t block_len;
     ++	byte *restart_bytes;
     ++	uint32_t full_block_size;
     ++	uint16_t restart_count;
      +};
      +
      +struct block_iter {
     -+  struct block_reader *br;
     -+  struct slice last_key;
     -+  uint32_t next_off;
     ++	struct block_reader *br;
     ++	struct slice last_key;
     ++	uint32_t next_off;
      +};
      +
      +int block_reader_init(struct block_reader *br, struct block *bl,
     -+                      uint32_t header_off, uint32_t table_block_size,
     -+                      int hash_size);
     ++		      uint32_t header_off, uint32_t table_block_size,
     ++		      int hash_size);
      +void block_reader_start(struct block_reader *br, struct block_iter *it);
      +int block_reader_seek(struct block_reader *br, struct block_iter *it,
     -+                      struct slice want);
     ++		      struct slice want);
      +byte block_reader_type(struct block_reader *r);
      +int block_reader_first_key(struct block_reader *br, struct slice *key);
      +
     @@ -1032,7 +848,7 @@
      +
      +#include "block.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "constants.h"
     @@ -1041,131 +857,137 @@
      +#include "test_framework.h"
      +
      +struct binsearch_args {
     -+  int key;
     -+  int *arr;
     ++	int key;
     ++	int *arr;
      +};
      +
     -+static int binsearch_func(int i, void *void_args) {
     -+  struct binsearch_args *args = (struct binsearch_args *)void_args;
     -+
     -+  return args->key < args->arr[i];
     -+}
     -+
     -+void test_binsearch() {
     -+  int arr[] = {2, 4, 6, 8, 10};
     -+  int sz = ARRAYSIZE(arr);
     -+  struct binsearch_args args = {
     -+      .arr = arr,
     -+  };
     -+
     -+  for (int i = 1; i < 11; i++) {
     -+    args.key = i;
     -+    int res = binsearch(sz, &binsearch_func, &args);
     -+
     -+    if (res < sz) {
     -+      assert(args.key < arr[res]);
     -+      if (res > 0) {
     -+        assert(args.key >= arr[res - 1]);
     -+      }
     -+    } else {
     -+      assert(args.key == 10 || args.key == 11);
     -+    }
     -+  }
     -+}
     -+
     -+void test_block_read_write() {
     -+  const int header_off = 21;  // random
     -+  const int N = 30;
     -+  char *names[N];
     -+  const int block_size = 1024;
     -+  struct block block = {};
     -+  block.data = calloc(block_size, 1);
     -+  block.len = block_size;
     -+
     -+  struct block_writer bw = {};
     -+  block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size, header_off,
     -+                    SHA1_SIZE);
     -+  struct ref_record ref = {};
     -+  struct record rec = {};
     -+  record_from_ref(&rec, &ref);
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    char name[100];
     -+    sprintf(name, "branch%02d", i);
     -+
     -+    byte hash[SHA1_SIZE];
     -+    memset(hash, i, sizeof(hash));
     -+
     -+    ref.ref_name = name;
     -+    ref.value = hash;
     -+    names[i] = strdup(name);
     -+    int n = block_writer_add(&bw, rec);
     -+    ref.ref_name = NULL;
     -+    ref.value = NULL;
     -+    assert(n == 0);
     -+  }
     -+
     -+  int n = block_writer_finish(&bw);
     -+  assert(n > 0);
     -+
     -+  block_writer_clear(&bw);
     -+
     -+  struct block_reader br = {};
     -+  block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
     -+
     -+  struct block_iter it = {};
     -+  block_reader_start(&br, &it);
     -+
     -+  int j = 0;
     -+  while (true) {
     -+    int r = block_iter_next(&it, rec);
     -+    assert(r >= 0);
     -+    if (r > 0) {
     -+      break;
     -+    }
     -+    assert_streq(names[j], ref.ref_name);
     -+    j++;
     -+  }
     -+
     -+  record_clear(rec);
     -+  block_iter_close(&it);
     -+
     -+  struct slice want = {};
     -+  for (int i = 0; i < N; i++) {
     -+    slice_set_string(&want, names[i]);
     -+
     -+    struct block_iter it = {};
     -+    int n = block_reader_seek(&br, &it, want);
     -+    assert(n == 0);
     -+
     -+    n = block_iter_next(&it, rec);
     -+    assert(n == 0);
     -+
     -+    assert_streq(names[i], ref.ref_name);
     -+
     -+    want.len--;
     -+    n = block_reader_seek(&br, &it, want);
     -+    assert(n == 0);
     -+
     -+    n = block_iter_next(&it, rec);
     -+    assert(n == 0);
     -+    assert_streq(names[10 * (i / 10)], ref.ref_name);
     -+
     -+    block_iter_close(&it);
     -+  }
     -+
     -+  record_clear(rec);
     -+  free(block.data);
     -+  free(slice_yield(&want));
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+}
     -+
     -+int main() {
     -+  add_test_case("binsearch", &test_binsearch);
     -+  add_test_case("block_read_write", &test_block_read_write);
     -+  test_main();
     ++static int binsearch_func(int i, void *void_args)
     ++{
     ++	struct binsearch_args *args = (struct binsearch_args *)void_args;
     ++
     ++	return args->key < args->arr[i];
     ++}
     ++
     ++void test_binsearch()
     ++{
     ++	int arr[] = { 2, 4, 6, 8, 10 };
     ++	int sz = ARRAYSIZE(arr);
     ++	struct binsearch_args args = {
     ++		.arr = arr,
     ++	};
     ++
     ++	int i = 0;
     ++	for (i = 1; i < 11; i++) {
     ++		args.key = i;
     ++		int res = binsearch(sz, &binsearch_func, &args);
     ++
     ++		if (res < sz) {
     ++			assert(args.key < arr[res]);
     ++			if (res > 0) {
     ++				assert(args.key >= arr[res - 1]);
     ++			}
     ++		} else {
     ++			assert(args.key == 10 || args.key == 11);
     ++		}
     ++	}
     ++}
     ++
     ++void test_block_read_write()
     ++{
     ++	const int header_off = 21; /* random */
     ++	const int N = 30;
     ++	char *names[N];
     ++	const int block_size = 1024;
     ++	struct block block = {};
     ++	block.data = calloc(block_size, 1);
     ++	block.len = block_size;
     ++
     ++	struct block_writer bw = {};
     ++	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
     ++			  header_off, SHA1_SIZE);
     ++	struct ref_record ref = {};
     ++	struct record rec = {};
     ++	record_from_ref(&rec, &ref);
     ++
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		snprintf(name, sizeof(name), "branch%02d", i);
     ++
     ++		byte hash[SHA1_SIZE];
     ++		memset(hash, i, sizeof(hash));
     ++
     ++		ref.ref_name = name;
     ++		ref.value = hash;
     ++		names[i] = strdup(name);
     ++		int n = block_writer_add(&bw, rec);
     ++		ref.ref_name = NULL;
     ++		ref.value = NULL;
     ++		assert(n == 0);
     ++	}
     ++
     ++	int n = block_writer_finish(&bw);
     ++	assert(n > 0);
     ++
     ++	block_writer_clear(&bw);
     ++
     ++	struct block_reader br = {};
     ++	block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
     ++
     ++	struct block_iter it = {};
     ++	block_reader_start(&br, &it);
     ++
     ++	int j = 0;
     ++	while (true) {
     ++		int r = block_iter_next(&it, rec);
     ++		assert(r >= 0);
     ++		if (r > 0) {
     ++			break;
     ++		}
     ++		assert_streq(names[j], ref.ref_name);
     ++		j++;
     ++	}
     ++
     ++	record_clear(rec);
     ++	block_iter_close(&it);
     ++
     ++	struct slice want = {};
     ++	for (i = 0; i < N; i++) {
     ++		slice_set_string(&want, names[i]);
     ++
     ++		struct block_iter it = {};
     ++		int n = block_reader_seek(&br, &it, want);
     ++		assert(n == 0);
     ++
     ++		n = block_iter_next(&it, rec);
     ++		assert(n == 0);
     ++
     ++		assert_streq(names[i], ref.ref_name);
     ++
     ++		want.len--;
     ++		n = block_reader_seek(&br, &it, want);
     ++		assert(n == 0);
     ++
     ++		n = block_iter_next(&it, rec);
     ++		assert(n == 0);
     ++		assert_streq(names[10 * (i / 10)], ref.ref_name);
     ++
     ++		block_iter_close(&it);
     ++	}
     ++
     ++	record_clear(rec);
     ++	free(block.data);
     ++	free(slice_yield(&want));
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("binsearch", &test_binsearch);
     ++	add_test_case("block_read_write", &test_block_read_write);
     ++	test_main();
      +}
      
       diff --git a/reftable/blocksource.h b/reftable/blocksource.h
     @@ -1188,7 +1010,7 @@
      +
      +uint64_t block_source_size(struct block_source source);
      +int block_source_read_block(struct block_source source, struct block *dest,
     -+                            uint64_t off, uint32_t size);
     ++			    uint64_t off, uint32_t size);
      +void block_source_return_block(struct block_source source, struct block *ret);
      +void block_source_close(struct block_source source);
      +
     @@ -1197,6 +1019,13 @@
       diff --git a/reftable/bytes.c b/reftable/bytes.c
       new file mode 100644
      
     + diff --git a/reftable/config.h b/reftable/config.h
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/config.h
     +@@
     ++/* empty */
     +
       diff --git a/reftable/constants.h b/reftable/constants.h
       new file mode 100644
       --- /dev/null
     @@ -1235,107 +1064,102 @@
       --- /dev/null
       +++ b/reftable/dump.c
      @@
     -+// Copyright 2020 Google Inc. All rights reserved.
     -+//
     -+// Licensed under the Apache License, Version 2.0 (the "License");
     -+// you may not use this file except in compliance with the License.
     -+// You may obtain a copy of the License at
     -+//
     -+//    http://www.apache.org/licenses/LICENSE-2.0
     -+//
     -+// Unless required by applicable law or agreed to in writing, software
     -+// distributed under the License is distributed on an "AS IS" BASIS,
     -+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     -+// See the License for the specific language governing permissions and
     -+// limitations under the License.
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+#include <stdio.h>
     -+#include <string.h>
     -+#include <unistd.h>
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     -+static int dump_table(const char *tablename) {
     -+  struct block_source src = {};
     -+  int err = block_source_from_file(&src, tablename);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  struct reader *r = NULL;
     -+  err = new_reader(&r, src, tablename);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_ref(r, &it, "");
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    struct ref_record ref = {};
     -+    while (1) {
     -+      err = iterator_next_ref(it, &ref);
     -+      if (err > 0) {
     -+        break;
     -+      }
     -+      if (err < 0) {
     -+        return err;
     -+      }
     -+      ref_record_print(&ref, 20);
     -+    }
     -+    iterator_destroy(&it);
     -+    ref_record_clear(&ref);
     -+  }
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_log(r, &it, "");
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+    struct log_record log = {};
     -+    while (1) {
     -+      err = iterator_next_log(it, &log);
     -+      if (err > 0) {
     -+        break;
     -+      }
     -+      if (err < 0) {
     -+        return err;
     -+      }
     -+      log_record_print(&log, 20);
     -+    }
     -+    iterator_destroy(&it);
     -+    log_record_clear(&log);
     -+  }
     -+  return 0;
     -+}
     -+
     -+int main(int argc, char *argv[]) {
     -+  int opt;
     -+  const char *table = NULL;
     -+  while ((opt = getopt(argc, argv, "t:")) != -1) {
     -+    switch (opt) {
     -+      case 't':
     -+        table = strdup(optarg);
     -+        break;
     -+      case '?':
     -+        printf("usage: %s [-table tablefile]\n", argv[0]);
     -+        return 2;
     -+        break;
     -+    }
     -+  }
     -+
     -+  if (table != NULL) {
     -+    int err = dump_table(table);
     -+    if (err < 0) {
     -+      fprintf(stderr, "%s: %s: %s\n", argv[0], table, error_str(err));
     -+      return 1;
     -+    }
     -+  }
     -+  return 0;
     ++static int dump_table(const char *tablename)
     ++{
     ++	struct block_source src = {};
     ++	int err = block_source_from_file(&src, tablename);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	struct reader *r = NULL;
     ++	err = new_reader(&r, src, tablename);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_ref(r, &it, "");
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		struct ref_record ref = {};
     ++		while (1) {
     ++			err = iterator_next_ref(it, &ref);
     ++			if (err > 0) {
     ++				break;
     ++			}
     ++			if (err < 0) {
     ++				return err;
     ++			}
     ++			ref_record_print(&ref, 20);
     ++		}
     ++		iterator_destroy(&it);
     ++		ref_record_clear(&ref);
     ++	}
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_log(r, &it, "");
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		struct log_record log = {};
     ++		while (1) {
     ++			err = iterator_next_log(it, &log);
     ++			if (err > 0) {
     ++				break;
     ++			}
     ++			if (err < 0) {
     ++				return err;
     ++			}
     ++			log_record_print(&log, 20);
     ++		}
     ++		iterator_destroy(&it);
     ++		log_record_clear(&log);
     ++	}
     ++	return 0;
     ++}
     ++
     ++int main(int argc, char *argv[])
     ++{
     ++	int opt;
     ++	const char *table = NULL;
     ++	while ((opt = getopt(argc, argv, "t:")) != -1) {
     ++		switch (opt) {
     ++		case 't':
     ++			table = strdup(optarg);
     ++			break;
     ++		case '?':
     ++			printf("usage: %s [-table tablefile]\n", argv[0]);
     ++			return 2;
     ++			break;
     ++		}
     ++	}
     ++
     ++	if (table != NULL) {
     ++		int err = dump_table(table);
     ++		if (err < 0) {
     ++			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
     ++				error_str(err));
     ++			return 1;
     ++		}
     ++	}
     ++	return 0;
      +}
      
       diff --git a/reftable/file.c b/reftable/file.c
     @@ -1351,15 +1175,7 @@
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include <assert.h>
     -+#include <errno.h>
     -+#include <fcntl.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <sys/stat.h>
     -+#include <sys/types.h>
     -+#include <unistd.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "iter.h"
     @@ -1368,78 +1184,85 @@
      +#include "tree.h"
      +
      +struct file_block_source {
     -+  int fd;
     -+  uint64_t size;
     ++	int fd;
     ++	uint64_t size;
      +};
      +
     -+static uint64_t file_size(void *b) {
     -+  return ((struct file_block_source *)b)->size;
     ++static uint64_t file_size(void *b)
     ++{
     ++	return ((struct file_block_source *)b)->size;
      +}
      +
     -+static void file_return_block(void *b, struct block *dest) {
     -+  memset(dest->data, 0xff, dest->len);
     -+  free(dest->data);
     ++static void file_return_block(void *b, struct block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	free(dest->data);
      +}
      +
     -+static void file_close(void *b) {
     -+  int fd = ((struct file_block_source *)b)->fd;
     -+  if (fd > 0) {
     -+    close(fd);
     -+    ((struct file_block_source *)b)->fd = 0;
     -+  }
     ++static void file_close(void *b)
     ++{
     ++	int fd = ((struct file_block_source *)b)->fd;
     ++	if (fd > 0) {
     ++		close(fd);
     ++		((struct file_block_source *)b)->fd = 0;
     ++	}
      +
     -+  free(b);
     ++	free(b);
      +}
      +
      +static int file_read_block(void *v, struct block *dest, uint64_t off,
     -+                           uint32_t size) {
     -+  struct file_block_source *b = (struct file_block_source *)v;
     -+  assert(off + size <= b->size);
     -+  dest->data = malloc(size);
     -+  if (pread(b->fd, dest->data, size, off) != size) {
     -+    return -1;
     -+  }
     -+  dest->len = size;
     -+  return size;
     ++			   uint32_t size)
     ++{
     ++	struct file_block_source *b = (struct file_block_source *)v;
     ++	assert(off + size <= b->size);
     ++	dest->data = malloc(size);
     ++	if (pread(b->fd, dest->data, size, off) != size) {
     ++		return -1;
     ++	}
     ++	dest->len = size;
     ++	return size;
      +}
      +
      +struct block_source_vtable file_vtable = {
     -+    .size = &file_size,
     -+    .read_block = &file_read_block,
     -+    .return_block = &file_return_block,
     -+    .close = &file_close,
     ++	.size = &file_size,
     ++	.read_block = &file_read_block,
     ++	.return_block = &file_return_block,
     ++	.close = &file_close,
      +};
      +
     -+int block_source_from_file(struct block_source *bs, const char *name) {
     -+  struct stat st = {};
     -+  int err = 0;
     -+  int fd = open(name, O_RDONLY);
     -+  if (fd < 0) {
     -+    if (errno == ENOENT) {
     -+      return NOT_EXIST_ERROR;
     -+    }
     -+    return -1;
     -+  }
     -+
     -+  err = fstat(fd, &st);
     -+  if (err < 0) {
     -+    return -1;
     -+  }
     -+
     -+  {
     -+    struct file_block_source *p = calloc(sizeof(struct file_block_source), 1);
     -+    p->size = st.st_size;
     -+    p->fd = fd;
     -+
     -+    bs->ops = &file_vtable;
     -+    bs->arg = p;
     -+  }
     -+  return 0;
     -+}
     -+
     -+int fd_writer(void *arg, byte *data, int sz) {
     -+  int *fdp = (int *)arg;
     -+  return write(*fdp, data, sz);
     ++int block_source_from_file(struct block_source *bs, const char *name)
     ++{
     ++	struct stat st = {};
     ++	int err = 0;
     ++	int fd = open(name, O_RDONLY);
     ++	if (fd < 0) {
     ++		if (errno == ENOENT) {
     ++			return NOT_EXIST_ERROR;
     ++		}
     ++		return -1;
     ++	}
     ++
     ++	err = fstat(fd, &st);
     ++	if (err < 0) {
     ++		return -1;
     ++	}
     ++
     ++	{
     ++		struct file_block_source *p =
     ++			calloc(sizeof(struct file_block_source), 1);
     ++		p->size = st.st_size;
     ++		p->fd = fd;
     ++
     ++		bs->ops = &file_vtable;
     ++		bs->arg = p;
     ++	}
     ++	return 0;
     ++}
     ++
     ++int fd_writer(void *arg, byte *data, int sz)
     ++{
     ++	int *fdp = (int *)arg;
     ++	return write(*fdp, data, sz);
      +}
      
       diff --git a/reftable/iter.c b/reftable/iter.c
     @@ -1457,206 +1280,225 @@
      +
      +#include "iter.h"
      +
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "constants.h"
      +#include "reader.h"
      +#include "reftable.h"
      +
     -+bool iterator_is_null(struct iterator it) { return it.ops == NULL; }
     ++bool iterator_is_null(struct iterator it)
     ++{
     ++	return it.ops == NULL;
     ++}
      +
     -+static int empty_iterator_next(void *arg, struct record rec) { return 1; }
     ++static int empty_iterator_next(void *arg, struct record rec)
     ++{
     ++	return 1;
     ++}
      +
     -+static void empty_iterator_close(void *arg) {}
     ++static void empty_iterator_close(void *arg)
     ++{
     ++}
      +
      +struct iterator_vtable empty_vtable = {
     -+    .next = &empty_iterator_next,
     -+    .close = &empty_iterator_close,
     ++	.next = &empty_iterator_next,
     ++	.close = &empty_iterator_close,
      +};
      +
     -+void iterator_set_empty(struct iterator *it) {
     -+  it->iter_arg = NULL;
     -+  it->ops = &empty_vtable;
     ++void iterator_set_empty(struct iterator *it)
     ++{
     ++	it->iter_arg = NULL;
     ++	it->ops = &empty_vtable;
      +}
      +
     -+int iterator_next(struct iterator it, struct record rec) {
     -+  return it.ops->next(it.iter_arg, rec);
     ++int iterator_next(struct iterator it, struct record rec)
     ++{
     ++	return it.ops->next(it.iter_arg, rec);
      +}
      +
     -+void iterator_destroy(struct iterator *it) {
     -+  if (it->ops == NULL) {
     -+    return;
     -+  }
     -+  it->ops->close(it->iter_arg);
     -+  it->ops = NULL;
     -+  free(it->iter_arg);
     -+  it->iter_arg = NULL;
     ++void iterator_destroy(struct iterator *it)
     ++{
     ++	if (it->ops == NULL) {
     ++		return;
     ++	}
     ++	it->ops->close(it->iter_arg);
     ++	it->ops = NULL;
     ++	free(it->iter_arg);
     ++	it->iter_arg = NULL;
      +}
      +
     -+int iterator_next_ref(struct iterator it, struct ref_record *ref) {
     -+  struct record rec = {};
     -+  record_from_ref(&rec, ref);
     -+  return iterator_next(it, rec);
     ++int iterator_next_ref(struct iterator it, struct ref_record *ref)
     ++{
     ++	struct record rec = {};
     ++	record_from_ref(&rec, ref);
     ++	return iterator_next(it, rec);
      +}
      +
     -+int iterator_next_log(struct iterator it, struct log_record *log) {
     -+  struct record rec = {};
     -+  record_from_log(&rec, log);
     -+  return iterator_next(it, rec);
     ++int iterator_next_log(struct iterator it, struct log_record *log)
     ++{
     ++	struct record rec = {};
     ++	record_from_log(&rec, log);
     ++	return iterator_next(it, rec);
      +}
      +
     -+static void filtering_ref_iterator_close(void *iter_arg) {
     -+  struct filtering_ref_iterator *fri =
     -+      (struct filtering_ref_iterator *)iter_arg;
     -+  free(slice_yield(&fri->oid));
     -+  iterator_destroy(&fri->it);
     ++static void filtering_ref_iterator_close(void *iter_arg)
     ++{
     ++	struct filtering_ref_iterator *fri =
     ++		(struct filtering_ref_iterator *)iter_arg;
     ++	free(slice_yield(&fri->oid));
     ++	iterator_destroy(&fri->it);
      +}
      +
     -+static int filtering_ref_iterator_next(void *iter_arg, struct record rec) {
     -+  struct filtering_ref_iterator *fri =
     -+      (struct filtering_ref_iterator *)iter_arg;
     -+  struct ref_record *ref = (struct ref_record *)rec.data;
     ++static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
     ++{
     ++	struct filtering_ref_iterator *fri =
     ++		(struct filtering_ref_iterator *)iter_arg;
     ++	struct ref_record *ref = (struct ref_record *)rec.data;
      +
     -+  while (true) {
     -+    int err = iterator_next_ref(fri->it, ref);
     -+    if (err != 0) {
     -+      return err;
     -+    }
     ++	while (true) {
     ++		int err = iterator_next_ref(fri->it, ref);
     ++		if (err != 0) {
     ++			return err;
     ++		}
      +
     -+    if (fri->double_check) {
     -+      struct iterator it = {};
     ++		if (fri->double_check) {
     ++			struct iterator it = {};
      +
     -+      int err = reader_seek_ref(fri->r, &it, ref->ref_name);
     -+      if (err == 0) {
     -+        err = iterator_next_ref(it, ref);
     -+      }
     ++			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
     ++			if (err == 0) {
     ++				err = iterator_next_ref(it, ref);
     ++			}
      +
     -+      iterator_destroy(&it);
     ++			iterator_destroy(&it);
      +
     -+      if (err < 0) {
     -+        return err;
     -+      }
     ++			if (err < 0) {
     ++				return err;
     ++			}
      +
     -+      if (err > 0) {
     -+        continue;
     -+      }
     -+    }
     ++			if (err > 0) {
     ++				continue;
     ++			}
     ++		}
      +
     -+    if ((ref->target_value != NULL &&
     -+         0 == memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
     -+        (ref->value != NULL &&
     -+         0 == memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
     -+      return 0;
     -+    }
     -+  }
     ++		if ((ref->target_value != NULL &&
     ++		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
     ++		    (ref->value != NULL &&
     ++		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
     ++			return 0;
     ++		}
     ++	}
      +}
      +
      +struct iterator_vtable filtering_ref_iterator_vtable = {
     -+    .next = &filtering_ref_iterator_next,
     -+    .close = &filtering_ref_iterator_close,
     ++	.next = &filtering_ref_iterator_next,
     ++	.close = &filtering_ref_iterator_close,
      +};
      +
      +void iterator_from_filtering_ref_iterator(struct iterator *it,
     -+                                          struct filtering_ref_iterator *fri) {
     -+  it->iter_arg = fri;
     -+  it->ops = &filtering_ref_iterator_vtable;
     -+}
     -+
     -+static void indexed_table_ref_iter_close(void *p) {
     -+  struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     -+  block_iter_close(&it->cur);
     -+  reader_return_block(it->r, &it->block_reader.block);
     -+  free(slice_yield(&it->oid));
     -+}
     -+
     -+static int indexed_table_ref_iter_next_block(
     -+    struct indexed_table_ref_iter *it) {
     -+  if (it->offset_idx == it->offset_len) {
     -+    it->finished = true;
     -+    return 1;
     -+  }
     -+
     -+  reader_return_block(it->r, &it->block_reader.block);
     -+
     -+  {
     -+    uint64_t off = it->offsets[it->offset_idx++];
     -+    int err =
     -+        reader_init_block_reader(it->r, &it->block_reader, off, BLOCK_TYPE_REF);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+    if (err > 0) {
     -+      // indexed block does not exist.
     -+      return FORMAT_ERROR;
     -+    }
     -+  }
     -+  block_reader_start(&it->block_reader, &it->cur);
     -+  return 0;
     -+}
     -+
     -+static int indexed_table_ref_iter_next(void *p, struct record rec) {
     -+  struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     -+  struct ref_record *ref = (struct ref_record *)rec.data;
     -+
     -+  while (true) {
     -+    int err = block_iter_next(&it->cur, rec);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    if (err > 0) {
     -+      err = indexed_table_ref_iter_next_block(it);
     -+      if (err < 0) {
     -+        return err;
     -+      }
     -+
     -+      if (it->finished) {
     -+        return 1;
     -+      }
     -+      continue;
     -+    }
     -+
     -+    if (0 == memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
     -+        0 == memcmp(it->oid.buf, ref->value, it->oid.len)) {
     -+      return 0;
     -+    }
     -+  }
     ++					  struct filtering_ref_iterator *fri)
     ++{
     ++	it->iter_arg = fri;
     ++	it->ops = &filtering_ref_iterator_vtable;
     ++}
     ++
     ++static void indexed_table_ref_iter_close(void *p)
     ++{
     ++	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     ++	block_iter_close(&it->cur);
     ++	reader_return_block(it->r, &it->block_reader.block);
     ++	free(slice_yield(&it->oid));
     ++}
     ++
     ++static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
     ++{
     ++	if (it->offset_idx == it->offset_len) {
     ++		it->finished = true;
     ++		return 1;
     ++	}
     ++
     ++	reader_return_block(it->r, &it->block_reader.block);
     ++
     ++	{
     ++		uint64_t off = it->offsets[it->offset_idx++];
     ++		int err = reader_init_block_reader(it->r, &it->block_reader,
     ++						   off, BLOCK_TYPE_REF);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		if (err > 0) {
     ++			/* indexed block does not exist. */
     ++			return FORMAT_ERROR;
     ++		}
     ++	}
     ++	block_reader_start(&it->block_reader, &it->cur);
     ++	return 0;
     ++}
     ++
     ++static int indexed_table_ref_iter_next(void *p, struct record rec)
     ++{
     ++	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     ++	struct ref_record *ref = (struct ref_record *)rec.data;
     ++
     ++	while (true) {
     ++		int err = block_iter_next(&it->cur, rec);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		if (err > 0) {
     ++			err = indexed_table_ref_iter_next_block(it);
     ++			if (err < 0) {
     ++				return err;
     ++			}
     ++
     ++			if (it->finished) {
     ++				return 1;
     ++			}
     ++			continue;
     ++		}
     ++
     ++		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
     ++		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
     ++			return 0;
     ++		}
     ++	}
      +}
      +
      +int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
     -+                               struct reader *r, byte *oid, int oid_len,
     -+                               uint64_t *offsets, int offset_len) {
     -+  struct indexed_table_ref_iter *itr =
     -+      calloc(sizeof(struct indexed_table_ref_iter), 1);
     -+  int err = 0;
     ++			       struct reader *r, byte *oid, int oid_len,
     ++			       uint64_t *offsets, int offset_len)
     ++{
     ++	struct indexed_table_ref_iter *itr =
     ++		calloc(sizeof(struct indexed_table_ref_iter), 1);
     ++	int err = 0;
      +
     -+  itr->r = r;
     -+  slice_resize(&itr->oid, oid_len);
     -+  memcpy(itr->oid.buf, oid, oid_len);
     ++	itr->r = r;
     ++	slice_resize(&itr->oid, oid_len);
     ++	memcpy(itr->oid.buf, oid, oid_len);
      +
     -+  itr->offsets = offsets;
     -+  itr->offset_len = offset_len;
     ++	itr->offsets = offsets;
     ++	itr->offset_len = offset_len;
      +
     -+  err = indexed_table_ref_iter_next_block(itr);
     -+  if (err < 0) {
     -+    free(itr);
     -+  } else {
     -+    *dest = itr;
     -+  }
     -+  return err;
     ++	err = indexed_table_ref_iter_next_block(itr);
     ++	if (err < 0) {
     ++		free(itr);
     ++	} else {
     ++		*dest = itr;
     ++	}
     ++	return err;
      +}
      +
      +struct iterator_vtable indexed_table_ref_iter_vtable = {
     -+    .next = &indexed_table_ref_iter_next,
     -+    .close = &indexed_table_ref_iter_close,
     ++	.next = &indexed_table_ref_iter_next,
     ++	.close = &indexed_table_ref_iter_close,
      +};
      +
      +void iterator_from_indexed_table_ref_iter(struct iterator *it,
     -+                                          struct indexed_table_ref_iter *itr) {
     -+  it->iter_arg = itr;
     -+  it->ops = &indexed_table_ref_iter_vtable;
     ++					  struct indexed_table_ref_iter *itr)
     ++{
     ++	it->iter_arg = itr;
     ++	it->ops = &indexed_table_ref_iter_vtable;
      +}
      
       diff --git a/reftable/iter.h b/reftable/iter.h
     @@ -1680,8 +1522,8 @@
      +#include "slice.h"
      +
      +struct iterator_vtable {
     -+  int (*next)(void *iter_arg, struct record rec);
     -+  void (*close)(void *iter_arg);
     ++	int (*next)(void *iter_arg, struct record rec);
     ++	void (*close)(void *iter_arg);
      +};
      +
      +void iterator_set_empty(struct iterator *it);
     @@ -1689,35 +1531,35 @@
      +bool iterator_is_null(struct iterator it);
      +
      +struct filtering_ref_iterator {
     -+  struct reader *r;
     -+  struct slice oid;
     -+  bool double_check;
     -+  struct iterator it;
     ++	struct reader *r;
     ++	struct slice oid;
     ++	bool double_check;
     ++	struct iterator it;
      +};
      +
      +void iterator_from_filtering_ref_iterator(struct iterator *,
     -+                                          struct filtering_ref_iterator *);
     ++					  struct filtering_ref_iterator *);
      +
      +struct indexed_table_ref_iter {
     -+  struct reader *r;
     -+  struct slice oid;
     -+
     -+  // mutable
     -+  uint64_t *offsets;
     -+
     -+  // Points to the next offset to read.
     -+  int offset_idx;
     -+  int offset_len;
     -+  struct block_reader block_reader;
     -+  struct block_iter cur;
     -+  bool finished;
     ++	struct reader *r;
     ++	struct slice oid;
     ++
     ++	/* mutable */
     ++	uint64_t *offsets;
     ++
     ++	/* Points to the next offset to read. */
     ++	int offset_idx;
     ++	int offset_len;
     ++	struct block_reader block_reader;
     ++	struct block_iter cur;
     ++	bool finished;
      +};
      +
      +void iterator_from_indexed_table_ref_iter(struct iterator *it,
     -+                                          struct indexed_table_ref_iter *itr);
     ++					  struct indexed_table_ref_iter *itr);
      +int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
     -+                               struct reader *r, byte *oid, int oid_len,
     -+                               uint64_t *offsets, int offset_len);
     ++			       struct reader *r, byte *oid, int oid_len,
     ++			       uint64_t *offsets, int offset_len);
      +
      +#endif
      
     @@ -1736,257 +1578,283 @@
      +
      +#include "merged.h"
      +
     -+#include <stdlib.h>
     ++#include "system.h"
      +
      +#include "constants.h"
      +#include "iter.h"
      +#include "pq.h"
      +#include "reader.h"
      +
     -+static int merged_iter_init(struct merged_iter *mi) {
     -+  for (int i = 0; i < mi->stack_len; i++) {
     -+    struct record rec = new_record(mi->typ);
     -+    int err = iterator_next(mi->stack[i], rec);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    if (err > 0) {
     -+      iterator_destroy(&mi->stack[i]);
     -+      record_clear(rec);
     -+      free(record_yield(&rec));
     -+    } else {
     -+      struct pq_entry e = {
     -+          .rec = rec,
     -+          .index = i,
     -+      };
     -+      merged_iter_pqueue_add(&mi->pq, e);
     -+    }
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+static void merged_iter_close(void *p) {
     -+  struct merged_iter *mi = (struct merged_iter *)p;
     -+  merged_iter_pqueue_clear(&mi->pq);
     -+  for (int i = 0; i < mi->stack_len; i++) {
     -+    iterator_destroy(&mi->stack[i]);
     -+  }
     -+  free(mi->stack);
     -+}
     -+
     -+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx) {
     -+  if (iterator_is_null(mi->stack[idx])) {
     -+    return 0;
     -+  }
     -+
     -+  {
     -+    struct record rec = new_record(mi->typ);
     -+    struct pq_entry e = {
     -+        .rec = rec,
     -+        .index = idx,
     -+    };
     -+    int err = iterator_next(mi->stack[idx], rec);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    if (err > 0) {
     -+      iterator_destroy(&mi->stack[idx]);
     -+      record_clear(rec);
     -+      free(record_yield(&rec));
     -+      return 0;
     -+    }
     -+
     -+    merged_iter_pqueue_add(&mi->pq, e);
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int merged_iter_next(struct merged_iter *mi, struct record rec) {
     -+  struct slice entry_key = {};
     -+  struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
     -+  int err = merged_iter_advance_subiter(mi, entry.index);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  record_key(entry.rec, &entry_key);
     -+  while (!merged_iter_pqueue_is_empty(mi->pq)) {
     -+    struct pq_entry top = merged_iter_pqueue_top(mi->pq);
     -+    struct slice k = {};
     -+    int err = 0, cmp = 0;
     -+
     -+    record_key(top.rec, &k);
     -+
     -+    cmp = slice_compare(k, entry_key);
     -+    free(slice_yield(&k));
     -+
     -+    if (cmp > 0) {
     -+      break;
     -+    }
     -+
     -+    merged_iter_pqueue_remove(&mi->pq);
     -+    err = merged_iter_advance_subiter(mi, top.index);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+    record_clear(top.rec);
     -+    free(record_yield(&top.rec));
     -+  }
     -+
     -+  record_copy_from(rec, entry.rec, mi->hash_size);
     -+  record_clear(entry.rec);
     -+  free(record_yield(&entry.rec));
     -+  free(slice_yield(&entry_key));
     -+  return 0;
     -+}
     -+
     -+static int merged_iter_next_void(void *p, struct record rec) {
     -+  struct merged_iter *mi = (struct merged_iter *)p;
     -+  if (merged_iter_pqueue_is_empty(mi->pq)) {
     -+    return 1;
     -+  }
     -+
     -+  return merged_iter_next(mi, rec);
     ++static int merged_iter_init(struct merged_iter *mi)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < mi->stack_len; i++) {
     ++		struct record rec = new_record(mi->typ);
     ++		int err = iterator_next(mi->stack[i], rec);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		if (err > 0) {
     ++			iterator_destroy(&mi->stack[i]);
     ++			record_clear(rec);
     ++			free(record_yield(&rec));
     ++		} else {
     ++			struct pq_entry e = {
     ++				.rec = rec,
     ++				.index = i,
     ++			};
     ++			merged_iter_pqueue_add(&mi->pq, e);
     ++		}
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++static void merged_iter_close(void *p)
     ++{
     ++	struct merged_iter *mi = (struct merged_iter *)p;
     ++	int i = 0;
     ++	merged_iter_pqueue_clear(&mi->pq);
     ++	for (i = 0; i < mi->stack_len; i++) {
     ++		iterator_destroy(&mi->stack[i]);
     ++	}
     ++	free(mi->stack);
     ++}
     ++
     ++static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
     ++{
     ++	if (iterator_is_null(mi->stack[idx])) {
     ++		return 0;
     ++	}
     ++
     ++	{
     ++		struct record rec = new_record(mi->typ);
     ++		struct pq_entry e = {
     ++			.rec = rec,
     ++			.index = idx,
     ++		};
     ++		int err = iterator_next(mi->stack[idx], rec);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		if (err > 0) {
     ++			iterator_destroy(&mi->stack[idx]);
     ++			record_clear(rec);
     ++			free(record_yield(&rec));
     ++			return 0;
     ++		}
     ++
     ++		merged_iter_pqueue_add(&mi->pq, e);
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int merged_iter_next(struct merged_iter *mi, struct record rec)
     ++{
     ++	struct slice entry_key = {};
     ++	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
     ++	int err = merged_iter_advance_subiter(mi, entry.index);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	record_key(entry.rec, &entry_key);
     ++	while (!merged_iter_pqueue_is_empty(mi->pq)) {
     ++		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
     ++		struct slice k = {};
     ++		int err = 0, cmp = 0;
     ++
     ++		record_key(top.rec, &k);
     ++
     ++		cmp = slice_compare(k, entry_key);
     ++		free(slice_yield(&k));
     ++
     ++		if (cmp > 0) {
     ++			break;
     ++		}
     ++
     ++		merged_iter_pqueue_remove(&mi->pq);
     ++		err = merged_iter_advance_subiter(mi, top.index);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		record_clear(top.rec);
     ++		free(record_yield(&top.rec));
     ++	}
     ++
     ++	record_copy_from(rec, entry.rec, mi->hash_size);
     ++	record_clear(entry.rec);
     ++	free(record_yield(&entry.rec));
     ++	free(slice_yield(&entry_key));
     ++	return 0;
     ++}
     ++
     ++static int merged_iter_next_void(void *p, struct record rec)
     ++{
     ++	struct merged_iter *mi = (struct merged_iter *)p;
     ++	if (merged_iter_pqueue_is_empty(mi->pq)) {
     ++		return 1;
     ++	}
     ++
     ++	return merged_iter_next(mi, rec);
      +}
      +
      +struct iterator_vtable merged_iter_vtable = {
     -+    .next = &merged_iter_next_void,
     -+    .close = &merged_iter_close,
     ++	.next = &merged_iter_next_void,
     ++	.close = &merged_iter_close,
      +};
      +
      +static void iterator_from_merged_iter(struct iterator *it,
     -+                                      struct merged_iter *mi) {
     -+  it->iter_arg = mi;
     -+  it->ops = &merged_iter_vtable;
     -+}
     -+
     -+int new_merged_table(struct merged_table **dest, struct reader **stack, int n) {
     -+  uint64_t last_max = 0;
     -+  uint64_t first_min = 0;
     -+  for (int i = 0; i < n; i++) {
     -+    struct reader *r = stack[i];
     -+    if (i > 0 && last_max >= reader_min_update_index(r)) {
     -+      return FORMAT_ERROR;
     -+    }
     -+    if (i == 0) {
     -+      first_min = reader_min_update_index(r);
     -+    }
     -+
     -+    last_max = reader_max_update_index(r);
     -+  }
     -+
     -+  {
     -+    struct merged_table m = {
     -+        .stack = stack,
     -+        .stack_len = n,
     -+        .min = first_min,
     -+        .max = last_max,
     -+        .hash_size = SHA1_SIZE,
     -+    };
     -+
     -+    *dest = calloc(sizeof(struct merged_table), 1);
     -+    **dest = m;
     -+  }
     -+  return 0;
     -+}
     -+
     -+void merged_table_close(struct merged_table *mt) {
     -+  for (int i = 0; i < mt->stack_len; i++) {
     -+    reader_free(mt->stack[i]);
     -+  }
     -+  free(mt->stack);
     -+  mt->stack = NULL;
     -+  mt->stack_len = 0;
     ++				      struct merged_iter *mi)
     ++{
     ++	it->iter_arg = mi;
     ++	it->ops = &merged_iter_vtable;
     ++}
     ++
     ++int new_merged_table(struct merged_table **dest, struct reader **stack, int n)
     ++{
     ++	uint64_t last_max = 0;
     ++	uint64_t first_min = 0;
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		struct reader *r = stack[i];
     ++		if (i > 0 && last_max >= reader_min_update_index(r)) {
     ++			return FORMAT_ERROR;
     ++		}
     ++		if (i == 0) {
     ++			first_min = reader_min_update_index(r);
     ++		}
     ++
     ++		last_max = reader_max_update_index(r);
     ++	}
     ++
     ++	{
     ++		struct merged_table m = {
     ++			.stack = stack,
     ++			.stack_len = n,
     ++			.min = first_min,
     ++			.max = last_max,
     ++			.hash_size = SHA1_SIZE,
     ++		};
     ++
     ++		*dest = calloc(sizeof(struct merged_table), 1);
     ++		**dest = m;
     ++	}
     ++	return 0;
     ++}
     ++
     ++void merged_table_close(struct merged_table *mt)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < mt->stack_len; i++) {
     ++		reader_free(mt->stack[i]);
     ++	}
     ++	free(mt->stack);
     ++	mt->stack = NULL;
     ++	mt->stack_len = 0;
      +}
      +
      +/* clears the list of subtable, without affecting the readers themselves. */
     -+void merged_table_clear(struct merged_table *mt) {
     -+  free(mt->stack);
     -+  mt->stack = NULL;
     -+  mt->stack_len = 0;
     ++void merged_table_clear(struct merged_table *mt)
     ++{
     ++	free(mt->stack);
     ++	mt->stack = NULL;
     ++	mt->stack_len = 0;
      +}
      +
     -+void merged_table_free(struct merged_table *mt) {
     -+  if (mt == NULL) {
     -+    return;
     -+  }
     -+  merged_table_clear(mt);
     -+  free(mt);
     ++void merged_table_free(struct merged_table *mt)
     ++{
     ++	if (mt == NULL) {
     ++		return;
     ++	}
     ++	merged_table_clear(mt);
     ++	free(mt);
      +}
      +
     -+uint64_t merged_max_update_index(struct merged_table *mt) { return mt->max; }
     ++uint64_t merged_max_update_index(struct merged_table *mt)
     ++{
     ++	return mt->max;
     ++}
      +
     -+uint64_t merged_min_update_index(struct merged_table *mt) { return mt->min; }
     ++uint64_t merged_min_update_index(struct merged_table *mt)
     ++{
     ++	return mt->min;
     ++}
      +
      +static int merged_table_seek_record(struct merged_table *mt,
     -+                                    struct iterator *it, struct record rec) {
     -+  struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
     -+  struct merged_iter merged = {
     -+      .stack = iters,
     -+      .typ = record_type(rec),
     -+      .hash_size = mt->hash_size,
     -+  };
     -+  int n = 0;
     -+  int err = 0;
     -+  for (int i = 0; i < mt->stack_len && err == 0; i++) {
     -+    int e = reader_seek(mt->stack[i], &iters[n], rec);
     -+    if (e < 0) {
     -+      err = e;
     -+    }
     -+    if (e == 0) {
     -+      n++;
     -+    }
     -+  }
     -+  if (err < 0) {
     -+    for (int i = 0; i < n; i++) {
     -+      iterator_destroy(&iters[i]);
     -+    }
     -+    free(iters);
     -+    return err;
     -+  }
     -+
     -+  merged.stack_len = n, err = merged_iter_init(&merged);
     -+  if (err < 0) {
     -+    merged_iter_close(&merged);
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct merged_iter *p = malloc(sizeof(struct merged_iter));
     -+    *p = merged;
     -+    iterator_from_merged_iter(it, p);
     -+  }
     -+  return 0;
     ++				    struct iterator *it, struct record rec)
     ++{
     ++	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
     ++	struct merged_iter merged = {
     ++		.stack = iters,
     ++		.typ = record_type(rec),
     ++		.hash_size = mt->hash_size,
     ++	};
     ++	int n = 0;
     ++	int err = 0;
     ++	int i = 0;
     ++	for (i = 0; i < mt->stack_len && err == 0; i++) {
     ++		int e = reader_seek(mt->stack[i], &iters[n], rec);
     ++		if (e < 0) {
     ++			err = e;
     ++		}
     ++		if (e == 0) {
     ++			n++;
     ++		}
     ++	}
     ++	if (err < 0) {
     ++		int i = 0;
     ++		for (i = 0; i < n; i++) {
     ++			iterator_destroy(&iters[i]);
     ++		}
     ++		free(iters);
     ++		return err;
     ++	}
     ++
     ++	merged.stack_len = n, err = merged_iter_init(&merged);
     ++	if (err < 0) {
     ++		merged_iter_close(&merged);
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct merged_iter *p = malloc(sizeof(struct merged_iter));
     ++		*p = merged;
     ++		iterator_from_merged_iter(it, p);
     ++	}
     ++	return 0;
      +}
      +
      +int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
     -+                          const char *name) {
     -+  struct ref_record ref = {
     -+      .ref_name = (char *)name,
     -+  };
     -+  struct record rec = {};
     -+  record_from_ref(&rec, &ref);
     -+  return merged_table_seek_record(mt, it, rec);
     ++			  const char *name)
     ++{
     ++	struct ref_record ref = {
     ++		.ref_name = (char *)name,
     ++	};
     ++	struct record rec = {};
     ++	record_from_ref(&rec, &ref);
     ++	return merged_table_seek_record(mt, it, rec);
      +}
      +
      +int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
     -+                             const char *name, uint64_t update_index) {
     -+  struct log_record log = {
     -+      .ref_name = (char *)name,
     -+      .update_index = update_index,
     -+  };
     -+  struct record rec = {};
     -+  record_from_log(&rec, &log);
     -+  return merged_table_seek_record(mt, it, rec);
     ++			     const char *name, uint64_t update_index)
     ++{
     ++	struct log_record log = {
     ++		.ref_name = (char *)name,
     ++		.update_index = update_index,
     ++	};
     ++	struct record rec = {};
     ++	record_from_log(&rec, &log);
     ++	return merged_table_seek_record(mt, it, rec);
      +}
      +
      +int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
     -+                          const char *name) {
     -+  uint64_t max = ~((uint64_t)0);
     -+  return merged_table_seek_log_at(mt, it, name, max);
     ++			  const char *name)
     ++{
     ++	uint64_t max = ~((uint64_t)0);
     ++	return merged_table_seek_log_at(mt, it, name, max);
      +}
      
       diff --git a/reftable/merged.h b/reftable/merged.h
     @@ -2009,20 +1877,20 @@
      +#include "reftable.h"
      +
      +struct merged_table {
     -+  struct reader **stack;
     -+  int stack_len;
     -+  int hash_size;
     ++	struct reader **stack;
     ++	int stack_len;
     ++	int hash_size;
      +
     -+  uint64_t min;
     -+  uint64_t max;
     ++	uint64_t min;
     ++	uint64_t max;
      +};
      +
      +struct merged_iter {
     -+  struct iterator *stack;
     -+  int hash_size;
     -+  int stack_len;
     -+  byte typ;
     -+  struct merged_iter_pqueue pq;
     ++	struct iterator *stack;
     ++	int hash_size;
     ++	int stack_len;
     ++	byte typ;
     ++	struct merged_iter_pqueue pq;
      +} merged_iter;
      +
      +void merged_table_clear(struct merged_table *mt);
     @@ -2044,7 +1912,7 @@
      +
      +#include "merged.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "block.h"
     @@ -2055,231 +1923,242 @@
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void test_pq(void) {
     -+  char *names[54] = {};
     -+  int N = ARRAYSIZE(names) - 1;
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    char name[100];
     -+    sprintf(name, "%02d", i);
     -+    names[i] = strdup(name);
     -+  }
     -+
     -+  struct merged_iter_pqueue pq = {};
     -+
     -+  int i = 1;
     -+  do {
     -+    struct record rec = new_record(BLOCK_TYPE_REF);
     -+    record_as_ref(rec)->ref_name = names[i];
     -+
     -+    struct pq_entry e = {
     -+        .rec = rec,
     -+    };
     -+    merged_iter_pqueue_add(&pq, e);
     -+    merged_iter_pqueue_check(pq);
     -+    i = (i * 7) % N;
     -+  } while (i != 1);
     -+
     -+  const char *last = NULL;
     -+  while (!merged_iter_pqueue_is_empty(pq)) {
     -+    struct pq_entry e = merged_iter_pqueue_remove(&pq);
     -+    merged_iter_pqueue_check(pq);
     -+    struct ref_record *ref = record_as_ref(e.rec);
     -+
     -+    if (last != NULL) {
     -+      assert(strcmp(last, ref->ref_name) < 0);
     -+    }
     -+    last = ref->ref_name;
     -+    ref->ref_name = NULL;
     -+    free(ref);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+
     -+  merged_iter_pqueue_clear(&pq);
     -+}
     -+
     -+void write_test_table(struct slice *buf, struct ref_record refs[], int n) {
     -+  int min = 0xffffffff;
     -+  int max = 0;
     -+  for (int i = 0; i < n; i++) {
     -+    uint64_t ui = refs[i].update_index;
     -+    if (ui > max) {
     -+      max = ui;
     -+    }
     -+    if (ui < min) {
     -+      min = ui;
     -+    }
     -+  }
     -+
     -+  struct write_options opts = {
     -+      .block_size = 256,
     -+  };
     -+
     -+  struct writer *w = new_writer(&slice_write_void, buf, &opts);
     -+  writer_set_limits(w, min, max);
     -+
     -+  for (int i = 0; i < n; i++) {
     -+    uint64_t before = refs[i].update_index;
     -+    int n = writer_add_ref(w, &refs[i]);
     -+    assert(n == 0);
     -+    assert(before == refs[i].update_index);
     -+  }
     -+
     -+  int err = writer_close(w);
     -+  assert(err == 0);
     -+
     -+  writer_free(w);
     -+  w = NULL;
     ++void test_pq(void)
     ++{
     ++	char *names[54] = {};
     ++	int N = ARRAYSIZE(names) - 1;
     ++
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		snprintf(name, sizeof(name), "%02d", i);
     ++		names[i] = strdup(name);
     ++	}
     ++
     ++	struct merged_iter_pqueue pq = {};
     ++
     ++	i = 1;
     ++	do {
     ++		struct record rec = new_record(BLOCK_TYPE_REF);
     ++		record_as_ref(rec)->ref_name = names[i];
     ++
     ++		struct pq_entry e = {
     ++			.rec = rec,
     ++		};
     ++		merged_iter_pqueue_add(&pq, e);
     ++		merged_iter_pqueue_check(pq);
     ++		i = (i * 7) % N;
     ++	} while (i != 1);
     ++
     ++	const char *last = NULL;
     ++	while (!merged_iter_pqueue_is_empty(pq)) {
     ++		struct pq_entry e = merged_iter_pqueue_remove(&pq);
     ++		merged_iter_pqueue_check(pq);
     ++		struct ref_record *ref = record_as_ref(e.rec);
     ++
     ++		if (last != NULL) {
     ++			assert(strcmp(last, ref->ref_name) < 0);
     ++		}
     ++		last = ref->ref_name;
     ++		ref->ref_name = NULL;
     ++		free(ref);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++
     ++	merged_iter_pqueue_clear(&pq);
     ++}
     ++
     ++void write_test_table(struct slice *buf, struct ref_record refs[], int n)
     ++{
     ++	int min = 0xffffffff;
     ++	int max = 0;
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		uint64_t ui = refs[i].update_index;
     ++		if (ui > max) {
     ++			max = ui;
     ++		}
     ++		if (ui < min) {
     ++			min = ui;
     ++		}
     ++	}
     ++
     ++	struct write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++
     ++	struct writer *w = new_writer(&slice_write_void, buf, &opts);
     ++	writer_set_limits(w, min, max);
     ++
     ++	for (i = 0; i < n; i++) {
     ++		uint64_t before = refs[i].update_index;
     ++		int n = writer_add_ref(w, &refs[i]);
     ++		assert(n == 0);
     ++		assert(before == refs[i].update_index);
     ++	}
     ++
     ++	int err = writer_close(w);
     ++	assert(err == 0);
     ++
     ++	writer_free(w);
     ++	w = NULL;
      +}
      +
      +static struct merged_table *merged_table_from_records(struct ref_record **refs,
     -+                                                      int *sizes,
     -+                                                      struct slice *buf,
     -+                                                      int n) {
     -+  struct block_source *source = calloc(n, sizeof(*source));
     -+  struct reader **rd = calloc(n, sizeof(*rd));
     -+  for (int i = 0; i < n; i++) {
     -+    write_test_table(&buf[i], refs[i], sizes[i]);
     -+    block_source_from_slice(&source[i], &buf[i]);
     -+
     -+    int err = new_reader(&rd[i], source[i], "name");
     -+    assert(err == 0);
     -+  }
     -+
     -+  struct merged_table *mt = NULL;
     -+  int err = new_merged_table(&mt, rd, n);
     -+  assert(err == 0);
     -+  return mt;
     -+}
     -+
     -+void test_merged_between(void) {
     -+  byte hash1[SHA1_SIZE];
     -+  byte hash2[SHA1_SIZE];
     -+
     -+  set_test_hash(hash1, 1);
     -+  set_test_hash(hash2, 2);
     -+  struct ref_record r1[] = {{
     -+      .ref_name = "b",
     -+      .update_index = 1,
     -+      .value = hash1,
     -+  }};
     -+  struct ref_record r2[] = {{
     -+      .ref_name = "a",
     -+      .update_index = 2,
     -+  }};
     -+
     -+  struct ref_record *refs[] = {r1, r2};
     -+  int sizes[] = {1, 1};
     -+  struct slice bufs[2] = {};
     -+  struct merged_table *mt = merged_table_from_records(refs, sizes, bufs, 2);
     -+
     -+  struct iterator it = {};
     -+  int err = merged_table_seek_ref(mt, &it, "a");
     -+  assert(err == 0);
     -+
     -+  struct ref_record ref = {};
     -+  err = iterator_next_ref(it, &ref);
     -+  assert_err(err);
     -+  assert(ref.update_index == 2);
     -+}
     -+
     -+void test_merged(void) {
     -+  byte hash1[SHA1_SIZE];
     -+  byte hash2[SHA1_SIZE];
     -+
     -+  set_test_hash(hash1, 1);
     -+  set_test_hash(hash2, 2);
     -+  struct ref_record r1[] = {{
     -+                                .ref_name = "a",
     -+                                .update_index = 1,
     -+                                .value = hash1,
     -+                            },
     -+                            {
     -+                                .ref_name = "b",
     -+                                .update_index = 1,
     -+                                .value = hash1,
     -+                            },
     -+                            {
     -+                                .ref_name = "c",
     -+                                .update_index = 1,
     -+                                .value = hash1,
     -+                            }};
     -+  struct ref_record r2[] = {{
     -+      .ref_name = "a",
     -+      .update_index = 2,
     -+  }};
     -+  struct ref_record r3[] = {
     -+      {
     -+          .ref_name = "c",
     -+          .update_index = 3,
     -+          .value = hash2,
     -+      },
     -+      {
     -+          .ref_name = "d",
     -+          .update_index = 3,
     -+          .value = hash1,
     -+      },
     -+  };
     -+
     -+  struct ref_record *refs[] = {r1, r2, r3};
     -+  int sizes[3] = {3, 1, 2};
     -+  struct slice bufs[3] = {};
     -+
     -+  struct merged_table *mt = merged_table_from_records(refs, sizes, bufs, 3);
     -+
     -+  struct iterator it = {};
     -+  int err = merged_table_seek_ref(mt, &it, "a");
     -+  assert(err == 0);
     -+
     -+  struct ref_record *out = NULL;
     -+  int len = 0;
     -+  int cap = 0;
     -+  while (len < 100) {  // cap loops/recursion.
     -+    struct ref_record ref = {};
     -+    int err = iterator_next_ref(it, &ref);
     -+    if (err > 0) {
     -+      break;
     -+    }
     -+    if (len == cap) {
     -+      cap = 2 * cap + 1;
     -+      out = realloc(out, sizeof(struct ref_record) * cap);
     -+    }
     -+    out[len++] = ref;
     -+  }
     -+  iterator_destroy(&it);
     -+
     -+  struct ref_record want[] = {
     -+      r2[0],
     -+      r1[1],
     -+      r3[0],
     -+      r3[1],
     -+  };
     -+  assert(ARRAYSIZE(want) == len);
     -+  for (int i = 0; i < len; i++) {
     -+    assert(ref_record_equal(&want[i], &out[i], SHA1_SIZE));
     -+  }
     -+  for (int i = 0; i < len; i++) {
     -+    ref_record_clear(&out[i]);
     -+  }
     -+  free(out);
     -+
     -+  for (int i = 0; i < 3; i++) {
     -+    free(slice_yield(&bufs[i]));
     -+  }
     -+  merged_table_close(mt);
     -+  merged_table_free(mt);
     -+}
     -+
     -+// XXX test refs_for(oid)
     -+
     -+int main() {
     -+  add_test_case("test_merged_between", &test_merged_between);
     -+  add_test_case("test_pq", &test_pq);
     -+  add_test_case("test_merged", &test_merged);
     -+  test_main();
     ++						      int *sizes,
     ++						      struct slice *buf, int n)
     ++{
     ++	struct block_source *source = calloc(n, sizeof(*source));
     ++	struct reader **rd = calloc(n, sizeof(*rd));
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		write_test_table(&buf[i], refs[i], sizes[i]);
     ++		block_source_from_slice(&source[i], &buf[i]);
     ++
     ++		int err = new_reader(&rd[i], source[i], "name");
     ++		assert(err == 0);
     ++	}
     ++
     ++	struct merged_table *mt = NULL;
     ++	int err = new_merged_table(&mt, rd, n);
     ++	assert(err == 0);
     ++	return mt;
     ++}
     ++
     ++void test_merged_between(void)
     ++{
     ++	byte hash1[SHA1_SIZE];
     ++	byte hash2[SHA1_SIZE];
     ++
     ++	set_test_hash(hash1, 1);
     ++	set_test_hash(hash2, 2);
     ++	struct ref_record r1[] = { {
     ++		.ref_name = "b",
     ++		.update_index = 1,
     ++		.value = hash1,
     ++	} };
     ++	struct ref_record r2[] = { {
     ++		.ref_name = "a",
     ++		.update_index = 2,
     ++	} };
     ++
     ++	struct ref_record *refs[] = { r1, r2 };
     ++	int sizes[] = { 1, 1 };
     ++	struct slice bufs[2] = {};
     ++	struct merged_table *mt =
     ++		merged_table_from_records(refs, sizes, bufs, 2);
     ++
     ++	struct iterator it = {};
     ++	int err = merged_table_seek_ref(mt, &it, "a");
     ++	assert(err == 0);
     ++
     ++	struct ref_record ref = {};
     ++	err = iterator_next_ref(it, &ref);
     ++	assert_err(err);
     ++	assert(ref.update_index == 2);
     ++}
     ++
     ++void test_merged(void)
     ++{
     ++	byte hash1[SHA1_SIZE];
     ++	byte hash2[SHA1_SIZE];
     ++
     ++	set_test_hash(hash1, 1);
     ++	set_test_hash(hash2, 2);
     ++	struct ref_record r1[] = { {
     ++					   .ref_name = "a",
     ++					   .update_index = 1,
     ++					   .value = hash1,
     ++				   },
     ++				   {
     ++					   .ref_name = "b",
     ++					   .update_index = 1,
     ++					   .value = hash1,
     ++				   },
     ++				   {
     ++					   .ref_name = "c",
     ++					   .update_index = 1,
     ++					   .value = hash1,
     ++				   } };
     ++	struct ref_record r2[] = { {
     ++		.ref_name = "a",
     ++		.update_index = 2,
     ++	} };
     ++	struct ref_record r3[] = {
     ++		{
     ++			.ref_name = "c",
     ++			.update_index = 3,
     ++			.value = hash2,
     ++		},
     ++		{
     ++			.ref_name = "d",
     ++			.update_index = 3,
     ++			.value = hash1,
     ++		},
     ++	};
     ++
     ++	struct ref_record *refs[] = { r1, r2, r3 };
     ++	int sizes[3] = { 3, 1, 2 };
     ++	struct slice bufs[3] = {};
     ++
     ++	struct merged_table *mt =
     ++		merged_table_from_records(refs, sizes, bufs, 3);
     ++
     ++	struct iterator it = {};
     ++	int err = merged_table_seek_ref(mt, &it, "a");
     ++	assert(err == 0);
     ++
     ++	struct ref_record *out = NULL;
     ++	int len = 0;
     ++	int cap = 0;
     ++	while (len < 100) { /* cap loops/recursion. */
     ++		struct ref_record ref = {};
     ++		int err = iterator_next_ref(it, &ref);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (len == cap) {
     ++			cap = 2 * cap + 1;
     ++			out = realloc(out, sizeof(struct ref_record) * cap);
     ++		}
     ++		out[len++] = ref;
     ++	}
     ++	iterator_destroy(&it);
     ++
     ++	struct ref_record want[] = {
     ++		r2[0],
     ++		r1[1],
     ++		r3[0],
     ++		r3[1],
     ++	};
     ++	assert(ARRAYSIZE(want) == len);
     ++	int i = 0;
     ++	for (i = 0; i < len; i++) {
     ++		assert(ref_record_equal(&want[i], &out[i], SHA1_SIZE));
     ++	}
     ++	for (i = 0; i < len; i++) {
     ++		ref_record_clear(&out[i]);
     ++	}
     ++	free(out);
     ++
     ++	for (i = 0; i < 3; i++) {
     ++		free(slice_yield(&bufs[i]));
     ++	}
     ++	merged_table_close(mt);
     ++	merged_table_free(mt);
     ++}
     ++
     ++/* XXX test refs_for(oid) */
     ++
     ++int main()
     ++{
     ++	add_test_case("test_merged_between", &test_merged_between);
     ++	add_test_case("test_pq", &test_pq);
     ++	add_test_case("test_merged", &test_merged);
     ++	test_main();
      +}
      
       diff --git a/reftable/pq.c b/reftable/pq.c
     @@ -2297,111 +2176,119 @@
      +
      +#include "pq.h"
      +
     -+#include <assert.h>
     -+#include <stdlib.h>
     ++#include "system.h"
      +
     -+int pq_less(struct pq_entry a, struct pq_entry b) {
     -+  struct slice ak = {};
     -+  struct slice bk = {};
     -+  int cmp = 0;
     -+  record_key(a.rec, &ak);
     -+  record_key(b.rec, &bk);
     ++int pq_less(struct pq_entry a, struct pq_entry b)
     ++{
     ++	struct slice ak = {};
     ++	struct slice bk = {};
     ++	int cmp = 0;
     ++	record_key(a.rec, &ak);
     ++	record_key(b.rec, &bk);
      +
     -+  cmp = slice_compare(ak, bk);
     ++	cmp = slice_compare(ak, bk);
     ++
     ++	free(slice_yield(&ak));
     ++	free(slice_yield(&bk));
      +
     -+  free(slice_yield(&ak));
     -+  free(slice_yield(&bk));
     ++	if (cmp == 0) {
     ++		return a.index > b.index;
     ++	}
      +
     -+  if (cmp == 0) {
     -+    return a.index > b.index;
     -+  }
     -+
     -+  return cmp < 0;
     ++	return cmp < 0;
      +}
      +
     -+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq) {
     -+  return pq.heap[0];
     ++struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
     ++{
     ++	return pq.heap[0];
      +}
      +
     -+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq) {
     -+  return pq.len == 0;
     ++bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
     ++{
     ++	return pq.len == 0;
      +}
      +
     -+void merged_iter_pqueue_check(struct merged_iter_pqueue pq) {
     -+  for (int i = 1; i < pq.len; i++) {
     -+    int parent = (i - 1) / 2;
     ++void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
     ++{
     ++	int i = 0;
     ++	for (i = 1; i < pq.len; i++) {
     ++		int parent = (i - 1) / 2;
      +
     -+    assert(pq_less(pq.heap[parent], pq.heap[i]));
     -+  }
     ++		assert(pq_less(pq.heap[parent], pq.heap[i]));
     ++	}
      +}
      +
     -+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq) {
     -+  int i = 0;
     -+  struct pq_entry e = pq->heap[0];
     -+  pq->heap[0] = pq->heap[pq->len - 1];
     -+  pq->len--;
     ++struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
     ++{
     ++	int i = 0;
     ++	struct pq_entry e = pq->heap[0];
     ++	pq->heap[0] = pq->heap[pq->len - 1];
     ++	pq->len--;
      +
     -+  i = 0;
     -+  while (i < pq->len) {
     -+    int min = i;
     -+    int j = 2 * i + 1;
     -+    int k = 2 * i + 2;
     -+    if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
     -+      min = j;
     -+    }
     -+    if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
     -+      min = k;
     -+    }
     ++	i = 0;
     ++	while (i < pq->len) {
     ++		int min = i;
     ++		int j = 2 * i + 1;
     ++		int k = 2 * i + 2;
     ++		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
     ++			min = j;
     ++		}
     ++		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
     ++			min = k;
     ++		}
      +
     -+    if (min == i) {
     -+      break;
     -+    }
     ++		if (min == i) {
     ++			break;
     ++		}
      +
     -+    {
     -+      struct pq_entry tmp = pq->heap[min];
     -+      pq->heap[min] = pq->heap[i];
     -+      pq->heap[i] = tmp;
     -+    }
     ++		{
     ++			struct pq_entry tmp = pq->heap[min];
     ++			pq->heap[min] = pq->heap[i];
     ++			pq->heap[i] = tmp;
     ++		}
      +
     -+    i = min;
     -+  }
     ++		i = min;
     ++	}
      +
     -+  return e;
     ++	return e;
      +}
      +
     -+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e) {
     -+  int i = 0;
     -+  if (pq->len == pq->cap) {
     -+    pq->cap = 2 * pq->cap + 1;
     -+    pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
     -+  }
     -+
     -+  pq->heap[pq->len++] = e;
     -+  i = pq->len - 1;
     -+  while (i > 0) {
     -+    int j = (i - 1) / 2;
     -+    if (pq_less(pq->heap[j], pq->heap[i])) {
     -+      break;
     -+    }
     ++void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
     ++{
     ++	int i = 0;
     ++	if (pq->len == pq->cap) {
     ++		pq->cap = 2 * pq->cap + 1;
     ++		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
     ++	}
      +
     -+    {
     -+      struct pq_entry tmp = pq->heap[j];
     -+      pq->heap[j] = pq->heap[i];
     -+      pq->heap[i] = tmp;
     -+    }
     ++	pq->heap[pq->len++] = e;
     ++	i = pq->len - 1;
     ++	while (i > 0) {
     ++		int j = (i - 1) / 2;
     ++		if (pq_less(pq->heap[j], pq->heap[i])) {
     ++			break;
     ++		}
      +
     -+    i = j;
     -+  }
     -+}
     ++		{
     ++			struct pq_entry tmp = pq->heap[j];
     ++			pq->heap[j] = pq->heap[i];
     ++			pq->heap[i] = tmp;
     ++		}
      +
     -+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq) {
     -+  for (int i = 0; i < pq->len; i++) {
     -+    record_clear(pq->heap[i].rec);
     -+    free(record_yield(&pq->heap[i].rec));
     -+  }
     -+  free(pq->heap);
     -+  pq->heap = NULL;
     -+  pq->len = pq->cap = 0;
     ++		i = j;
     ++	}
     ++}
     ++
     ++void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < pq->len; i++) {
     ++		record_clear(pq->heap[i].rec);
     ++		free(record_yield(&pq->heap[i].rec));
     ++	}
     ++	free(pq->heap);
     ++	pq->heap = NULL;
     ++	pq->len = pq->cap = 0;
      +}
      
       diff --git a/reftable/pq.h b/reftable/pq.h
     @@ -2423,16 +2310,16 @@
      +#include "record.h"
      +
      +struct pq_entry {
     -+  struct record rec;
     -+  int index;
     ++	struct record rec;
     ++	int index;
      +};
      +
      +int pq_less(struct pq_entry a, struct pq_entry b);
      +
      +struct merged_iter_pqueue {
     -+  struct pq_entry *heap;
     -+  int len;
     -+  int cap;
     ++	struct pq_entry *heap;
     ++	int len;
     ++	int cap;
      +};
      +
      +struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
     @@ -2459,10 +2346,7 @@
      +
      +#include "reader.h"
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <zlib.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "constants.h"
     @@ -2471,655 +2355,696 @@
      +#include "reftable.h"
      +#include "tree.h"
      +
     -+uint64_t block_source_size(struct block_source source) {
     -+  return source.ops->size(source.arg);
     ++uint64_t block_source_size(struct block_source source)
     ++{
     ++	return source.ops->size(source.arg);
      +}
      +
      +int block_source_read_block(struct block_source source, struct block *dest,
     -+                            uint64_t off, uint32_t size) {
     -+  int result = source.ops->read_block(source.arg, dest, off, size);
     -+  dest->source = source;
     -+  return result;
     ++			    uint64_t off, uint32_t size)
     ++{
     ++	int result = source.ops->read_block(source.arg, dest, off, size);
     ++	dest->source = source;
     ++	return result;
      +}
      +
     -+void block_source_return_block(struct block_source source,
     -+                               struct block *blockp) {
     -+  source.ops->return_block(source.arg, blockp);
     -+  blockp->data = NULL;
     -+  blockp->len = 0;
     -+  blockp->source.ops = NULL;
     -+  blockp->source.arg = NULL;
     ++void block_source_return_block(struct block_source source, struct block *blockp)
     ++{
     ++	source.ops->return_block(source.arg, blockp);
     ++	blockp->data = NULL;
     ++	blockp->len = 0;
     ++	blockp->source.ops = NULL;
     ++	blockp->source.arg = NULL;
      +}
      +
     -+void block_source_close(struct block_source *source) {
     -+  if (source->ops == NULL) {
     -+    return;
     -+  }
     ++void block_source_close(struct block_source *source)
     ++{
     ++	if (source->ops == NULL) {
     ++		return;
     ++	}
      +
     -+  source->ops->close(source->arg);
     -+  source->ops = NULL;
     ++	source->ops->close(source->arg);
     ++	source->ops = NULL;
      +}
      +
     -+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ) {
     -+  switch (typ) {
     -+    case BLOCK_TYPE_REF:
     -+      return &r->ref_offsets;
     -+    case BLOCK_TYPE_LOG:
     -+      return &r->log_offsets;
     -+    case BLOCK_TYPE_OBJ:
     -+      return &r->obj_offsets;
     -+  }
     -+  abort();
     ++static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
     ++{
     ++	switch (typ) {
     ++	case BLOCK_TYPE_REF:
     ++		return &r->ref_offsets;
     ++	case BLOCK_TYPE_LOG:
     ++		return &r->log_offsets;
     ++	case BLOCK_TYPE_OBJ:
     ++		return &r->obj_offsets;
     ++	}
     ++	abort();
      +}
      +
      +static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
     -+                            uint32_t sz) {
     -+  if (off >= r->size) {
     -+    return 0;
     -+  }
     -+
     -+  if (off + sz > r->size) {
     -+    sz = r->size - off;
     -+  }
     -+
     -+  return block_source_read_block(r->source, dest, off, sz);
     -+}
     -+
     -+void reader_return_block(struct reader *r, struct block *p) {
     -+  block_source_return_block(r->source, p);
     -+}
     -+
     -+const char *reader_name(struct reader *r) { return r->name; }
     -+
     -+static int parse_footer(struct reader *r, byte *footer, byte *header) {
     -+  byte *f = footer;
     -+  int err = 0;
     -+  if (memcmp(f, "REFT", 4)) {
     -+    err = FORMAT_ERROR;
     -+    goto exit;
     -+  }
     -+  f += 4;
     -+
     -+  if (0 != memcmp(footer, header, HEADER_SIZE)) {
     -+    err = FORMAT_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  {
     -+    byte version = *f++;
     -+    if (version != 1) {
     -+      err = FORMAT_ERROR;
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  r->block_size = get_u24(f);
     -+
     -+  f += 3;
     -+  r->min_update_index = get_u64(f);
     -+  f += 8;
     -+  r->max_update_index = get_u64(f);
     -+  f += 8;
     -+
     -+  r->ref_offsets.index_offset = get_u64(f);
     -+  f += 8;
     -+
     -+  r->obj_offsets.offset = get_u64(f);
     -+  f += 8;
     -+
     -+  r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
     -+  r->obj_offsets.offset >>= 5;
     -+
     -+  r->obj_offsets.index_offset = get_u64(f);
     -+  f += 8;
     -+  r->log_offsets.offset = get_u64(f);
     -+  f += 8;
     -+  r->log_offsets.index_offset = get_u64(f);
     -+  f += 8;
     -+
     -+  {
     -+    uint32_t computed_crc = crc32(0, footer, f - footer);
     -+    uint32_t file_crc = get_u32(f);
     -+    f += 4;
     -+    if (computed_crc != file_crc) {
     -+      err = FORMAT_ERROR;
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  {
     -+    byte first_block_typ = header[HEADER_SIZE];
     -+    r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
     -+    r->ref_offsets.offset = 0;
     -+    r->log_offsets.present =
     -+        (first_block_typ == BLOCK_TYPE_LOG || r->log_offsets.offset > 0);
     -+    r->obj_offsets.present = r->obj_offsets.offset > 0;
     -+  }
     -+  err = 0;
     ++			    uint32_t sz)
     ++{
     ++	if (off >= r->size) {
     ++		return 0;
     ++	}
     ++
     ++	if (off + sz > r->size) {
     ++		sz = r->size - off;
     ++	}
     ++
     ++	return block_source_read_block(r->source, dest, off, sz);
     ++}
     ++
     ++void reader_return_block(struct reader *r, struct block *p)
     ++{
     ++	block_source_return_block(r->source, p);
     ++}
     ++
     ++const char *reader_name(struct reader *r)
     ++{
     ++	return r->name;
     ++}
     ++
     ++static int parse_footer(struct reader *r, byte *footer, byte *header)
     ++{
     ++	byte *f = footer;
     ++	int err = 0;
     ++	if (memcmp(f, "REFT", 4)) {
     ++		err = FORMAT_ERROR;
     ++		goto exit;
     ++	}
     ++	f += 4;
     ++
     ++	if (memcmp(footer, header, HEADER_SIZE)) {
     ++		err = FORMAT_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	{
     ++		byte version = *f++;
     ++		if (version != 1) {
     ++			err = FORMAT_ERROR;
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	r->block_size = get_u24(f);
     ++
     ++	f += 3;
     ++	r->min_update_index = get_u64(f);
     ++	f += 8;
     ++	r->max_update_index = get_u64(f);
     ++	f += 8;
     ++
     ++	r->ref_offsets.index_offset = get_u64(f);
     ++	f += 8;
     ++
     ++	r->obj_offsets.offset = get_u64(f);
     ++	f += 8;
     ++
     ++	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
     ++	r->obj_offsets.offset >>= 5;
     ++
     ++	r->obj_offsets.index_offset = get_u64(f);
     ++	f += 8;
     ++	r->log_offsets.offset = get_u64(f);
     ++	f += 8;
     ++	r->log_offsets.index_offset = get_u64(f);
     ++	f += 8;
     ++
     ++	{
     ++		uint32_t computed_crc = crc32(0, footer, f - footer);
     ++		uint32_t file_crc = get_u32(f);
     ++		f += 4;
     ++		if (computed_crc != file_crc) {
     ++			err = FORMAT_ERROR;
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	{
     ++		byte first_block_typ = header[HEADER_SIZE];
     ++		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
     ++		r->ref_offsets.offset = 0;
     ++		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
     ++					  r->log_offsets.offset > 0);
     ++		r->obj_offsets.present = r->obj_offsets.offset > 0;
     ++	}
     ++	err = 0;
      +exit:
     -+  return err;
     -+}
     -+
     -+int init_reader(struct reader *r, struct block_source source,
     -+                const char *name) {
     -+  struct block footer = {};
     -+  struct block header = {};
     -+  int err = 0;
     -+
     -+  memset(r, 0, sizeof(struct reader));
     -+  r->size = block_source_size(source) - FOOTER_SIZE;
     -+  r->source = source;
     -+  r->name = strdup(name);
     -+  r->hash_size = SHA1_SIZE;
     -+
     -+  err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
     -+  if (err != FOOTER_SIZE) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  // Need +1 to read type of first block.
     -+  err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
     -+  if (err != HEADER_SIZE + 1) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = parse_footer(r, footer.data, header.data);
     ++	return err;
     ++}
     ++
     ++int init_reader(struct reader *r, struct block_source source, const char *name)
     ++{
     ++	struct block footer = {};
     ++	struct block header = {};
     ++	int err = 0;
     ++
     ++	memset(r, 0, sizeof(struct reader));
     ++	r->size = block_source_size(source) - FOOTER_SIZE;
     ++	r->source = source;
     ++	r->name = strdup(name);
     ++	r->hash_size = SHA1_SIZE;
     ++
     ++	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
     ++	if (err != FOOTER_SIZE) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	/* Need +1 to read type of first block. */
     ++	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
     ++	if (err != HEADER_SIZE + 1) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = parse_footer(r, footer.data, header.data);
      +exit:
     -+  block_source_return_block(r->source, &footer);
     -+  block_source_return_block(r->source, &header);
     -+  return err;
     ++	block_source_return_block(r->source, &footer);
     ++	block_source_return_block(r->source, &header);
     ++	return err;
      +}
      +
      +struct table_iter {
     -+  struct reader *r;
     -+  byte typ;
     -+  uint64_t block_off;
     -+  struct block_iter bi;
     -+  bool finished;
     ++	struct reader *r;
     ++	byte typ;
     ++	uint64_t block_off;
     ++	struct block_iter bi;
     ++	bool finished;
      +};
      +
      +static void table_iter_copy_from(struct table_iter *dest,
     -+                                 struct table_iter *src) {
     -+  dest->r = src->r;
     -+  dest->typ = src->typ;
     -+  dest->block_off = src->block_off;
     -+  dest->finished = src->finished;
     -+  block_iter_copy_from(&dest->bi, &src->bi);
     ++				 struct table_iter *src)
     ++{
     ++	dest->r = src->r;
     ++	dest->typ = src->typ;
     ++	dest->block_off = src->block_off;
     ++	dest->finished = src->finished;
     ++	block_iter_copy_from(&dest->bi, &src->bi);
      +}
      +
     -+static int table_iter_next_in_block(struct table_iter *ti, struct record rec) {
     -+  int res = block_iter_next(&ti->bi, rec);
     -+  if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
     -+    ((struct ref_record *)rec.data)->update_index += ti->r->min_update_index;
     -+  }
     ++static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
     ++{
     ++	int res = block_iter_next(&ti->bi, rec);
     ++	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
     ++		((struct ref_record *)rec.data)->update_index +=
     ++			ti->r->min_update_index;
     ++	}
      +
     -+  return res;
     ++	return res;
      +}
      +
     -+static void table_iter_block_done(struct table_iter *ti) {
     -+  if (ti->bi.br == NULL) {
     -+    return;
     -+  }
     -+  reader_return_block(ti->r, &ti->bi.br->block);
     -+  free(ti->bi.br);
     -+  ti->bi.br = NULL;
     ++static void table_iter_block_done(struct table_iter *ti)
     ++{
     ++	if (ti->bi.br == NULL) {
     ++		return;
     ++	}
     ++	reader_return_block(ti->r, &ti->bi.br->block);
     ++	free(ti->bi.br);
     ++	ti->bi.br = NULL;
      +
     -+  ti->bi.last_key.len = 0;
     -+  ti->bi.next_off = 0;
     ++	ti->bi.last_key.len = 0;
     ++	ti->bi.next_off = 0;
      +}
      +
     -+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off) {
     -+  int32_t result = 0;
     ++static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
     ++{
     ++	int32_t result = 0;
      +
     -+  if (off == 0) {
     -+    data += 24;
     -+  }
     ++	if (off == 0) {
     ++		data += 24;
     ++	}
      +
     -+  *typ = data[0];
     -+  if (is_block_type(*typ)) {
     -+    result = get_u24(data + 1);
     -+  }
     -+  return result;
     ++	*typ = data[0];
     ++	if (is_block_type(*typ)) {
     ++		result = get_u24(data + 1);
     ++	}
     ++	return result;
      +}
      +
      +int reader_init_block_reader(struct reader *r, struct block_reader *br,
     -+                             uint64_t next_off, byte want_typ) {
     -+  int32_t guess_block_size = r->block_size ? r->block_size : DEFAULT_BLOCK_SIZE;
     -+  struct block block = {};
     -+  byte block_typ = 0;
     -+  int err = 0;
     -+  uint32_t header_off = next_off ? 0 : HEADER_SIZE;
     -+  int32_t block_size = 0;
     -+
     -+  if (next_off >= r->size) {
     -+    return 1;
     -+  }
     -+
     -+  err = reader_get_block(r, &block, next_off, guess_block_size);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  block_size = extract_block_size(block.data, &block_typ, next_off);
     -+  if (block_size < 0) {
     -+    return block_size;
     -+  }
     -+
     -+  if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
     -+    reader_return_block(r, &block);
     -+    return 1;
     -+  }
     -+
     -+  if (block_size > guess_block_size) {
     -+    reader_return_block(r, &block);
     -+    err = reader_get_block(r, &block, next_off, block_size);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+  }
     -+
     -+  return block_reader_init(br, &block, header_off, r->block_size, r->hash_size);
     ++			     uint64_t next_off, byte want_typ)
     ++{
     ++	int32_t guess_block_size = r->block_size ? r->block_size :
     ++						   DEFAULT_BLOCK_SIZE;
     ++	struct block block = {};
     ++	byte block_typ = 0;
     ++	int err = 0;
     ++	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
     ++	int32_t block_size = 0;
     ++
     ++	if (next_off >= r->size) {
     ++		return 1;
     ++	}
     ++
     ++	err = reader_get_block(r, &block, next_off, guess_block_size);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	block_size = extract_block_size(block.data, &block_typ, next_off);
     ++	if (block_size < 0) {
     ++		return block_size;
     ++	}
     ++
     ++	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
     ++		reader_return_block(r, &block);
     ++		return 1;
     ++	}
     ++
     ++	if (block_size > guess_block_size) {
     ++		reader_return_block(r, &block);
     ++		err = reader_get_block(r, &block, next_off, block_size);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	return block_reader_init(br, &block, header_off, r->block_size,
     ++				 r->hash_size);
      +}
      +
      +static int table_iter_next_block(struct table_iter *dest,
     -+                                 struct table_iter *src) {
     -+  uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
     -+  struct block_reader br = {};
     -+  int err = 0;
     -+
     -+  dest->r = src->r;
     -+  dest->typ = src->typ;
     -+  dest->block_off = next_block_off;
     -+
     -+  err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
     -+  if (err > 0) {
     -+    dest->finished = true;
     -+    return 1;
     -+  }
     -+  if (err != 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct block_reader *brp = malloc(sizeof(struct block_reader));
     -+    *brp = br;
     -+
     -+    dest->finished = false;
     -+    block_reader_start(brp, &dest->bi);
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int table_iter_next(struct table_iter *ti, struct record rec) {
     -+  if (record_type(rec) != ti->typ) {
     -+    return API_ERROR;
     -+  }
     -+
     -+  while (true) {
     -+    struct table_iter next = {};
     -+    int err = 0;
     -+    if (ti->finished) {
     -+      return 1;
     -+    }
     -+
     -+    err = table_iter_next_in_block(ti, rec);
     -+    if (err <= 0) {
     -+      return err;
     -+    }
     -+
     -+    err = table_iter_next_block(&next, ti);
     -+    if (err != 0) {
     -+      ti->finished = true;
     -+    }
     -+    table_iter_block_done(ti);
     -+    if (err != 0) {
     -+      return err;
     -+    }
     -+    table_iter_copy_from(ti, &next);
     -+    block_iter_close(&next.bi);
     -+  }
     -+}
     -+
     -+static int table_iter_next_void(void *ti, struct record rec) {
     -+  return table_iter_next((struct table_iter *)ti, rec);
     -+}
     -+
     -+static void table_iter_close(void *p) {
     -+  struct table_iter *ti = (struct table_iter *)p;
     -+  table_iter_block_done(ti);
     -+  block_iter_close(&ti->bi);
     ++				 struct table_iter *src)
     ++{
     ++	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
     ++	struct block_reader br = {};
     ++	int err = 0;
     ++
     ++	dest->r = src->r;
     ++	dest->typ = src->typ;
     ++	dest->block_off = next_block_off;
     ++
     ++	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
     ++	if (err > 0) {
     ++		dest->finished = true;
     ++		return 1;
     ++	}
     ++	if (err != 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct block_reader *brp = malloc(sizeof(struct block_reader));
     ++		*brp = br;
     ++
     ++		dest->finished = false;
     ++		block_reader_start(brp, &dest->bi);
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int table_iter_next(struct table_iter *ti, struct record rec)
     ++{
     ++	if (record_type(rec) != ti->typ) {
     ++		return API_ERROR;
     ++	}
     ++
     ++	while (true) {
     ++		struct table_iter next = {};
     ++		int err = 0;
     ++		if (ti->finished) {
     ++			return 1;
     ++		}
     ++
     ++		err = table_iter_next_in_block(ti, rec);
     ++		if (err <= 0) {
     ++			return err;
     ++		}
     ++
     ++		err = table_iter_next_block(&next, ti);
     ++		if (err != 0) {
     ++			ti->finished = true;
     ++		}
     ++		table_iter_block_done(ti);
     ++		if (err != 0) {
     ++			return err;
     ++		}
     ++		table_iter_copy_from(ti, &next);
     ++		block_iter_close(&next.bi);
     ++	}
     ++}
     ++
     ++static int table_iter_next_void(void *ti, struct record rec)
     ++{
     ++	return table_iter_next((struct table_iter *)ti, rec);
     ++}
     ++
     ++static void table_iter_close(void *p)
     ++{
     ++	struct table_iter *ti = (struct table_iter *)p;
     ++	table_iter_block_done(ti);
     ++	block_iter_close(&ti->bi);
      +}
      +
      +struct iterator_vtable table_iter_vtable = {
     -+    .next = &table_iter_next_void,
     -+    .close = &table_iter_close,
     ++	.next = &table_iter_next_void,
     ++	.close = &table_iter_close,
      +};
      +
     -+static void iterator_from_table_iter(struct iterator *it,
     -+                                     struct table_iter *ti) {
     -+  it->iter_arg = ti;
     -+  it->ops = &table_iter_vtable;
     ++static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
     ++{
     ++	it->iter_arg = ti;
     ++	it->ops = &table_iter_vtable;
      +}
      +
      +static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
     -+                                uint64_t off, byte typ) {
     -+  struct block_reader br = {};
     -+  struct block_reader *brp = NULL;
     ++				uint64_t off, byte typ)
     ++{
     ++	struct block_reader br = {};
     ++	struct block_reader *brp = NULL;
      +
     -+  int err = reader_init_block_reader(r, &br, off, typ);
     -+  if (err != 0) {
     -+    return err;
     -+  }
     ++	int err = reader_init_block_reader(r, &br, off, typ);
     ++	if (err != 0) {
     ++		return err;
     ++	}
      +
     -+  brp = malloc(sizeof(struct block_reader));
     -+  *brp = br;
     -+  ti->r = r;
     -+  ti->typ = block_reader_type(brp);
     -+  ti->block_off = off;
     -+  block_reader_start(brp, &ti->bi);
     -+  return 0;
     ++	brp = malloc(sizeof(struct block_reader));
     ++	*brp = br;
     ++	ti->r = r;
     ++	ti->typ = block_reader_type(brp);
     ++	ti->block_off = off;
     ++	block_reader_start(brp, &ti->bi);
     ++	return 0;
      +}
      +
      +static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
     -+                        bool index) {
     -+  struct reader_offsets *offs = reader_offsets_for(r, typ);
     -+  uint64_t off = offs->offset;
     -+  if (index) {
     -+    off = offs->index_offset;
     -+    if (off == 0) {
     -+      return 1;
     -+    }
     -+    typ = BLOCK_TYPE_INDEX;
     -+  }
     ++			bool index)
     ++{
     ++	struct reader_offsets *offs = reader_offsets_for(r, typ);
     ++	uint64_t off = offs->offset;
     ++	if (index) {
     ++		off = offs->index_offset;
     ++		if (off == 0) {
     ++			return 1;
     ++		}
     ++		typ = BLOCK_TYPE_INDEX;
     ++	}
      +
     -+  return reader_table_iter_at(r, ti, off, typ);
     ++	return reader_table_iter_at(r, ti, off, typ);
      +}
      +
      +static int reader_seek_linear(struct reader *r, struct table_iter *ti,
     -+                              struct record want) {
     -+  struct record rec = new_record(record_type(want));
     -+  struct slice want_key = {};
     -+  struct slice got_key = {};
     -+  struct table_iter next = {};
     -+  int err = -1;
     -+  record_key(want, &want_key);
     -+
     -+  while (true) {
     -+    err = table_iter_next_block(&next, ti);
     -+    if (err < 0) {
     -+      goto exit;
     -+    }
     -+
     -+    if (err > 0) {
     -+      break;
     -+    }
     -+
     -+    err = block_reader_first_key(next.bi.br, &got_key);
     -+    if (err < 0) {
     -+      goto exit;
     -+    }
     -+    {
     -+      int cmp = slice_compare(got_key, want_key);
     -+      if (cmp > 0) {
     -+        table_iter_block_done(&next);
     -+        break;
     -+      }
     -+    }
     -+
     -+    table_iter_block_done(ti);
     -+    table_iter_copy_from(ti, &next);
     -+  }
     -+
     -+  err = block_iter_seek(&ti->bi, want_key);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  err = 0;
     ++			      struct record want)
     ++{
     ++	struct record rec = new_record(record_type(want));
     ++	struct slice want_key = {};
     ++	struct slice got_key = {};
     ++	struct table_iter next = {};
     ++	int err = -1;
     ++	record_key(want, &want_key);
     ++
     ++	while (true) {
     ++		err = table_iter_next_block(&next, ti);
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
     ++
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++
     ++		err = block_reader_first_key(next.bi.br, &got_key);
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
     ++		{
     ++			int cmp = slice_compare(got_key, want_key);
     ++			if (cmp > 0) {
     ++				table_iter_block_done(&next);
     ++				break;
     ++			}
     ++		}
     ++
     ++		table_iter_block_done(ti);
     ++		table_iter_copy_from(ti, &next);
     ++	}
     ++
     ++	err = block_iter_seek(&ti->bi, want_key);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	err = 0;
      +
      +exit:
     -+  block_iter_close(&next.bi);
     -+  record_clear(rec);
     -+  free(record_yield(&rec));
     -+  free(slice_yield(&want_key));
     -+  free(slice_yield(&got_key));
     -+  return err;
     ++	block_iter_close(&next.bi);
     ++	record_clear(rec);
     ++	free(record_yield(&rec));
     ++	free(slice_yield(&want_key));
     ++	free(slice_yield(&got_key));
     ++	return err;
      +}
      +
      +static int reader_seek_indexed(struct reader *r, struct iterator *it,
     -+                               struct record rec) {
     -+  struct index_record want_index = {};
     -+  struct record want_index_rec = {};
     -+  struct index_record index_result = {};
     -+  struct record index_result_rec = {};
     -+  struct table_iter index_iter = {};
     -+  struct table_iter next = {};
     -+  int err = 0;
     -+
     -+  record_key(rec, &want_index.last_key);
     -+  record_from_index(&want_index_rec, &want_index);
     -+  record_from_index(&index_result_rec, &index_result);
     -+
     -+  err = reader_start(r, &index_iter, record_type(rec), true);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  err = reader_seek_linear(r, &index_iter, want_index_rec);
     -+  while (true) {
     -+    err = table_iter_next(&index_iter, index_result_rec);
     -+    table_iter_block_done(&index_iter);
     -+    if (err != 0) {
     -+      goto exit;
     -+    }
     -+
     -+    err = reader_table_iter_at(r, &next, index_result.offset, 0);
     -+    if (err != 0) {
     -+      goto exit;
     -+    }
     -+
     -+    err = block_iter_seek(&next.bi, want_index.last_key);
     -+    if (err < 0) {
     -+      goto exit;
     -+    }
     -+
     -+    if (next.typ == record_type(rec)) {
     -+      err = 0;
     -+      break;
     -+    }
     -+
     -+    if (next.typ != BLOCK_TYPE_INDEX) {
     -+      err = FORMAT_ERROR;
     -+      break;
     -+    }
     -+
     -+    table_iter_copy_from(&index_iter, &next);
     -+  }
     -+
     -+  if (err == 0) {
     -+    struct table_iter *malloced = calloc(sizeof(struct table_iter), 1);
     -+    table_iter_copy_from(malloced, &next);
     -+    iterator_from_table_iter(it, malloced);
     -+  }
     ++			       struct record rec)
     ++{
     ++	struct index_record want_index = {};
     ++	struct record want_index_rec = {};
     ++	struct index_record index_result = {};
     ++	struct record index_result_rec = {};
     ++	struct table_iter index_iter = {};
     ++	struct table_iter next = {};
     ++	int err = 0;
     ++
     ++	record_key(rec, &want_index.last_key);
     ++	record_from_index(&want_index_rec, &want_index);
     ++	record_from_index(&index_result_rec, &index_result);
     ++
     ++	err = reader_start(r, &index_iter, record_type(rec), true);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	err = reader_seek_linear(r, &index_iter, want_index_rec);
     ++	while (true) {
     ++		err = table_iter_next(&index_iter, index_result_rec);
     ++		table_iter_block_done(&index_iter);
     ++		if (err != 0) {
     ++			goto exit;
     ++		}
     ++
     ++		err = reader_table_iter_at(r, &next, index_result.offset, 0);
     ++		if (err != 0) {
     ++			goto exit;
     ++		}
     ++
     ++		err = block_iter_seek(&next.bi, want_index.last_key);
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
     ++
     ++		if (next.typ == record_type(rec)) {
     ++			err = 0;
     ++			break;
     ++		}
     ++
     ++		if (next.typ != BLOCK_TYPE_INDEX) {
     ++			err = FORMAT_ERROR;
     ++			break;
     ++		}
     ++
     ++		table_iter_copy_from(&index_iter, &next);
     ++	}
     ++
     ++	if (err == 0) {
     ++		struct table_iter *malloced =
     ++			calloc(sizeof(struct table_iter), 1);
     ++		table_iter_copy_from(malloced, &next);
     ++		iterator_from_table_iter(it, malloced);
     ++	}
      +exit:
     -+  block_iter_close(&next.bi);
     -+  table_iter_close(&index_iter);
     -+  record_clear(want_index_rec);
     -+  record_clear(index_result_rec);
     -+  return err;
     ++	block_iter_close(&next.bi);
     ++	table_iter_close(&index_iter);
     ++	record_clear(want_index_rec);
     ++	record_clear(index_result_rec);
     ++	return err;
      +}
      +
      +static int reader_seek_internal(struct reader *r, struct iterator *it,
     -+                                struct record rec) {
     -+  struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
     -+  uint64_t idx = offs->index_offset;
     -+  struct table_iter ti = {};
     -+  int err = 0;
     -+  if (idx > 0) {
     -+    return reader_seek_indexed(r, it, rec);
     -+  }
     -+
     -+  err = reader_start(r, &ti, record_type(rec), false);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+  err = reader_seek_linear(r, &ti, rec);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct table_iter *p = malloc(sizeof(struct table_iter));
     -+    *p = ti;
     -+    iterator_from_table_iter(it, p);
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+int reader_seek(struct reader *r, struct iterator *it, struct record rec) {
     -+  byte typ = record_type(rec);
     -+
     -+  struct reader_offsets *offs = reader_offsets_for(r, typ);
     -+  if (!offs->present) {
     -+    iterator_set_empty(it);
     -+    return 0;
     -+  }
     -+
     -+  return reader_seek_internal(r, it, rec);
     -+}
     -+
     -+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name) {
     -+  struct ref_record ref = {
     -+      .ref_name = (char *)name,
     -+  };
     -+  struct record rec = {};
     -+  record_from_ref(&rec, &ref);
     -+  return reader_seek(r, it, rec);
     ++				struct record rec)
     ++{
     ++	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
     ++	uint64_t idx = offs->index_offset;
     ++	struct table_iter ti = {};
     ++	int err = 0;
     ++	if (idx > 0) {
     ++		return reader_seek_indexed(r, it, rec);
     ++	}
     ++
     ++	err = reader_start(r, &ti, record_type(rec), false);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++	err = reader_seek_linear(r, &ti, rec);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct table_iter *p = malloc(sizeof(struct table_iter));
     ++		*p = ti;
     ++		iterator_from_table_iter(it, p);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++int reader_seek(struct reader *r, struct iterator *it, struct record rec)
     ++{
     ++	byte typ = record_type(rec);
     ++
     ++	struct reader_offsets *offs = reader_offsets_for(r, typ);
     ++	if (!offs->present) {
     ++		iterator_set_empty(it);
     ++		return 0;
     ++	}
     ++
     ++	return reader_seek_internal(r, it, rec);
     ++}
     ++
     ++int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
     ++{
     ++	struct ref_record ref = {
     ++		.ref_name = (char *)name,
     ++	};
     ++	struct record rec = {};
     ++	record_from_ref(&rec, &ref);
     ++	return reader_seek(r, it, rec);
      +}
      +
      +int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
     -+                    uint64_t update_index) {
     -+  struct log_record log = {
     -+      .ref_name = (char *)name,
     -+      .update_index = update_index,
     -+  };
     -+  struct record rec = {};
     -+  record_from_log(&rec, &log);
     -+  return reader_seek(r, it, rec);
     ++		       uint64_t update_index)
     ++{
     ++	struct log_record log = {
     ++		.ref_name = (char *)name,
     ++		.update_index = update_index,
     ++	};
     ++	struct record rec = {};
     ++	record_from_log(&rec, &log);
     ++	return reader_seek(r, it, rec);
      +}
      +
     -+int reader_seek_log(struct reader *r, struct iterator *it, const char *name) {
     -+  uint64_t max = ~((uint64_t)0);
     -+  return reader_seek_log_at(r, it, name, max);
     ++int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
     ++{
     ++	uint64_t max = ~((uint64_t)0);
     ++	return reader_seek_log_at(r, it, name, max);
      +}
      +
     -+void reader_close(struct reader *r) {
     -+  block_source_close(&r->source);
     -+  free(r->name);
     -+  r->name = NULL;
     ++void reader_close(struct reader *r)
     ++{
     ++	block_source_close(&r->source);
     ++	free(r->name);
     ++	r->name = NULL;
      +}
      +
     -+int new_reader(struct reader **p, struct block_source src, char const *name) {
     -+  struct reader *rd = calloc(sizeof(struct reader), 1);
     -+  int err = init_reader(rd, src, name);
     -+  if (err == 0) {
     -+    *p = rd;
     -+  } else {
     -+    free(rd);
     -+  }
     -+  return err;
     ++int new_reader(struct reader **p, struct block_source src, char const *name)
     ++{
     ++	struct reader *rd = calloc(sizeof(struct reader), 1);
     ++	int err = init_reader(rd, src, name);
     ++	if (err == 0) {
     ++		*p = rd;
     ++	} else {
     ++		free(rd);
     ++	}
     ++	return err;
      +}
      +
     -+void reader_free(struct reader *r) {
     -+  reader_close(r);
     -+  free(r);
     ++void reader_free(struct reader *r)
     ++{
     ++	reader_close(r);
     ++	free(r);
      +}
      +
      +static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
     -+                                   byte *oid) {
     -+  struct obj_record want = {
     -+      .hash_prefix = oid,
     -+      .hash_prefix_len = r->object_id_len,
     -+  };
     -+  struct record want_rec = {};
     -+  struct iterator oit = {};
     -+  struct obj_record got = {};
     -+  struct record got_rec = {};
     -+  int err = 0;
     -+
     -+  record_from_obj(&want_rec, &want);
     -+
     -+  err = reader_seek(r, &oit, want_rec);
     -+  if (err != 0) {
     -+    return err;
     -+  }
     -+
     -+  record_from_obj(&got_rec, &got);
     -+  err = iterator_next(oit, got_rec);
     -+  iterator_destroy(&oit);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  if (err > 0 || memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
     -+    iterator_set_empty(it);
     -+    return 0;
     -+  }
     -+
     -+  {
     -+    struct indexed_table_ref_iter *itr = NULL;
     -+    err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size, got.offsets,
     -+                                     got.offset_len);
     -+    if (err < 0) {
     -+      record_clear(got_rec);
     -+      return err;
     -+    }
     -+    got.offsets = NULL;
     -+    record_clear(got_rec);
     -+
     -+    iterator_from_indexed_table_ref_iter(it, itr);
     -+  }
     -+
     -+  return 0;
     ++				   byte *oid)
     ++{
     ++	struct obj_record want = {
     ++		.hash_prefix = oid,
     ++		.hash_prefix_len = r->object_id_len,
     ++	};
     ++	struct record want_rec = {};
     ++	struct iterator oit = {};
     ++	struct obj_record got = {};
     ++	struct record got_rec = {};
     ++	int err = 0;
     ++
     ++	record_from_obj(&want_rec, &want);
     ++
     ++	err = reader_seek(r, &oit, want_rec);
     ++	if (err != 0) {
     ++		return err;
     ++	}
     ++
     ++	record_from_obj(&got_rec, &got);
     ++	err = iterator_next(oit, got_rec);
     ++	iterator_destroy(&oit);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	if (err > 0 ||
     ++	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
     ++		iterator_set_empty(it);
     ++		return 0;
     ++	}
     ++
     ++	{
     ++		struct indexed_table_ref_iter *itr = NULL;
     ++		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
     ++						 got.offsets, got.offset_len);
     ++		if (err < 0) {
     ++			record_clear(got_rec);
     ++			return err;
     ++		}
     ++		got.offsets = NULL;
     ++		record_clear(got_rec);
     ++
     ++		iterator_from_indexed_table_ref_iter(it, itr);
     ++	}
     ++
     ++	return 0;
      +}
      +
      +static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
     -+                                     byte *oid, int oid_len) {
     -+  struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
     -+  struct filtering_ref_iterator *filter = NULL;
     -+  int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
     -+  if (err < 0) {
     -+    free(ti);
     -+    return err;
     -+  }
     -+
     -+  filter = calloc(sizeof(struct filtering_ref_iterator), 1);
     -+  slice_resize(&filter->oid, oid_len);
     -+  memcpy(filter->oid.buf, oid, oid_len);
     -+  filter->r = r;
     -+  filter->double_check = false;
     -+  iterator_from_table_iter(&filter->it, ti);
     -+
     -+  iterator_from_filtering_ref_iterator(it, filter);
     -+  return 0;
     ++				     byte *oid, int oid_len)
     ++{
     ++	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
     ++	struct filtering_ref_iterator *filter = NULL;
     ++	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
     ++	if (err < 0) {
     ++		free(ti);
     ++		return err;
     ++	}
     ++
     ++	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
     ++	slice_resize(&filter->oid, oid_len);
     ++	memcpy(filter->oid.buf, oid, oid_len);
     ++	filter->r = r;
     ++	filter->double_check = false;
     ++	iterator_from_table_iter(&filter->it, ti);
     ++
     ++	iterator_from_filtering_ref_iterator(it, filter);
     ++	return 0;
      +}
      +
      +int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
     -+                    int oid_len) {
     -+  if (r->obj_offsets.present) {
     -+    return reader_refs_for_indexed(r, it, oid);
     -+  }
     -+  return reader_refs_for_unindexed(r, it, oid, oid_len);
     ++		    int oid_len)
     ++{
     ++	if (r->obj_offsets.present) {
     ++		return reader_refs_for_indexed(r, it, oid);
     ++	}
     ++	return reader_refs_for_unindexed(r, it, oid, oid_len);
      +}
      +
     -+uint64_t reader_max_update_index(struct reader *r) {
     -+  return r->max_update_index;
     ++uint64_t reader_max_update_index(struct reader *r)
     ++{
     ++	return r->max_update_index;
      +}
      +
     -+uint64_t reader_min_update_index(struct reader *r) {
     -+  return r->min_update_index;
     ++uint64_t reader_min_update_index(struct reader *r)
     ++{
     ++	return r->min_update_index;
      +}
      
       diff --git a/reftable/reader.h b/reftable/reader.h
     @@ -3145,29 +3070,29 @@
      +uint64_t block_source_size(struct block_source source);
      +
      +int block_source_read_block(struct block_source source, struct block *dest,
     -+                            uint64_t off, uint32_t size);
     ++			    uint64_t off, uint32_t size);
      +void block_source_return_block(struct block_source source, struct block *ret);
      +void block_source_close(struct block_source *source);
      +
      +struct reader_offsets {
     -+  bool present;
     -+  uint64_t offset;
     -+  uint64_t index_offset;
     ++	bool present;
     ++	uint64_t offset;
     ++	uint64_t index_offset;
      +};
      +
      +struct reader {
     -+  struct block_source source;
     -+  char *name;
     -+  int hash_size;
     -+  uint64_t size;
     -+  uint32_t block_size;
     -+  uint64_t min_update_index;
     -+  uint64_t max_update_index;
     -+  int object_id_len;
     -+
     -+  struct reader_offsets ref_offsets;
     -+  struct reader_offsets obj_offsets;
     -+  struct reader_offsets log_offsets;
     ++	struct block_source source;
     ++	char *name;
     ++	int hash_size;
     ++	uint64_t size;
     ++	uint32_t block_size;
     ++	uint64_t min_update_index;
     ++	uint64_t max_update_index;
     ++	int object_id_len;
     ++
     ++	struct reader_offsets ref_offsets;
     ++	struct reader_offsets obj_offsets;
     ++	struct reader_offsets log_offsets;
      +};
      +
      +int init_reader(struct reader *r, struct block_source source, const char *name);
     @@ -3176,7 +3101,7 @@
      +const char *reader_name(struct reader *r);
      +void reader_return_block(struct reader *r, struct block *p);
      +int reader_init_block_reader(struct reader *r, struct block_reader *br,
     -+                             uint64_t next_off, byte want_typ);
     ++			     uint64_t next_off, byte want_typ);
      +
      +#endif
      
     @@ -3195,1025 +3120,1105 @@
      +
      +#include "record.h"
      +
     -+#include <assert.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "constants.h"
      +#include "reftable.h"
      +
     -+int is_block_type(byte typ) {
     -+  switch (typ) {
     -+    case BLOCK_TYPE_REF:
     -+    case BLOCK_TYPE_LOG:
     -+    case BLOCK_TYPE_OBJ:
     -+    case BLOCK_TYPE_INDEX:
     -+      return true;
     -+  }
     -+  return false;
     -+}
     -+
     -+int get_var_int(uint64_t *dest, struct slice in) {
     -+  int ptr = 0;
     -+  uint64_t val;
     -+
     -+  if (in.len == 0) {
     -+    return -1;
     -+  }
     -+  val = in.buf[ptr] & 0x7f;
     -+
     -+  while (in.buf[ptr] & 0x80) {
     -+    ptr++;
     -+    if (ptr > in.len) {
     -+      return -1;
     -+    }
     -+    val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
     -+  }
     -+
     -+  *dest = val;
     -+  return ptr + 1;
     -+}
     -+
     -+int put_var_int(struct slice dest, uint64_t val) {
     -+  byte buf[10] = {};
     -+  int i = 9;
     -+  buf[i] = (byte)(val & 0x7f);
     -+  i--;
     -+  while (true) {
     -+    val >>= 7;
     -+    if (!val) {
     -+      break;
     -+    }
     -+    val--;
     -+    buf[i] = 0x80 | (byte)(val & 0x7f);
     -+    i--;
     -+  }
     -+
     -+  {
     -+    int n = sizeof(buf) - i - 1;
     -+    if (dest.len < n) {
     -+      return -1;
     -+    }
     -+    memcpy(dest.buf, &buf[i + 1], n);
     -+    return n;
     -+  }
     -+}
     -+
     -+int common_prefix_size(struct slice a, struct slice b) {
     -+  int p = 0;
     -+  while (p < a.len && p < b.len) {
     -+    if (a.buf[p] != b.buf[p]) {
     -+      break;
     -+    }
     -+    p++;
     -+  }
     -+
     -+  return p;
     -+}
     -+
     -+static int decode_string(struct slice *dest, struct slice in) {
     -+  int start_len = in.len;
     -+  uint64_t tsize = 0;
     -+  int n = get_var_int(&tsize, in);
     -+  if (n <= 0) {
     -+    return -1;
     -+  }
     -+  in.buf += n;
     -+  in.len -= n;
     -+  if (in.len < tsize) {
     -+    return -1;
     -+  }
     -+
     -+  slice_resize(dest, tsize + 1);
     -+  dest->buf[tsize] = 0;
     -+  memcpy(dest->buf, in.buf, tsize);
     -+  in.buf += tsize;
     -+  in.len -= tsize;
     -+
     -+  return start_len - in.len;
     ++int is_block_type(byte typ)
     ++{
     ++	switch (typ) {
     ++	case BLOCK_TYPE_REF:
     ++	case BLOCK_TYPE_LOG:
     ++	case BLOCK_TYPE_OBJ:
     ++	case BLOCK_TYPE_INDEX:
     ++		return true;
     ++	}
     ++	return false;
     ++}
     ++
     ++int get_var_int(uint64_t *dest, struct slice in)
     ++{
     ++	int ptr = 0;
     ++	uint64_t val;
     ++
     ++	if (in.len == 0) {
     ++		return -1;
     ++	}
     ++	val = in.buf[ptr] & 0x7f;
     ++
     ++	while (in.buf[ptr] & 0x80) {
     ++		ptr++;
     ++		if (ptr > in.len) {
     ++			return -1;
     ++		}
     ++		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
     ++	}
     ++
     ++	*dest = val;
     ++	return ptr + 1;
     ++}
     ++
     ++int put_var_int(struct slice dest, uint64_t val)
     ++{
     ++	byte buf[10] = {};
     ++	int i = 9;
     ++	buf[i] = (byte)(val & 0x7f);
     ++	i--;
     ++	while (true) {
     ++		val >>= 7;
     ++		if (!val) {
     ++			break;
     ++		}
     ++		val--;
     ++		buf[i] = 0x80 | (byte)(val & 0x7f);
     ++		i--;
     ++	}
     ++
     ++	{
     ++		int n = sizeof(buf) - i - 1;
     ++		if (dest.len < n) {
     ++			return -1;
     ++		}
     ++		memcpy(dest.buf, &buf[i + 1], n);
     ++		return n;
     ++	}
     ++}
     ++
     ++int common_prefix_size(struct slice a, struct slice b)
     ++{
     ++	int p = 0;
     ++	while (p < a.len && p < b.len) {
     ++		if (a.buf[p] != b.buf[p]) {
     ++			break;
     ++		}
     ++		p++;
     ++	}
     ++
     ++	return p;
     ++}
     ++
     ++static int decode_string(struct slice *dest, struct slice in)
     ++{
     ++	int start_len = in.len;
     ++	uint64_t tsize = 0;
     ++	int n = get_var_int(&tsize, in);
     ++	if (n <= 0) {
     ++		return -1;
     ++	}
     ++	in.buf += n;
     ++	in.len -= n;
     ++	if (in.len < tsize) {
     ++		return -1;
     ++	}
     ++
     ++	slice_resize(dest, tsize + 1);
     ++	dest->buf[tsize] = 0;
     ++	memcpy(dest->buf, in.buf, tsize);
     ++	in.buf += tsize;
     ++	in.len -= tsize;
     ++
     ++	return start_len - in.len;
      +}
      +
      +int encode_key(bool *restart, struct slice dest, struct slice prev_key,
     -+               struct slice key, byte extra) {
     -+  struct slice start = dest;
     -+  int prefix_len = common_prefix_size(prev_key, key);
     -+  uint64_t suffix_len = key.len - prefix_len;
     -+  int n = put_var_int(dest, (uint64_t)prefix_len);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  dest.buf += n;
     -+  dest.len -= n;
     -+
     -+  *restart = (prefix_len == 0);
     -+
     -+  n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  dest.buf += n;
     -+  dest.len -= n;
     -+
     -+  if (dest.len < suffix_len) {
     -+    return -1;
     -+  }
     -+  memcpy(dest.buf, key.buf + prefix_len, suffix_len);
     -+  dest.buf += suffix_len;
     -+  dest.len -= suffix_len;
     -+
     -+  return start.len - dest.len;
     -+}
     -+
     -+static byte ref_record_type(void) { return BLOCK_TYPE_REF; }
     -+
     -+static void ref_record_key(const void *r, struct slice *dest) {
     -+  const struct ref_record *rec = (const struct ref_record *)r;
     -+  slice_set_string(dest, rec->ref_name);
     -+}
     -+
     -+static void ref_record_copy_from(void *rec, const void *src_rec,
     -+                                 int hash_size) {
     -+  struct ref_record *ref = (struct ref_record *)rec;
     -+  struct ref_record *src = (struct ref_record *)src_rec;
     -+  assert(hash_size > 0);
     -+
     -+  // This is simple and correct, but we could probably reuse the hash fields.
     -+  ref_record_clear(ref);
     -+  if (src->ref_name != NULL) {
     -+    ref->ref_name = strdup(src->ref_name);
     -+  }
     -+
     -+  if (src->target != NULL) {
     -+    ref->target = strdup(src->target);
     -+  }
     -+
     -+  if (src->target_value != NULL) {
     -+    ref->target_value = malloc(hash_size);
     -+    memcpy(ref->target_value, src->target_value, hash_size);
     -+  }
     -+
     -+  if (src->value != NULL) {
     -+    ref->value = malloc(hash_size);
     -+    memcpy(ref->value, src->value, hash_size);
     -+  }
     -+  ref->update_index = src->update_index;
     -+}
     -+
     -+static char hexdigit(int c) {
     -+  if (c <= 9) {
     -+    return '0' + c;
     -+  }
     -+  return 'a' + (c - 10);
     -+}
     -+
     -+static void hex_format(char *dest, byte *src, int hash_size) {
     -+  assert(hash_size > 0);
     -+  if (src != NULL) {
     -+    for (int i = 0; i < hash_size; i++) {
     -+      dest[2 * i] = hexdigit(src[i] >> 4);
     -+      dest[2 * i + 1] = hexdigit(src[i] & 0xf);
     -+    }
     -+    dest[2 * hash_size] = 0;
     -+  }
     -+}
     -+
     -+void ref_record_print(struct ref_record *ref, int hash_size) {
     -+  char hex[SHA256_SIZE + 1] = {};
     -+
     -+  printf("ref{%s(%ld) ", ref->ref_name, ref->update_index);
     -+  if (ref->value != NULL) {
     -+    hex_format(hex, ref->value, hash_size);
     -+    printf("%s", hex);
     -+  }
     -+  if (ref->target_value != NULL) {
     -+    hex_format(hex, ref->target_value, hash_size);
     -+    printf(" (T %s)", hex);
     -+  }
     -+  if (ref->target != NULL) {
     -+    printf("=> %s", ref->target);
     -+  }
     -+  printf("}\n");
     -+}
     -+
     -+static void ref_record_clear_void(void *rec) {
     -+  ref_record_clear((struct ref_record *)rec);
     -+}
     -+
     -+void ref_record_clear(struct ref_record *ref) {
     -+  free(ref->ref_name);
     -+  free(ref->target);
     -+  free(ref->target_value);
     -+  free(ref->value);
     -+  memset(ref, 0, sizeof(struct ref_record));
     -+}
     -+
     -+static byte ref_record_val_type(const void *rec) {
     -+  const struct ref_record *r = (const struct ref_record *)rec;
     -+  if (r->value != NULL) {
     -+    if (r->target_value != NULL) {
     -+      return 2;
     -+    } else {
     -+      return 1;
     -+    }
     -+  } else if (r->target != NULL) {
     -+    return 3;
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int encode_string(char *str, struct slice s) {
     -+  struct slice start = s;
     -+  int l = strlen(str);
     -+  int n = put_var_int(s, l);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+  if (s.len < l) {
     -+    return -1;
     -+  }
     -+  memcpy(s.buf, str, l);
     -+  s.buf += l;
     -+  s.len -= l;
     -+
     -+  return start.len - s.len;
     -+}
     -+
     -+static int ref_record_encode(const void *rec, struct slice s, int hash_size) {
     -+  const struct ref_record *r = (const struct ref_record *)rec;
     -+  struct slice start = s;
     -+  int n = put_var_int(s, r->update_index);
     -+  assert(hash_size > 0);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+
     -+  if (r->value != NULL) {
     -+    if (s.len < hash_size) {
     -+      return -1;
     -+    }
     -+    memcpy(s.buf, r->value, hash_size);
     -+    s.buf += hash_size;
     -+    s.len -= hash_size;
     -+  }
     -+
     -+  if (r->target_value != NULL) {
     -+    if (s.len < hash_size) {
     -+      return -1;
     -+    }
     -+    memcpy(s.buf, r->target_value, hash_size);
     -+    s.buf += hash_size;
     -+    s.len -= hash_size;
     -+  }
     -+
     -+  if (r->target != NULL) {
     -+    int n = encode_string(r->target, s);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+    s.buf += n;
     -+    s.len -= n;
     -+  }
     -+
     -+  return start.len - s.len;
     ++	       struct slice key, byte extra)
     ++{
     ++	struct slice start = dest;
     ++	int prefix_len = common_prefix_size(prev_key, key);
     ++	uint64_t suffix_len = key.len - prefix_len;
     ++	int n = put_var_int(dest, (uint64_t)prefix_len);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	dest.buf += n;
     ++	dest.len -= n;
     ++
     ++	*restart = (prefix_len == 0);
     ++
     ++	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	dest.buf += n;
     ++	dest.len -= n;
     ++
     ++	if (dest.len < suffix_len) {
     ++		return -1;
     ++	}
     ++	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
     ++	dest.buf += suffix_len;
     ++	dest.len -= suffix_len;
     ++
     ++	return start.len - dest.len;
     ++}
     ++
     ++static byte ref_record_type(void)
     ++{
     ++	return BLOCK_TYPE_REF;
     ++}
     ++
     ++static void ref_record_key(const void *r, struct slice *dest)
     ++{
     ++	const struct ref_record *rec = (const struct ref_record *)r;
     ++	slice_set_string(dest, rec->ref_name);
     ++}
     ++
     ++static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
     ++{
     ++	struct ref_record *ref = (struct ref_record *)rec;
     ++	struct ref_record *src = (struct ref_record *)src_rec;
     ++	assert(hash_size > 0);
     ++
     ++	/* This is simple and correct, but we could probably reuse the hash
     ++           fields. */
     ++	ref_record_clear(ref);
     ++	if (src->ref_name != NULL) {
     ++		ref->ref_name = strdup(src->ref_name);
     ++	}
     ++
     ++	if (src->target != NULL) {
     ++		ref->target = strdup(src->target);
     ++	}
     ++
     ++	if (src->target_value != NULL) {
     ++		ref->target_value = malloc(hash_size);
     ++		memcpy(ref->target_value, src->target_value, hash_size);
     ++	}
     ++
     ++	if (src->value != NULL) {
     ++		ref->value = malloc(hash_size);
     ++		memcpy(ref->value, src->value, hash_size);
     ++	}
     ++	ref->update_index = src->update_index;
     ++}
     ++
     ++static char hexdigit(int c)
     ++{
     ++	if (c <= 9) {
     ++		return '0' + c;
     ++	}
     ++	return 'a' + (c - 10);
     ++}
     ++
     ++static void hex_format(char *dest, byte *src, int hash_size)
     ++{
     ++	assert(hash_size > 0);
     ++	if (src != NULL) {
     ++		int i = 0;
     ++		for (i = 0; i < hash_size; i++) {
     ++			dest[2 * i] = hexdigit(src[i] >> 4);
     ++			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
     ++		}
     ++		dest[2 * hash_size] = 0;
     ++	}
     ++}
     ++
     ++void ref_record_print(struct ref_record *ref, int hash_size)
     ++{
     ++	char hex[SHA256_SIZE + 1] = {};
     ++
     ++	printf("ref{%s(%" PRIdMAX ") ", ref->ref_name, ref->update_index);
     ++	if (ref->value != NULL) {
     ++		hex_format(hex, ref->value, hash_size);
     ++		printf("%s", hex);
     ++	}
     ++	if (ref->target_value != NULL) {
     ++		hex_format(hex, ref->target_value, hash_size);
     ++		printf(" (T %s)", hex);
     ++	}
     ++	if (ref->target != NULL) {
     ++		printf("=> %s", ref->target);
     ++	}
     ++	printf("}\n");
     ++}
     ++
     ++static void ref_record_clear_void(void *rec)
     ++{
     ++	ref_record_clear((struct ref_record *)rec);
     ++}
     ++
     ++void ref_record_clear(struct ref_record *ref)
     ++{
     ++	free(ref->ref_name);
     ++	free(ref->target);
     ++	free(ref->target_value);
     ++	free(ref->value);
     ++	memset(ref, 0, sizeof(struct ref_record));
     ++}
     ++
     ++static byte ref_record_val_type(const void *rec)
     ++{
     ++	const struct ref_record *r = (const struct ref_record *)rec;
     ++	if (r->value != NULL) {
     ++		if (r->target_value != NULL) {
     ++			return 2;
     ++		} else {
     ++			return 1;
     ++		}
     ++	} else if (r->target != NULL) {
     ++		return 3;
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int encode_string(char *str, struct slice s)
     ++{
     ++	struct slice start = s;
     ++	int l = strlen(str);
     ++	int n = put_var_int(s, l);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++	if (s.len < l) {
     ++		return -1;
     ++	}
     ++	memcpy(s.buf, str, l);
     ++	s.buf += l;
     ++	s.len -= l;
     ++
     ++	return start.len - s.len;
     ++}
     ++
     ++static int ref_record_encode(const void *rec, struct slice s, int hash_size)
     ++{
     ++	const struct ref_record *r = (const struct ref_record *)rec;
     ++	struct slice start = s;
     ++	int n = put_var_int(s, r->update_index);
     ++	assert(hash_size > 0);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++
     ++	if (r->value != NULL) {
     ++		if (s.len < hash_size) {
     ++			return -1;
     ++		}
     ++		memcpy(s.buf, r->value, hash_size);
     ++		s.buf += hash_size;
     ++		s.len -= hash_size;
     ++	}
     ++
     ++	if (r->target_value != NULL) {
     ++		if (s.len < hash_size) {
     ++			return -1;
     ++		}
     ++		memcpy(s.buf, r->target_value, hash_size);
     ++		s.buf += hash_size;
     ++		s.len -= hash_size;
     ++	}
     ++
     ++	if (r->target != NULL) {
     ++		int n = encode_string(r->target, s);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		s.buf += n;
     ++		s.len -= n;
     ++	}
     ++
     ++	return start.len - s.len;
      +}
      +
      +static int ref_record_decode(void *rec, struct slice key, byte val_type,
     -+                             struct slice in, int hash_size) {
     -+  struct ref_record *r = (struct ref_record *)rec;
     -+  struct slice start = in;
     -+  bool seen_value = false;
     -+  bool seen_target_value = false;
     -+  bool seen_target = false;
     -+
     -+  int n = get_var_int(&r->update_index, in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+  assert(hash_size > 0);
     -+
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  r->ref_name = realloc(r->ref_name, key.len + 1);
     -+  memcpy(r->ref_name, key.buf, key.len);
     -+  r->ref_name[key.len] = 0;
     -+
     -+  switch (val_type) {
     -+    case 1:
     -+    case 2:
     -+      if (in.len < hash_size) {
     -+        return -1;
     -+      }
     -+
     -+      if (r->value == NULL) {
     -+        r->value = malloc(hash_size);
     -+      }
     -+      seen_value = true;
     -+      memcpy(r->value, in.buf, hash_size);
     -+      in.buf += hash_size;
     -+      in.len -= hash_size;
     -+      if (val_type == 1) {
     -+        break;
     -+      }
     -+      if (r->target_value == NULL) {
     -+        r->target_value = malloc(hash_size);
     -+      }
     -+      seen_target_value = true;
     -+      memcpy(r->target_value, in.buf, hash_size);
     -+      in.buf += hash_size;
     -+      in.len -= hash_size;
     -+      break;
     -+    case 3: {
     -+      struct slice dest = {};
     -+      int n = decode_string(&dest, in);
     -+      if (n < 0) {
     -+        return -1;
     -+      }
     -+      in.buf += n;
     -+      in.len -= n;
     -+      seen_target = true;
     -+      r->target = (char *)slice_as_string(&dest);
     -+    } break;
     -+
     -+    case 0:
     -+      break;
     -+    default:
     -+      abort();
     -+      break;
     -+  }
     -+
     -+  if (!seen_target && r->target != NULL) {
     -+    free(r->target);
     -+    r->target = NULL;
     -+  }
     -+  if (!seen_target_value && r->target_value != NULL) {
     -+    free(r->target_value);
     -+    r->target_value = NULL;
     -+  }
     -+  if (!seen_value && r->value != NULL) {
     -+    free(r->value);
     -+    r->value = NULL;
     -+  }
     -+
     -+  return start.len - in.len;
     ++			     struct slice in, int hash_size)
     ++{
     ++	struct ref_record *r = (struct ref_record *)rec;
     ++	struct slice start = in;
     ++	bool seen_value = false;
     ++	bool seen_target_value = false;
     ++	bool seen_target = false;
     ++
     ++	int n = get_var_int(&r->update_index, in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++	assert(hash_size > 0);
     ++
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	r->ref_name = realloc(r->ref_name, key.len + 1);
     ++	memcpy(r->ref_name, key.buf, key.len);
     ++	r->ref_name[key.len] = 0;
     ++
     ++	switch (val_type) {
     ++	case 1:
     ++	case 2:
     ++		if (in.len < hash_size) {
     ++			return -1;
     ++		}
     ++
     ++		if (r->value == NULL) {
     ++			r->value = malloc(hash_size);
     ++		}
     ++		seen_value = true;
     ++		memcpy(r->value, in.buf, hash_size);
     ++		in.buf += hash_size;
     ++		in.len -= hash_size;
     ++		if (val_type == 1) {
     ++			break;
     ++		}
     ++		if (r->target_value == NULL) {
     ++			r->target_value = malloc(hash_size);
     ++		}
     ++		seen_target_value = true;
     ++		memcpy(r->target_value, in.buf, hash_size);
     ++		in.buf += hash_size;
     ++		in.len -= hash_size;
     ++		break;
     ++	case 3: {
     ++		struct slice dest = {};
     ++		int n = decode_string(&dest, in);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		in.buf += n;
     ++		in.len -= n;
     ++		seen_target = true;
     ++		r->target = (char *)slice_as_string(&dest);
     ++	} break;
     ++
     ++	case 0:
     ++		break;
     ++	default:
     ++		abort();
     ++		break;
     ++	}
     ++
     ++	if (!seen_target && r->target != NULL) {
     ++		free(r->target);
     ++		r->target = NULL;
     ++	}
     ++	if (!seen_target_value && r->target_value != NULL) {
     ++		free(r->target_value);
     ++		r->target_value = NULL;
     ++	}
     ++	if (!seen_value && r->value != NULL) {
     ++		free(r->value);
     ++		r->value = NULL;
     ++	}
     ++
     ++	return start.len - in.len;
      +}
      +
      +int decode_key(struct slice *key, byte *extra, struct slice last_key,
     -+               struct slice in) {
     -+  int start_len = in.len;
     -+  uint64_t prefix_len = 0;
     -+  uint64_t suffix_len = 0;
     -+  int n = get_var_int(&prefix_len, in);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  if (prefix_len > last_key.len) {
     -+    return -1;
     -+  }
     -+
     -+  n = get_var_int(&suffix_len, in);
     -+  if (n <= 0) {
     -+    return -1;
     -+  }
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  *extra = (byte)(suffix_len & 0x7);
     -+  suffix_len >>= 3;
     -+
     -+  if (in.len < suffix_len) {
     -+    return -1;
     -+  }
     -+
     -+  slice_resize(key, suffix_len + prefix_len);
     -+  memcpy(key->buf, last_key.buf, prefix_len);
     -+
     -+  memcpy(key->buf + prefix_len, in.buf, suffix_len);
     -+  in.buf += suffix_len;
     -+  in.len -= suffix_len;
     -+
     -+  return start_len - in.len;
     ++	       struct slice in)
     ++{
     ++	int start_len = in.len;
     ++	uint64_t prefix_len = 0;
     ++	uint64_t suffix_len = 0;
     ++	int n = get_var_int(&prefix_len, in);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	if (prefix_len > last_key.len) {
     ++		return -1;
     ++	}
     ++
     ++	n = get_var_int(&suffix_len, in);
     ++	if (n <= 0) {
     ++		return -1;
     ++	}
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	*extra = (byte)(suffix_len & 0x7);
     ++	suffix_len >>= 3;
     ++
     ++	if (in.len < suffix_len) {
     ++		return -1;
     ++	}
     ++
     ++	slice_resize(key, suffix_len + prefix_len);
     ++	memcpy(key->buf, last_key.buf, prefix_len);
     ++
     ++	memcpy(key->buf + prefix_len, in.buf, suffix_len);
     ++	in.buf += suffix_len;
     ++	in.len -= suffix_len;
     ++
     ++	return start_len - in.len;
      +}
      +
      +struct record_vtable ref_record_vtable = {
     -+    .key = &ref_record_key,
     -+    .type = &ref_record_type,
     -+    .copy_from = &ref_record_copy_from,
     -+    .val_type = &ref_record_val_type,
     -+    .encode = &ref_record_encode,
     -+    .decode = &ref_record_decode,
     -+    .clear = &ref_record_clear_void,
     ++	.key = &ref_record_key,
     ++	.type = &ref_record_type,
     ++	.copy_from = &ref_record_copy_from,
     ++	.val_type = &ref_record_val_type,
     ++	.encode = &ref_record_encode,
     ++	.decode = &ref_record_decode,
     ++	.clear = &ref_record_clear_void,
      +};
      +
     -+static byte obj_record_type(void) { return BLOCK_TYPE_OBJ; }
     -+
     -+static void obj_record_key(const void *r, struct slice *dest) {
     -+  const struct obj_record *rec = (const struct obj_record *)r;
     -+  slice_resize(dest, rec->hash_prefix_len);
     -+  memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
     -+}
     -+
     -+static void obj_record_copy_from(void *rec, const void *src_rec,
     -+                                 int hash_size) {
     -+  struct obj_record *ref = (struct obj_record *)rec;
     -+  const struct obj_record *src = (const struct obj_record *)src_rec;
     -+
     -+  *ref = *src;
     -+  ref->hash_prefix = malloc(ref->hash_prefix_len);
     -+  memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
     -+
     -+  {
     -+    int olen = ref->offset_len * sizeof(uint64_t);
     -+    ref->offsets = malloc(olen);
     -+    memcpy(ref->offsets, src->offsets, olen);
     -+  }
     -+}
     -+
     -+static void obj_record_clear(void *rec) {
     -+  struct obj_record *ref = (struct obj_record *)rec;
     -+  free(ref->hash_prefix);
     -+  free(ref->offsets);
     -+  memset(ref, 0, sizeof(struct obj_record));
     -+}
     -+
     -+static byte obj_record_val_type(const void *rec) {
     -+  struct obj_record *r = (struct obj_record *)rec;
     -+  if (r->offset_len > 0 && r->offset_len < 8) {
     -+    return r->offset_len;
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int obj_record_encode(const void *rec, struct slice s, int hash_size) {
     -+  struct obj_record *r = (struct obj_record *)rec;
     -+  struct slice start = s;
     -+  int n = 0;
     -+  if (r->offset_len == 0 || r->offset_len >= 8) {
     -+    n = put_var_int(s, r->offset_len);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+    s.buf += n;
     -+    s.len -= n;
     -+  }
     -+  if (r->offset_len == 0) {
     -+    return start.len - s.len;
     -+  }
     -+  n = put_var_int(s, r->offsets[0]);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+
     -+  {
     -+    uint64_t last = r->offsets[0];
     -+    for (int i = 1; i < r->offset_len; i++) {
     -+      int n = put_var_int(s, r->offsets[i] - last);
     -+      if (n < 0) {
     -+        return -1;
     -+      }
     -+      s.buf += n;
     -+      s.len -= n;
     -+      last = r->offsets[i];
     -+    }
     -+  }
     -+  return start.len - s.len;
     ++static byte obj_record_type(void)
     ++{
     ++	return BLOCK_TYPE_OBJ;
     ++}
     ++
     ++static void obj_record_key(const void *r, struct slice *dest)
     ++{
     ++	const struct obj_record *rec = (const struct obj_record *)r;
     ++	slice_resize(dest, rec->hash_prefix_len);
     ++	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
     ++}
     ++
     ++static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
     ++{
     ++	struct obj_record *ref = (struct obj_record *)rec;
     ++	const struct obj_record *src = (const struct obj_record *)src_rec;
     ++
     ++	*ref = *src;
     ++	ref->hash_prefix = malloc(ref->hash_prefix_len);
     ++	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
     ++
     ++	{
     ++		int olen = ref->offset_len * sizeof(uint64_t);
     ++		ref->offsets = malloc(olen);
     ++		memcpy(ref->offsets, src->offsets, olen);
     ++	}
     ++}
     ++
     ++static void obj_record_clear(void *rec)
     ++{
     ++	struct obj_record *ref = (struct obj_record *)rec;
     ++	free(ref->hash_prefix);
     ++	free(ref->offsets);
     ++	memset(ref, 0, sizeof(struct obj_record));
     ++}
     ++
     ++static byte obj_record_val_type(const void *rec)
     ++{
     ++	struct obj_record *r = (struct obj_record *)rec;
     ++	if (r->offset_len > 0 && r->offset_len < 8) {
     ++		return r->offset_len;
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int obj_record_encode(const void *rec, struct slice s, int hash_size)
     ++{
     ++	struct obj_record *r = (struct obj_record *)rec;
     ++	struct slice start = s;
     ++	int n = 0;
     ++	if (r->offset_len == 0 || r->offset_len >= 8) {
     ++		n = put_var_int(s, r->offset_len);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		s.buf += n;
     ++		s.len -= n;
     ++	}
     ++	if (r->offset_len == 0) {
     ++		return start.len - s.len;
     ++	}
     ++	n = put_var_int(s, r->offsets[0]);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++
     ++	{
     ++		uint64_t last = r->offsets[0];
     ++		int i = 0;
     ++		for (i = 1; i < r->offset_len; i++) {
     ++			int n = put_var_int(s, r->offsets[i] - last);
     ++			if (n < 0) {
     ++				return -1;
     ++			}
     ++			s.buf += n;
     ++			s.len -= n;
     ++			last = r->offsets[i];
     ++		}
     ++	}
     ++	return start.len - s.len;
      +}
      +
      +static int obj_record_decode(void *rec, struct slice key, byte val_type,
     -+                             struct slice in, int hash_size) {
     -+  struct slice start = in;
     -+  struct obj_record *r = (struct obj_record *)rec;
     -+  uint64_t count = val_type;
     -+  int n = 0;
     -+  r->hash_prefix = malloc(key.len);
     -+  memcpy(r->hash_prefix, key.buf, key.len);
     -+  r->hash_prefix_len = key.len;
     -+
     -+  if (val_type == 0) {
     -+    n = get_var_int(&count, in);
     -+    if (n < 0) {
     -+      return n;
     -+    }
     -+
     -+    in.buf += n;
     -+    in.len -= n;
     -+  }
     -+
     -+  r->offsets = NULL;
     -+  r->offset_len = 0;
     -+  if (count == 0) {
     -+    return start.len - in.len;
     -+  }
     -+
     -+  r->offsets = malloc(count * sizeof(uint64_t));
     -+  r->offset_len = count;
     -+
     -+  n = get_var_int(&r->offsets[0], in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  {
     -+    uint64_t last = r->offsets[0];
     -+    int j = 1;
     -+    while (j < count) {
     -+      uint64_t delta = 0;
     -+      int n = get_var_int(&delta, in);
     -+      if (n < 0) {
     -+        return n;
     -+      }
     -+
     -+      in.buf += n;
     -+      in.len -= n;
     -+
     -+      last = r->offsets[j] = (delta + last);
     -+      j++;
     -+    }
     -+  }
     -+  return start.len - in.len;
     ++			     struct slice in, int hash_size)
     ++{
     ++	struct slice start = in;
     ++	struct obj_record *r = (struct obj_record *)rec;
     ++	uint64_t count = val_type;
     ++	int n = 0;
     ++	r->hash_prefix = malloc(key.len);
     ++	memcpy(r->hash_prefix, key.buf, key.len);
     ++	r->hash_prefix_len = key.len;
     ++
     ++	if (val_type == 0) {
     ++		n = get_var_int(&count, in);
     ++		if (n < 0) {
     ++			return n;
     ++		}
     ++
     ++		in.buf += n;
     ++		in.len -= n;
     ++	}
     ++
     ++	r->offsets = NULL;
     ++	r->offset_len = 0;
     ++	if (count == 0) {
     ++		return start.len - in.len;
     ++	}
     ++
     ++	r->offsets = malloc(count * sizeof(uint64_t));
     ++	r->offset_len = count;
     ++
     ++	n = get_var_int(&r->offsets[0], in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	{
     ++		uint64_t last = r->offsets[0];
     ++		int j = 1;
     ++		while (j < count) {
     ++			uint64_t delta = 0;
     ++			int n = get_var_int(&delta, in);
     ++			if (n < 0) {
     ++				return n;
     ++			}
     ++
     ++			in.buf += n;
     ++			in.len -= n;
     ++
     ++			last = r->offsets[j] = (delta + last);
     ++			j++;
     ++		}
     ++	}
     ++	return start.len - in.len;
      +}
      +
      +struct record_vtable obj_record_vtable = {
     -+    .key = &obj_record_key,
     -+    .type = &obj_record_type,
     -+    .copy_from = &obj_record_copy_from,
     -+    .val_type = &obj_record_val_type,
     -+    .encode = &obj_record_encode,
     -+    .decode = &obj_record_decode,
     -+    .clear = &obj_record_clear,
     ++	.key = &obj_record_key,
     ++	.type = &obj_record_type,
     ++	.copy_from = &obj_record_copy_from,
     ++	.val_type = &obj_record_val_type,
     ++	.encode = &obj_record_encode,
     ++	.decode = &obj_record_decode,
     ++	.clear = &obj_record_clear,
      +};
      +
     -+void log_record_print(struct log_record *log, int hash_size) {
     -+  char hex[SHA256_SIZE + 1] = {};
     -+
     -+  printf("log{%s(%ld) %s <%s> %lu %04d\n", log->ref_name, log->update_index,
     -+         log->name, log->email, log->time, log->tz_offset);
     -+  hex_format(hex, log->old_hash, hash_size);
     -+  printf("%s => ", hex);
     -+  hex_format(hex, log->new_hash, hash_size);
     -+  printf("%s\n\n%s\n}\n", hex, log->message);
     ++void log_record_print(struct log_record *log, int hash_size)
     ++{
     ++	char hex[SHA256_SIZE + 1] = {};
     ++
     ++	printf("log{%s(%" PRIdMAX ") %s <%s> %lu %04d\n", log->ref_name,
     ++	       log->update_index, log->name, log->email, log->time,
     ++	       log->tz_offset);
     ++	hex_format(hex, log->old_hash, hash_size);
     ++	printf("%s => ", hex);
     ++	hex_format(hex, log->new_hash, hash_size);
     ++	printf("%s\n\n%s\n}\n", hex, log->message);
     ++}
     ++
     ++static byte log_record_type(void)
     ++{
     ++	return BLOCK_TYPE_LOG;
     ++}
     ++
     ++static void log_record_key(const void *r, struct slice *dest)
     ++{
     ++	const struct log_record *rec = (const struct log_record *)r;
     ++	int len = strlen(rec->ref_name);
     ++	uint64_t ts = 0;
     ++	slice_resize(dest, len + 9);
     ++	memcpy(dest->buf, rec->ref_name, len + 1);
     ++	ts = (~ts) - rec->update_index;
     ++	put_u64(dest->buf + 1 + len, ts);
     ++}
     ++
     ++static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
     ++{
     ++	struct log_record *dst = (struct log_record *)rec;
     ++	const struct log_record *src = (const struct log_record *)src_rec;
     ++
     ++	*dst = *src;
     ++	dst->ref_name = strdup(dst->ref_name);
     ++	dst->email = strdup(dst->email);
     ++	dst->name = strdup(dst->name);
     ++	dst->message = strdup(dst->message);
     ++	if (dst->new_hash != NULL) {
     ++		dst->new_hash = malloc(hash_size);
     ++		memcpy(dst->new_hash, src->new_hash, hash_size);
     ++	}
     ++	if (dst->old_hash != NULL) {
     ++		dst->old_hash = malloc(hash_size);
     ++		memcpy(dst->old_hash, src->old_hash, hash_size);
     ++	}
     ++}
     ++
     ++static void log_record_clear_void(void *rec)
     ++{
     ++	struct log_record *r = (struct log_record *)rec;
     ++	log_record_clear(r);
     ++}
     ++
     ++void log_record_clear(struct log_record *r)
     ++{
     ++	free(r->ref_name);
     ++	free(r->new_hash);
     ++	free(r->old_hash);
     ++	free(r->name);
     ++	free(r->email);
     ++	free(r->message);
     ++	memset(r, 0, sizeof(struct log_record));
     ++}
     ++
     ++static byte log_record_val_type(const void *rec)
     ++{
     ++	return 1;
      +}
      +
     -+static byte log_record_type(void) { return BLOCK_TYPE_LOG; }
     -+
     -+static void log_record_key(const void *r, struct slice *dest) {
     -+  const struct log_record *rec = (const struct log_record *)r;
     -+  int len = strlen(rec->ref_name);
     -+  uint64_t ts = 0;
     -+  slice_resize(dest, len + 9);
     -+  memcpy(dest->buf, rec->ref_name, len + 1);
     -+  ts = (~ts) - rec->update_index;
     -+  put_u64(dest->buf + 1 + len, ts);
     -+}
     -+
     -+static void log_record_copy_from(void *rec, const void *src_rec,
     -+                                 int hash_size) {
     -+  struct log_record *dst = (struct log_record *)rec;
     -+  const struct log_record *src = (const struct log_record *)src_rec;
     -+
     -+  *dst = *src;
     -+  dst->ref_name = strdup(dst->ref_name);
     -+  dst->email = strdup(dst->email);
     -+  dst->name = strdup(dst->name);
     -+  dst->message = strdup(dst->message);
     -+  if (dst->new_hash != NULL) {
     -+    dst->new_hash = malloc(hash_size);
     -+    memcpy(dst->new_hash, src->new_hash, hash_size);
     -+  }
     -+  if (dst->old_hash != NULL) {
     -+    dst->old_hash = malloc(hash_size);
     -+    memcpy(dst->old_hash, src->old_hash, hash_size);
     -+  }
     -+}
     -+
     -+static void log_record_clear_void(void *rec) {
     -+  struct log_record *r = (struct log_record *)rec;
     -+  log_record_clear(r);
     -+}
     -+
     -+void log_record_clear(struct log_record *r) {
     -+  free(r->ref_name);
     -+  free(r->new_hash);
     -+  free(r->old_hash);
     -+  free(r->name);
     -+  free(r->email);
     -+  free(r->message);
     -+  memset(r, 0, sizeof(struct log_record));
     -+}
     -+
     -+static byte log_record_val_type(const void *rec) { return 1; }
     -+
      +static byte zero[SHA256_SIZE] = {};
      +
     -+static int log_record_encode(const void *rec, struct slice s, int hash_size) {
     -+  struct log_record *r = (struct log_record *)rec;
     -+  struct slice start = s;
     -+  int n = 0;
     -+  byte *oldh = r->old_hash;
     -+  byte *newh = r->new_hash;
     -+  if (oldh == NULL) {
     -+    oldh = zero;
     -+  }
     -+  if (newh == NULL) {
     -+    newh = zero;
     -+  }
     -+
     -+  if (s.len < 2 * hash_size) {
     -+    return -1;
     -+  }
     -+
     -+  memcpy(s.buf, oldh, hash_size);
     -+  memcpy(s.buf + hash_size, newh, hash_size);
     -+  s.buf += 2 * hash_size;
     -+  s.len -= 2 * hash_size;
     -+
     -+  n = encode_string(r->name ? r->name : "", s);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.len -= n;
     -+  s.buf += n;
     -+
     -+  n = encode_string(r->email ? r->email : "", s);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.len -= n;
     -+  s.buf += n;
     -+
     -+  n = put_var_int(s, r->time);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+
     -+  if (s.len < 2) {
     -+    return -1;
     -+  }
     -+
     -+  put_u16(s.buf, r->tz_offset);
     -+  s.buf += 2;
     -+  s.len -= 2;
     -+
     -+  n = encode_string(r->message ? r->message : "", s);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.len -= n;
     -+  s.buf += n;
     -+
     -+  return start.len - s.len;
     ++static int log_record_encode(const void *rec, struct slice s, int hash_size)
     ++{
     ++	struct log_record *r = (struct log_record *)rec;
     ++	struct slice start = s;
     ++	int n = 0;
     ++	byte *oldh = r->old_hash;
     ++	byte *newh = r->new_hash;
     ++	if (oldh == NULL) {
     ++		oldh = zero;
     ++	}
     ++	if (newh == NULL) {
     ++		newh = zero;
     ++	}
     ++
     ++	if (s.len < 2 * hash_size) {
     ++		return -1;
     ++	}
     ++
     ++	memcpy(s.buf, oldh, hash_size);
     ++	memcpy(s.buf + hash_size, newh, hash_size);
     ++	s.buf += 2 * hash_size;
     ++	s.len -= 2 * hash_size;
     ++
     ++	n = encode_string(r->name ? r->name : "", s);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.len -= n;
     ++	s.buf += n;
     ++
     ++	n = encode_string(r->email ? r->email : "", s);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.len -= n;
     ++	s.buf += n;
     ++
     ++	n = put_var_int(s, r->time);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++
     ++	if (s.len < 2) {
     ++		return -1;
     ++	}
     ++
     ++	put_u16(s.buf, r->tz_offset);
     ++	s.buf += 2;
     ++	s.len -= 2;
     ++
     ++	n = encode_string(r->message ? r->message : "", s);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.len -= n;
     ++	s.buf += n;
     ++
     ++	return start.len - s.len;
      +}
      +
      +static int log_record_decode(void *rec, struct slice key, byte val_type,
     -+                             struct slice in, int hash_size) {
     -+  struct slice start = in;
     -+  struct log_record *r = (struct log_record *)rec;
     -+  uint64_t max = 0;
     -+  uint64_t ts = 0;
     -+  struct slice dest = {};
     -+  int n;
     -+
     -+  if (key.len <= 9 || key.buf[key.len - 9] != 0) {
     -+    return FORMAT_ERROR;
     -+  }
     -+
     -+  r->ref_name = realloc(r->ref_name, key.len - 8);
     -+  memcpy(r->ref_name, key.buf, key.len - 8);
     -+  ts = get_u64(key.buf + key.len - 8);
     -+
     -+  r->update_index = (~max) - ts;
     -+
     -+  if (in.len < 2 * hash_size) {
     -+    return FORMAT_ERROR;
     -+  }
     -+
     -+  r->old_hash = realloc(r->old_hash, hash_size);
     -+  r->new_hash = realloc(r->new_hash, hash_size);
     -+
     -+  memcpy(r->old_hash, in.buf, hash_size);
     -+  memcpy(r->new_hash, in.buf + hash_size, hash_size);
     -+
     -+  in.buf += 2 * hash_size;
     -+  in.len -= 2 * hash_size;
     -+
     -+  n = decode_string(&dest, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+
     -+  r->name = realloc(r->name, dest.len + 1);
     -+  memcpy(r->name, dest.buf, dest.len);
     -+  r->name[dest.len] = 0;
     -+
     -+  slice_resize(&dest, 0);
     -+  n = decode_string(&dest, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+
     -+  r->email = realloc(r->email, dest.len + 1);
     -+  memcpy(r->email, dest.buf, dest.len);
     -+  r->email[dest.len] = 0;
     -+
     -+  ts = 0;
     -+  n = get_var_int(&ts, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+  r->time = ts;
     -+  if (in.len < 2) {
     -+    goto error;
     -+  }
     -+
     -+  r->tz_offset = get_u16(in.buf);
     -+  in.buf += 2;
     -+  in.len -= 2;
     -+
     -+  slice_resize(&dest, 0);
     -+  n = decode_string(&dest, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+
     -+  r->message = realloc(r->message, dest.len + 1);
     -+  memcpy(r->message, dest.buf, dest.len);
     -+  r->message[dest.len] = 0;
     -+
     -+  return start.len - in.len;
     ++			     struct slice in, int hash_size)
     ++{
     ++	struct slice start = in;
     ++	struct log_record *r = (struct log_record *)rec;
     ++	uint64_t max = 0;
     ++	uint64_t ts = 0;
     ++	struct slice dest = {};
     ++	int n;
     ++
     ++	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
     ++		return FORMAT_ERROR;
     ++	}
     ++
     ++	r->ref_name = realloc(r->ref_name, key.len - 8);
     ++	memcpy(r->ref_name, key.buf, key.len - 8);
     ++	ts = get_u64(key.buf + key.len - 8);
     ++
     ++	r->update_index = (~max) - ts;
     ++
     ++	if (in.len < 2 * hash_size) {
     ++		return FORMAT_ERROR;
     ++	}
     ++
     ++	r->old_hash = realloc(r->old_hash, hash_size);
     ++	r->new_hash = realloc(r->new_hash, hash_size);
     ++
     ++	memcpy(r->old_hash, in.buf, hash_size);
     ++	memcpy(r->new_hash, in.buf + hash_size, hash_size);
     ++
     ++	in.buf += 2 * hash_size;
     ++	in.len -= 2 * hash_size;
     ++
     ++	n = decode_string(&dest, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++
     ++	r->name = realloc(r->name, dest.len + 1);
     ++	memcpy(r->name, dest.buf, dest.len);
     ++	r->name[dest.len] = 0;
     ++
     ++	slice_resize(&dest, 0);
     ++	n = decode_string(&dest, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++
     ++	r->email = realloc(r->email, dest.len + 1);
     ++	memcpy(r->email, dest.buf, dest.len);
     ++	r->email[dest.len] = 0;
     ++
     ++	ts = 0;
     ++	n = get_var_int(&ts, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++	r->time = ts;
     ++	if (in.len < 2) {
     ++		goto error;
     ++	}
     ++
     ++	r->tz_offset = get_u16(in.buf);
     ++	in.buf += 2;
     ++	in.len -= 2;
     ++
     ++	slice_resize(&dest, 0);
     ++	n = decode_string(&dest, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++
     ++	r->message = realloc(r->message, dest.len + 1);
     ++	memcpy(r->message, dest.buf, dest.len);
     ++	r->message[dest.len] = 0;
     ++
     ++	return start.len - in.len;
      +
      +error:
     -+  free(slice_yield(&dest));
     -+  return FORMAT_ERROR;
     -+}
     -+
     -+static bool null_streq(char *a, char *b) {
     -+  char *empty = "";
     -+  if (a == NULL) {
     -+    a = empty;
     -+  }
     -+  if (b == NULL) {
     -+    b = empty;
     -+  }
     -+  return 0 == strcmp(a, b);
     -+}
     -+
     -+static bool zero_hash_eq(byte *a, byte *b, int sz) {
     -+  if (a == NULL) {
     -+    a = zero;
     -+  }
     -+  if (b == NULL) {
     -+    b = zero;
     -+  }
     -+  return 0 == memcmp(a, b, sz);
     -+}
     -+
     -+bool log_record_equal(struct log_record *a, struct log_record *b,
     -+                      int hash_size) {
     -+  return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
     -+         null_streq(a->message, b->message) &&
     -+         zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
     -+         zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
     -+         a->time == b->time && a->tz_offset == b->tz_offset &&
     -+         a->update_index == b->update_index;
     ++	free(slice_yield(&dest));
     ++	return FORMAT_ERROR;
     ++}
     ++
     ++static bool null_streq(char *a, char *b)
     ++{
     ++	char *empty = "";
     ++	if (a == NULL) {
     ++		a = empty;
     ++	}
     ++	if (b == NULL) {
     ++		b = empty;
     ++	}
     ++	return 0 == strcmp(a, b);
     ++}
     ++
     ++static bool zero_hash_eq(byte *a, byte *b, int sz)
     ++{
     ++	if (a == NULL) {
     ++		a = zero;
     ++	}
     ++	if (b == NULL) {
     ++		b = zero;
     ++	}
     ++	return !memcmp(a, b, sz);
     ++}
     ++
     ++bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
     ++{
     ++	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
     ++	       null_streq(a->message, b->message) &&
     ++	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
     ++	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
     ++	       a->time == b->time && a->tz_offset == b->tz_offset &&
     ++	       a->update_index == b->update_index;
      +}
      +
      +struct record_vtable log_record_vtable = {
     -+    .key = &log_record_key,
     -+    .type = &log_record_type,
     -+    .copy_from = &log_record_copy_from,
     -+    .val_type = &log_record_val_type,
     -+    .encode = &log_record_encode,
     -+    .decode = &log_record_decode,
     -+    .clear = &log_record_clear_void,
     ++	.key = &log_record_key,
     ++	.type = &log_record_type,
     ++	.copy_from = &log_record_copy_from,
     ++	.val_type = &log_record_val_type,
     ++	.encode = &log_record_encode,
     ++	.decode = &log_record_decode,
     ++	.clear = &log_record_clear_void,
      +};
      +
     -+struct record new_record(byte typ) {
     -+  struct record rec;
     -+  switch (typ) {
     -+    case BLOCK_TYPE_REF: {
     -+      struct ref_record *r = calloc(1, sizeof(struct ref_record));
     -+      record_from_ref(&rec, r);
     -+      return rec;
     -+    }
     -+
     -+    case BLOCK_TYPE_OBJ: {
     -+      struct obj_record *r = calloc(1, sizeof(struct obj_record));
     -+      record_from_obj(&rec, r);
     -+      return rec;
     -+    }
     -+    case BLOCK_TYPE_LOG: {
     -+      struct log_record *r = calloc(1, sizeof(struct log_record));
     -+      record_from_log(&rec, r);
     -+      return rec;
     -+    }
     -+    case BLOCK_TYPE_INDEX: {
     -+      struct index_record *r = calloc(1, sizeof(struct index_record));
     -+      record_from_index(&rec, r);
     -+      return rec;
     -+    }
     -+  }
     -+  abort();
     -+  return rec;
     -+}
     -+
     -+static byte index_record_type(void) { return BLOCK_TYPE_INDEX; }
     -+
     -+static void index_record_key(const void *r, struct slice *dest) {
     -+  struct index_record *rec = (struct index_record *)r;
     -+  slice_copy(dest, rec->last_key);
     ++struct record new_record(byte typ)
     ++{
     ++	struct record rec;
     ++	switch (typ) {
     ++	case BLOCK_TYPE_REF: {
     ++		struct ref_record *r = calloc(1, sizeof(struct ref_record));
     ++		record_from_ref(&rec, r);
     ++		return rec;
     ++	}
     ++
     ++	case BLOCK_TYPE_OBJ: {
     ++		struct obj_record *r = calloc(1, sizeof(struct obj_record));
     ++		record_from_obj(&rec, r);
     ++		return rec;
     ++	}
     ++	case BLOCK_TYPE_LOG: {
     ++		struct log_record *r = calloc(1, sizeof(struct log_record));
     ++		record_from_log(&rec, r);
     ++		return rec;
     ++	}
     ++	case BLOCK_TYPE_INDEX: {
     ++		struct index_record *r = calloc(1, sizeof(struct index_record));
     ++		record_from_index(&rec, r);
     ++		return rec;
     ++	}
     ++	}
     ++	abort();
     ++	return rec;
     ++}
     ++
     ++static byte index_record_type(void)
     ++{
     ++	return BLOCK_TYPE_INDEX;
     ++}
     ++
     ++static void index_record_key(const void *r, struct slice *dest)
     ++{
     ++	struct index_record *rec = (struct index_record *)r;
     ++	slice_copy(dest, rec->last_key);
      +}
      +
      +static void index_record_copy_from(void *rec, const void *src_rec,
     -+                                   int hash_size) {
     -+  struct index_record *dst = (struct index_record *)rec;
     -+  struct index_record *src = (struct index_record *)src_rec;
     ++				   int hash_size)
     ++{
     ++	struct index_record *dst = (struct index_record *)rec;
     ++	struct index_record *src = (struct index_record *)src_rec;
      +
     -+  slice_copy(&dst->last_key, src->last_key);
     -+  dst->offset = src->offset;
     ++	slice_copy(&dst->last_key, src->last_key);
     ++	dst->offset = src->offset;
      +}
      +
     -+static void index_record_clear(void *rec) {
     -+  struct index_record *idx = (struct index_record *)rec;
     -+  free(slice_yield(&idx->last_key));
     ++static void index_record_clear(void *rec)
     ++{
     ++	struct index_record *idx = (struct index_record *)rec;
     ++	free(slice_yield(&idx->last_key));
      +}
      +
     -+static byte index_record_val_type(const void *rec) { return 0; }
     ++static byte index_record_val_type(const void *rec)
     ++{
     ++	return 0;
     ++}
      +
     -+static int index_record_encode(const void *rec, struct slice out,
     -+                               int hash_size) {
     -+  const struct index_record *r = (const struct index_record *)rec;
     -+  struct slice start = out;
     ++static int index_record_encode(const void *rec, struct slice out, int hash_size)
     ++{
     ++	const struct index_record *r = (const struct index_record *)rec;
     ++	struct slice start = out;
      +
     -+  int n = put_var_int(out, r->offset);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     ++	int n = put_var_int(out, r->offset);
     ++	if (n < 0) {
     ++		return n;
     ++	}
      +
     -+  out.buf += n;
     -+  out.len -= n;
     ++	out.buf += n;
     ++	out.len -= n;
      +
     -+  return start.len - out.len;
     ++	return start.len - out.len;
      +}
      +
      +static int index_record_decode(void *rec, struct slice key, byte val_type,
     -+                               struct slice in, int hash_size) {
     -+  struct slice start = in;
     -+  struct index_record *r = (struct index_record *)rec;
     -+  int n = 0;
     ++			       struct slice in, int hash_size)
     ++{
     ++	struct slice start = in;
     ++	struct index_record *r = (struct index_record *)rec;
     ++	int n = 0;
      +
     -+  slice_copy(&r->last_key, key);
     ++	slice_copy(&r->last_key, key);
      +
     -+  n = get_var_int(&r->offset, in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     ++	n = get_var_int(&r->offset, in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
      +
     -+  in.buf += n;
     -+  in.len -= n;
     -+  return start.len - in.len;
     ++	in.buf += n;
     ++	in.len -= n;
     ++	return start.len - in.len;
      +}
      +
      +struct record_vtable index_record_vtable = {
     -+    .key = &index_record_key,
     -+    .type = &index_record_type,
     -+    .copy_from = &index_record_copy_from,
     -+    .val_type = &index_record_val_type,
     -+    .encode = &index_record_encode,
     -+    .decode = &index_record_decode,
     -+    .clear = &index_record_clear,
     ++	.key = &index_record_key,
     ++	.type = &index_record_type,
     ++	.copy_from = &index_record_copy_from,
     ++	.val_type = &index_record_val_type,
     ++	.encode = &index_record_encode,
     ++	.decode = &index_record_decode,
     ++	.clear = &index_record_clear,
      +};
      +
     -+void record_key(struct record rec, struct slice *dest) {
     -+  rec.ops->key(rec.data, dest);
     ++void record_key(struct record rec, struct slice *dest)
     ++{
     ++	rec.ops->key(rec.data, dest);
      +}
      +
     -+byte record_type(struct record rec) { return rec.ops->type(); }
     ++byte record_type(struct record rec)
     ++{
     ++	return rec.ops->type();
     ++}
      +
     -+int record_encode(struct record rec, struct slice dest, int hash_size) {
     -+  return rec.ops->encode(rec.data, dest, hash_size);
     ++int record_encode(struct record rec, struct slice dest, int hash_size)
     ++{
     ++	return rec.ops->encode(rec.data, dest, hash_size);
      +}
      +
     -+void record_copy_from(struct record rec, struct record src, int hash_size) {
     -+  assert(src.ops->type() == rec.ops->type());
     ++void record_copy_from(struct record rec, struct record src, int hash_size)
     ++{
     ++	assert(src.ops->type() == rec.ops->type());
      +
     -+  rec.ops->copy_from(rec.data, src.data, hash_size);
     ++	rec.ops->copy_from(rec.data, src.data, hash_size);
      +}
      +
     -+byte record_val_type(struct record rec) { return rec.ops->val_type(rec.data); }
     ++byte record_val_type(struct record rec)
     ++{
     ++	return rec.ops->val_type(rec.data);
     ++}
      +
      +int record_decode(struct record rec, struct slice key, byte extra,
     -+                  struct slice src, int hash_size) {
     -+  return rec.ops->decode(rec.data, key, extra, src, hash_size);
     ++		  struct slice src, int hash_size)
     ++{
     ++	return rec.ops->decode(rec.data, key, extra, src, hash_size);
      +}
      +
     -+void record_clear(struct record rec) { return rec.ops->clear(rec.data); }
     ++void record_clear(struct record rec)
     ++{
     ++	return rec.ops->clear(rec.data);
     ++}
      +
     -+void record_from_ref(struct record *rec, struct ref_record *ref_rec) {
     -+  rec->data = ref_rec;
     -+  rec->ops = &ref_record_vtable;
     ++void record_from_ref(struct record *rec, struct ref_record *ref_rec)
     ++{
     ++	rec->data = ref_rec;
     ++	rec->ops = &ref_record_vtable;
      +}
      +
     -+void record_from_obj(struct record *rec, struct obj_record *obj_rec) {
     -+  rec->data = obj_rec;
     -+  rec->ops = &obj_record_vtable;
     ++void record_from_obj(struct record *rec, struct obj_record *obj_rec)
     ++{
     ++	rec->data = obj_rec;
     ++	rec->ops = &obj_record_vtable;
      +}
      +
     -+void record_from_index(struct record *rec, struct index_record *index_rec) {
     -+  rec->data = index_rec;
     -+  rec->ops = &index_record_vtable;
     ++void record_from_index(struct record *rec, struct index_record *index_rec)
     ++{
     ++	rec->data = index_rec;
     ++	rec->ops = &index_record_vtable;
      +}
      +
     -+void record_from_log(struct record *rec, struct log_record *log_rec) {
     -+  rec->data = log_rec;
     -+  rec->ops = &log_record_vtable;
     ++void record_from_log(struct record *rec, struct log_record *log_rec)
     ++{
     ++	rec->data = log_rec;
     ++	rec->ops = &log_record_vtable;
      +}
      +
     -+void *record_yield(struct record *rec) {
     -+  void *p = rec->data;
     -+  rec->data = NULL;
     -+  return p;
     ++void *record_yield(struct record *rec)
     ++{
     ++	void *p = rec->data;
     ++	rec->data = NULL;
     ++	return p;
      +}
      +
     -+struct ref_record *record_as_ref(struct record rec) {
     -+  assert(record_type(rec) == BLOCK_TYPE_REF);
     -+  return (struct ref_record *)rec.data;
     ++struct ref_record *record_as_ref(struct record rec)
     ++{
     ++	assert(record_type(rec) == BLOCK_TYPE_REF);
     ++	return (struct ref_record *)rec.data;
      +}
      +
     -+static bool hash_equal(byte *a, byte *b, int hash_size) {
     -+  if (a != NULL && b != NULL) {
     -+    return 0 == memcmp(a, b, hash_size);
     -+  }
     ++static bool hash_equal(byte *a, byte *b, int hash_size)
     ++{
     ++	if (a != NULL && b != NULL) {
     ++		return !memcmp(a, b, hash_size);
     ++	}
      +
     -+  return a == b;
     ++	return a == b;
      +}
      +
     -+static bool str_equal(char *a, char *b) {
     -+  if (a != NULL && b != NULL) {
     -+    return 0 == strcmp(a, b);
     -+  }
     ++static bool str_equal(char *a, char *b)
     ++{
     ++	if (a != NULL && b != NULL) {
     ++		return 0 == strcmp(a, b);
     ++	}
      +
     -+  return a == b;
     ++	return a == b;
      +}
      +
     -+bool ref_record_equal(struct ref_record *a, struct ref_record *b,
     -+                      int hash_size) {
     -+  assert(hash_size > 0);
     -+  return 0 == strcmp(a->ref_name, b->ref_name) &&
     -+         a->update_index == b->update_index &&
     -+         hash_equal(a->value, b->value, hash_size) &&
     -+         hash_equal(a->target_value, b->target_value, hash_size) &&
     -+         str_equal(a->target, b->target);
     ++bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
     ++{
     ++	assert(hash_size > 0);
     ++	return 0 == strcmp(a->ref_name, b->ref_name) &&
     ++	       a->update_index == b->update_index &&
     ++	       hash_equal(a->value, b->value, hash_size) &&
     ++	       hash_equal(a->target_value, b->target_value, hash_size) &&
     ++	       str_equal(a->target, b->target);
      +}
      +
     -+int ref_record_compare_name(const void *a, const void *b) {
     -+  return strcmp(((struct ref_record *)a)->ref_name,
     -+                ((struct ref_record *)b)->ref_name);
     ++int ref_record_compare_name(const void *a, const void *b)
     ++{
     ++	return strcmp(((struct ref_record *)a)->ref_name,
     ++		      ((struct ref_record *)b)->ref_name);
      +}
      +
     -+bool ref_record_is_deletion(const struct ref_record *ref) {
     -+  return ref->value == NULL && ref->target == NULL && ref->target_value == NULL;
     ++bool ref_record_is_deletion(const struct ref_record *ref)
     ++{
     ++	return ref->value == NULL && ref->target == NULL &&
     ++	       ref->target_value == NULL;
      +}
      +
     -+int log_record_compare_key(const void *a, const void *b) {
     -+  struct log_record *la = (struct log_record *)a;
     -+  struct log_record *lb = (struct log_record *)b;
     ++int log_record_compare_key(const void *a, const void *b)
     ++{
     ++	struct log_record *la = (struct log_record *)a;
     ++	struct log_record *lb = (struct log_record *)b;
      +
     -+  int cmp  = strcmp(la->ref_name, lb->ref_name);
     -+  if (cmp) {
     -+    return cmp;
     -+  }
     -+  if (la->update_index > lb->update_index) {
     -+    return -1;
     -+  }
     -+  return (la->update_index < lb->update_index) ? 1 : 0;
     ++	int cmp = strcmp(la->ref_name, lb->ref_name);
     ++	if (cmp) {
     ++		return cmp;
     ++	}
     ++	if (la->update_index > lb->update_index) {
     ++		return -1;
     ++	}
     ++	return (la->update_index < lb->update_index) ? 1 : 0;
      +}
      +
     -+bool log_record_is_deletion(const struct log_record *log) {
     -+  // XXX
     -+  return false;
     ++bool log_record_is_deletion(const struct log_record *log)
     ++{
     ++	/* XXX */
     ++	return false;
      +}
      
       diff --git a/reftable/record.h b/reftable/record.h
     @@ -4236,20 +4241,20 @@
      +#include "slice.h"
      +
      +struct record_vtable {
     -+  void (*key)(const void *rec, struct slice *dest);
     -+  byte (*type)(void);
     -+  void (*copy_from)(void *rec, const void *src, int hash_size);
     -+  byte (*val_type)(const void *rec);
     -+  int (*encode)(const void *rec, struct slice dest, int hash_size);
     -+  int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
     -+                int hash_size);
     -+  void (*clear)(void *rec);
     ++	void (*key)(const void *rec, struct slice *dest);
     ++	byte (*type)(void);
     ++	void (*copy_from)(void *rec, const void *src, int hash_size);
     ++	byte (*val_type)(const void *rec);
     ++	int (*encode)(const void *rec, struct slice dest, int hash_size);
     ++	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
     ++		      int hash_size);
     ++	void (*clear)(void *rec);
      +};
      +
      +/* record is a generic wrapper for differnt types of records. */
      +struct record {
     -+  void *data;
     -+  struct record_vtable *ops;
     ++	void *data;
     ++	struct record_vtable *ops;
      +};
      +
      +int get_var_int(uint64_t *dest, struct slice in);
     @@ -4262,20 +4267,20 @@
      +extern struct record_vtable ref_record_vtable;
      +
      +int encode_key(bool *restart, struct slice dest, struct slice prev_key,
     -+               struct slice key, byte extra);
     ++	       struct slice key, byte extra);
      +int decode_key(struct slice *key, byte *extra, struct slice last_key,
     -+               struct slice in);
     ++	       struct slice in);
      +
      +struct index_record {
     -+  struct slice last_key;
     -+  uint64_t offset;
     ++	struct slice last_key;
     ++	uint64_t offset;
      +};
      +
      +struct obj_record {
     -+  byte *hash_prefix;
     -+  int hash_prefix_len;
     -+  uint64_t *offsets;
     -+  int offset_len;
     ++	byte *hash_prefix;
     ++	int hash_prefix_len;
     ++	uint64_t *offsets;
     ++	int offset_len;
      +};
      +
      +void record_key(struct record rec, struct slice *dest);
     @@ -4284,7 +4289,7 @@
      +byte record_val_type(struct record rec);
      +int record_encode(struct record rec, struct slice dest, int hash_size);
      +int record_decode(struct record rec, struct slice key, byte extra,
     -+                  struct slice src, int hash_size);
     ++		  struct slice src, int hash_size);
      +void record_clear(struct record rec);
      +void *record_yield(struct record *rec);
      +void record_from_obj(struct record *rec, struct obj_record *objrec);
     @@ -4293,10 +4298,10 @@
      +void record_from_log(struct record *rec, struct log_record *logrec);
      +struct ref_record *record_as_ref(struct record ref);
      +
     -+// for qsort.
     ++/* for qsort. */
      +int ref_record_compare_name(const void *a, const void *b);
      +
     -+// for qsort.
     ++/* for qsort. */
      +int log_record_compare_key(const void *a, const void *b);
      +
      +#endif
     @@ -4316,308 +4321,327 @@
      +
      +#include "record.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "constants.h"
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void varint_roundtrip() {
     -+  uint64_t inputs[] = {0,
     -+                       1,
     -+                       27,
     -+                       127,
     -+                       128,
     -+                       257,
     -+                       4096,
     -+                       ((uint64_t)1 << 63),
     -+                       ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1};
     -+  for (int i = 0; i < ARRAYSIZE(inputs); i++) {
     -+    byte dest[10];
     -+
     -+    struct slice out = {.buf = dest, .len = 10, .cap = 10};
     -+
     -+    uint64_t in = inputs[i];
     -+    int n = put_var_int(out, in);
     -+    assert(n > 0);
     -+    out.len = n;
     -+
     -+    uint64_t got = 0;
     -+    n = get_var_int(&got, out);
     -+    assert(n > 0);
     -+
     -+    assert(got == in);
     -+  }
     -+}
     -+
     -+void test_common_prefix() {
     -+  struct {
     -+    const char *a, *b;
     -+    int want;
     -+  } cases[] = {
     -+      {"abc", "ab", 2},
     -+      {"", "abc", 0},
     -+      {"abc", "abd", 2},
     -+      {"abc", "pqr", 0},
     -+  };
     -+
     -+  for (int i = 0; i < ARRAYSIZE(cases); i++) {
     -+    struct slice a = {};
     -+    struct slice b = {};
     -+    slice_set_string(&a, cases[i].a);
     -+    slice_set_string(&b, cases[i].b);
     -+
     -+    int got = common_prefix_size(a, b);
     -+    assert(got == cases[i].want);
     -+
     -+    free(slice_yield(&a));
     -+    free(slice_yield(&b));
     -+  }
     -+}
     -+
     -+void set_hash(byte *h, int j) {
     -+  for (int i = 0; i < SHA1_SIZE; i++) {
     -+    h[i] = (j >> i) & 0xff;
     -+  }
     -+}
     -+
     -+void test_ref_record_roundtrip() {
     -+  for (int i = 0; i <= 3; i++) {
     -+    printf("subtest %d\n", i);
     -+    struct ref_record in = {};
     -+    switch (i) {
     -+      case 0:
     -+        break;
     -+      case 1:
     -+        in.value = malloc(SHA1_SIZE);
     -+        set_hash(in.value, 1);
     -+        break;
     -+      case 2:
     -+        in.value = malloc(SHA1_SIZE);
     -+        set_hash(in.value, 1);
     -+        in.target_value = malloc(SHA1_SIZE);
     -+        set_hash(in.target_value, 2);
     -+        break;
     -+      case 3:
     -+        in.target = strdup("target");
     -+        break;
     -+    }
     -+    in.ref_name = strdup("refs/heads/master");
     -+
     -+    struct record rec = {};
     -+    record_from_ref(&rec, &in);
     -+    assert(record_val_type(rec) == i);
     -+    byte buf[1024];
     -+    struct slice key = {};
     -+    record_key(rec, &key);
     -+    struct slice dest = {
     -+        .buf = buf,
     -+        .len = sizeof(buf),
     -+    };
     -+    int n = record_encode(rec, dest, SHA1_SIZE);
     -+    assert(n > 0);
     -+
     -+    struct ref_record out = {};
     -+    struct record rec_out = {};
     -+    record_from_ref(&rec_out, &out);
     -+    int m = record_decode(rec_out, key, i, dest, SHA1_SIZE);
     -+    assert(n == m);
     -+
     -+    assert((out.value != NULL) == (in.value != NULL));
     -+    assert((out.target_value != NULL) == (in.target_value != NULL));
     -+    assert((out.target != NULL) == (in.target != NULL));
     -+    free(slice_yield(&key));
     -+    record_clear(rec_out);
     -+    ref_record_clear(&in);
     -+  }
     -+}
     -+
     -+void test_log_record_roundtrip() {
     -+  struct log_record in = {
     -+      .ref_name = strdup("refs/heads/master"),
     -+      .old_hash = malloc(SHA1_SIZE),
     -+      .new_hash = malloc(SHA1_SIZE),
     -+      .name = strdup("han-wen"),
     -+      .email = strdup("hanwen@google.com"),
     -+      .message = strdup("test"),
     -+      .update_index = 42,
     -+      .time = 1577123507,
     -+      .tz_offset = 100,
     -+  };
     -+
     -+  struct record rec = {};
     -+  record_from_log(&rec, &in);
     -+
     -+  struct slice key = {};
     -+  record_key(rec, &key);
     -+
     -+  byte buf[1024];
     -+  struct slice dest = {
     -+      .buf = buf,
     -+      .len = sizeof(buf),
     -+  };
     -+
     -+  int n = record_encode(rec, dest, SHA1_SIZE);
     -+  assert(n > 0);
     -+
     -+  struct log_record out = {};
     -+  struct record rec_out = {};
     -+  record_from_log(&rec_out, &out);
     -+  int valtype = record_val_type(rec);
     -+  int m = record_decode(rec_out, key, valtype, dest, SHA1_SIZE);
     -+  assert(n == m);
     -+
     -+  assert(log_record_equal(&in, &out, SHA1_SIZE));
     -+  log_record_clear(&in);
     -+  free(slice_yield(&key));
     -+  record_clear(rec_out);
     -+}
     -+
     -+void test_u24_roundtrip() {
     -+  uint32_t in = 0x112233;
     -+  byte dest[3];
     -+
     -+  put_u24(dest, in);
     -+  uint32_t out = get_u24(dest);
     -+  assert(in == out);
     -+}
     -+
     -+void test_key_roundtrip() {
     -+  struct slice dest = {}, last_key = {}, key = {}, roundtrip = {};
     -+
     -+  slice_resize(&dest, 1024);
     -+  slice_set_string(&last_key, "refs/heads/master");
     -+  slice_set_string(&key, "refs/tags/bla");
     -+
     -+  bool restart;
     -+  byte extra = 6;
     -+  int n = encode_key(&restart, dest, last_key, key, extra);
     -+  assert(!restart);
     -+  assert(n > 0);
     -+
     -+  byte rt_extra;
     -+  int m = decode_key(&roundtrip, &rt_extra, last_key, dest);
     -+  assert(n == m);
     -+  assert(slice_equal(key, roundtrip));
     -+  assert(rt_extra == extra);
     -+
     -+  free(slice_yield(&last_key));
     -+  free(slice_yield(&key));
     -+  free(slice_yield(&dest));
     -+  free(slice_yield(&roundtrip));
     -+}
     -+
     -+void print_bytes(byte *p, int l) {
     -+  for (int i = 0; i < l; i++) {
     -+    byte c = *p;
     -+    if (c < 32) {
     -+      c = '.';
     -+    }
     -+    printf("%02x[%c] ", p[i], c);
     -+  }
     -+  printf("(%d)\n", l);
     -+}
     -+
     -+void test_obj_record_roundtrip() {
     -+  byte testHash1[SHA1_SIZE] = {};
     -+  set_hash(testHash1, 1);
     -+  uint64_t till9[] = {1, 2, 3, 4, 500, 600, 700, 800, 9000};
     -+
     -+  struct obj_record recs[3] = {{
     -+                                   .hash_prefix = testHash1,
     -+                                   .hash_prefix_len = 5,
     -+                                   .offsets = till9,
     -+                                   .offset_len = 3,
     -+                               },
     -+                               {
     -+                                   .hash_prefix = testHash1,
     -+                                   .hash_prefix_len = 5,
     -+                                   .offsets = till9,
     -+                                   .offset_len = 9,
     -+                               },
     -+                               {
     -+                                   .hash_prefix = testHash1,
     -+                                   .hash_prefix_len = 5,
     -+                               }
     -+
     -+  };
     -+  for (int i = 0; i < ARRAYSIZE(recs); i++) {
     -+    printf("subtest %d\n", i);
     -+    struct obj_record in = recs[i];
     -+    byte buf[1024];
     -+    struct record rec = {};
     -+    record_from_obj(&rec, &in);
     -+    struct slice key = {};
     -+    record_key(rec, &key);
     -+    struct slice dest = {
     -+        .buf = buf,
     -+        .len = sizeof(buf),
     -+    };
     -+    int n = record_encode(rec, dest, SHA1_SIZE);
     -+    assert(n > 0);
     -+    byte extra = record_val_type(rec);
     -+    struct obj_record out = {};
     -+    struct record rec_out = {};
     -+    record_from_obj(&rec_out, &out);
     -+    int m = record_decode(rec_out, key, extra, dest, SHA1_SIZE);
     -+    assert(n == m);
     -+
     -+    assert(in.hash_prefix_len == out.hash_prefix_len);
     -+    assert(in.offset_len == out.offset_len);
     -+
     -+    assert(0 == memcmp(in.hash_prefix, out.hash_prefix, in.hash_prefix_len));
     -+    assert(0 ==
     -+           memcmp(in.offsets, out.offsets, sizeof(uint64_t) * in.offset_len));
     -+    free(slice_yield(&key));
     -+    record_clear(rec_out);
     -+  }
     -+}
     -+
     -+void test_index_record_roundtrip() {
     -+  struct index_record in = {.offset = 42};
     -+
     -+  slice_set_string(&in.last_key, "refs/heads/master");
     -+
     -+  struct slice key = {};
     -+  struct record rec = {};
     -+  record_from_index(&rec, &in);
     -+  record_key(rec, &key);
     -+
     -+  assert(0 == slice_compare(key, in.last_key));
     -+
     -+  byte buf[1024];
     -+  struct slice dest = {
     -+      .buf = buf,
     -+      .len = sizeof(buf),
     -+  };
     -+  int n = record_encode(rec, dest, SHA1_SIZE);
     -+  assert(n > 0);
     -+
     -+  byte extra = record_val_type(rec);
     -+  struct index_record out = {};
     -+  struct record out_rec;
     -+  record_from_index(&out_rec, &out);
     -+  int m = record_decode(out_rec, key, extra, dest, SHA1_SIZE);
     -+  assert(m == n);
     -+
     -+  assert(in.offset == out.offset);
     -+
     -+  record_clear(out_rec);
     -+  free(slice_yield(&key));
     -+  free(slice_yield(&in.last_key));
     -+}
     -+
     -+int main() {
     -+  add_test_case("test_log_record_roundtrip", &test_log_record_roundtrip);
     -+  add_test_case("test_ref_record_roundtrip", &test_ref_record_roundtrip);
     -+  add_test_case("varint_roundtrip", &varint_roundtrip);
     -+  add_test_case("test_key_roundtrip", &test_key_roundtrip);
     -+  add_test_case("test_common_prefix", &test_common_prefix);
     -+  add_test_case("test_obj_record_roundtrip", &test_obj_record_roundtrip);
     -+  add_test_case("test_index_record_roundtrip", &test_index_record_roundtrip);
     -+  add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
     -+  test_main();
     ++void varint_roundtrip()
     ++{
     ++	uint64_t inputs[] = { 0,
     ++			      1,
     ++			      27,
     ++			      127,
     ++			      128,
     ++			      257,
     ++			      4096,
     ++			      ((uint64_t)1 << 63),
     ++			      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };
     ++	int i = 0;
     ++	for (i = 0; i < ARRAYSIZE(inputs); i++) {
     ++		byte dest[10];
     ++
     ++		struct slice out = { .buf = dest, .len = 10, .cap = 10 };
     ++
     ++		uint64_t in = inputs[i];
     ++		int n = put_var_int(out, in);
     ++		assert(n > 0);
     ++		out.len = n;
     ++
     ++		uint64_t got = 0;
     ++		n = get_var_int(&got, out);
     ++		assert(n > 0);
     ++
     ++		assert(got == in);
     ++	}
     ++}
     ++
     ++void test_common_prefix()
     ++{
     ++	struct {
     ++		const char *a, *b;
     ++		int want;
     ++	} cases[] = {
     ++		{ "abc", "ab", 2 },
     ++		{ "", "abc", 0 },
     ++		{ "abc", "abd", 2 },
     ++		{ "abc", "pqr", 0 },
     ++	};
     ++
     ++	int i = 0;
     ++	for (i = 0; i < ARRAYSIZE(cases); i++) {
     ++		struct slice a = {};
     ++		struct slice b = {};
     ++		slice_set_string(&a, cases[i].a);
     ++		slice_set_string(&b, cases[i].b);
     ++
     ++		int got = common_prefix_size(a, b);
     ++		assert(got == cases[i].want);
     ++
     ++		free(slice_yield(&a));
     ++		free(slice_yield(&b));
     ++	}
     ++}
     ++
     ++void set_hash(byte *h, int j)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < SHA1_SIZE; i++) {
     ++		h[i] = (j >> i) & 0xff;
     ++	}
     ++}
     ++
     ++void test_ref_record_roundtrip()
     ++{
     ++	int i = 0;
     ++	for (i = 0; i <= 3; i++) {
     ++		printf("subtest %d\n", i);
     ++		struct ref_record in = {};
     ++		switch (i) {
     ++		case 0:
     ++			break;
     ++		case 1:
     ++			in.value = malloc(SHA1_SIZE);
     ++			set_hash(in.value, 1);
     ++			break;
     ++		case 2:
     ++			in.value = malloc(SHA1_SIZE);
     ++			set_hash(in.value, 1);
     ++			in.target_value = malloc(SHA1_SIZE);
     ++			set_hash(in.target_value, 2);
     ++			break;
     ++		case 3:
     ++			in.target = strdup("target");
     ++			break;
     ++		}
     ++		in.ref_name = strdup("refs/heads/master");
     ++
     ++		struct record rec = {};
     ++		record_from_ref(&rec, &in);
     ++		assert(record_val_type(rec) == i);
     ++		byte buf[1024];
     ++		struct slice key = {};
     ++		record_key(rec, &key);
     ++		struct slice dest = {
     ++			.buf = buf,
     ++			.len = sizeof(buf),
     ++		};
     ++		int n = record_encode(rec, dest, SHA1_SIZE);
     ++		assert(n > 0);
     ++
     ++		struct ref_record out = {};
     ++		struct record rec_out = {};
     ++		record_from_ref(&rec_out, &out);
     ++		int m = record_decode(rec_out, key, i, dest, SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		assert((out.value != NULL) == (in.value != NULL));
     ++		assert((out.target_value != NULL) == (in.target_value != NULL));
     ++		assert((out.target != NULL) == (in.target != NULL));
     ++		free(slice_yield(&key));
     ++		record_clear(rec_out);
     ++		ref_record_clear(&in);
     ++	}
     ++}
     ++
     ++void test_log_record_roundtrip()
     ++{
     ++	struct log_record in = {
     ++		.ref_name = strdup("refs/heads/master"),
     ++		.old_hash = malloc(SHA1_SIZE),
     ++		.new_hash = malloc(SHA1_SIZE),
     ++		.name = strdup("han-wen"),
     ++		.email = strdup("hanwen@google.com"),
     ++		.message = strdup("test"),
     ++		.update_index = 42,
     ++		.time = 1577123507,
     ++		.tz_offset = 100,
     ++	};
     ++
     ++	struct record rec = {};
     ++	record_from_log(&rec, &in);
     ++
     ++	struct slice key = {};
     ++	record_key(rec, &key);
     ++
     ++	byte buf[1024];
     ++	struct slice dest = {
     ++		.buf = buf,
     ++		.len = sizeof(buf),
     ++	};
     ++
     ++	int n = record_encode(rec, dest, SHA1_SIZE);
     ++	assert(n > 0);
     ++
     ++	struct log_record out = {};
     ++	struct record rec_out = {};
     ++	record_from_log(&rec_out, &out);
     ++	int valtype = record_val_type(rec);
     ++	int m = record_decode(rec_out, key, valtype, dest, SHA1_SIZE);
     ++	assert(n == m);
     ++
     ++	assert(log_record_equal(&in, &out, SHA1_SIZE));
     ++	log_record_clear(&in);
     ++	free(slice_yield(&key));
     ++	record_clear(rec_out);
     ++}
     ++
     ++void test_u24_roundtrip()
     ++{
     ++	uint32_t in = 0x112233;
     ++	byte dest[3];
     ++
     ++	put_u24(dest, in);
     ++	uint32_t out = get_u24(dest);
     ++	assert(in == out);
     ++}
     ++
     ++void test_key_roundtrip()
     ++{
     ++	struct slice dest = {}, last_key = {}, key = {}, roundtrip = {};
     ++
     ++	slice_resize(&dest, 1024);
     ++	slice_set_string(&last_key, "refs/heads/master");
     ++	slice_set_string(&key, "refs/tags/bla");
     ++
     ++	bool restart;
     ++	byte extra = 6;
     ++	int n = encode_key(&restart, dest, last_key, key, extra);
     ++	assert(!restart);
     ++	assert(n > 0);
     ++
     ++	byte rt_extra;
     ++	int m = decode_key(&roundtrip, &rt_extra, last_key, dest);
     ++	assert(n == m);
     ++	assert(slice_equal(key, roundtrip));
     ++	assert(rt_extra == extra);
     ++
     ++	free(slice_yield(&last_key));
     ++	free(slice_yield(&key));
     ++	free(slice_yield(&dest));
     ++	free(slice_yield(&roundtrip));
     ++}
     ++
     ++void print_bytes(byte *p, int l)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < l; i++) {
     ++		byte c = *p;
     ++		if (c < 32) {
     ++			c = '.';
     ++		}
     ++		printf("%02x[%c] ", p[i], c);
     ++	}
     ++	printf("(%d)\n", l);
     ++}
     ++
     ++void test_obj_record_roundtrip()
     ++{
     ++	byte testHash1[SHA1_SIZE] = {};
     ++	set_hash(testHash1, 1);
     ++	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
     ++
     ++	struct obj_record recs[3] = { {
     ++					      .hash_prefix = testHash1,
     ++					      .hash_prefix_len = 5,
     ++					      .offsets = till9,
     ++					      .offset_len = 3,
     ++				      },
     ++				      {
     ++					      .hash_prefix = testHash1,
     ++					      .hash_prefix_len = 5,
     ++					      .offsets = till9,
     ++					      .offset_len = 9,
     ++				      },
     ++				      {
     ++					      .hash_prefix = testHash1,
     ++					      .hash_prefix_len = 5,
     ++				      }
     ++
     ++	};
     ++	int i = 0;
     ++	for (i = 0; i < ARRAYSIZE(recs); i++) {
     ++		printf("subtest %d\n", i);
     ++		struct obj_record in = recs[i];
     ++		byte buf[1024];
     ++		struct record rec = {};
     ++		record_from_obj(&rec, &in);
     ++		struct slice key = {};
     ++		record_key(rec, &key);
     ++		struct slice dest = {
     ++			.buf = buf,
     ++			.len = sizeof(buf),
     ++		};
     ++		int n = record_encode(rec, dest, SHA1_SIZE);
     ++		assert(n > 0);
     ++		byte extra = record_val_type(rec);
     ++		struct obj_record out = {};
     ++		struct record rec_out = {};
     ++		record_from_obj(&rec_out, &out);
     ++		int m = record_decode(rec_out, key, extra, dest, SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		assert(in.hash_prefix_len == out.hash_prefix_len);
     ++		assert(in.offset_len == out.offset_len);
     ++
     ++		assert(!memcmp(in.hash_prefix, out.hash_prefix,
     ++			       in.hash_prefix_len));
     ++		assert(0 == memcmp(in.offsets, out.offsets,
     ++				   sizeof(uint64_t) * in.offset_len));
     ++		free(slice_yield(&key));
     ++		record_clear(rec_out);
     ++	}
     ++}
     ++
     ++void test_index_record_roundtrip()
     ++{
     ++	struct index_record in = { .offset = 42 };
     ++
     ++	slice_set_string(&in.last_key, "refs/heads/master");
     ++
     ++	struct slice key = {};
     ++	struct record rec = {};
     ++	record_from_index(&rec, &in);
     ++	record_key(rec, &key);
     ++
     ++	assert(0 == slice_compare(key, in.last_key));
     ++
     ++	byte buf[1024];
     ++	struct slice dest = {
     ++		.buf = buf,
     ++		.len = sizeof(buf),
     ++	};
     ++	int n = record_encode(rec, dest, SHA1_SIZE);
     ++	assert(n > 0);
     ++
     ++	byte extra = record_val_type(rec);
     ++	struct index_record out = {};
     ++	struct record out_rec;
     ++	record_from_index(&out_rec, &out);
     ++	int m = record_decode(out_rec, key, extra, dest, SHA1_SIZE);
     ++	assert(m == n);
     ++
     ++	assert(in.offset == out.offset);
     ++
     ++	record_clear(out_rec);
     ++	free(slice_yield(&key));
     ++	free(slice_yield(&in.last_key));
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("test_log_record_roundtrip", &test_log_record_roundtrip);
     ++	add_test_case("test_ref_record_roundtrip", &test_ref_record_roundtrip);
     ++	add_test_case("varint_roundtrip", &varint_roundtrip);
     ++	add_test_case("test_key_roundtrip", &test_key_roundtrip);
     ++	add_test_case("test_common_prefix", &test_common_prefix);
     ++	add_test_case("test_obj_record_roundtrip", &test_obj_record_roundtrip);
     ++	add_test_case("test_index_record_roundtrip",
     ++		      &test_index_record_roundtrip);
     ++	add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
     ++	test_main();
      +}
      
       diff --git a/reftable/reftable.h b/reftable/reftable.h
     @@ -4636,7 +4660,7 @@
      +#ifndef REFTABLE_H
      +#define REFTABLE_H
      +
     -+#include <stdint.h>
     ++#include "system.h"
      +
      +typedef uint8_t byte;
      +typedef byte bool;
     @@ -4645,33 +4669,33 @@
      +   It is generally passed around by value.
      + */
      +struct block_source {
     -+  struct block_source_vtable *ops;
     -+  void *arg;
     ++	struct block_source_vtable *ops;
     ++	void *arg;
      +};
      +
      +/* a contiguous segment of bytes. It keeps track of its generating block_source
      +   so it can return itself into the pool.
      +*/
      +struct block {
     -+  byte *data;
     -+  int len;
     -+  struct block_source source;
     ++	byte *data;
     ++	int len;
     ++	struct block_source source;
      +};
      +
      +/* block_source_vtable are the operations that make up block_source */
      +struct block_source_vtable {
     -+  /* returns the size of a block source */
     -+  uint64_t (*size)(void *source);
     -+
     -+  /* reads a segment from the block source. It is an error to read
     -+     beyond the end of the block */
     -+  int (*read_block)(void *source, struct block *dest, uint64_t off,
     -+                    uint32_t size);
     -+  /* mark the block as read; may return the data back to malloc */
     -+  void (*return_block)(void *source, struct block *blockp);
     -+
     -+  /* release all resources associated with the block source */
     -+  void (*close)(void *source);
     ++	/* returns the size of a block source */
     ++	uint64_t (*size)(void *source);
     ++
     ++	/* reads a segment from the block source. It is an error to read
     ++	   beyond the end of the block */
     ++	int (*read_block)(void *source, struct block *dest, uint64_t off,
     ++			  uint32_t size);
     ++	/* mark the block as read; may return the data back to malloc */
     ++	void (*return_block)(void *source, struct block *blockp);
     ++
     ++	/* release all resources associated with the block source */
     ++	void (*close)(void *source);
      +};
      +
      +/* opens a file on the file system as a block_source */
     @@ -4679,26 +4703,27 @@
      +
      +/* write_options sets options for writing a single reftable. */
      +struct write_options {
     -+  /* do not pad out blocks to block size. */
     -+  bool unpadded;
     ++	/* do not pad out blocks to block size. */
     ++	bool unpadded;
      +
     -+  /* the blocksize. Should be less than 2^24. */
     -+  uint32_t block_size;
     ++	/* the blocksize. Should be less than 2^24. */
     ++	uint32_t block_size;
      +
     -+  /* do not generate a SHA1 => ref index. */
     -+  bool skip_index_objects;
     ++	/* do not generate a SHA1 => ref index. */
     ++	bool skip_index_objects;
      +
     -+  /* how often to write complete keys in each block. */
     -+  int restart_interval;
     ++	/* how often to write complete keys in each block. */
     ++	int restart_interval;
      +};
      +
      +/* ref_record holds a ref database entry target_value */
      +struct ref_record {
     -+  char *ref_name;        /* Name of the ref, malloced. */
     -+  uint64_t update_index; /* Logical timestamp at which this value is written */
     -+  byte *value;           /* SHA1, or NULL. malloced. */
     -+  byte *target_value;    /* peeled annotated tag, or NULL. malloced. */
     -+  char *target;          /* symref, or NULL. malloced. */
     ++	char *ref_name; /* Name of the ref, malloced. */
     ++	uint64_t update_index; /* Logical timestamp at which this value is
     ++				  written */
     ++	byte *value; /* SHA1, or NULL. malloced. */
     ++	byte *target_value; /* peeled annotated tag, or NULL. malloced. */
     ++	char *target; /* symref, or NULL. malloced. */
      +};
      +
      +/* returns whether 'ref' represents a deletion */
     @@ -4712,19 +4737,19 @@
      +
      +/* returns whether two ref_records are the same */
      +bool ref_record_equal(struct ref_record *a, struct ref_record *b,
     -+                      int hash_size);
     ++		      int hash_size);
      +
      +/* log_record holds a reflog entry */
      +struct log_record {
     -+  char *ref_name;
     -+  uint64_t update_index;
     -+  byte *new_hash;
     -+  byte *old_hash;
     -+  char *name;
     -+  char *email;
     -+  uint64_t time;
     -+  int16_t tz_offset;
     -+  char *message;
     ++	char *ref_name;
     ++	uint64_t update_index;
     ++	byte *new_hash;
     ++	byte *old_hash;
     ++	char *name;
     ++	char *email;
     ++	uint64_t time;
     ++	int16_t tz_offset;
     ++	char *message;
      +};
      +
      +/* returns whether 'ref' represents the deletion of a log record. */
     @@ -4735,7 +4760,7 @@
      +
      +/* returns whether two records are equal. */
      +bool log_record_equal(struct log_record *a, struct log_record *b,
     -+                      int hash_size);
     ++		      int hash_size);
      +
      +void log_record_print(struct log_record *log, int hash_size);
      +
     @@ -4743,8 +4768,8 @@
      +   reftable. It is generally passed around by value.
      +*/
      +struct iterator {
     -+  struct iterator_vtable *ops;
     -+  void *iter_arg;
     ++	struct iterator_vtable *ops;
     ++	void *iter_arg;
      +};
      +
      +/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
     @@ -4762,38 +4787,39 @@
      +
      +/* block_stats holds statistics for a single block type */
      +struct block_stats {
     -+  /* total number of entries written */
     -+  int entries;
     -+  /* total number of key restarts */
     -+  int restarts;
     -+  /* total number of blocks */
     -+  int blocks;
     -+  /* total number of index blocks */
     -+  int index_blocks;
     -+  /* depth of the index */
     -+  int max_index_level;
     -+
     -+  /* offset of the first block for this type */
     -+  uint64_t offset;
     -+  /* offset of the top level index block for this type, or 0 if not present */
     -+  uint64_t index_offset;
     ++	/* total number of entries written */
     ++	int entries;
     ++	/* total number of key restarts */
     ++	int restarts;
     ++	/* total number of blocks */
     ++	int blocks;
     ++	/* total number of index blocks */
     ++	int index_blocks;
     ++	/* depth of the index */
     ++	int max_index_level;
     ++
     ++	/* offset of the first block for this type */
     ++	uint64_t offset;
     ++	/* offset of the top level index block for this type, or 0 if not
     ++	 * present */
     ++	uint64_t index_offset;
      +};
      +
      +/* stats holds overall statistics for a single reftable */
      +struct stats {
     -+  /* total number of blocks written. */
     -+  int blocks;
     -+  /* stats for ref data */
     -+  struct block_stats ref_stats;
     -+  /* stats for the SHA1 to ref map. */
     -+  struct block_stats obj_stats;
     -+  /* stats for index blocks */
     -+  struct block_stats idx_stats;
     -+  /* stats for log blocks */
     -+  struct block_stats log_stats;
     -+
     -+  /* disambiguation length of shortened object IDs. */
     -+  int object_id_len;
     ++	/* total number of blocks written. */
     ++	int blocks;
     ++	/* stats for ref data */
     ++	struct block_stats ref_stats;
     ++	/* stats for the SHA1 to ref map. */
     ++	struct block_stats obj_stats;
     ++	/* stats for index blocks */
     ++	struct block_stats idx_stats;
     ++	/* stats for log blocks */
     ++	struct block_stats log_stats;
     ++
     ++	/* disambiguation length of shortened object IDs. */
     ++	int object_id_len;
      +};
      +
      +/* different types of errors */
     @@ -4814,6 +4840,7 @@
      +#define LOCK_ERROR -5
      +
      +/* Misuse of the API:
     ++   - on writing a record with NULL ref_name.
      +   - on writing a ref_record outside the table limits
      +   - on writing a ref or log record before the stack's next_update_index
      +   - on reading a ref_record from log iterator, or vice versa.
     @@ -4827,7 +4854,7 @@
      +
      +/* new_writer creates a new writer */
      +struct writer *new_writer(int (*writer_func)(void *, byte *, int),
     -+                          void *writer_arg, struct write_options *opts);
     ++			  void *writer_arg, struct write_options *opts);
      +
      +/* write to a file descriptor. fdp should be an int* pointing to the fd. */
      +int fd_writer(void *fdp, byte *data, int size);
     @@ -4857,7 +4884,6 @@
      +   key before adding. */
      +int writer_add_logs(struct writer *w, struct log_record *logs, int n);
      +
     -+
      +/* writer_close finalizes the reftable. The writer is retained so statistics can
      + * be inspected. */
      +int writer_close(struct writer *w);
     @@ -4905,7 +4931,7 @@
      +
      +/* seek to logs for the given name, older than update_index. */
      +int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
     -+                       uint64_t update_index);
     ++		       uint64_t update_index);
      +
      +/* seek to newest log entry for given name. */
      +int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
     @@ -4915,7 +4941,7 @@
      +
      +/* return an iterator for the refs pointing to oid */
      +int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
     -+                    int oid_len);
     ++		    int oid_len);
      +
      +/* return the max_update_index for a table */
      +uint64_t reader_max_update_index(struct reader *r);
     @@ -4933,15 +4959,15 @@
      +
      +/* returns an iterator positioned just before 'name' */
      +int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
     -+                          const char *name);
     ++			  const char *name);
      +
      +/* returns an iterator for log entry, at given update_index */
      +int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
     -+                             const char *name, uint64_t update_index);
     ++			     const char *name, uint64_t update_index);
      +
      +/* like merged_table_seek_log_at but look for the newest entry. */
      +int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
     -+                          const char *name);
     ++			  const char *name);
      +
      +/* returns the max update_index covered by this merged table. */
      +uint64_t merged_max_update_index(struct merged_table *mt);
     @@ -4964,7 +4990,7 @@
      +   .git/refs respectively.
      +*/
      +int new_stack(struct stack **dest, const char *dir, const char *list_file,
     -+              struct write_options config);
     ++	      struct write_options config);
      +
      +/* returns the update_index at which a next table should be written. */
      +uint64_t stack_next_update_index(struct stack *st);
     @@ -4972,8 +4998,8 @@
      +/* add a new table to the stack. The write_table function must call
      +   writer_set_limits, add refs and return an error value. */
      +int stack_add(struct stack *st,
     -+              int (*write_table)(struct writer *wr, void *write_arg),
     -+              void *write_arg);
     ++	      int (*write_table)(struct writer *wr, void *write_arg),
     ++	      void *write_arg);
      +
      +/* returns the merged_table for seeking. This table is valid until the
      +   next write or reload, and should not be closed or deleted.
     @@ -4988,14 +5014,15 @@
      +
      +/* Policy for expiring reflog entries. */
      +struct log_expiry_config {
     -+  /* Drop entries older than this timestamp */
     -+  uint64_t time;
     ++	/* Drop entries older than this timestamp */
     ++	uint64_t time;
      +
     -+  /* Drop older entries */
     -+  uint64_t min_update_index;
     ++	/* Drop older entries */
     ++	uint64_t min_update_index;
      +};
      +
     -+/* compacts all reftables into a giant table. Expire reflog entries if config is non-NULL */
     ++/* compacts all reftables into a giant table. Expire reflog entries if config is
     ++ * non-NULL */
      +int stack_compact_all(struct stack *st, struct log_expiry_config *config);
      +
      +/* heuristically compact unbalanced table stack. */
     @@ -5004,18 +5031,18 @@
      +/* convenience function to read a single ref. Returns < 0 for error, 0
      +   for success, and 1 if ref not found. */
      +int stack_read_ref(struct stack *st, const char *refname,
     -+                   struct ref_record *ref);
     ++		   struct ref_record *ref);
      +
      +/* convenience function to read a single log. Returns < 0 for error, 0
      +   for success, and 1 if ref not found. */
      +int stack_read_log(struct stack *st, const char *refname,
     -+                   struct log_record *log);
     ++		   struct log_record *log);
      +
      +/* statistics on past compactions. */
      +struct compaction_stats {
     -+  uint64_t bytes;
     -+  int attempts;
     -+  int failures;
     ++	uint64_t bytes;
     ++	int attempts;
     ++	int failures;
      +};
      +
      +struct compaction_stats *stack_compaction_stats(struct stack *st);
     @@ -5037,7 +5064,7 @@
      +
      +#include "reftable.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "block.h"
     @@ -5048,418 +5075,465 @@
      +
      +static const int update_index = 5;
      +
     -+void test_buffer(void) {
     -+  struct slice buf = {};
     -+
     -+  byte in[] = "hello";
     -+  slice_write(&buf, in, sizeof(in));
     -+  struct block_source source;
     -+  block_source_from_slice(&source, &buf);
     -+  assert(block_source_size(source) == 6);
     -+  struct block out = {};
     -+  int n = block_source_read_block(source, &out, 0, sizeof(in));
     -+  assert(n == sizeof(in));
     -+  assert(0 == memcmp(in, out.data, n));
     -+  block_source_return_block(source, &out);
     -+
     -+  n = block_source_read_block(source, &out, 1, 2);
     -+  assert(n == 2);
     -+  assert(0 == memcmp(out.data, "el", 2));
     -+
     -+  block_source_return_block(source, &out);
     -+  block_source_close(&source);
     -+  free(slice_yield(&buf));
     -+}
     -+
     -+void write_table(char ***names, struct slice *buf, int N, int block_size) {
     -+  *names = calloc(sizeof(char *), N + 1);
     -+
     -+  struct write_options opts = {
     -+      .block_size = block_size,
     -+  };
     -+
     -+  struct writer *w = new_writer(&slice_write_void, buf, &opts);
     -+
     -+  writer_set_limits(w, update_index, update_index);
     -+  {
     -+    struct ref_record ref = {};
     -+    for (int i = 0; i < N; i++) {
     -+      byte hash[SHA1_SIZE];
     -+      set_test_hash(hash, i);
     -+
     -+      char name[100];
     -+      sprintf(name, "refs/heads/branch%02d", i);
     -+
     -+      ref.ref_name = name;
     -+      ref.value = hash;
     -+      ref.update_index = update_index;
     -+      (*names)[i] = strdup(name);
     -+
     -+      fflush(stdout);
     -+      int n = writer_add_ref(w, &ref);
     -+      assert(n == 0);
     -+    }
     -+  }
     -+  int n = writer_close(w);
     -+  assert(n == 0);
     -+
     -+  struct stats *stats = writer_stats(w);
     -+  for (int i = 0; i < stats->ref_stats.blocks; i++) {
     -+    int off = i * opts.block_size;
     -+    if (off == 0) {
     -+      off = HEADER_SIZE;
     -+    }
     -+    assert(buf->buf[off] == 'r');
     -+  }
     -+
     -+  writer_free(w);
     -+  w = NULL;
     -+}
     -+
     -+void test_log_write_read(void) {
     -+  int N = 2;
     -+  char **names = calloc(sizeof(char *), N + 1);
     -+
     -+  struct write_options opts = {
     -+      .block_size = 256,
     -+  };
     -+
     -+  struct slice buf = {};
     -+  struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     -+
     -+  writer_set_limits(w, 0, N);
     -+  {
     -+    struct ref_record ref = {};
     -+    for (int i = 0; i < N; i++) {
     -+      char name[256];
     -+      sprintf(name, "b%02d%0*d", i, 130, 7);
     -+      names[i] = strdup(name);
     -+      puts(name);
     -+      ref.ref_name = name;
     -+      ref.update_index = i;
     -+
     -+      int err = writer_add_ref(w, &ref);
     -+      assert_err(err);
     -+    }
     -+  }
     -+
     -+  {
     -+    struct log_record log = {};
     -+    for (int i = 0; i < N; i++) {
     -+      byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     -+      set_test_hash(hash1, i);
     -+      set_test_hash(hash2, i + 1);
     -+
     -+      log.ref_name = names[i];
     -+      log.update_index = i;
     -+      log.old_hash = hash1;
     -+      log.new_hash = hash2;
     -+
     -+      int err = writer_add_log(w, &log);
     -+      assert_err(err);
     -+    }
     -+  }
     -+
     -+  int n = writer_close(w);
     -+  assert(n == 0);
     -+
     -+  struct stats *stats = writer_stats(w);
     -+  assert(stats->log_stats.blocks > 0);
     -+  writer_free(w);
     -+  w = NULL;
     -+
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  struct reader rd = {};
     -+  int err = init_reader(&rd, source, "file.log");
     -+  assert(err == 0);
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_ref(&rd, &it, names[N-1]);
     -+    assert(err == 0);
     -+
     -+    struct ref_record ref = {};
     -+    err = iterator_next_ref(it, &ref);
     -+    assert_err(err);
     -+
     -+    // end of iteration.
     -+    err = iterator_next_ref(it, &ref);
     -+    assert(0 < err);
     -+
     -+    iterator_destroy(&it);
     -+    ref_record_clear(&ref);
     -+  }
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_log(&rd, &it, "");
     -+    assert(err == 0);
     -+
     -+    struct log_record log = {};
     -+    int i = 0;
     -+    while (true) {
     -+      int err = iterator_next_log(it, &log);
     -+      if (err > 0) {
     -+        break;
     -+      }
     -+
     -+      assert_err(err);
     -+      assert_streq(names[i], log.ref_name);
     -+      assert(i == log.update_index);
     -+      i++;
     -+    }
     -+
     -+    assert(i == N);
     -+    iterator_destroy(&it);
     -+  }
     -+
     -+  // cleanup.
     -+  free(slice_yield(&buf));
     -+  free_names(names);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_read_write_sequential(void) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 50;
     -+  write_table(&names, &buf, N, 256);
     -+
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  struct reader rd = {};
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+
     -+  struct iterator it = {};
     -+  err = reader_seek_ref(&rd, &it, "");
     -+  assert(err == 0);
     -+
     -+  int j = 0;
     -+  while (true) {
     -+    struct ref_record ref = {};
     -+    int r = iterator_next_ref(it, &ref);
     -+    assert(r >= 0);
     -+    if (r > 0) {
     -+      break;
     -+    }
     -+    assert(0 == strcmp(names[j], ref.ref_name));
     -+    assert(update_index == ref.update_index);
     -+
     -+    j++;
     -+    ref_record_clear(&ref);
     -+  }
     -+  assert(j == N);
     -+  iterator_destroy(&it);
     -+  free(slice_yield(&buf));
     -+  free_names(names);
     -+
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_write_small_table(void) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 1;
     -+  write_table(&names, &buf, N, 4096);
     -+  assert(buf.len < 200);
     -+  free(slice_yield(&buf));
     -+  free_names(names);
     -+}
     -+
     -+void test_table_read_api(void) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 50;
     -+  write_table(&names, &buf, N, 256);
     -+
     -+  struct reader rd = {};
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+
     -+  struct iterator it = {};
     -+  err = reader_seek_ref(&rd, &it, names[0]);
     -+  assert(err == 0);
     -+
     -+  struct log_record log = {};
     -+  err = iterator_next_log(it, &log);
     -+  assert(err == API_ERROR);
     -+
     -+  free(slice_yield(&buf));
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+  free(names);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_read_write_seek(bool index) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 50;
     -+  write_table(&names, &buf, N, 256);
     -+
     -+  struct reader rd = {};
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+
     -+  if (!index) {
     -+    rd.ref_offsets.index_offset = 0;
     -+  }
     -+
     -+  for (int i = 1; i < N; i++) {
     -+    struct iterator it = {};
     -+    int err = reader_seek_ref(&rd, &it, names[i]);
     -+    assert(err == 0);
     -+    struct ref_record ref = {};
     -+    err = iterator_next_ref(it, &ref);
     -+    assert(err == 0);
     -+    assert(0 == strcmp(names[i], ref.ref_name));
     -+    assert(i == ref.value[0]);
     -+
     -+    ref_record_clear(&ref);
     -+    iterator_destroy(&it);
     -+  }
     -+
     -+  free(slice_yield(&buf));
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+  free(names);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_read_write_seek_linear(void) {
     -+  test_table_read_write_seek(false);
     -+}
     -+
     -+void test_table_read_write_seek_index(void) {
     -+  test_table_read_write_seek(true);
     -+}
     -+
     -+void test_table_refs_for(bool indexed) {
     -+  int N = 50;
     -+
     -+  char **want_names = calloc(sizeof(char *), N + 1);
     -+
     -+  int want_names_len = 0;
     -+  byte want_hash[SHA1_SIZE];
     -+  set_test_hash(want_hash, 4);
     -+
     -+  struct write_options opts = {
     -+      .block_size = 256,
     -+  };
     -+
     -+  struct slice buf = {};
     -+  struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     -+  {
     -+    struct ref_record ref = {};
     -+    for (int i = 0; i < N; i++) {
     -+      byte hash[SHA1_SIZE];
     -+      memset(hash, i, sizeof(hash));
     -+      char fill[51] = {};
     -+      memset(fill, 'x', 50);
     -+      char name[100];
     -+      // Put the variable part in the start
     -+      sprintf(name, "br%02d%s", i, fill);
     -+      name[40] = 0;
     -+      ref.ref_name = name;
     -+
     -+      byte hash1[SHA1_SIZE];
     -+      byte hash2[SHA1_SIZE];
     -+
     -+      set_test_hash(hash1, i / 4);
     -+      set_test_hash(hash2, 3 + i / 4);
     -+      ref.value = hash1;
     -+      ref.target_value = hash2;
     -+
     -+      // 80 bytes / entry, so 3 entries per block. Yields 17 blocks.
     -+      int n = writer_add_ref(w, &ref);
     -+      assert(n == 0);
     -+
     -+      if (0 == memcmp(hash1, want_hash, SHA1_SIZE) ||
     -+          0 == memcmp(hash2, want_hash, SHA1_SIZE)) {
     -+        want_names[want_names_len++] = strdup(name);
     -+      }
     -+    }
     -+  }
     -+
     -+  int n = writer_close(w);
     -+  assert(n == 0);
     -+
     -+  writer_free(w);
     -+  w = NULL;
     -+
     -+  struct reader rd;
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+  if (!indexed) {
     -+    rd.obj_offsets.present = 0;
     -+  }
     -+
     -+  struct iterator it = {};
     -+  err = reader_seek_ref(&rd, &it, "");
     -+  assert(err == 0);
     -+  iterator_destroy(&it);
     -+
     -+  err = reader_refs_for(&rd, &it, want_hash, SHA1_SIZE);
     -+  assert(err == 0);
     -+
     -+  struct ref_record ref = {};
     -+
     -+  int j = 0;
     -+  while (true) {
     -+    int err = iterator_next_ref(it, &ref);
     -+    assert(err >= 0);
     -+    if (err > 0) {
     -+      break;
     -+    }
     -+
     -+    assert(j < want_names_len);
     -+    assert(0 == strcmp(ref.ref_name, want_names[j]));
     -+    j++;
     -+    ref_record_clear(&ref);
     -+  }
     -+  assert(j == want_names_len);
     -+
     -+  free(slice_yield(&buf));
     -+  free_names(want_names);
     -+  iterator_destroy(&it);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_refs_for_no_index(void) { test_table_refs_for(false); }
     -+
     -+void test_table_refs_for_obj_index(void) { test_table_refs_for(true); }
     -+
     -+int main() {
     -+  add_test_case("test_log_write_read", test_log_write_read);
     -+  add_test_case("test_table_write_small_table", &test_table_write_small_table);
     -+  add_test_case("test_buffer", &test_buffer);
     -+  add_test_case("test_table_read_api", &test_table_read_api);
     -+  add_test_case("test_table_read_write_sequential",
     -+                &test_table_read_write_sequential);
     -+  add_test_case("test_table_read_write_seek_linear",
     -+                &test_table_read_write_seek_linear);
     -+  add_test_case("test_table_read_write_seek_index",
     -+                &test_table_read_write_seek_index);
     -+  add_test_case("test_table_read_write_refs_for_no_index",
     -+                &test_table_refs_for_no_index);
     -+  add_test_case("test_table_read_write_refs_for_obj_index",
     -+                &test_table_refs_for_obj_index);
     -+  test_main();
     ++void test_buffer(void)
     ++{
     ++	struct slice buf = {};
     ++
     ++	byte in[] = "hello";
     ++	slice_write(&buf, in, sizeof(in));
     ++	struct block_source source;
     ++	block_source_from_slice(&source, &buf);
     ++	assert(block_source_size(source) == 6);
     ++	struct block out = {};
     ++	int n = block_source_read_block(source, &out, 0, sizeof(in));
     ++	assert(n == sizeof(in));
     ++	assert(!memcmp(in, out.data, n));
     ++	block_source_return_block(source, &out);
     ++
     ++	n = block_source_read_block(source, &out, 1, 2);
     ++	assert(n == 2);
     ++	assert(!memcmp(out.data, "el", 2));
     ++
     ++	block_source_return_block(source, &out);
     ++	block_source_close(&source);
     ++	free(slice_yield(&buf));
     ++}
     ++
     ++void write_table(char ***names, struct slice *buf, int N, int block_size)
     ++{
     ++	*names = calloc(sizeof(char *), N + 1);
     ++
     ++	struct write_options opts = {
     ++		.block_size = block_size,
     ++	};
     ++
     ++	struct writer *w = new_writer(&slice_write_void, buf, &opts);
     ++
     ++	writer_set_limits(w, update_index, update_index);
     ++	{
     ++		struct ref_record ref = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash[SHA1_SIZE];
     ++			set_test_hash(hash, i);
     ++
     ++			char name[100];
     ++			snprintf(name, sizeof(name), "refs/heads/branch%02d",
     ++				 i);
     ++
     ++			ref.ref_name = name;
     ++			ref.value = hash;
     ++			ref.update_index = update_index;
     ++			(*names)[i] = strdup(name);
     ++
     ++			int n = writer_add_ref(w, &ref);
     ++			assert(n == 0);
     ++		}
     ++	}
     ++	{
     ++		struct log_record log = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash[SHA1_SIZE];
     ++			set_test_hash(hash, i);
     ++
     ++			char name[100];
     ++			snprintf(name, sizeof(name), "refs/heads/branch%02d",
     ++				 i);
     ++
     ++			log.ref_name = name;
     ++			log.new_hash = hash;
     ++			log.update_index = update_index;
     ++			log.message = "message";
     ++
     ++			int n = writer_add_log(w, &log);
     ++			assert(n == 0);
     ++		}
     ++	}
     ++
     ++	int n = writer_close(w);
     ++	assert(n == 0);
     ++
     ++	struct stats *stats = writer_stats(w);
     ++	int i = 0;
     ++	for (i = 0; i < stats->ref_stats.blocks; i++) {
     ++		int off = i * opts.block_size;
     ++		if (off == 0) {
     ++			off = HEADER_SIZE;
     ++		}
     ++		assert(buf->buf[off] == 'r');
     ++	}
     ++
     ++	writer_free(w);
     ++	w = NULL;
     ++}
     ++
     ++void test_log_write_read(void)
     ++{
     ++	int N = 2;
     ++	char **names = calloc(sizeof(char *), N + 1);
     ++
     ++	struct write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++
     ++	struct slice buf = {};
     ++	struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     ++
     ++	writer_set_limits(w, 0, N);
     ++	{
     ++		struct ref_record ref = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			char name[256];
     ++			snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
     ++			names[i] = strdup(name);
     ++			puts(name);
     ++			ref.ref_name = name;
     ++			ref.update_index = i;
     ++
     ++			int err = writer_add_ref(w, &ref);
     ++			assert_err(err);
     ++		}
     ++	}
     ++
     ++	{
     ++		struct log_record log = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     ++			set_test_hash(hash1, i);
     ++			set_test_hash(hash2, i + 1);
     ++
     ++			log.ref_name = names[i];
     ++			log.update_index = i;
     ++			log.old_hash = hash1;
     ++			log.new_hash = hash2;
     ++
     ++			int err = writer_add_log(w, &log);
     ++			assert_err(err);
     ++		}
     ++	}
     ++
     ++	int n = writer_close(w);
     ++	assert(n == 0);
     ++
     ++	struct stats *stats = writer_stats(w);
     ++	assert(stats->log_stats.blocks > 0);
     ++	writer_free(w);
     ++	w = NULL;
     ++
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	struct reader rd = {};
     ++	int err = init_reader(&rd, source, "file.log");
     ++	assert(err == 0);
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_ref(&rd, &it, names[N - 1]);
     ++		assert(err == 0);
     ++
     ++		struct ref_record ref = {};
     ++		err = iterator_next_ref(it, &ref);
     ++		assert_err(err);
     ++
     ++		/* end of iteration. */
     ++		err = iterator_next_ref(it, &ref);
     ++		assert(0 < err);
     ++
     ++		iterator_destroy(&it);
     ++		ref_record_clear(&ref);
     ++	}
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_log(&rd, &it, "");
     ++		assert(err == 0);
     ++
     ++		struct log_record log = {};
     ++		int i = 0;
     ++		while (true) {
     ++			int err = iterator_next_log(it, &log);
     ++			if (err > 0) {
     ++				break;
     ++			}
     ++
     ++			assert_err(err);
     ++			assert_streq(names[i], log.ref_name);
     ++			assert(i == log.update_index);
     ++			i++;
     ++		}
     ++
     ++		assert(i == N);
     ++		iterator_destroy(&it);
     ++	}
     ++
     ++	/* cleanup. */
     ++	free(slice_yield(&buf));
     ++	free_names(names);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_read_write_sequential(void)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 50;
     ++	write_table(&names, &buf, N, 256);
     ++
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	struct reader rd = {};
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++
     ++	struct iterator it = {};
     ++	err = reader_seek_ref(&rd, &it, "");
     ++	assert(err == 0);
     ++
     ++	int j = 0;
     ++	while (true) {
     ++		struct ref_record ref = {};
     ++		int r = iterator_next_ref(it, &ref);
     ++		assert(r >= 0);
     ++		if (r > 0) {
     ++			break;
     ++		}
     ++		assert(0 == strcmp(names[j], ref.ref_name));
     ++		assert(update_index == ref.update_index);
     ++
     ++		j++;
     ++		ref_record_clear(&ref);
     ++	}
     ++	assert(j == N);
     ++	iterator_destroy(&it);
     ++	free(slice_yield(&buf));
     ++	free_names(names);
     ++
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_write_small_table(void)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 1;
     ++	write_table(&names, &buf, N, 4096);
     ++	assert(buf.len < 200);
     ++	free(slice_yield(&buf));
     ++	free_names(names);
     ++}
     ++
     ++void test_table_read_api(void)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 50;
     ++	write_table(&names, &buf, N, 256);
     ++
     ++	struct reader rd = {};
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++
     ++	struct iterator it = {};
     ++	err = reader_seek_ref(&rd, &it, names[0]);
     ++	assert(err == 0);
     ++
     ++	struct log_record log = {};
     ++	err = iterator_next_log(it, &log);
     ++	assert(err == API_ERROR);
     ++
     ++	free(slice_yield(&buf));
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++	free(names);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_read_write_seek(bool index)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 50;
     ++	write_table(&names, &buf, N, 256);
     ++
     ++	struct reader rd = {};
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++
     ++	if (!index) {
     ++		rd.ref_offsets.index_offset = 0;
     ++	}
     ++
     ++	int i = 0;
     ++	for (i = 1; i < N; i++) {
     ++		struct iterator it = {};
     ++		int err = reader_seek_ref(&rd, &it, names[i]);
     ++		assert(err == 0);
     ++		struct ref_record ref = {};
     ++		err = iterator_next_ref(it, &ref);
     ++		assert(err == 0);
     ++		assert(0 == strcmp(names[i], ref.ref_name));
     ++		assert(i == ref.value[0]);
     ++
     ++		ref_record_clear(&ref);
     ++		iterator_destroy(&it);
     ++	}
     ++
     ++	free(slice_yield(&buf));
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++	free(names);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_read_write_seek_linear(void)
     ++{
     ++	test_table_read_write_seek(false);
     ++}
     ++
     ++void test_table_read_write_seek_index(void)
     ++{
     ++	test_table_read_write_seek(true);
     ++}
     ++
     ++void test_table_refs_for(bool indexed)
     ++{
     ++	int N = 50;
     ++
     ++	char **want_names = calloc(sizeof(char *), N + 1);
     ++
     ++	int want_names_len = 0;
     ++	byte want_hash[SHA1_SIZE];
     ++	set_test_hash(want_hash, 4);
     ++
     ++	struct write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++
     ++	struct slice buf = {};
     ++	struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     ++	{
     ++		struct ref_record ref = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash[SHA1_SIZE];
     ++			memset(hash, i, sizeof(hash));
     ++			char fill[51] = {};
     ++			memset(fill, 'x', 50);
     ++			char name[100];
     ++			/* Put the variable part in the start */
     ++			snprintf(name, sizeof(name), "br%02d%s", i, fill);
     ++			name[40] = 0;
     ++			ref.ref_name = name;
     ++
     ++			byte hash1[SHA1_SIZE];
     ++			byte hash2[SHA1_SIZE];
     ++
     ++			set_test_hash(hash1, i / 4);
     ++			set_test_hash(hash2, 3 + i / 4);
     ++			ref.value = hash1;
     ++			ref.target_value = hash2;
     ++
     ++			/* 80 bytes / entry, so 3 entries per block. Yields 17 */
     ++			/* blocks. */
     ++			int n = writer_add_ref(w, &ref);
     ++			assert(n == 0);
     ++
     ++			if (!memcmp(hash1, want_hash, SHA1_SIZE) ||
     ++			    !memcmp(hash2, want_hash, SHA1_SIZE)) {
     ++				want_names[want_names_len++] = strdup(name);
     ++			}
     ++		}
     ++	}
     ++
     ++	int n = writer_close(w);
     ++	assert(n == 0);
     ++
     ++	writer_free(w);
     ++	w = NULL;
     ++
     ++	struct reader rd;
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++	if (!indexed) {
     ++		rd.obj_offsets.present = 0;
     ++	}
     ++
     ++	struct iterator it = {};
     ++	err = reader_seek_ref(&rd, &it, "");
     ++	assert(err == 0);
     ++	iterator_destroy(&it);
     ++
     ++	err = reader_refs_for(&rd, &it, want_hash, SHA1_SIZE);
     ++	assert(err == 0);
     ++
     ++	struct ref_record ref = {};
     ++
     ++	int j = 0;
     ++	while (true) {
     ++		int err = iterator_next_ref(it, &ref);
     ++		assert(err >= 0);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++
     ++		assert(j < want_names_len);
     ++		assert(0 == strcmp(ref.ref_name, want_names[j]));
     ++		j++;
     ++		ref_record_clear(&ref);
     ++	}
     ++	assert(j == want_names_len);
     ++
     ++	free(slice_yield(&buf));
     ++	free_names(want_names);
     ++	iterator_destroy(&it);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_refs_for_no_index(void)
     ++{
     ++	test_table_refs_for(false);
     ++}
     ++
     ++void test_table_refs_for_obj_index(void)
     ++{
     ++	test_table_refs_for(true);
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("test_log_write_read", test_log_write_read);
     ++	add_test_case("test_table_write_small_table",
     ++		      &test_table_write_small_table);
     ++	add_test_case("test_buffer", &test_buffer);
     ++	add_test_case("test_table_read_api", &test_table_read_api);
     ++	add_test_case("test_table_read_write_sequential",
     ++		      &test_table_read_write_sequential);
     ++	add_test_case("test_table_read_write_seek_linear",
     ++		      &test_table_read_write_seek_linear);
     ++	add_test_case("test_table_read_write_seek_index",
     ++		      &test_table_read_write_seek_index);
     ++	add_test_case("test_table_read_write_refs_for_no_index",
     ++		      &test_table_refs_for_no_index);
     ++	add_test_case("test_table_read_write_refs_for_obj_index",
     ++		      &test_table_refs_for_obj_index);
     ++	test_main();
      +}
      
       diff --git a/reftable/slice.c b/reftable/slice.c
     @@ -5477,174 +5551,194 @@
      +
      +#include "slice.h"
      +
     -+#include <assert.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     -+void slice_set_string(struct slice *s, const char *str) {
     -+  if (str == NULL) {
     -+    s->len = 0;
     -+    return;
     -+  }
     ++void slice_set_string(struct slice *s, const char *str)
     ++{
     ++	if (str == NULL) {
     ++		s->len = 0;
     ++		return;
     ++	}
      +
     -+  {
     -+    int l = strlen(str);
     -+    l++;  // \0
     -+    slice_resize(s, l);
     -+    memcpy(s->buf, str, l);
     -+    s->len = l - 1;
     -+  }
     ++	{
     ++		int l = strlen(str);
     ++		l++; /* \0 */
     ++		slice_resize(s, l);
     ++		memcpy(s->buf, str, l);
     ++		s->len = l - 1;
     ++	}
      +}
      +
     -+void slice_resize(struct slice *s, int l) {
     -+  if (s->cap < l) {
     -+    int c = s->cap * 2;
     -+    if (c < l) {
     -+      c = l;
     -+    }
     -+    s->cap = c;
     -+    s->buf = realloc(s->buf, s->cap);
     -+  }
     -+  s->len = l;
     ++void slice_resize(struct slice *s, int l)
     ++{
     ++	if (s->cap < l) {
     ++		int c = s->cap * 2;
     ++		if (c < l) {
     ++			c = l;
     ++		}
     ++		s->cap = c;
     ++		s->buf = realloc(s->buf, s->cap);
     ++	}
     ++	s->len = l;
      +}
      +
     -+void slice_append_string(struct slice *d, const char *s) {
     -+  int l1 = d->len;
     -+  int l2 = strlen(s);
     ++void slice_append_string(struct slice *d, const char *s)
     ++{
     ++	int l1 = d->len;
     ++	int l2 = strlen(s);
      +
     -+  slice_resize(d, l2 + l1);
     -+  memcpy(d->buf + l1, s, l2);
     ++	slice_resize(d, l2 + l1);
     ++	memcpy(d->buf + l1, s, l2);
      +}
      +
     -+void slice_append(struct slice *s, struct slice a) {
     -+  int end = s->len;
     -+  slice_resize(s, s->len + a.len);
     -+  memcpy(s->buf + end, a.buf, a.len);
     ++void slice_append(struct slice *s, struct slice a)
     ++{
     ++	int end = s->len;
     ++	slice_resize(s, s->len + a.len);
     ++	memcpy(s->buf + end, a.buf, a.len);
      +}
      +
     -+byte *slice_yield(struct slice *s) {
     -+  byte *p = s->buf;
     -+  s->buf = NULL;
     -+  s->cap = 0;
     -+  s->len = 0;
     -+  return p;
     ++byte *slice_yield(struct slice *s)
     ++{
     ++	byte *p = s->buf;
     ++	s->buf = NULL;
     ++	s->cap = 0;
     ++	s->len = 0;
     ++	return p;
      +}
      +
     -+void slice_copy(struct slice *dest, struct slice src) {
     -+  slice_resize(dest, src.len);
     -+  memcpy(dest->buf, src.buf, src.len);
     ++void slice_copy(struct slice *dest, struct slice src)
     ++{
     ++	slice_resize(dest, src.len);
     ++	memcpy(dest->buf, src.buf, src.len);
      +}
      +
      +/* return the underlying data as char*. len is left unchanged, but
      +   a \0 is added at the end. */
     -+const char *slice_as_string(struct slice *s) {
     -+  if (s->cap == s->len) {
     -+    int l = s->len;
     -+    slice_resize(s, l + 1);
     -+    s->len = l;
     -+  }
     -+  s->buf[s->len] = 0;
     -+  return (const char *)s->buf;
     ++const char *slice_as_string(struct slice *s)
     ++{
     ++	if (s->cap == s->len) {
     ++		int l = s->len;
     ++		slice_resize(s, l + 1);
     ++		s->len = l;
     ++	}
     ++	s->buf[s->len] = 0;
     ++	return (const char *)s->buf;
      +}
      +
      +/* return a newly malloced string for this slice */
     -+char *slice_to_string(struct slice in) {
     -+  struct slice s = {};
     -+  slice_resize(&s, in.len + 1);
     -+  s.buf[in.len] = 0;
     -+  memcpy(s.buf, in.buf, in.len);
     -+  return (char *)slice_yield(&s);
     ++char *slice_to_string(struct slice in)
     ++{
     ++	struct slice s = {};
     ++	slice_resize(&s, in.len + 1);
     ++	s.buf[in.len] = 0;
     ++	memcpy(s.buf, in.buf, in.len);
     ++	return (char *)slice_yield(&s);
      +}
      +
     -+bool slice_equal(struct slice a, struct slice b) {
     -+  if (a.len != b.len) {
     -+    return 0;
     -+  }
     -+  return memcmp(a.buf, b.buf, a.len) == 0;
     ++bool slice_equal(struct slice a, struct slice b)
     ++{
     ++	if (a.len != b.len) {
     ++		return 0;
     ++	}
     ++	return memcmp(a.buf, b.buf, a.len) == 0;
      +}
      +
     -+int slice_compare(struct slice a, struct slice b) {
     -+  int min = a.len < b.len ? a.len : b.len;
     -+  int res = memcmp(a.buf, b.buf, min);
     -+  if (res != 0) {
     -+    return res;
     -+  }
     -+  if (a.len < b.len) {
     -+    return -1;
     -+  } else if (a.len > b.len) {
     -+    return 1;
     -+  } else {
     -+    return 0;
     -+  }
     ++int slice_compare(struct slice a, struct slice b)
     ++{
     ++	int min = a.len < b.len ? a.len : b.len;
     ++	int res = memcmp(a.buf, b.buf, min);
     ++	if (res != 0) {
     ++		return res;
     ++	}
     ++	if (a.len < b.len) {
     ++		return -1;
     ++	} else if (a.len > b.len) {
     ++		return 1;
     ++	} else {
     ++		return 0;
     ++	}
      +}
      +
     -+int slice_write(struct slice *b, byte *data, int sz) {
     -+  if (b->len + sz > b->cap) {
     -+    int newcap = 2 * b->cap + 1;
     -+    if (newcap < b->len + sz) {
     -+      newcap = (b->len + sz);
     -+    }
     -+    b->buf = realloc(b->buf, newcap);
     -+    b->cap = newcap;
     -+  }
     ++int slice_write(struct slice *b, byte *data, int sz)
     ++{
     ++	if (b->len + sz > b->cap) {
     ++		int newcap = 2 * b->cap + 1;
     ++		if (newcap < b->len + sz) {
     ++			newcap = (b->len + sz);
     ++		}
     ++		b->buf = realloc(b->buf, newcap);
     ++		b->cap = newcap;
     ++	}
      +
     -+  memcpy(b->buf + b->len, data, sz);
     -+  b->len += sz;
     -+  return sz;
     ++	memcpy(b->buf + b->len, data, sz);
     ++	b->len += sz;
     ++	return sz;
      +}
      +
     -+int slice_write_void(void *b, byte *data, int sz) {
     -+  return slice_write((struct slice *)b, data, sz);
     ++int slice_write_void(void *b, byte *data, int sz)
     ++{
     ++	return slice_write((struct slice *)b, data, sz);
      +}
      +
     -+static uint64_t slice_size(void *b) { return ((struct slice *)b)->len; }
     ++static uint64_t slice_size(void *b)
     ++{
     ++	return ((struct slice *)b)->len;
     ++}
      +
     -+static void slice_return_block(void *b, struct block *dest) {
     -+  memset(dest->data, 0xff, dest->len);
     -+  free(dest->data);
     ++static void slice_return_block(void *b, struct block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	free(dest->data);
      +}
      +
     -+static void slice_close(void *b) {}
     ++static void slice_close(void *b)
     ++{
     ++}
      +
      +static int slice_read_block(void *v, struct block *dest, uint64_t off,
     -+                            uint32_t size) {
     -+  struct slice *b = (struct slice *)v;
     -+  assert(off + size <= b->len);
     -+  dest->data = calloc(size, 1);
     -+  memcpy(dest->data, b->buf + off, size);
     -+  dest->len = size;
     -+  return size;
     ++			    uint32_t size)
     ++{
     ++	struct slice *b = (struct slice *)v;
     ++	assert(off + size <= b->len);
     ++	dest->data = calloc(size, 1);
     ++	memcpy(dest->data, b->buf + off, size);
     ++	dest->len = size;
     ++	return size;
      +}
      +
      +struct block_source_vtable slice_vtable = {
     -+    .size = &slice_size,
     -+    .read_block = &slice_read_block,
     -+    .return_block = &slice_return_block,
     -+    .close = &slice_close,
     ++	.size = &slice_size,
     ++	.read_block = &slice_read_block,
     ++	.return_block = &slice_return_block,
     ++	.close = &slice_close,
      +};
      +
     -+void block_source_from_slice(struct block_source *bs, struct slice *buf) {
     -+  bs->ops = &slice_vtable;
     -+  bs->arg = buf;
     ++void block_source_from_slice(struct block_source *bs, struct slice *buf)
     ++{
     ++	bs->ops = &slice_vtable;
     ++	bs->arg = buf;
      +}
      +
     -+static void malloc_return_block(void *b, struct block *dest) {
     -+  memset(dest->data, 0xff, dest->len);
     -+  free(dest->data);
     ++static void malloc_return_block(void *b, struct block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	free(dest->data);
      +}
      +
      +struct block_source_vtable malloc_vtable = {
     -+    .return_block = &malloc_return_block,
     ++	.return_block = &malloc_return_block,
      +};
      +
      +struct block_source malloc_block_source_instance = {
     -+    .ops = &malloc_vtable,
     ++	.ops = &malloc_vtable,
      +};
      +
     -+struct block_source malloc_block_source(void) {
     -+  return malloc_block_source_instance;
     ++struct block_source malloc_block_source(void)
     ++{
     ++	return malloc_block_source_instance;
      +}
      
       diff --git a/reftable/slice.h b/reftable/slice.h
     @@ -5667,9 +5761,9 @@
      +#include "reftable.h"
      +
      +struct slice {
     -+  byte *buf;
     -+  int len;
     -+  int cap;
     ++	byte *buf;
     ++	int len;
     ++	int cap;
      +};
      +
      +void slice_set_string(struct slice *dest, const char *);
     @@ -5707,33 +5801,33 @@
      +
      +#include "slice.h"
      +
     -+#include <assert.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "record.h"
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void test_slice(void) {
     -+  struct slice s = {};
     -+  slice_set_string(&s, "abc");
     -+  assert(0 == strcmp("abc", slice_as_string(&s)));
     ++void test_slice(void)
     ++{
     ++	struct slice s = {};
     ++	slice_set_string(&s, "abc");
     ++	assert(0 == strcmp("abc", slice_as_string(&s)));
      +
     -+  struct slice t = {};
     -+  slice_set_string(&t, "pqr");
     ++	struct slice t = {};
     ++	slice_set_string(&t, "pqr");
      +
     -+  slice_append(&s, t);
     -+  assert(0 == strcmp("abcpqr", slice_as_string(&s)));
     ++	slice_append(&s, t);
     ++	assert(0 == strcmp("abcpqr", slice_as_string(&s)));
      +
     -+  free(slice_yield(&s));
     -+  free(slice_yield(&t));
     ++	free(slice_yield(&s));
     ++	free(slice_yield(&t));
      +}
      +
     -+int main() {
     -+  add_test_case("test_slice", &test_slice);
     -+  test_main();
     ++int main()
     ++{
     ++	add_test_case("test_slice", &test_slice);
     ++	test_main();
      +}
      
       diff --git a/reftable/stack.c b/reftable/stack.c
     @@ -5751,926 +5845,980 @@
      +
      +#include "stack.h"
      +
     -+#include <assert.h>
     -+#include <errno.h>
     -+#include <fcntl.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <sys/stat.h>
     -+#include <sys/time.h>
     -+#include <sys/types.h>
     -+#include <unistd.h>
     -+
     ++#include "system.h"
      +#include "merged.h"
      +#include "reader.h"
      +#include "reftable.h"
      +#include "writer.h"
      +
      +int new_stack(struct stack **dest, const char *dir, const char *list_file,
     -+              struct write_options config) {
     -+  struct stack *p = calloc(sizeof(struct stack), 1);
     -+  int err = 0;
     -+  *dest = NULL;
     -+  p->list_file = strdup(list_file);
     -+  p->reftable_dir = strdup(dir);
     -+  p->config = config;
     -+
     -+  err = stack_reload(p);
     -+  if (err < 0) {
     -+    stack_destroy(p);
     -+  } else {
     -+    *dest = p;
     -+  }
     -+  return err;
     -+}
     -+
     -+static int fread_lines(FILE *f, char ***namesp) {
     -+  long size = 0;
     -+  int err = fseek(f, 0, SEEK_END);
     -+  char *buf = NULL;
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  size = ftell(f);
     -+  if (size < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  err = fseek(f, 0, SEEK_SET);
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  buf = malloc(size + 1);
     -+  if (fread(buf, 1, size, f) != size) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  buf[size] = 0;
     -+
     -+  parse_names(buf, size, namesp);
     ++	      struct write_options config)
     ++{
     ++	struct stack *p = calloc(sizeof(struct stack), 1);
     ++	int err = 0;
     ++	*dest = NULL;
     ++	p->list_file = strdup(list_file);
     ++	p->reftable_dir = strdup(dir);
     ++	p->config = config;
     ++
     ++	err = stack_reload(p);
     ++	if (err < 0) {
     ++		stack_destroy(p);
     ++	} else {
     ++		*dest = p;
     ++	}
     ++	return err;
     ++}
     ++
     ++static int fread_lines(FILE *f, char ***namesp)
     ++{
     ++	long size = 0;
     ++	int err = fseek(f, 0, SEEK_END);
     ++	char *buf = NULL;
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	size = ftell(f);
     ++	if (size < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	err = fseek(f, 0, SEEK_SET);
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	buf = malloc(size + 1);
     ++	if (fread(buf, 1, size, f) != size) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	buf[size] = 0;
     ++
     ++	parse_names(buf, size, namesp);
      +exit:
     -+  free(buf);
     -+  return err;
     ++	free(buf);
     ++	return err;
      +}
      +
     -+int read_lines(const char *filename, char ***namesp) {
     -+  FILE *f = fopen(filename, "r");
     -+  int err = 0;
     -+  if (f == NULL) {
     -+    if (errno == ENOENT) {
     -+      *namesp = calloc(sizeof(char *), 1);
     -+      return 0;
     -+    }
     ++int read_lines(const char *filename, char ***namesp)
     ++{
     ++	FILE *f = fopen(filename, "r");
     ++	int err = 0;
     ++	if (f == NULL) {
     ++		if (errno == ENOENT) {
     ++			*namesp = calloc(sizeof(char *), 1);
     ++			return 0;
     ++		}
      +
     -+    return IO_ERROR;
     -+  }
     -+  err = fread_lines(f, namesp);
     -+  fclose(f);
     -+  return err;
     ++		return IO_ERROR;
     ++	}
     ++	err = fread_lines(f, namesp);
     ++	fclose(f);
     ++	return err;
      +}
      +
     -+struct merged_table *stack_merged_table(struct stack *st) {
     -+  return st->merged;
     ++struct merged_table *stack_merged_table(struct stack *st)
     ++{
     ++	return st->merged;
      +}
      +
      +/* Close and free the stack */
     -+void stack_destroy(struct stack *st) {
     -+  if (st->merged == NULL) {
     -+    return;
     -+  }
     -+
     -+  merged_table_close(st->merged);
     -+  merged_table_free(st->merged);
     -+  st->merged = NULL;
     -+
     -+  free(st->list_file);
     -+  st->list_file = NULL;
     -+  free(st->reftable_dir);
     -+  st->reftable_dir = NULL;
     -+  free(st);
     -+}
     -+
     -+static struct reader **stack_copy_readers(struct stack *st, int cur_len) {
     -+  struct reader **cur = calloc(sizeof(struct reader *), cur_len);
     -+  for (int i = 0; i < cur_len; i++) {
     -+    cur[i] = st->merged->stack[i];
     -+  }
     -+  return cur;
     -+}
     -+
     -+static int stack_reload_once(struct stack *st, char **names, bool reuse_open) {
     -+  int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     -+  struct reader **cur = stack_copy_readers(st, cur_len);
     -+  int err = 0;
     -+  int names_len = names_length(names);
     -+  struct reader **new_tables = malloc(sizeof(struct reader *) * names_len);
     -+  int new_tables_len = 0;
     -+  struct merged_table *new_merged = NULL;
     -+
     -+  struct slice table_path = {};
     -+
     -+  while (*names) {
     -+    struct reader *rd = NULL;
     -+    char *name = *names++;
     -+
     -+    // this is linear; we assume compaction keeps the number of tables
     -+    // under control so this is not quadratic.
     -+    for (int j = 0; reuse_open && j < cur_len; j++) {
     -+      if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
     -+        rd = cur[j];
     -+        cur[j] = NULL;
     -+        break;
     -+      }
     -+    }
     -+
     -+    if (rd == NULL) {
     -+      struct block_source src = {};
     -+      slice_set_string(&table_path, st->reftable_dir);
     -+      slice_append_string(&table_path, "/");
     -+      slice_append_string(&table_path, name);
     -+
     -+      err = block_source_from_file(&src, slice_as_string(&table_path));
     -+      if (err < 0) {
     -+        goto exit;
     -+      }
     -+
     -+      err = new_reader(&rd, src, name);
     -+      if (err < 0) {
     -+        goto exit;
     -+      }
     -+    }
     -+
     -+    new_tables[new_tables_len++] = rd;
     -+  }
     -+
     -+  // success!
     -+  err = new_merged_table(&new_merged, new_tables, new_tables_len);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  new_tables = NULL;
     -+  new_tables_len = 0;
     -+  if (st->merged != NULL) {
     -+    merged_table_clear(st->merged);
     -+    merged_table_free(st->merged);
     -+  }
     -+  st->merged = new_merged;
     -+
     -+  for (int i = 0; i < cur_len; i++) {
     -+    if (cur[i] != NULL) {
     -+      reader_close(cur[i]);
     -+      reader_free(cur[i]);
     -+    }
     -+  }
     -+
     ++void stack_destroy(struct stack *st)
     ++{
     ++	if (st->merged == NULL) {
     ++		return;
     ++	}
     ++
     ++	merged_table_close(st->merged);
     ++	merged_table_free(st->merged);
     ++	st->merged = NULL;
     ++
     ++	free(st->list_file);
     ++	st->list_file = NULL;
     ++	free(st->reftable_dir);
     ++	st->reftable_dir = NULL;
     ++	free(st);
     ++}
     ++
     ++static struct reader **stack_copy_readers(struct stack *st, int cur_len)
     ++{
     ++	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
     ++	int i = 0;
     ++	for (i = 0; i < cur_len; i++) {
     ++		cur[i] = st->merged->stack[i];
     ++	}
     ++	return cur;
     ++}
     ++
     ++static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
     ++{
     ++	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     ++	struct reader **cur = stack_copy_readers(st, cur_len);
     ++	int err = 0;
     ++	int names_len = names_length(names);
     ++	struct reader **new_tables =
     ++		malloc(sizeof(struct reader *) * names_len);
     ++	int new_tables_len = 0;
     ++	struct merged_table *new_merged = NULL;
     ++
     ++	struct slice table_path = {};
     ++
     ++	while (*names) {
     ++		struct reader *rd = NULL;
     ++		char *name = *names++;
     ++
     ++		/* this is linear; we assume compaction keeps the number of
     ++		   tables under control so this is not quadratic. */
     ++		int j = 0;
     ++		for (j = 0; reuse_open && j < cur_len; j++) {
     ++			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
     ++				rd = cur[j];
     ++				cur[j] = NULL;
     ++				break;
     ++			}
     ++		}
     ++
     ++		if (rd == NULL) {
     ++			struct block_source src = {};
     ++			slice_set_string(&table_path, st->reftable_dir);
     ++			slice_append_string(&table_path, "/");
     ++			slice_append_string(&table_path, name);
     ++
     ++			err = block_source_from_file(
     ++				&src, slice_as_string(&table_path));
     ++			if (err < 0) {
     ++				goto exit;
     ++			}
     ++
     ++			err = new_reader(&rd, src, name);
     ++			if (err < 0) {
     ++				goto exit;
     ++			}
     ++		}
     ++
     ++		new_tables[new_tables_len++] = rd;
     ++	}
     ++
     ++	/* success! */
     ++	err = new_merged_table(&new_merged, new_tables, new_tables_len);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	new_tables = NULL;
     ++	new_tables_len = 0;
     ++	if (st->merged != NULL) {
     ++		merged_table_clear(st->merged);
     ++		merged_table_free(st->merged);
     ++	}
     ++	st->merged = new_merged;
     ++
     ++	{
     ++		int i = 0;
     ++		for (i = 0; i < cur_len; i++) {
     ++			if (cur[i] != NULL) {
     ++				reader_close(cur[i]);
     ++				reader_free(cur[i]);
     ++			}
     ++		}
     ++	}
      +exit:
     -+  free(slice_yield(&table_path));
     -+  for (int i = 0; i < new_tables_len; i++) {
     -+    reader_close(new_tables[i]);
     -+  }
     -+  free(new_tables);
     -+  free(cur);
     -+  return err;
     -+}
     -+
     -+// return negative if a before b.
     -+static int tv_cmp(struct timeval *a, struct timeval *b) {
     -+  time_t diff = a->tv_sec - b->tv_sec;
     -+  suseconds_t udiff = a->tv_usec - b->tv_usec;
     -+
     -+  if (diff != 0) {
     -+    return diff;
     -+  }
     -+
     -+  return udiff;
     -+}
     -+
     -+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open) {
     -+  struct timeval deadline = {};
     -+  int err = gettimeofday(&deadline, NULL);
     -+  int64_t delay = 0;
     -+  int tries = 0;
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  deadline.tv_sec += 3;
     -+  while (true) {
     -+    char **names = NULL;
     -+    char **names_after = NULL;
     -+    struct timeval now = {};
     -+    int err = gettimeofday(&now, NULL);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    // Only look at deadlines after the first few times. This
     -+    // simplifies debugging in GDB
     -+    tries++;
     -+    if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
     -+      break;
     -+    }
     -+
     -+    err = read_lines(st->list_file, &names);
     -+    if (err < 0) {
     -+      free_names(names);
     -+      return err;
     -+    }
     -+    err = stack_reload_once(st, names, reuse_open);
     -+    if (err == 0) {
     -+      free_names(names);
     -+      break;
     -+    }
     -+    if (err != NOT_EXIST_ERROR) {
     -+      free_names(names);
     -+      return err;
     -+    }
     -+
     -+    err = read_lines(st->list_file, &names_after);
     -+    if (err < 0) {
     -+      free_names(names);
     -+      return err;
     -+    }
     -+
     -+    if (names_equal(names_after, names)) {
     -+      free_names(names);
     -+      free_names(names_after);
     -+      return -1;
     -+    }
     -+    free_names(names);
     -+    free_names(names_after);
     -+
     -+    delay = delay + (delay * rand()) / RAND_MAX + 100;
     -+    usleep(delay);
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+int stack_reload(struct stack *st) {
     -+  return stack_reload_maybe_reuse(st, true);
     -+}
     -+
     -+// -1 = error
     -+// 0 = up to date
     -+// 1 = changed.
     -+static int stack_uptodate(struct stack *st) {
     -+  char **names = NULL;
     -+  int err = read_lines(st->list_file, &names);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  for (int i = 0; i < st->merged->stack_len; i++) {
     -+    if (names[i] == NULL) {
     -+      err = 1;
     -+      goto exit;
     -+    }
     -+
     -+    if (0 != strcmp(st->merged->stack[i]->name, names[i])) {
     -+      err = 1;
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  if (names[st->merged->stack_len] != NULL) {
     -+    err = 1;
     -+    goto exit;
     -+  }
     ++	free(slice_yield(&table_path));
     ++	{
     ++		int i = 0;
     ++		for (i = 0; i < new_tables_len; i++) {
     ++			reader_close(new_tables[i]);
     ++		}
     ++	}
     ++	free(new_tables);
     ++	free(cur);
     ++	return err;
     ++}
     ++
     ++/* return negative if a before b. */
     ++static int tv_cmp(struct timeval *a, struct timeval *b)
     ++{
     ++	time_t diff = a->tv_sec - b->tv_sec;
     ++	int udiff = a->tv_usec - b->tv_usec;
     ++
     ++	if (diff != 0) {
     ++		return diff;
     ++	}
     ++
     ++	return udiff;
     ++}
     ++
     ++static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
     ++{
     ++	struct timeval deadline = {};
     ++	int err = gettimeofday(&deadline, NULL);
     ++	int64_t delay = 0;
     ++	int tries = 0;
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	deadline.tv_sec += 3;
     ++	while (true) {
     ++		char **names = NULL;
     ++		char **names_after = NULL;
     ++		struct timeval now = {};
     ++		int err = gettimeofday(&now, NULL);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		/* Only look at deadlines after the first few times. This
     ++		   simplifies debugging in GDB */
     ++		tries++;
     ++		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
     ++			break;
     ++		}
     ++
     ++		err = read_lines(st->list_file, &names);
     ++		if (err < 0) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++		err = stack_reload_once(st, names, reuse_open);
     ++		if (err == 0) {
     ++			free_names(names);
     ++			break;
     ++		}
     ++		if (err != NOT_EXIST_ERROR) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++
     ++		err = read_lines(st->list_file, &names_after);
     ++		if (err < 0) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++
     ++		if (names_equal(names_after, names)) {
     ++			free_names(names);
     ++			free_names(names_after);
     ++			return -1;
     ++		}
     ++		free_names(names);
     ++		free_names(names_after);
     ++
     ++		delay = delay + (delay * rand()) / RAND_MAX + 100;
     ++		usleep(delay);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++int stack_reload(struct stack *st)
     ++{
     ++	return stack_reload_maybe_reuse(st, true);
     ++}
     ++
     ++/* -1 = error
     ++ 0 = up to date
     ++ 1 = changed. */
     ++static int stack_uptodate(struct stack *st)
     ++{
     ++	char **names = NULL;
     ++	int err = read_lines(st->list_file, &names);
     ++	int i = 0;
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	for (i = 0; i < st->merged->stack_len; i++) {
     ++		if (names[i] == NULL) {
     ++			err = 1;
     ++			goto exit;
     ++		}
     ++
     ++		if (strcmp(st->merged->stack[i]->name, names[i])) {
     ++			err = 1;
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	if (names[st->merged->stack_len] != NULL) {
     ++		err = 1;
     ++		goto exit;
     ++	}
      +
      +exit:
     -+  free_names(names);
     -+  return err;
     ++	free_names(names);
     ++	return err;
      +}
      +
      +int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
     -+              void *arg) {
     -+  int err = stack_try_add(st, write, arg);
     -+  if (err < 0) {
     -+    if (err == LOCK_ERROR) {
     -+      err = stack_reload(st);
     -+    }
     -+    return err;
     -+  }
     ++	      void *arg)
     ++{
     ++	int err = stack_try_add(st, write, arg);
     ++	if (err < 0) {
     ++		if (err == LOCK_ERROR) {
     ++			err = stack_reload(st);
     ++		}
     ++		return err;
     ++	}
      +
     -+  return stack_auto_compact(st);
     ++	return stack_auto_compact(st);
      +}
      +
     -+static void format_name(struct slice *dest, uint64_t min, uint64_t max) {
     -+  char buf[1024];
     -+  sprintf(buf, "%012lx-%012lx", min, max);
     -+  slice_set_string(dest, buf);
     ++static void format_name(struct slice *dest, uint64_t min, uint64_t max)
     ++{
     ++	char buf[100];
     ++	snprintf(buf, sizeof(buf), "%012lx-%012lx", min, max);
     ++	slice_set_string(dest, buf);
      +}
      +
      +int stack_try_add(struct stack *st,
     -+                  int (*write_table)(struct writer *wr, void *arg), void *arg) {
     -+  struct slice lock_name = {};
     -+  struct slice temp_tab_name = {};
     -+  struct slice tab_name = {};
     -+  struct slice next_name = {};
     -+  struct slice table_list = {};
     -+  struct writer *wr = NULL;
     -+  int err = 0;
     -+  int tab_fd = 0;
     -+  int lock_fd = 0;
     -+  uint64_t next_update_index = 0;
     -+
     -+  slice_set_string(&lock_name, st->list_file);
     -+  slice_append_string(&lock_name, ".lock");
     -+
     -+  lock_fd =
     -+      open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+  if (lock_fd < 0) {
     -+    if (errno == EEXIST) {
     -+      err = LOCK_ERROR;
     -+      goto exit;
     -+    }
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = stack_uptodate(st);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  if (err > 1) {
     -+    err = LOCK_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  next_update_index = stack_next_update_index(st);
     -+
     -+  slice_resize(&next_name, 0);
     -+  format_name(&next_name, next_update_index, next_update_index);
     -+
     -+  slice_set_string(&temp_tab_name, st->reftable_dir);
     -+  slice_append_string(&temp_tab_name, "/");
     -+  slice_append(&temp_tab_name, next_name);
     -+  slice_append_string(&temp_tab_name, "XXXXXX");
     -+
     -+  tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
     -+  if (tab_fd < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  wr = new_writer(fd_writer, &tab_fd, &st->config);
     -+  err = write_table(wr, arg);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  err = writer_close(wr);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  err = close(tab_fd);
     -+  tab_fd = 0;
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  if (wr->min_update_index < next_update_index) {
     -+    err = API_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  for (int i = 0; i < st->merged->stack_len; i++) {
     -+    slice_append_string(&table_list, st->merged->stack[i]->name);
     -+    slice_append_string(&table_list, "\n");
     -+  }
     -+
     -+  format_name(&next_name, wr->min_update_index, wr->max_update_index);
     -+  slice_append_string(&next_name, ".ref");
     -+  slice_append(&table_list, next_name);
     -+  slice_append_string(&table_list, "\n");
     -+
     -+  slice_set_string(&tab_name, st->reftable_dir);
     -+  slice_append_string(&tab_name, "/");
     -+  slice_append(&tab_name, next_name);
     -+
     -+  err = rename(slice_as_string(&temp_tab_name), slice_as_string(&tab_name));
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  free(slice_yield(&temp_tab_name));
     -+
     -+  err = write(lock_fd, table_list.buf, table_list.len);
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  err = close(lock_fd);
     -+  lock_fd = 0;
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&tab_name));
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = rename(slice_as_string(&lock_name), st->list_file);
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&tab_name));
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = stack_reload(st);
     ++		  int (*write_table)(struct writer *wr, void *arg), void *arg)
     ++{
     ++	struct slice lock_name = {};
     ++	struct slice temp_tab_name = {};
     ++	struct slice tab_name = {};
     ++	struct slice next_name = {};
     ++	struct slice table_list = {};
     ++	struct writer *wr = NULL;
     ++	int err = 0;
     ++	int tab_fd = 0;
     ++	int lock_fd = 0;
     ++	uint64_t next_update_index = 0;
     ++
     ++	slice_set_string(&lock_name, st->list_file);
     ++	slice_append_string(&lock_name, ".lock");
     ++
     ++	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
     ++		       0644);
     ++	if (lock_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = LOCK_ERROR;
     ++			goto exit;
     ++		}
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = stack_uptodate(st);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	if (err > 1) {
     ++		err = LOCK_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	next_update_index = stack_next_update_index(st);
     ++
     ++	slice_resize(&next_name, 0);
     ++	format_name(&next_name, next_update_index, next_update_index);
     ++
     ++	slice_set_string(&temp_tab_name, st->reftable_dir);
     ++	slice_append_string(&temp_tab_name, "/");
     ++	slice_append(&temp_tab_name, next_name);
     ++	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
     ++
     ++	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
     ++	if (tab_fd < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	wr = new_writer(fd_writer, &tab_fd, &st->config);
     ++	err = write_table(wr, arg);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	err = writer_close(wr);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	err = close(tab_fd);
     ++	tab_fd = 0;
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	if (wr->min_update_index < next_update_index) {
     ++		err = API_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	{
     ++		int i = 0;
     ++		for (i = 0; i < st->merged->stack_len; i++) {
     ++			slice_append_string(&table_list,
     ++					    st->merged->stack[i]->name);
     ++			slice_append_string(&table_list, "\n");
     ++		}
     ++	}
     ++
     ++	format_name(&next_name, wr->min_update_index, wr->max_update_index);
     ++	slice_append_string(&next_name, ".ref");
     ++	slice_append(&table_list, next_name);
     ++	slice_append_string(&table_list, "\n");
     ++
     ++	slice_set_string(&tab_name, st->reftable_dir);
     ++	slice_append_string(&tab_name, "/");
     ++	slice_append(&tab_name, next_name);
     ++
     ++	err = rename(slice_as_string(&temp_tab_name),
     ++		     slice_as_string(&tab_name));
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	free(slice_yield(&temp_tab_name));
     ++
     ++	err = write(lock_fd, table_list.buf, table_list.len);
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	err = close(lock_fd);
     ++	lock_fd = 0;
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&tab_name));
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = rename(slice_as_string(&lock_name), st->list_file);
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&tab_name));
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = stack_reload(st);
      +exit:
     -+  if (tab_fd > 0) {
     -+    close(tab_fd);
     -+    tab_fd = 0;
     -+  }
     -+  if (temp_tab_name.len > 0) {
     -+    unlink(slice_as_string(&temp_tab_name));
     -+  }
     -+  unlink(slice_as_string(&lock_name));
     -+
     -+  if (lock_fd > 0) {
     -+    close(lock_fd);
     -+    lock_fd = 0;
     -+  }
     -+
     -+  free(slice_yield(&lock_name));
     -+  free(slice_yield(&temp_tab_name));
     -+  free(slice_yield(&tab_name));
     -+  free(slice_yield(&next_name));
     -+  free(slice_yield(&table_list));
     -+  writer_free(wr);
     -+  return err;
     -+}
     -+
     -+uint64_t stack_next_update_index(struct stack *st) {
     -+  int sz = st->merged->stack_len;
     -+  if (sz > 0) {
     -+    return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
     -+  }
     -+  return 1;
     ++	if (tab_fd > 0) {
     ++		close(tab_fd);
     ++		tab_fd = 0;
     ++	}
     ++	if (temp_tab_name.len > 0) {
     ++		unlink(slice_as_string(&temp_tab_name));
     ++	}
     ++	unlink(slice_as_string(&lock_name));
     ++
     ++	if (lock_fd > 0) {
     ++		close(lock_fd);
     ++		lock_fd = 0;
     ++	}
     ++
     ++	free(slice_yield(&lock_name));
     ++	free(slice_yield(&temp_tab_name));
     ++	free(slice_yield(&tab_name));
     ++	free(slice_yield(&next_name));
     ++	free(slice_yield(&table_list));
     ++	writer_free(wr);
     ++	return err;
     ++}
     ++
     ++uint64_t stack_next_update_index(struct stack *st)
     ++{
     ++	int sz = st->merged->stack_len;
     ++	if (sz > 0) {
     ++		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
     ++	}
     ++	return 1;
      +}
      +
      +static int stack_compact_locked(struct stack *st, int first, int last,
     -+                                struct slice *temp_tab, struct log_expiry_config *config) {
     -+  struct slice next_name = {};
     -+  int tab_fd = -1;
     -+  struct writer *wr = NULL;
     -+  int err = 0;
     -+
     -+  format_name(&next_name, reader_min_update_index(st->merged->stack[first]),
     -+              reader_max_update_index(st->merged->stack[first]));
     -+
     -+  slice_set_string(temp_tab, st->reftable_dir);
     -+  slice_append_string(temp_tab, "/");
     -+  slice_append(temp_tab, next_name);
     -+  slice_append_string(temp_tab, "XXXXXX");
     -+
     -+  tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     -+  wr = new_writer(fd_writer, &tab_fd, &st->config);
     -+
     -+  err = stack_write_compact(st, wr, first, last, config);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  err = writer_close(wr);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  writer_free(wr);
     -+
     -+  err = close(tab_fd);
     -+  tab_fd = 0;
     ++				struct slice *temp_tab,
     ++				struct log_expiry_config *config)
     ++{
     ++	struct slice next_name = {};
     ++	int tab_fd = -1;
     ++	struct writer *wr = NULL;
     ++	int err = 0;
     ++
     ++	format_name(&next_name,
     ++		    reader_min_update_index(st->merged->stack[first]),
     ++		    reader_max_update_index(st->merged->stack[first]));
     ++
     ++	slice_set_string(temp_tab, st->reftable_dir);
     ++	slice_append_string(temp_tab, "/");
     ++	slice_append(temp_tab, next_name);
     ++	slice_append_string(temp_tab, ".temp.XXXXXX");
     ++
     ++	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     ++	wr = new_writer(fd_writer, &tab_fd, &st->config);
     ++
     ++	err = stack_write_compact(st, wr, first, last, config);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	err = writer_close(wr);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	writer_free(wr);
     ++
     ++	err = close(tab_fd);
     ++	tab_fd = 0;
      +
      +exit:
     -+  if (tab_fd > 0) {
     -+    close(tab_fd);
     -+    tab_fd = 0;
     -+  }
     -+  if (err != 0 && temp_tab->len > 0) {
     -+    unlink(slice_as_string(temp_tab));
     -+    free(slice_yield(temp_tab));
     -+  }
     -+  free(slice_yield(&next_name));
     -+  return err;
     ++	if (tab_fd > 0) {
     ++		close(tab_fd);
     ++		tab_fd = 0;
     ++	}
     ++	if (err != 0 && temp_tab->len > 0) {
     ++		unlink(slice_as_string(temp_tab));
     ++		free(slice_yield(temp_tab));
     ++	}
     ++	free(slice_yield(&next_name));
     ++	return err;
      +}
      +
      +int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+                        int last, struct log_expiry_config *config) {
     -+  int subtabs_len = last - first + 1;
     -+  struct reader **subtabs = calloc(sizeof(struct reader *), last - first + 1);
     -+  struct merged_table *mt = NULL;
     -+  int err = 0;
     -+  struct iterator it = {};
     -+  struct ref_record ref = {};
     -+  struct log_record log = {};
     -+
     -+  for (int i = first, j = 0; i <= last; i++) {
     -+    struct reader *t = st->merged->stack[i];
     -+    subtabs[j++] = t;
     -+    st->stats.bytes += t->size;
     -+  }
     -+  writer_set_limits(wr, st->merged->stack[first]->min_update_index,
     -+                    st->merged->stack[last]->max_update_index);
     -+
     -+  err = new_merged_table(&mt, subtabs, subtabs_len);
     -+  if (err < 0) {
     -+    free(subtabs);
     -+    goto exit;
     -+  }
     -+
     -+  err = merged_table_seek_ref(mt, &it, "");
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  while (true) {
     -+    err = iterator_next_ref(it, &ref);
     -+    if (err > 0) {
     -+      err = 0;
     -+      break;
     -+    }
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+    if (first == 0 && ref_record_is_deletion(&ref)) {
     -+      continue;
     -+    }
     -+
     -+    err = writer_add_ref(wr, &ref);
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+  }
     -+
     -+  err = merged_table_seek_log(mt, &it, "");
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  while (true) {
     -+    err = iterator_next_log(it, &log);
     -+    if (err > 0) {
     -+      err = 0;
     -+      break;
     -+    }
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+    if (first == 0 && log_record_is_deletion(&log)) {
     -+      continue;
     -+    }
     -+
     -+    // XXX collect stats?
     -+
     -+    if (config != NULL && config->time > 0 && log.time < config->time) {
     -+      continue;
     -+    }
     -+
     -+    if (config != NULL && config->min_update_index > 0 && log.update_index < config->min_update_index) {
     -+      continue;
     -+    }
     -+
     -+    err = writer_add_log(wr, &log);
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+  }
     -+
     ++			int last, struct log_expiry_config *config)
     ++{
     ++	int subtabs_len = last - first + 1;
     ++	struct reader **subtabs =
     ++		calloc(sizeof(struct reader *), last - first + 1);
     ++	struct merged_table *mt = NULL;
     ++	int err = 0;
     ++	struct iterator it = {};
     ++	struct ref_record ref = {};
     ++	struct log_record log = {};
     ++
     ++	int i = 0, j = 0;
     ++	for (i = first, j = 0; i <= last; i++) {
     ++		struct reader *t = st->merged->stack[i];
     ++		subtabs[j++] = t;
     ++		st->stats.bytes += t->size;
     ++	}
     ++	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
     ++			  st->merged->stack[last]->max_update_index);
     ++
     ++	err = new_merged_table(&mt, subtabs, subtabs_len);
     ++	if (err < 0) {
     ++		free(subtabs);
     ++		goto exit;
     ++	}
     ++
     ++	err = merged_table_seek_ref(mt, &it, "");
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	while (true) {
     ++		err = iterator_next_ref(it, &ref);
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++		if (first == 0 && ref_record_is_deletion(&ref)) {
     ++			continue;
     ++		}
     ++
     ++		err = writer_add_ref(wr, &ref);
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++	}
     ++
     ++	err = merged_table_seek_log(mt, &it, "");
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	while (true) {
     ++		err = iterator_next_log(it, &log);
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++		if (first == 0 && log_record_is_deletion(&log)) {
     ++			continue;
     ++		}
     ++
     ++		/* XXX collect stats? */
     ++
     ++		if (config != NULL && config->time > 0 &&
     ++		    log.time < config->time) {
     ++			continue;
     ++		}
     ++
     ++		if (config != NULL && config->min_update_index > 0 &&
     ++		    log.update_index < config->min_update_index) {
     ++			continue;
     ++		}
     ++
     ++		err = writer_add_log(wr, &log);
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++	}
      +
      +exit:
     -+  iterator_destroy(&it);
     -+  if (mt != NULL) {
     -+    merged_table_clear(mt);
     -+    merged_table_free(mt);
     -+  }
     -+  ref_record_clear(&ref);
     -+
     -+  return err;
     -+}
     -+
     -+// <  0: error. 0 == OK, > 0 attempt failed; could retry.
     -+static int stack_compact_range(struct stack *st, int first, int last, struct log_expiry_config *expiry) {
     -+  struct slice temp_tab_name = {};
     -+  struct slice new_table_name = {};
     -+  struct slice lock_file_name = {};
     -+  struct slice ref_list_contents = {};
     -+  struct slice new_table_path = {};
     -+  int err = 0;
     -+  bool have_lock = false;
     -+  int lock_file_fd = 0;
     -+  int compact_count = last - first + 1;
     -+  char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
     -+  char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
     -+
     -+  if (first > last || (expiry == NULL && first == last)) {
     -+    err = 0;
     -+    goto exit;
     -+  }
     -+
     -+  st->stats.attempts++;
     -+
     -+  slice_set_string(&lock_file_name, st->list_file);
     -+  slice_append_string(&lock_file_name, ".lock");
     -+
     -+  lock_file_fd =
     -+      open(slice_as_string(&lock_file_name), O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+  if (lock_file_fd < 0) {
     -+    if (errno == EEXIST) {
     -+      err = 1;
     -+    } else {
     -+      err = IO_ERROR;
     -+    }
     -+    goto exit;
     -+  }
     -+  have_lock = true;
     -+  err = stack_uptodate(st);
     -+  if (err != 0) {
     -+    goto exit;
     -+  }
     -+
     -+  for (int i = first, j = 0; i <= last; i++) {
     -+    struct slice subtab_name = {};
     -+    struct slice subtab_lock = {};
     -+    slice_set_string(&subtab_name, st->reftable_dir);
     -+    slice_append_string(&subtab_name, "/");
     -+    slice_append_string(&subtab_name, reader_name(st->merged->stack[i]));
     -+
     -+    slice_copy(&subtab_lock, subtab_name);
     -+    slice_append_string(&subtab_lock, ".lock");
     -+
     -+    {
     -+      int sublock_file_fd = open(slice_as_string(&subtab_lock),
     -+                                 O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+      if (sublock_file_fd > 0) {
     -+        close(sublock_file_fd);
     -+      } else if (sublock_file_fd < 0) {
     -+        if (errno == EEXIST) {
     -+          err = 1;
     -+        }
     -+        err = IO_ERROR;
     -+      }
     -+    }
     -+
     -+    subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
     -+    delete_on_success[j] = (char *)slice_as_string(&subtab_name);
     -+    j++;
     -+
     -+    if (err != 0) {
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  err = unlink(slice_as_string(&lock_file_name));
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  have_lock = false;
     -+
     -+  err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  lock_file_fd =
     -+      open(slice_as_string(&lock_file_name), O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+  if (lock_file_fd < 0) {
     -+    if (errno == EEXIST) {
     -+      err = 1;
     -+    } else {
     -+      err = IO_ERROR;
     -+    }
     -+    goto exit;
     -+  }
     -+  have_lock = true;
     -+
     -+  format_name(&new_table_name, st->merged->stack[first]->min_update_index,
     -+              st->merged->stack[last]->max_update_index);
     -+  slice_append_string(&new_table_name, ".ref");
     -+
     -+  slice_set_string(&new_table_path, st->reftable_dir);
     -+  slice_append_string(&new_table_path, "/");
     -+
     -+  slice_append(&new_table_path, new_table_name);
     -+
     -+  err =
     -+      rename(slice_as_string(&temp_tab_name), slice_as_string(&new_table_path));
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  for (int i = 0; i < first; i++) {
     -+    slice_append_string(&ref_list_contents, st->merged->stack[i]->name);
     -+    slice_append_string(&ref_list_contents, "\n");
     -+  }
     -+  slice_append(&ref_list_contents, new_table_name);
     -+  slice_append_string(&ref_list_contents, "\n");
     -+  for (int i = last + 1; i < st->merged->stack_len; i++) {
     -+    slice_append_string(&ref_list_contents, st->merged->stack[i]->name);
     -+    slice_append_string(&ref_list_contents, "\n");
     -+  }
     -+
     -+  err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&new_table_path));
     -+    goto exit;
     -+  }
     -+  err = close(lock_file_fd);
     -+  lock_file_fd = 0;
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&new_table_path));
     -+    goto exit;
     -+  }
     -+
     -+  err = rename(slice_as_string(&lock_file_name), st->list_file);
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&new_table_path));
     -+    goto exit;
     -+  }
     -+  have_lock = false;
     -+
     -+  for (char **p = delete_on_success; *p; p++) {
     -+    if (0 != strcmp(*p, slice_as_string(&new_table_path))) {
     -+      unlink(*p);
     -+    }
     -+  }
     -+
     -+  err = stack_reload_maybe_reuse(st, first < last);
     ++	iterator_destroy(&it);
     ++	if (mt != NULL) {
     ++		merged_table_clear(mt);
     ++		merged_table_free(mt);
     ++	}
     ++	ref_record_clear(&ref);
     ++
     ++	return err;
     ++}
     ++
     ++/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
     ++static int stack_compact_range(struct stack *st, int first, int last,
     ++			       struct log_expiry_config *expiry)
     ++{
     ++	struct slice temp_tab_name = {};
     ++	struct slice new_table_name = {};
     ++	struct slice lock_file_name = {};
     ++	struct slice ref_list_contents = {};
     ++	struct slice new_table_path = {};
     ++	int err = 0;
     ++	bool have_lock = false;
     ++	int lock_file_fd = 0;
     ++	int compact_count = last - first + 1;
     ++	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
     ++	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
     ++	int i = 0;
     ++	int j = 0;
     ++
     ++	if (first > last || (expiry == NULL && first == last)) {
     ++		err = 0;
     ++		goto exit;
     ++	}
     ++
     ++	st->stats.attempts++;
     ++
     ++	slice_set_string(&lock_file_name, st->list_file);
     ++	slice_append_string(&lock_file_name, ".lock");
     ++
     ++	lock_file_fd = open(slice_as_string(&lock_file_name),
     ++			    O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++	if (lock_file_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = 1;
     ++		} else {
     ++			err = IO_ERROR;
     ++		}
     ++		goto exit;
     ++	}
     ++	have_lock = true;
     ++	err = stack_uptodate(st);
     ++	if (err != 0) {
     ++		goto exit;
     ++	}
     ++
     ++	for (i = first, j = 0; i <= last; i++) {
     ++		struct slice subtab_name = {};
     ++		struct slice subtab_lock = {};
     ++		slice_set_string(&subtab_name, st->reftable_dir);
     ++		slice_append_string(&subtab_name, "/");
     ++		slice_append_string(&subtab_name,
     ++				    reader_name(st->merged->stack[i]));
     ++
     ++		slice_copy(&subtab_lock, subtab_name);
     ++		slice_append_string(&subtab_lock, ".lock");
     ++
     ++		{
     ++			int sublock_file_fd =
     ++				open(slice_as_string(&subtab_lock),
     ++				     O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++			if (sublock_file_fd > 0) {
     ++				close(sublock_file_fd);
     ++			} else if (sublock_file_fd < 0) {
     ++				if (errno == EEXIST) {
     ++					err = 1;
     ++				}
     ++				err = IO_ERROR;
     ++			}
     ++		}
     ++
     ++		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
     ++		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
     ++		j++;
     ++
     ++		if (err != 0) {
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	err = unlink(slice_as_string(&lock_file_name));
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	have_lock = false;
     ++
     ++	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	lock_file_fd = open(slice_as_string(&lock_file_name),
     ++			    O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++	if (lock_file_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = 1;
     ++		} else {
     ++			err = IO_ERROR;
     ++		}
     ++		goto exit;
     ++	}
     ++	have_lock = true;
     ++
     ++	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
     ++		    st->merged->stack[last]->max_update_index);
     ++	slice_append_string(&new_table_name, ".ref");
     ++
     ++	slice_set_string(&new_table_path, st->reftable_dir);
     ++	slice_append_string(&new_table_path, "/");
     ++
     ++	slice_append(&new_table_path, new_table_name);
     ++
     ++	err = rename(slice_as_string(&temp_tab_name),
     ++		     slice_as_string(&new_table_path));
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	for (i = 0; i < first; i++) {
     ++		slice_append_string(&ref_list_contents,
     ++				    st->merged->stack[i]->name);
     ++		slice_append_string(&ref_list_contents, "\n");
     ++	}
     ++	slice_append(&ref_list_contents, new_table_name);
     ++	slice_append_string(&ref_list_contents, "\n");
     ++	for (i = last + 1; i < st->merged->stack_len; i++) {
     ++		slice_append_string(&ref_list_contents,
     ++				    st->merged->stack[i]->name);
     ++		slice_append_string(&ref_list_contents, "\n");
     ++	}
     ++
     ++	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto exit;
     ++	}
     ++	err = close(lock_file_fd);
     ++	lock_file_fd = 0;
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto exit;
     ++	}
     ++
     ++	err = rename(slice_as_string(&lock_file_name), st->list_file);
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto exit;
     ++	}
     ++	have_lock = false;
     ++
     ++	for (char **p = delete_on_success; *p; p++) {
     ++		if (strcmp(*p, slice_as_string(&new_table_path))) {
     ++			unlink(*p);
     ++		}
     ++	}
     ++
     ++	err = stack_reload_maybe_reuse(st, first < last);
      +exit:
     -+  for (char **p = subtable_locks; *p; p++) {
     -+    unlink(*p);
     -+  }
     -+  free_names(delete_on_success);
     -+  free_names(subtable_locks);
     -+  if (lock_file_fd > 0) {
     -+    close(lock_file_fd);
     -+    lock_file_fd = 0;
     -+  }
     -+  if (have_lock) {
     -+    unlink(slice_as_string(&lock_file_name));
     -+  }
     -+  free(slice_yield(&new_table_name));
     -+  free(slice_yield(&new_table_path));
     -+  free(slice_yield(&ref_list_contents));
     -+  free(slice_yield(&temp_tab_name));
     -+  free(slice_yield(&lock_file_name));
     -+  return err;
     -+}
     -+
     -+int stack_compact_all(struct stack *st, struct log_expiry_config *config) {
     -+  return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     -+}
     -+
     -+static int stack_compact_range_stats(struct stack *st, int first, int last, struct log_expiry_config *config) {
     -+  int err = stack_compact_range(st, first, last, config);
     -+  if (err > 0) {
     -+    st->stats.failures++;
     -+  }
     -+  return err;
     -+}
     -+
     -+static int segment_size(struct segment *s) { return s->end - s->start; }
     -+
     -+int fastlog2(uint64_t sz) {
     -+  int l = 0;
     -+  assert(sz > 0);
     -+  for (; sz; sz /= 2) {
     -+    l++;
     -+  }
     -+  return l - 1;
     -+}
     -+
     -+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n) {
     -+  struct segment *segs = calloc(sizeof(struct segment), n);
     -+  int next = 0;
     -+  struct segment cur = {};
     -+  for (int i = 0; i < n; i++) {
     -+    int log = fastlog2(sizes[i]);
     -+    if (cur.log != log && cur.bytes > 0) {
     -+      struct segment fresh = {
     -+          .start = i,
     -+      };
     -+
     -+      segs[next++] = cur;
     -+      cur = fresh;
     -+    }
     -+
     -+    cur.log = log;
     -+    cur.end = i + 1;
     -+    cur.bytes += sizes[i];
     -+  }
     -+
     -+  segs[next++] = cur;
     -+  *seglen = next;
     -+  return segs;
     -+}
     -+
     -+struct segment suggest_compaction_segment(uint64_t *sizes, int n) {
     -+  int seglen = 0;
     -+  struct segment *segs = sizes_to_segments(&seglen, sizes, n);
     -+  struct segment min_seg = {
     -+      .log = 64,
     -+  };
     -+  for (int i = 0; i < seglen; i++) {
     -+    if (segment_size(&segs[i]) == 1) {
     -+      continue;
     -+    }
     -+
     -+    if (segs[i].log < min_seg.log) {
     -+      min_seg = segs[i];
     -+    }
     -+  }
     -+
     -+  while (min_seg.start > 0) {
     -+    int prev = min_seg.start - 1;
     -+    if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
     -+      break;
     -+    }
     -+
     -+    min_seg.start = prev;
     -+    min_seg.bytes += sizes[prev];
     -+  }
     -+
     -+  free(segs);
     -+  return min_seg;
     -+}
     -+
     -+static uint64_t *stack_table_sizes_for_compaction(struct stack *st) {
     -+  uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
     -+  for (int i = 0; i < st->merged->stack_len; i++) {
     -+    // overhead is 24 + 68 = 92.
     -+    sizes[i] = st->merged->stack[i]->size - 91;
     -+  }
     -+  return sizes;
     -+}
     -+
     -+int stack_auto_compact(struct stack *st) {
     -+  uint64_t *sizes = stack_table_sizes_for_compaction(st);
     -+  struct segment seg = suggest_compaction_segment(sizes, st->merged->stack_len);
     -+  free(sizes);
     -+  if (segment_size(&seg) > 0) {
     -+    return stack_compact_range_stats(st, seg.start, seg.end - 1, NULL);
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+struct compaction_stats *stack_compaction_stats(struct stack *st) {
     -+  return &st->stats;
     ++	for (char **p = subtable_locks; *p; p++) {
     ++		unlink(*p);
     ++	}
     ++	free_names(delete_on_success);
     ++	free_names(subtable_locks);
     ++	if (lock_file_fd > 0) {
     ++		close(lock_file_fd);
     ++		lock_file_fd = 0;
     ++	}
     ++	if (have_lock) {
     ++		unlink(slice_as_string(&lock_file_name));
     ++	}
     ++	free(slice_yield(&new_table_name));
     ++	free(slice_yield(&new_table_path));
     ++	free(slice_yield(&ref_list_contents));
     ++	free(slice_yield(&temp_tab_name));
     ++	free(slice_yield(&lock_file_name));
     ++	return err;
     ++}
     ++
     ++int stack_compact_all(struct stack *st, struct log_expiry_config *config)
     ++{
     ++	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     ++}
     ++
     ++static int stack_compact_range_stats(struct stack *st, int first, int last,
     ++				     struct log_expiry_config *config)
     ++{
     ++	int err = stack_compact_range(st, first, last, config);
     ++	if (err > 0) {
     ++		st->stats.failures++;
     ++	}
     ++	return err;
     ++}
     ++
     ++static int segment_size(struct segment *s)
     ++{
     ++	return s->end - s->start;
     ++}
     ++
     ++int fastlog2(uint64_t sz)
     ++{
     ++	int l = 0;
     ++	assert(sz > 0);
     ++	for (; sz; sz /= 2) {
     ++		l++;
     ++	}
     ++	return l - 1;
     ++}
     ++
     ++struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
     ++{
     ++	struct segment *segs = calloc(sizeof(struct segment), n);
     ++	int next = 0;
     ++	struct segment cur = {};
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		int log = fastlog2(sizes[i]);
     ++		if (cur.log != log && cur.bytes > 0) {
     ++			struct segment fresh = {
     ++				.start = i,
     ++			};
     ++
     ++			segs[next++] = cur;
     ++			cur = fresh;
     ++		}
     ++
     ++		cur.log = log;
     ++		cur.end = i + 1;
     ++		cur.bytes += sizes[i];
     ++	}
     ++
     ++	segs[next++] = cur;
     ++	*seglen = next;
     ++	return segs;
     ++}
     ++
     ++struct segment suggest_compaction_segment(uint64_t *sizes, int n)
     ++{
     ++	int seglen = 0;
     ++	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
     ++	struct segment min_seg = {
     ++		.log = 64,
     ++	};
     ++	int i = 0;
     ++	for (i = 0; i < seglen; i++) {
     ++		if (segment_size(&segs[i]) == 1) {
     ++			continue;
     ++		}
     ++
     ++		if (segs[i].log < min_seg.log) {
     ++			min_seg = segs[i];
     ++		}
     ++	}
     ++
     ++	while (min_seg.start > 0) {
     ++		int prev = min_seg.start - 1;
     ++		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
     ++			break;
     ++		}
     ++
     ++		min_seg.start = prev;
     ++		min_seg.bytes += sizes[prev];
     ++	}
     ++
     ++	free(segs);
     ++	return min_seg;
     ++}
     ++
     ++static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
     ++{
     ++	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
     ++	int i = 0;
     ++	for (i = 0; i < st->merged->stack_len; i++) {
     ++		/* overhead is 24 + 68 = 92. */
     ++		sizes[i] = st->merged->stack[i]->size - 91;
     ++	}
     ++	return sizes;
     ++}
     ++
     ++int stack_auto_compact(struct stack *st)
     ++{
     ++	uint64_t *sizes = stack_table_sizes_for_compaction(st);
     ++	struct segment seg =
     ++		suggest_compaction_segment(sizes, st->merged->stack_len);
     ++	free(sizes);
     ++	if (segment_size(&seg) > 0) {
     ++		return stack_compact_range_stats(st, seg.start, seg.end - 1,
     ++						 NULL);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++struct compaction_stats *stack_compaction_stats(struct stack *st)
     ++{
     ++	return &st->stats;
      +}
      +
      +int stack_read_ref(struct stack *st, const char *refname,
     -+                   struct ref_record *ref) {
     -+  struct iterator it = {};
     -+  struct merged_table *mt = stack_merged_table(st);
     -+  int err = merged_table_seek_ref(mt, &it, refname);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  err = iterator_next_ref(it, ref);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  if (0 != strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
     -+    err = 1;
     -+    goto exit;
     -+  }
     ++		   struct ref_record *ref)
     ++{
     ++	struct iterator it = {};
     ++	struct merged_table *mt = stack_merged_table(st);
     ++	int err = merged_table_seek_ref(mt, &it, refname);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	err = iterator_next_ref(it, ref);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
     ++		err = 1;
     ++		goto exit;
     ++	}
      +
      +exit:
     -+  iterator_destroy(&it);
     -+  return err;
     ++	iterator_destroy(&it);
     ++	return err;
      +}
      +
      +int stack_read_log(struct stack *st, const char *refname,
     -+                   struct log_record *log) {
     -+  struct iterator it = {};
     -+  struct merged_table *mt = stack_merged_table(st);
     -+  int err = merged_table_seek_log(mt, &it, refname);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  err = iterator_next_log(it, log);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  if (0 != strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
     -+    err = 1;
     -+    goto exit;
     -+  }
     ++		   struct log_record *log)
     ++{
     ++	struct iterator it = {};
     ++	struct merged_table *mt = stack_merged_table(st);
     ++	int err = merged_table_seek_log(mt, &it, refname);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	err = iterator_next_log(it, log);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
     ++		err = 1;
     ++		goto exit;
     ++	}
      +
      +exit:
     -+  iterator_destroy(&it);
     -+  return err;
     ++	iterator_destroy(&it);
     ++	return err;
      +}
      
       diff --git a/reftable/stack.h b/reftable/stack.h
     @@ -6692,26 +6840,26 @@
      +#include "reftable.h"
      +
      +struct stack {
     -+  char *list_file;
     -+  char *reftable_dir;
     ++	char *list_file;
     ++	char *reftable_dir;
      +
     -+  struct write_options config;
     ++	struct write_options config;
      +
     -+  struct merged_table *merged;
     -+  struct compaction_stats stats;
     ++	struct merged_table *merged;
     ++	struct compaction_stats stats;
      +};
      +
      +int read_lines(const char *filename, char ***lines);
      +int stack_try_add(struct stack *st,
     -+                  int (*write_table)(struct writer *wr, void *arg), void *arg);
     ++		  int (*write_table)(struct writer *wr, void *arg), void *arg);
      +int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+                        int last, struct log_expiry_config *config);
     ++			int last, struct log_expiry_config *config);
      +int fastlog2(uint64_t sz);
      +
      +struct segment {
     -+  int start, end;
     -+  int log;
     -+  uint64_t bytes;
     ++	int start, end;
     ++	int log;
     ++	uint64_t bytes;
      +};
      +
      +struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
     @@ -6734,8 +6882,7 @@
      +
      +#include "stack.h"
      +
     -+#include <string.h>
     -+#include <unistd.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "constants.h"
     @@ -6743,252 +6890,311 @@
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void test_read_file(void) {
     -+  char fn[256] = "/tmp/stack.XXXXXX";
     -+  int fd = mkstemp(fn);
     -+  assert(fd > 0);
     ++void test_read_file(void)
     ++{
     ++	char fn[256] = "/tmp/stack.test_read_file.XXXXXX";
     ++	int fd = mkstemp(fn);
     ++	assert(fd > 0);
     ++
     ++	char out[1024] = "line1\n\nline2\nline3";
     ++
     ++	int n = write(fd, out, strlen(out));
     ++	assert(n == strlen(out));
     ++	int err = close(fd);
     ++	assert(err >= 0);
     ++
     ++	char **names = NULL;
     ++	err = read_lines(fn, &names);
     ++	assert_err(err);
     ++
     ++	char *want[] = { "line1", "line2", "line3" };
     ++	int i = 0;
     ++	for (i = 0; names[i] != NULL; i++) {
     ++		assert(0 == strcmp(want[i], names[i]));
     ++	}
     ++	free_names(names);
     ++	remove(fn);
     ++}
     ++
     ++void test_parse_names(void)
     ++{
     ++	char buf[] = "line\n";
     ++	char **names = NULL;
     ++	parse_names(buf, strlen(buf), &names);
     ++
     ++	assert(NULL != names[0]);
     ++	assert(0 == strcmp(names[0], "line"));
     ++	assert(NULL == names[1]);
     ++	free_names(names);
     ++}
     ++
     ++void test_names_equal(void)
     ++{
     ++	char *a[] = { "a", "b", "c", NULL };
     ++	char *b[] = { "a", "b", "d", NULL };
     ++	char *c[] = { "a", "b", NULL };
     ++
     ++	assert(names_equal(a, a));
     ++	assert(!names_equal(a, b));
     ++	assert(!names_equal(a, c));
     ++}
     ++
     ++int write_test_ref(struct writer *wr, void *arg)
     ++{
     ++	struct ref_record *ref = arg;
     ++
     ++	writer_set_limits(wr, ref->update_index, ref->update_index);
     ++	int err = writer_add_ref(wr, ref);
     ++
     ++	return err;
     ++}
     ++
     ++int write_test_log(struct writer *wr, void *arg)
     ++{
     ++	struct log_record *log = arg;
     ++
     ++	writer_set_limits(wr, log->update_index, log->update_index);
     ++	int err = writer_add_log(wr, log);
     ++
     ++	return err;
     ++}
     ++
     ++void test_stack_add(void)
     ++{
     ++	int i = 0;
     ++	char dir[256] = "/tmp/stack.test_stack_add.XXXXXX";
     ++	assert(mkdtemp(dir));
     ++	printf("%s\n", dir);
     ++	char fn[256] = "";
     ++	strcat(fn, dir);
     ++	strcat(fn, "/refs");
     ++
     ++	struct write_options cfg = {};
     ++	struct stack *st = NULL;
     ++	int err = new_stack(&st, dir, fn, cfg);
     ++	assert_err(err);
     ++
     ++	struct ref_record refs[2] = {};
     ++	struct log_record logs[2] = {};
     ++	int N = ARRAYSIZE(refs);
     ++	for (i = 0; i < N; i++) {
     ++		char buf[256];
     ++		snprintf(buf, sizeof(buf), "branch%02d", i);
     ++		refs[i].ref_name = strdup(buf);
     ++		refs[i].value = malloc(SHA1_SIZE);
     ++		refs[i].update_index = i + 1;
     ++		set_test_hash(refs[i].value, i);
     ++
     ++		logs[i].ref_name = strdup(buf);
     ++		logs[i].update_index = N + i + 1;
     ++		logs[i].new_hash = malloc(SHA1_SIZE);
     ++		logs[i].email = strdup("identity@invalid");
     ++		set_test_hash(logs[i].new_hash, i);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		int err = stack_add(st, &write_test_ref, &refs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		int err = stack_add(st, &write_test_log, &logs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	err = stack_compact_all(st, NULL);
     ++	assert_err(err);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct ref_record dest = {};
     ++		int err = stack_read_ref(st, refs[i].ref_name, &dest);
     ++		assert_err(err);
     ++		assert(ref_record_equal(&dest, refs + i, SHA1_SIZE));
     ++		ref_record_clear(&dest);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct log_record dest = {};
     ++		int err = stack_read_log(st, refs[i].ref_name, &dest);
     ++		assert_err(err);
     ++		assert(log_record_equal(&dest, logs + i, SHA1_SIZE));
     ++		log_record_clear(&dest);
     ++	}
     ++
     ++	/* cleanup */
     ++	stack_destroy(st);
     ++	for (i = 0; i < N; i++) {
     ++		ref_record_clear(&refs[i]);
     ++		log_record_clear(&logs[i]);
     ++	}
     ++}
     ++
     ++void test_log2(void)
     ++{
     ++	assert(1 == fastlog2(3));
     ++	assert(2 == fastlog2(4));
     ++	assert(2 == fastlog2(5));
     ++}
     ++
     ++void test_sizes_to_segments(void)
     ++{
     ++	uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };
     ++	/* .................0  1  2  3  4  5 */
     ++
     ++	int seglen = 0;
     ++	struct segment *segs =
     ++		sizes_to_segments(&seglen, sizes, ARRAYSIZE(sizes));
     ++	assert(segs[2].log == 3);
     ++	assert(segs[2].start == 5);
     ++	assert(segs[2].end == 6);
     ++
     ++	assert(segs[1].log == 2);
     ++	assert(segs[1].start == 2);
     ++	assert(segs[1].end == 5);
     ++	free(segs);
     ++}
     ++
     ++void test_suggest_compaction_segment(void)
     ++{
     ++	{
     ++		uint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };
     ++		/* .................0    1    2  3   4  5  6 */
     ++		struct segment min =
     ++			suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     ++		assert(min.start == 2);
     ++		assert(min.end == 7);
     ++	}
     ++
     ++	{
     ++		uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
     ++		struct segment result =
     ++			suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     ++		assert(result.start == result.end);
     ++	}
     ++}
     ++
     ++void test_reflog_expire(void)
     ++{
     ++	char dir[256] = "/tmp/stack.test_reflog_expire.XXXXXX";
     ++	assert(mkdtemp(dir));
     ++	printf("%s\n", dir);
     ++	char fn[256] = "";
     ++	strcat(fn, dir);
     ++	strcat(fn, "/refs");
     ++
     ++	struct write_options cfg = {};
     ++	struct stack *st = NULL;
     ++	int err = new_stack(&st, dir, fn, cfg);
     ++	assert_err(err);
     ++
     ++	struct log_record logs[20] = {};
     ++	int N = ARRAYSIZE(logs) - 1;
     ++	int i = 0;
     ++	for (i = 1; i <= N; i++) {
     ++		char buf[256];
     ++		snprintf(buf, sizeof(buf), "branch%02d", i);
     ++
     ++		logs[i].ref_name = strdup(buf);
     ++		logs[i].update_index = i;
     ++		logs[i].time = i;
     ++		logs[i].new_hash = malloc(SHA1_SIZE);
     ++		logs[i].email = strdup("identity@invalid");
     ++		set_test_hash(logs[i].new_hash, i);
     ++	}
     ++
     ++	for (i = 1; i <= N; i++) {
     ++		int err = stack_add(st, &write_test_log, &logs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	err = stack_compact_all(st, NULL);
     ++	assert_err(err);
     ++
     ++	struct log_expiry_config expiry = {
     ++		.time = 10,
     ++	};
     ++	err = stack_compact_all(st, &expiry);
     ++	assert_err(err);
     ++
     ++	struct log_record log = {};
     ++	err = stack_read_log(st, logs[9].ref_name, &log);
     ++	assert(err == 1);
     ++
     ++	err = stack_read_log(st, logs[11].ref_name, &log);
     ++	assert_err(err);
     ++
     ++	expiry.min_update_index = 15;
     ++	err = stack_compact_all(st, &expiry);
     ++	assert_err(err);
     ++
     ++	err = stack_read_log(st, logs[14].ref_name, &log);
     ++	assert(err == 1);
     ++
     ++	err = stack_read_log(st, logs[16].ref_name, &log);
     ++	assert_err(err);
     ++
     ++	/* cleanup */
     ++	stack_destroy(st);
     ++	for (i = 0; i < N; i++) {
     ++		log_record_clear(&logs[i]);
     ++	}
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("test_reflog_expire", test_reflog_expire);
     ++	add_test_case("test_suggest_compaction_segment",
     ++		      &test_suggest_compaction_segment);
     ++	add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     ++	add_test_case("test_log2", &test_log2);
     ++	add_test_case("test_parse_names", &test_parse_names);
     ++	add_test_case("test_read_file", &test_read_file);
     ++	add_test_case("test_names_equal", &test_names_equal);
     ++	add_test_case("test_stack_add", &test_stack_add);
     ++	test_main();
     ++}
     +
     + diff --git a/reftable/system.h b/reftable/system.h
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/system.h
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+  char out[1024] = "line1\n\nline2\nline3";
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
      +
     -+  int n = write(fd, out, strlen(out));
     -+  assert(n == strlen(out));
     -+  int err = close(fd);
     -+  assert(err >= 0);
     ++#ifndef SYSTEM_H
     ++#define SYSTEM_H
      +
     -+  char **names = NULL;
     -+  err = read_lines(fn, &names);
     -+  assert_err(err);
     ++#include "config.h"
      +
     -+  char *want[] = {"line1", "line2", "line3"};
     -+  for (int i = 0; names[i] != NULL; i++) {
     -+    assert(0 == strcmp(want[i], names[i]));
     -+  }
     -+  free_names(names);
     -+  remove(fn);
     -+}
     -+
     -+void test_parse_names(void) {
     -+  char buf[] = "line\n";
     -+  char **names = NULL;
     -+  parse_names(buf, strlen(buf), &names);
     -+
     -+  assert(NULL != names[0]);
     -+  assert(0 == strcmp(names[0], "line"));
     -+  assert(NULL == names[1]);
     -+  free_names(names);
     -+}
     -+
     -+void test_names_equal(void) {
     -+  char *a[] = {"a", "b", "c", NULL};
     -+  char *b[] = {"a", "b", "d", NULL};
     -+  char *c[] = {"a", "b", NULL};
     -+
     -+  assert(names_equal(a, a));
     -+  assert(!names_equal(a, b));
     -+  assert(!names_equal(a, c));
     -+}
     -+
     -+int write_test_ref(struct writer *wr, void *arg) {
     -+  struct ref_record *ref = arg;
     -+
     -+  writer_set_limits(wr, ref->update_index, ref->update_index);
     -+  int err = writer_add_ref(wr, ref);
     -+
     -+  return err;
     -+}
     ++#ifndef REFTABLE_STANDALONE
     ++#include "git-compat-util.h"
     ++#else
     ++#include <assert.h>
     ++#include <errno.h>
     ++#include <fcntl.h>
     ++#include <stdint.h>
     ++#include <stdio.h>
     ++#include <stdlib.h>
     ++#include <string.h>
     ++#include <sys/stat.h>
     ++#include <sys/time.h>
     ++#include <sys/types.h>
     ++#include <unistd.h>
     ++#include <zlib.h>
      +
     -+int write_test_log(struct writer *wr, void *arg) {
     -+  struct log_record *log = arg;
     -+
     -+  writer_set_limits(wr, log->update_index, log->update_index);
     -+  int err = writer_add_log(wr, log);
     -+
     -+  return err;
     -+}
     -+
     -+void test_stack_add(void) {
     -+  char dir[256] = "/tmp/stack.XXXXXX";
     -+  assert(mkdtemp(dir));
     -+  printf("%s\n", dir);
     -+  char fn[256] = "";
     -+  strcat(fn, dir);
     -+  strcat(fn, "/refs");
     -+
     -+  struct write_options cfg = {};
     -+  struct stack *st = NULL;
     -+  int err = new_stack(&st, dir, fn, cfg);
     -+  assert_err(err);
     -+
     -+  struct ref_record refs[2] = {};
     -+  struct log_record logs[2] = {};
     -+  int N = ARRAYSIZE(refs);
     -+  for (int i = 0; i < N; i++) {
     -+    char buf[256];
     -+    sprintf(buf, "branch%02d", i);
     -+    refs[i].ref_name = strdup(buf);
     -+    refs[i].value = malloc(SHA1_SIZE);
     -+    refs[i].update_index = i + 1;
     -+    set_test_hash(refs[i].value, i);
     -+
     -+    logs[i].ref_name = strdup(buf);
     -+    logs[i].update_index = N + i+1;
     -+    logs[i].new_hash = malloc(SHA1_SIZE);
     -+    logs[i].email = strdup("identity@invalid");
     -+    set_test_hash(logs[i].new_hash, i);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    int err = stack_add(st, &write_test_ref, &refs[i]);
     -+    assert_err(err);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    int err = stack_add(st, &write_test_log, &logs[i]);
     -+    assert_err(err);
     -+  }
     -+
     -+  err = stack_compact_all(st, NULL);
     -+  assert_err(err);
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    struct ref_record dest = {};
     -+    int err = stack_read_ref(st, refs[i].ref_name, &dest);
     -+    assert_err(err);
     -+    assert(ref_record_equal(&dest, refs + i, SHA1_SIZE));
     -+    ref_record_clear(&dest);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    struct log_record dest = {};
     -+    int err = stack_read_log(st, refs[i].ref_name, &dest);
     -+    assert_err(err);
     -+    assert(log_record_equal(&dest, logs + i, SHA1_SIZE));
     -+    log_record_clear(&dest);
     -+  }
     -+
     -+  // cleanup
     -+  stack_destroy(st);
     -+  for (int i = 0; i < N; i++) {
     -+    ref_record_clear(&refs[i]);
     -+    log_record_clear(&logs[i]);
     -+  }
     -+}
     -+
     -+void test_log2(void) {
     -+  assert(1 == fastlog2(3));
     -+  assert(2 == fastlog2(4));
     -+  assert(2 == fastlog2(5));
     -+}
     -+
     -+void test_sizes_to_segments(void) {
     -+  uint64_t sizes[] = {2, 3, 4, 5, 7, 9};
     -+  // .................0  1  2  3  4  5
     -+
     -+  int seglen = 0;
     -+  struct segment *segs = sizes_to_segments(&seglen, sizes, ARRAYSIZE(sizes));
     -+  assert(segs[2].log == 3);
     -+  assert(segs[2].start == 5);
     -+  assert(segs[2].end == 6);
     -+
     -+  assert(segs[1].log == 2);
     -+  assert(segs[1].start == 2);
     -+  assert(segs[1].end == 5);
     -+  free(segs);
     -+}
     -+
     -+void test_suggest_compaction_segment(void) {
     -+  {
     -+    uint64_t sizes[] = {128, 64, 17, 16, 9, 9, 9, 16, 16};
     -+    // .................0    1    2  3   4  5  6
     -+    struct segment min = suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     -+    assert(min.start == 2);
     -+    assert(min.end == 7);
     -+  }
     -+
     -+  {
     -+    uint64_t sizes[] = {64, 32, 16, 8, 4, 2};
     -+    struct segment result = suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     -+    assert(result.start == result.end);
     -+  }
     -+}
     -+
     -+void test_reflog_expire(void) {
     -+  char dir[256] = "/tmp/stack.XXXXXX";
     -+  assert(mkdtemp(dir));
     -+  printf("%s\n", dir);
     -+  char fn[256] = "";
     -+  strcat(fn, dir);
     -+  strcat(fn, "/refs");
     -+
     -+  struct write_options cfg = {};
     -+  struct stack *st = NULL;
     -+  int err = new_stack(&st, dir, fn, cfg);
     -+  assert_err(err);
     -+
     -+  struct log_record logs[20] = {};
     -+  int N = ARRAYSIZE(logs) -1;
     -+  for (int i = 1; i <= N; i++) {
     -+    char buf[256];
     -+    sprintf(buf, "branch%02d", i);
     -+
     -+    logs[i].ref_name = strdup(buf);
     -+    logs[i].update_index = i;
     -+    logs[i].time = i;
     -+    logs[i].new_hash = malloc(SHA1_SIZE);
     -+    logs[i].email = strdup("identity@invalid");
     -+    set_test_hash(logs[i].new_hash, i);
     -+  }
     -+
     -+  for (int i = 1; i <= N; i++) {
     -+    int err = stack_add(st, &write_test_log, &logs[i]);
     -+    assert_err(err);
     -+  }
     -+
     -+  err = stack_compact_all(st, NULL);
     -+  assert_err(err);
     -+
     -+  struct log_expiry_config expiry = {
     -+    .time = 10,
     -+  };
     -+  err = stack_compact_all(st, &expiry);
     -+  assert_err(err);
     -+
     -+  struct log_record log = {};
     -+  err = stack_read_log(st, logs[9].ref_name, &log);
     -+  assert(err == 1);
     -+
     -+  err = stack_read_log(st, logs[11].ref_name, &log);
     -+  assert_err(err);
     -+
     -+  expiry.min_update_index = 15;
     -+  err = stack_compact_all(st, &expiry);
     -+  assert_err(err);
     -+
     -+  err = stack_read_log(st, logs[14].ref_name, &log);
     -+  assert(err == 1);
     -+
     -+  err = stack_read_log(st, logs[16].ref_name, &log);
     -+  assert_err(err);
     -+
     -+  // cleanup
     -+  stack_destroy(st);
     -+  for (int i = 0; i < N; i++) {
     -+    log_record_clear(&logs[i]);
     -+  }
     -+}
     -+
     -+int main() {
     -+  add_test_case("test_reflog_expire", test_reflog_expire);
     -+  add_test_case("test_suggest_compaction_segment",
     -+                &test_suggest_compaction_segment);
     -+  add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     -+  add_test_case("test_log2", &test_log2);
     -+  add_test_case("test_parse_names", &test_parse_names);
     -+  add_test_case("test_read_file", &test_read_file);
     -+  add_test_case("test_names_equal", &test_names_equal);
     -+  add_test_case("test_stack_add", &test_stack_add);
     -+  test_main();
     -+}
     ++#define PRIuMAX "lu"
     ++#define PRIdMAX "ld"
     ++#define PRIxMAX "lx"
     ++
     ++#endif
     ++
     ++#endif
      
       diff --git a/reftable/test_framework.c b/reftable/test_framework.c
       new file mode 100644
     @@ -7005,9 +7211,7 @@
      +
      +#include "test_framework.h"
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "constants.h"
      +
     @@ -7015,45 +7219,54 @@
      +int test_case_len;
      +int test_case_cap;
      +
     -+struct test_case *new_test_case(const char *name, void (*testfunc)()) {
     -+  struct test_case *tc = malloc(sizeof(struct test_case));
     -+  tc->name = name;
     -+  tc->testfunc = testfunc;
     -+  return tc;
     -+}
     -+
     -+struct test_case *add_test_case(const char *name, void (*testfunc)()) {
     -+  struct test_case *tc = new_test_case(name, testfunc);
     -+  if (test_case_len == test_case_cap) {
     -+    test_case_cap = 2 * test_case_cap + 1;
     -+    test_cases = realloc(test_cases, sizeof(struct test_case) * test_case_cap);
     -+  }
     -+
     -+  test_cases[test_case_len++] = tc;
     -+  return tc;
     -+}
     -+
     -+void test_main() {
     -+  for (int i = 0; i < test_case_len; i++) {
     -+    printf("case %s\n", test_cases[i]->name);
     -+    test_cases[i]->testfunc();
     -+  }
     -+}
     -+
     -+void set_test_hash(byte *p, int i) { memset(p, (byte)i, SHA1_SIZE); }
     -+
     -+void print_names(char **a) {
     -+  if (a == NULL || *a == NULL) {
     -+    puts("[]");
     -+    return;
     -+  }
     -+  puts("[");
     -+  char **p = a;
     -+  while (*p) {
     -+    puts(*p);
     -+    p++;
     -+  }
     -+  puts("]");
     ++struct test_case *new_test_case(const char *name, void (*testfunc)())
     ++{
     ++	struct test_case *tc = malloc(sizeof(struct test_case));
     ++	tc->name = name;
     ++	tc->testfunc = testfunc;
     ++	return tc;
     ++}
     ++
     ++struct test_case *add_test_case(const char *name, void (*testfunc)())
     ++{
     ++	struct test_case *tc = new_test_case(name, testfunc);
     ++	if (test_case_len == test_case_cap) {
     ++		test_case_cap = 2 * test_case_cap + 1;
     ++		test_cases = realloc(test_cases,
     ++				     sizeof(struct test_case) * test_case_cap);
     ++	}
     ++
     ++	test_cases[test_case_len++] = tc;
     ++	return tc;
     ++}
     ++
     ++void test_main()
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < test_case_len; i++) {
     ++		printf("case %s\n", test_cases[i]->name);
     ++		test_cases[i]->testfunc();
     ++	}
     ++}
     ++
     ++void set_test_hash(byte *p, int i)
     ++{
     ++	memset(p, (byte)i, SHA1_SIZE);
     ++}
     ++
     ++void print_names(char **a)
     ++{
     ++	if (a == NULL || *a == NULL) {
     ++		puts("[]");
     ++		return;
     ++	}
     ++	puts("[");
     ++	char **p = a;
     ++	while (*p) {
     ++		puts(*p);
     ++		p++;
     ++	}
     ++	puts("]");
      +}
      
       diff --git a/reftable/test_framework.h b/reftable/test_framework.h
     @@ -7072,8 +7285,7 @@
      +#ifndef TEST_FRAMEWORK_H
      +#define TEST_FRAMEWORK_H
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     @@ -7081,40 +7293,42 @@
      +#undef NDEBUG
      +#endif
      +
     -+#include <assert.h>
     ++#include "system.h"
      +
      +#ifdef assert
      +#undef assert
      +#endif
      +
     -+#define assert_err(c)                                                        \
     -+  if (c != 0) {                                                              \
     -+    fflush(stderr);                                                          \
     -+    fflush(stdout);                                                          \
     -+    fprintf(stderr, "%s: %d: error == %d (%s), want 0\n", __FILE__, __LINE__, c, error_str(c)); \
     -+    abort();                                                                 \
     -+  }
     -+
     -+#define assert_streq(a, b)                                                    \
     -+  if (0 != strcmp(a, b)) {                                                    \
     -+    fflush(stderr);                                                           \
     -+    fflush(stdout);                                                           \
     -+    fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, __LINE__, #a, a, \
     -+            #b, b);                                                           \
     -+    abort();                                                                  \
     -+  }
     -+
     -+#define assert(c)                                                             \
     -+  if (!(c)) {                                                                 \
     -+    fflush(stderr);                                                           \
     -+    fflush(stdout);                                                           \
     -+    fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, __LINE__, #c); \
     -+    abort();                                                                  \
     -+  }
     ++#define assert_err(c)                                                 \
     ++	if (c != 0) {                                                 \
     ++		fflush(stderr);                                       \
     ++		fflush(stdout);                                       \
     ++		fprintf(stderr, "%s: %d: error == %d (%s), want 0\n", \
     ++			__FILE__, __LINE__, c, error_str(c));         \
     ++		abort();                                              \
     ++	}
     ++
     ++#define assert_streq(a, b)                                               \
     ++	if (strcmp(a, b)) {                                              \
     ++		fflush(stderr);                                          \
     ++		fflush(stdout);                                          \
     ++		fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \
     ++			__LINE__, #a, a, #b, b);                         \
     ++		abort();                                                 \
     ++	}
     ++
     ++#define assert(c)                                                          \
     ++	if (!(c)) {                                                        \
     ++		fflush(stderr);                                            \
     ++		fflush(stdout);                                            \
     ++		fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \
     ++			__LINE__, #c);                                     \
     ++		abort();                                                   \
     ++	}
      +
      +struct test_case {
     -+  const char *name;
     -+  void (*testfunc)();
     ++	const char *name;
     ++	void (*testfunc)();
      +};
      +
      +struct test_case *new_test_case(const char *name, void (*testfunc)());
     @@ -7140,55 +7354,61 @@
      +
      +#include "tree.h"
      +
     -+#include <stdlib.h>
     ++#include "system.h"
      +
      +struct tree_node *tree_search(void *key, struct tree_node **rootp,
     -+                              int (*compare)(const void *, const void *),
     -+                              int insert) {
     -+  if (*rootp == NULL) {
     -+    if (!insert) {
     -+      return NULL;
     -+    } else {
     -+      struct tree_node *n = calloc(sizeof(struct tree_node), 1);
     -+      n->key = key;
     -+      *rootp = n;
     -+      return *rootp;
     -+    }
     -+  }
     -+
     -+  {
     -+    int res = compare(key, (*rootp)->key);
     -+    if (res < 0) {
     -+      return tree_search(key, &(*rootp)->left, compare, insert);
     -+    } else if (res > 0) {
     -+      return tree_search(key, &(*rootp)->right, compare, insert);
     -+    }
     -+  }
     -+  return *rootp;
     ++			      int (*compare)(const void *, const void *),
     ++			      int insert)
     ++{
     ++	if (*rootp == NULL) {
     ++		if (!insert) {
     ++			return NULL;
     ++		} else {
     ++			struct tree_node *n =
     ++				calloc(sizeof(struct tree_node), 1);
     ++			n->key = key;
     ++			*rootp = n;
     ++			return *rootp;
     ++		}
     ++	}
     ++
     ++	{
     ++		int res = compare(key, (*rootp)->key);
     ++		if (res < 0) {
     ++			return tree_search(key, &(*rootp)->left, compare,
     ++					   insert);
     ++		} else if (res > 0) {
     ++			return tree_search(key, &(*rootp)->right, compare,
     ++					   insert);
     ++		}
     ++	}
     ++	return *rootp;
      +}
      +
      +void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
     -+                void *arg) {
     -+  if (t->left != NULL) {
     -+    infix_walk(t->left, action, arg);
     -+  }
     -+  action(arg, t->key);
     -+  if (t->right != NULL) {
     -+    infix_walk(t->right, action, arg);
     -+  }
     -+}
     -+
     -+void tree_free(struct tree_node *t) {
     -+  if (t == NULL) {
     -+    return;
     -+  }
     -+  if (t->left != NULL) {
     -+    tree_free(t->left);
     -+  }
     -+  if (t->right != NULL) {
     -+    tree_free(t->right);
     -+  }
     -+  free(t);
     ++		void *arg)
     ++{
     ++	if (t->left != NULL) {
     ++		infix_walk(t->left, action, arg);
     ++	}
     ++	action(arg, t->key);
     ++	if (t->right != NULL) {
     ++		infix_walk(t->right, action, arg);
     ++	}
     ++}
     ++
     ++void tree_free(struct tree_node *t)
     ++{
     ++	if (t == NULL) {
     ++		return;
     ++	}
     ++	if (t->left != NULL) {
     ++		tree_free(t->left);
     ++	}
     ++	if (t->right != NULL) {
     ++		tree_free(t->right);
     ++	}
     ++	free(t);
      +}
      
       diff --git a/reftable/tree.h b/reftable/tree.h
     @@ -7208,15 +7428,15 @@
      +#define TREE_H
      +
      +struct tree_node {
     -+  void *key;
     -+  struct tree_node *left, *right;
     ++	void *key;
     ++	struct tree_node *left, *right;
      +};
      +
      +struct tree_node *tree_search(void *key, struct tree_node **rootp,
     -+                              int (*compare)(const void *, const void *),
     -+                              int insert);
     ++			      int (*compare)(const void *, const void *),
     ++			      int insert);
      +void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
     -+                void *arg);
     ++		void *arg);
      +void tree_free(struct tree_node *t);
      +
      +#endif
     @@ -7241,44 +7461,51 @@
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+static int test_compare(const void *a, const void *b) { return a - b; }
     ++static int test_compare(const void *a, const void *b)
     ++{
     ++	return a - b;
     ++}
      +
      +struct curry {
     -+  void *last;
     ++	void *last;
      +};
      +
     -+void check_increasing(void *arg, void *key) {
     -+  struct curry *c = (struct curry *)arg;
     -+  if (c->last != NULL) {
     -+    assert(test_compare(c->last, key) < 0);
     -+  }
     -+  c->last = key;
     ++void check_increasing(void *arg, void *key)
     ++{
     ++	struct curry *c = (struct curry *)arg;
     ++	if (c->last != NULL) {
     ++		assert(test_compare(c->last, key) < 0);
     ++	}
     ++	c->last = key;
      +}
      +
     -+void test_tree() {
     -+  struct tree_node *root = NULL;
     ++void test_tree()
     ++{
     ++	struct tree_node *root = NULL;
      +
     -+  void *values[11] = {};
     -+  struct tree_node *nodes[11] = {};
     -+  int i = 1;
     -+  do {
     -+    nodes[i] = tree_search(values + i, &root, &test_compare, 1);
     -+    i = (i * 7) % 11;
     -+  } while (i != 1);
     ++	void *values[11] = {};
     ++	struct tree_node *nodes[11] = {};
     ++	int i = 1;
     ++	do {
     ++		nodes[i] = tree_search(values + i, &root, &test_compare, 1);
     ++		i = (i * 7) % 11;
     ++	} while (i != 1);
      +
     -+  for (int i = 1; i < ARRAYSIZE(nodes); i++) {
     -+    assert(values + i == nodes[i]->key);
     -+    assert(nodes[i] == tree_search(values + i, &root, &test_compare, 0));
     -+  }
     ++	for (i = 1; i < ARRAYSIZE(nodes); i++) {
     ++		assert(values + i == nodes[i]->key);
     ++		assert(nodes[i] ==
     ++		       tree_search(values + i, &root, &test_compare, 0));
     ++	}
      +
     -+  struct curry c = {};
     -+  infix_walk(root, check_increasing, &c);
     -+  tree_free(root);
     ++	struct curry c = {};
     ++	infix_walk(root, check_increasing, &c);
     ++	tree_free(root);
      +}
      +
     -+int main() {
     -+  add_test_case("test_tree", &test_tree);
     -+  test_main();
     ++int main()
     ++{
     ++	add_test_case("test_tree", &test_tree);
     ++	test_main();
      +}
      
       diff --git a/reftable/writer.c b/reftable/writer.c
     @@ -7296,12 +7523,7 @@
      +
      +#include "writer.h"
      +
     -+#include <assert.h>
     -+#include <stdint.h>
     -+#include <stdio.h>  // debug
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <zlib.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "constants.h"
     @@ -7309,568 +7531,611 @@
      +#include "reftable.h"
      +#include "tree.h"
      +
     -+static struct block_stats *writer_block_stats(struct writer *w, byte typ) {
     -+  switch (typ) {
     -+    case 'r':
     -+      return &w->stats.ref_stats;
     -+    case 'o':
     -+      return &w->stats.obj_stats;
     -+    case 'i':
     -+      return &w->stats.idx_stats;
     -+    case 'g':
     -+      return &w->stats.log_stats;
     -+  }
     -+  assert(false);
     ++static struct block_stats *writer_block_stats(struct writer *w, byte typ)
     ++{
     ++	switch (typ) {
     ++	case 'r':
     ++		return &w->stats.ref_stats;
     ++	case 'o':
     ++		return &w->stats.obj_stats;
     ++	case 'i':
     ++		return &w->stats.idx_stats;
     ++	case 'g':
     ++		return &w->stats.log_stats;
     ++	}
     ++	assert(false);
      +}
      +
      +/* write data, queuing the padding for the next write. Returns negative for
      + * error. */
     -+static int padded_write(struct writer *w, byte *data, size_t len, int padding) {
     -+  int n = 0;
     -+  if (w->pending_padding > 0) {
     -+    byte *zeroed = calloc(w->pending_padding, 1);
     -+    int n = w->write(w->write_arg, zeroed, w->pending_padding);
     -+    if (n < 0) {
     -+      return n;
     -+    }
     -+
     -+    w->pending_padding = 0;
     -+    free(zeroed);
     -+  }
     -+
     -+  w->pending_padding = padding;
     -+  n = w->write(w->write_arg, data, len);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+  n += padding;
     -+  return 0;
     -+}
     -+
     -+static void options_set_defaults(struct write_options *opts) {
     -+  if (opts->restart_interval == 0) {
     -+    opts->restart_interval = 16;
     -+  }
     -+
     -+  if (opts->block_size == 0) {
     -+    opts->block_size = DEFAULT_BLOCK_SIZE;
     -+  }
     -+}
     -+
     -+static int writer_write_header(struct writer *w, byte *dest) {
     -+  strcpy((char *)dest, "REFT");
     -+  dest[4] = 1;  // version
     -+  put_u24(dest + 5, w->opts.block_size);
     -+  put_u64(dest + 8, w->min_update_index);
     -+  put_u64(dest + 16, w->max_update_index);
     -+  return 24;
     -+}
     -+
     -+static void writer_reinit_block_writer(struct writer *w, byte typ) {
     -+  int block_start = 0;
     -+  if (w->next == 0) {
     -+    block_start = HEADER_SIZE;
     -+  }
     -+
     -+  block_writer_init(&w->block_writer_data, typ, w->block, w->opts.block_size,
     -+                    block_start, w->hash_size);
     -+  w->block_writer = &w->block_writer_data;
     -+  w->block_writer->restart_interval = w->opts.restart_interval;
     ++static int padded_write(struct writer *w, byte *data, size_t len, int padding)
     ++{
     ++	int n = 0;
     ++	if (w->pending_padding > 0) {
     ++		byte *zeroed = calloc(w->pending_padding, 1);
     ++		int n = w->write(w->write_arg, zeroed, w->pending_padding);
     ++		if (n < 0) {
     ++			return n;
     ++		}
     ++
     ++		w->pending_padding = 0;
     ++		free(zeroed);
     ++	}
     ++
     ++	w->pending_padding = padding;
     ++	n = w->write(w->write_arg, data, len);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++	n += padding;
     ++	return 0;
     ++}
     ++
     ++static void options_set_defaults(struct write_options *opts)
     ++{
     ++	if (opts->restart_interval == 0) {
     ++		opts->restart_interval = 16;
     ++	}
     ++
     ++	if (opts->block_size == 0) {
     ++		opts->block_size = DEFAULT_BLOCK_SIZE;
     ++	}
     ++}
     ++
     ++static int writer_write_header(struct writer *w, byte *dest)
     ++{
     ++	memcpy((char *)dest, "REFT", 4);
     ++	dest[4] = 1; /* version */
     ++	put_u24(dest + 5, w->opts.block_size);
     ++	put_u64(dest + 8, w->min_update_index);
     ++	put_u64(dest + 16, w->max_update_index);
     ++	return 24;
     ++}
     ++
     ++static void writer_reinit_block_writer(struct writer *w, byte typ)
     ++{
     ++	int block_start = 0;
     ++	if (w->next == 0) {
     ++		block_start = HEADER_SIZE;
     ++	}
     ++
     ++	block_writer_init(&w->block_writer_data, typ, w->block,
     ++			  w->opts.block_size, block_start, w->hash_size);
     ++	w->block_writer = &w->block_writer_data;
     ++	w->block_writer->restart_interval = w->opts.restart_interval;
      +}
      +
      +struct writer *new_writer(int (*writer_func)(void *, byte *, int),
     -+                          void *writer_arg, struct write_options *opts) {
     -+  struct writer *wp = calloc(sizeof(struct writer), 1);
     -+  options_set_defaults(opts);
     -+  if (opts->block_size >= (1 << 24)) {
     -+    // TODO - error return?
     -+    abort();
     -+  }
     -+  wp->hash_size = SHA1_SIZE;
     -+  wp->block = calloc(opts->block_size, 1);
     -+  wp->write = writer_func;
     -+  wp->write_arg = writer_arg;
     -+  wp->opts = *opts;
     -+  writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
     ++			  void *writer_arg, struct write_options *opts)
     ++{
     ++	struct writer *wp = calloc(sizeof(struct writer), 1);
     ++	options_set_defaults(opts);
     ++	if (opts->block_size >= (1 << 24)) {
     ++		/* TODO - error return? */
     ++		abort();
     ++	}
     ++	wp->hash_size = SHA1_SIZE;
     ++	wp->block = calloc(opts->block_size, 1);
     ++	wp->write = writer_func;
     ++	wp->write_arg = writer_arg;
     ++	wp->opts = *opts;
     ++	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
      +
     -+  return wp;
     ++	return wp;
      +}
      +
     -+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max) {
     -+  w->min_update_index = min;
     -+  w->max_update_index = max;
     ++void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
     ++{
     ++	w->min_update_index = min;
     ++	w->max_update_index = max;
      +}
      +
     -+void writer_free(struct writer *w) {
     -+  free(w->block);
     -+  free(w);
     ++void writer_free(struct writer *w)
     ++{
     ++	free(w->block);
     ++	free(w);
      +}
      +
      +struct obj_index_tree_node {
     -+  struct slice hash;
     -+  uint64_t *offsets;
     -+  int offset_len;
     -+  int offset_cap;
     ++	struct slice hash;
     ++	uint64_t *offsets;
     ++	int offset_len;
     ++	int offset_cap;
      +};
      +
     -+static int obj_index_tree_node_compare(const void *a, const void *b) {
     -+  return slice_compare(((const struct obj_index_tree_node *)a)->hash,
     -+                       ((const struct obj_index_tree_node *)b)->hash);
     -+}
     -+
     -+static void writer_index_hash(struct writer *w, struct slice hash) {
     -+  uint64_t off = w->next;
     -+
     -+  struct obj_index_tree_node want = {.hash = hash};
     -+
     -+  struct tree_node *node =
     -+      tree_search(&want, &w->obj_index_tree, &obj_index_tree_node_compare, 0);
     -+  struct obj_index_tree_node *key = NULL;
     -+  if (node == NULL) {
     -+    key = calloc(sizeof(struct obj_index_tree_node), 1);
     -+    slice_copy(&key->hash, hash);
     -+    tree_search((void *)key, &w->obj_index_tree, &obj_index_tree_node_compare,
     -+                1);
     -+  } else {
     -+    key = node->key;
     -+  }
     -+
     -+  if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
     -+    return;
     -+  }
     -+
     -+  if (key->offset_len == key->offset_cap) {
     -+    key->offset_cap = 2 * key->offset_cap + 1;
     -+    key->offsets = realloc(key->offsets, sizeof(uint64_t) * key->offset_cap);
     -+  }
     -+
     -+  key->offsets[key->offset_len++] = off;
     -+}
     -+
     -+static int writer_add_record(struct writer *w, struct record rec) {
     -+  int result = -1;
     -+  struct slice key = {};
     -+  int err = 0;
     -+  record_key(rec, &key);
     -+  if (slice_compare(w->last_key, key) >= 0) {
     -+    goto exit;
     -+  }
     -+
     -+  slice_copy(&w->last_key, key);
     -+  if (w->block_writer == NULL) {
     -+    writer_reinit_block_writer(w, record_type(rec));
     -+  }
     -+
     -+  assert(block_writer_type(w->block_writer) == record_type(rec));
     -+
     -+  if (block_writer_add(w->block_writer, rec) == 0) {
     -+    result = 0;
     -+    goto exit;
     -+  }
     -+
     -+  err = writer_flush_block(w);
     -+  if (err < 0) {
     -+    result = err;
     -+    goto exit;
     -+  }
     -+
     -+  writer_reinit_block_writer(w, record_type(rec));
     -+  err = block_writer_add(w->block_writer, rec);
     -+  if (err < 0) {
     -+    result = err;
     -+    goto exit;
     -+  }
     -+
     -+  result = 0;
     ++static int obj_index_tree_node_compare(const void *a, const void *b)
     ++{
     ++	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
     ++			     ((const struct obj_index_tree_node *)b)->hash);
     ++}
     ++
     ++static void writer_index_hash(struct writer *w, struct slice hash)
     ++{
     ++	uint64_t off = w->next;
     ++
     ++	struct obj_index_tree_node want = { .hash = hash };
     ++
     ++	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
     ++					     &obj_index_tree_node_compare, 0);
     ++	struct obj_index_tree_node *key = NULL;
     ++	if (node == NULL) {
     ++		key = calloc(sizeof(struct obj_index_tree_node), 1);
     ++		slice_copy(&key->hash, hash);
     ++		tree_search((void *)key, &w->obj_index_tree,
     ++			    &obj_index_tree_node_compare, 1);
     ++	} else {
     ++		key = node->key;
     ++	}
     ++
     ++	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
     ++		return;
     ++	}
     ++
     ++	if (key->offset_len == key->offset_cap) {
     ++		key->offset_cap = 2 * key->offset_cap + 1;
     ++		key->offsets = realloc(key->offsets,
     ++				       sizeof(uint64_t) * key->offset_cap);
     ++	}
     ++
     ++	key->offsets[key->offset_len++] = off;
     ++}
     ++
     ++static int writer_add_record(struct writer *w, struct record rec)
     ++{
     ++	int result = -1;
     ++	struct slice key = {};
     ++	int err = 0;
     ++	record_key(rec, &key);
     ++	if (slice_compare(w->last_key, key) >= 0) {
     ++		goto exit;
     ++	}
     ++
     ++	slice_copy(&w->last_key, key);
     ++	if (w->block_writer == NULL) {
     ++		writer_reinit_block_writer(w, record_type(rec));
     ++	}
     ++
     ++	assert(block_writer_type(w->block_writer) == record_type(rec));
     ++
     ++	if (block_writer_add(w->block_writer, rec) == 0) {
     ++		result = 0;
     ++		goto exit;
     ++	}
     ++
     ++	err = writer_flush_block(w);
     ++	if (err < 0) {
     ++		result = err;
     ++		goto exit;
     ++	}
     ++
     ++	writer_reinit_block_writer(w, record_type(rec));
     ++	err = block_writer_add(w->block_writer, rec);
     ++	if (err < 0) {
     ++		result = err;
     ++		goto exit;
     ++	}
     ++
     ++	result = 0;
      +exit:
     -+  free(slice_yield(&key));
     -+  return result;
     -+}
     -+
     -+int writer_add_ref(struct writer *w, struct ref_record *ref) {
     -+  struct record rec = {};
     -+  struct ref_record copy = *ref;
     -+  int err = 0;
     -+
     -+  if (ref->update_index < w->min_update_index ||
     -+      ref->update_index > w->max_update_index) {
     -+    return API_ERROR;
     -+  }
     -+
     -+  record_from_ref(&rec, &copy);
     -+  copy.update_index -= w->min_update_index;
     -+  err = writer_add_record(w, rec);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  if (!w->opts.skip_index_objects && ref->value != NULL) {
     -+    struct slice h = {
     -+        .buf = ref->value,
     -+        .len = w->hash_size,
     -+    };
     -+
     -+    writer_index_hash(w, h);
     -+  }
     -+  if (!w->opts.skip_index_objects && ref->target_value != NULL) {
     -+    struct slice h = {
     -+        .buf = ref->target_value,
     -+        .len = w->hash_size,
     -+    };
     -+    writer_index_hash(w, h);
     -+  }
     -+  return 0;
     -+}
     -+
     -+int writer_add_refs(struct writer *w, struct ref_record *refs, int n) {
     -+  int err = 0;
     -+  qsort(refs, n, sizeof(struct ref_record), ref_record_compare_name);
     -+  for (int i = 0; err == 0 && i < n; i++) {
     -+    err = writer_add_ref(w, &refs[i]);
     -+  }
     -+  return err;
     -+}
     -+
     -+int writer_add_log(struct writer *w, struct log_record *log) {
     -+  if (w->block_writer != NULL &&
     -+      block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
     -+    int err = writer_finish_public_section(w);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+  }
     -+
     -+  {
     -+    struct record rec = {};
     -+    int err;
     -+    record_from_log(&rec, log);
     -+    err = writer_add_record(w, rec);
     -+    return err;
     -+  }
     -+}
     -+
     -+int writer_add_logs(struct writer *w, struct log_record *logs, int n) {
     -+  int err = 0;
     -+  qsort(logs, n, sizeof(struct log_record), log_record_compare_key);
     -+  for (int i = 0; err == 0 && i < n; i++) {
     -+    err = writer_add_log(w, &logs[i]);
     -+  }
     -+  return err;
     -+}
     -+
     -+
     -+static int writer_finish_section(struct writer *w) {
     -+  byte typ = block_writer_type(w->block_writer);
     -+  uint64_t index_start = 0;
     -+  int max_level = 0;
     -+  int threshold = w->opts.unpadded ? 1 : 3;
     -+  int before_blocks = w->stats.idx_stats.blocks;
     -+  int err = writer_flush_block(w);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  while (w->index_len > threshold) {
     -+    struct index_record *idx = NULL;
     -+    int idx_len = 0;
     -+
     -+    max_level++;
     -+    index_start = w->next;
     -+    writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     -+
     -+    idx = w->index;
     -+    idx_len = w->index_len;
     -+
     -+    w->index = NULL;
     -+    w->index_len = 0;
     -+    w->index_cap = 0;
     -+    for (int i = 0; i < idx_len; i++) {
     -+      struct record rec = {};
     -+      record_from_index(&rec, idx + i);
     -+      if (block_writer_add(w->block_writer, rec) == 0) {
     -+        continue;
     -+      }
     -+
     -+      {
     -+        int err = writer_flush_block(w);
     -+        if (err < 0) {
     -+          return err;
     -+        }
     -+      }
     -+
     -+      writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     -+
     -+      err = block_writer_add(w->block_writer, rec);
     -+      assert(err == 0);
     -+    }
     -+    for (int i = 0; i < idx_len; i++) {
     -+      free(slice_yield(&idx[i].last_key));
     -+    }
     -+    free(idx);
     -+  }
     -+
     -+  writer_clear_index(w);
     -+
     -+  err = writer_flush_block(w);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct block_stats *bstats = writer_block_stats(w, typ);
     -+    bstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;
     -+    bstats->index_offset = index_start;
     -+    bstats->max_index_level = max_level;
     -+  }
     -+
     -+  // Reinit lastKey, as the next section can start with any key.
     -+  w->last_key.len = 0;
     -+
     -+  return 0;
     ++	free(slice_yield(&key));
     ++	return result;
     ++}
     ++
     ++int writer_add_ref(struct writer *w, struct ref_record *ref)
     ++{
     ++	struct record rec = {};
     ++	struct ref_record copy = *ref;
     ++	int err = 0;
     ++
     ++	if (ref->ref_name == NULL) {
     ++		return API_ERROR;
     ++	}
     ++	if (ref->update_index < w->min_update_index ||
     ++	    ref->update_index > w->max_update_index) {
     ++		return API_ERROR;
     ++	}
     ++
     ++	record_from_ref(&rec, &copy);
     ++	copy.update_index -= w->min_update_index;
     ++	err = writer_add_record(w, rec);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	if (!w->opts.skip_index_objects && ref->value != NULL) {
     ++		struct slice h = {
     ++			.buf = ref->value,
     ++			.len = w->hash_size,
     ++		};
     ++
     ++		writer_index_hash(w, h);
     ++	}
     ++	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
     ++		struct slice h = {
     ++			.buf = ref->target_value,
     ++			.len = w->hash_size,
     ++		};
     ++		writer_index_hash(w, h);
     ++	}
     ++	return 0;
     ++}
     ++
     ++int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
     ++{
     ++	int err = 0;
     ++	int i = 0;
     ++	qsort(refs, n, sizeof(struct ref_record), ref_record_compare_name);
     ++	for (i = 0; err == 0 && i < n; i++) {
     ++		err = writer_add_ref(w, &refs[i]);
     ++	}
     ++	return err;
     ++}
     ++
     ++int writer_add_log(struct writer *w, struct log_record *log)
     ++{
     ++	if (log->ref_name == NULL) {
     ++		return API_ERROR;
     ++	}
     ++
     ++	if (w->block_writer != NULL &&
     ++	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
     ++		int err = writer_finish_public_section(w);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	w->next -= w->pending_padding;
     ++	w->pending_padding = 0;
     ++
     ++	{
     ++		struct record rec = {};
     ++		int err;
     ++		record_from_log(&rec, log);
     ++		err = writer_add_record(w, rec);
     ++		return err;
     ++	}
     ++}
     ++
     ++int writer_add_logs(struct writer *w, struct log_record *logs, int n)
     ++{
     ++	int err = 0;
     ++	int i = 0;
     ++	qsort(logs, n, sizeof(struct log_record), log_record_compare_key);
     ++	for (i = 0; err == 0 && i < n; i++) {
     ++		err = writer_add_log(w, &logs[i]);
     ++	}
     ++	return err;
     ++}
     ++
     ++static int writer_finish_section(struct writer *w)
     ++{
     ++	byte typ = block_writer_type(w->block_writer);
     ++	uint64_t index_start = 0;
     ++	int max_level = 0;
     ++	int threshold = w->opts.unpadded ? 1 : 3;
     ++	int before_blocks = w->stats.idx_stats.blocks;
     ++	int err = writer_flush_block(w);
     ++	int i = 0;
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	while (w->index_len > threshold) {
     ++		struct index_record *idx = NULL;
     ++		int idx_len = 0;
     ++
     ++		max_level++;
     ++		index_start = w->next;
     ++		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     ++
     ++		idx = w->index;
     ++		idx_len = w->index_len;
     ++
     ++		w->index = NULL;
     ++		w->index_len = 0;
     ++		w->index_cap = 0;
     ++		for (i = 0; i < idx_len; i++) {
     ++			struct record rec = {};
     ++			record_from_index(&rec, idx + i);
     ++			if (block_writer_add(w->block_writer, rec) == 0) {
     ++				continue;
     ++			}
     ++
     ++			{
     ++				int err = writer_flush_block(w);
     ++				if (err < 0) {
     ++					return err;
     ++				}
     ++			}
     ++
     ++			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     ++
     ++			err = block_writer_add(w->block_writer, rec);
     ++			assert(err == 0);
     ++		}
     ++		for (i = 0; i < idx_len; i++) {
     ++			free(slice_yield(&idx[i].last_key));
     ++		}
     ++		free(idx);
     ++	}
     ++
     ++	writer_clear_index(w);
     ++
     ++	err = writer_flush_block(w);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct block_stats *bstats = writer_block_stats(w, typ);
     ++		bstats->index_blocks =
     ++			w->stats.idx_stats.blocks - before_blocks;
     ++		bstats->index_offset = index_start;
     ++		bstats->max_index_level = max_level;
     ++	}
     ++
     ++	/* Reinit lastKey, as the next section can start with any key. */
     ++	w->last_key.len = 0;
     ++
     ++	return 0;
      +}
      +
      +struct common_prefix_arg {
     -+  struct slice *last;
     -+  int max;
     ++	struct slice *last;
     ++	int max;
      +};
      +
     -+static void update_common(void *void_arg, void *key) {
     -+  struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
     -+  struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     -+  if (arg->last != NULL) {
     -+    int n = common_prefix_size(entry->hash, *arg->last);
     -+    if (n > arg->max) {
     -+      arg->max = n;
     -+    }
     -+  }
     -+  arg->last = &entry->hash;
     ++static void update_common(void *void_arg, void *key)
     ++{
     ++	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
     ++	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     ++	if (arg->last != NULL) {
     ++		int n = common_prefix_size(entry->hash, *arg->last);
     ++		if (n > arg->max) {
     ++			arg->max = n;
     ++		}
     ++	}
     ++	arg->last = &entry->hash;
      +}
      +
      +struct write_record_arg {
     -+  struct writer *w;
     -+  int err;
     ++	struct writer *w;
     ++	int err;
      +};
      +
     -+static void write_object_record(void *void_arg, void *key) {
     -+  struct write_record_arg *arg = (struct write_record_arg *)void_arg;
     -+  struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     -+  struct obj_record obj_rec = {
     -+      .hash_prefix = entry->hash.buf,
     -+      .hash_prefix_len = arg->w->stats.object_id_len,
     -+      .offsets = entry->offsets,
     -+      .offset_len = entry->offset_len,
     -+  };
     -+  struct record rec = {};
     -+  if (arg->err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  record_from_obj(&rec, &obj_rec);
     -+  arg->err = block_writer_add(arg->w->block_writer, rec);
     -+  if (arg->err == 0) {
     -+    goto exit;
     -+  }
     -+
     -+  arg->err = writer_flush_block(arg->w);
     -+  if (arg->err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
     -+  arg->err = block_writer_add(arg->w->block_writer, rec);
     -+  if (arg->err == 0) {
     -+    goto exit;
     -+  }
     -+  obj_rec.offset_len = 0;
     -+  arg->err = block_writer_add(arg->w->block_writer, rec);
     -+
     -+  // Should be able to write into a fresh block.
     -+  assert(arg->err == 0);
     ++static void write_object_record(void *void_arg, void *key)
     ++{
     ++	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
     ++	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     ++	struct obj_record obj_rec = {
     ++		.hash_prefix = entry->hash.buf,
     ++		.hash_prefix_len = arg->w->stats.object_id_len,
     ++		.offsets = entry->offsets,
     ++		.offset_len = entry->offset_len,
     ++	};
     ++	struct record rec = {};
     ++	if (arg->err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	record_from_obj(&rec, &obj_rec);
     ++	arg->err = block_writer_add(arg->w->block_writer, rec);
     ++	if (arg->err == 0) {
     ++		goto exit;
     ++	}
     ++
     ++	arg->err = writer_flush_block(arg->w);
     ++	if (arg->err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
     ++	arg->err = block_writer_add(arg->w->block_writer, rec);
     ++	if (arg->err == 0) {
     ++		goto exit;
     ++	}
     ++	obj_rec.offset_len = 0;
     ++	arg->err = block_writer_add(arg->w->block_writer, rec);
     ++
     ++	/* Should be able to write into a fresh block. */
     ++	assert(arg->err == 0);
      +
      +exit:;
      +}
      +
     -+static void object_record_free(void *void_arg, void *key) {
     -+  struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     -+
     -+  free(entry->offsets);
     -+  entry->offsets = NULL;
     -+  free(slice_yield(&entry->hash));
     -+  free(entry);
     -+}
     -+
     -+static int writer_dump_object_index(struct writer *w) {
     -+  struct write_record_arg closure = {.w = w};
     -+  struct common_prefix_arg common = {};
     -+  if (w->obj_index_tree != NULL) {
     -+    infix_walk(w->obj_index_tree, &update_common, &common);
     -+  }
     -+  w->stats.object_id_len = common.max + 1;
     -+
     -+  writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
     -+
     -+  if (w->obj_index_tree != NULL) {
     -+    infix_walk(w->obj_index_tree, &write_object_record, &closure);
     -+  }
     -+
     -+  if (closure.err < 0) {
     -+    return closure.err;
     -+  }
     -+  return writer_finish_section(w);
     -+}
     -+
     -+int writer_finish_public_section(struct writer *w) {
     -+  byte typ = 0;
     -+  int err = 0;
     -+
     -+  if (w->block_writer == NULL) {
     -+    return 0;
     -+  }
     -+
     -+  typ = block_writer_type(w->block_writer);
     -+  err = writer_finish_section(w);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+  if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
     -+      w->stats.ref_stats.index_blocks > 0) {
     -+    err = writer_dump_object_index(w);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+  }
     -+
     -+  if (w->obj_index_tree != NULL) {
     -+    infix_walk(w->obj_index_tree, &object_record_free, NULL);
     -+    tree_free(w->obj_index_tree);
     -+    w->obj_index_tree = NULL;
     -+  }
     -+
     -+  w->block_writer = NULL;
     -+  return 0;
     -+}
     -+
     -+int writer_close(struct writer *w) {
     -+  byte footer[68];
     -+  byte *p = footer;
     -+
     -+  writer_finish_public_section(w);
     -+
     -+  writer_write_header(w, footer);
     -+  p += 24;
     -+  put_u64(p, w->stats.ref_stats.index_offset);
     -+  p += 8;
     -+  put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
     -+  p += 8;
     -+  put_u64(p, w->stats.obj_stats.index_offset);
     -+  p += 8;
     -+
     -+  put_u64(p, w->stats.log_stats.offset);
     -+  p += 8;
     -+  put_u64(p, w->stats.log_stats.index_offset);
     -+  p += 8;
     -+
     -+  put_u32(p, crc32(0, footer, p - footer));
     -+  p += 4;
     -+  w->pending_padding = 0;
     -+
     -+  {
     -+    int n = padded_write(w, footer, sizeof(footer), 0);
     -+    if (n < 0) {
     -+      return n;
     -+    }
     -+  }
     -+
     -+  // free up memory.
     -+  block_writer_clear(&w->block_writer_data);
     -+  writer_clear_index(w);
     -+  free(slice_yield(&w->last_key));
     -+  return 0;
     -+}
     -+
     -+void writer_clear_index(struct writer *w) {
     -+  for (int i = 0; i < w->index_len; i++) {
     -+    free(slice_yield(&w->index[i].last_key));
     -+  }
     -+
     -+  free(w->index);
     -+  w->index = NULL;
     -+  w->index_len = 0;
     -+  w->index_cap = 0;
     ++static void object_record_free(void *void_arg, void *key)
     ++{
     ++	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     ++
     ++	free(entry->offsets);
     ++	entry->offsets = NULL;
     ++	free(slice_yield(&entry->hash));
     ++	free(entry);
     ++}
     ++
     ++static int writer_dump_object_index(struct writer *w)
     ++{
     ++	struct write_record_arg closure = { .w = w };
     ++	struct common_prefix_arg common = {};
     ++	if (w->obj_index_tree != NULL) {
     ++		infix_walk(w->obj_index_tree, &update_common, &common);
     ++	}
     ++	w->stats.object_id_len = common.max + 1;
     ++
     ++	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
     ++
     ++	if (w->obj_index_tree != NULL) {
     ++		infix_walk(w->obj_index_tree, &write_object_record, &closure);
     ++	}
     ++
     ++	if (closure.err < 0) {
     ++		return closure.err;
     ++	}
     ++	return writer_finish_section(w);
     ++}
     ++
     ++int writer_finish_public_section(struct writer *w)
     ++{
     ++	byte typ = 0;
     ++	int err = 0;
     ++
     ++	if (w->block_writer == NULL) {
     ++		return 0;
     ++	}
     ++
     ++	typ = block_writer_type(w->block_writer);
     ++	err = writer_finish_section(w);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
     ++	    w->stats.ref_stats.index_blocks > 0) {
     ++		err = writer_dump_object_index(w);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	if (w->obj_index_tree != NULL) {
     ++		infix_walk(w->obj_index_tree, &object_record_free, NULL);
     ++		tree_free(w->obj_index_tree);
     ++		w->obj_index_tree = NULL;
     ++	}
     ++
     ++	w->block_writer = NULL;
     ++	return 0;
     ++}
     ++
     ++int writer_close(struct writer *w)
     ++{
     ++	byte footer[68];
     ++	byte *p = footer;
     ++
     ++	writer_finish_public_section(w);
     ++
     ++	writer_write_header(w, footer);
     ++	p += 24;
     ++	put_u64(p, w->stats.ref_stats.index_offset);
     ++	p += 8;
     ++	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
     ++	p += 8;
     ++	put_u64(p, w->stats.obj_stats.index_offset);
     ++	p += 8;
     ++
     ++	put_u64(p, w->stats.log_stats.offset);
     ++	p += 8;
     ++	put_u64(p, w->stats.log_stats.index_offset);
     ++	p += 8;
     ++
     ++	put_u32(p, crc32(0, footer, p - footer));
     ++	p += 4;
     ++	w->pending_padding = 0;
     ++
     ++	{
     ++		int n = padded_write(w, footer, sizeof(footer), 0);
     ++		if (n < 0) {
     ++			return n;
     ++		}
     ++	}
     ++
     ++	/* free up memory. */
     ++	block_writer_clear(&w->block_writer_data);
     ++	writer_clear_index(w);
     ++	free(slice_yield(&w->last_key));
     ++	return 0;
     ++}
     ++
     ++void writer_clear_index(struct writer *w)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < w->index_len; i++) {
     ++		free(slice_yield(&w->index[i].last_key));
     ++	}
     ++
     ++	free(w->index);
     ++	w->index = NULL;
     ++	w->index_len = 0;
     ++	w->index_cap = 0;
      +}
      +
      +const int debug = 0;
      +
     -+static int writer_flush_nonempty_block(struct writer *w) {
     -+  byte typ = block_writer_type(w->block_writer);
     -+  struct block_stats *bstats = writer_block_stats(w, typ);
     -+  uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
     -+  int raw_bytes = block_writer_finish(w->block_writer);
     -+  int padding = 0;
     -+  int err = 0;
     -+  if (raw_bytes < 0) {
     -+    return raw_bytes;
     -+  }
     -+
     -+  if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
     -+    padding = w->opts.block_size - raw_bytes;
     -+  }
     -+
     -+  if (block_typ_off > 0) {
     -+    bstats->offset = block_typ_off;
     -+  }
     -+
     -+  bstats->entries += w->block_writer->entries;
     -+  bstats->restarts += w->block_writer->restart_len;
     -+  bstats->blocks++;
     -+  w->stats.blocks++;
     -+
     -+  if (debug) {
     -+    fprintf(stderr, "block %c off %ld sz %d (%d)\n", typ, w->next, raw_bytes,
     -+            get_u24(w->block + w->block_writer->header_off + 1));
     -+  }
     -+
     -+  if (w->next == 0) {
     -+    writer_write_header(w, w->block);
     -+  }
     -+
     -+  err = padded_write(w, w->block, raw_bytes, padding);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  if (w->index_cap == w->index_len) {
     -+    w->index_cap = 2 * w->index_cap + 1;
     -+    w->index = realloc(w->index, sizeof(struct index_record) * w->index_cap);
     -+  }
     -+
     -+  {
     -+    struct index_record ir = {
     -+        .offset = w->next,
     -+    };
     -+    slice_copy(&ir.last_key, w->block_writer->last_key);
     -+    w->index[w->index_len] = ir;
     -+  }
     -+
     -+  w->index_len++;
     -+  w->next += padding + raw_bytes;
     -+  block_writer_reset(&w->block_writer_data);
     -+  w->block_writer = NULL;
     -+  return 0;
     -+}
     -+
     -+int writer_flush_block(struct writer *w) {
     -+  if (w->block_writer == NULL) {
     -+    return 0;
     -+  }
     -+  if (w->block_writer->entries == 0) {
     -+    return 0;
     -+  }
     -+  return writer_flush_nonempty_block(w);
     -+}
     -+
     -+struct stats *writer_stats(struct writer *w) {
     -+  return &w->stats;
     ++static int writer_flush_nonempty_block(struct writer *w)
     ++{
     ++	byte typ = block_writer_type(w->block_writer);
     ++	struct block_stats *bstats = writer_block_stats(w, typ);
     ++	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
     ++	int raw_bytes = block_writer_finish(w->block_writer);
     ++	int padding = 0;
     ++	int err = 0;
     ++	if (raw_bytes < 0) {
     ++		return raw_bytes;
     ++	}
     ++
     ++	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
     ++		padding = w->opts.block_size - raw_bytes;
     ++	}
     ++
     ++	if (block_typ_off > 0) {
     ++		bstats->offset = block_typ_off;
     ++	}
     ++
     ++	bstats->entries += w->block_writer->entries;
     ++	bstats->restarts += w->block_writer->restart_len;
     ++	bstats->blocks++;
     ++	w->stats.blocks++;
     ++
     ++	if (debug) {
     ++		fprintf(stderr, "block %c off %" PRIuMAX " sz %d (%d)\n", typ,
     ++			w->next, raw_bytes,
     ++			get_u24(w->block + w->block_writer->header_off + 1));
     ++	}
     ++
     ++	if (w->next == 0) {
     ++		writer_write_header(w, w->block);
     ++	}
     ++
     ++	err = padded_write(w, w->block, raw_bytes, padding);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	if (w->index_cap == w->index_len) {
     ++		w->index_cap = 2 * w->index_cap + 1;
     ++		w->index = realloc(w->index,
     ++				   sizeof(struct index_record) * w->index_cap);
     ++	}
     ++
     ++	{
     ++		struct index_record ir = {
     ++			.offset = w->next,
     ++		};
     ++		slice_copy(&ir.last_key, w->block_writer->last_key);
     ++		w->index[w->index_len] = ir;
     ++	}
     ++
     ++	w->index_len++;
     ++	w->next += padding + raw_bytes;
     ++	block_writer_reset(&w->block_writer_data);
     ++	w->block_writer = NULL;
     ++	return 0;
     ++}
     ++
     ++int writer_flush_block(struct writer *w)
     ++{
     ++	if (w->block_writer == NULL) {
     ++		return 0;
     ++	}
     ++	if (w->block_writer->entries == 0) {
     ++		return 0;
     ++	}
     ++	return writer_flush_nonempty_block(w);
     ++}
     ++
     ++struct stats *writer_stats(struct writer *w)
     ++{
     ++	return &w->stats;
      +}
      
       diff --git a/reftable/writer.h b/reftable/writer.h
     @@ -7896,27 +8161,27 @@
      +#include "tree.h"
      +
      +struct writer {
     -+  int (*write)(void *, byte *, int);
     -+  void *write_arg;
     -+  int pending_padding;
     -+  int hash_size;
     -+  struct slice last_key;
     -+
     -+  uint64_t next;
     -+  uint64_t min_update_index, max_update_index;
     -+  struct write_options opts;
     -+
     -+  byte *block;
     -+  struct block_writer *block_writer;
     -+  struct block_writer block_writer_data;
     -+  struct index_record *index;
     -+  int index_len;
     -+  int index_cap;
     -+
     -+  // tree for use with tsearch
     -+  struct tree_node *obj_index_tree;
     -+
     -+  struct stats stats;
     ++	int (*write)(void *, byte *, int);
     ++	void *write_arg;
     ++	int pending_padding;
     ++	int hash_size;
     ++	struct slice last_key;
     ++
     ++	uint64_t next;
     ++	uint64_t min_update_index, max_update_index;
     ++	struct write_options opts;
     ++
     ++	byte *block;
     ++	struct block_writer *block_writer;
     ++	struct block_writer block_writer_data;
     ++	struct index_record *index;
     ++	int index_len;
     ++	int index_cap;
     ++
     ++	/* tree for use with tsearch */
     ++	struct tree_node *obj_index_tree;
     ++
     ++	struct stats stats;
      +};
      +
      +int writer_flush_block(struct writer *w);
 5:  a615afa0a8 ! 5:  721201269d Reftable support for git-core
     @@ -2,33 +2,39 @@
      
          Reftable support for git-core
      
     -    $ ~/vc/git/git init
     -    warning: templates not found in /usr/local/google/home/hanwen/share/git-core/templates
     -    Initialized empty Git repository in /tmp/qz/.git/
     -    $ echo q > a
     -    $ ~/vc/git/git add a
     -    $ ~/vc/git/git commit -mx
     -    fatal: not a git repository (or any of the parent directories): .git
     -    [master (root-commit) 373d969] x
     -     1 file changed, 1 insertion(+)
     -     create mode 100644 a
     -    $ ~/vc/git/git show-ref
     -    373d96972fca9b63595740bba3898a762778ba20 HEAD
     -    373d96972fca9b63595740bba3898a762778ba20 refs/heads/master
     -    $ ls -l .git/reftable/
     -    total 12
     -    -rw------- 1 hanwen primarygroup  126 Jan 23 20:08 000000000001-000000000001.ref
     -    -rw------- 1 hanwen primarygroup 4277 Jan 23 20:08 000000000002-000000000002.ref
     -    $ go run ~/vc/reftable/cmd/dump.go  -table /tmp/qz/.git/reftable/000000000002-000000000002.ref
     -    ** DEBUG **
     -    name /tmp/qz/.git/reftable/000000000002-000000000002.ref, sz 4209: 'r' reftable.readerOffsets{Present:true, Offset:0x0, IndexOffset:0x0}, 'o' reftable.readerOffsets{Present:false, Offset:0x0, IndexOffset:0x0} 'g' reftable.readerOffsets{Present:true, Offset:0x1000, IndexOffset:0x0}
     -    ** REFS **
     -    reftable.RefRecord{RefName:"refs/heads/master", UpdateIndex:0x2, Value:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, TargetValue:[]uint8(nil), Target:""}
     -    ** LOGS **
     -    reftable.LogRecord{RefName:"HEAD", UpdateIndex:0x2, New:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, Old:[]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Name:"Han-Wen Nienhuys", Email:"hanwen@google.com", Time:0x5e29ef27, TZOffset:100, Message:"commit (initial): x\n"}
     +    For background, see the previous commit introducing the library.
      
          TODO:
     -     * resolve the design problem with reflog expiry.
     +
     +     * Resolve the design problem with reflog expiry.
     +     * Resolve spots marked with XXX
     +
     +    Example use:
     +
     +      $ ~/vc/git/git init
     +      warning: templates not found in /usr/local/google/home/hanwen/share/git-core/templates
     +      Initialized empty Git repository in /tmp/qz/.git/
     +      $ echo q > a
     +      $ ~/vc/git/git add a
     +      $ ~/vc/git/git commit -mx
     +      fatal: not a git repository (or any of the parent directories): .git
     +      [master (root-commit) 373d969] x
     +       1 file changed, 1 insertion(+)
     +       create mode 100644 a
     +      $ ~/vc/git/git show-ref
     +      373d96972fca9b63595740bba3898a762778ba20 HEAD
     +      373d96972fca9b63595740bba3898a762778ba20 refs/heads/master
     +      $ ls -l .git/reftable/
     +      total 12
     +      -rw------- 1 hanwen primarygroup  126 Jan 23 20:08 000000000001-000000000001.ref
     +      -rw------- 1 hanwen primarygroup 4277 Jan 23 20:08 000000000002-000000000002.ref
     +      $ go run ~/vc/reftable/cmd/dump.go  -table /tmp/qz/.git/reftable/000000000002-000000000002.ref
     +      ** DEBUG **
     +      name /tmp/qz/.git/reftable/000000000002-000000000002.ref, sz 4209: 'r' reftable.readerOffsets{Present:true, Offset:0x0, IndexOffset:0x0}, 'o' reftable.readerOffsets{Present:false, Offset:0x0, IndexOffset:0x0} 'g' reftable.readerOffsets{Present:true, Offset:0x1000, IndexOffset:0x0}
     +      ** REFS **
     +      reftable.RefRecord{RefName:"refs/heads/master", UpdateIndex:0x2, Value:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, TargetValue:[]uint8(nil), Target:""}
     +      ** LOGS **
     +      reftable.LogRecord{RefName:"HEAD", UpdateIndex:0x2, New:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, Old:[]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Name:"Han-Wen Nienhuys", Email:"hanwen@google.com", Time:0x5e29ef27, TZOffset:100, Message:"commit (initial): x\n"}
      
          Change-Id: I225ee6317b7911edf9aa95f43299f6c7c4511914
          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
     @@ -120,8 +126,8 @@ $^
      -	struct ref_storage_be *be = find_ref_storage_backend(be_name);
      +	struct strbuf refs_path = STRBUF_INIT;
      +
     -+        // XXX this should probably come from a git config setting and not
     -+        // default to reftable.
     ++        /* XXX this should probably come from a git config setting and not
     ++           default to reftable. */
      +	const char *be_name = "reftable";
      +	struct ref_storage_be *be;
       	struct ref_store *refs;
     @@ -146,7 +152,7 @@ $^
       				     each_reflog_ent_fn fn,
       				     void *cb_data);
      +
     -+// XXX which ordering are these? Newest or oldest first?
     ++/* XXX which ordering are these? Newest or oldest first? */
       int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
       int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
       
     @@ -190,40 +196,45 @@ $^
      +	struct stack *stack;
      +};
      +
     -+static void clear_log_record(struct log_record* log) {
     -+        log->old_hash = NULL;
     -+        log->new_hash = NULL;
     -+        log->message = NULL;
     -+        log->ref_name = NULL;
     -+        log_record_clear(log);
     -+}
     -+
     -+static void fill_log_record(struct log_record* log) {
     -+        const char* info = git_committer_info(0);
     -+        struct ident_split split = {};
     -+        int result = split_ident_line(&split, info, strlen(info));
     -+        int sign = 1;
     -+        assert(0==result);
     -+
     -+        log_record_clear(log);
     -+        log->name = xstrndup(split.name_begin, split.name_end - split.name_begin);
     -+        log->email = xstrndup(split.mail_begin, split.mail_end - split.mail_begin);
     -+        log->time = atol(split.date_begin);
     -+        if (*split.tz_begin == '-') {
     -+                sign = -1;
     -+                split.tz_begin ++;
     -+        }
     -+        if (*split.tz_begin == '+') {
     -+                sign = 1;
     -+                split.tz_begin ++;
     -+        }
     -+
     -+        log->tz_offset = sign * atoi(split.tz_begin);
     ++static void clear_log_record(struct log_record *log)
     ++{
     ++	log->old_hash = NULL;
     ++	log->new_hash = NULL;
     ++	log->message = NULL;
     ++	log->ref_name = NULL;
     ++	log_record_clear(log);
     ++}
     ++
     ++static void fill_log_record(struct log_record *log)
     ++{
     ++	const char *info = git_committer_info(0);
     ++	struct ident_split split = {};
     ++	int result = split_ident_line(&split, info, strlen(info));
     ++	int sign = 1;
     ++	assert(0 == result);
     ++
     ++	log_record_clear(log);
     ++	log->name =
     ++		xstrndup(split.name_begin, split.name_end - split.name_begin);
     ++	log->email =
     ++		xstrndup(split.mail_begin, split.mail_end - split.mail_begin);
     ++	log->time = atol(split.date_begin);
     ++	if (*split.tz_begin == '-') {
     ++		sign = -1;
     ++		split.tz_begin++;
     ++	}
     ++	if (*split.tz_begin == '+') {
     ++		sign = 1;
     ++		split.tz_begin++;
     ++	}
     ++
     ++	log->tz_offset = sign * atoi(split.tz_begin);
      +}
      +
      +static struct ref_store *reftable_ref_store_create(const char *path,
     -+                                                   unsigned int store_flags) {
     -+  	struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
     ++						   unsigned int store_flags)
     ++{
     ++	struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
      +	struct ref_store *ref_store = (struct ref_store *)refs;
      +	struct write_options cfg = {
      +		.block_size = 4096,
     @@ -240,17 +251,19 @@ $^
      +	refs->reftable_dir = xstrdup(sb.buf);
      +	strbuf_release(&sb);
      +
     -+	refs->err = new_stack(&refs->stack, refs->reftable_dir, refs->table_list_file, cfg);
     ++	refs->err = new_stack(&refs->stack, refs->reftable_dir,
     ++			      refs->table_list_file, cfg);
      +
      +	return ref_store;
      +}
      +
      +static int reftable_init_db(struct ref_store *ref_store, struct strbuf *err)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	FILE *f = fopen(refs->table_list_file, "a");
      +	if (f == NULL) {
     -+	  return -1;
     ++		return -1;
      +	}
      +	fclose(f);
      +
     @@ -258,56 +271,58 @@ $^
      +	return 0;
      +}
      +
     -+
      +struct reftable_iterator {
      +	struct ref_iterator base;
      +	struct iterator iter;
      +	struct ref_record ref;
      +	struct object_id oid;
     -+        struct ref_store *ref_store;
     ++	struct ref_store *ref_store;
      +	char *prefix;
      +};
      +
      +static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
      +{
     -+        while (1) {
     -+                struct reftable_iterator *ri = (struct reftable_iterator*) ref_iterator;
     -+                int err = iterator_next_ref(ri->iter, &ri->ref);
     -+                if (err > 0) {
     -+                        return ITER_DONE;
     -+                }
     -+                if (err < 0) {
     -+                        return ITER_ERROR;
     -+                }
     -+
     -+                ri->base.refname = ri->ref.ref_name;
     -+                if (ri->prefix != NULL && 0 != strncmp(ri->prefix, ri->ref.ref_name, strlen(ri->prefix))) {
     -+                        return ITER_DONE;
     -+                }
     -+
     -+                ri->base.flags = 0;
     -+                if (ri->ref.value != NULL) {
     -+                        memcpy(ri->oid.hash, ri->ref.value, GIT_SHA1_RAWSZ);
     -+                } else if (ri->ref.target != NULL) {
     -+                        int out_flags = 0;
     -+                        const char *resolved =
     -+                                refs_resolve_ref_unsafe(ri->ref_store, ri->ref.ref_name, RESOLVE_REF_READING,
     -+                                                        &ri->oid, &out_flags);
     -+                        ri->base.flags = out_flags;
     -+                        if (resolved == NULL && !(ri->base.flags & DO_FOR_EACH_INCLUDE_BROKEN)
     -+                            && (ri->base.flags & REF_ISBROKEN)) {
     -+                                continue;
     -+                        }
     -+                }
     -+                ri->base.oid  = &ri->oid;
     -+                return ITER_OK;
     -+        }
     ++	while (1) {
     ++		struct reftable_iterator *ri =
     ++			(struct reftable_iterator *)ref_iterator;
     ++		int err = iterator_next_ref(ri->iter, &ri->ref);
     ++		if (err > 0) {
     ++			return ITER_DONE;
     ++		}
     ++		if (err < 0) {
     ++			return ITER_ERROR;
     ++		}
     ++
     ++		ri->base.refname = ri->ref.ref_name;
     ++		if (ri->prefix != NULL &&
     ++		    strncmp(ri->prefix, ri->ref.ref_name, strlen(ri->prefix))) {
     ++			return ITER_DONE;
     ++		}
     ++
     ++		ri->base.flags = 0;
     ++		if (ri->ref.value != NULL) {
     ++			memcpy(ri->oid.hash, ri->ref.value, GIT_SHA1_RAWSZ);
     ++		} else if (ri->ref.target != NULL) {
     ++			int out_flags = 0;
     ++			const char *resolved = refs_resolve_ref_unsafe(
     ++				ri->ref_store, ri->ref.ref_name,
     ++				RESOLVE_REF_READING, &ri->oid, &out_flags);
     ++			ri->base.flags = out_flags;
     ++			if (resolved == NULL &&
     ++			    !(ri->base.flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
     ++			    (ri->base.flags & REF_ISBROKEN)) {
     ++				continue;
     ++			}
     ++		}
     ++		ri->base.oid = &ri->oid;
     ++		return ITER_OK;
     ++	}
      +}
      +
      +static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,
      +				      struct object_id *peeled)
      +{
     -+	struct reftable_iterator *ri = (struct reftable_iterator*) ref_iterator;
     ++	struct reftable_iterator *ri = (struct reftable_iterator *)ref_iterator;
      +	if (ri->ref.target_value != NULL) {
      +		memcpy(peeled->hash, ri->ref.target_value, GIT_SHA1_RAWSZ);
      +		return 0;
     @@ -318,40 +333,40 @@ $^
      +
      +static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator)
      +{
     -+	struct reftable_iterator *ri = (struct reftable_iterator*) ref_iterator;
     ++	struct reftable_iterator *ri = (struct reftable_iterator *)ref_iterator;
      +	ref_record_clear(&ri->ref);
      +	iterator_destroy(&ri->iter);
      +	return 0;
      +}
      +
      +static struct ref_iterator_vtable reftable_ref_iterator_vtable = {
     -+	reftable_ref_iterator_advance,
     -+	reftable_ref_iterator_peel,
     ++	reftable_ref_iterator_advance, reftable_ref_iterator_peel,
      +	reftable_ref_iterator_abort
      +};
      +
     -+static struct ref_iterator *reftable_ref_iterator_begin(
     -+		struct ref_store *ref_store,
     -+		const char *prefix, unsigned int flags)
     ++static struct ref_iterator *
     ++reftable_ref_iterator_begin(struct ref_store *ref_store, const char *prefix,
     ++			    unsigned int flags)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct reftable_iterator *ri = xcalloc(1, sizeof(*ri));
      +	struct merged_table *mt = NULL;
      +	int err = 0;
      +	if (refs->err) {
     -+		// XXX ?
     ++		/* how to propagate errors? */
      +		return NULL;
      +	}
      +
      +	mt = stack_merged_table(refs->stack);
      +
     -+	// XXX something with flags?
     ++	/* XXX something with flags? */
      +	err = merged_table_seek_ref(mt, &ri->iter, prefix);
     -+	// XXX what to do with err?
     ++	/* XXX what to do with err? */
      +	assert(err == 0);
      +	base_ref_iterator_init(&ri->base, &reftable_ref_iterator_vtable, 1);
      +	ri->base.oid = &ri->oid;
     -+        ri->ref_store = ref_store;
     ++	ri->ref_store = ref_store;
      +	return &ri->base;
      +}
      +
     @@ -363,92 +378,100 @@ $^
      +}
      +
      +static int reftable_transaction_abort(struct ref_store *ref_store,
     -+				    struct ref_transaction *transaction,
     -+				    struct strbuf *err)
     ++				      struct ref_transaction *transaction,
     ++				      struct strbuf *err)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	(void)refs;
      +	return 0;
      +}
      +
     -+
     -+static int ref_update_cmp(const void *a, const void *b) {
     -+	return strcmp(((struct ref_update*)a)->refname, ((struct ref_update*)b)->refname);
     ++static int ref_update_cmp(const void *a, const void *b)
     ++{
     ++	return strcmp(((struct ref_update *)a)->refname,
     ++		      ((struct ref_update *)b)->refname);
      +}
      +
     -+static int reftable_check_old_oid(struct ref_store *refs, const char *refname, struct object_id *want_oid) {
     -+        struct object_id out_oid = {};
     -+        int out_flags = 0;
     -+        const char *resolved =
     -+                refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING,
     -+                                        &out_oid, &out_flags);
     -+        if (is_null_oid(want_oid) != (resolved == NULL)) {
     -+                return LOCK_ERROR;
     -+        }
     ++static int reftable_check_old_oid(struct ref_store *refs, const char *refname,
     ++				  struct object_id *want_oid)
     ++{
     ++	struct object_id out_oid = {};
     ++	int out_flags = 0;
     ++	const char *resolved = refs_resolve_ref_unsafe(
     ++		refs, refname, RESOLVE_REF_READING, &out_oid, &out_flags);
     ++	if (is_null_oid(want_oid) != (resolved == NULL)) {
     ++		return LOCK_ERROR;
     ++	}
      +
     -+        if (resolved != NULL && !oideq(&out_oid, want_oid)) {
     -+                return LOCK_ERROR;
     -+        }
     ++	if (resolved != NULL && !oideq(&out_oid, want_oid)) {
     ++		return LOCK_ERROR;
     ++	}
      +
     -+        return 0;
     ++	return 0;
      +}
      +
     -+static int write_transaction_table(struct writer *writer, void *arg) {
     ++static int write_transaction_table(struct writer *writer, void *arg)
     ++{
      +	struct ref_transaction *transaction = (struct ref_transaction *)arg;
     -+	struct reftable_ref_store *refs
     -+		= (struct reftable_ref_store*)transaction->ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)transaction->ref_store;
      +	uint64_t ts = stack_next_update_index(refs->stack);
      +	int err = 0;
     -+	// XXX - are we allowed to mutate the input data?
     -+	qsort(transaction->updates, transaction->nr, sizeof(struct ref_update*),
     -+	      ref_update_cmp);
     ++	/* XXX - are we allowed to mutate the input data? */
     ++	qsort(transaction->updates, transaction->nr,
     ++	      sizeof(struct ref_update *), ref_update_cmp);
      +	writer_set_limits(writer, ts, ts);
      +
     -+	for (int i = 0; i <  transaction->nr; i++) {
     -+		struct ref_update * u = transaction->updates[i];
     ++	for (int i = 0; i < transaction->nr; i++) {
     ++		struct ref_update *u = transaction->updates[i];
      +		if (u->flags & REF_HAVE_OLD) {
     -+			err = reftable_check_old_oid(transaction->ref_store, u->refname, &u->old_oid);
     ++			err = reftable_check_old_oid(transaction->ref_store,
     ++						     u->refname, &u->old_oid);
      +			if (err < 0) {
      +				goto exit;
      +			}
      +		}
      +	}
      +
     -+	for (int i = 0; i <  transaction->nr; i++) {
     -+		struct ref_update * u = transaction->updates[i];
     ++	for (int i = 0; i < transaction->nr; i++) {
     ++		struct ref_update *u = transaction->updates[i];
      +		if (u->flags & REF_HAVE_NEW) {
     -+                        struct object_id out_oid = {};
     -+                        int out_flags = 0;
     -+                        // XXX who owns the memory here?
     -+                        const char *resolved
     -+                                = refs_resolve_ref_unsafe(transaction->ref_store, u->refname, 0, &out_oid, &out_flags);
     -+                        struct ref_record ref = {};
     -+                        ref.ref_name = (char*)(resolved ? resolved : u->refname);
     -+                        ref.value = u->new_oid.hash;
     -+                        ref.update_index = ts;
     -+                        err = writer_add_ref(writer, &ref);
     -+                        if (err < 0) {
     -+                                goto exit;
     -+                        }
     ++			struct object_id out_oid = {};
     ++			int out_flags = 0;
     ++			/* XXX who owns the memory here? */
     ++			const char *resolved = refs_resolve_ref_unsafe(
     ++				transaction->ref_store, u->refname, 0, &out_oid,
     ++				&out_flags);
     ++			struct ref_record ref = {};
     ++			ref.ref_name =
     ++				(char *)(resolved ? resolved : u->refname);
     ++			ref.value = u->new_oid.hash;
     ++			ref.update_index = ts;
     ++			err = writer_add_ref(writer, &ref);
     ++			if (err < 0) {
     ++				goto exit;
     ++			}
      +		}
      +	}
      +
     ++	for (int i = 0; i < transaction->nr; i++) {
     ++		struct ref_update *u = transaction->updates[i];
     ++		struct log_record log = {};
     ++		fill_log_record(&log);
      +
     -+	for (int i = 0; i <  transaction->nr; i++) {
     -+		struct ref_update * u = transaction->updates[i];
     -+                struct log_record log = {};
     -+                fill_log_record(&log);
     ++		log.ref_name = (char *)u->refname;
     ++		log.old_hash = u->old_oid.hash;
     ++		log.new_hash = u->new_oid.hash;
     ++		log.update_index = ts;
     ++		log.message = u->msg;
      +
     -+                log.ref_name = (char*)u->refname;
     -+                log.old_hash = u->old_oid.hash;
     -+                log.new_hash = u->new_oid.hash;
     -+                log.update_index = ts;
     -+                log.message = u->msg;
     -+
     -+                err = writer_add_log(writer, &log);
     -+                clear_log_record(&log);
     -+                if (err < 0) { return err; }
     -+        }
     ++		err = writer_add_log(writer, &log);
     ++		clear_log_record(&log);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
      +exit:
      +	return err;
      +}
     @@ -457,89 +480,91 @@ $^
      +				       struct ref_transaction *transaction,
      +				       struct strbuf *errmsg)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     -+	int err = stack_add(refs->stack,
     -+		  &write_transaction_table,
     -+		  transaction);
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	int err = stack_add(refs->stack, &write_transaction_table, transaction);
      +	if (err < 0) {
     -+		strbuf_addf(errmsg, "reftable: transaction failure %s", error_str(err));
     ++		strbuf_addf(errmsg, "reftable: transaction failure %s",
     ++			    error_str(err));
      +		return -1;
     -+ 	}
     ++	}
      +
      +	return 0;
      +}
      +
     -+
      +static int reftable_transaction_finish(struct ref_store *ref_store,
     -+				     struct ref_transaction *transaction,
     -+				     struct strbuf *err)
     ++				       struct ref_transaction *transaction,
     ++				       struct strbuf *err)
      +{
     -+        return reftable_transaction_commit(ref_store, transaction, err);
     ++	return reftable_transaction_commit(ref_store, transaction, err);
      +}
      +
     -+
     -+
      +struct write_delete_refs_arg {
      +	struct stack *stack;
      +	struct string_list *refnames;
      +	const char *logmsg;
     -+        unsigned int flags;
     ++	unsigned int flags;
      +};
      +
     -+static int write_delete_refs_table(struct writer *writer, void *argv) {
     -+	struct write_delete_refs_arg *arg = (struct write_delete_refs_arg*)argv;
     ++static int write_delete_refs_table(struct writer *writer, void *argv)
     ++{
     ++	struct write_delete_refs_arg *arg =
     ++		(struct write_delete_refs_arg *)argv;
      +	uint64_t ts = stack_next_update_index(arg->stack);
      +	int err = 0;
      +
     -+        writer_set_limits(writer, ts, ts);
     -+        for (int i = 0; i < arg->refnames->nr; i++) {
     -+                struct ref_record ref = {
     -+                        .ref_name = (char*) arg->refnames->items[i].string,
     -+                        .update_index = ts,
     -+                };
     -+                err = writer_add_ref(writer, &ref);
     -+                if (err < 0) {
     -+                        return err;
     -+                }
     -+        }
     -+
     -+        for (int i = 0; i < arg->refnames->nr; i++) {
     -+                struct log_record log = {};
     -+                fill_log_record(&log);
     -+                log.message = xstrdup(arg->logmsg);
     -+                log.new_hash = NULL;
     -+
     -+                // XXX should lookup old oid.
     -+                log.old_hash = NULL;
     -+                log.update_index = ts;
     -+                log.ref_name = (char*) arg->refnames->items[i].string;
     -+
     -+                err = writer_add_log(writer, &log);
     -+                clear_log_record(&log);
     -+                if (err < 0) {
     -+                        return err;
     -+                }
     -+        }
     ++	writer_set_limits(writer, ts, ts);
     ++	for (int i = 0; i < arg->refnames->nr; i++) {
     ++		struct ref_record ref = {
     ++			.ref_name = (char *)arg->refnames->items[i].string,
     ++			.update_index = ts,
     ++		};
     ++		err = writer_add_ref(writer, &ref);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	for (int i = 0; i < arg->refnames->nr; i++) {
     ++		struct log_record log = {};
     ++		fill_log_record(&log);
     ++		log.message = xstrdup(arg->logmsg);
     ++		log.new_hash = NULL;
     ++
     ++		/* XXX should lookup old oid. */
     ++		log.old_hash = NULL;
     ++		log.update_index = ts;
     ++		log.ref_name = (char *)arg->refnames->items[i].string;
     ++
     ++		err = writer_add_log(writer, &log);
     ++		clear_log_record(&log);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
      +	return 0;
      +}
      +
      +static int reftable_delete_refs(struct ref_store *ref_store, const char *msg,
     -+                                struct string_list *refnames, unsigned int flags)
     ++				struct string_list *refnames,
     ++				unsigned int flags)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*)ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct write_delete_refs_arg arg = {
      +		.stack = refs->stack,
      +		.refnames = refnames,
      +		.logmsg = msg,
     -+                .flags = flags,
     ++		.flags = flags,
      +	};
      +	return stack_add(refs->stack, &write_delete_refs_table, &arg);
      +}
      +
      +static int reftable_pack_refs(struct ref_store *ref_store, unsigned int flags)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*)ref_store;
     -+        // XXX reflog expiry.
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	/* XXX reflog expiry. */
      +	return stack_compact_all(refs->stack, NULL);
      +}
      +
     @@ -550,14 +575,16 @@ $^
      +	const char *logmsg;
      +};
      +
     -+static int write_create_symref_table(struct writer *writer, void *arg) {
     -+	struct write_create_symref_arg *create = (struct write_create_symref_arg*)arg;
     ++static int write_create_symref_table(struct writer *writer, void *arg)
     ++{
     ++	struct write_create_symref_arg *create =
     ++		(struct write_create_symref_arg *)arg;
      +	uint64_t ts = stack_next_update_index(create->stack);
      +	int err = 0;
      +
      +	struct ref_record ref = {
     -+		.ref_name = (char*) create->refname,
     -+		.target = (char*) create->target,
     ++		.ref_name = (char *)create->refname,
     ++		.target = (char *)create->target,
      +		.update_index = ts,
      +	};
      +	writer_set_limits(writer, ts, ts);
     @@ -566,7 +593,7 @@ $^
      +		return err;
      +	}
      +
     -+	// XXX reflog?
     ++	/* XXX reflog? */
      +
      +	return 0;
      +}
     @@ -575,17 +602,15 @@ $^
      +				  const char *refname, const char *target,
      +				  const char *logmsg)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*)ref_store;
     -+	struct write_create_symref_arg arg = {
     -+		.stack = refs->stack,
     -+		.refname = refname,
     -+		.target = target,
     -+		.logmsg = logmsg
     -+	};
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	struct write_create_symref_arg arg = { .stack = refs->stack,
     ++					       .refname = refname,
     ++					       .target = target,
     ++					       .logmsg = logmsg };
      +	return stack_add(refs->stack, &write_create_symref_table, &arg);
      +}
      +
     -+
      +struct write_rename_arg {
      +	struct stack *stack;
      +	const char *oldname;
     @@ -593,8 +618,9 @@ $^
      +	const char *logmsg;
      +};
      +
     -+static int write_rename_table(struct writer *writer, void *argv) {
     -+	struct write_rename_arg *arg = (struct write_rename_arg*)argv;
     ++static int write_rename_table(struct writer *writer, void *argv)
     ++{
     ++	struct write_rename_arg *arg = (struct write_rename_arg *)argv;
      +	uint64_t ts = stack_next_update_index(arg->stack);
      +	struct ref_record ref = {};
      +	int err = stack_read_ref(arg->stack, arg->oldname, &ref);
     @@ -602,15 +628,15 @@ $^
      +		goto exit;
      +	}
      +
     -+	// XXX should check that dest doesn't exist?
     ++	/* XXX should check that dest doesn't exist? */
      +	free(ref.ref_name);
      +	ref.ref_name = strdup(arg->newname);
      +	writer_set_limits(writer, ts, ts);
      +	ref.update_index = ts;
      +
      +	{
     -+		struct ref_record todo[2]  = {};
     -+		todo[0].ref_name =  (char *) arg->oldname;
     ++		struct ref_record todo[2] = {};
     ++		todo[0].ref_name = (char *)arg->oldname;
      +		todo[0].update_index = ts;
      +		todo[1] = ref;
      +		todo[1].update_index = ts;
     @@ -621,47 +647,47 @@ $^
      +		}
      +	}
      +
     -+        if (ref.value != NULL) {
     -+                struct log_record todo[2] = {};
     -+                fill_log_record(&todo[0]);
     -+                fill_log_record(&todo[1]);
     ++	if (ref.value != NULL) {
     ++		struct log_record todo[2] = {};
     ++		fill_log_record(&todo[0]);
     ++		fill_log_record(&todo[1]);
      +
     -+                todo[0].ref_name = (char *) arg->oldname;
     -+                todo[0].update_index = ts;
     -+                todo[0].message = (char*)arg->logmsg;
     -+                todo[0].old_hash = ref.value;
     -+                todo[0].new_hash = NULL;
     ++		todo[0].ref_name = (char *)arg->oldname;
     ++		todo[0].update_index = ts;
     ++		todo[0].message = (char *)arg->logmsg;
     ++		todo[0].old_hash = ref.value;
     ++		todo[0].new_hash = NULL;
      +
     -+                todo[1].ref_name = (char *) arg->newname;
     -+                todo[1].update_index = ts;
     -+                todo[1].old_hash = NULL;
     -+                todo[1].new_hash = ref.value;
     -+                todo[1].message = (char*) arg->logmsg;
     ++		todo[1].ref_name = (char *)arg->newname;
     ++		todo[1].update_index = ts;
     ++		todo[1].old_hash = NULL;
     ++		todo[1].new_hash = ref.value;
     ++		todo[1].message = (char *)arg->logmsg;
      +
      +		err = writer_add_logs(writer, todo, 2);
      +
     -+                clear_log_record(&todo[0]);
     -+                clear_log_record(&todo[1]);
     ++		clear_log_record(&todo[0]);
     ++		clear_log_record(&todo[1]);
      +
      +		if (err < 0) {
      +			goto exit;
      +		}
      +
     -+        } else {
     -+                // symrefs?
     -+        }
     ++	} else {
     ++		/* XXX symrefs? */
     ++	}
      +
      +exit:
      +	ref_record_clear(&ref);
      +	return err;
      +}
      +
     -+
      +static int reftable_rename_ref(struct ref_store *ref_store,
     -+			    const char *oldrefname, const char *newrefname,
     -+			    const char *logmsg)
     ++			       const char *oldrefname, const char *newrefname,
     ++			       const char *logmsg)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct write_rename_arg arg = {
      +		.stack = refs->stack,
      +		.oldname = oldrefname,
     @@ -672,237 +698,249 @@ $^
      +}
      +
      +static int reftable_copy_ref(struct ref_store *ref_store,
     -+			   const char *oldrefname, const char *newrefname,
     -+			   const char *logmsg)
     ++			     const char *oldrefname, const char *newrefname,
     ++			     const char *logmsg)
      +{
      +	BUG("reftable reference store does not support copying references");
      +}
      +
      +struct reftable_reflog_ref_iterator {
     -+        struct ref_iterator base;
     -+        struct iterator iter;
     -+        struct log_record log;
     -+        struct object_id oid;
     -+        char *last_name;
     ++	struct ref_iterator base;
     ++	struct iterator iter;
     ++	struct log_record log;
     ++	struct object_id oid;
     ++	char *last_name;
      +};
      +
     -+static int reftable_reflog_ref_iterator_advance(struct ref_iterator *ref_iterator)
     ++static int
     ++reftable_reflog_ref_iterator_advance(struct ref_iterator *ref_iterator)
      +{
     -+        struct reftable_reflog_ref_iterator *ri = (struct reftable_reflog_ref_iterator*) ref_iterator;
     ++	struct reftable_reflog_ref_iterator *ri =
     ++		(struct reftable_reflog_ref_iterator *)ref_iterator;
      +
     -+        while (1) {
     -+                int err = iterator_next_log(ri->iter, &ri->log);
     -+                if (err > 0) {
     -+                        return ITER_DONE;
     -+                }
     -+                if (err < 0) {
     -+                        return ITER_ERROR;
     -+                }
     ++	while (1) {
     ++		int err = iterator_next_log(ri->iter, &ri->log);
     ++		if (err > 0) {
     ++			return ITER_DONE;
     ++		}
     ++		if (err < 0) {
     ++			return ITER_ERROR;
     ++		}
      +
     -+                ri->base.refname = ri->log.ref_name;
     -+                if (ri->last_name != NULL && 0 == strcmp(ri->log.ref_name, ri->last_name)) {
     -+                        continue;
     -+                }
     ++		ri->base.refname = ri->log.ref_name;
     ++		if (ri->last_name != NULL &&
     ++		    !strcmp(ri->log.ref_name, ri->last_name)) {
     ++			continue;
     ++		}
      +
     -+                free(ri->last_name);
     -+                ri->last_name = xstrdup(ri->log.ref_name);
     -+                // XXX const?
     -+                memcpy(&ri->oid.hash, ri->log.new_hash, GIT_SHA1_RAWSZ);
     -+                return ITER_OK;
     -+        }
     ++		free(ri->last_name);
     ++		ri->last_name = xstrdup(ri->log.ref_name);
     ++		/* XXX const? */
     ++		memcpy(&ri->oid.hash, ri->log.new_hash, GIT_SHA1_RAWSZ);
     ++		return ITER_OK;
     ++	}
      +}
      +
      +static int reftable_reflog_ref_iterator_peel(struct ref_iterator *ref_iterator,
     -+                                             struct object_id *peeled)
     ++					     struct object_id *peeled)
      +{
     -+        BUG("not supported.");
     ++	BUG("not supported.");
      +	return -1;
      +}
      +
      +static int reftable_reflog_ref_iterator_abort(struct ref_iterator *ref_iterator)
      +{
     -+	struct reftable_reflog_ref_iterator *ri = (struct reftable_reflog_ref_iterator*) ref_iterator;
     ++	struct reftable_reflog_ref_iterator *ri =
     ++		(struct reftable_reflog_ref_iterator *)ref_iterator;
      +	log_record_clear(&ri->log);
      +	iterator_destroy(&ri->iter);
      +	return 0;
      +}
      +
      +static struct ref_iterator_vtable reftable_reflog_ref_iterator_vtable = {
     -+	reftable_reflog_ref_iterator_advance,
     -+	reftable_reflog_ref_iterator_peel,
     ++	reftable_reflog_ref_iterator_advance, reftable_reflog_ref_iterator_peel,
      +	reftable_reflog_ref_iterator_abort
      +};
      +
     -+static struct ref_iterator *reftable_reflog_iterator_begin(struct ref_store *ref_store)
     ++static struct ref_iterator *
     ++reftable_reflog_iterator_begin(struct ref_store *ref_store)
      +{
     -+        struct reftable_reflog_ref_iterator *ri = xcalloc(sizeof(*ri), 1);
     -+        struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_reflog_ref_iterator *ri = xcalloc(sizeof(*ri), 1);
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +
      +	struct merged_table *mt = stack_merged_table(refs->stack);
     -+        int err = merged_table_seek_log(mt, &ri->iter, "");
     -+        if (err < 0) {
     -+                free(ri);
     -+                return NULL;
     -+        }
     ++	int err = merged_table_seek_log(mt, &ri->iter, "");
     ++	if (err < 0) {
     ++		free(ri);
     ++		return NULL;
     ++	}
      +
     -+	base_ref_iterator_init(&ri->base, &reftable_reflog_ref_iterator_vtable, 1);
     ++	base_ref_iterator_init(&ri->base, &reftable_reflog_ref_iterator_vtable,
     ++			       1);
      +	ri->base.oid = &ri->oid;
      +
      +	return empty_ref_iterator_begin();
      +}
      +
     -+static int reftable_for_each_reflog_ent_newest_first(struct ref_store *ref_store,
     -+                                        const char *refname,
     -+                                        each_reflog_ent_fn fn, void *cb_data)
     -+{
     -+        struct iterator it = { };
     -+        struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     -+        struct merged_table *mt = stack_merged_table(refs->stack);
     -+        int err = merged_table_seek_log(mt, &it, refname);
     -+        struct log_record log = {};
     -+
     -+        while (err == 0)  {
     -+                err = iterator_next_log(it, &log);
     -+                if (err != 0) {
     -+                        break;
     -+                }
     -+
     -+                if (0 != strcmp(log.ref_name, refname)) {
     -+                        break;
     -+                }
     -+
     -+                {
     -+                        struct object_id old_oid = {};
     -+                        struct object_id new_oid = {};
     -+
     -+                        memcpy(&old_oid.hash, log.old_hash, GIT_SHA1_RAWSZ);
     -+                        memcpy(&new_oid.hash, log.new_hash, GIT_SHA1_RAWSZ);
     -+
     -+                        // XXX committer = email? name?
     -+                        if (fn(&old_oid, &new_oid, log.name, log.time, log.tz_offset, log.message, cb_data)) {
     -+                                err = -1;
     -+                                break;
     -+                        }
     -+                }
     -+        }
     -+
     -+        log_record_clear(&log);
     -+        iterator_destroy(&it);
     -+        if (err > 0) {
     -+                err = 0;
     -+        }
     ++static int
     ++reftable_for_each_reflog_ent_newest_first(struct ref_store *ref_store,
     ++					  const char *refname,
     ++					  each_reflog_ent_fn fn, void *cb_data)
     ++{
     ++	struct iterator it = {};
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	struct merged_table *mt = stack_merged_table(refs->stack);
     ++	int err = merged_table_seek_log(mt, &it, refname);
     ++	struct log_record log = {};
     ++
     ++	while (err == 0) {
     ++		err = iterator_next_log(it, &log);
     ++		if (err != 0) {
     ++			break;
     ++		}
     ++
     ++		if (strcmp(log.ref_name, refname)) {
     ++			break;
     ++		}
     ++
     ++		{
     ++			struct object_id old_oid = {};
     ++			struct object_id new_oid = {};
     ++
     ++			memcpy(&old_oid.hash, log.old_hash, GIT_SHA1_RAWSZ);
     ++			memcpy(&new_oid.hash, log.new_hash, GIT_SHA1_RAWSZ);
     ++
     ++			/* XXX committer = email? name? */
     ++			if (fn(&old_oid, &new_oid, log.name, log.time,
     ++			       log.tz_offset, log.message, cb_data)) {
     ++				err = -1;
     ++				break;
     ++			}
     ++		}
     ++	}
     ++
     ++	log_record_clear(&log);
     ++	iterator_destroy(&it);
     ++	if (err > 0) {
     ++		err = 0;
     ++	}
      +	return err;
      +}
      +
     -+static int reftable_for_each_reflog_ent_oldest_first(struct ref_store *ref_store,
     -+                                                     const char *refname,
     -+                                                     each_reflog_ent_fn fn,
     -+                                                     void *cb_data)
     -+{
     -+        struct iterator it = { };
     -+        struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     -+        struct merged_table *mt = stack_merged_table(refs->stack);
     -+        int err = merged_table_seek_log(mt, &it, refname);
     -+
     -+        struct log_record *logs = NULL;
     -+        int cap = 0;
     -+        int len = 0;
     -+
     -+        printf("oldest first\n");
     -+        while (err == 0)  {
     -+                struct log_record log = {};
     -+                err = iterator_next_log(it, &log);
     -+                if (err != 0) {
     -+                        break;
     -+                }
     -+
     -+                if (0 != strcmp(log.ref_name, refname)) {
     -+                        break;
     -+                }
     -+
     -+                if (len == cap) {
     -+                        cap  =2*cap + 1;
     -+                        logs = realloc(logs, cap * sizeof(*logs));
     -+                }
     -+
     -+                logs[len++] = log;
     -+        }
     -+
     -+        for (int i = len; i--; ) {
     -+                struct log_record *log = &logs[i];
     -+                struct object_id old_oid = {};
     -+                struct object_id new_oid = {};
     -+
     -+                memcpy(&old_oid.hash, log->old_hash, GIT_SHA1_RAWSZ);
     -+                memcpy(&new_oid.hash, log->new_hash, GIT_SHA1_RAWSZ);
     -+
     -+                // XXX committer = email? name?
     -+                if (!fn(&old_oid, &new_oid, log->name, log->time, log->tz_offset, log->message, cb_data)) {
     -+                        err = -1;
     -+                        break;
     -+                }
     -+        }
     -+
     -+        for (int i = 0; i < len; i++) {
     -+                log_record_clear(&logs[i]);
     -+        }
     -+        free(logs);
     -+
     -+        iterator_destroy(&it);
     -+        if (err > 0) {
     -+                err = 0;
     -+        }
     ++static int
     ++reftable_for_each_reflog_ent_oldest_first(struct ref_store *ref_store,
     ++					  const char *refname,
     ++					  each_reflog_ent_fn fn, void *cb_data)
     ++{
     ++	struct iterator it = {};
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	struct merged_table *mt = stack_merged_table(refs->stack);
     ++	int err = merged_table_seek_log(mt, &it, refname);
     ++
     ++	struct log_record *logs = NULL;
     ++	int cap = 0;
     ++	int len = 0;
     ++
     ++	printf("oldest first\n");
     ++	while (err == 0) {
     ++		struct log_record log = {};
     ++		err = iterator_next_log(it, &log);
     ++		if (err != 0) {
     ++			break;
     ++		}
     ++
     ++		if (strcmp(log.ref_name, refname)) {
     ++			break;
     ++		}
     ++
     ++		if (len == cap) {
     ++			cap = 2 * cap + 1;
     ++			logs = realloc(logs, cap * sizeof(*logs));
     ++		}
     ++
     ++		logs[len++] = log;
     ++	}
     ++
     ++	for (int i = len; i--;) {
     ++		struct log_record *log = &logs[i];
     ++		struct object_id old_oid = {};
     ++		struct object_id new_oid = {};
     ++
     ++		memcpy(&old_oid.hash, log->old_hash, GIT_SHA1_RAWSZ);
     ++		memcpy(&new_oid.hash, log->new_hash, GIT_SHA1_RAWSZ);
     ++
     ++		/* XXX committer = email? name? */
     ++		if (!fn(&old_oid, &new_oid, log->name, log->time,
     ++			log->tz_offset, log->message, cb_data)) {
     ++			err = -1;
     ++			break;
     ++		}
     ++	}
     ++
     ++	for (int i = 0; i < len; i++) {
     ++		log_record_clear(&logs[i]);
     ++	}
     ++	free(logs);
     ++
     ++	iterator_destroy(&it);
     ++	if (err > 0) {
     ++		err = 0;
     ++	}
      +	return err;
      +}
      +
      +static int reftable_reflog_exists(struct ref_store *ref_store,
     -+                                  const char *refname)
     ++				  const char *refname)
      +{
     -+        // always exists.
     ++	/* always exists. */
      +	return 1;
      +}
      +
      +static int reftable_create_reflog(struct ref_store *ref_store,
     -+                                  const char *refname, int force_create,
     -+                                  struct strbuf *err)
     ++				  const char *refname, int force_create,
     ++				  struct strbuf *err)
      +{
     -+        return 0;
     ++	return 0;
      +}
      +
      +static int reftable_delete_reflog(struct ref_store *ref_store,
     -+                                  const char *refname)
     ++				  const char *refname)
      +{
      +	return 0;
      +}
      +
      +static int reftable_reflog_expire(struct ref_store *ref_store,
     -+				const char *refname, const struct object_id *oid,
     -+				unsigned int flags,
     -+				reflog_expiry_prepare_fn prepare_fn,
     -+				reflog_expiry_should_prune_fn should_prune_fn,
     -+				reflog_expiry_cleanup_fn cleanup_fn,
     -+				void *policy_cb_data)
     -+{
     -+        /*
     -+          XXX
     -+
     -+          This doesn't fit with the reftable API. If we are expiring for space
     -+          reasons, the expiry should be combined with a compaction, and there
     -+          should be a policy that can be called for all refnames, not for a
     -+          single ref name.
     -+
     -+          If this is for cleaning up individual entries, we'll have to write
     -+          extra data to create tombstones.
     -+         */
     ++				  const char *refname,
     ++				  const struct object_id *oid,
     ++				  unsigned int flags,
     ++				  reflog_expiry_prepare_fn prepare_fn,
     ++				  reflog_expiry_should_prune_fn should_prune_fn,
     ++				  reflog_expiry_cleanup_fn cleanup_fn,
     ++				  void *policy_cb_data)
     ++{
     ++	/*
     ++	  XXX
     ++
     ++	  This doesn't fit with the reftable API. If we are expiring for space
     ++	  reasons, the expiry should be combined with a compaction, and there
     ++	  should be a policy that can be called for all refnames, not for a
     ++	  single ref name.
     ++
     ++	  If this is for cleaning up individual entries, we'll have to write
     ++	  extra data to create tombstones.
     ++	 */
      +	return 0;
      +}
      +
     -+
      +static int reftable_read_raw_ref(struct ref_store *ref_store,
      +				 const char *refname, struct object_id *oid,
      +				 struct strbuf *referent, unsigned int *type)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct ref_record ref = {};
      +	int err = stack_read_ref(refs->stack, refname, &ref);
      +	if (err) {
     @@ -912,7 +950,7 @@ $^
      +		strbuf_reset(referent);
      +		strbuf_addstr(referent, ref.target);
      +		*type |= REF_ISSYMREF;
     -+ 	} else  {
     ++	} else {
      +		memcpy(oid->hash, ref.value, GIT_SHA1_RAWSZ);
      +	}
      +

-- 
gitgitgadget

  parent reply	other threads:[~2020-01-27 14:22 UTC|newest]

Thread overview: 409+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-01-23 19:41 [PATCH " Han-Wen Nienhuys via GitGitGadget
2020-01-23 19:41 ` [PATCH 1/5] setup.c: enable repo detection for reftable Han-Wen Nienhuys via GitGitGadget
2020-01-23 19:41 ` [PATCH 2/5] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-01-23 19:41 ` [PATCH 3/5] Document how ref iterators and symrefs interact Han-Wen Nienhuys via GitGitGadget
2020-01-23 19:41 ` [PATCH 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-01-23 19:41 ` [PATCH 5/5] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-01-23 21:44 ` [PATCH 0/5] Reftable support git-core Junio C Hamano
2020-01-27 13:52   ` Han-Wen Nienhuys
2020-01-27 13:57     ` Han-Wen Nienhuys
2020-01-23 22:45 ` Stephan Beyer
2020-01-27 13:57   ` Han-Wen Nienhuys
2020-01-27 14:22 ` Han-Wen Nienhuys via GitGitGadget [this message]
2020-01-27 14:22   ` [PATCH v2 1/5] setup.c: enable repo detection for reftable Han-Wen Nienhuys via GitGitGadget
2020-01-27 14:22   ` [PATCH v2 2/5] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-01-27 22:28     ` Junio C Hamano
2020-01-28 15:58       ` Han-Wen Nienhuys
2020-01-30  4:19         ` Junio C Hamano
2020-01-27 14:22   ` [PATCH v2 3/5] Document how ref iterators and symrefs interact Han-Wen Nienhuys via GitGitGadget
2020-01-27 22:53     ` Junio C Hamano
2020-01-28 16:07       ` Han-Wen Nienhuys
2020-01-28 19:35         ` Junio C Hamano
2020-01-27 14:22   ` [PATCH v2 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-01-27 14:22   ` [PATCH v2 5/5] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-01-28  7:31     ` Jeff King
2020-01-28 15:36       ` Martin Fick
2020-01-29  8:12         ` Jeff King
2020-01-29 16:49           ` Martin Fick
2020-01-29 18:40             ` Han-Wen Nienhuys
2020-01-29 19:47               ` Martin Fick
2020-01-29 19:50                 ` Han-Wen Nienhuys
2020-01-30  7:21                   ` Jeff King
2020-02-03 16:39                     ` Han-Wen Nienhuys
2020-02-03 17:05                       ` Jeff King
2020-02-03 17:09                         ` Han-Wen Nienhuys
2020-02-04 18:54                         ` Han-Wen Nienhuys
2020-02-04 20:06                           ` Jeff King
2020-02-04 20:26                             ` Han-Wen Nienhuys
2020-01-29 18:34           ` Junio C Hamano
2020-01-28 15:56       ` Han-Wen Nienhuys
2020-01-29 10:47         ` Jeff King
2020-01-29 18:43           ` Junio C Hamano
2020-01-29 18:53             ` Han-Wen Nienhuys
2020-01-30  7:26             ` Jeff King
2020-02-04 19:06           ` Han-Wen Nienhuys
2020-02-04 19:54             ` Jeff King
2020-02-04 20:22       ` Han-Wen Nienhuys
2020-02-04 22:13         ` Jeff King
2020-02-04 20:27   ` [PATCH v3 0/6] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:27     ` [PATCH v3 1/6] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:27     ` [PATCH v3 2/6] setup.c: enable repo detection for reftable Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:31       ` Han-Wen Nienhuys
2020-02-04 20:27     ` [PATCH v3 3/6] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-02-04 21:29       ` Junio C Hamano
2020-02-05 11:34         ` Han-Wen Nienhuys
2020-02-05 11:42       ` SZEDER Gábor
2020-02-05 12:24         ` Jeff King
2020-02-04 20:27     ` [PATCH v3 4/6] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:27     ` [PATCH v3 5/6] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:27     ` [PATCH v3 6/6] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55     ` [PATCH v4 0/5] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55       ` [PATCH v4 1/5] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55       ` [PATCH v4 2/5] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55       ` [PATCH v4 3/5] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55       ` [PATCH v4 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-06 23:07         ` Junio C Hamano
2020-02-07  0:16         ` brian m. carlson
2020-02-10 13:16           ` Han-Wen Nienhuys
2020-02-11  0:05             ` brian m. carlson
2020-02-11 14:20               ` Han-Wen Nienhuys
2020-02-11 16:31                 ` Junio C Hamano
2020-02-11 16:40                   ` Han-Wen Nienhuys
2020-02-11 23:40                     ` brian m. carlson
2020-02-18  9:25                       ` Han-Wen Nienhuys
2020-02-11 16:46                   ` Han-Wen Nienhuys
2020-02-20 17:20                     ` Jonathan Nieder
2020-02-06 22:55       ` [PATCH v4 5/5] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-02-06 23:49         ` brian m. carlson
2020-02-10 13:18           ` Han-Wen Nienhuys
2020-02-06 23:31       ` [PATCH v4 0/5] Reftable support git-core brian m. carlson
2020-02-10 14:14       ` [PATCH v5 " Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14         ` [PATCH v5 1/5] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14         ` [PATCH v5 2/5] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14         ` [PATCH v5 3/5] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14         ` [PATCH v5 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14         ` [PATCH v5 5/5] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43         ` [PATCH v6 0/5] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43           ` [PATCH v6 1/5] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43           ` [PATCH v6 2/5] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43           ` [PATCH v6 3/5] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43           ` [PATCH v6 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-18 21:11             ` Junio C Hamano
2020-02-19  6:55               ` Jeff King
2020-02-19 17:00                 ` Han-Wen Nienhuys
2020-02-18  8:43           ` [PATCH v6 5/5] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-02-18 21:05           ` [PATCH v6 0/5] Reftable support git-core Junio C Hamano
2020-02-19 16:59             ` Han-Wen Nienhuys
2020-02-19 17:02               ` Junio C Hamano
2020-02-19 17:21                 ` Han-Wen Nienhuys
2020-02-19 18:10                   ` Junio C Hamano
2020-02-19 19:14                     ` Han-Wen Nienhuys
2020-02-19 20:09                       ` Junio C Hamano
2020-02-20 11:19                     ` Jeff King
2020-02-21  6:40           ` Jonathan Nieder
2020-02-26 17:16             ` Han-Wen Nienhuys
2020-02-26 20:04               ` Junio C Hamano
2020-02-27  0:01               ` brian m. carlson
     [not found]             ` <CAFQ2z_NQn9O3kFmHk8Cr31FY66ToU4bUdE=asHUfN++zBG+SPw@mail.gmail.com>
2020-02-26 17:41               ` Jonathan Nieder
2020-02-26 17:54                 ` Han-Wen Nienhuys
2020-02-26  8:49           ` [PATCH v7 0/6] " Han-Wen Nienhuys via GitGitGadget
2020-02-26  8:49             ` [PATCH v7 1/6] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-02-26  8:49             ` [PATCH v7 2/6] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-02-26  8:49             ` [PATCH v7 3/6] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-02-26  8:49             ` [PATCH v7 4/6] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-02-26  8:49             ` [PATCH v7 5/6] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-26  8:49             ` [PATCH v7 6/6] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-02-26 18:12               ` Junio C Hamano
2020-02-26 18:59                 ` Han-Wen Nienhuys
2020-02-26 19:59                   ` Junio C Hamano
2020-02-27 16:03                     ` Han-Wen Nienhuys
2020-02-27 16:23                       ` Junio C Hamano
2020-02-27 17:56                         ` Han-Wen Nienhuys
2020-02-26 21:31               ` Junio C Hamano
2020-02-27 16:01                 ` Han-Wen Nienhuys
2020-02-27 16:26                   ` Junio C Hamano
2020-02-26 17:35             ` [PATCH v7 0/6] Reftable support git-core Junio C Hamano
2020-03-24  6:06             ` Jonathan Nieder
2020-04-01 11:28             ` [PATCH v8 0/9] " Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 1/9] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 2/9] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 3/9] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 4/9] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 5/9] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 6/9] reftable: define version 2 of the spec to accomodate SHA256 Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 7/9] reftable: clarify how empty tables should be written Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 8/9] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-04-01 11:28               ` [PATCH v8 9/9] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-04-15 23:29               ` [PATCH v8 0/9] Reftable support git-core Junio C Hamano
2020-04-18  3:22                 ` Danh Doan
2020-04-20 21:14               ` [PATCH v9 00/10] " Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 01/10] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 02/10] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 03/10] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 04/10] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 05/10] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 06/10] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 07/10] reftable: define version 2 of the spec to accomodate SHA256 Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 08/10] reftable: clarify how empty tables should be written Han-Wen Nienhuys via GitGitGadget
2020-04-20 21:14                 ` [PATCH v9 09/10] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-04-20 22:06                   ` Junio C Hamano
2020-04-21 19:04                     ` Han-Wen Nienhuys
2020-04-22 17:35                     ` Johannes Schindelin
2020-04-20 21:14                 ` [PATCH v9 10/10] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-04-21 20:13                 ` [PATCH v9 00/10] Reftable support git-core Junio C Hamano
2020-04-23 21:27                   ` Han-Wen Nienhuys
2020-04-23 21:43                     ` Junio C Hamano
2020-04-23 21:52                       ` Junio C Hamano
2020-04-25 13:58                         ` Johannes Schindelin
2020-04-27 20:13                 ` [PATCH v10 00/12] " Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 01/12] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 02/12] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-04-30 21:17                     ` Emily Shaffer
2020-05-04 18:03                       ` Han-Wen Nienhuys
2020-05-05 18:26                         ` Pseudo ref handling (was Re: [PATCH v10 02/12] Iterate over the "refs/" namespace in for_each_[raw]ref) Han-Wen Nienhuys
2020-04-27 20:13                   ` [PATCH v10 03/12] create .git/refs in files-backend.c Han-Wen Nienhuys via GitGitGadget
2020-04-30 21:24                     ` Emily Shaffer
2020-04-30 21:49                       ` Junio C Hamano
2020-05-04 18:10                       ` Han-Wen Nienhuys
2020-04-27 20:13                   ` [PATCH v10 04/12] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 05/12] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 06/12] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 07/12] reftable: define version 2 of the spec to accomodate SHA256 Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 08/12] reftable: clarify how empty tables should be written Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 09/12] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-04-28 14:55                     ` Danh Doan
2020-04-28 15:29                       ` Junio C Hamano
2020-04-28 15:31                         ` Junio C Hamano
2020-04-28 20:21                       ` Han-Wen Nienhuys
2020-04-28 20:23                         ` Han-Wen Nienhuys
2020-04-27 20:13                   ` [PATCH v10 10/12] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 11/12] Add some reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-04-27 20:13                   ` [PATCH v10 12/12] t: use update-ref and show-ref to reading/writing refs Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                   ` [PATCH v11 00/12] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 01/12] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 02/12] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 03/12] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 04/12] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 05/12] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 06/12] reftable: define version 2 of the spec to accomodate SHA256 Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 07/12] reftable: clarify how empty tables should be written Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 08/12] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 09/12] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 10/12] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 11/12] Add some reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-05-04 19:03                     ` [PATCH v11 12/12] t: use update-ref and show-ref to reading/writing refs Han-Wen Nienhuys via GitGitGadget
2020-05-06  4:29                     ` [PATCH v11 00/12] Reftable support git-core Junio C Hamano
2020-05-07  9:59                     ` [PATCH v12 " Han-Wen Nienhuys via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 01/12] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-05-08 18:54                         ` Junio C Hamano
2020-05-07  9:59                       ` [PATCH v12 02/12] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-05-08 18:54                         ` Junio C Hamano
2020-05-11 11:41                           ` Han-Wen Nienhuys
2020-05-07  9:59                       ` [PATCH v12 03/12] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-05-08 18:58                         ` Junio C Hamano
2020-05-11 11:42                           ` Han-Wen Nienhuys
2020-05-11 14:49                             ` Junio C Hamano
2020-05-11 15:11                               ` Han-Wen Nienhuys
2020-05-07  9:59                       ` [PATCH v12 04/12] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 05/12] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 06/12] reftable: define version 2 of the spec to accomodate SHA256 Han-Wen Nienhuys via GitGitGadget
2020-05-08 19:59                         ` Junio C Hamano
2020-05-07  9:59                       ` [PATCH v12 07/12] reftable: clarify how empty tables should be written Han-Wen Nienhuys via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 08/12] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 09/12] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 10/12] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 11/12] t: use update-ref and show-ref to reading/writing refs Han-Wen Nienhuys via GitGitGadget
2020-05-07  9:59                       ` [PATCH v12 12/12] Add some reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-05-11 19:46                       ` [PATCH v13 00/13] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-05-11 19:46                         ` [PATCH v13 01/13] refs.h: clarify reflog iteration order Han-Wen Nienhuys via GitGitGadget
2020-05-18 23:31                           ` Junio C Hamano
2020-05-11 19:46                         ` [PATCH v13 02/13] t: use update-ref and show-ref to reading/writing refs Han-Wen Nienhuys via GitGitGadget
2020-05-18 23:34                           ` Junio C Hamano
2020-05-11 19:46                         ` [PATCH v13 03/13] refs: document how ref_iterator_advance_fn should handle symrefs Han-Wen Nienhuys via GitGitGadget
2020-05-18 23:43                           ` Junio C Hamano
2020-05-11 19:46                         ` [PATCH v13 04/13] reftable: file format documentation Jonathan Nieder via GitGitGadget
2020-05-19 22:00                           ` Junio C Hamano
2020-05-20 16:06                             ` Han-Wen Nienhuys
2020-05-20 17:20                               ` Han-Wen Nienhuys
2020-05-20 17:25                                 ` Han-Wen Nienhuys
2020-05-20 17:33                                   ` Junio C Hamano
2020-05-20 18:52                             ` Jonathan Nieder
2020-05-11 19:46                         ` [PATCH v13 05/13] reftable: clarify how empty tables should be written Han-Wen Nienhuys via GitGitGadget
2020-05-19 22:01                           ` Junio C Hamano
2020-05-11 19:46                         ` [PATCH v13 06/13] reftable: define version 2 of the spec to accomodate SHA256 Han-Wen Nienhuys via GitGitGadget
2020-05-19 22:32                           ` Junio C Hamano
2020-05-20 12:38                             ` Han-Wen Nienhuys
2020-05-20 14:40                               ` Junio C Hamano
2020-05-11 19:46                         ` [PATCH v13 07/13] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-05-12 10:22                           ` Phillip Wood
2020-05-12 16:48                             ` Han-Wen Nienhuys
2020-05-13 10:06                               ` Phillip Wood
2020-05-13 18:10                                 ` Phillip Wood
2020-05-11 19:46                         ` [PATCH v13 08/13] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-05-11 19:46                         ` [PATCH v13 09/13] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-05-11 19:46                         ` [PATCH v13 10/13] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-05-11 19:46                         ` [PATCH v13 11/13] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-05-13 19:55                           ` Junio C Hamano
2020-05-11 19:46                         ` [PATCH v13 12/13] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-05-11 19:46                         ` [PATCH v13 13/13] Add some reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-05-13 19:57                           ` Junio C Hamano
2020-05-19 13:54                             ` Han-Wen Nienhuys
2020-05-19 15:21                               ` Junio C Hamano
2020-05-12  0:41                         ` [PATCH v13 00/13] Reftable support git-core Junio C Hamano
2020-05-12  7:49                           ` Han-Wen Nienhuys
2020-05-13 21:21                             ` Junio C Hamano
2020-05-18 20:31                         ` [PATCH v14 0/9] " Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 1/9] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 2/9] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 3/9] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 4/9] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 5/9] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 6/9] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 7/9] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 8/9] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-05-18 20:31                           ` [PATCH v14 9/9] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                           ` [PATCH v15 00/13] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 01/13] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 02/13] Make refs_ref_exists public Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 03/13] Treat BISECT_HEAD as a pseudo ref Han-Wen Nienhuys via GitGitGadget
2020-05-28 20:52                               ` Junio C Hamano
2020-05-28 19:46                             ` [PATCH v15 04/13] Treat CHERRY_PICK_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 05/13] Treat REVERT_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 06/13] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 07/13] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 08/13] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 09/13] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 10/13] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 11/13] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 12/13] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-05-28 19:46                             ` [PATCH v15 13/13] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-05-28 20:15                             ` [PATCH v15 00/13] Reftable support git-core Junio C Hamano
2020-05-28 21:21                             ` Junio C Hamano
2020-06-05 18:03                             ` [PATCH v16 00/14] " Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 01/14] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-06-09 10:16                                 ` Phillip Wood
2020-06-05 18:03                               ` [PATCH v16 02/14] Make refs_ref_exists public Han-Wen Nienhuys via GitGitGadget
2020-06-09 10:36                                 ` Phillip Wood
2020-06-10 18:05                                   ` Han-Wen Nienhuys
2020-06-11 14:59                                     ` Phillip Wood
2020-06-12  9:51                                       ` Phillip Wood
2020-06-15 11:32                                         ` Han-Wen Nienhuys
2020-06-05 18:03                               ` [PATCH v16 03/14] Treat BISECT_HEAD as a pseudo ref Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 04/14] Treat CHERRY_PICK_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 05/14] Treat REVERT_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 06/14] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 07/14] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 08/14] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 09/14] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 10/14] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 11/14] Hookup unittests for the reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-08 19:39                                 ` Junio C Hamano
2020-06-09 17:22                                   ` [PATCH] Fixup! Add t/helper/test-reftable.c hanwen
2020-06-09 20:45                                     ` Junio C Hamano
2020-06-05 18:03                               ` [PATCH v16 12/14] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 13/14] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-06-05 18:03                               ` [PATCH v16 14/14] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-06-09 23:14                               ` [PATCH v16 00/14] Reftable support git-core Junio C Hamano
2020-06-10  6:56                                 ` Han-Wen Nienhuys
2020-06-10 17:09                                   ` Junio C Hamano
2020-06-10 17:38                                     ` Junio C Hamano
2020-06-10 18:59                                     ` Johannes Schindelin
2020-06-10 19:04                                       ` Han-Wen Nienhuys
2020-06-10 19:20                                         ` Johannes Schindelin
2020-06-10 16:57                                 ` Han-Wen Nienhuys
2020-06-16 19:20                               ` [PATCH v17 00/17] " Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 01/17] lib-t6000.sh: write tag using git-update-ref Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 02/17] checkout: add '\n' to reflog message Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 03/17] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 04/17] Make refs_ref_exists public Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 05/17] Treat BISECT_HEAD as a pseudo ref Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 06/17] Treat CHERRY_PICK_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 07/17] Treat REVERT_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 08/17] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 09/17] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 10/17] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 11/17] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 12/17] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-06-19 14:24                                   ` SZEDER Gábor
2020-06-16 19:20                                 ` [PATCH v17 13/17] Hookup unittests for the reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 14/17] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 15/17] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-06-16 19:20                                 ` [PATCH v17 16/17] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-06-19 16:03                                   ` SZEDER Gábor
2020-06-16 19:20                                 ` [PATCH v17 17/17] Add "test-tool dump-reftable" command Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                 ` [PATCH v18 00/19] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 01/19] lib-t6000.sh: write tag using git-update-ref Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 02/19] checkout: add '\n' to reflog message Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 03/19] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 04/19] Make refs_ref_exists public Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 05/19] Treat BISECT_HEAD as a pseudo ref Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 06/19] Treat CHERRY_PICK_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 07/19] Treat REVERT_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 08/19] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 09/19] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 10/19] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 11/19] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 12/19] Add standalone build infrastructure for reftable Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 13/19] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 14/19] Hookup unittests for the reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 15/19] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 16/19] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 17/19] git-prompt: prepare for reftable refs backend SZEDER Gábor via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 18/19] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-06-22 21:55                                   ` [PATCH v18 19/19] Add "test-tool dump-reftable" command Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                   ` [PATCH v19 00/20] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 01/20] lib-t6000.sh: write tag using git-update-ref Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 02/20] t3432: use git-reflog to inspect the reflog for HEAD Han-Wen Nienhuys via GitGitGadget
2020-06-30 15:23                                       ` Denton Liu
2020-06-29 18:56                                     ` [PATCH v19 03/20] checkout: add '\n' to reflog message Han-Wen Nienhuys via GitGitGadget
2020-06-29 20:07                                       ` Junio C Hamano
2020-06-30  8:30                                         ` Han-Wen Nienhuys
2020-06-30 23:58                                           ` Junio C Hamano
2020-07-01 16:56                                             ` Han-Wen Nienhuys
2020-07-01 20:22                                               ` Re* " Junio C Hamano
2020-07-06 15:56                                                 ` Han-Wen Nienhuys
2020-07-06 18:53                                                   ` Junio C Hamano
2020-06-29 18:56                                     ` [PATCH v19 04/20] Write pseudorefs through ref backends Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 05/20] Make refs_ref_exists public Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 06/20] Treat BISECT_HEAD as a pseudo ref Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 07/20] Treat CHERRY_PICK_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 08/20] Treat REVERT_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 09/20] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 10/20] Iterate over the "refs/" namespace in for_each_[raw]ref Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 11/20] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 12/20] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 13/20] Add standalone build infrastructure for reftable Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 14/20] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 15/20] Hookup unittests for the reftable library Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 16/20] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 17/20] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 18/20] git-prompt: prepare for reftable refs backend SZEDER Gábor via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 19/20] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-06-29 18:56                                     ` [PATCH v19 20/20] Add "test-tool dump-reftable" command Han-Wen Nienhuys via GitGitGadget
2020-06-29 22:54                                     ` [PATCH v19 00/20] Reftable support git-core Junio C Hamano
2020-06-30  9:28                                       ` Han-Wen Nienhuys
2020-07-01  0:03                                         ` Junio C Hamano
2020-07-01 10:16                                           ` Han-Wen Nienhuys
2020-07-01 20:56                                             ` Junio C Hamano
2020-07-31 15:26                                     ` [PATCH v20 00/21] " Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:26                                       ` [PATCH v20 01/21] refs: add \t to reflog in the files backend Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:26                                       ` [PATCH v20 02/21] Split off reading loose ref data in separate function Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:26                                       ` [PATCH v20 03/21] t1400: use git rev-parse for testing PSEUDOREF existence Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 04/21] Modify pseudo refs through ref backend storage Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 05/21] Make HEAD a PSEUDOREF rather than PER_WORKTREE Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 06/21] Make refs_ref_exists public Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 07/21] Treat CHERRY_PICK_HEAD as a pseudo ref Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 08/21] Treat REVERT_HEAD " Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 09/21] Move REF_LOG_ONLY to refs-internal.h Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 10/21] Iteration over entire ref namespace is iterating over "refs/" Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 11/21] Add .gitattributes for the reftable/ directory Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 12/21] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 13/21] Add standalone build infrastructure for reftable Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 14/21] Reftable support for git-core Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 15/21] Read FETCH_HEAD as loose ref Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 16/21] Hookup unittests for the reftable library Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 17/21] Add GIT_DEBUG_REFS debugging mechanism Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 18/21] vcxproj: adjust for the reftable changes Johannes Schindelin via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 19/21] git-prompt: prepare for reftable refs backend SZEDER Gábor via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 20/21] Add reftable testing infrastructure Han-Wen Nienhuys via GitGitGadget
2020-07-31 15:27                                       ` [PATCH v20 21/21] Add "test-tool dump-reftable" command Han-Wen Nienhuys via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=pull.539.v2.git.1580134944.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=hanwenn@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

git@vger.kernel.org list mirror (unofficial, one of many)

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://public-inbox.org/git
	git clone --mirror http://ou63pmih66umazou.onion/git
	git clone --mirror http://czquwvybam4bgbro.onion/git
	git clone --mirror http://hjrcffqmbrq6wope.onion/git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V1 git git/ https://public-inbox.org/git \
		git@vger.kernel.org
	public-inbox-index git

Example config snippet for mirrors.
Newsgroups are available over NNTP:
	nntp://news.public-inbox.org/inbox.comp.version-control.git
	nntp://ou63pmih66umazou.onion/inbox.comp.version-control.git
	nntp://czquwvybam4bgbro.onion/inbox.comp.version-control.git
	nntp://hjrcffqmbrq6wope.onion/inbox.comp.version-control.git
	nntp://news.gmane.io/gmane.comp.version-control.git
 note: .onion URLs require Tor: https://www.torproject.org/

code repositories for the project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

AGPL code for this site: git clone https://public-inbox.org/public-inbox.git