Skip to contentSkip to conversation

Reftable support git-core

PATCH Superseded Han-Wen Nienhuys via GitGitGadget 16 participants 409 messages Jan 23, 2020 – Jul 31, 2020 1 message not in the archive Page 4 of 6

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

PATCH v14 5/9 Add reftable library

In reply to Han-Wen Nienhuys via GitGitGadget

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

Reftable is a format for storing the ref database. Its rationale and specification is in the preceding commit.

This imports the upstream library as one big commit. For understanding the code, it is suggested to read the code in the following order:

* The specification under Documentation/technical/reftable.txt

* reftable.h - the public API

* record.{c,h} - reading and writing records

* block.{c,h} - reading and writing blocks.

* writer.{c,h} - writing a complete reftable file.

* merged.{c,h} and pq.{c,h} - reading a stack of reftables

* stack.{c,h} - writing and compacting stacks of reftable on the filesystem.

---
 reftable/LICENSE       |   31 +
 reftable/README.md     |   11 +
 reftable/VERSION       |    5 +
 reftable/basics.c      |  215 +++++++
 reftable/basics.h      |   53 ++
 reftable/block.c       |  435 ++++++++++++++
 reftable/block.h       |  129 +++++
 reftable/constants.h   |   21 +
 reftable/file.c        |   97 ++++
 reftable/iter.c        |  241 ++++++++
 reftable/iter.h        |   63 ++
 reftable/merged.c      |  327 +++++++++++
 reftable/merged.h      |   39 ++
 reftable/pq.c          |  114 ++++
 reftable/pq.h          |   34 ++
 reftable/reader.c      |  754 ++++++++++++++++++++++++
 reftable/reader.h      |   65 +++
 reftable/record.c      | 1154 +++++++++++++++++++++++++++++++++++++
 reftable/record.h      |  128 +++++
 reftable/refname.c     |  215 +++++++
 reftable/refname.h     |   38 ++
 reftable/reftable.c    |   92 +++
 reftable/reftable.h    |  564 ++++++++++++++++++
 reftable/slice.c       |  225 ++++++++
 reftable/slice.h       |   77 +++
 reftable/stack.c       | 1234 ++++++++++++++++++++++++++++++++++++++++
 reftable/stack.h       |   48 ++
 reftable/system.h      |   54 ++
 reftable/tree.c        |   64 +++
 reftable/tree.h        |   34 ++
 reftable/update.sh     |   24 +
 reftable/writer.c      |  659 +++++++++++++++++++++
 reftable/writer.h      |   60 ++
 reftable/zlib-compat.c |   92 +++
 34 files changed, 7396 insertions(+)
 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/constants.h
 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/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/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
reftable/LICENSE new file +31 -0
new file mode 100644
index 00000000000..402e0f9356b
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
reftable/README.md new file +11 -0
new file mode 100644
index 00000000000..21500563fd4
@@ -0,0 +1,11 @@
+
+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:
+
+   sh reftable/update.sh
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
reftable/VERSION new file +5 -0
new file mode 100644
index 00000000000..02ba07c199a
@@ -0,0 +1,5 @@
+commit bad78b6de70700933a2f93ebadaa37a5e852d9ea
+Author: Han-Wen Nienhuys <[email protected]>
+Date:   Mon May 18 20:12:57 2020 +0200
+
+    C: get rid of inner blocks for local scoping
reftable/basics.c new file +215 -0

215 lines not on this page · raw message

reftable/basics.h new file +53 -0

53 lines not on this page · raw message

reftable/block.c new file +435 -0

435 lines not on this page · raw message

reftable/block.h new file +129 -0

129 lines not on this page · raw message

reftable/constants.h new file +21 -0
new file mode 100644
index 00000000000..5eee72c4c11
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
reftable/file.c new file +97 -0

97 lines not on this page · raw message

reftable/iter.c new file +241 -0

241 lines not on this page · raw message

reftable/iter.h new file +63 -0

63 lines not on this page · raw message

reftable/merged.c new file +327 -0

327 lines not on this page · raw message

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

114 lines not on this page · raw message

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

754 lines not on this page · raw message

reftable/reader.h new file +65 -0

65 lines not on this page · raw message

reftable/record.c new file +1154 -0

366 lines not on this page · raw message

reftable/record.h new file +128 -0

128 lines not on this page · raw message

reftable/refname.c new file +215 -0

215 lines not on this page · raw message

reftable/refname.h new file +38 -0
new file mode 100644
index 00000000000..f335287fcdb

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

reftable/reftable.c new file +92 -0

92 lines not on this page · raw message

reftable/reftable.h new file +564 -0

564 lines not on this page · raw message

reftable/slice.c new file +225 -0

225 lines not on this page · raw message

reftable/slice.h new file +77 -0

77 lines not on this page · raw message

reftable/stack.c new file +1234 -0

1,234 lines not on this page · raw message

reftable/stack.h new file +48 -0

48 lines not on this page · raw message

reftable/system.h new file +54 -0

54 lines not on this page · raw message

reftable/tree.c new file +64 -0

64 lines not on this page · raw message

reftable/tree.h new file +34 -0
new file mode 100644
index 00000000000..954512e9a3a

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

reftable/update.sh new file +24 -0
new file mode 100755
index 00000000000..ef32aeda515

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

reftable/writer.c new file +659 -0

659 lines not on this page · raw message

reftable/writer.h new file +60 -0

60 lines not on this page · raw message

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

92 lines not on this page · raw message

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

Signature · 1 line
gitgitgadget

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

#219 lore raw

Re: [PATCH v13 01/13] refs.h: clarify reflog iteration order

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

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

---
 refs.h | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

I think the split of the topics was a good first move; now we need to make sure that we need no more work on the early part.

refs.h int delete_refs(const char *msg, struct string_list *refnames, · line 436

Quoted 11 lines
refs.h +2 -1
index a92d2c74c83..99ba9e331e5 100644
@@ -432,19 +432,31 @@ int delete_refs(const char *msg, struct string_list *refnames,
 int refs_delete_reflog(struct ref_store *refs, const char *refname);
 int delete_reflog(const char *refname);
 
-/* iterate over reflog entries */
+/* Callback to process a reflog entry found by the iteration functions (see
+ * below) */

/*
* A multi-line comment in our codebase
* look like this, with the opening slash-asterisk
* and closing asterisk-slash occupying their own
* lines.
*/

refs.h int delete_refs(const char *msg, struct string_list *refnames, · line 442

Quoted 6 lines
refs.h +1 -0
 typedef int each_reflog_ent_fn(
 		struct object_id *old_oid, struct object_id *new_oid,
 		const char *committer, timestamp_t timestamp,
 		int tz, const char *msg, void *cb_data);
 
+/* Iterate in over reflog entries in the log for `refname`. */

"in over"??? Should this just be "iterate over reflog entries in ..."?

Quoted 8 lines
refs.h +4 -0
+
+/* oldest entry first */
 int refs_for_each_reflog_ent(struct ref_store *refs, const char *refname,
 			     each_reflog_ent_fn fn, void *cb_data);
+
+/* youngest entry first */
 int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
 				     const char *refname,

refs.h int delete_refs(const char *msg, struct string_list *refnames, · line 460

Quoted 10 lines
refs.h +6 -0
 				     each_reflog_ent_fn fn,
 				     void *cb_data);
+
+/* Call a function for each reflog entry in the log for `refname`. */
+
+/* oldest entry first */
 int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
+
+/* youngest entry first */
 int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);

The only difference betwen refs_for_each_reflog_ent() and for_each_reflog_ent() is that the former takes an arbitrary ref-store while the latter works on the default ref-store, no? They should otherwise be identical, so describing one as "Iterate over reflog entries" while describing the other as "Call a function for each" feel misleading.

#221 lore raw

Re: [PATCH v13 02/13] t: use update-ref and show-ref to reading/writing refs

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 32 lines · Han-Wen Nienhuys via GitGitGadget

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

Reading and writing .git/refs/* assumes that refs are stored in the 'files' ref backend.

---
 t/t0002-gitfile.sh             |  2 +-
 t/t1400-update-ref.sh          | 32 ++++++++++++++++----------------
 t/t1506-rev-parse-diagnosis.sh |  2 +-
 t/t6050-replace.sh             |  2 +-
 t/t9020-remote-svn.sh          |  4 ++--
 5 files changed, 21 insertions(+), 21 deletions(-)
t/t0002-gitfile.sh +1 -1
index 0aa9908ea12..960ed150cb5 100755
@@ -62,7 +62,7 @@ test_expect_success 'check commit-tree' '
 '
 
 test_expect_success 'check rev-list' '
-	echo $SHA >"$REAL/HEAD" &&
+	git update-ref "HEAD" "$SHA" &&
 	test "$SHA" = "$(git rev-list HEAD)"
 '
 
t/t1400-update-ref.sh +0 -0
index e1197ac8189..27171f82612 100755

t/t1400-update-ref.sh test_expect_success setup ' · line 40

Quoted 5 lines
t/t1400-update-ref.sh +1 -1
@@ -37,15 +37,15 @@ test_expect_success setup '
 
 test_expect_success "create $m" '
 	git update-ref $m $A &&
-	test $A = $(cat .git/$m)
+	test $A = $(git show-ref -s --verify $m)

All of the above looks good. These were written long time ago, and we should have cleaned them up much earlier, but we didn't.

t/t1506-rev-parse-diagnosis.sh test_expect_success 'arg before dashdash must be a revision (ambiguous)' ' · line 211

Quoted 11 lines
t/t1506-rev-parse-diagnosis.sh +1 -1
index 52edcbdcc32..dbf690b9c1b 100755
@@ -207,7 +207,7 @@ test_expect_success 'arg before dashdash must be a revision (ambiguous)' '
 	{
 		# we do not want to use rev-parse here, because
 		# we are testing it
-		cat .git/refs/heads/foobar &&
+		git show-ref -s refs/heads/foobar &&
 		printf "%s\n" --

Likewise.

t/t6050-replace.sh test_expect_success 'tag replaced commit' ' · line 138

Quoted 12 lines
t/t1506-rev-parse-diagnosis.sh +0 -0
 	} >expect &&
 	git rev-parse foobar -- >actual &&
t/t6050-replace.sh +1 -1
index e7e64e085dd..c80dc10b8f1 100755
@@ -135,7 +135,7 @@ test_expect_success 'tag replaced commit' '
 test_expect_success '"git fsck" works' '
      git fsck master >fsck_master.out &&
      test_i18ngrep "dangling commit $R" fsck_master.out &&
-     test_i18ngrep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out &&
+     test_i18ngrep "dangling tag $(git show-ref -s refs/tags/mytag)" fsck_master.out &&

Likewise; this has no excuse of being old code ;-)

Quoted 8 lines
t/t6050-replace.sh +0 -0
      test -z "$(git fsck)"
 '
 
t/t9020-remote-svn.sh +0 -0
index 6fca08e5e35..9fcfa969a9b 100755

t/t9020-remote-svn.sh test_expect_success REMOTE_SVN 'simple fetch' ' · line 53

Quoted 8 lines
t/t9020-remote-svn.sh +2 -2
@@ -48,8 +48,8 @@ test_expect_success REMOTE_SVN 'simple fetch' '
 '
 
 test_debug '
-	cat .git/refs/svn/svnsim/master
-	cat .git/refs/remotes/svnsim/master
+	git show-ref -s refs/svn/svnsim/master
+	git show-ref -s refs/remotes/svnsim/master
 '

Looks good.

Thanks.

#223 lore raw

Re: [PATCH v13 03/13] refs: document how ref_iterator_advance_fn should handle symrefs

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 12 lines · Han-Wen Nienhuys via GitGitGadget

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

---
 refs/refs-internal.h | 5 +++++
 1 file changed, 5 insertions(+)
refs/refs-internal.h +0 -0
index ff2436c0fb7..3490aac3a40 100644

refs/refs-internal.h void base_ref_iterator_free(struct ref_iterator *iter); · line 446

Quoted 9 lines
refs/refs-internal.h +5 -0
@@ -438,6 +438,11 @@ void base_ref_iterator_free(struct ref_iterator *iter);
 
 /* Virtual function declarations for ref_iterators: */
 
+/*
+ * backend-specific implementation of ref_iterator_advance.
+ * For symrefs, the function should set REF_ISSYMREF, and it should also
+ * dereference the symref to provide the OID referent.
+ */
 typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);

Shouldn't we also talk about the need for the backend to yield broken refs with ISBROKEN (iow, you are not allowed to skip---let the caller make the decision to skip) etc.?

Other than that, this is a good additional piece of information to tell the implementors of new backend.

Thanks.

Quoted 2 lines
refs/refs-internal.h +0 -0
 typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,
#250 lore raw

Re: [PATCH v13 13/13] Add some reftable testing infrastructure

In reply to Junio C Hamano
Quoted 26 lines · Junio C Hamano
Quoted 23 lines · Han-Wen Nienhuys via GitGitGadget
builtin/init-db.c +1 -1
index b7053b9e370..da5b4670c84 100644
@@ -545,7 +545,7 @@ static const char *const init_db_usage[] = {
 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
      const char *git_dir;
-     const char *ref_storage_format = DEFAULT_REF_STORAGE;
+     const char *ref_storage_format = default_ref_storage();
      const char *real_git_dir = NULL;
      const char *work_tree;
      const char *template_dir = NULL;
refs.c +1 -1
index e8751415a9e..70c11b05391 100644
@@ -1742,7 +1742,7 @@ struct ref_store *get_main_ref_store(struct repository *r)
      r->refs_private = ref_store_init(r->gitdir,
                                       r->ref_storage_format ?
                                               r->ref_storage_format :
-                                              DEFAULT_REF_STORAGE,
+                                              default_ref_storage(),
Quoted 8 lines

refs.c struct ref_store *get_main_ref_store(struct repository *r) · line 1748

Quoted 3 lines
refs.c +0 -0
                                       REF_STORE_ALL_CAPS);
      return r->refs_private;
 }

Would it make sense to let NULL stand for "we use the default, whatever it is" so that anybody outside the implementation of the refs API does not have to care what the default is, and make ref_store_init() function the only thing that knows what it is?

Is this your strong recommendation in the form of a question? :)

If it is just a question: maybe. I don't know enough of Git to say for sure.

How do submodules decide on other settings, such as the hash ID?

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

Re: [PATCH v13 13/13] Add some reftable testing infrastructure

Quoted 19 lines · Han-Wen Nienhuys
Quoted 17 lines · Junio C Hamano

refs.c struct ref_store *get_main_ref_store(struct repository *r) · line 1748

Quoted 12 lines
refs.c +1 -1
@@ -1742,7 +1742,7 @@ struct ref_store *get_main_ref_store(struct repository *r)
      r->refs_private = ref_store_init(r->gitdir,
                                       r->ref_storage_format ?
                                               r->ref_storage_format :
-                                              DEFAULT_REF_STORAGE,
+                                              default_ref_storage(),
                                       REF_STORE_ALL_CAPS);
      return r->refs_private;
 }

Would it make sense to let NULL stand for "we use the default, whatever it is" so that anybody outside the implementation of the refs API does not have to care what the default is, and make ref_store_init() function the only thing that knows what it is?

Quoted 3 lines

Is this your strong recommendation in the form of a question? :)

If it is just a question: maybe. I don't know enough of Git to say for sure.

I try not to ask a rhetorical question without marking it explicitly as such. The question "instead of having to sprinkle ?: all over the place on the callers' side, would it make it better or worse to give a 'not decided' default to the ref_storage_format field?" came to mind while reading the series.

Quoted 1 line

How do submodules decide on other settings, such as the hash ID?

I am not sure the issue of "what default to use" is limited to the submodule case (e.g. "git init" and "git clone" starts from having no "current repository" and they need to make the decision among available choices). As I've seen this series and SHA-256 series conflict in the "init" area, it probably is time for me to introduce you to Brian (cc'ed) to pick his brain ;-).

Thanks.

#225 lore raw

Re: [PATCH v13 04/13] reftable: file format documentation

In reply to Jonathan Nieder via GitGitGadget
Quoted 11 lines · Jonathan Nieder via GitGitGadget

From: Jonathan Nieder <[email protected]>

Shawn Pearce explains:

Some repositories contain a lot of references (e.g. android at 866k, rails at 31k). The reftable format provides:

- Near constant time lookup for any single reference, even when the repository is cold and not in process or kernel cache.
- Near constant time verification a SHA-1 is referred to by at least one reference (for allow-tip-sha1-in-want).

Not quite grammatical sentence? Perhaps "if" after "verification?

Quoted 17 lines

- Efficient lookup of an entire namespace, such as refs/tags/.
- Support atomic push O(size_of_update) operations.
- Combine reflog storage with ref storage.

This file format spec was originally written in July, 2017 by Shawn Pearce. Some refinements since then were made by Shawn and by Han-Wen Nienhuys based on experiences implementing and experimenting with the format. (All of this was in the context of our work at Google and Google is happy to contribute the result to the Git project.)

Imported from JGit[1]'s current version (c217d33ff, "Documentation/technical/reftable: improve repo layout", 2020-02-04) of Documentation/technical/reftable.md and converted to asciidoc by running

pandoc -t asciidoc -f markdown reftable.md >reftable.txt

Quoted 7 lines

using pandoc 2.2.1. The result required the following additional minor changes:

- removed the [TOC] directive to add a table of contents, since asciidoc does not support it
- replaced git-scm.com/docs links with linkgit: directives that link to other pages within Git's documentation

There are many

’

funny-quotes where we would prefer to place vanilla single quotes, which may also need to be corrected in the conversion toolchain.

Typoes pointed out below may probably be from the original where they should be corrected.

Quoted 6 lines
Documentation/technical/reftable.txt new file +0 -0
new file mode 100644
index 00000000000..8bad9ade256

Documentation/technical/reftable.txt line 10

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
@@ -0,0 +1,1067 @@
+reftable
+--------
+
+Overview
+~~~~~~~~
+
+Problem statement
+^^^^^^^^^^^^^^^^^
+
+Some repositories contain a lot of references (e.g. android at 866k,

Let's not use &nbsp; here after "e.g.". I see a normal space after "e.g." a few lines below.

Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+rails at 31k). The existing packed-refs format takes up a lot of space
+(e.g. 62M), and does not scale with additional references. Lookup of a
+single reference requires linearly scanning the file.
+Atomic pushes modifying multiple references require copying the entire

Documentation/technical/reftable.txt line 25

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+packed-refs file, which can be a considerable amount of data moved
+(e.g. 62M in, 62M out) for even small transactions (2 refs modified).
+
+Repositories with many loose references occupy a large number of disk
+blocks from the local file system, as each reference is its own file
+storing 41 bytes (and another file for the corresponding reflog). This
+negatively affects the number of inodes available when a large number of
+repositories are stored on the same filesystem. Readers can be penalized
+due to the larger number of syscalls required to traverse and read the
+`$GIT_DIR/refs` directory.

Another downside is that we cannot arrange atomic updates to multiple refs over loose refs, even though the "lookup of a single reference does not require linear scan" unlike packed-refs, (as long as the filesystem does its job). Worth mentioning?

Documentation/technical/reftable.txt line 34

Quoted 9 lines
Documentation/technical/reftable.txt new file +9 -0
+
+Objectives
+^^^^^^^^^^
+
+* Near constant time lookup for any single reference, even when the
+repository is cold and not in process or kernel cache.
+* Near constant time verification if a SHA-1 is referred to by at least
+one reference (for allow-tip-sha1-in-want).
+* Efficient lookup of an entire namespace, such as `refs/tags/`.

Does this "lookup" refer to "do we have anything in refs/tags/ hierarchy?" or "enumerate all refs under refs/tags/ hierarchy?"

If the latter, perhaps s/lookup of/iteration over/

Documentation/technical/reftable.txt line 93

Quoted 13 lines
Documentation/technical/reftable.txt new file +12 -0
+* Support atomic push with `O(size_of_update)` operations.
+* Combine reflog storage with ref storage for small transactions.
+* Separate reflog storage for base refs and historical logs.
 
+Details
+~~~~~~~
+
+Peeling
+^^^^^^^
+
+References stored in a reftable are peeled, a record for an annotated
+(or signed) tag records both the tag object, and the object it refers
+to.

OK. Peeled results are recorded in packed-refs file because quite often when we use a tag object, what we actually want to access is the commit object it points at. We do so here for the same reason?

Not a rhetorical question, but if it invites a question from a reader, it may deserve to be described before readers ask it.

Documentation/technical/reftable.txt line 99

Quoted 5 lines
Documentation/technical/reftable.txt new file +5 -0
+Reference name encoding
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Reference names are an uninterpreted sequence of bytes that must pass
+linkgit:git-check-ref-format[1] as a valid reference name.

OK. We want to be able to express any reference that we allow in the current backends.

Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+Key unicity
+^^^^^^^^^^^
+
+Each entry must have a unique key; repeated keys are disallowed.

Documentation/technical/reftable.txt line 114

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+Network byte order
+^^^^^^^^^^^^^^^^^^
+
+All multi-byte, fixed width fields are in network byte order.
+
+Ordering
+^^^^^^^^
+
+Blocks are lexicographically ordered by their first reference.

Key and Block are not explained until quite later, so these two among the above three are "Huh?" to readers at this point during their first read, but it probably cannot be helped. Let's read on.

Documentation/technical/reftable.txt line 125

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+Directory/file conflicts
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The reftable format accepts both `refs/heads/foo` and
+`refs/heads/foo/bar` as distinct references.
+
+This property is useful for retaining log records in reftable, but may
+confuse versions of Git using `$GIT_DIR/refs` directory tree to maintain
+references. Users of reftable may choose to continue to reject `foo` and
+`foo/bar` type conflicts to prevent problems for peers.

Here "users" refer to things like "git-core", "jgit", "libgit2", etc.?

Let's say we have these two "conflicting" branches and want to interoperate with existing versions of Git (e.g. a "git ls-remote" client requests us to show what we have). We could either show "refs/heads/foo" with its object name, or "refs/heads/foo/bar" with its object name, but not both.

"users ... may choose" implies that it is up to the implementation of reftable user which one to show, so given a single repository, "jgit" may show "refs/heads/foo" while "libgit2" may choose to show the other one.

I am not sure if that is desirable---I suspect that we want to record which one needs to be chosen so that these "D/F conflicts disallowing" users can make consistent choices, but I dunno.

Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+Block size
+^^^^^^^^^^
+
+The file’s block size is arbitrarily determined by the writer, and does

Documentation/technical/reftable.txt line 177

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+not have to be a power of 2. The block size must be larger than the
+longest reference name or log entry used in the repository, as
+references cannot span blocks.
+
+Powers of two that are friendly to the virtual memory system or
+filesystem (such as 4k or 8k) are recommended. Larger sizes (64k) can
+yield better compression, with a possible increased cost incurred by
+readers during access.
+
+The largest block size is `16777215` bytes (15.99 MiB).

The number being "(2**24) - 1" might be as significant as "15.99 MiB" to readers. As we recommend, and the users would find it natural, to use powers of two, the largest block size in practice would be 8 MiB?

Quoted 21 lines
Documentation/technical/reftable.txt new file +21 -0
+Ref block format
+^^^^^^^^^^^^^^^^
+
+A ref block is written as:
+
+....
+'r'
+uint24( block_len )
+ref_record+
+uint24( restart_offset )+
+uint16( restart_count )
+
+padding?
+....
+
+Blocks begin with `block_type = 'r'` and a 3-byte `block_len` which
+encodes the number of bytes in the block up to, but not including the
+optional `padding`. This is always less than or equal to the file’s
+block size. In the first ref block, `block_len` includes 24 bytes for
+the file header.
+

Documentation/technical/reftable.txt line 258

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+The 2-byte `restart_count` stores the number of entries in the
+`restart_offset` list, which must not be empty. Readers can use
+`restart_count` to binary search between restarts before starting a
+linear scan.
+
+Exactly `restart_count` 3-byte `restart_offset` values precedes the
+`restart_count`. Offsets are relative to the start of the block and
+refer to the first byte of any `ref_record` whose name has not been
+prefix compressed. Entries in the `restart_offset` list must be sorted,
+ascending. Readers can start linear scans from any of these records.

So the algorithm to find a record in a single block would be

- see how big the block_len is by reading the first four bytes;

- look at the last 16-bit word to see how many restart_offset entries there are;

- bisect using restart_offset array to see where to start a linear scan in the ref_record array

- linear scan the range between two adjacent offsets in restart_offset array.

(this is a mental exercise to make sure the information given here is sufficient, which I think it is).

Documentation/technical/reftable.txt line 266

Quoted 7 lines
Documentation/technical/reftable.txt new file +7 -0
+A variable number of `ref_record` fill the middle of the block,
+describing reference names and values. The format is described below.
+
+As the first ref block shares the first file block with the file header,
+all `restart_offset` in the first block are relative to the start of the
+file (position 0), and include the file header. This forces the first
+`restart_offset` to be `28`.

OK.

Quoted 9 lines
Documentation/technical/reftable.txt new file +9 -0
+ref record
+++++++++++
+
+A `ref_record` describes a single reference, storing both the name and
+its value(s). Records are formatted as:
+
+....
+varint( prefix_length )
+varint( (suffix_length << 3) | value_type )

Documentation/technical/reftable.txt line 286

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+suffix
+varint( update_index_delta )
+value?
+....
+
+The `prefix_length` field specifies how many leading bytes of the prior
+reference record’s name should be copied to obtain this reference’s
+name. This must be 0 for the first reference in any block, and also must
+be 0 for any `ref_record` whose offset is listed in the `restart_offset`
+table at the end of the block.

OK. That's quite similar to how v4 index format shortens pathnames. We have encode_varint() helper (in varint.c), that is used from the index v4 code (in read-cache.c) and also untracked cache code (in dir.c).

The OFS_DELTA codepaths, both for encoding (in packfile.c) and for decoding (in builtin/pack-objects.c), uses the same algorithm but open codes it without using helper functions from varint.c (could this become leftoverbit? I dunno).

The document clarifies that the chosen variant of varint representation much later, but it may want to be moved up close to where "we use network byte order" etc. are declared.

Documentation/technical/reftable.txt line 295

Quoted 8 lines
Documentation/technical/reftable.txt new file +8 -0
+Recovering a reference name from any `ref_record` is a simple concat:
+
+....
+this_name = prior_name[0..prefix_length] + suffix
+....
+
+The `suffix_length` value provides the number of bytes available in
+`suffix` to copy from `suffix` to complete the reference name.

It is interesting that suffix is *not* a simple NUL terminated string. This DOES allow a NUL byte in a refname, but because we upfront declared that only the refnames allowed are the ones that pass check-ref-format, that would not be an advantage the use of varint to encode the suffix seeks. Then what is it?

I guess the answer is that a lot of time, the suffix length is shorter than 16 bytes (= 128/8), so we can store 3-bit value_type for free in such a case. Is that worth a mention?

Documentation/technical/reftable.txt line 299

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+The `update_index` that last modified the reference can be obtained by
+adding `update_index_delta` to the `min_update_index` from the file
+header: `min_update_index + update_index_delta`.

At this point, we can infer the following from we have learned so far by reading the document:

- there is a quantity called "update index" attached to each ref record;

- each file knows the range of update indices used in it; and

- when files are chained together, the range of update indices do not overlap---all indices in one file are strictly larger or smaller than those in another file.

But we haven't learned at all what "update index" is and how it is used, which makes it a frustrating read. We probably should give a mention of what it is here (a brief description at the same level of detail as say "monotonically increasing counter that is used as a transaction id when any ref update is made"), even though we will go into much deeper details in a later section.

Documentation/technical/reftable.txt line 307

Quoted 7 lines
Documentation/technical/reftable.txt new file +7 -0
+The `value` follows. Its format is determined by `value_type`, one of
+the following:
+
+* `0x0`: deletion; no value data (see transactions, below)
+* `0x1`: one 20-byte object id; value of the ref
+* `0x2`: two 20-byte object ids; value of the ref, peeled target
+* `0x3`: symbolic reference: `varint( target_len ) target`

We probably should write these as "one (binary) object name", and "two (binary) object names", without hardwiring the number of bytes needed to represent an object name.

Documentation/technical/reftable.txt line 310

Quoted 2 lines
Documentation/technical/reftable.txt new file +2 -0
+Symbolic references use `0x3`, followed by the complete name of the
+reference target. No compression is applied to the target name.

Is there a place in the file format where an incomplete name can be stored? If not, I think it makes it easier to read if we drop "complete" from the sentence.

Quoted 7 lines
Documentation/technical/reftable.txt new file +7 -0
+Types `0x4..0x7` are reserved for future use.
+
+Ref index
+^^^^^^^^^
+
+The ref index stores the name of the last reference from every ref block
+in the file, enabling reduced disk seeks for lookups. Any reference can

Documentation/technical/reftable.txt line 328

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+be found by searching the index, identifying the containing block, and
+searching within that block.
+
+The index may be organized into a multi-level index, where the 1st level
+index block points to additional ref index blocks (2nd level), which may
+in turn point to either additional index blocks (e.g. 3rd level) or ref
+blocks (leaf level). Disk reads required to access a ref go up with
+higher index levels. Multi-level indexes may be required to ensure no
+single index block exceeds the file format’s max block size of
+`16777215` bytes (15.99 MiB). To acheive constant O(1) disk seeks for

achieve

Documentation/technical/reftable.txt line 407

Quoted 10 lines
Documentation/technical/reftable.txt new file +8 -0
+lookups the index must be a single level, which is permitted to exceed
+the file’s configured block size, but not the format’s max block size of
+15.99 MiB.
 
 
+Obj block format
+^^^^^^^^^^^^^^^^
+
+Object blocks are optional. Writers may choose to omit object blocks,
+especially if readers will not use the SHA-1 to ref mapping.

"the object name to ref mapping".

Quoted 1 line

+Object blocks use unique, abbreviated 2-20 byte SHA-1 keys, mapping to

Likewise. "unique prefix of object names no less than 2 bytes" or somesuch to futureproof "2-20 byte SHA-1".

Documentation/technical/reftable.txt line 412

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+ref blocks containing references pointing to that object directly, or as
+the peeled value of an annotated tag. Like ref blocks, object blocks use
+the file’s standard block size. The abbrevation length is available in

abbreviation

Quoted 15 lines
Documentation/technical/reftable.txt new file +15 -0
+the footer as `obj_id_len`.
+
+To save space in small files, object blocks may be omitted if the ref
+index is not present, as brute force search will only need to read a few
+ref blocks. When missing, readers should brute force a linear search of
+all references to lookup by SHA-1.
+
+An object block is written as:
+
+....
+'o'
+uint24( block_len )
+obj_record+
+uint24( restart_offset )+
+uint16( restart_count )

Documentation/technical/reftable.txt line 437

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+padding?
+....
+
+Fields are identical to ref block. Binary search using the restart table
+works the same as in reference blocks.
+
+Because object identifiers are abbreviated by writers to the shortest
+unique abbreviation within the reftable, obj key lengths are variable
+between 2 and 20 bytes. Readers must compare only for common prefix

Futureproof "2 and 20" similarly.

Documentation/technical/reftable.txt line 517

Quoted 11 lines
Documentation/technical/reftable.txt new file +10 -0
+match within an obj block or obj index.
 
+Log block format
+^^^^^^^^^^^^^^^^
+
+Unlike ref and obj blocks, log blocks are always unaligned.
+
+Log blocks are variable in size, and do not match the `block_size`
+specified in the file header or footer. Writers should choose an
+appropriate buffer size to prepare a log block for deflation, such as
+`2 * block_size`.

I can guess the reason behind this design decision, but the readers may not be able to. Should we write it down here, or would it make too much irrelevant details?

Quoted 37 lines
Documentation/technical/reftable.txt new file +37 -0
+A log block is written as:
+
+....
+'g'
+uint24( block_len )
+zlib_deflate {
+  log_record+
+  uint24( restart_offset )+
+  uint16( restart_count )
+}
+....
+
+Log blocks look similar to ref blocks, except `block_type = 'g'`.
+
+The 4-byte block header is followed by the deflated block contents using
+zlib deflate. The `block_len` in the header is the inflated size
+(including 4-byte block header), and should be used by readers to
+preallocate the inflation output buffer. A log block’s `block_len` may
+exceed the file’s block size.
+
+Offsets within the log block (e.g. `restart_offset`) still include the
+4-byte header. Readers may prefer prefixing the inflation output buffer
+with the 4-byte header.
+
+Within the deflate container, a variable number of `log_record` describe
+reference changes. The log record format is described below. See ref
+block format (above) for a description of `restart_offset` and
+`restart_count`.
+
+Because log blocks have no alignment or padding between blocks, readers
+must keep track of the bytes consumed by the inflater to know where the
+next log block begins.
+
+log record
+++++++++++
+
+Log record keys are structured as:

Documentation/technical/reftable.txt line 565

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+....
+ref_name '\0' reverse_int64( update_index )
+....
+
+where `update_index` is the unique transaction identifier. The
+`update_index` field must be unique within the scope of a `ref_name`.
+See the update transactions section below for further details.
+
+The `reverse_int64` function inverses the value so lexographical

lexicographical

Documentation/technical/reftable.txt line 573

Quoted 8 lines
Documentation/technical/reftable.txt new file +8 -0
+ordering the network byte order encoding sorts the more recent records
+with higher `update_index` values first:
+
+....
+reverse_int64(int64 t) {
+  return 0xffffffffffffffff - t;
+}
+....

Rationale? It may be to ease the iteration over reflog i.e. "log -g" that wants to go from the youngest to older---isn't it worth mentioning?

Quoted 23 lines
Documentation/technical/reftable.txt new file +23 -0
+Log records have a similar starting structure to ref and index records,
+utilizing the same prefix compression scheme applied to the log record
+key described above.
+
+....
+    varint( prefix_length )
+    varint( (suffix_length << 3) | log_type )
+    suffix
+    log_data {
+      old_id
+      new_id
+      varint( name_length    )  name
+      varint( email_length   )  email
+      varint( time_seconds )
+      sint16( tz_offset )
+      varint( message_length )  message
+    }?
+....
+
+Log record entries use `log_type` to indicate what follows:
+
+* `0x0`: deletion; no log data.
+* `0x1`: standard git reflog data using `log_data` above.

Documentation/technical/reftable.txt line 607

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+The `log_type = 0x0` is mostly useful for `git stash drop`, removing an
+entry from the reflog of `refs/stash` in a transaction file (below),
+without needing to rewrite larger files. Readers reading a stack of
+reflogs must treat this as a deletion.
+
+For `log_type = 0x1`, the `log_data` section follows
+linkgit:git-update-ref[1] logging and includes:
+
+* two 20-byte SHA-1s (old id, new id)

"two (binary) object names (old name, new name)" for futureproof out of SHA-1 world.

Documentation/technical/reftable.txt line 611

Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+* varint string of committer’s name
+* varint string of committer’s email
+* varint time in seconds since epoch (Jan 1, 1970)
+* 2-byte timezone offset in minutes (signed)

We use minus eight hundred for "GMT-0800" internally, but this would use -480, which makes more sense ;-)

Documentation/technical/reftable.txt line 619

Quoted 8 lines
Documentation/technical/reftable.txt new file +8 -0
+* varint string of message
+
+`tz_offset` is the absolute number of minutes from GMT the committer was
+at the time of the update. For example `GMT-0800` is encoded in reftable
+as `sint16(-480)` and `GMT+0230` is `sint16(150)`.
+
+The committer email does not contain `<` or `>`, it’s the value normally
+found between the `<>` in a git commit object header.

Saving two precious bytes?

This is a tangent but in a repository at hosting provider, whose primary (and often the only) source of updates are by end-user pushing into it, if reflogs are enabled, whose name and email are recorded in the logs? The committer or tagger of the object that sits at the tip of the ref after the update? What happens when a blob is pushed to update a ref? Or would it be just a single "user" that represents the "server operator"?

We know in a non-bare repository an individual contributor works on typically records only one <name, email> in the reflog: the user who works in it.

What I am trying to get at is if it makes more sense to have a small table of unique <name, email> pairs used in the file and have log_data record a single varint that is the index into that "committer ident" table. I would suspect that it would give us significantly more gain than mere <> two bytes per log_data entry.

Documentation/technical/reftable.txt line 625

Quoted 5 lines
Documentation/technical/reftable.txt new file +5 -0
+The `message_length` may be 0, in which case there was no message
+supplied for the update.
+
+Contrary to traditional reflog (which is a file), renames are encoded as
+a combination of ref deletion and ref creation.

Yay? How does the deletion record look like? The new object name being 0*hashlength? I didn't see it defined in the description (and I am guessing that log_type of 0x0 is *NOT* used for that purpose).

So, NEEDSWORK: describe how "creation of a ref" and "deletion of a ref" appears in a log as a log record entry.

Quoted 21 lines
Documentation/technical/reftable.txt new file +21 -0
+Footer
+^^^^^^
+
+After the last block of the file, a file footer is written. It begins
+like the file header, but is extended with additional data.
+
+A 68-byte footer appears at the end:
+
+....
+    'REFT'
+    uint8( version_number = 1 )
+    uint24( block_size )
+    uint64( min_update_index )
+    uint64( max_update_index )
+
+    uint64( ref_index_position )
+    uint64( (obj_position << 5) | obj_id_len )
+    uint64( obj_index_position )
+
+    uint64( log_position )
+    uint64( log_index_position )

Documentation/technical/reftable.txt line 698

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+    uint32( CRC-32 of above )
+....
+
+If a section is missing (e.g. ref index) the corresponding position
+field (e.g. `ref_index_position`) will be 0.
+
+* `obj_position`: byte position for the first obj block.
+* `obj_id_len`: number of bytes used to abbreviate object identifiers in
+obj blocks.

Should we write "this can be up to 31" somewhere? It is more than enough for SHA-1 and not quite sufficient for SHA-256 (unless we say "we store obj_id_len-1 here")?

Quoted 11 lines
Documentation/technical/reftable.txt new file +9 -0
+* `log_position`: byte position for the first log block.
+* `ref_index_position`: byte position for the start of the ref index.
+* `obj_index_position`: byte position for the start of the obj index.
+* `log_index_position`: byte position for the start of the log index.
 
 
+Varint encoding
+^^^^^^^^^^^^^^^
+
+Varint encoding is identical to the ofs-delta encoding method used
+within pack files.

Documentation/technical/reftable.txt line 732

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+Decoder works such as:
+
+....
+val = buf[ptr] & 0x7f
+while (buf[ptr] & 0x80) {
+  ptr++
+  val = ((val + 1) << 7) | (buf[ptr] & 0x7f)
+}
+....

As already said, I think this should be given upfront next to where we declare that we use network byte order.

Documentation/technical/reftable.txt line 763

Quoted 13 lines
Documentation/technical/reftable.txt new file +13 -0
+Restart point selection
++++++++++++++++++++++++
+
+Writers determine the restart points at file creation. The process is
+arbitrary, but every 16 or 64 records is recommended. Every 16 may be
+more suitable for smaller block sizes (4k or 8k), every 64 for larger
+block sizes (64k).
+
+More frequent restart points reduces prefix compression and increases
+space consumed by the restart table, both of which increase file size.
+
+Less frequent restart points makes prefix compression more effective,
+decreasing overall file size, with increased penalities for readers

penalties

Quoted 5 lines
Documentation/technical/reftable.txt new file +3 -0
+walking through more records after the binary search step.
+
+A maximum of `65535` restart points per block is supported.
 
 

Documentation/technical/reftable.txt line 1052

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+LMDB
+^^^^
+
+David Turner proposed
+https://public-inbox.org/git/[email protected]/[using
+LMDB], as LMDB is lightweight (64k of runtime code) and GPL-compatible
+license.
+
+A downside of LMDB is its reliance on a single C implementation. This
+makes embedding inside JGit (a popular reimplemenation of Git)

reimplementation

Quoted 15 lines
Documentation/technical/reftable.txt new file +15 -0
+difficult, and hoisting onto virtual storage (for JGit DFS) virtually
+impossible.
+
+A common format that can be supported by all major Git implementations
+(git-core, JGit, libgit2) is strongly preferred.
+
+Future
+~~~~~~
+
+Longer hashes
+^^^^^^^^^^^^^
+
+Version will bump (e.g. 2) to indicate `value` uses a different object
+id length other than 20. The length could be stored in an expanded file
+header, or hardcoded as part of the version.
#232 lore raw

Re: [PATCH v13 05/13] reftable: clarify how empty tables should be written

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 5 lines · Han-Wen Nienhuys via GitGitGadget

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

The format allows for some ambiguity, as a lone footer also starts with a valid file header. However, the current JGit code will barf on this. This commit codifies this behavior into the standard.

Nice to see the documentation being careful.

Quoted 23 lines
---
 Documentation/technical/reftable.txt | 7 +++++++
 1 file changed, 7 insertions(+)
Documentation/technical/reftable.txt +7 -0
index 8bad9ade256..6223538d64e 100644
@@ -715,6 +715,13 @@ version)
 
 Once verified, the other fields of the footer can be accessed.
 
+Empty tables
+++++++++++++
+
+A reftable may be empty. In this case, the file starts with a header
+and is immediately followed by a footer.
+
+
 Varint encoding
 ^^^^^^^^^^^^^^^
#234 lore raw

Re: [PATCH v13 06/13] reftable: define version 2 of the spec to accomodate SHA256

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 16 lines · Han-Wen Nienhuys via GitGitGadget

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

Version appends a hash ID to the file header, making it slightly larger.

This commit also changes "SHA-1" into "object ID" in many places.

---
 Documentation/technical/reftable.txt | 79 ++++++++++++++++------------
 1 file changed, 44 insertions(+), 35 deletions(-)
Documentation/technical/reftable.txt +0 -0
index 6223538d64e..1464c4e7437 100644

Documentation/technical/reftable.txt Objectives · line 33

Quoted 6 lines
Documentation/technical/reftable.txt +1 -1
@@ -29,7 +29,7 @@ Objectives
 
 * Near constant time lookup for any single reference, even when the
 repository is cold and not in process or kernel cache.
-* Near constant time verification if a SHA-1 is referred to by at least
+* Near constant time verification if an object ID is referred to by at least
 one reference (for allow-tip-sha1-in-want).

Good. These are called "object names", though.

Quoted 40 lines
Documentation/technical/reftable.txt +23 -2
@@ -193,8 +193,8 @@ and non-aligned files.
 Very small files (e.g. a single ref block) may omit `padding` and the ref
 index to reduce total file size.
 
-Header
-^^^^^^
+Header (version 1)
+^^^^^^^^^^^^^^^^^^
 
 A 24-byte header appears at the beginning of the file:
 
@@ -215,6 +215,27 @@ used in a stack for link:#Update-transactions[transactions], these
 fields can order the files such that the prior file’s
 `max_update_index + 1` is the next file’s `min_update_index`.
 
+Header (version 2)
+^^^^^^^^^^^^^^^^^^
+
+A 28-byte header appears at the beginning of the file:
+
+....
+'REFT'
+uint8( version_number = 2 )
+uint24( block_size )
+uint64( min_update_index )
+uint64( max_update_index )
+uint32( hash_id )
+....
+
+The header is identical to `version_number=1`, with the 4-byte hash ID
+("sha1" for SHA1 and "s256" for SHA-256) append to the header.
+
+For maximum backward compatibility, it is recommended to use version 1 when
+writing SHA1 reftables.
+
+
 First ref block
 ^^^^^^^^^^^^^^^
 

Documentation/technical/reftable.txt The `value` follows. Its format is determined by `value_type`, one of · line 327

Quoted 7 lines
Documentation/technical/reftable.txt +2 -2
@@ -302,8 +323,8 @@ The `value` follows. Its format is determined by `value_type`, one of
 the following:
 
 * `0x0`: deletion; no value data (see transactions, below)
-* `0x1`: one 20-byte object id; value of the ref
-* `0x2`: two 20-byte object ids; value of the ref, peeled target
+* `0x1`: one object id; value of the ref
+* `0x2`: two object ids; value of the ref, peeled target

Ah, OK, I pointed out these future-proofing for the previous step, but as long as the end result is written in a hash-algorithm agnostic way, it is OK. Again these are called "object names", though.

Documentation/technical/reftable.txt works the same as in reference blocks. · line 458

Quoted 8 lines
Documentation/technical/reftable.txt +1 -1
 * `0x3`: symbolic reference: `varint( target_len ) target`
 
@@ -434,7 +455,7 @@ works the same as in reference blocks.
 
 Because object identifiers are abbreviated by writers to the shortest
 unique abbreviation within the reftable, obj key lengths are variable
-between 2 and 20 bytes. Readers must compare only for common prefix
+between 2 and 32 bytes. Readers must compare only for common prefix

Is it allowed for a reftable file whose hash_id field says "sha1" to use more than 20 bytes of obj key? Phrasing it like "unique prefix of object name, no shorter than 2 bytes" would avoid the problem, I would think.

This version also adds more

’

apostrophes, where we would prefer to place vanilla single quotes, which may need to be corrected in the conversion toolchain.

I did not see any new typo introduced in this step.

Thanks.

#235 lore raw

Re: [PATCH v13 06/13] reftable: define version 2 of the spec to accomodate SHA256

Quoted 6 lines · Junio C Hamano

Documentation/technical/reftable.txt Objectives · line 33

Quoted 3 lines
Documentation/technical/reftable.txt +1 -1
-* Near constant time verification if a SHA-1 is referred to by at least
+* Near constant time verification if an object ID is referred to by at least
 one reference (for allow-tip-sha1-in-want).

Good. These are called "object names", though.

Fixed throughout.

All of the source code (libgit2, Jgit, git itself) calls this object ID (git_oid, ObjectId, struct object_id respectively), so it's a bit surprising.

Quoted 4 lines
Quoted 4 lines

* 0x3: symbolic reference: varint( target_len ) target

(quoted diff) +0 -0
@@ -434,7 +455,7 @@ works the same as in reference blocks.
 
Quoted 9 lines

Documentation/technical/reftable.txt works the same as in reference blocks. · line 458

Quoted 4 lines
Documentation/technical/reftable.txt +1 -1
 Because object identifiers are abbreviated by writers to the shortest
 unique abbreviation within the reftable, obj key lengths are variable
-between 2 and 20 bytes. Readers must compare only for common prefix
+between 2 and 32 bytes. Readers must compare only for common prefix

Is it allowed for a reftable file whose hash_id field says "sha1" to use more than 20 bytes of obj key? Phrasing it like "unique prefix of object name, no shorter than 2 bytes" would avoid the problem, I would think.

rephrased.

Quoted 4 lines

This version also adds more

’

curious. I have no idea where they came from. Fixed now.

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

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

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

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

Re: [PATCH v13 06/13] reftable: define version 2 of the spec to accomodate SHA256

Quoted 6 lines · Han-Wen Nienhuys
Quoted 4 lines

This version also adds more

’

curious. I have no idea where they came from. Fixed now.

The version 1 format in 04/13 has the same; it may already be in the original document, or the conversion process via pandoc transformed a plain single quote intelligently?

#226 lore raw

Re: [PATCH v13 04/13] reftable: file format documentation

In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano
Quoted 7 lines

Some repositories contain a lot of references (e.g. android at 866k, rails at 31k). The reftable format provides:

- Near constant time lookup for any single reference, even when the repository is cold and not in process or kernel cache.
- Near constant time verification a SHA-1 is referred to by at least one reference (for allow-tip-sha1-in-want).

Not quite grammatical sentence? Perhaps "if" after "verification?

Done.

Quoted 20 lines
Quoted 20 lines

- Efficient lookup of an entire namespace, such as refs/tags/.
- Support atomic push O(size_of_update) operations.
- Combine reflog storage with ref storage.

This file format spec was originally written in July, 2017 by Shawn Pearce. Some refinements since then were made by Shawn and by Han-Wen Nienhuys based on experiences implementing and experimenting with the format. (All of this was in the context of our work at Google and Google is happy to contribute the result to the Git project.)

Imported from JGit[1]'s current version (c217d33ff, "Documentation/technical/reftable: improve repo layout", 2020-02-04) of Documentation/technical/reftable.md and converted to asciidoc by running

pandoc -t asciidoc -f markdown reftable.md >reftable.txt

using pandoc 2.2.1. The result required the following additional minor changes:

Quoted 8 lines
Quoted 4 lines

- removed the [TOC] directive to add a table of contents, since asciidoc does not support it
- replaced git-scm.com/docs links with linkgit: directives that link to other pages within Git's documentation

There are many

’

fixed.

Quoted 2 lines

Typoes pointed out below may probably be from the original where they should be corrected.

fixed.

Quoted 2 lines

Let's not use &nbsp; here after "e.g.". I see a normal space after "e.g." a few lines below.

fixed.

Quoted 7 lines

Documentation/technical/reftable.txt line 17

Quoted 7 lines
Documentation/technical/reftable.txt new file +7 -0
+rails at 31k). The existing packed-refs format takes up a lot of space
+(e.g. 62M), and does not scale with additional references. Lookup of a
+single reference requires linearly scanning the file.
+
+Atomic pushes modifying multiple references require copying the entire
+packed-refs file, which can be a considerable amount of data moved
+(e.g. 62M in, 62M out) for even small transactions (2 refs modified).

(*)

Quoted 4 lines
Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+Repositories with many loose references occupy a large number of disk
+blocks from the local file system, as each reference is its own file
+storing 41 bytes (and another file for the corresponding reflog). This
+negatively affects the number of inodes available when a large number of
Quoted 8 lines

Documentation/technical/reftable.txt line 25

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+repositories are stored on the same filesystem. Readers can be penalized
+due to the larger number of syscalls required to traverse and read the
+`$GIT_DIR/refs` directory.

Another downside is that we cannot arrange atomic updates to multiple refs over loose refs, even though the "lookup of a single reference does not require linear scan" unlike packed-refs, (as long as the filesystem does its job). Worth mentioning?

that's already there at (*), no?

Quoted 10 lines

Documentation/technical/reftable.txt line 34

Quoted 5 lines
Documentation/technical/reftable.txt new file +5 -0
+* Near constant time lookup for any single reference, even when the
+repository is cold and not in process or kernel cache.
+* Near constant time verification if a SHA-1 is referred to by at least
+one reference (for allow-tip-sha1-in-want).
+* Efficient lookup of an entire namespace, such as `refs/tags/`.

Does this "lookup" refer to "do we have anything in refs/tags/ hierarchy?" or "enumerate all refs under refs/tags/ hierarchy?"

If the latter, perhaps s/lookup of/iteration over/

changed to "efficient enumeration".

Quoted 10 lines

Documentation/technical/reftable.txt line 93

Quoted 6 lines
Documentation/technical/reftable.txt new file +6 -0
+Peeling
+^^^^^^^
+
+References stored in a reftable are peeled, a record for an annotated
+(or signed) tag records both the tag object, and the object it refers
+to.

OK. Peeled results are recorded in packed-refs file because quite often when we use a tag object, what we actually want to access is the commit object it points at. We do so here for the same reason?

I think so. Added a reference to packed-refs

Quoted 19 lines

Documentation/technical/reftable.txt line 125

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+Directory/file conflicts
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The reftable format accepts both `refs/heads/foo` and
+`refs/heads/foo/bar` as distinct references.
+
+This property is useful for retaining log records in reftable, but may
+confuse versions of Git using `$GIT_DIR/refs` directory tree to maintain
+references. Users of reftable may choose to continue to reject `foo` and
+`foo/bar` type conflicts to prevent problems for peers.

Here "users" refer to things like "git-core", "jgit", "libgit2", etc.?

Let's say we have these two "conflicting" branches and want to interoperate with existing versions of Git (e.g. a "git ls-remote" client requests us to show what we have). We could either show "refs/heads/foo" with its object name, or "refs/heads/foo/bar" with its object name, but not both.

Quoted 8 lines

"users ... may choose" implies that it is up to the implementation of reftable user which one to show, so given a single repository, "jgit" may show "refs/heads/foo" while "libgit2" may choose to show the other one.

I am not sure if that is desirable---I suspect that we want to record which one needs to be chosen so that these "D/F conflicts disallowing" users can make consistent choices, but I dunno.

I think it's beyond the scope of the document. In reftable the library, I added an option to validate for file/dir conflicts.

(Google's deployment doesn't do the validation completely correctly. We had a ref ending in '/' for example.)

Quoted 12 lines
Quoted 12 lines
Documentation/technical/reftable.txt new file +11 -0
+Block size
+^^^^^^^^^^
+
+The file’s block size is arbitrarily determined by the writer, and does
+not have to be a power of 2. The block size must be larger than the
+longest reference name or log entry used in the repository, as
+references cannot span blocks.
+
+Powers of two that are friendly to the virtual memory system or
+filesystem (such as 4k or 8k) are recommended. Larger sizes (64k) can
+yield better compression, with a possible increased cost incurred by
Quoted 8 lines

Documentation/technical/reftable.txt line 177

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+readers during access.
+
+The largest block size is `16777215` bytes (15.99 MiB).

The number being "(2**24) - 1" might be as significant as "15.99 MiB" to readers. As we recommend, and the users would find it natural, to use powers of two, the largest block size in practice would be 8 MiB?

Beyond 4096 bytes (size of a page on x86), I don't think there is a need for powers of two. What makes for a good block size would also be decided by the characteristics of the underlying storage system. At google, we use 64k, IIRC.

Quoted 36 lines
Quoted 21 lines
Documentation/technical/reftable.txt new file +21 -0
+Ref block format
+^^^^^^^^^^^^^^^^
+
+A ref block is written as:
+
+....
+'r'
+uint24( block_len )
+ref_record+
+uint24( restart_offset )+
+uint16( restart_count )
+
+padding?
+....
+
+Blocks begin with `block_type = 'r'` and a 3-byte `block_len` which
+encodes the number of bytes in the block up to, but not including the
+optional `padding`. This is always less than or equal to the file’s
+block size. In the first ref block, `block_len` includes 24 bytes for
+the file header.
+

Documentation/technical/reftable.txt line 258

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+The 2-byte `restart_count` stores the number of entries in the
+`restart_offset` list, which must not be empty. Readers can use
+`restart_count` to binary search between restarts before starting a
+linear scan.
+
+Exactly `restart_count` 3-byte `restart_offset` values precedes the
+`restart_count`. Offsets are relative to the start of the block and
+refer to the first byte of any `ref_record` whose name has not been
+prefix compressed. Entries in the `restart_offset` list must be sorted,
+ascending. Readers can start linear scans from any of these records.

So the algorithm to find a record in a single block would be

- see how big the block_len is by reading the first four bytes;

Quoted 8 lines

- look at the last 16-bit word to see how many restart_offset entries there are;

- bisect using restart_offset array to see where to start a linear scan in the ref_record array

- linear scan the range between two adjacent offsets in restart_offset array.

yes, that works. In practice, you find the last restart before the key, and then iterate until you find a key that is >= the wanted key. There is no real need to find the next restart.

Quoted 3 lines

The document clarifies that the chosen variant of varint representation much later, but it may want to be moved up close to where "we use network byte order" etc. are declared.

moved.

Quoted 6 lines
Quoted 6 lines
Documentation/technical/reftable.txt new file +6 -0
+Recovering a reference name from any `ref_record` is a simple concat:
+
+....
+this_name = prior_name[0..prefix_length] + suffix
+....
+
Quoted 8 lines
Quoted 2 lines

+The suffix_length value provides the number of bytes available in +suffix to copy from suffix to complete the reference name.

It is interesting that suffix is *not* a simple NUL terminated string. This DOES allow a NUL byte in a refname, but because we upfront declared that only the refnames allowed are the ones that pass check-ref-format, that would not be an advantage the use of varint to encode the suffix seeks. Then what is it?

reflog records encode update_index in the key, which may lead to keys with zero bytes. So you can't rely on zero termination.

Quoted 3 lines

I guess the answer is that a lot of time, the suffix length is shorter than 16 bytes (= 128/8), so we can store 3-bit value_type for free in such a case. Is that worth a mention?

Here, and in other places, I would like to avoid annotating rationales in detail. First of all, this was Shawn's spec; if I go and rewrite whole sections, the authorship becomes muddled. Second, a lot of the "why" will invite questions how to do it differently to achieve the same objectives (see below, where you muse about '<' and '>'), and that's a past station given that it's in JGit. Finally, all these edits cause extra work with conflict resolution in follow-up changes in the series. If we want to editorialize more, I'd rather do it in a follow up series.

Quoted 13 lines

Documentation/technical/reftable.txt line 299

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+The `update_index` that last modified the reference can be obtained by
+adding `update_index_delta` to the `min_update_index` from the file
+header: `min_update_index + update_index_delta`.

At this point, we can infer the following from we have learned so far by reading the document:

- there is a quantity called "update index" attached to each ref record;

- each file knows the range of update indices used in it; and

Quoted 6 lines

But we haven't learned at all what "update index" is and how it is used, which makes it a frustrating read. We probably should give a mention of what it is here (a brief description at the same level of detail as say "monotonically increasing counter that is used as a transaction id when any ref update is made"), even though we will go into much deeper details in a later section.

I think update_index is poorly named. I think it's supposed to be the index into an ordered array of updates.

A better name would be 'logical timestamp'. It is a timestamp, because we use it to determine which update came first, but it is 'logical' because it can't depend on actual clocks, because of synchronization problems. Inside Google, I think we use microsecond Spanner timestamps of the database transaction on the global ref database.

In a local setting, it's good enough to use a sequential number, which is determined by incrementing the last update_index.

Quoted 3 lines

- when files are chained together, the range of update indices do not overlap---all indices in one file are strictly larger or smaller than those in another file.

That's what I thought initially too , but it is a dangerous assumption. See reftable/merged.c, merged_iter_next_entry() for a comment.

At Google, in the datacenter deployment, update indices of different reftable can overlap. This can happen when compaction and replication delay interact.

Quoted 3 lines

We probably should write these as "one (binary) object name", and "two (binary) object names", without hardwiring the number of bytes needed to represent an object name.

This is addressed in the follow-up change for SHA256. Let's leave this alone.

Quoted 6 lines

Documentation/technical/reftable.txt line 310

Quoted 2 lines
Documentation/technical/reftable.txt new file +2 -0
+Symbolic references use `0x3`, followed by the complete name of the
+reference target. No compression is applied to the target name.

Is there a place in the file format where an incomplete name can be stored? If not, I think it makes it easier to read if we drop "complete" from the sentence.

All of the keys prefix compressed, so most refnames are incomplete.

Quoted 74 lines
Quoted 7 lines
Documentation/technical/reftable.txt new file +7 -0
+Types `0x4..0x7` are reserved for future use.
+
+Ref index
+^^^^^^^^^
+
+The ref index stores the name of the last reference from every ref block
+in the file, enabling reduced disk seeks for lookups. Any reference can

Documentation/technical/reftable.txt line 328

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+be found by searching the index, identifying the containing block, and
+searching within that block.
+
+The index may be organized into a multi-level index, where the 1st level
+index block points to additional ref index blocks (2nd level), which may
+in turn point to either additional index blocks (e.g. 3rd level) or ref
+blocks (leaf level). Disk reads required to access a ref go up with
+higher index levels. Multi-level indexes may be required to ensure no
+single index block exceeds the file format’s max block size of
+`16777215` bytes (15.99 MiB). To acheive constant O(1) disk seeks for

achieve

Documentation/technical/reftable.txt line 407

Quoted 10 lines
Documentation/technical/reftable.txt new file +8 -0
+lookups the index must be a single level, which is permitted to exceed
+the file’s configured block size, but not the format’s max block size of
+15.99 MiB.
 
 
+Obj block format
+^^^^^^^^^^^^^^^^
+
+Object blocks are optional. Writers may choose to omit object blocks,
+especially if readers will not use the SHA-1 to ref mapping.

"the object name to ref mapping".

Quoted 1 line

+Object blocks use unique, abbreviated 2-20 byte SHA-1 keys, mapping to

Likewise. "unique prefix of object names no less than 2 bytes" or somesuch to futureproof "2-20 byte SHA-1".

Documentation/technical/reftable.txt line 412

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+ref blocks containing references pointing to that object directly, or as
+the peeled value of an annotated tag. Like ref blocks, object blocks use
+the file’s standard block size. The abbrevation length is available in

abbreviation

Quoted 15 lines
Documentation/technical/reftable.txt new file +15 -0
+the footer as `obj_id_len`.
+
+To save space in small files, object blocks may be omitted if the ref
+index is not present, as brute force search will only need to read a few
+ref blocks. When missing, readers should brute force a linear search of
+all references to lookup by SHA-1.
+
+An object block is written as:
+
+....
+'o'
+uint24( block_len )
+obj_record+
+uint24( restart_offset )+
+uint16( restart_count )

Documentation/technical/reftable.txt line 437

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+padding?
+....
+
+Fields are identical to ref block. Binary search using the restart table
+works the same as in reference blocks.
+
+Because object identifiers are abbreviated by writers to the shortest
+unique abbreviation within the reftable, obj key lengths are variable
+between 2 and 20 bytes. Readers must compare only for common prefix

Futureproof "2 and 20" similarly.

Quoted 1 line

+match within an obj block or obj index.

Quoted 13 lines

Documentation/technical/reftable.txt line 517

Quoted 9 lines
Documentation/technical/reftable.txt new file +9 -0
+Log block format
+^^^^^^^^^^^^^^^^
+
+Unlike ref and obj blocks, log blocks are always unaligned.
+
+Log blocks are variable in size, and do not match the `block_size`
+specified in the file header or footer. Writers should choose an
+appropriate buffer size to prepare a log block for deflation, such as
+`2 * block_size`.

I can guess the reason behind this design decision, but the readers may not be able to. Should we write it down here, or would it make too much irrelevant details?

I honestly don't know about the reason. reflogs will compress well (because there is lots of ascii text), but it makes the format needlessly complex IMO. But it's there, so we can't do much about it now.

Quoted 108 lines
Quoted 37 lines
Documentation/technical/reftable.txt new file +37 -0
+A log block is written as:
+
+....
+'g'
+uint24( block_len )
+zlib_deflate {
+  log_record+
+  uint24( restart_offset )+
+  uint16( restart_count )
+}
+....
+
+Log blocks look similar to ref blocks, except `block_type = 'g'`.
+
+The 4-byte block header is followed by the deflated block contents using
+zlib deflate. The `block_len` in the header is the inflated size
+(including 4-byte block header), and should be used by readers to
+preallocate the inflation output buffer. A log block’s `block_len` may
+exceed the file’s block size.
+
+Offsets within the log block (e.g. `restart_offset`) still include the
+4-byte header. Readers may prefer prefixing the inflation output buffer
+with the 4-byte header.
+
+Within the deflate container, a variable number of `log_record` describe
+reference changes. The log record format is described below. See ref
+block format (above) for a description of `restart_offset` and
+`restart_count`.
+
+Because log blocks have no alignment or padding between blocks, readers
+must keep track of the bytes consumed by the inflater to know where the
+next log block begins.
+
+log record
+++++++++++
+
+Log record keys are structured as:

Documentation/technical/reftable.txt line 565

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+....
+ref_name '\0' reverse_int64( update_index )
+....
+
+where `update_index` is the unique transaction identifier. The
+`update_index` field must be unique within the scope of a `ref_name`.
+See the update transactions section below for further details.
+
+The `reverse_int64` function inverses the value so lexographical

lexicographical

Documentation/technical/reftable.txt line 573

Quoted 8 lines
Documentation/technical/reftable.txt new file +8 -0
+ordering the network byte order encoding sorts the more recent records
+with higher `update_index` values first:
+
+....
+reverse_int64(int64 t) {
+  return 0xffffffffffffffff - t;
+}
+....

Rationale? It may be to ease the iteration over reflog i.e. "log -g" that wants to go from the youngest to older---isn't it worth mentioning?

Quoted 23 lines
Documentation/technical/reftable.txt new file +23 -0
+Log records have a similar starting structure to ref and index records,
+utilizing the same prefix compression scheme applied to the log record
+key described above.
+
+....
+    varint( prefix_length )
+    varint( (suffix_length << 3) | log_type )
+    suffix
+    log_data {
+      old_id
+      new_id
+      varint( name_length    )  name
+      varint( email_length   )  email
+      varint( time_seconds )
+      sint16( tz_offset )
+      varint( message_length )  message
+    }?
+....
+
+Log record entries use `log_type` to indicate what follows:
+
+* `0x0`: deletion; no log data.
+* `0x1`: standard git reflog data using `log_data` above.

Documentation/technical/reftable.txt line 607

Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+
+The `log_type = 0x0` is mostly useful for `git stash drop`, removing an
+entry from the reflog of `refs/stash` in a transaction file (below),
+without needing to rewrite larger files. Readers reading a stack of
+reflogs must treat this as a deletion.
+
+For `log_type = 0x1`, the `log_data` section follows
+linkgit:git-update-ref[1] logging and includes:
+
+* two 20-byte SHA-1s (old id, new id)

"two (binary) object names (old name, new name)" for futureproof out of SHA-1 world.

Documentation/technical/reftable.txt line 611

Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+* varint string of committer’s name
+* varint string of committer’s email
+* varint time in seconds since epoch (Jan 1, 1970)
+* 2-byte timezone offset in minutes (signed)

We use minus eight hundred for "GMT-0800" internally, but this would use -480, which makes more sense ;-)

Quoted 10 lines

Documentation/technical/reftable.txt line 619

Quoted 8 lines
Documentation/technical/reftable.txt new file +8 -0
+* varint string of message
+
+`tz_offset` is the absolute number of minutes from GMT the committer was
+at the time of the update. For example `GMT-0800` is encoded in reftable
+as `sint16(-480)` and `GMT+0230` is `sint16(150)`.
+
+The committer email does not contain `<` or `>`, it’s the value normally
+found between the `<>` in a git commit object header.

Saving two precious bytes?

If you store reflogs in ascii files, both for convention, and to separate the name from the email. Internally, it's much more convenient to not have the '<' '>'. For any practical application of the email address (Say, you want to send out an email to the user), you'll need to strip off the < >

Quoted 7 lines

This is a tangent but in a repository at hosting provider, whose primary (and often the only) source of updates are by end-user pushing into it, if reflogs are enabled, whose name and email are recorded in the logs? The committer or tagger of the object that sits at the tip of the ref after the update? What happens when a blob is pushed to update a ref? Or would it be just a single "user" that represents the "server operator"?

That's up to the caller to decide. I think JGit uses the committer identity, but I'd have to check.

Quoted 9 lines

We know in a non-bare repository an individual contributor works on typically records only one <name, email> in the reflog: the user who works in it.

What I am trying to get at is if it makes more sense to have a small table of unique <name, email> pairs used in the file and have log_data record a single varint that is the index into that "committer ident" table. I would suspect that it would give us significantly more gain than mere <> two bytes per log_data entry.

gain in what dimension? Space-wise, the zlib compression will remove all slack anyway.

Quoted 9 lines

Documentation/technical/reftable.txt line 625

Quoted 5 lines
Documentation/technical/reftable.txt new file +5 -0
+The `message_length` may be 0, in which case there was no message
+supplied for the update.
+
+Contrary to traditional reflog (which is a file), renames are encoded as
+a combination of ref deletion and ref creation.

Yay? How does the deletion record look like? The new object name being 0*hashlength? I didn't see it defined in the description (and I am guessing that log_type of 0x0 is *NOT* used for that purpose).

quoting the spec:

"Log record entries use log_type to indicate what follows:

* 0x0: deletion; no log data."

Quoted 2 lines

So, NEEDSWORK: describe how "creation of a ref" and "deletion of a ref" appears in a log as a log record entry.

the creation would be the appearance of a reflog record for the ref. You'd have to search back in time to decide if a reflog record it was an update to an existing record or a creation.

Quoted 4 lines
Quoted 1 line

+....

As already said, I think this should be given upfront next to where we declare that we use network byte order.

I moved this

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

#227 lore raw

Re: [PATCH v13 04/13] reftable: file format documentation

Quoted 14 lines · Han-Wen Nienhuys
Quoted 9 lines

Documentation/technical/reftable.txt line 625

Quoted 5 lines
Documentation/technical/reftable.txt new file +5 -0
+The `message_length` may be 0, in which case there was no message
+supplied for the update.
+
+Contrary to traditional reflog (which is a file), renames are encoded as
+a combination of ref deletion and ref creation.

Yay? How does the deletion record look like? The new object name being 0*hashlength? I didn't see it defined in the description (and I am guessing that log_type of 0x0 is *NOT* used for that purpose).

quoting the spec:

"Log record entries use log_type to indicate what follows:

Quoted 8 lines

* 0x0: deletion; no log data."

Quoted 2 lines

So, NEEDSWORK: describe how "creation of a ref" and "deletion of a ref" appears in a log as a log record entry.

the creation would be the appearance of a reflog record for the ref. You'd have to search back in time to decide if a reflog record it was an update to an existing record or a creation.

Correction. This is one of the things that confused me earlier: reflog entries for creating and deleting branches look like this

    000000000000 -> xxxxx      (create)
    xxxxxx.. xx        -> 00000 .. (delete)

respectively. When the rename happens, we can signal that the deletion and addition are linked, because they occur at the same update_index.

The deletion records for logs (type=0x0) remove a reflog entry at a specific (earlier used) update_index. So you could have the following situation:

0x0001.ref : reflog "refs/heads/master" @ update_index=0x0001, new=xxx, old=yyy ...

and then a subsequent table could specify

0x0020.ref : reflog "refs/heads/master" @ update_index=0x0001 (type=0x0)

which would hide the earlier reflog entry.

Jonathan Nieder said that this is used for git-stash, but I have never understood why this is necessary, and would love to clarify this better.

I'll clarify the explanation to reflect this.

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

Re: [PATCH v13 04/13] reftable: file format documentation

Quoted 3 lines · Han-Wen Nienhuys

Jonathan Nieder said that this is used for git-stash, but I have never understood why this is necessary, and would love to clarify this better.

The doc says this:

"The log_type = 0x0 is mostly useful for git stash drop, removing an entry from the reflog of refs/stash in a transaction file (below), without needing to rewrite larger files. Readers reading a stack of reflogs must treat this as a deletion."

I should probably look at the code for git-stash to see how this plays out.

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

Re: [PATCH v13 04/13] reftable: file format documentation

Quoted 11 lines · Han-Wen Nienhuys
Quoted 3 lines · Han-Wen Nienhuys

Jonathan Nieder said that this is used for git-stash, but I have never understood why this is necessary, and would love to clarify this better.

The doc says this:

"The log_type = 0x0 is mostly useful for git stash drop, removing an entry from the reflog of refs/stash in a transaction file (below), without needing to rewrite larger files. Readers reading a stack of reflogs must treat this as a deletion."

Yup, I saw that and that is where my "I am guessing that log_type of 0x0 is *NOT* used for that purpose)." in my review comment came from.

The "git stash" itself is an abuse of the reflog mechanism, but its "drop" subcommand is probably the worst offender. It wants to remove any arbitrary reflog entry in the middle of a reflog, not just topmost ones (pop) or bottommost ones (expire).

Quoted 1 line

I should probably look at the code for git-stash to see how this plays out.

#230 lore raw

Re: [PATCH v13 04/13] reftable: file format documentation

In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano
Quoted 7 lines · Han-wen

From: Jonathan Nieder <[email protected]>

Shawn Pearce explains:

Some repositories contain a lot of references (e.g. android at 866k, rails at 31k). The reftable format provides:

Quoted 6 lines
Quoted 4 lines

- Near constant time lookup for any single reference, even when the repository is cold and not in process or kernel cache.
- Near constant time verification a SHA-1 is referred to by at least one reference (for allow-tip-sha1-in-want).

Not quite grammatical sentence? Perhaps "if" after "verification?

Good catch, thanks.

[...]

Quoted 8 lines
Quoted 7 lines

using pandoc 2.2.1. The result required the following additional minor changes:

- removed the [TOC] directive to add a table of contents, since asciidoc does not support it
- replaced git-scm.com/docs links with linkgit: directives that link to other pages within Git's documentation

Quoted 6 lines

There are many

’

funny-quotes where we would prefer to place vanilla single quotes, which may also need to be corrected in the conversion toolchain.

Looks like Han-Wen is taking care of this (thanks!).

Quoted 2 lines

Typoes pointed out below may probably be from the original where they should be corrected.

I'm happy to do one final update the doc in JGit to match what we end up with and then replace it with a pointer to Git's copy once that lands.

[...]

Quoted 4 lines
Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+Repositories with many loose references occupy a large number of disk
+blocks from the local file system, as each reference is its own file
+storing 41 bytes (and another file for the corresponding reflog). This
+negatively affects the number of inodes available when a large number of
Quoted 8 lines

Documentation/technical/reftable.txt line 25

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+repositories are stored on the same filesystem. Readers can be penalized
+due to the larger number of syscalls required to traverse and read the
+`$GIT_DIR/refs` directory.

Another downside is that we cannot arrange atomic updates to multiple refs over loose refs, even though the "lookup of a single reference does not require linear scan" unlike packed-refs, (as long as the filesystem does its job). Worth mentioning?

Yes, this was another major part of the motivation (avoiding the complication of the "atomic" multi-ref updates to packed-refs that Git and JGit had to learn).

[...]

Quoted 10 lines

Documentation/technical/reftable.txt line 93

Quoted 3 lines
Documentation/technical/reftable.txt new file +3 -0
+References stored in a reftable are peeled, a record for an annotated
+(or signed) tag records both the tag object, and the object it refers
+to.

OK. Peeled results are recorded in packed-refs file because quite often when we use a tag object, what we actually want to access is the commit object it points at. We do so here for the same reason?

Not a rhetorical question, but if it invites a question from a reader, it may deserve to be described before readers ask it.

For a single tag ref, peeling to a commit is not very expensive. But for batch lookups e.g. when serving a response to an ls-remote request, it adds up, and having the peeled results recorded helps.

[...]

Quoted 10 lines
Quoted 10 lines
Documentation/technical/reftable.txt new file +10 -0
+Directory/file conflicts
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The reftable format accepts both `refs/heads/foo` and
+`refs/heads/foo/bar` as distinct references.
+
+This property is useful for retaining log records in reftable, but may
+confuse versions of Git using `$GIT_DIR/refs` directory tree to maintain
+references. Users of reftable may choose to continue to reject `foo` and
+`foo/bar` type conflicts to prevent problems for peers.

[...]

Quoted 8 lines

"users ... may choose" implies that it is up to the implementation of reftable user which one to show, so given a single repository, "jgit" may show "refs/heads/foo" while "libgit2" may choose to show the other one.

I am not sure if that is desirable---I suspect that we want to record which one needs to be chosen so that these "D/F conflicts disallowing" users can make consistent choices, but I dunno.

Yes, I think it would be better to explicitly say that Git will continue to reject D/F conflicts for refs (*not* reflogs) even though the format can support them in principle.

If we choose to permit them some day in the future, I believe that would be a separate repository format extension and protocol capability to avoid confusing old versions of Git.

[...]

Quoted 6 lines

Documentation/technical/reftable.txt line 310

Quoted 2 lines
Documentation/technical/reftable.txt new file +2 -0
+Symbolic references use `0x3`, followed by the complete name of the
+reference target. No compression is applied to the target name.

Is there a place in the file format where an incomplete name can be stored? If not, I think it makes it easier to read if we drop "complete" from the sentence.

The sentence about "no compression" covers the lack of prefix encoding, so I suppose I agree.

Might make sense to say "full name" to convey that we're talking about rev-parse --symbolic-full-name, not a relative path like symlinks support.

[...]

Quoted 5 lines
Quoted 5 lines
Documentation/technical/reftable.txt new file +5 -0
+Log block format
+^^^^^^^^^^^^^^^^
+
+Unlike ref and obj blocks, log blocks are always unaligned.
+
Quoted 8 lines

Documentation/technical/reftable.txt line 517

Quoted 4 lines
Documentation/technical/reftable.txt new file +4 -0
+Log blocks are variable in size, and do not match the `block_size`
+specified in the file header or footer. Writers should choose an
+appropriate buffer size to prepare a log block for deflation, such as
+`2 * block_size`.

I can guess the reason behind this design decision, but the readers may not be able to. Should we write it down here, or would it make too much irrelevant details?

I don't have a strong opinion. It sounds like Han-Wen sees something to explain there, so I suppose it would be nice to spell out.

(My take: reflog lookups are not on the critical path for most operations; especially, random accesses do not need to be fast. From a performance perspective, the best we can do is to compress them well to decrease I/O cost, hence there's not much value to alignment.)

[...]

Quoted 7 lines

This is a tangent but in a repository at hosting provider, whose primary (and often the only) source of updates are by end-user pushing into it, if reflogs are enabled, whose name and email are recorded in the logs? The committer or tagger of the object that sits at the tip of the ref after the update? What happens when a blob is pushed to update a ref? Or would it be just a single "user" that represents the "server operator"?

The latter, "server operator" (GIT_COMMITTER_IDENT at the server).

Committer in commit objects is forgeable, hence wouldn't be very useful here.

Quoted 9 lines

We know in a non-bare repository an individual contributor works on typically records only one <name, email> in the reflog: the user who works in it.

What I am trying to get at is if it makes more sense to have a small table of unique <name, email> pairs used in the file and have log_data record a single varint that is the index into that "committer ident" table. I would suspect that it would give us significantly more gain than mere <> two bytes per log_data entry.

That's true, and a good idea for the next rev of the format.

[...]

Quoted 11 lines
Quoted 11 lines
Documentation/technical/reftable.txt new file +11 -0
+A 68-byte footer appears at the end:
+
+....
+    'REFT'
+    uint8( version_number = 1 )
+    uint24( block_size )
+    uint64( min_update_index )
+    uint64( max_update_index )
+
+    uint64( ref_index_position )
+    uint64( (obj_position << 5) | obj_id_len )

[...]

Quoted 6 lines

Documentation/technical/reftable.txt line 698

Quoted 2 lines
Documentation/technical/reftable.txt new file +2 -0
+* `obj_id_len`: number of bytes used to abbreviate object identifiers in
+obj blocks.

Should we write "this can be up to 31" somewhere? It is more than enough for SHA-1 and not quite sufficient for SHA-256 (unless we say "we store obj_id_len-1 here")?

Oh! I'll take a closer look and then follow up.

Thanks for looking it over,
Jonathan

#279 lore raw

PATCH v15 13/13 Add reftable testing infrastructure

In reply to Han-Wen Nienhuys via GitGitGadget

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

* Add GIT_TEST_REFTABLE environment var to control default ref storage

* Add test_prerequisite REFTABLE.

* Skip some tests that are incompatible:

* t3210-pack-refs.sh - does not apply
* t9903-bash-prompt - The bash mode reads .git/HEAD directly
* t1450-fsck.sh - manipulates .git/ directly to create invalid state

Major test failures:

* t1400-update-ref.sh - Reads from .git/{refs,logs} directly
* t1404-update-ref-errors.sh - Manipulates .git/refs/ directly
* t1405 - inspecs .git/ directly.

Worst offenders:

t1400-update-ref.sh                      - 82 of 185
t2400-worktree-add.sh                    - 58 of 69
t1404-update-ref-errors.sh               - 44 of 53
t3514-cherry-pick-revert-gpg.sh          - 36 of 36
t5541-http-push-smart.sh                 - 29 of 38
t6003-rev-list-topo-order.sh             - 29 of 35
t3420-rebase-autostash.sh                - 28 of 42
t6120-describe.sh                        - 21 of 82
t3430-rebase-merges.sh                   - 18 of 24
t2018-checkout-branch.sh                 - 15 of 22

..

---
 builtin/clone.c               | 2 +-
 builtin/init-db.c             | 2 +-
 refs.c                        | 6 +++---
 t/t1409-avoid-packing-refs.sh | 6 ++++++
 t/t1450-fsck.sh               | 6 ++++++
 t/t3210-pack-refs.sh          | 6 ++++++
 t/t9903-bash-prompt.sh        | 6 ++++++
 t/test-lib.sh                 | 5 +++++
 8 files changed, 34 insertions(+), 5 deletions(-)
builtin/clone.c +1 -1
index 4d0cf065e4a..780c5807415 100644
@@ -1109,7 +1109,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
-		DEFAULT_REF_STORAGE, INIT_DB_QUIET);
+		default_ref_storage(), INIT_DB_QUIET);
 
 	if (real_git_dir)
 		git_dir = real_git_dir;
builtin/init-db.c +1 -1
index b7053b9e370..da5b4670c84 100644
@@ -545,7 +545,7 @@ static const char *const init_db_usage[] = {
 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
 	const char *git_dir;
-	const char *ref_storage_format = DEFAULT_REF_STORAGE;
+	const char *ref_storage_format = default_ref_storage();
 	const char *real_git_dir = NULL;
 	const char *work_tree;
 	const char *template_dir = NULL;
refs.c +3 -3
index bd1f3cc0e45..ba5239b8421 100644
@@ -1748,7 +1748,7 @@ struct ref_store *get_main_ref_store(struct repository *r)
 	r->refs_private = ref_store_init(r->gitdir,
 					 r->ref_storage_format ?
 						 r->ref_storage_format :
-						 DEFAULT_REF_STORAGE,
+						 default_ref_storage(),
 					 REF_STORE_ALL_CAPS);
 	if (getenv("GIT_DEBUG_REFS")) {
 		r->refs_private = debug_wrap(r->refs_private);
@@ -1807,7 +1807,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 		goto done;
 
 	/* assume that add_submodule_odb() has been called */
-	refs = ref_store_init(submodule_sb.buf, DEFAULT_REF_STORAGE, /* XXX */
+	refs = ref_store_init(submodule_sb.buf, default_ref_storage(),
 			      REF_STORE_READ | REF_STORE_ODB);
 	register_ref_store_map(&submodule_ref_stores, "submodule",
 			       refs, submodule);
@@ -1821,7 +1821,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 
 struct ref_store *get_worktree_ref_store(const struct worktree *wt)
 {
-	const char *format = DEFAULT_REF_STORAGE; /* XXX */
+	const char *format = default_ref_storage();
 	struct ref_store *refs;
 	const char *id;
 
t/t1409-avoid-packing-refs.sh +6 -0
index be12fb63506..c6f78325563 100755
@@ -4,6 +4,12 @@ test_description='avoid rewriting packed-refs unnecessarily'
 
 . ./test-lib.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping pack-refs tests; incompatible with reftable'
+  test_done
+fi
+
 # Add an identifying mark to the packed-refs file header line. This
 # shouldn't upset readers, and it should be omitted if the file is
 # ever rewritten.
t/t1450-fsck.sh +6 -0
index 344a2aad82f..09669203249 100755
@@ -8,6 +8,12 @@ test_description='git fsck random collection of tests
 
 . ./test-lib.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping tests; incompatible with reftable'
+  test_done
+fi
+
 test_expect_success setup '
 	test_oid_init &&
 	git config gc.auto 0 &&
t/t3210-pack-refs.sh +6 -0
index f41b2afb996..edaef2c175a 100755
@@ -11,6 +11,12 @@ semantic is still the same.
 '
 . ./test-lib.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping pack-refs tests; incompatible with reftable'
+  test_done
+fi
+
 test_expect_success 'enable reflogs' '
 	git config core.logallrefupdates true
 '
t/t9903-bash-prompt.sh +6 -0
index ab5da2cabc4..5deaaf968b0 100755
@@ -7,6 +7,12 @@ test_description='test git-specific bash prompt functions'
 
 . ./lib-bash.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping tests; incompatible with reftable'
+  test_done
+fi
+
 . "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
 
 actual="$TRASH_DIRECTORY/actual"
t/test-lib.sh +5 -0
index dbc027ff267..3ce9b957b1b 100644
@@ -1504,6 +1504,11 @@ parisc* | hppa*)
 	;;
 esac
 
+if test -n "$GIT_TEST_REFTABLE"
+then
+  test_set_prereq REFTABLE
+fi
+
 ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
Signature · 1 line
gitgitgadget
#276 lore raw

PATCH v15 10/13 Reftable support for git-core

In reply to Han-Wen Nienhuys via GitGitGadget

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

For background, see the previous commit introducing the library.

This introduces the refs/reftable-backend.c containing reftable powered ref storage backend.

It can be activated by passing --ref-storage=reftable to "git init".

TODO:

* Fix worktree commands

* Spots marked XXX

Example use: see t/t0031-reftable.sh

---
 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   27 +-
 builtin/clone.c                               |    3 +-
 builtin/init-db.c                             |   56 +-
 cache.h                                       |    6 +-
 refs.c                                        |   27 +-
 refs.h                                        |    3 +
 refs/refs-internal.h                          |    1 +
 refs/reftable-backend.c                       | 1332 +++++++++++++++++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 setup.c                                       |   12 +-
 t/t0031-reftable.sh                           |  142 ++
 13 files changed, 1591 insertions(+), 30 deletions(-)
 create mode 100644 refs/reftable-backend.c
 create mode 100755 t/t0031-reftable.sh
Documentation/technical/repository-version.txt +7 -0
index 7844ef30ffd..72576235833 100644
@@ -100,3 +100,10 @@ If set, by default "git config" reads from both "config" and
 multiple working directory mode, "config" file is shared while
 "config.worktree" is per-working directory (i.e., it's in
 GIT_COMMON_DIR/worktrees/<id>/config.worktree)
+
+==== `refStorage`
+
+Specifies the file format for the ref database. Values are `files`
+(for the traditional packed + loose ref format) and `reftable` for the
+binary reftable format. See https://github.com/google/reftable for
+more information.
Makefile +25 -2
index 372139f1f24..6b21c247b12 100644
@@ -807,6 +807,7 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
 VCSSVN_LIB = vcs-svn/lib.a
+REFTABLE_LIB = reftable/libreftable.a
 
 GENERATED_H += config-list.h
 GENERATED_H += command-list.h
@@ -959,6 +960,7 @@ LIB_OBJS += ref-filter.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
 LIB_OBJS += refs/files-backend.o
+LIB_OBJS += refs/reftable-backend.o
 LIB_OBJS += refs/iterator.o
 LIB_OBJS += refs/packed-backend.o
 LIB_OBJS += refs/ref-cache.o
@@ -1162,7 +1164,7 @@ THIRD_PARTY_SOURCES += compat/regex/%
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2352,11 +2354,29 @@ VCSSVN_OBJS += vcs-svn/sliding_window.o
 VCSSVN_OBJS += vcs-svn/svndiff.o
 VCSSVN_OBJS += vcs-svn/svndump.o
 
+REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/block.o
+REFTABLE_OBJS += reftable/file.o
+REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/merged.o
+REFTABLE_OBJS += reftable/pq.o
+REFTABLE_OBJS += reftable/reader.o
+REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/refname.o
+REFTABLE_OBJS += reftable/reftable.o
+REFTABLE_OBJS += reftable/slice.o
+REFTABLE_OBJS += reftable/stack.o
+REFTABLE_OBJS += reftable/tree.o
+REFTABLE_OBJS += reftable/writer.o
+REFTABLE_OBJS += reftable/zlib-compat.o
+
+
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 	$(XDIFF_OBJS) \
 	$(VCSSVN_OBJS) \
 	$(FUZZ_OBJS) \
+	$(REFTABLE_OBJS) \
 	common-main.o \
 	git.o
 ifndef NO_CURL
@@ -2497,6 +2517,9 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
 $(VCSSVN_LIB): $(VCSSVN_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+$(REFTABLE_LIB): $(REFTABLE_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -3112,7 +3135,7 @@ cocciclean:
 clean: profile-clean coverage-clean cocciclean
 	$(RM) *.res
 	$(RM) $(OBJECTS)
-	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
+	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(REFTABLE_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
builtin/clone.c +2 -1
index cb48a291caf..4d0cf065e4a 100644
@@ -1108,7 +1108,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET);
+	init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
+		DEFAULT_REF_STORAGE, INIT_DB_QUIET);
 
 	if (real_git_dir)
 		git_dir = real_git_dir;
builtin/init-db.c +42 -14

132 lines not on this page · raw message

cache.h +4 -2
index 0f0485ecfe2..8cb884773c3 100644
@@ -628,8 +628,9 @@ int path_inside_repo(const char *prefix, const char *path);
 
 int init_db(const char *git_dir, const char *real_git_dir,
 	    const char *template_dir, int hash_algo,
-	    unsigned int flags);
-void initialize_repository_version(int hash_algo);
+	    const char *ref_storage_format, unsigned int flags);
+void initialize_repository_version(int hash_algo,
+				   const char *ref_storage_format);
 
 void sanitize_stdfds(void);
 int daemonize(void);
@@ -1043,6 +1044,7 @@ struct repository_format {
 	int is_bare;
 	int hash_algo;
 	char *work_tree;
+	char *ref_storage;
 	struct string_list unknown_extensions;
 };
 
refs.c +19 -8
index 29e710a29e9..4409080dfd8 100644
@@ -17,10 +17,16 @@
 #include "argv-array.h"
 #include "repository.h"
 
+const char *default_ref_storage(void)
+{
+	const char *test = getenv("GIT_TEST_REFTABLE");
+	return test ? "reftable" : "files";
+}
+
 /*
  * List of all available backends
  */
-static struct ref_storage_be *refs_backends = &refs_be_files;
+static struct ref_storage_be *refs_backends = &refs_be_reftable;
 
 static struct ref_storage_be *find_ref_storage_backend(const char *name)
 {
@@ -1717,13 +1723,13 @@ static struct ref_store *lookup_ref_store_map(struct hashmap *map,
  * Create, record, and return a ref_store instance for the specified
  * gitdir.
  */
-static struct ref_store *ref_store_init(const char *gitdir,
+static struct ref_store *ref_store_init(const char *gitdir, const char *be_name,
 					unsigned int flags)
 {
-	const char *be_name = "files";
-	struct ref_storage_be *be = find_ref_storage_backend(be_name);
+	struct ref_storage_be *be;
 	struct ref_store *refs;
 
+	be = find_ref_storage_backend(be_name);
 	if (!be)
 		BUG("reference backend %s is unknown", be_name);
 
@@ -1739,7 +1745,11 @@ struct ref_store *get_main_ref_store(struct repository *r)
 	if (!r->gitdir)
 		BUG("attempting to get main_ref_store outside of repository");
 
-	r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+	r->refs_private = ref_store_init(r->gitdir,
+					 r->ref_storage_format ?
+						 r->ref_storage_format :
+						 DEFAULT_REF_STORAGE,
+					 REF_STORE_ALL_CAPS);
 	return r->refs_private;
 }
 
@@ -1794,7 +1804,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 		goto done;
 
 	/* assume that add_submodule_odb() has been called */
-	refs = ref_store_init(submodule_sb.buf,
+	refs = ref_store_init(submodule_sb.buf, DEFAULT_REF_STORAGE, /* XXX */
 			      REF_STORE_READ | REF_STORE_ODB);
 	register_ref_store_map(&submodule_ref_stores, "submodule",
 			       refs, submodule);
@@ -1808,6 +1818,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 
 struct ref_store *get_worktree_ref_store(const struct worktree *wt)
 {
+	const char *format = DEFAULT_REF_STORAGE; /* XXX */
 	struct ref_store *refs;
 	const char *id;
 
@@ -1821,9 +1832,9 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt)
 
 	if (wt->id)
 		refs = ref_store_init(git_common_path("worktrees/%s", wt->id),
-				      REF_STORE_ALL_CAPS);
+				      format, REF_STORE_ALL_CAPS);
 	else
-		refs = ref_store_init(get_git_common_dir(),
+		refs = ref_store_init(get_git_common_dir(), format,
 				      REF_STORE_ALL_CAPS);
 
 	if (refs)
refs.h +3 -0
index 7aaa1226551..ddcc940d5c7 100644
@@ -9,6 +9,9 @@ struct string_list;
 struct string_list_item;
 struct worktree;
 
+/* Returns the ref storage backend to use by default. */
+const char *default_ref_storage(void);
+
 /*
  * Resolve a reference, recursively following symbolic refererences.
  *
refs/refs-internal.h +1 -0
index d1d00947990..1d58fe17033 100644
@@ -687,6 +687,7 @@ struct ref_storage_be {
 };
 
 extern struct ref_storage_be refs_be_files;
+extern struct ref_storage_be refs_be_reftable;
 extern struct ref_storage_be refs_be_packed;
 
 /*
refs/reftable-backend.c new file +1332 -0

1,332 lines not on this page · raw message

repository.c +2 -0
index 6f7f6f002b1..087760bc184 100644
@@ -178,6 +178,8 @@ int repo_init(struct repository *repo,
 	if (worktree)
 		repo_set_worktree(repo, worktree);
 
+	repo->ref_storage_format = xstrdup_or_null(format.ref_storage);
+
 	clear_repository_format(&format);
 	return 0;
 
repository.h +3 -0
index 6534fbb7b31..f57b73f4a27 100644
@@ -74,6 +74,9 @@ struct repository {
 	 */
 	struct ref_store *refs_private;
 
+	/* The format to use for the ref database. */
+	char *ref_storage_format;
+
 	/*
 	 * Contains path to often used file names.
 	 */
setup.c +9 -3
index 65fe5ecefbe..2ef970f9f88 100644
@@ -468,9 +468,11 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
 			if (!value)
 				return config_error_nonbool(var);
 			data->partial_clone = xstrdup(value);
-		} else if (!strcmp(ext, "worktreeconfig"))
+		} else if (!strcmp(ext, "worktreeconfig")) {
 			data->worktree_config = git_config_bool(var, value);
-		else
+		} else if (!strcmp(ext, "refstorage")) {
+			data->ref_storage = xstrdup(value);
+		} else
 			string_list_append(&data->unknown_extensions, ext);
 	}
 
@@ -559,6 +561,7 @@ void clear_repository_format(struct repository_format *format)
 	string_list_clear(&format->unknown_extensions, 0);
 	free(format->work_tree);
 	free(format->partial_clone);
+	free(format->ref_storage);
 	init_repository_format(format);
 }
 
@@ -1204,8 +1207,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
 				gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
 			setup_git_env(gitdir);
 		}
-		if (startup_info->have_repository)
+		if (startup_info->have_repository) {
 			repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
+			the_repository->ref_storage_format =
+				xstrdup_or_null(repo_fmt.ref_storage);
+		}
 	}
 
 	strbuf_release(&dir);
t/t0031-reftable.sh new file +142 -0

142 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#275 lore raw

PATCH v15 9/13 Add reftable library

In reply to Han-Wen Nienhuys via GitGitGadget

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

Reftable is a format for storing the ref database. Its rationale and specification is in the preceding commit.

This imports the upstream library as one big commit. For understanding the code, it is suggested to read the code in the following order:

* The specification under Documentation/technical/reftable.txt

* reftable.h - the public API

* record.{c,h} - reading and writing records

* block.{c,h} - reading and writing blocks.

* writer.{c,h} - writing a complete reftable file.

* merged.{c,h} and pq.{c,h} - reading a stack of reftables

* stack.{c,h} - writing and compacting stacks of reftable on the filesystem.

---
 reftable/LICENSE       |   31 ++
 reftable/README.md     |   11 +
 reftable/VERSION       |    8 +
 reftable/basics.c      |  215 +++++++
 reftable/basics.h      |   53 ++
 reftable/block.c       |  433 ++++++++++++++
 reftable/block.h       |  129 +++++
 reftable/constants.h   |   21 +
 reftable/file.c        |   95 ++++
 reftable/iter.c        |  243 ++++++++
 reftable/iter.h        |   72 +++
 reftable/merged.c      |  320 +++++++++++
 reftable/merged.h      |   39 ++
 reftable/pq.c          |  113 ++++
 reftable/pq.h          |   34 ++
 reftable/reader.c      |  742 ++++++++++++++++++++++++
 reftable/reader.h      |   65 +++
 reftable/record.c      | 1113 ++++++++++++++++++++++++++++++++++++
 reftable/record.h      |  128 +++++
 reftable/refname.c     |  209 +++++++
 reftable/refname.h     |   38 ++
 reftable/reftable.c    |   90 +++
 reftable/reftable.h    |  564 +++++++++++++++++++
 reftable/slice.c       |  247 ++++++++
 reftable/slice.h       |   87 +++
 reftable/stack.c       | 1207 ++++++++++++++++++++++++++++++++++++++++
 reftable/stack.h       |   48 ++
 reftable/system.h      |   54 ++
 reftable/tree.c        |   63 +++
 reftable/tree.h        |   34 ++
 reftable/update.sh     |   24 +
 reftable/writer.c      |  644 +++++++++++++++++++++
 reftable/writer.h      |   60 ++
 reftable/zlib-compat.c |   92 +++
 34 files changed, 7326 insertions(+)
 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/constants.h
 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/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/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
reftable/LICENSE new file +31 -0
new file mode 100644
index 00000000000..402e0f9356b
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
reftable/README.md new file +11 -0
new file mode 100644
index 00000000000..21500563fd4
@@ -0,0 +1,11 @@
+
+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:
+
+   sh reftable/update.sh
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
reftable/VERSION new file +8 -0
new file mode 100644
index 00000000000..eb557542cbe
@@ -0,0 +1,8 @@
+commit fe15c65dd95b6a13de92b4baf711e50338ef20ae
+Author: Han-Wen Nienhuys <[email protected]>
+Date:   Thu May 28 19:28:24 2020 +0200
+
+    C: add 'canary' to struct slice
+    
+    This enforces that all slices are initialized explicitly with
+    SLICE_INIT, for future compatibility with git's strbuf.
reftable/basics.c new file +215 -0

215 lines not on this page · raw message

reftable/basics.h new file +53 -0

53 lines not on this page · raw message

reftable/block.c new file +433 -0

433 lines not on this page · raw message

reftable/block.h new file +129 -0

129 lines not on this page · raw message

reftable/constants.h new file +21 -0
new file mode 100644
index 00000000000..5eee72c4c11
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
reftable/file.c new file +95 -0

95 lines not on this page · raw message

reftable/iter.c new file +243 -0

243 lines not on this page · raw message

reftable/iter.h new file +72 -0

72 lines not on this page · raw message

reftable/merged.c new file +320 -0

320 lines not on this page · raw message

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

113 lines not on this page · raw message

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

742 lines not on this page · raw message

reftable/reader.h new file +65 -0

65 lines not on this page · raw message

reftable/record.c new file +1113 -0

376 lines not on this page · raw message

reftable/record.h new file +128 -0

128 lines not on this page · raw message

reftable/refname.c new file +209 -0

209 lines not on this page · raw message

reftable/refname.h new file +38 -0
new file mode 100644
index 00000000000..f335287fcdb

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

reftable/reftable.c new file +90 -0

90 lines not on this page · raw message

reftable/reftable.h new file +564 -0

564 lines not on this page · raw message

reftable/slice.c new file +247 -0

247 lines not on this page · raw message

reftable/slice.h new file +87 -0

87 lines not on this page · raw message

reftable/stack.c new file +1207 -0

1,207 lines not on this page · raw message

reftable/stack.h new file +48 -0

48 lines not on this page · raw message

reftable/system.h new file +54 -0

54 lines not on this page · raw message

reftable/tree.c new file +63 -0

63 lines not on this page · raw message

reftable/tree.h new file +34 -0
new file mode 100644
index 00000000000..954512e9a3a

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

reftable/update.sh new file +24 -0
new file mode 100755
index 00000000000..ef32aeda515

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

reftable/writer.c new file +644 -0

644 lines not on this page · raw message

reftable/writer.h new file +60 -0

60 lines not on this page · raw message

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

92 lines not on this page · raw message

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

Signature · 1 line
gitgitgadget

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

#277 lore raw

PATCH v15 11/13 Add GIT_DEBUG_REFS debugging mechanism

In reply to Han-Wen Nienhuys via GitGitGadget

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

When set in the environment, GIT_DEBUG_REFS makes git print operations and results as they flow through the ref storage backend. This helps debug discrepancies between different ref backends.

---
 Makefile              |   1 +
 refs.c                |   3 +
 refs/debug.c          | 309 ++++++++++++++++++++++++++++++++++++++++++
 refs/refs-internal.h  |   6 +
 t/t0033-debug-refs.sh |  18 +++
 5 files changed, 337 insertions(+)
 create mode 100644 refs/debug.c
 create mode 100755 t/t0033-debug-refs.sh
Makefile +1 -0
index 6b21c247b12..9bab1acd6a4 100644
@@ -959,6 +959,7 @@ LIB_OBJS += rebase.o
 LIB_OBJS += ref-filter.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
+LIB_OBJS += refs/debug.o
 LIB_OBJS += refs/files-backend.o
 LIB_OBJS += refs/reftable-backend.o
 LIB_OBJS += refs/iterator.o
refs.c +3 -0
index 4409080dfd8..bd1f3cc0e45 100644
@@ -1750,6 +1750,9 @@ struct ref_store *get_main_ref_store(struct repository *r)
 						 r->ref_storage_format :
 						 DEFAULT_REF_STORAGE,
 					 REF_STORE_ALL_CAPS);
+	if (getenv("GIT_DEBUG_REFS")) {
+		r->refs_private = debug_wrap(r->refs_private);
+	}
 	return r->refs_private;
 }
 
refs/debug.c new file +309 -0

309 lines not on this page · raw message

refs/refs-internal.h +6 -0
index 1d58fe17033..4dfa0c9b156 100644
@@ -707,4 +707,10 @@ struct ref_store {
 void base_ref_store_init(struct ref_store *refs,
 			 const struct ref_storage_be *be);
 
+/*
+ * Print out ref operations as they occur. Useful for debugging alternate ref
+ * backends.
+ */
+struct ref_store *debug_wrap(struct ref_store *store);
+
 #endif /* REFS_REFS_INTERNAL_H */
t/t0033-debug-refs.sh new file +18 -0
new file mode 100755
index 00000000000..96266a3ee3a
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Google LLC
+#
+
+test_description='cross-check reftable with files, using GIT_DEBUG_REFS output'
+
+. ./test-lib.sh
+
+test_expect_success 'GIT_DEBUG_REFS' '
+	git init --ref-storage=files files &&
+	git init --ref-storage=reftable reftable &&
+	(cd files && GIT_DEBUG_REFS=1 test_commit message file) > files.txt && 
+	(cd reftable && GIT_DEBUG_REFS=1 test_commit message file) > reftable.txt &&
+	test_cmp files.txt reftable.txt
+'
+
+test_done
Signature · 1 line
gitgitgadget
#274 lore raw

PATCH v15 8/13 Add .gitattributes for the reftable/ directory

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 reftable/.gitattributes | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 reftable/.gitattributes
reftable/.gitattributes new file +1 -0
new file mode 100644
index 00000000000..f44451a3795
@@ -0,0 +1 @@
+/zlib-compat.c	whitespace=-indent-with-non-tab,-trailing-space
Signature · 1 line
gitgitgadget
#278 lore raw

PATCH v15 12/13 vcxproj: adjust for the reftable changes

In reply to Han-Wen Nienhuys via GitGitGadget

From: Johannes Schindelin <[email protected]>

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

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

PATCH v15 7/13 Iterate over the "refs/" namespace in for_each_[raw]ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

This happens implicitly in the files/packed ref backend; making it explicit simplifies adding alternate ref storage backends, such as reftable.

---
 refs.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
refs.c +3 -3
index 812fee47108..29e710a29e9 100644
@@ -1450,7 +1450,7 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,
 
 int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(refs, "", fn, 0, 0, cb_data);
+	return do_for_each_ref(refs, "refs/", fn, 0, 0, cb_data);
 }
 
 int for_each_ref(each_ref_fn fn, void *cb_data)
@@ -1510,8 +1510,8 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(refs, "", fn, 0,
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+	return do_for_each_ref(refs, "refs/", fn, 0, DO_FOR_EACH_INCLUDE_BROKEN,
+			       cb_data);
 }
 
 int for_each_rawref(each_ref_fn fn, void *cb_data)
Signature · 1 line
gitgitgadget
#272 lore raw

PATCH v15 6/13 Move REF_LOG_ONLY to refs-internal.h

In reply to Han-Wen Nienhuys via GitGitGadget

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

REF_LOG_ONLY is used in the transaction preparation: if a symref is involved in a transaction, the referent of the symref should be updated, and the symref itself should only be updated in the reflog. Other ref backends will need to duplicate this logic.

---
 refs/files-backend.c | 7 -------
 refs/refs-internal.h | 7 +++++++
 2 files changed, 7 insertions(+), 7 deletions(-)
refs/files-backend.c +0 -7
index df7553f4cc3..141b6b08816 100644
@@ -38,13 +38,6 @@
  */
 #define REF_NEEDS_COMMIT (1 << 6)
 
-/*
- * Used as a flag in ref_update::flags when we want to log a ref
- * update but not actually perform it.  This is used when a symbolic
- * ref update is split up.
- */
-#define REF_LOG_ONLY (1 << 7)
-
 /*
  * Used as a flag in ref_update::flags when the ref_update was via an
  * update to HEAD.
refs/refs-internal.h +7 -0
index 4c6d77898aa..d1d00947990 100644
@@ -31,6 +31,13 @@ struct ref_transaction;
  */
 #define REF_HAVE_OLD (1 << 3)
 
+/*
+ * Used as a flag in ref_update::flags when we want to log a ref
+ * update but not actually perform it.  This is used when a symbolic
+ * ref update is split up.
+ */
+#define REF_LOG_ONLY (1 << 7)
+
 /*
  * Return the length of time to retry acquiring a loose reference lock
  * before giving up, in milliseconds:
Signature · 1 line
gitgitgadget
#271 lore raw

PATCH v15 5/13 Treat REVERT_HEAD as a pseudo ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 path.c      |  1 -
 path.h      |  8 +++-----
 sequencer.c | 18 ++++++++++--------
 wt-status.c |  2 +-
 4 files changed, 14 insertions(+), 15 deletions(-)
path.c +0 -1
index 783cc2ae819..7b385e5eb28 100644
@@ -1528,7 +1528,6 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
-REPO_GIT_PATH_FUNC(revert_head, "REVERT_HEAD")
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
path.h +3 -5
index 8941c018a99..e7e77da6aaa 100644
@@ -170,7 +170,6 @@ void report_linked_checkout_garbage(void);
 	}
 
 struct path_cache {
-	const char *revert_head;
 	const char *squash_msg;
 	const char *merge_msg;
 	const char *merge_rr;
@@ -181,12 +180,11 @@ struct path_cache {
 	const char *shallow;
 };
 
-#define PATH_CACHE_INIT                                              \
-	{                                                            \
-		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
+#define PATH_CACHE_INIT                                        \
+	{                                                      \
+		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
 	}
 
-const char *git_path_revert_head(struct repository *r);
 const char *git_path_squash_msg(struct repository *r);
 const char *git_path_merge_msg(struct repository *r);
 const char *git_path_merge_rr(struct repository *r);
sequencer.c +10 -8
index 188488cb40b..a41913455ec 100644
@@ -2317,8 +2317,10 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
 		need_cleanup = 1;
 	}
 
-	if (file_exists(git_path_revert_head(r))) {
-		if (!unlink(git_path_revert_head(r)) && verbose)
+	if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
+		if (!refs_delete_pseudoref(get_main_ref_store(r), "REVERT_HEAD",
+					   NULL) &&
+		    verbose)
 			warning(_("cancelling a revert in progress"));
 		opts.action = REPLAY_REVERT;
 		need_cleanup = 1;
@@ -2677,8 +2679,8 @@ static int create_seq_dir(struct repository *r)
 	const char *in_progress_error = NULL;
 	const char *in_progress_advice = NULL;
 	unsigned int advise_skip =
-		file_exists(git_path_revert_head(r)) ||
-		refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
+		refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD");
+	refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
 
 	if (!sequencer_get_last_command(r, &action)) {
 		switch (action) {
@@ -2778,7 +2780,7 @@ static int rollback_single_pick(struct repository *r)
 	struct object_id head_oid;
 
 	if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
-	    !file_exists(git_path_revert_head(r)))
+	    !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
 		return error(_("no cherry-pick or revert in progress"));
 	if (read_ref_full("HEAD", 0, &head_oid, NULL))
 		return error(_("cannot resolve HEAD"));
@@ -2872,7 +2874,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
 	 */
 	switch (opts->action) {
 	case REPLAY_REVERT:
-		if (!file_exists(git_path_revert_head(r))) {
+		if (!refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
 			if (action != REPLAY_REVERT)
 				return error(_("no revert in progress"));
 			if (!rollback_is_safe())
@@ -4210,7 +4212,7 @@ static int continue_single_pick(struct repository *r)
 	const char *argv[] = { "commit", NULL };
 
 	if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
-	    !file_exists(git_path_revert_head(r)))
+	    !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
 		return error(_("no cherry-pick or revert in progress"));
 	return run_command_v_opt(argv, RUN_GIT_CMD);
 }
@@ -4390,7 +4392,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
 		/* Verify that the conflict has been resolved */
 		if (refs_ref_exists(get_main_ref_store(r),
 				    "CHERRY_PICK_HEAD") ||
-		    file_exists(git_path_revert_head(r))) {
+		    refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
 			res = continue_single_pick(r);
 			if (res)
 				goto release_todo_list;
wt-status.c +1 -1
index 96302be030b..81b768504a1 100644
@@ -1642,7 +1642,7 @@ void wt_status_get_state(struct repository *r,
 		oidcpy(&state->cherry_pick_head_oid, &oid);
 	}
 	wt_status_check_bisect(NULL, state);
-	if (!stat(git_path_revert_head(r), &st) &&
+	if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") &&
 	    !get_oid("REVERT_HEAD", &oid)) {
 		state->revert_in_progress = 1;
 		oidcpy(&state->revert_head_oid, &oid);
Signature · 1 line
gitgitgadget
#267 lore raw

PATCH v15 2/13 Make refs_ref_exists public

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 refs.c | 2 +-
 refs.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +1 -1
index 12908066b13..812fee47108 100644
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct object_id *oid)
 	return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
 }
 
-static int refs_ref_exists(struct ref_store *refs, const char *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
 {
 	return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
 }
refs.h +2 -0
index 4dad8f24914..7aaa1226551 100644
@@ -105,6 +105,8 @@ int refs_verify_refname_available(struct ref_store *refs,
 				  const struct string_list *skip,
 				  struct strbuf *err);
 
+int refs_ref_exists(struct ref_store *refs, const char *refname);
+
 int ref_exists(const char *refname);
 
 int should_autocreate_reflog(const char *refname);
Signature · 1 line
gitgitgadget
#270 lore raw

PATCH v15 4/13 Treat CHERRY_PICK_HEAD as a pseudo ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

Check for existence and delete CHERRY_PICK_HEAD through pseudo ref functions. This will help cherry-pick work with alternate ref storage backends.

---
 builtin/commit.c | 34 +++++++++++++++++++---------------
 builtin/merge.c  |  2 +-
 path.c           |  1 -
 path.h           |  7 ++++---
 sequencer.c      | 42 ++++++++++++++++++++++++++----------------
 wt-status.c      |  4 ++--
 6 files changed, 52 insertions(+), 38 deletions(-)
builtin/commit.c +19 -15
index d1b7396052a..e27120b982b 100644
@@ -847,21 +847,25 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 			if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
 				!merge_contains_scissors)
 				wt_status_add_cut_line(s->fp);
-			status_printf_ln(s, GIT_COLOR_NORMAL,
-			    whence == FROM_MERGE
-				? _("\n"
-					"It looks like you may be committing a merge.\n"
-					"If this is not correct, please remove the file\n"
-					"	%s\n"
-					"and try again.\n")
-				: _("\n"
-					"It looks like you may be committing a cherry-pick.\n"
-					"If this is not correct, please remove the file\n"
-					"	%s\n"
-					"and try again.\n"),
-				whence == FROM_MERGE ?
-					git_path_merge_head(the_repository) :
-					git_path_cherry_pick_head(the_repository));
+			if (whence == FROM_MERGE)
+				status_printf_ln(
+					s, GIT_COLOR_NORMAL,
+
+					_("\n"
+					  "It looks like you may be committing a merge.\n"
+					  "If this is not correct, please remove the file\n"
+					  "	%s\n"
+					  "and try again.\n"),
+					git_path_merge_head(the_repository));
+			else
+				status_printf_ln(
+					s, GIT_COLOR_NORMAL,
+
+					_("\n"
+					  "It looks like you may be committing a cherry-pick.\n"
+					  "If this is not correct, please run\n"
+					  "	git cherry-pick --abort\n"
+					  "and try again.\n"));
 		}
 
 		fprintf(s->fp, "\n");
builtin/merge.c +1 -1
index 7da707bf55d..93b0a7b6eda 100644
@@ -1352,7 +1352,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		else
 			die(_("You have not concluded your merge (MERGE_HEAD exists)."));
 	}
-	if (file_exists(git_path_cherry_pick_head(the_repository))) {
+	if (ref_exists("CHERRY_PICK_HEAD")) {
 		if (advice_resolve_conflict)
 			die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
 			    "Please, commit your changes before you merge."));
path.c +0 -1
index 8b2c7531919..783cc2ae819 100644
@@ -1528,7 +1528,6 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
-REPO_GIT_PATH_FUNC(cherry_pick_head, "CHERRY_PICK_HEAD")
 REPO_GIT_PATH_FUNC(revert_head, "REVERT_HEAD")
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
path.h +4 -3
index 1f1bf8f87a8..8941c018a99 100644
@@ -170,7 +170,6 @@ void report_linked_checkout_garbage(void);
 	}
 
 struct path_cache {
-	const char *cherry_pick_head;
 	const char *revert_head;
 	const char *squash_msg;
 	const char *merge_msg;
@@ -182,9 +181,11 @@ struct path_cache {
 	const char *shallow;
 };
 
-#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+#define PATH_CACHE_INIT                                              \
+	{                                                            \
+		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
+	}
 
-const char *git_path_cherry_pick_head(struct repository *r);
 const char *git_path_revert_head(struct repository *r);
 const char *git_path_squash_msg(struct repository *r);
 const char *git_path_merge_msg(struct repository *r);
sequencer.c +26 -16

114 lines not on this page · raw message

wt-status.c +2 -2
index 98dfa6f73f9..96302be030b 100644
@@ -1636,8 +1636,8 @@ void wt_status_get_state(struct repository *r,
 		state->merge_in_progress = 1;
 	} else if (wt_status_check_rebase(NULL, state)) {
 		;		/* all set */
-	} else if (!stat(git_path_cherry_pick_head(r), &st) &&
-			!get_oid("CHERRY_PICK_HEAD", &oid)) {
+	} else if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
+		   !get_oid("CHERRY_PICK_HEAD", &oid)) {
 		state->cherry_pick_in_progress = 1;
 		oidcpy(&state->cherry_pick_head_oid, &oid);
 	}
Signature · 1 line
gitgitgadget
#268 lore raw

PATCH v15 3/13 Treat BISECT_HEAD as a pseudo ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

Both the git-bisect.sh as bisect--helper inspected the file system directly.

---
 builtin/bisect--helper.c | 3 +--
 git-bisect.sh            | 4 ++--
 2 files changed, 3 insertions(+), 4 deletions(-)
builtin/bisect--helper.c +1 -2
index ec4996282e3..73f9324ad7d 100644
@@ -13,7 +13,6 @@ static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
 static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
 static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
 static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
-static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
 static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
 static GIT_PATH_FUNC(git_path_head_name, "head-name")
 static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
@@ -164,7 +163,7 @@ static int bisect_reset(const char *commit)
 		strbuf_addstr(&branch, commit);
 	}
 
-	if (!file_exists(git_path_bisect_head())) {
+	if (!ref_exists("BISECT_HEAD")) {
 		struct argv_array argv = ARGV_ARRAY_INIT;
 
 		argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
git-bisect.sh +2 -2
index 08a6ed57ddb..f03fbb18f00 100755
@@ -41,7 +41,7 @@ TERM_GOOD=good
 
 bisect_head()
 {
-	if test -f "$GIT_DIR/BISECT_HEAD"
+	if git rev-parse --verify -q BISECT_HEAD > /dev/null
 	then
 		echo BISECT_HEAD
 	else
@@ -153,7 +153,7 @@ bisect_next() {
 	git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
 
 	# Perform all bisection computation, display and checkout
-	git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
+	git bisect--helper --next-all $(git rev-parse --verify -q BISECT_HEAD > /dev/null && echo --no-checkout)
 	res=$?
 
 	# Check if we should exit because bisection is finished
Signature · 1 line
gitgitgadget
#266 lore raw

PATCH v15 1/13 Write pseudorefs through ref backends.

In reply to Han-Wen Nienhuys via GitGitGadget

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

Pseudorefs store transient data in in the repository. Examples are HEAD, CHERRY_PICK_HEAD, etc.

These refs have always been read through the ref backends, but they were written in a one-off routine that wrote an object ID or symref directly into .git/<pseudo_ref_name>.

This causes problems when introducing a new ref storage backend. To remedy this, extend the ref backend implementation with a write_pseudoref_fn and update_pseudoref_fn.

---
 refs.c                | 119 ++++++++----------------------------------
 refs.h                |  11 ++++
 refs/files-backend.c  | 114 +++++++++++++++++++++++++++++++++++++++-
 refs/packed-backend.c |  21 +++++++-
 refs/refs-internal.h  |  12 +++++
 5 files changed, 178 insertions(+), 99 deletions(-)
refs.c +22 -97
index 224ff66c7bb..12908066b13 100644
@@ -321,6 +321,12 @@ int ref_exists(const char *refname)
 	return refs_ref_exists(get_main_ref_store(the_repository), refname);
 }
 
+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
+{
+	return refs_delete_pseudoref(get_main_ref_store(the_repository),
+				     pseudoref, old_oid);
+}
+
 static int filter_refs(const char *refname, const struct object_id *oid,
 			   int flags, void *data)
 {
@@ -739,101 +745,6 @@ long get_files_ref_lock_timeout_ms(void)
 	return timeout_ms;
 }
 
-static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
-			   const struct object_id *old_oid, struct strbuf *err)
-{
-	const char *filename;
-	int fd;
-	struct lock_file lock = LOCK_INIT;
-	struct strbuf buf = STRBUF_INIT;
-	int ret = -1;
-
-	if (!oid)
-		return 0;
-
-	strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
-
-	filename = git_path("%s", pseudoref);
-	fd = hold_lock_file_for_update_timeout(&lock, filename, 0,
-					       get_files_ref_lock_timeout_ms());
-	if (fd < 0) {
-		strbuf_addf(err, _("could not open '%s' for writing: %s"),
-			    filename, strerror(errno));
-		goto done;
-	}
-
-	if (old_oid) {
-		struct object_id actual_old_oid;
-
-		if (read_ref(pseudoref, &actual_old_oid)) {
-			if (!is_null_oid(old_oid)) {
-				strbuf_addf(err, _("could not read ref '%s'"),
-					    pseudoref);
-				rollback_lock_file(&lock);
-				goto done;
-			}
-		} else if (is_null_oid(old_oid)) {
-			strbuf_addf(err, _("ref '%s' already exists"),
-				    pseudoref);
-			rollback_lock_file(&lock);
-			goto done;
-		} else if (!oideq(&actual_old_oid, old_oid)) {
-			strbuf_addf(err, _("unexpected object ID when writing '%s'"),
-				    pseudoref);
-			rollback_lock_file(&lock);
-			goto done;
-		}
-	}
-
-	if (write_in_full(fd, buf.buf, buf.len) < 0) {
-		strbuf_addf(err, _("could not write to '%s'"), filename);
-		rollback_lock_file(&lock);
-		goto done;
-	}
-
-	commit_lock_file(&lock);
-	ret = 0;
-done:
-	strbuf_release(&buf);
-	return ret;
-}
-
-static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
-{
-	const char *filename;
-
-	filename = git_path("%s", pseudoref);
-
-	if (old_oid && !is_null_oid(old_oid)) {
-		struct lock_file lock = LOCK_INIT;
-		int fd;
-		struct object_id actual_old_oid;
-
-		fd = hold_lock_file_for_update_timeout(
-				&lock, filename, 0,
-				get_files_ref_lock_timeout_ms());
-		if (fd < 0) {
-			error_errno(_("could not open '%s' for writing"),
-				    filename);
-			return -1;
-		}
-		if (read_ref(pseudoref, &actual_old_oid))
-			die(_("could not read ref '%s'"), pseudoref);
-		if (!oideq(&actual_old_oid, old_oid)) {
-			error(_("unexpected object ID when deleting '%s'"),
-			      pseudoref);
-			rollback_lock_file(&lock);
-			return -1;
-		}
-
-		unlink(filename);
-		rollback_lock_file(&lock);
-	} else {
-		unlink(filename);
-	}
-
-	return 0;
-}
 
 int refs_delete_ref(struct ref_store *refs, const char *msg,
 		    const char *refname,
@@ -845,7 +756,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
 
 	if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 		assert(refs == get_main_ref_store(the_repository));
-		return delete_pseudoref(refname, old_oid);
+		return refs_delete_pseudoref(refs, refname, old_oid);
 	}
 
 	transaction = ref_store_transaction_begin(refs, &err);
@@ -1172,7 +1083,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 
 	if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 		assert(refs == get_main_ref_store(the_repository));
-		ret = write_pseudoref(refname, new_oid, old_oid, &err);
+		ret = refs_write_pseudoref(refs, refname, new_oid, old_oid,
+					   &err);
 	} else {
 		t = ref_store_transaction_begin(refs, &err);
 		if (!t ||
@@ -1441,6 +1353,19 @@ int head_ref(each_ref_fn fn, void *cb_data)
 	return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
 }
 
+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
+			 const struct object_id *oid,
+			 const struct object_id *old_oid, struct strbuf *err)
+{
+	return refs->be->write_pseudoref(refs, pseudoref, oid, old_oid, err);
+}
+
+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
+			  const struct object_id *old_oid)
+{
+	return refs->be->delete_pseudoref(refs, pseudoref, old_oid);
+}
+
 struct ref_iterator *refs_ref_iterator_begin(
 		struct ref_store *refs,
 		const char *prefix, int trim, int flags)
refs.h +11 -0
index e010f8aec28..4dad8f24914 100644
@@ -732,6 +732,17 @@ int update_ref(const char *msg, const char *refname,
 	       const struct object_id *new_oid, const struct object_id *old_oid,
 	       unsigned int flags, enum action_on_err onerr);
 
+/* Pseudorefs (eg. HEAD, CHERRY_PICK_HEAD) have a separate routines for updating
+   and deletion as they cannot take part in normal transactional updates.
+   Pseudorefs should only be written for the main repository.
+*/
+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
+			 const struct object_id *oid,
+			 const struct object_id *old_oid, struct strbuf *err);
+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
+			  const struct object_id *old_oid);
+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid);
+
 int parse_hide_refs_config(const char *var, const char *value, const char *);
 
 /*
refs/files-backend.c +113 -1

130 lines not on this page · raw message

refs/packed-backend.c +20 -1
index 4458a0f69cc..08e8253a893 100644
@@ -1590,6 +1590,22 @@ static int packed_copy_ref(struct ref_store *ref_store,
 	BUG("packed reference store does not support copying references");
 }
 
+static int packed_write_pseudoref(struct ref_store *ref_store,
+				  const char *pseudoref,
+				  const struct object_id *oid,
+				  const struct object_id *old_oid,
+				  struct strbuf *err)
+{
+	BUG("packed reference store does not support writing pseudo-references");
+}
+
+static int packed_delete_pseudoref(struct ref_store *ref_store,
+				   const char *pseudoref,
+				   const struct object_id *old_oid)
+{
+	BUG("packed reference store does not support deleting pseudo-references");
+}
+
 static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
 {
 	return empty_ref_iterator_begin();
@@ -1656,6 +1672,9 @@ struct ref_storage_be refs_be_packed = {
 	packed_rename_ref,
 	packed_copy_ref,
 
+	packed_write_pseudoref,
+	packed_delete_pseudoref,
+
 	packed_ref_iterator_begin,
 	packed_read_raw_ref,
 
@@ -1665,5 +1684,5 @@ struct ref_storage_be refs_be_packed = {
 	packed_reflog_exists,
 	packed_create_reflog,
 	packed_delete_reflog,
-	packed_reflog_expire
+	packed_reflog_expire,
 };
refs/refs-internal.h +12 -0
index 4271362d264..4c6d77898aa 100644
@@ -556,6 +556,15 @@ typedef int copy_ref_fn(struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
 
+typedef int write_pseudoref_fn(struct ref_store *ref_store,
+			       const char *pseudoref,
+			       const struct object_id *oid,
+			       const struct object_id *old_oid,
+			       struct strbuf *err);
+typedef int delete_pseudoref_fn(struct ref_store *ref_store,
+				const char *pseudoref,
+				const struct object_id *old_oid);
+
 /*
  * Iterate over the references in `ref_store` whose names start with
  * `prefix`. `prefix` is matched as a literal string, without regard
@@ -655,6 +664,9 @@ struct ref_storage_be {
 	rename_ref_fn *rename_ref;
 	copy_ref_fn *copy_ref;
 
+	write_pseudoref_fn *write_pseudoref;
+	delete_pseudoref_fn *delete_pseudoref;
+
 	ref_iterator_begin_fn *iterator_begin;
 	read_raw_ref_fn *read_raw_ref;
 
Signature · 1 line
gitgitgadget
#265 lore raw

v15 cover Reftable support git-core

In reply to Han-Wen Nienhuys via GitGitGadget

This adds the reftable library, and hooks it up as a ref backend. Based on hn/refs-cleanup.

Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1

Summary 20479 tests pass 1299 tests fail

Some issues:

* many tests inspect .git/{logs,heads}/ directly.
* worktrees broken.
* Rebase/cherry-pick still largely broken

v17

* many style tweaks in reftable/
* fix rebase
* GIT_DEBUG_REFS support.

v18

* fix pseudo ref usage

Han-Wen Nienhuys (12):
Write pseudorefs through ref backends.
Make refs_ref_exists public
Treat BISECT_HEAD as a pseudo ref
Treat CHERRY_PICK_HEAD as a pseudo ref
Treat REVERT_HEAD as a pseudo ref
Move REF_LOG_ONLY to refs-internal.h
Iterate over the "refs/" namespace in for_each_[raw]ref Add .gitattributes for the reftable/ directory Add reftable library
Reftable support for git-core
Add GIT_DEBUG_REFS debugging mechanism
Add reftable testing infrastructure

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

 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   28 +-
 builtin/bisect--helper.c                      |    3 +-
 builtin/clone.c                               |    3 +-
 builtin/commit.c                              |   34 +-
 builtin/init-db.c                             |   56 +-
 builtin/merge.c                               |    2 +-
 cache.h                                       |    6 +-
 config.mak.uname                              |    2 +-
 contrib/buildsystems/Generators/Vcxproj.pm    |   11 +-
 git-bisect.sh                                 |    4 +-
 path.c                                        |    2 -
 path.h                                        |    9 +-
 refs.c                                        |  157 +-
 refs.h                                        |   16 +
 refs/debug.c                                  |  309 ++++
 refs/files-backend.c                          |  121 +-
 refs/packed-backend.c                         |   21 +-
 refs/refs-internal.h                          |   26 +
 refs/reftable-backend.c                       | 1332 +++++++++++++++++
 reftable/.gitattributes                       |    1 +
 reftable/LICENSE                              |   31 +
 reftable/README.md                            |   11 +
 reftable/VERSION                              |    8 +
 reftable/basics.c                             |  215 +++
 reftable/basics.h                             |   53 +
 reftable/block.c                              |  433 ++++++
 reftable/block.h                              |  129 ++
 reftable/constants.h                          |   21 +
 reftable/file.c                               |   95 ++
 reftable/iter.c                               |  243 +++
 reftable/iter.h                               |   72 +
 reftable/merged.c                             |  320 ++++
 reftable/merged.h                             |   39 +
 reftable/pq.c                                 |  113 ++
 reftable/pq.h                                 |   34 +
 reftable/reader.c                             |  742 +++++++++
 reftable/reader.h                             |   65 +
 reftable/record.c                             | 1113 ++++++++++++++
 reftable/record.h                             |  128 ++
 reftable/refname.c                            |  209 +++
 reftable/refname.h                            |   38 +
 reftable/reftable.c                           |   90 ++
 reftable/reftable.h                           |  564 +++++++
 reftable/slice.c                              |  247 +++
 reftable/slice.h                              |   87 ++
 reftable/stack.c                              | 1207 +++++++++++++++
 reftable/stack.h                              |   48 +
 reftable/system.h                             |   54 +
 reftable/tree.c                               |   63 +
 reftable/tree.h                               |   34 +
 reftable/update.sh                            |   24 +
 reftable/writer.c                             |  644 ++++++++
 reftable/writer.h                             |   60 +
 reftable/zlib-compat.c                        |   92 ++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 sequencer.c                                   |   56 +-
 setup.c                                       |   12 +-
 t/t0031-reftable.sh                           |  142 ++
 t/t0033-debug-refs.sh                         |   18 +
 t/t1409-avoid-packing-refs.sh                 |    6 +
 t/t1450-fsck.sh                               |    6 +
 t/t3210-pack-refs.sh                          |    6 +
 t/t9903-bash-prompt.sh                        |    6 +
 t/test-lib.sh                                 |    5 +
 wt-status.c                                   |    6 +-
 67 files changed, 9550 insertions(+), 194 deletions(-)
 create mode 100644 refs/debug.c
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/.gitattributes
 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/constants.h
 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/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/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100755 t/t0031-reftable.sh
 create mode 100755 t/t0033-debug-refs.sh

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

Range-diff vs v14:

1: 46d04f6740e ! 1: 9bb64f748dc Write pseudorefs through ref backends.
@@ Commit message
CHERRY_PICK_HEAD, etc.

These refs have always been read through the ref backends, but they were written
- in a one-off routine that wrote a object ID or symref directly wrote into
+ in a one-off routine that wrote an object ID or symref directly into
.git/<pseudo_ref_name>.

This causes problems when introducing a new ref storage backend. To remedy this,
@@ Commit message

## refs.c ##
+@@ refs.c: int ref_exists(const char *refname)
+ return refs_ref_exists(get_main_ref_store(the_repository), refname);
+ }
+
++int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid) ++{
++ return refs_delete_pseudoref(get_main_ref_store(the_repository),
++ pseudoref, old_oid);
++}
++
+ static int filter_refs(const char *refname, const struct object_id *oid,
+ int flags, void *data)
+ {
@@ refs.c: long get_files_ref_lock_timeout_ms(void)
return timeout_ms;
}
@@ refs.c: int refs_delete_ref(struct ref_store *refs, const char *msg,
if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
assert(refs == get_main_ref_store(the_repository));
- return delete_pseudoref(refname, old_oid);
-+ return ref_store_delete_pseudoref(refs, refname, old_oid);
++ return refs_delete_pseudoref(refs, refname, old_oid);
}

transaction = ref_store_transaction_begin(refs, &err);
@@ refs.c: int refs_update_ref(struct ref_store *refs, const char *msg,
if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
assert(refs == get_main_ref_store(the_repository));
- ret = write_pseudoref(refname, new_oid, old_oid, &err);
-+ ret = ref_store_write_pseudoref(refs, refname, new_oid, old_oid,
-+ &err);
++ ret = refs_write_pseudoref(refs, refname, new_oid, old_oid,
++ &err);
} else {
t = ref_store_transaction_begin(refs, &err);
if (!t ||
@@ refs.c: int head_ref(each_ref_fn fn, void *cb_data)
return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
}

-+int ref_store_write_pseudoref(struct ref_store *refs, const char *pseudoref,
-+ const struct object_id *oid,
-+ const struct object_id *old_oid,
-+ struct strbuf *err)
++int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
++ const struct object_id *oid,
++ const struct object_id *old_oid, struct strbuf *err)
+{
+ return refs->be->write_pseudoref(refs, pseudoref, oid, old_oid, err);
+}
+
-+int ref_store_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
-+ const struct object_id *old_oid)
++int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
++ const struct object_id *old_oid)
+{
+ return refs->be->delete_pseudoref(refs, pseudoref, old_oid);
+}
@@ refs.h: int update_ref(const char *msg, const char *refname,
+ and deletion as they cannot take part in normal transactional updates.
+ Pseudorefs should only be written for the main repository.
+*/
-+int ref_store_write_pseudoref(struct ref_store *refs, const char *pseudoref,
-+ const struct object_id *oid,
-+ const struct object_id *old_oid,
-+ struct strbuf *err);
-+int ref_store_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
-+ const struct object_id *old_oid);
++int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
++ const struct object_id *oid,
++ const struct object_id *old_oid, struct strbuf *err);
++int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
++ const struct object_id *old_oid);
++int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid);
+
int parse_hide_refs_config(const char *var, const char *value, const char *);

  -:  ----------- >  2:  d489806907b Make refs_ref_exists public
  -:  ----------- >  3:  60a82678a1b Treat BISECT_HEAD as a pseudo ref
  -:  ----------- >  4:  1a925f5671a Treat CHERRY_PICK_HEAD as a pseudo ref
  -:  ----------- >  5:  8f2ebf65b90 Treat REVERT_HEAD as a pseudo ref
  2:  c650f7e4345 =  6:  e0e1e3a558d Move REF_LOG_ONLY to refs-internal.h
  3:  0c953fce52a =  7:  27a980d1d9d Iterate over the "refs/" namespace in for_each_[raw]ref
  4:  206b7d329f8 =  8:  dcbd000e7f7 Add .gitattributes for the reftable/ directory
  5:  9a8e504a1d0 !  9:  718b646a54e Add reftable library
     @@ reftable/README.md (new)
      
       ## reftable/VERSION (new) ##
      @@
     -+commit bad78b6de70700933a2f93ebadaa37a5e852d9ea
     ++commit fe15c65dd95b6a13de92b4baf711e50338ef20ae
      +Author: Han-Wen Nienhuys <[email protected]>
     -+Date:   Mon May 18 20:12:57 2020 +0200
     ++Date:   Thu May 28 19:28:24 2020 +0200
      +
     -+    C: get rid of inner blocks for local scoping
     ++    C: add 'canary' to struct slice
     ++    
     ++    This enforces that all slices are initialized explicitly with
     ++    SLICE_INIT, for future compatibility with git's strbuf.
      
       ## reftable/basics.c (new) ##
      @@
     @@ reftable/block.c (new)
      +	bw->entries = 0;
      +	bw->restart_len = 0;
      +	bw->last_key.len = 0;
     ++	bw->last_key.canary = SLICE_CANARY;
      +}
      +
      +byte block_writer_type(struct block_writer *bw)
     @@ reftable/block.c (new)
      +   success */
      +int block_writer_add(struct block_writer *w, struct reftable_record *rec)
      +{
     -+	struct slice empty = { 0 };
     ++	struct slice empty = SLICE_INIT;
      +	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,
     ++		.canary = SLICE_CANARY,
      +	};
      +
      +	struct slice start = out;
      +
      +	bool restart = false;
     -+	struct slice key = { 0 };
     ++	struct slice key = SLICE_INIT;
      +	int n = 0;
      +
      +	reftable_record_key(rec, &key);
      +	n = reftable_encode_key(&restart, out, last, key,
      +				reftable_record_val_type(rec));
     -+	if (n < 0) {
     -+		goto err;
     -+	}
     ++	if (n < 0)
     ++		goto done;
      +	slice_consume(&out, n);
      +
      +	n = reftable_record_encode(rec, out, w->hash_size);
     -+	if (n < 0) {
     -+		goto err;
     -+	}
     ++	if (n < 0)
     ++		goto done;
      +	slice_consume(&out, n);
      +
      +	if (block_writer_register_restart(w, start.len - out.len, restart,
     -+					  key) < 0) {
     -+		goto err;
     -+	}
     ++					  key) < 0)
     ++		goto done;
      +
      +	slice_release(&key);
      +	return 0;
      +
     -+err:
     ++done:
      +	slice_release(&key);
      +	return -1;
      +}
     @@ reftable/block.c (new)
      +	if (restart) {
      +		rlen++;
      +	}
     -+	if (2 + 3 * rlen + n > w->block_size - w->next) {
     ++	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;
     @@ reftable/block.c (new)
      +	}
      +
      +	w->next += n;
     ++
      +	slice_copy(&w->last_key, key);
      +	w->entries++;
      +	return 0;
     @@ reftable/block.c (new)
      +
      +	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
      +		int block_header_skip = 4 + w->header_off;
     -+		struct slice compressed = { 0 };
     ++		struct slice compressed = SLICE_INIT;
      +		int zresult = 0;
      +		uLongf src_len = w->next - block_header_skip;
      +		slice_resize(&compressed, src_len);
     @@ reftable/block.c (new)
      +	byte typ = block->data[header_off];
      +	uint32_t sz = get_be24(block->data + header_off + 1);
      +
     -+	if (!reftable_is_block_type(typ)) {
     ++	uint16_t restart_count = 0;
     ++	uint32_t restart_start = 0;
     ++	byte *restart_bytes = NULL;
     ++
     ++	if (!reftable_is_block_type(typ))
      +		return REFTABLE_FORMAT_ERROR;
     -+	}
      +
      +	if (typ == BLOCK_TYPE_LOG) {
     -+		struct slice uncompressed = { 0 };
     ++		struct slice uncompressed = SLICE_INIT;
      +		int block_header_skip = 4 + header_off;
      +		uLongf dst_len = sz - block_header_skip; /* total size of dest
      +							    buffer. */
     @@ reftable/block.c (new)
      +			return REFTABLE_ZLIB_ERROR;
      +		}
      +
     -+		if (dst_len + block_header_skip != sz) {
     ++		if (dst_len + block_header_skip != sz)
      +			return REFTABLE_FORMAT_ERROR;
     -+		}
      +
      +		/* We're done with the input data. */
      +		reftable_block_done(block);
     @@ reftable/block.c (new)
      +		full_block_size = sz;
      +	}
      +
     -+	{
     -+		uint16_t restart_count = get_be16(block->data + sz - 2);
     -+		uint32_t restart_start = sz - 2 - 3 * restart_count;
     ++	restart_count = get_be16(block->data + sz - 2);
     ++	restart_start = sz - 2 - 3 * restart_count;
     ++	restart_bytes = block->data + restart_start;
      +
     -+		byte *restart_bytes = block->data + restart_start;
     ++	/* transfer ownership. */
     ++	br->block = *block;
     ++	block->data = NULL;
     ++	block->len = 0;
      +
     -+		/* 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;
     -+	}
     ++	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;
      +}
     @@ reftable/block.c (new)
      +	struct slice in = {
      +		.buf = a->r->block.data + off,
      +		.len = a->r->block_len - off,
     ++		.canary = SLICE_CANARY,
      +	};
      +
      +	/* the restart key is verbatim in the block, so this could avoid the
      +	   alloc for decoding the key */
     -+	struct slice rkey = { 0 };
     -+	struct slice last_key = { 0 };
     ++	struct slice rkey = SLICE_INIT;
     ++	struct slice last_key = SLICE_INIT;
      +	byte unused_extra;
      +	int n = reftable_decode_key(&rkey, &unused_extra, last_key, in);
     ++	int result;
      +	if (n < 0) {
      +		a->error = 1;
      +		return -1;
      +	}
      +
     -+	{
     -+		int result = slice_cmp(a->key, rkey);
     -+		slice_release(&rkey);
     -+		return result;
     -+	}
     ++	result = slice_cmp(a->key, rkey);
     ++	slice_release(&rkey);
     ++	return result;
      +}
      +
      +void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
     @@ reftable/block.c (new)
      +	struct slice in = {
      +		.buf = it->br->block.data + it->next_off,
      +		.len = it->br->block_len - it->next_off,
     ++		.canary = SLICE_CANARY,
      +	};
      +	struct slice start = in;
     -+	struct slice key = { 0 };
     ++	struct slice key = SLICE_INIT;
      +	byte extra = 0;
      +	int n = 0;
      +
     -+	if (it->next_off >= it->br->block_len) {
     ++	if (it->next_off >= it->br->block_len)
      +		return 1;
     -+	}
      +
      +	n = reftable_decode_key(&key, &extra, it->last_key, in);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +
      +	slice_consume(&in, n);
      +	n = reftable_record_decode(rec, key, extra, in, it->br->hash_size);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&in, n);
      +
      +	slice_copy(&it->last_key, key);
     @@ reftable/block.c (new)
      +
      +int block_reader_first_key(struct block_reader *br, struct slice *key)
      +{
     -+	struct slice empty = { 0 };
     ++	struct slice empty = SLICE_INIT;
      +	int off = br->header_off + 4;
      +	struct slice in = {
      +		.buf = br->block.data + off,
      +		.len = br->block_len - off,
     ++		.canary = SLICE_CANARY,
      +	};
      +
      +	byte extra = 0;
      +	int n = reftable_decode_key(key, &extra, empty, in);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return n;
     -+	}
     ++
      +	return 0;
      +}
      +
     @@ reftable/block.c (new)
      +		.r = br,
      +	};
      +	struct reftable_record rec = reftable_new_record(block_reader_type(br));
     -+	struct slice key = { 0 };
     ++	struct slice key = SLICE_INIT;
      +	int err = 0;
     -+	struct block_iter next = { 0 };
     ++	struct block_iter next = {
     ++		.last_key = SLICE_INIT,
     ++	};
      +
      +	int i = binsearch(br->restart_count, &restart_key_less, &args);
      +	if (args.error) {
      +		err = REFTABLE_FORMAT_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	it->br = br;
     @@ reftable/block.c (new)
      +	while (true) {
      +		block_iter_copy_from(&next, it);
      +		err = block_iter_next(&next, &rec);
     -+		if (err < 0) {
     -+			goto exit;
     -+		}
     ++		if (err < 0)
     ++			goto done;
      +
      +		reftable_record_key(&rec, &key);
      +		if (err > 0 || slice_cmp(key, want) >= 0) {
      +			err = 0;
     -+			goto exit;
     ++			goto done;
      +		}
      +
      +		block_iter_copy_from(it, &next);
      +	}
      +
     -+exit:
     ++done:
      +	slice_release(&key);
      +	slice_release(&next.last_key);
      +	reftable_record_destroy(&rec);
     @@ reftable/block.h (new)
      +	struct slice last_key;
      +};
      +
     -+/* initializes a block reader */
     ++/* initializes a block reader. */
      +int block_reader_init(struct block_reader *br, struct reftable_block *bl,
      +		      uint32_t header_off, uint32_t table_block_size,
      +		      int hash_size);
     @@ reftable/file.c (new)
      +	struct file_block_source *b = (struct file_block_source *)v;
      +	assert(off + size <= b->size);
      +	dest->data = reftable_malloc(size);
     -+	if (pread(b->fd, dest->data, size, off) != size) {
     ++	if (pread(b->fd, dest->data, size, off) != size)
      +		return -1;
     -+	}
      +	dest->len = size;
      +	return size;
      +}
     @@ reftable/file.c (new)
      +	}
      +
      +	err = fstat(fd, &st);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return -1;
     -+	}
      +
      +	p = reftable_calloc(sizeof(struct file_block_source));
      +	p->size = st.st_size;
     @@ reftable/iter.c (new)
      +
      +static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
      +{
     ++	uint64_t off;
     ++	int err = 0;
      +	if (it->offset_idx == it->offset_len) {
      +		it->finished = true;
      +		return 1;
     @@ reftable/iter.c (new)
      +
      +	reftable_block_done(&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 REFTABLE_FORMAT_ERROR;
     -+		}
     ++	off = it->offsets[it->offset_idx++];
     ++	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 REFTABLE_FORMAT_ERROR;
      +	}
      +	block_reader_start(&it->block_reader, &it->cur);
      +	return 0;
     @@ reftable/iter.c (new)
      +			       struct reftable_reader *r, byte *oid,
      +			       int oid_len, uint64_t *offsets, int offset_len)
      +{
     ++	struct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT;
      +	struct indexed_table_ref_iter *itr =
      +		reftable_calloc(sizeof(struct indexed_table_ref_iter));
      +	int err = 0;
      +
     ++	*itr = empty;
      +	itr->r = r;
      +	slice_resize(&itr->oid, oid_len);
      +	memcpy(itr->oid.buf, oid, oid_len);
     @@ reftable/iter.h (new)
      +	struct slice oid;
      +	struct reftable_iterator it;
      +};
     ++#define FILTERING_REF_ITERATOR_INIT \
     ++	{                           \
     ++		.oid = SLICE_INIT   \
     ++	}
      +
      +void iterator_from_filtering_ref_iterator(struct reftable_iterator *,
      +					  struct filtering_ref_iterator *);
     @@ reftable/iter.h (new)
      +	bool finished;
      +};
      +
     ++#define INDEXED_TABLE_REF_ITER_INIT                                   \
     ++	{                                                             \
     ++		.cur = { .last_key = SLICE_INIT }, .oid = SLICE_INIT, \
     ++	}
     ++
      +void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,
      +					  struct indexed_table_ref_iter *itr);
      +int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
     @@ reftable/merged.c (new)
      +		.index = idx,
      +	};
      +	int err = iterator_next(&mi->stack[idx], &rec);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	if (err > 0) {
      +		reftable_iterator_destroy(&mi->stack[idx]);
     @@ reftable/merged.c (new)
      +
      +static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)
      +{
     -+	if (iterator_is_null(&mi->stack[idx])) {
     ++	if (iterator_is_null(&mi->stack[idx]))
      +		return 0;
     -+	}
      +	return merged_iter_advance_nonnull_subiter(mi, idx);
      +}
      +
      +static int merged_iter_next_entry(struct merged_iter *mi,
      +				  struct reftable_record *rec)
      +{
     -+	struct slice entry_key = { 0 };
     ++	struct slice entry_key = SLICE_INIT;
      +	struct pq_entry entry = { 0 };
      +	int err = 0;
      +
     -+	if (merged_iter_pqueue_is_empty(mi->pq)) {
     ++	if (merged_iter_pqueue_is_empty(mi->pq))
      +		return 1;
     -+	}
      +
      +	entry = merged_iter_pqueue_remove(&mi->pq);
      +	err = merged_iter_advance_subiter(mi, entry.index);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	/*
      +	  One can also use reftable as datacenter-local storage, where the ref
     @@ reftable/merged.c (new)
      +	reftable_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 = { 0 };
     ++		struct slice k = SLICE_INIT;
      +		int err = 0, cmp = 0;
      +
      +		reftable_record_key(&top.rec, &k);
     @@ reftable/merged.c (new)
      +static int merged_iter_next_void(void *p, struct reftable_record *rec)
      +{
      +	struct merged_iter *mi = (struct merged_iter *)p;
     -+	if (merged_iter_pqueue_is_empty(mi->pq)) {
     ++	if (merged_iter_pqueue_is_empty(mi->pq))
      +		return 1;
     -+	}
      +
      +	return merged_iter_next(mi, rec);
      +}
     @@ reftable/merged.c (new)
      +	if (err < 0) {
      +		merged_iter_close(&merged);
      +		return err;
     -+	}
     -+
     -+	{
     ++	} else {
      +		struct merged_iter *p =
      +			reftable_malloc(sizeof(struct merged_iter));
      +		*p = merged;
     @@ reftable/pq.c (new)
      +
      +int pq_less(struct pq_entry a, struct pq_entry b)
      +{
     -+	struct slice ak = { 0 };
     -+	struct slice bk = { 0 };
     ++	struct slice ak = SLICE_INIT;
     ++	struct slice bk = SLICE_INIT;
      +	int cmp = 0;
      +	reftable_record_key(&a.rec, &ak);
      +	reftable_record_key(&b.rec, &bk);
     @@ reftable/pq.c (new)
      +	slice_release(&ak);
      +	slice_release(&bk);
      +
     -+	if (cmp == 0) {
     ++	if (cmp == 0)
      +		return a.index > b.index;
     -+	}
      +
      +	return cmp < 0;
      +}
     @@ reftable/reader.c (new)
      +			    struct reftable_block *dest, uint64_t off,
      +			    uint32_t sz)
      +{
     -+	if (off >= r->size) {
     ++	if (off >= r->size)
      +		return 0;
     -+	}
      +
      +	if (off + sz > r->size) {
      +		sz = r->size - off;
     @@ reftable/reader.c (new)
      +
      +	if (memcmp(f, "REFT", 4)) {
      +		err = REFTABLE_FORMAT_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +	f += 4;
      +
      +	if (memcmp(footer, header, header_size(r->version))) {
      +		err = REFTABLE_FORMAT_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	f++;
     @@ reftable/reader.c (new)
      +			break;
      +		default:
      +			err = REFTABLE_FORMAT_ERROR;
     -+			goto exit;
     ++			goto done;
      +		}
      +		f += 4;
      +	}
     @@ reftable/reader.c (new)
      +	f += 4;
      +	if (computed_crc != file_crc) {
      +		err = REFTABLE_FORMAT_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	first_block_typ = header[header_size(r->version)];
     @@ reftable/reader.c (new)
      +				  r->log_offsets.offset > 0);
      +	r->obj_offsets.present = r->obj_offsets.offset > 0;
      +	err = 0;
     -+exit:
     ++done:
      +	return err;
      +}
      +
     @@ reftable/reader.c (new)
      +	err = block_source_read_block(source, &header, 0, header_size(2) + 1);
      +	if (err != header_size(2) + 1) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	if (memcmp(header.data, "REFT", 4)) {
      +		err = REFTABLE_FORMAT_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +	r->version = header.data[4];
      +	if (r->version != 1 && r->version != 2) {
      +		err = REFTABLE_FORMAT_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	r->size = block_source_size(source) - footer_size(r->version);
     @@ reftable/reader.c (new)
      +				      footer_size(r->version));
      +	if (err != footer_size(r->version)) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = parse_footer(r, footer.data, header.data);
     -+exit:
     ++done:
      +	reftable_block_done(&footer);
      +	reftable_block_done(&header);
      +	return err;
     @@ reftable/reader.c (new)
      +	struct block_iter bi;
      +	bool finished;
      +};
     ++#define TABLE_ITER_INIT                         \
     ++	{                                       \
     ++		.bi = {.last_key = SLICE_INIT } \
     ++	}
      +
      +static void table_iter_copy_from(struct table_iter *dest,
      +				 struct table_iter *src)
     @@ reftable/reader.c (new)
      +	uint32_t header_off = next_off ? 0 : header_size(r->version);
      +	int32_t block_size = 0;
      +
     -+	if (next_off >= r->size) {
     ++	if (next_off >= r->size)
      +		return 1;
     -+	}
      +
      +	err = reader_get_block(r, &block, next_off, guess_block_size);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	block_size = extract_block_size(block.data, &block_typ, next_off,
      +					r->version);
     -+	if (block_size < 0) {
     ++	if (block_size < 0)
      +		return block_size;
     -+	}
      +
      +	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
      +		reftable_block_done(&block);
     @@ reftable/reader.c (new)
      +		dest->finished = true;
      +		return 1;
      +	}
     -+	if (err != 0) {
     ++	if (err != 0)
      +		return err;
     -+	}
     -+
     -+	{
     ++	else {
      +		struct block_reader *brp =
      +			reftable_malloc(sizeof(struct block_reader));
      +		*brp = br;
     @@ reftable/reader.c (new)
      +
      +static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)
      +{
     -+	if (reftable_record_type(rec) != ti->typ) {
     ++	if (reftable_record_type(rec) != ti->typ)
      +		return REFTABLE_API_ERROR;
     -+	}
      +
      +	while (true) {
     -+		struct table_iter next = { 0 };
     ++		struct table_iter next = TABLE_ITER_INIT;
      +		int err = 0;
      +		if (ti->finished) {
      +			return 1;
     @@ reftable/reader.c (new)
      +	struct block_reader *brp = NULL;
      +
      +	int err = reader_init_block_reader(r, &br, off, typ);
     -+	if (err != 0) {
     ++	if (err != 0)
      +		return err;
     -+	}
      +
      +	brp = reftable_malloc(sizeof(struct block_reader));
      +	*brp = br;
     @@ reftable/reader.c (new)
      +{
      +	struct reftable_record rec =
      +		reftable_new_record(reftable_record_type(want));
     -+	struct slice want_key = { 0 };
     -+	struct slice got_key = { 0 };
     -+	struct table_iter next = { 0 };
     ++	struct slice want_key = SLICE_INIT;
     ++	struct slice got_key = SLICE_INIT;
     ++	struct table_iter next = TABLE_ITER_INIT;
      +	int err = -1;
     ++
      +	reftable_record_key(want, &want_key);
      +
      +	while (true) {
      +		err = table_iter_next_block(&next, ti);
     -+		if (err < 0) {
     -+			goto exit;
     -+		}
     ++		if (err < 0)
     ++			goto done;
      +
      +		if (err > 0) {
      +			break;
      +		}
      +
      +		err = block_reader_first_key(next.bi.br, &got_key);
     -+		if (err < 0) {
     -+			goto exit;
     -+		}
     -+		{
     -+			int cmp = slice_cmp(got_key, want_key);
     -+			if (cmp > 0) {
     -+				table_iter_block_done(&next);
     -+				break;
     -+			}
     ++		if (err < 0)
     ++			goto done;
     ++
     ++		if (slice_cmp(got_key, want_key) > 0) {
     ++			table_iter_block_done(&next);
     ++			break;
      +		}
      +
      +		table_iter_block_done(ti);
     @@ reftable/reader.c (new)
      +	}
      +
      +	err = block_iter_seek(&ti->bi, want_key);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +	err = 0;
      +
     -+exit:
     ++done:
      +	block_iter_close(&next.bi);
      +	reftable_record_destroy(&rec);
      +	slice_release(&want_key);
     @@ reftable/reader.c (new)
      +			       struct reftable_iterator *it,
      +			       struct reftable_record *rec)
      +{
     -+	struct reftable_index_record want_index = { 0 };
     ++	struct reftable_index_record want_index = { .last_key = SLICE_INIT };
      +	struct reftable_record want_index_rec = { 0 };
     -+	struct reftable_index_record index_result = { 0 };
     ++	struct reftable_index_record index_result = { .last_key = SLICE_INIT };
      +	struct reftable_record index_result_rec = { 0 };
     -+	struct table_iter index_iter = { 0 };
     -+	struct table_iter next = { 0 };
     ++	struct table_iter index_iter = TABLE_ITER_INIT;
     ++	struct table_iter next = TABLE_ITER_INIT;
      +	int err = 0;
      +
      +	reftable_record_key(rec, &want_index.last_key);
     @@ reftable/reader.c (new)
      +	reftable_record_from_index(&index_result_rec, &index_result);
      +
      +	err = reader_start(r, &index_iter, reftable_record_type(rec), true);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	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;
     -+		}
     ++		if (err != 0)
     ++			goto done;
      +
      +		err = reader_table_iter_at(r, &next, index_result.offset, 0);
     -+		if (err != 0) {
     -+			goto exit;
     -+		}
     ++		if (err != 0)
     ++			goto done;
      +
      +		err = block_iter_seek(&next.bi, want_index.last_key);
     -+		if (err < 0) {
     -+			goto exit;
     -+		}
     ++		if (err < 0)
     ++			goto done;
      +
      +		if (next.typ == reftable_record_type(rec)) {
      +			err = 0;
     @@ reftable/reader.c (new)
      +	}
      +
      +	if (err == 0) {
     ++		struct table_iter empty = TABLE_ITER_INIT;
      +		struct table_iter *malloced =
      +			reftable_calloc(sizeof(struct table_iter));
     ++		*malloced = empty;
      +		table_iter_copy_from(malloced, &next);
      +		iterator_from_table_iter(it, malloced);
      +	}
     -+exit:
     ++done:
      +	block_iter_close(&next.bi);
      +	table_iter_close(&index_iter);
      +	reftable_record_clear(&want_index_rec);
     @@ reftable/reader.c (new)
      +	struct reftable_reader_offsets *offs =
      +		reader_offsets_for(r, reftable_record_type(rec));
      +	uint64_t idx = offs->index_offset;
     -+	struct table_iter ti = { 0 };
     ++	struct table_iter ti = TABLE_ITER_INIT;
      +	int err = 0;
     -+	if (idx > 0) {
     ++	if (idx > 0)
      +		return reader_seek_indexed(r, it, rec);
     -+	}
      +
      +	err = reader_start(r, &ti, reftable_record_type(rec), false);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +	err = reader_seek_linear(r, &ti, rec);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
     -+
     -+	{
     ++	else {
      +		struct table_iter *p =
      +			reftable_malloc(sizeof(struct table_iter));
      +		*p = ti;
     @@ reftable/reader.c (new)
      +	/* Look through the reverse index. */
      +	reftable_record_from_obj(&want_rec, &want);
      +	err = reader_seek(r, &oit, &want_rec);
     -+	if (err != 0) {
     -+		goto exit;
     -+	}
     ++	if (err != 0)
     ++		goto done;
      +
      +	/* read out the reftable_obj_record */
      +	reftable_record_from_obj(&got_rec, &got);
      +	err = iterator_next(&oit, &got_rec);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	if (err > 0 ||
      +	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
      +		/* didn't find it; return empty iterator */
      +		iterator_set_empty(it);
      +		err = 0;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
      +					 got.offsets, got.offset_len);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +	got.offsets = NULL;
      +	iterator_from_indexed_table_ref_iter(it, itr);
      +
     -+exit:
     ++done:
      +	reftable_iterator_destroy(&oit);
      +	reftable_record_clear(&got_rec);
      +	return err;
     @@ reftable/reader.c (new)
      +					      struct reftable_iterator *it,
      +					      byte *oid)
      +{
     ++	struct table_iter ti_empty = TABLE_ITER_INIT;
      +	struct table_iter *ti = reftable_calloc(sizeof(struct table_iter));
      +	struct filtering_ref_iterator *filter = NULL;
     ++	struct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT;
      +	int oid_len = hash_size(r->hash_id);
     -+	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
     ++	int err;
     ++
     ++	*ti = ti_empty;
     ++	err = reader_start(r, ti, BLOCK_TYPE_REF, false);
      +	if (err < 0) {
      +		reftable_free(ti);
      +		return err;
      +	}
      +
     -+	filter = reftable_calloc(sizeof(struct filtering_ref_iterator));
     ++	filter = reftable_malloc(sizeof(struct filtering_ref_iterator));
     ++	*filter = empty;
      +	slice_resize(&filter->oid, oid_len);
      +	memcpy(filter->oid.buf, oid, oid_len);
      +	reftable_table_from_reader(&filter->tab, r);
     @@ reftable/reader.c (new)
      +int reftable_reader_refs_for(struct reftable_reader *r,
      +			     struct reftable_iterator *it, byte *oid)
      +{
     -+	if (r->obj_offsets.present) {
     ++	if (r->obj_offsets.present)
      +		return reftable_reader_refs_for_indexed(r, it, oid);
     -+	}
      +	return reftable_reader_refs_for_unindexed(r, it, oid);
      +}
      +
     @@ reftable/record.c (new)
      +	int ptr = 0;
      +	uint64_t val;
      +
     -+	if (in.len == 0) {
     ++	if (in.len == 0)
      +		return -1;
     -+	}
      +	val = in.buf[ptr] & 0x7f;
      +
      +	while (in.buf[ptr] & 0x80) {
     @@ reftable/record.c (new)
      +	}
      +
      +	n = sizeof(buf) - i - 1;
     -+	if (dest.len < n) {
     ++	if (dest.len < n)
      +		return -1;
     -+	}
      +	memcpy(dest.buf, &buf[i + 1], n);
      +	return n;
      +}
     @@ reftable/record.c (new)
      +	int start_len = in.len;
      +	uint64_t tsize = 0;
      +	int n = get_var_int(&tsize, in);
     -+	if (n <= 0) {
     ++	if (n <= 0)
      +		return -1;
     -+	}
      +	slice_consume(&in, n);
     -+	if (in.len < tsize) {
     ++	if (in.len < tsize)
      +		return -1;
     -+	}
      +
      +	slice_resize(dest, tsize + 1);
      +	dest->buf[tsize] = 0;
     @@ reftable/record.c (new)
      +	struct slice start = s;
      +	int l = strlen(str);
      +	int n = put_var_int(s, l);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
     -+	if (s.len < l) {
     ++	if (s.len < l)
      +		return -1;
     -+	}
      +	memcpy(s.buf, str, l);
      +	slice_consume(&s, l);
      +
     @@ reftable/record.c (new)
      +	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) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&dest, n);
      +
      +	*restart = (prefix_len == 0);
      +
      +	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&dest, n);
      +
     -+	if (dest.len < suffix_len) {
     ++	if (dest.len < suffix_len)
      +		return -1;
     -+	}
      +	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
      +	slice_consume(&dest, suffix_len);
      +
     @@ reftable/record.c (new)
      +	uint64_t prefix_len = 0;
      +	uint64_t suffix_len = 0;
      +	int n = get_var_int(&prefix_len, in);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&in, n);
      +
     -+	if (prefix_len > last_key.len) {
     ++	if (prefix_len > last_key.len)
      +		return -1;
     -+	}
      +
      +	n = get_var_int(&suffix_len, in);
     -+	if (n <= 0) {
     ++	if (n <= 0)
      +		return -1;
     -+	}
      +	slice_consume(&in, n);
      +
      +	*extra = (byte)(suffix_len & 0x7);
      +	suffix_len >>= 3;
      +
     -+	if (in.len < suffix_len) {
     ++	if (in.len < suffix_len)
      +		return -1;
     -+	}
      +
      +	slice_resize(key, suffix_len + prefix_len);
      +	memcpy(key->buf, last_key.buf, prefix_len);
     @@ reftable/record.c (new)
      +
      +static char hexdigit(int c)
      +{
     -+	if (c <= 9) {
     ++	if (c <= 9)
      +		return '0' + c;
     -+	}
      +	return 'a' + (c - 10);
      +}
      +
     @@ reftable/record.c (new)
      +		} else {
      +			return 1;
      +		}
     -+	} else if (r->target != NULL) {
     ++	} else if (r->target != NULL)
      +		return 3;
     -+	}
      +	return 0;
      +}
      +
     @@ reftable/record.c (new)
      +	struct slice start = s;
      +	int n = put_var_int(s, r->update_index);
      +	assert(hash_size > 0);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
      +
      +	if (r->value != NULL) {
     @@ reftable/record.c (new)
      +	bool seen_target = false;
      +
      +	int n = get_var_int(&r->update_index, in);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return n;
     -+	}
      +	assert(hash_size > 0);
      +
      +	slice_consume(&in, n);
     @@ reftable/record.c (new)
      +		slice_consume(&in, hash_size);
      +		break;
      +	case 3: {
     -+		struct slice dest = { 0 };
     ++		struct slice dest = SLICE_INIT;
      +		int n = decode_string(&dest, in);
      +		if (n < 0) {
      +			return -1;
     @@ reftable/record.c (new)
      +	struct reftable_obj_record *obj = (struct reftable_obj_record *)rec;
      +	const struct reftable_obj_record *src =
      +		(const struct reftable_obj_record *)src_rec;
     ++	int olen;
      +
      +	reftable_obj_record_clear(obj);
      +	*obj = *src;
      +	obj->hash_prefix = reftable_malloc(obj->hash_prefix_len);
      +	memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
      +
     -+	{
     -+		int olen = obj->offset_len * sizeof(uint64_t);
     -+		obj->offsets = reftable_malloc(olen);
     -+		memcpy(obj->offsets, src->offsets, olen);
     -+	}
     ++	olen = obj->offset_len * sizeof(uint64_t);
     ++	obj->offsets = reftable_malloc(olen);
     ++	memcpy(obj->offsets, src->offsets, olen);
      +}
      +
      +static byte reftable_obj_record_val_type(const void *rec)
      +{
      +	struct reftable_obj_record *r = (struct reftable_obj_record *)rec;
     -+	if (r->offset_len > 0 && r->offset_len < 8) {
     ++	if (r->offset_len > 0 && r->offset_len < 8)
      +		return r->offset_len;
     -+	}
      +	return 0;
      +}
      +
     @@ reftable/record.c (new)
      +		}
      +		slice_consume(&s, n);
      +	}
     -+	if (r->offset_len == 0) {
     ++	if (r->offset_len == 0)
      +		return start.len - s.len;
     -+	}
      +	n = put_var_int(s, r->offsets[0]);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
      +
      +	last = r->offsets[0];
     @@ reftable/record.c (new)
      +
      +	r->offsets = NULL;
      +	r->offset_len = 0;
     -+	if (count == 0) {
     ++	if (count == 0)
      +		return start.len - in.len;
     -+	}
      +
      +	r->offsets = reftable_malloc(count * sizeof(uint64_t));
      +	r->offset_len = count;
      +
      +	n = get_var_int(&r->offsets[0], in);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return n;
     -+	}
      +	slice_consume(&in, n);
      +
      +	last = r->offsets[0];
     @@ reftable/record.c (new)
      +	int n = 0;
      +	byte *oldh = r->old_hash;
      +	byte *newh = r->new_hash;
     -+	if (reftable_log_record_is_deletion(r)) {
     ++	if (reftable_log_record_is_deletion(r))
      +		return 0;
     -+	}
      +
      +	if (oldh == NULL) {
      +		oldh = zero;
     @@ reftable/record.c (new)
      +		newh = zero;
      +	}
      +
     -+	if (s.len < 2 * hash_size) {
     ++	if (s.len < 2 * hash_size)
      +		return -1;
     -+	}
      +
      +	memcpy(s.buf, oldh, hash_size);
      +	memcpy(s.buf + hash_size, newh, hash_size);
      +	slice_consume(&s, 2 * hash_size);
      +
      +	n = encode_string(r->name ? r->name : "", s);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
      +
      +	n = encode_string(r->email ? r->email : "", s);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
      +
      +	n = put_var_int(s, r->time);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
      +
     -+	if (s.len < 2) {
     ++	if (s.len < 2)
      +		return -1;
     -+	}
      +
      +	put_be16(s.buf, r->tz_offset);
      +	slice_consume(&s, 2);
      +
      +	n = encode_string(r->message ? r->message : "", s);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return -1;
     -+	}
      +	slice_consume(&s, n);
      +
      +	return start.len - s.len;
     @@ reftable/record.c (new)
      +	struct reftable_log_record *r = (struct reftable_log_record *)rec;
      +	uint64_t max = 0;
      +	uint64_t ts = 0;
     -+	struct slice dest = { 0 };
     ++	struct slice dest = SLICE_INIT;
      +	int n;
      +
     -+	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
     ++	if (key.len <= 9 || key.buf[key.len - 9] != 0)
      +		return REFTABLE_FORMAT_ERROR;
     -+	}
      +
      +	r->ref_name = reftable_realloc(r->ref_name, key.len - 8);
      +	memcpy(r->ref_name, key.buf, key.len - 8);
     @@ reftable/record.c (new)
      +		return 0;
      +	}
      +
     -+	if (in.len < 2 * hash_size) {
     ++	if (in.len < 2 * hash_size)
      +		return REFTABLE_FORMAT_ERROR;
     -+	}
      +
      +	r->old_hash = reftable_realloc(r->old_hash, hash_size);
      +	r->new_hash = reftable_realloc(r->new_hash, hash_size);
     @@ reftable/record.c (new)
      +	slice_consume(&in, 2 * hash_size);
      +
      +	n = decode_string(&dest, in);
     -+	if (n < 0) {
     -+		goto error;
     -+	}
     ++	if (n < 0)
     ++		goto done;
      +	slice_consume(&in, n);
      +
      +	r->name = reftable_realloc(r->name, dest.len + 1);
     @@ reftable/record.c (new)
      +
      +	slice_resize(&dest, 0);
      +	n = decode_string(&dest, in);
     -+	if (n < 0) {
     -+		goto error;
     -+	}
     ++	if (n < 0)
     ++		goto done;
      +	slice_consume(&in, n);
      +
      +	r->email = reftable_realloc(r->email, dest.len + 1);
     @@ reftable/record.c (new)
      +
      +	ts = 0;
      +	n = get_var_int(&ts, in);
     -+	if (n < 0) {
     -+		goto error;
     -+	}
     ++	if (n < 0)
     ++		goto done;
      +	slice_consume(&in, n);
      +	r->time = ts;
     -+	if (in.len < 2) {
     -+		goto error;
     -+	}
     ++	if (in.len < 2)
     ++		goto done;
      +
      +	r->tz_offset = get_be16(in.buf);
      +	slice_consume(&in, 2);
      +
      +	slice_resize(&dest, 0);
      +	n = decode_string(&dest, in);
     -+	if (n < 0) {
     -+		goto error;
     -+	}
     ++	if (n < 0)
     ++		goto done;
      +	slice_consume(&in, n);
      +
      +	r->message = reftable_realloc(r->message, dest.len + 1);
     @@ reftable/record.c (new)
      +	slice_release(&dest);
      +	return start.len - in.len;
      +
     -+error:
     ++done:
      +	slice_release(&dest);
      +	return REFTABLE_FORMAT_ERROR;
      +}
     @@ reftable/record.c (new)
      +static bool null_streq(char *a, char *b)
      +{
      +	char *empty = "";
     -+	if (a == NULL) {
     ++	if (a == NULL)
      +		a = empty;
     -+	}
     -+	if (b == NULL) {
     ++
     ++	if (b == NULL)
      +		b = empty;
     -+	}
     ++
      +	return 0 == strcmp(a, b);
      +}
      +
      +static bool zero_hash_eq(byte *a, byte *b, int sz)
      +{
     -+	if (a == NULL) {
     ++	if (a == NULL)
      +		a = zero;
     -+	}
     -+	if (b == NULL) {
     ++
     ++	if (b == NULL)
      +		b = zero;
     -+	}
     ++
      +	return !memcmp(a, b, sz);
      +}
      +
     @@ reftable/record.c (new)
      +		return rec;
      +	}
      +	case BLOCK_TYPE_INDEX: {
     ++		struct reftable_index_record empty = { .last_key = SLICE_INIT };
      +		struct reftable_index_record *r =
      +			reftable_calloc(sizeof(struct reftable_index_record));
     ++		*r = empty;
      +		reftable_record_from_index(&rec, r);
      +		return rec;
      +	}
     @@ reftable/record.c (new)
      +	struct slice start = out;
      +
      +	int n = put_var_int(out, r->offset);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return n;
     -+	}
      +
      +	slice_consume(&out, n);
      +
     @@ reftable/record.c (new)
      +	slice_copy(&r->last_key, key);
      +
      +	n = get_var_int(&r->offset, in);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return n;
     -+	}
      +
      +	slice_consume(&in, n);
      +	return start.len - in.len;
     @@ reftable/record.c (new)
      +
      +static bool hash_equal(byte *a, byte *b, int hash_size)
      +{
     -+	if (a != NULL && b != NULL) {
     ++	if (a != NULL && b != NULL)
      +		return !memcmp(a, b, hash_size);
     -+	}
      +
      +	return a == b;
      +}
      +
      +static bool str_equal(char *a, char *b)
      +{
     -+	if (a != NULL && b != NULL) {
     ++	if (a != NULL && b != NULL)
      +		return 0 == strcmp(a, b);
     -+	}
      +
      +	return a == b;
      +}
     @@ reftable/record.c (new)
      +	struct reftable_log_record *lb = (struct reftable_log_record *)b;
      +
      +	int cmp = strcmp(la->ref_name, lb->ref_name);
     -+	if (cmp) {
     ++	if (cmp)
      +		return cmp;
     -+	}
     -+	if (la->update_index > lb->update_index) {
     ++	if (la->update_index > lb->update_index)
      +		return -1;
     -+	}
      +	return (la->update_index < lb->update_index) ? 1 : 0;
      +}
      +
     @@ reftable/refname.c (new)
      +		};
      +		int idx = binsearch(mod->add_len, find_name, &arg);
      +		if (idx < mod->add_len &&
     -+		    !strncmp(prefix, mod->add[idx], strlen(prefix))) {
     -+			goto exit;
     -+		}
     ++		    !strncmp(prefix, mod->add[idx], strlen(prefix)))
     ++			goto done;
      +	}
      +	err = reftable_table_seek_ref(&mod->tab, &it, prefix);
     -+	if (err) {
     -+		goto exit;
     -+	}
     ++	if (err)
     ++		goto done;
      +
      +	while (true) {
      +		err = reftable_iterator_next_ref(&it, &ref);
     -+		if (err) {
     -+			goto exit;
     -+		}
     ++		if (err)
     ++			goto done;
      +
      +		if (mod->del_len > 0) {
      +			struct find_arg arg = {
     @@ reftable/refname.c (new)
      +
      +		if (strncmp(ref.ref_name, prefix, strlen(prefix))) {
      +			err = 1;
     -+			goto exit;
     ++			goto done;
      +		}
      +		err = 0;
     -+		goto exit;
     ++		goto done;
      +	}
      +
     -+exit:
     ++done:
      +	reftable_ref_record_clear(&ref);
      +	reftable_iterator_destroy(&it);
      +	return err;
     @@ reftable/refname.c (new)
      +
      +int modification_validate(struct modification *mod)
      +{
     -+	struct slice slashed = { 0 };
     ++	struct slice slashed = SLICE_INIT;
      +	int err = 0;
      +	int i = 0;
      +	for (; i < mod->add_len; i++) {
      +		err = validate_ref_name(mod->add[i]);
     -+		if (err) {
     -+			goto exit;
     -+		}
     ++		if (err)
     ++			goto done;
      +		slice_set_string(&slashed, mod->add[i]);
      +		slice_addstr(&slashed, "/");
      +
     @@ reftable/refname.c (new)
      +			mod, slice_as_string(&slashed));
      +		if (err == 0) {
      +			err = REFTABLE_NAME_CONFLICT;
     -+			goto exit;
     -+		}
     -+		if (err < 0) {
     -+			goto exit;
     ++			goto done;
      +		}
     ++		if (err < 0)
     ++			goto done;
      +
      +		slice_set_string(&slashed, mod->add[i]);
      +		while (slashed.len) {
     @@ reftable/refname.c (new)
      +						   slice_as_string(&slashed));
      +			if (err == 0) {
      +				err = REFTABLE_NAME_CONFLICT;
     -+				goto exit;
     -+			}
     -+			if (err < 0) {
     -+				goto exit;
     ++				goto done;
      +			}
     ++			if (err < 0)
     ++				goto done;
      +		}
      +	}
      +	err = 0;
     -+exit:
     ++done:
      +	slice_release(&slashed);
      +	return err;
      +}
     @@ reftable/reftable.c (new)
      +{
      +	struct reftable_iterator it = { 0 };
      +	int err = reftable_table_seek_ref(tab, &it, name);
     -+	if (err) {
     -+		goto exit;
     -+	}
     ++	if (err)
     ++		goto done;
      +
      +	err = reftable_iterator_next_ref(&it, ref);
     -+	if (err) {
     -+		goto exit;
     -+	}
     ++	if (err)
     ++		goto done;
      +
      +	if (strcmp(ref->ref_name, name) ||
      +	    reftable_ref_record_is_deletion(ref)) {
      +		reftable_ref_record_clear(ref);
      +		err = 1;
     -+		goto exit;
     ++		goto done;
      +	}
      +
     -+exit:
     ++done:
      +	reftable_iterator_destroy(&it);
      +	return err;
      +}
     @@ reftable/slice.c (new)
      +
      +#include "reftable.h"
      +
     ++struct slice reftable_empty_slice = SLICE_INIT;
     ++
      +void slice_set_string(struct slice *s, const char *str)
      +{
     ++	int l;
      +	if (str == NULL) {
      +		s->len = 0;
      +		return;
      +	}
     ++	assert(s->canary == SLICE_CANARY);
      +
     -+	{
     -+		int l = strlen(str);
     -+		l++; /* \0 */
     -+		slice_resize(s, l);
     -+		memcpy(s->buf, str, l);
     -+		s->len = l - 1;
     -+	}
     ++	l = strlen(str);
     ++	l++; /* \0 */
     ++	slice_resize(s, l);
     ++	memcpy(s->buf, str, l);
     ++	s->len = l - 1;
     ++}
     ++
     ++void slice_init(struct slice *s)
     ++{
     ++	struct slice empty = SLICE_INIT;
     ++	*s = empty;
      +}
      +
      +void slice_resize(struct slice *s, int l)
      +{
     ++	assert(s->canary == SLICE_CANARY);
      +	if (s->cap < l) {
      +		int c = s->cap * 2;
      +		if (c < l) {
     @@ reftable/slice.c (new)
      +{
      +	int l1 = d->len;
      +	int l2 = strlen(s);
     ++	assert(d->canary == SLICE_CANARY);
      +
      +	slice_resize(d, l2 + l1);
      +	memcpy(d->buf + l1, s, l2);
     @@ reftable/slice.c (new)
      +void slice_addbuf(struct slice *s, struct slice a)
      +{
      +	int end = s->len;
     ++	assert(s->canary == SLICE_CANARY);
      +	slice_resize(s, s->len + a.len);
      +	memcpy(s->buf + end, a.buf, a.len);
      +}
      +
      +void slice_consume(struct slice *s, int n)
      +{
     ++	assert(s->canary == SLICE_CANARY);
      +	s->buf += n;
      +	s->len -= n;
      +}
     @@ reftable/slice.c (new)
      +byte *slice_detach(struct slice *s)
      +{
      +	byte *p = s->buf;
     ++	assert(s->canary == SLICE_CANARY);
      +	s->buf = NULL;
      +	s->cap = 0;
      +	s->len = 0;
     @@ reftable/slice.c (new)
      +
      +void slice_release(struct slice *s)
      +{
     ++	assert(s->canary == SLICE_CANARY);
      +	reftable_free(slice_detach(s));
      +}
      +
      +void slice_copy(struct slice *dest, struct slice src)
      +{
     ++	assert(dest->canary == SLICE_CANARY);
     ++	assert(src.canary == SLICE_CANARY);
      +	slice_resize(dest, src.len);
      +	memcpy(dest->buf, src.buf, src.len);
      +}
     @@ reftable/slice.c (new)
      +   a \0 is added at the end. */
      +const char *slice_as_string(struct slice *s)
      +{
     ++	assert(s->canary == SLICE_CANARY);
      +	if (s->cap == s->len) {
      +		int l = s->len;
      +		slice_resize(s, l + 1);
     @@ reftable/slice.c (new)
      +/* return a newly malloced string for this slice */
      +char *slice_to_string(struct slice in)
      +{
     -+	struct slice s = { 0 };
     ++	struct slice s = SLICE_INIT;
     ++	assert(in.canary == SLICE_CANARY);
      +	slice_resize(&s, in.len + 1);
      +	s.buf[in.len] = 0;
      +	memcpy(s.buf, in.buf, in.len);
     @@ reftable/slice.c (new)
      +
      +bool slice_equal(struct slice a, struct slice b)
      +{
     -+	if (a.len != b.len) {
     ++	assert(a.canary == SLICE_CANARY);
     ++	assert(b.canary == SLICE_CANARY);
     ++	if (a.len != b.len)
      +		return 0;
     -+	}
      +	return memcmp(a.buf, b.buf, a.len) == 0;
      +}
      +
     @@ reftable/slice.c (new)
      +{
      +	int min = a.len < b.len ? a.len : b.len;
      +	int res = memcmp(a.buf, b.buf, min);
     -+	if (res != 0) {
     ++	assert(a.canary == SLICE_CANARY);
     ++	assert(b.canary == SLICE_CANARY);
     ++	if (res != 0)
      +		return res;
     -+	}
     -+	if (a.len < b.len) {
     ++	if (a.len < b.len)
      +		return -1;
     -+	} else if (a.len > b.len) {
     ++	else if (a.len > b.len)
      +		return 1;
     -+	} else {
     ++	else
      +		return 0;
     -+	}
      +}
      +
      +int slice_add(struct slice *b, byte *data, size_t sz)
      +{
     ++	assert(b->canary == SLICE_CANARY);
      +	if (b->len + sz > b->cap) {
      +		int newcap = 2 * b->cap + 1;
      +		if (newcap < b->len + sz) {
     @@ reftable/slice.c (new)
      +int common_prefix_size(struct slice a, struct slice b)
      +{
      +	int p = 0;
     ++	assert(a.canary == SLICE_CANARY);
     ++	assert(b.canary == SLICE_CANARY);
      +	while (p < a.len && p < b.len) {
      +		if (a.buf[p] != b.buf[p]) {
      +			break;
     @@ reftable/slice.h (new)
      +	int len;
      +	int cap;
      +	byte *buf;
     ++	byte canary;
      +};
     ++#define SLICE_CANARY 0x42
     ++#define SLICE_INIT                       \
     ++	{                                \
     ++		0, 0, NULL, SLICE_CANARY \
     ++	}
     ++extern struct slice reftable_empty_slice;
      +
      +void slice_set_string(struct slice *dest, const char *src);
      +void slice_addstr(struct slice *dest, const char *src);
     @@ reftable/slice.h (new)
      +/* Return a malloced string for `src` */
      +char *slice_to_string(struct slice src);
      +
     ++/* Initializes a slice. Accepts a slice with random garbage. */
     ++void slice_init(struct slice *slice);
     ++
      +/* Ensure that `buf` is \0 terminated. */
      +const char *slice_as_string(struct slice *src);
      +
     @@ reftable/stack.c (new)
      +{
      +	struct reftable_stack *p =
      +		reftable_calloc(sizeof(struct reftable_stack));
     -+	struct slice list_file_name = { 0 };
     ++	struct slice list_file_name = SLICE_INIT;
      +	int err = 0;
      +
      +	if (config.hash_id == 0) {
     @@ reftable/stack.c (new)
      +	int err = 0;
      +	if (size < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +	err = lseek(fd, 0, SEEK_SET);
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	buf = reftable_malloc(size + 1);
      +	if (read(fd, buf, size) != size) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +	buf[size] = 0;
      +
      +	parse_names(buf, size, namesp);
      +
     -+exit:
     ++done:
      +	reftable_free(buf);
      +	return err;
      +}
     @@ reftable/stack.c (new)
      +	int new_tables_len = 0;
      +	struct reftable_merged_table *new_merged = NULL;
      +	int i;
     -+	struct slice table_path = { 0 };
     ++	struct slice table_path = SLICE_INIT;
      +
      +	while (*names) {
      +		struct reftable_reader *rd = NULL;
     @@ reftable/stack.c (new)
      +
      +			err = reftable_block_source_from_file(
      +				&src, slice_as_string(&table_path));
     -+			if (err < 0) {
     -+				goto exit;
     -+			}
     ++			if (err < 0)
     ++				goto done;
      +
      +			err = reftable_new_reader(&rd, &src, name);
     -+			if (err < 0) {
     -+				goto exit;
     -+			}
     ++			if (err < 0)
     ++				goto done;
      +		}
      +
      +		new_tables[new_tables_len++] = rd;
     @@ reftable/stack.c (new)
      +	/* success! */
      +	err = reftable_new_merged_table(&new_merged, new_tables, new_tables_len,
      +					st->config.hash_id);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	new_tables = NULL;
      +	new_tables_len = 0;
     @@ reftable/stack.c (new)
      +		}
      +	}
      +
     -+exit:
     ++done:
      +	slice_release(&table_path);
      +	for (i = 0; i < new_tables_len; i++) {
      +		reader_close(new_tables[i]);
     @@ reftable/stack.c (new)
      +	time_t diff = a->tv_sec - b->tv_sec;
      +	int udiff = a->tv_usec - b->tv_usec;
      +
     -+	if (diff != 0) {
     ++	if (diff != 0)
      +		return diff;
     -+	}
      +
      +	return udiff;
      +}
     @@ reftable/stack.c (new)
      +	int err = gettimeofday(&deadline, NULL);
      +	int64_t delay = 0;
      +	int tries = 0;
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	deadline.tv_sec += 3;
      +	while (true) {
     @@ reftable/stack.c (new)
      +	char **names = NULL;
      +	int err = read_lines(st->list_file, &names);
      +	int i = 0;
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	for (i = 0; i < st->merged->stack_len; i++) {
      +		if (names[i] == NULL) {
      +			err = 1;
     -+			goto exit;
     ++			goto done;
      +		}
      +
      +		if (strcmp(st->merged->stack[i]->name, names[i])) {
      +			err = 1;
     -+			goto exit;
     ++			goto done;
      +		}
      +	}
      +
      +	if (names[st->merged->stack_len] != NULL) {
      +		err = 1;
     -+		goto exit;
     ++		goto done;
      +	}
      +
     -+exit:
     ++done:
      +	free_names(names);
      +	return err;
      +}
     @@ reftable/stack.c (new)
      +int reftable_stack_reload(struct reftable_stack *st)
      +{
      +	int err = stack_uptodate(st);
     -+	if (err > 0) {
     ++	if (err > 0)
      +		return reftable_stack_reload_maybe_reuse(st, true);
     -+	}
      +	return err;
      +}
      +
     @@ reftable/stack.c (new)
      +		return err;
      +	}
      +
     -+	if (!st->disable_auto_compact) {
     ++	if (!st->disable_auto_compact)
      +		return reftable_stack_auto_compact(st);
     -+	}
      +
      +	return 0;
      +}
     @@ reftable/stack.c (new)
      +	uint64_t next_update_index;
      +};
      +
     ++#define REFTABLE_ADDITION_INIT               \
     ++	{                                    \
     ++		.lock_file_name = SLICE_INIT \
     ++	}
     ++
      +static int reftable_stack_init_addition(struct reftable_addition *add,
      +					struct reftable_stack *st)
      +{
     @@ reftable/stack.c (new)
      +		} else {
      +			err = REFTABLE_IO_ERROR;
      +		}
     -+		goto exit;
     ++		goto done;
      +	}
      +	err = stack_uptodate(st);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	if (err > 1) {
      +		err = REFTABLE_LOCK_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	add->next_update_index = reftable_stack_next_update_index(st);
     -+exit:
     ++done:
      +	if (err) {
      +		reftable_addition_close(add);
      +	}
     @@ reftable/stack.c (new)
      +void reftable_addition_close(struct reftable_addition *add)
      +{
      +	int i = 0;
     -+	struct slice nm = { 0 };
     ++	struct slice nm = SLICE_INIT;
      +	for (i = 0; i < add->new_tables_len; i++) {
      +		slice_set_string(&nm, add->stack->list_file);
      +		slice_addstr(&nm, "/");
     @@ reftable/stack.c (new)
      +
      +int reftable_addition_commit(struct reftable_addition *add)
      +{
     -+	struct slice table_list = { 0 };
     ++	struct slice table_list = SLICE_INIT;
      +	int i = 0;
      +	int err = 0;
     -+	if (add->new_tables_len == 0) {
     -+		goto exit;
     -+	}
     ++	if (add->new_tables_len == 0)
     ++		goto done;
      +
      +	for (i = 0; i < add->stack->merged->stack_len; i++) {
      +		slice_addstr(&table_list, add->stack->merged->stack[i]->name);
     @@ reftable/stack.c (new)
      +	slice_release(&table_list);
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = close(add->lock_file_fd);
      +	add->lock_file_fd = 0;
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = rename(slice_as_string(&add->lock_file_name),
      +		     add->stack->list_file);
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = reftable_stack_reload(add->stack);
      +
     -+exit:
     ++done:
      +	reftable_addition_close(add);
      +	return err;
      +}
     @@ reftable/stack.c (new)
      +				struct reftable_stack *st)
      +{
      +	int err = 0;
     ++	struct reftable_addition empty = REFTABLE_ADDITION_INIT;
      +	*dest = reftable_calloc(sizeof(**dest));
     ++	**dest = empty;
      +	err = reftable_stack_init_addition(*dest, st);
      +	if (err) {
      +		reftable_free(*dest);
     @@ reftable/stack.c (new)
      +		  int (*write_table)(struct reftable_writer *wr, void *arg),
      +		  void *arg)
      +{
     -+	struct reftable_addition add = { 0 };
     ++	struct reftable_addition add = REFTABLE_ADDITION_INIT;
      +	int err = reftable_stack_init_addition(&add, st);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +	if (err > 0) {
      +		err = REFTABLE_LOCK_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = reftable_addition_add(&add, write_table, arg);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	err = reftable_addition_commit(&add);
     -+exit:
     ++done:
      +	reftable_addition_close(&add);
      +	return err;
      +}
     @@ reftable/stack.c (new)
      +					     void *arg),
      +			  void *arg)
      +{
     -+	struct slice temp_tab_file_name = { 0 };
     -+	struct slice tab_file_name = { 0 };
     -+	struct slice next_name = { 0 };
     ++	struct slice temp_tab_file_name = SLICE_INIT;
     ++	struct slice tab_file_name = SLICE_INIT;
     ++	struct slice next_name = SLICE_INIT;
      +	struct reftable_writer *wr = NULL;
      +	int err = 0;
      +	int tab_fd = 0;
     @@ reftable/stack.c (new)
      +	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_file_name));
      +	if (tab_fd < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	wr = reftable_new_writer(reftable_fd_write, &tab_fd,
      +				 &add->stack->config);
      +	err = write_table(wr, arg);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	err = reftable_writer_close(wr);
      +	if (err == REFTABLE_EMPTY_TABLE_ERROR) {
      +		err = 0;
     -+		goto exit;
     -+	}
     -+	if (err < 0) {
     -+		goto exit;
     ++		goto done;
      +	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	err = close(tab_fd);
      +	tab_fd = 0;
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = stack_check_addition(add->stack,
      +				   slice_as_string(&temp_tab_file_name));
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	if (wr->min_update_index < add->next_update_index) {
      +		err = REFTABLE_API_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	format_name(&next_name, wr->min_update_index, wr->max_update_index);
     @@ reftable/stack.c (new)
      +		     slice_as_string(&tab_file_name));
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	add->new_tables = reftable_realloc(add->new_tables,
     @@ reftable/stack.c (new)
      +						   (add->new_tables_len + 1));
      +	add->new_tables[add->new_tables_len] = slice_to_string(next_name);
      +	add->new_tables_len++;
     -+exit:
     ++done:
      +	if (tab_fd > 0) {
      +		close(tab_fd);
      +		tab_fd = 0;
     @@ reftable/stack.c (new)
      +uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
      +{
      +	int sz = st->merged->stack_len;
     -+	if (sz > 0) {
     ++	if (sz > 0)
      +		return reftable_reader_max_update_index(
      +			       st->merged->stack[sz - 1]) +
      +		       1;
     -+	}
      +	return 1;
      +}
      +
     @@ reftable/stack.c (new)
      +				struct slice *temp_tab,
      +				struct reftable_log_expiry_config *config)
      +{
     -+	struct slice next_name = { 0 };
     ++	struct slice next_name = SLICE_INIT;
      +	int tab_fd = -1;
      +	struct reftable_writer *wr = NULL;
      +	int err = 0;
     @@ reftable/stack.c (new)
      +	wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);
      +
      +	err = stack_write_compact(st, wr, first, last, config);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +	err = reftable_writer_close(wr);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	err = close(tab_fd);
      +	tab_fd = 0;
      +
     -+exit:
     ++done:
      +	reftable_writer_free(wr);
      +	if (tab_fd > 0) {
      +		close(tab_fd);
     @@ reftable/stack.c (new)
      +					st->config.hash_id);
      +	if (err < 0) {
      +		reftable_free(subtabs);
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = reftable_merged_table_seek_ref(mt, &it, "");
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	while (true) {
      +		err = reftable_iterator_next_ref(&it, &ref);
     @@ reftable/stack.c (new)
      +	reftable_iterator_destroy(&it);
      +
      +	err = reftable_merged_table_seek_log(mt, &it, "");
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	while (true) {
      +		err = reftable_iterator_next_log(&it, &log);
     @@ reftable/stack.c (new)
      +		entries++;
      +	}
      +
     -+exit:
     ++done:
      +	reftable_iterator_destroy(&it);
      +	if (mt != NULL) {
      +		merged_table_clear(mt);
     @@ reftable/stack.c (new)
      +static int stack_compact_range(struct reftable_stack *st, int first, int last,
      +			       struct reftable_log_expiry_config *expiry)
      +{
     -+	struct slice temp_tab_file_name = { 0 };
     -+	struct slice new_table_name = { 0 };
     -+	struct slice lock_file_name = { 0 };
     -+	struct slice ref_list_contents = { 0 };
     -+	struct slice new_table_path = { 0 };
     ++	struct slice temp_tab_file_name = SLICE_INIT;
     ++	struct slice new_table_name = SLICE_INIT;
     ++	struct slice lock_file_name = SLICE_INIT;
     ++	struct slice ref_list_contents = SLICE_INIT;
     ++	struct slice new_table_path = SLICE_INIT;
      +	int err = 0;
      +	bool have_lock = false;
      +	int lock_file_fd = 0;
     @@ reftable/stack.c (new)
      +
      +	if (first > last || (expiry == NULL && first == last)) {
      +		err = 0;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	st->stats.attempts++;
     @@ reftable/stack.c (new)
      +		} else {
      +			err = REFTABLE_IO_ERROR;
      +		}
     -+		goto exit;
     ++		goto done;
      +	}
      +	/* Don't want to write to the lock for now.  */
      +	close(lock_file_fd);
     @@ reftable/stack.c (new)
      +
      +	have_lock = true;
      +	err = stack_uptodate(st);
     -+	if (err != 0) {
     -+		goto exit;
     -+	}
     ++	if (err != 0)
     ++		goto done;
      +
      +	for (i = first, j = 0; i <= last; i++) {
     -+		struct slice subtab_file_name = { 0 };
     -+		struct slice subtab_lock = { 0 };
     ++		struct slice subtab_file_name = SLICE_INIT;
     ++		struct slice subtab_lock = SLICE_INIT;
     ++		int sublock_file_fd = -1;
     ++
      +		slice_set_string(&subtab_file_name, st->reftable_dir);
      +		slice_addstr(&subtab_file_name, "/");
      +		slice_addstr(&subtab_file_name,
     @@ reftable/stack.c (new)
      +		slice_copy(&subtab_lock, subtab_file_name);
      +		slice_addstr(&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;
     -+				} else {
     -+					err = REFTABLE_IO_ERROR;
     -+				}
     ++		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;
     ++			} else {
     ++				err = REFTABLE_IO_ERROR;
      +			}
      +		}
      +
     @@ reftable/stack.c (new)
      +			(char *)slice_as_string(&subtab_file_name);
      +		j++;
      +
     -+		if (err != 0) {
     -+			goto exit;
     -+		}
     ++		if (err != 0)
     ++			goto done;
      +	}
      +
      +	err = unlink(slice_as_string(&lock_file_name));
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +	have_lock = false;
      +
      +	err = stack_compact_locked(st, first, last, &temp_tab_file_name,
     @@ reftable/stack.c (new)
      +	if (is_empty_table) {
      +		err = 0;
      +	}
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	lock_file_fd = open(slice_as_string(&lock_file_name),
      +			    O_EXCL | O_CREAT | O_WRONLY, 0644);
     @@ reftable/stack.c (new)
      +		} else {
      +			err = REFTABLE_IO_ERROR;
      +		}
     -+		goto exit;
     ++		goto done;
      +	}
      +	have_lock = true;
      +
     @@ reftable/stack.c (new)
      +			     slice_as_string(&new_table_path));
      +		if (err < 0) {
      +			err = REFTABLE_IO_ERROR;
     -+			goto exit;
     ++			goto done;
      +		}
      +	}
      +
     @@ reftable/stack.c (new)
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
      +		unlink(slice_as_string(&new_table_path));
     -+		goto exit;
     ++		goto done;
      +	}
      +	err = close(lock_file_fd);
      +	lock_file_fd = 0;
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
      +		unlink(slice_as_string(&new_table_path));
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = rename(slice_as_string(&lock_file_name), st->list_file);
      +	if (err < 0) {
      +		err = REFTABLE_IO_ERROR;
      +		unlink(slice_as_string(&new_table_path));
     -+		goto exit;
     ++		goto done;
      +	}
      +	have_lock = false;
      +
     @@ reftable/stack.c (new)
      +		listp++;
      +	}
      +
     -+exit:
     ++done:
      +	free_names(delete_on_success);
      +
      +	listp = subtable_locks;
     @@ reftable/stack.c (new)
      +int fastlog2(uint64_t sz)
      +{
      +	int l = 0;
     -+	if (sz == 0) {
     ++	if (sz == 0)
      +		return 0;
     -+	}
      +	for (; sz; sz /= 2) {
      +		l++;
      +	}
     @@ reftable/stack.c (new)
      +	struct segment seg =
      +		suggest_compaction_segment(sizes, st->merged->stack_len);
      +	reftable_free(sizes);
     -+	if (segment_size(&seg) > 0) {
     ++	if (segment_size(&seg) > 0)
      +		return stack_compact_range_stats(st, seg.start, seg.end - 1,
      +						 NULL);
     -+	}
      +
      +	return 0;
      +}
     @@ reftable/stack.c (new)
      +	struct reftable_iterator it = { 0 };
      +	struct reftable_merged_table *mt = reftable_stack_merged_table(st);
      +	int err = reftable_merged_table_seek_log(mt, &it, refname);
     -+	if (err) {
     -+		goto exit;
     -+	}
     ++	if (err)
     ++		goto done;
      +
      +	err = reftable_iterator_next_log(&it, log);
     -+	if (err) {
     -+		goto exit;
     -+	}
     ++	if (err)
     ++		goto done;
      +
      +	if (strcmp(log->ref_name, refname) ||
      +	    reftable_log_record_is_deletion(log)) {
      +		err = 1;
     -+		goto exit;
     ++		goto done;
      +	}
      +
     -+exit:
     ++done:
      +	if (err) {
      +		reftable_log_record_clear(log);
      +	}
     @@ reftable/stack.c (new)
      +	int len = 0;
      +	int i = 0;
      +
     -+	if (st->config.skip_name_check) {
     ++	if (st->config.skip_name_check)
      +		return 0;
     -+	}
      +
      +	err = reftable_block_source_from_file(&src, new_tab_name);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	err = reftable_new_reader(&rd, &src, new_tab_name);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	err = reftable_reader_seek_ref(rd, &it, "");
      +	if (err > 0) {
      +		err = 0;
     -+		goto exit;
     -+	}
     -+	if (err < 0) {
     -+		goto exit;
     ++		goto done;
      +	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	while (true) {
      +		struct reftable_ref_record ref = { 0 };
     @@ reftable/stack.c (new)
      +		if (err > 0) {
      +			break;
      +		}
     -+		if (err < 0) {
     -+			goto exit;
     -+		}
     ++		if (err < 0)
     ++			goto done;
      +
      +		if (len >= cap) {
      +			cap = 2 * cap + 1;
     @@ reftable/stack.c (new)
      +
      +	err = validate_ref_record_addition(tab, refs, len);
      +
     -+exit:
     ++done:
      +	for (i = 0; i < len; i++) {
      +		reftable_ref_record_clear(&refs[i]);
      +	}
     @@ reftable/tree.c (new)
      +	}
      +
      +	res = compare(key, (*rootp)->key);
     -+	if (res < 0) {
     ++	if (res < 0)
      +		return tree_search(key, &(*rootp)->left, compare, insert);
     -+	} else if (res > 0) {
     ++	else if (res > 0)
      +		return tree_search(key, &(*rootp)->right, compare, insert);
     -+	}
      +	return *rootp;
      +}
      +
     @@ reftable/writer.c (new)
      +	if (w->pending_padding > 0) {
      +		byte *zeroed = reftable_calloc(w->pending_padding);
      +		int n = w->write(w->write_arg, zeroed, w->pending_padding);
     -+		if (n < 0) {
     ++		if (n < 0)
      +			return n;
     -+		}
      +
      +		w->pending_padding = 0;
      +		reftable_free(zeroed);
     @@ reftable/writer.c (new)
      +
      +	w->pending_padding = padding;
      +	n = w->write(w->write_arg, data, len);
     -+	if (n < 0) {
     ++	if (n < 0)
      +		return n;
     -+	}
      +	n += padding;
      +	return 0;
      +}
     @@ reftable/writer.c (new)
      +		block_start = header_size(writer_version(w));
      +	}
      +
     ++	slice_release(&w->last_key);
      +	block_writer_init(&w->block_writer_data, typ, w->block,
      +			  w->opts.block_size, block_start,
      +			  hash_size(w->opts.hash_id));
     @@ reftable/writer.c (new)
      +{
      +	struct reftable_writer *wp =
      +		reftable_calloc(sizeof(struct reftable_writer));
     ++	slice_init(&wp->block_writer_data.last_key);
      +	options_set_defaults(opts);
      +	if (opts->block_size >= (1 << 24)) {
      +		/* TODO - error return? */
      +		abort();
      +	}
     ++	wp->last_key = reftable_empty_slice;
      +	wp->block = reftable_calloc(opts->block_size);
      +	wp->write = writer_func;
      +	wp->write_arg = writer_arg;
     @@ reftable/writer.c (new)
      +	int offset_len;
      +	int offset_cap;
      +};
     ++#define OBJ_INDEX_TREE_NODE_INIT   \
     ++	{                          \
     ++		.hash = SLICE_INIT \
     ++	}
      +
      +static int obj_index_tree_node_compare(const void *a, const void *b)
      +{
     @@ reftable/writer.c (new)
      +					     &obj_index_tree_node_compare, 0);
      +	struct obj_index_tree_node *key = NULL;
      +	if (node == NULL) {
     -+		key = reftable_calloc(sizeof(struct obj_index_tree_node));
     ++		struct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;
     ++		key = reftable_malloc(sizeof(struct obj_index_tree_node));
     ++		*key = empty;
     ++
      +		slice_copy(&key->hash, hash);
      +		tree_search((void *)key, &w->obj_index_tree,
      +			    &obj_index_tree_node_compare, 1);
     @@ reftable/writer.c (new)
      +			     struct reftable_record *rec)
      +{
      +	int result = -1;
     -+	struct slice key = { 0 };
     ++	struct slice key = SLICE_INIT;
      +	int err = 0;
      +	reftable_record_key(rec, &key);
     -+	if (slice_cmp(w->last_key, key) >= 0) {
     -+		goto exit;
     -+	}
     ++	if (slice_cmp(w->last_key, key) >= 0)
     ++		goto done;
      +
      +	slice_copy(&w->last_key, key);
      +	if (w->block_writer == NULL) {
     @@ reftable/writer.c (new)
      +
      +	if (block_writer_add(w->block_writer, rec) == 0) {
      +		result = 0;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	err = writer_flush_block(w);
      +	if (err < 0) {
      +		result = err;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	writer_reinit_block_writer(w, reftable_record_type(rec));
      +	err = block_writer_add(w->block_writer, rec);
      +	if (err < 0) {
      +		result = err;
     -+		goto exit;
     ++		goto done;
      +	}
      +
      +	result = 0;
     -+exit:
     ++done:
      +	slice_release(&key);
      +	return result;
      +}
     @@ reftable/writer.c (new)
      +	struct reftable_ref_record copy = *ref;
      +	int err = 0;
      +
     -+	if (ref->ref_name == NULL) {
     ++	if (ref->ref_name == NULL)
      +		return REFTABLE_API_ERROR;
     -+	}
      +	if (ref->update_index < w->min_update_index ||
     -+	    ref->update_index > w->max_update_index) {
     ++	    ref->update_index > w->max_update_index)
      +		return REFTABLE_API_ERROR;
     -+	}
      +
      +	reftable_record_from_ref(&rec, &copy);
      +	copy.update_index -= w->min_update_index;
      +	err = writer_add_record(w, &rec);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	if (!w->opts.skip_index_objects && ref->value != NULL) {
      +		struct slice h = {
      +			.buf = ref->value,
      +			.len = hash_size(w->opts.hash_id),
     ++			.canary = SLICE_CANARY,
      +		};
      +
      +		writer_index_hash(w, h);
      +	}
     ++
      +	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
      +		struct slice h = {
      +			.buf = ref->target_value,
      +			.len = hash_size(w->opts.hash_id),
     ++			.canary = SLICE_CANARY,
      +		};
      +		writer_index_hash(w, h);
      +	}
     @@ reftable/writer.c (new)
      +{
      +	struct reftable_record rec = { 0 };
      +	int err;
     -+	if (log->ref_name == NULL) {
     ++	if (log->ref_name == NULL)
      +		return REFTABLE_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) {
     ++		if (err < 0)
      +			return err;
     -+		}
      +	}
      +
      +	w->next -= w->pending_padding;
     @@ reftable/writer.c (new)
      +	int err = writer_flush_block(w);
      +	int i = 0;
      +	struct reftable_block_stats *bstats = NULL;
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	while (w->index_len > threshold) {
      +		struct reftable_index_record *idx = NULL;
     @@ reftable/writer.c (new)
      +				continue;
      +			}
      +
     -+			{
     -+				int err = writer_flush_block(w);
     -+				if (err < 0) {
     -+					return err;
     -+				}
     -+			}
     ++			err = writer_flush_block(w);
     ++			if (err < 0)
     ++				return err;
      +
      +			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
      +
     @@ reftable/writer.c (new)
      +	writer_clear_index(w);
      +
      +	err = writer_flush_block(w);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	bstats = writer_reftable_block_stats(w, typ);
      +	bstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;
     @@ reftable/writer.c (new)
      +		.offset_len = entry->offset_len,
      +	};
      +	struct reftable_record rec = { 0 };
     -+	if (arg->err < 0) {
     -+		goto exit;
     -+	}
     ++	if (arg->err < 0)
     ++		goto done;
      +
      +	reftable_record_from_obj(&rec, &obj_rec);
      +	arg->err = block_writer_add(arg->w->block_writer, &rec);
     -+	if (arg->err == 0) {
     -+		goto exit;
     -+	}
     ++	if (arg->err == 0)
     ++		goto done;
      +
      +	arg->err = writer_flush_block(arg->w);
     -+	if (arg->err < 0) {
     -+		goto exit;
     -+	}
     ++	if (arg->err < 0)
     ++		goto done;
      +
      +	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;
     -+	}
     ++	if (arg->err == 0)
     ++		goto done;
      +	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:;
     ++done:;
      +}
      +
      +static void object_record_free(void *void_arg, void *key)
     @@ reftable/writer.c (new)
      +		infix_walk(w->obj_index_tree, &write_object_record, &closure);
      +	}
      +
     -+	if (closure.err < 0) {
     ++	if (closure.err < 0)
      +		return closure.err;
     -+	}
      +	return writer_finish_section(w);
      +}
      +
     @@ reftable/writer.c (new)
      +	byte typ = 0;
      +	int err = 0;
      +
     -+	if (w->block_writer == NULL) {
     ++	if (w->block_writer == NULL)
      +		return 0;
     -+	}
      +
      +	typ = block_writer_type(w->block_writer);
      +	err = writer_finish_section(w);
     -+	if (err < 0) {
     ++	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) {
     ++		if (err < 0)
      +			return err;
     -+		}
      +	}
      +
      +	if (w->obj_index_tree != NULL) {
     @@ reftable/writer.c (new)
      +	byte *p = footer;
      +	int err = writer_finish_public_section(w);
      +	int empty_table = w->next == 0;
     -+	if (err != 0) {
     -+		goto exit;
     -+	}
     ++	if (err != 0)
     ++		goto done;
      +	w->pending_padding = 0;
      +	if (empty_table) {
      +		/* Empty tables need a header anyway. */
      +		byte header[28];
      +		int n = writer_write_header(w, header);
      +		err = padded_write(w, header, n, 0);
     -+		if (err < 0) {
     -+			goto exit;
     -+		}
     ++		if (err < 0)
     ++			goto done;
      +	}
      +
      +	p += writer_write_header(w, footer);
     @@ reftable/writer.c (new)
      +	p += 4;
      +
      +	err = padded_write(w, footer, footer_size(writer_version(w)), 0);
     -+	if (err < 0) {
     -+		goto exit;
     -+	}
     ++	if (err < 0)
     ++		goto done;
      +
      +	if (empty_table) {
      +		err = REFTABLE_EMPTY_TABLE_ERROR;
     -+		goto exit;
     ++		goto done;
      +	}
      +
     -+exit:
     ++done:
      +	/* free up memory. */
      +	block_writer_clear(&w->block_writer_data);
      +	writer_clear_index(w);
     @@ reftable/writer.c (new)
      +	int raw_bytes = block_writer_finish(w->block_writer);
      +	int padding = 0;
      +	int err = 0;
     -+	struct reftable_index_record ir = { 0 };
     -+	if (raw_bytes < 0) {
     ++	struct reftable_index_record ir = { .last_key = SLICE_INIT };
     ++	if (raw_bytes < 0)
      +		return raw_bytes;
     -+	}
      +
      +	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
      +		padding = w->opts.block_size - raw_bytes;
     @@ reftable/writer.c (new)
      +	}
      +
      +	err = padded_write(w, w->block, raw_bytes, padding);
     -+	if (err < 0) {
     ++	if (err < 0)
      +		return err;
     -+	}
      +
      +	if (w->index_cap == w->index_len) {
      +		w->index_cap = 2 * w->index_cap + 1;
     @@ reftable/writer.c (new)
      +
      +int writer_flush_block(struct reftable_writer *w)
      +{
     -+	if (w->block_writer == NULL) {
     ++	if (w->block_writer == NULL)
      +		return 0;
     -+	}
     -+	if (w->block_writer->entries == 0) {
     ++	if (w->block_writer->entries == 0)
      +		return 0;
     -+	}
      +	return writer_flush_nonempty_block(w);
      +}
      +
  6:  865c2c4567a ! 10:  a86c3753717 Reftable support for git-core
     @@ Commit message
      
          TODO:
      
     -    * Resolve spots marked with XXX
     +    * Fix worktree commands
     +
     +    * Spots marked XXX
      
          Example use: see t/t0031-reftable.sh
      
     @@ refs/reftable-backend.c (new)
      +	strbuf_reset(&sb);
      +
      +	refs->err = reftable_new_stack(&refs->stack, refs->reftable_dir, cfg);
     ++	assert(refs->err != REFTABLE_API_ERROR);
      +	strbuf_release(&sb);
      +	return ref_store;
      +}
     @@ refs/reftable-backend.c (new)
      +	}
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	strbuf_release(&referent);
      +	return err;
      +}
     @@ refs/reftable-backend.c (new)
      +	transaction->state = REF_TRANSACTION_PREPARED;
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	if (err < 0) {
      +		transaction->state = REF_TRANSACTION_CLOSED;
      +		strbuf_addf(errbuf, "reftable: transaction prepare: %s",
     @@ refs/reftable-backend.c (new)
      +	}
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	free(logs);
      +	free(sorted);
      +	return err;
     @@ refs/reftable-backend.c (new)
      +	err = reftable_addition_commit(add);
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	reftable_addition_destroy(add);
      +	transaction->state = REF_TRANSACTION_CLOSED;
      +	transaction->backend_data = NULL;
     @@ refs/reftable-backend.c (new)
      +				    struct ref_transaction *transaction,
      +				    struct strbuf *errmsg)
      +{
     ++	int err = reftable_transaction_prepare(ref_store, transaction, errmsg);
     ++	if (err)
     ++		return err;
     ++
      +	return reftable_transaction_finish(ref_store, transaction, errmsg);
      +}
      +
     @@ refs/reftable-backend.c (new)
      +
      +	err = reftable_writer_add_ref(writer, &write_ref);
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	reftable_ref_record_clear(&read_ref);
      +	return err;
      +}
     @@ refs/reftable-backend.c (new)
      +	}
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	reftable_addition_destroy(add);
      +	return err;
      +}
     @@ refs/reftable-backend.c (new)
      +	if (err < 0) {
      +		goto done;
      +	}
     ++
     ++	string_list_sort(refnames);
      +	err = reftable_stack_reload(refs->stack);
      +	if (err) {
      +		goto done;
      +	}
      +	err = reftable_stack_add(refs->stack, &write_delete_refs_table, &arg);
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	return err;
      +}
      +
     @@ refs/reftable-backend.c (new)
      +	}
      +	err = reftable_stack_add(refs->stack, &write_create_symref_table, &arg);
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	return err;
      +}
      +
     @@ refs/reftable-backend.c (new)
      +	}
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	reftable_ref_record_clear(&ref);
      +	return err;
      +}
     @@ refs/reftable-backend.c (new)
      +
      +	err = reftable_stack_add(refs->stack, &write_rename_table, &arg);
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	return err;
      +}
      +
     @@ refs/reftable-backend.c (new)
      +	err = reftable_stack_add(refs->stack, &write_reflog_expiry_table, &arg);
      +
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	reftable_log_record_clear(&log);
      +	reftable_iterator_destroy(&it);
      +	clear_log_tombstones(&arg);
     @@ refs/reftable-backend.c (new)
      +		err = -1;
      +	}
      +done:
     ++	assert(err != REFTABLE_API_ERROR);
      +	reftable_ref_record_clear(&ref);
      +	return err;
      +}
     @@ t/t0031-reftable.sh (new)
      +	test_cmp expect actual
      +'
      +
     ++test_expect_success 'clone calls transaction_initial_commit' '
     ++	test_commit message1 file1 &&
     ++	git clone . cloned &&
     ++	(test  -f cloned/file1 || echo "Fixme.")
     ++'
     ++
      +test_expect_success 'basic operation of reftable storage: commit, show-ref' '
      +	initialize &&
      +	test_commit file &&
  7:  6b248d5fdb4 = 11:  d3613c2ff53 Add GIT_DEBUG_REFS debugging mechanism
  8:  54102355ce7 = 12:  9b98ed614ec vcxproj: adjust for the reftable changes
  9:  7764ebf0956 ! 13:  5e401e4f1ac Add reftable testing infrastructure

@@ Commit message
* t1404-update-ref-errors.sh - Manipulates .git/refs/ directly
* t1405 - inspecs .git/ directly.

- t6030-bisect-porcelain.sh - 62 of 72
+ Worst offenders:
+
+ t1400-update-ref.sh - 82 of 185
t2400-worktree-add.sh - 58 of 69
- t3200-branch.sh - 58 of 145
- t7406-submodule-update.sh - 54 of 54
- t5601-clone.sh - 51 of 105
- t9903-bash-prompt.sh - 50 of 66
t1404-update-ref-errors.sh - 44 of 53
- t5510-fetch.sh - 40 of 171
- t7400-submodule-basic.sh - 38 of 111
t3514-cherry-pick-revert-gpg.sh - 36 of 36
+ t5541-http-push-smart.sh - 29 of 38
+ t6003-rev-list-topo-order.sh - 29 of 35
+ t3420-rebase-autostash.sh - 28 of 42
+ t6120-describe.sh - 21 of 82
+ t3430-rebase-merges.sh - 18 of 24
+ t2018-checkout-branch.sh - 15 of 22
..

@@ t/t9903-bash-prompt.sh: test_description='test git-specific bash prompt function
actual="$TRASH_DIRECTORY/actual"

## t/test-lib.sh ##
-@@ t/test-lib.sh: FreeBSD)
+@@ t/test-lib.sh: parisc* | hppa*)
;;
esac

Signature · 1 line
gitgitgadget
#280 lore raw

I refrained from saying this but perhaps I should have in the early round. Next time, can you make sure the patches you are sending out apply cleanly without need for fixing whitespace errors?

This is what I got. Thanks.

$ git am -sc ./+hn13-v15-reftable
Applying: Write pseudorefs through ref backends.
Applying: Make refs_ref_exists public
Applying: Treat BISECT_HEAD as a pseudo ref
Applying: Treat CHERRY_PICK_HEAD as a pseudo ref
Applying: Treat REVERT_HEAD as a pseudo ref
Applying: Move REF_LOG_ONLY to refs-internal.h
Applying: Iterate over the "refs/" namespace in for_each_[raw]ref
Applying: Add .gitattributes for the reftable/ directory
.git/rebase-apply/patch:137: trailing whitespace.

.git/rebase-apply/patch:6784: trailing whitespace.
# Remove unittests and compatibility hacks we don't need here. .git/rebase-apply/patch:6787: trailing whitespace. git add reftable/*.[ch] reftable/LICENSE reftable/VERSION .git/rebase-apply/patch:7558: indent with spaces.

        left = *destLen;
.git/rebase-apply/patch:7559: indent with spaces.
        *destLen = 0;

warning: squelched 16 whitespace errors
warning: 21 lines applied after fixing whitespace errors.
Applying: Add reftable library .git/rebase-apply/patch:1865: indent with spaces.
return 1
.git/rebase-apply/patch:1876: trailing whitespace.
test_line_count = 0 output
.git/rebase-apply/patch:1892: trailing whitespace.

		print_ref "refs/tags/test_tag^{}" 

.git/rebase-apply/patch:1950: space before tab in indent.
git rebase source &&
.git/rebase-apply/patch:1955: new blank line at EOF. +
warning: 4 lines applied after fixing whitespace errors.
Applying: Reftable support for git-core
Test number t0031 already taken .git/rebase-apply/patch:385: trailing whitespace.

	(cd files && GIT_DEBUG_REFS=1 test_commit message file) > files.txt && 

warning: 1 line applied after fixing whitespace errors.
Applying: Add GIT_DEBUG_REFS debugging mechanism Test number t0033 already taken
Applying: vcxproj: adjust for the reftable changes
Applying: Add reftable testing infrastructure

#269 lore raw

Re: [PATCH v15 03/13] Treat BISECT_HEAD as a pseudo ref

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 9 lines · Han-Wen Nienhuys via GitGitGadget

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

Both the git-bisect.sh as bisect--helper inspected the file system directly.

---
 builtin/bisect--helper.c | 3 +--
 git-bisect.sh            | 4 ++--
 2 files changed, 3 insertions(+), 4 deletions(-)

Makes sense.

A topic that adds more callers to the git_path_bisect_head() function has already been in flight for quite a while; I wonder if we can meet the topic in the middle. For example, would it have helped if we had a helper like this one:

        static inline int bisect_head_exists(void) {
               return ref_exists("BISECT_HEAD");
        }

on this side, and have the other side have something like:

        static inline int bisect_head_exists(void) {
               return file_exists(git_path_bisect_head());
        }

Then the caller(s) of bisect_head_exists() don't have to be changed at all.

Anyway, it's just a lesson that communication and collaboration between developers may help coming up with correct integration results.

Thanks. Queued.

#281 lore raw
In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 6 lines · Han-Wen Nienhuys via GitGitGadget

This adds the reftable library, and hooks it up as a ref backend. Based on hn/refs-cleanup.

Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1

Summary 20479 tests pass 1299 tests fail

Testing without GIT_TEST_REFTABLE=0 gives a few failures, too. I haven't taken a look into them, though.

Thanks.

*** log for hn/reftable ***
HEAD is now at 341a26d318 Add reftable testing infrastructure GIT_VERSION = unknown.g00000000
* new build flags
CC fuzz-commit-graph.o
CC fuzz-pack-headers.o
CC fuzz-pack-idx.o
CC bugreport.o
...

[14:11:46] t7112-reset-submodule.sh ........................... ok   141577 ms ( 2.43 usr  0.95 sys + 661.06 cusr 464.38 csys = 1128.82 CPU)
[14:11:50] t9402-git-cvsserver-refs.sh ........................ ok    41912 ms ( 0.61 usr  0.17 sys + 260.43 cusr 177.60 csys = 438.81 CPU)
[14:12:03] t9001-send-email.sh ................................ ok   129424 ms ( 1.47 usr  0.57 sys + 576.18 cusr 387.33 csys = 965.55 CPU)

[14:12:03]

Test Summary Report
-------------------
t3510-cherry-pick-sequence.sh (Wstat: 256 Tests: 50 Failed: 0)
Non-zero exit status: 1

  Parse errors: Tests out of sequence.  Found (14) but expected (13)
                Tests out of sequence.  Found (15) but expected (14)
                Tests out of sequence.  Found (16) but expected (15)
                Tests out of sequence.  Found (17) but expected (16)
                Tests out of sequence.  Found (18) but expected (17)

Displayed the first 5 of 39 TAP syntax errors.
Re-run prove with the -p option to see them all.
t3404-rebase-interactive.sh (Wstat: 256 Tests: 118 Failed: 0)
Non-zero exit status: 1

  Parse errors: Tests out of sequence.  Found (100) but expected (98)
                Tests out of sequence.  Found (101) but expected (99)
                Tests out of sequence.  Found (102) but expected (100)
                Tests out of sequence.  Found (103) but expected (101)
                Tests out of sequence.  Found (104) but expected (102)

Displayed the first 5 of 22 TAP syntax errors.
Re-run prove with the -p option to see them all.
Files=906, Tests=22755, 360 wallclock secs (10.16 usr 3.35 sys + 1318.97 cusr 996.62 csys = 2329.10 CPU)
Result: FAIL
make[1]: *** [Makefile:52: prove] Error 1
make[1]: Leaving directory '/usr/local/google/home/jch/w/git.cycle/t'
make: *** [Makefile:2799: test] Error 2

#283 lore raw

PATCH v16 1/14 Write pseudorefs through ref backends.

In reply to Han-Wen Nienhuys via GitGitGadget

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

Pseudorefs store transient data in in the repository. Examples are HEAD, CHERRY_PICK_HEAD, etc.

These refs have always been read through the ref backends, but they were written in a one-off routine that wrote an object ID or symref directly into .git/<pseudo_ref_name>.

This causes problems when introducing a new ref storage backend. To remedy this, extend the ref backend implementation with a write_pseudoref_fn and update_pseudoref_fn.

---
 refs.c                | 119 ++++++++----------------------------------
 refs.h                |  11 ++++
 refs/files-backend.c  | 114 +++++++++++++++++++++++++++++++++++++++-
 refs/packed-backend.c |  21 +++++++-
 refs/refs-internal.h  |  18 +++++++
 5 files changed, 184 insertions(+), 99 deletions(-)
refs.c +22 -97
index 224ff66c7bb..12908066b13 100644
@@ -321,6 +321,12 @@ int ref_exists(const char *refname)
 	return refs_ref_exists(get_main_ref_store(the_repository), refname);
 }
 
+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
+{
+	return refs_delete_pseudoref(get_main_ref_store(the_repository),
+				     pseudoref, old_oid);
+}
+
 static int filter_refs(const char *refname, const struct object_id *oid,
 			   int flags, void *data)
 {
@@ -739,101 +745,6 @@ long get_files_ref_lock_timeout_ms(void)
 	return timeout_ms;
 }
 
-static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
-			   const struct object_id *old_oid, struct strbuf *err)
-{
-	const char *filename;
-	int fd;
-	struct lock_file lock = LOCK_INIT;
-	struct strbuf buf = STRBUF_INIT;
-	int ret = -1;
-
-	if (!oid)
-		return 0;
-
-	strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
-
-	filename = git_path("%s", pseudoref);
-	fd = hold_lock_file_for_update_timeout(&lock, filename, 0,
-					       get_files_ref_lock_timeout_ms());
-	if (fd < 0) {
-		strbuf_addf(err, _("could not open '%s' for writing: %s"),
-			    filename, strerror(errno));
-		goto done;
-	}
-
-	if (old_oid) {
-		struct object_id actual_old_oid;
-
-		if (read_ref(pseudoref, &actual_old_oid)) {
-			if (!is_null_oid(old_oid)) {
-				strbuf_addf(err, _("could not read ref '%s'"),
-					    pseudoref);
-				rollback_lock_file(&lock);
-				goto done;
-			}
-		} else if (is_null_oid(old_oid)) {
-			strbuf_addf(err, _("ref '%s' already exists"),
-				    pseudoref);
-			rollback_lock_file(&lock);
-			goto done;
-		} else if (!oideq(&actual_old_oid, old_oid)) {
-			strbuf_addf(err, _("unexpected object ID when writing '%s'"),
-				    pseudoref);
-			rollback_lock_file(&lock);
-			goto done;
-		}
-	}
-
-	if (write_in_full(fd, buf.buf, buf.len) < 0) {
-		strbuf_addf(err, _("could not write to '%s'"), filename);
-		rollback_lock_file(&lock);
-		goto done;
-	}
-
-	commit_lock_file(&lock);
-	ret = 0;
-done:
-	strbuf_release(&buf);
-	return ret;
-}
-
-static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
-{
-	const char *filename;
-
-	filename = git_path("%s", pseudoref);
-
-	if (old_oid && !is_null_oid(old_oid)) {
-		struct lock_file lock = LOCK_INIT;
-		int fd;
-		struct object_id actual_old_oid;
-
-		fd = hold_lock_file_for_update_timeout(
-				&lock, filename, 0,
-				get_files_ref_lock_timeout_ms());
-		if (fd < 0) {
-			error_errno(_("could not open '%s' for writing"),
-				    filename);
-			return -1;
-		}
-		if (read_ref(pseudoref, &actual_old_oid))
-			die(_("could not read ref '%s'"), pseudoref);
-		if (!oideq(&actual_old_oid, old_oid)) {
-			error(_("unexpected object ID when deleting '%s'"),
-			      pseudoref);
-			rollback_lock_file(&lock);
-			return -1;
-		}
-
-		unlink(filename);
-		rollback_lock_file(&lock);
-	} else {
-		unlink(filename);
-	}
-
-	return 0;
-}
 
 int refs_delete_ref(struct ref_store *refs, const char *msg,
 		    const char *refname,
@@ -845,7 +756,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
 
 	if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 		assert(refs == get_main_ref_store(the_repository));
-		return delete_pseudoref(refname, old_oid);
+		return refs_delete_pseudoref(refs, refname, old_oid);
 	}
 
 	transaction = ref_store_transaction_begin(refs, &err);
@@ -1172,7 +1083,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 
 	if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 		assert(refs == get_main_ref_store(the_repository));
-		ret = write_pseudoref(refname, new_oid, old_oid, &err);
+		ret = refs_write_pseudoref(refs, refname, new_oid, old_oid,
+					   &err);
 	} else {
 		t = ref_store_transaction_begin(refs, &err);
 		if (!t ||
@@ -1441,6 +1353,19 @@ int head_ref(each_ref_fn fn, void *cb_data)
 	return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
 }
 
+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
+			 const struct object_id *oid,
+			 const struct object_id *old_oid, struct strbuf *err)
+{
+	return refs->be->write_pseudoref(refs, pseudoref, oid, old_oid, err);
+}
+
+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
+			  const struct object_id *old_oid)
+{
+	return refs->be->delete_pseudoref(refs, pseudoref, old_oid);
+}
+
 struct ref_iterator *refs_ref_iterator_begin(
 		struct ref_store *refs,
 		const char *prefix, int trim, int flags)
refs.h +11 -0
index e010f8aec28..4dad8f24914 100644
@@ -732,6 +732,17 @@ int update_ref(const char *msg, const char *refname,
 	       const struct object_id *new_oid, const struct object_id *old_oid,
 	       unsigned int flags, enum action_on_err onerr);
 
+/* Pseudorefs (eg. HEAD, CHERRY_PICK_HEAD) have a separate routines for updating
+   and deletion as they cannot take part in normal transactional updates.
+   Pseudorefs should only be written for the main repository.
+*/
+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
+			 const struct object_id *oid,
+			 const struct object_id *old_oid, struct strbuf *err);
+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
+			  const struct object_id *old_oid);
+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid);
+
 int parse_hide_refs_config(const char *var, const char *value, const char *);
 
 /*
refs/files-backend.c +113 -1

130 lines not on this page · raw message

refs/packed-backend.c +20 -1
index 4458a0f69cc..08e8253a893 100644
@@ -1590,6 +1590,22 @@ static int packed_copy_ref(struct ref_store *ref_store,
 	BUG("packed reference store does not support copying references");
 }
 
+static int packed_write_pseudoref(struct ref_store *ref_store,
+				  const char *pseudoref,
+				  const struct object_id *oid,
+				  const struct object_id *old_oid,
+				  struct strbuf *err)
+{
+	BUG("packed reference store does not support writing pseudo-references");
+}
+
+static int packed_delete_pseudoref(struct ref_store *ref_store,
+				   const char *pseudoref,
+				   const struct object_id *old_oid)
+{
+	BUG("packed reference store does not support deleting pseudo-references");
+}
+
 static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
 {
 	return empty_ref_iterator_begin();
@@ -1656,6 +1672,9 @@ struct ref_storage_be refs_be_packed = {
 	packed_rename_ref,
 	packed_copy_ref,
 
+	packed_write_pseudoref,
+	packed_delete_pseudoref,
+
 	packed_ref_iterator_begin,
 	packed_read_raw_ref,
 
@@ -1665,5 +1684,5 @@ struct ref_storage_be refs_be_packed = {
 	packed_reflog_exists,
 	packed_create_reflog,
 	packed_delete_reflog,
-	packed_reflog_expire
+	packed_reflog_expire,
 };
refs/refs-internal.h +18 -0
index 4271362d264..59b053d53a2 100644
@@ -556,6 +556,21 @@ typedef int copy_ref_fn(struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
 
+typedef int write_pseudoref_fn(struct ref_store *ref_store,
+			       const char *pseudoref,
+			       const struct object_id *oid,
+			       const struct object_id *old_oid,
+			       struct strbuf *err);
+
+/*
+ * Deletes a pseudoref. Deletion always succeeds (even if the pseudoref doesn't
+ * exist.), except if old_oid is specified. If it is, it can fail due to lock
+ * failure, failure reading the old OID, or an OID mismatch
+ */
+typedef int delete_pseudoref_fn(struct ref_store *ref_store,
+				const char *pseudoref,
+				const struct object_id *old_oid);
+
 /*
  * Iterate over the references in `ref_store` whose names start with
  * `prefix`. `prefix` is matched as a literal string, without regard
@@ -655,6 +670,9 @@ struct ref_storage_be {
 	rename_ref_fn *rename_ref;
 	copy_ref_fn *copy_ref;
 
+	write_pseudoref_fn *write_pseudoref;
+	delete_pseudoref_fn *delete_pseudoref;
+
 	ref_iterator_begin_fn *iterator_begin;
 	read_raw_ref_fn *read_raw_ref;
 
Signature · 1 line
gitgitgadget
#285 lore raw

PATCH v16 2/14 Make refs_ref_exists public

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 refs.c | 2 +-
 refs.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +1 -1
index 12908066b13..812fee47108 100644
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct object_id *oid)
 	return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
 }
 
-static int refs_ref_exists(struct ref_store *refs, const char *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
 {
 	return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
 }
refs.h +2 -0
index 4dad8f24914..7aaa1226551 100644
@@ -105,6 +105,8 @@ int refs_verify_refname_available(struct ref_store *refs,
 				  const struct string_list *skip,
 				  struct strbuf *err);
 
+int refs_ref_exists(struct ref_store *refs, const char *refname);
+
 int ref_exists(const char *refname);
 
 int should_autocreate_reflog(const char *refname);
Signature · 1 line
gitgitgadget
#291 lore raw

PATCH v16 3/14 Treat BISECT_HEAD as a pseudo ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

Both the git-bisect.sh as bisect--helper inspected the file system directly.

---
 builtin/bisect--helper.c | 3 +--
 git-bisect.sh            | 4 ++--
 2 files changed, 3 insertions(+), 4 deletions(-)
builtin/bisect--helper.c +1 -2
index ec4996282e3..73f9324ad7d 100644
@@ -13,7 +13,6 @@ static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
 static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
 static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
 static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
-static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
 static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
 static GIT_PATH_FUNC(git_path_head_name, "head-name")
 static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
@@ -164,7 +163,7 @@ static int bisect_reset(const char *commit)
 		strbuf_addstr(&branch, commit);
 	}
 
-	if (!file_exists(git_path_bisect_head())) {
+	if (!ref_exists("BISECT_HEAD")) {
 		struct argv_array argv = ARGV_ARRAY_INIT;
 
 		argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
git-bisect.sh +2 -2
index 08a6ed57ddb..f03fbb18f00 100755
@@ -41,7 +41,7 @@ TERM_GOOD=good
 
 bisect_head()
 {
-	if test -f "$GIT_DIR/BISECT_HEAD"
+	if git rev-parse --verify -q BISECT_HEAD > /dev/null
 	then
 		echo BISECT_HEAD
 	else
@@ -153,7 +153,7 @@ bisect_next() {
 	git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
 
 	# Perform all bisection computation, display and checkout
-	git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
+	git bisect--helper --next-all $(git rev-parse --verify -q BISECT_HEAD > /dev/null && echo --no-checkout)
 	res=$?
 
 	# Check if we should exit because bisection is finished
Signature · 1 line
gitgitgadget
#282 lore raw

v16 cover Reftable support git-core

In reply to Han-Wen Nienhuys via GitGitGadget

This adds the reftable library, and hooks it up as a ref backend. Based on hn/refs-cleanup.

Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1

Summary 20479 tests pass 1299 tests fail

Some issues:

* many tests inspect .git/{logs,heads}/ directly.
* worktrees broken.

v18

* fix pseudo ref usage
* import and execute unittests too.

Han-Wen Nienhuys (13):
Write pseudorefs through ref backends.
Make refs_ref_exists public
Treat BISECT_HEAD as a pseudo ref
Treat CHERRY_PICK_HEAD as a pseudo ref
Treat REVERT_HEAD as a pseudo ref
Move REF_LOG_ONLY to refs-internal.h
Iterate over the "refs/" namespace in for_each_[raw]ref Add .gitattributes for the reftable/ directory Add reftable library
Reftable support for git-core
Hookup unittests for the reftable library.
Add GIT_DEBUG_REFS debugging mechanism
Add reftable testing infrastructure

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

 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   45 +-
 builtin/bisect--helper.c                      |    3 +-
 builtin/clone.c                               |    3 +-
 builtin/commit.c                              |   34 +-
 builtin/init-db.c                             |   56 +-
 builtin/merge.c                               |    2 +-
 cache.h                                       |    6 +-
 config.mak.uname                              |    2 +-
 contrib/buildsystems/Generators/Vcxproj.pm    |   11 +-
 git-bisect.sh                                 |    4 +-
 path.c                                        |    2 -
 path.h                                        |    9 +-
 refs.c                                        |  157 +-
 refs.h                                        |   16 +
 refs/debug.c                                  |  309 ++++
 refs/files-backend.c                          |  121 +-
 refs/packed-backend.c                         |   21 +-
 refs/refs-internal.h                          |   32 +
 refs/reftable-backend.c                       | 1331 +++++++++++++++++
 reftable/.gitattributes                       |    1 +
 reftable/LICENSE                              |   31 +
 reftable/README.md                            |   11 +
 reftable/VERSION                              |    1 +
 reftable/basics.c                             |  215 +++
 reftable/basics.h                             |   53 +
 reftable/block.c                              |  433 ++++++
 reftable/block.h                              |  129 ++
 reftable/block_test.c                         |  156 ++
 reftable/constants.h                          |   21 +
 reftable/file.c                               |   95 ++
 reftable/iter.c                               |  243 +++
 reftable/iter.h                               |   72 +
 reftable/merged.c                             |  320 ++++
 reftable/merged.h                             |   39 +
 reftable/merged_test.c                        |  273 ++++
 reftable/pq.c                                 |  113 ++
 reftable/pq.h                                 |   34 +
 reftable/reader.c                             |  742 +++++++++
 reftable/reader.h                             |   65 +
 reftable/record.c                             | 1113 ++++++++++++++
 reftable/record.h                             |  128 ++
 reftable/record_test.c                        |  400 +++++
 reftable/refname.c                            |  209 +++
 reftable/refname.h                            |   38 +
 reftable/refname_test.c                       |   99 ++
 reftable/reftable-tests.h                     |   13 +
 reftable/reftable.c                           |   90 ++
 reftable/reftable.h                           |  564 +++++++
 reftable/reftable_test.c                      |  631 ++++++++
 reftable/slice.c                              |  243 +++
 reftable/slice.h                              |   87 ++
 reftable/slice_test.c                         |   40 +
 reftable/stack.c                              | 1207 +++++++++++++++
 reftable/stack.h                              |   48 +
 reftable/stack_test.c                         |  743 +++++++++
 reftable/system.h                             |   54 +
 reftable/test_framework.c                     |   69 +
 reftable/test_framework.h                     |   64 +
 reftable/tree.c                               |   63 +
 reftable/tree.h                               |   34 +
 reftable/tree_test.c                          |   62 +
 reftable/update.sh                            |   25 +
 reftable/writer.c                             |  644 ++++++++
 reftable/writer.h                             |   60 +
 reftable/zlib-compat.c                        |   92 ++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 sequencer.c                                   |   56 +-
 setup.c                                       |   12 +-
 t/helper/test-tool.c                          |    1 +
 t/helper/test-tool.h                          |    1 +
 t/t0031-reftable.sh                           |  147 ++
 t/t0033-debug-refs.sh                         |   18 +
 t/t1409-avoid-packing-refs.sh                 |    6 +
 t/t1450-fsck.sh                               |    6 +
 t/t3210-pack-refs.sh                          |    6 +
 t/t9903-bash-prompt.sh                        |    6 +
 t/test-lib.sh                                 |    5 +
 wt-status.c                                   |    6 +-
 80 files changed, 12118 insertions(+), 195 deletions(-)
 create mode 100644 refs/debug.c
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/.gitattributes
 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/constants.h
 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/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/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 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100755 t/t0031-reftable.sh
 create mode 100755 t/t0033-debug-refs.sh

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

Range-diff vs v15:

1: 9bb64f748dc ! 1: 4a1f600fb85 Write pseudorefs through ref backends.
@@ refs/refs-internal.h: typedef int copy_ref_fn(struct ref_store *ref_store,
+ const struct object_id *oid,
+ const struct object_id *old_oid,
+ struct strbuf *err);
++
++/*
++ * Deletes a pseudoref. Deletion always succeeds (even if the pseudoref doesn't ++ * exist.), except if old_oid is specified. If it is, it can fail due to lock ++ * failure, failure reading the old OID, or an OID mismatch
++ */
+typedef int delete_pseudoref_fn(struct ref_store *ref_store,
+ const char *pseudoref,
+ const struct object_id *old_oid);

  2:  d489806907b =  2:  a4a67ce9635 Make refs_ref_exists public
  3:  60a82678a1b =  3:  ecd6591d86f Treat BISECT_HEAD as a pseudo ref
  4:  1a925f5671a !  4:  7c31727de69 Treat CHERRY_PICK_HEAD as a pseudo ref
     @@ sequencer.c: static int commit_staged_changes(struct repository *r,
      -		if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
      +		if (refs_ref_exists(get_main_ref_store(r),
      +				    "CHERRY_PICK_HEAD") &&
     -+		    !refs_delete_pseudoref(get_main_ref_store(r),
     -+					   "CHERRY_PICK_HEAD", NULL))
     ++		    refs_delete_pseudoref(get_main_ref_store(r),
     ++					  "CHERRY_PICK_HEAD", NULL))
       			return error(_("could not remove CHERRY_PICK_HEAD"));
       		if (!final_fixup)
       			return 0;
  5:  8f2ebf65b90 !  5:  fbdcee5208b Treat REVERT_HEAD as a pseudo ref
     @@ sequencer.c: static int create_seq_dir(struct repository *r)
       	const char *in_progress_advice = NULL;
       	unsigned int advise_skip =
      -		file_exists(git_path_revert_head(r)) ||
     --		refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
     -+		refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD");
     -+	refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
     ++		refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") ||
     + 		refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
       
       	if (!sequencer_get_last_command(r, &action)) {
     - 		switch (action) {
      @@ sequencer.c: static int rollback_single_pick(struct repository *r)
       	struct object_id head_oid;
  6:  e0e1e3a558d =  6:  d8801367f7d Move REF_LOG_ONLY to refs-internal.h
  7:  27a980d1d9d =  7:  556d57610cc Iterate over the "refs/" namespace in for_each_[raw]ref
  8:  dcbd000e7f7 =  8:  78cb1930172 Add .gitattributes for the reftable/ directory
  9:  718b646a54e !  9:  0bc28ac610f Add reftable library
     @@ reftable/README.md (new)
      
       ## reftable/VERSION (new) ##
      @@
     -+commit fe15c65dd95b6a13de92b4baf711e50338ef20ae
     -+Author: Han-Wen Nienhuys <[email protected]>
     -+Date:   Thu May 28 19:28:24 2020 +0200
     -+
     -+    C: add 'canary' to struct slice
     -+    
     -+    This enforces that all slices are initialized explicitly with
     -+    SLICE_INIT, for future compatibility with git's strbuf.
     ++0c164275ea8f8aa7d099f7425ddcef1affe137e9 C: compile framework with Git options
      
       ## reftable/basics.c (new) ##
      @@
     @@ reftable/block.c (new)
      +}
      +
      +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)
     @@ reftable/block.c (new)
      +	slice_consume(&out, n);
      +
      +	if (block_writer_register_restart(w, start.len - out.len, restart,
     -+					  key) < 0)
     ++					  &key) < 0)
      +		goto done;
      +
      +	slice_release(&key);
     @@ reftable/block.c (new)
      +}
      +
      +int block_writer_register_restart(struct block_writer *w, int n, bool restart,
     -+				  struct slice key)
     ++				  struct slice *key)
      +{
      +	int rlen = w->restart_len;
      +	if (rlen >= MAX_RESTARTS) {
     @@ reftable/block.c (new)
      +		return -1;
      +	}
      +
     -+	result = slice_cmp(a->key, rkey);
     ++	result = slice_cmp(&a->key, &rkey);
      +	slice_release(&rkey);
      +	return result;
      +}
     @@ reftable/block.c (new)
      +{
      +	dest->br = src->br;
      +	dest->next_off = src->next_off;
     -+	slice_copy(&dest->last_key, src->last_key);
     ++	slice_copy(&dest->last_key, &src->last_key);
      +}
      +
      +int block_iter_next(struct block_iter *it, struct reftable_record *rec)
     @@ reftable/block.c (new)
      +		return -1;
      +	slice_consume(&in, n);
      +
     -+	slice_copy(&it->last_key, key);
     ++	slice_copy(&it->last_key, &key);
      +	it->next_off += start.len - in.len;
      +	slice_release(&key);
      +	return 0;
     @@ reftable/block.c (new)
      +	return 0;
      +}
      +
     -+int block_iter_seek(struct block_iter *it, struct slice want)
     ++int block_iter_seek(struct block_iter *it, struct slice *want)
      +{
      +	return block_reader_seek(it->br, it, want);
      +}
     @@ reftable/block.c (new)
      +}
      +
      +int block_reader_seek(struct block_reader *br, struct block_iter *it,
     -+		      struct slice want)
     ++		      struct slice *want)
      +{
      +	struct restart_find_args args = {
     -+		.key = want,
     ++		.key = *want,
      +		.r = br,
      +	};
      +	struct reftable_record rec = reftable_new_record(block_reader_type(br));
     @@ reftable/block.c (new)
      +			goto done;
      +
      +		reftable_record_key(&rec, &key);
     -+		if (err > 0 || slice_cmp(key, want) >= 0) {
     ++		if (err > 0 || slice_cmp(&key, want) >= 0) {
      +			err = 0;
      +			goto done;
      +		}
     @@ reftable/block.h (new)
      +
      +/* Position `it` to the `want` key in the block */
      +int block_reader_seek(struct block_reader *br, struct block_iter *it,
     -+		      struct slice want);
     ++		      struct slice *want);
      +
      +/* Returns the block type (eg. 'r' for refs) */
      +byte block_reader_type(struct block_reader *r);
     @@ reftable/block.h (new)
      +int block_iter_next(struct block_iter *it, struct reftable_record *rec);
      +
      +/* Seek to `want` with in the block pointed to by `it` */
     -+int block_iter_seek(struct block_iter *it, struct slice want);
     ++int block_iter_seek(struct block_iter *it, struct slice *want);
      +
      +/* deallocate memory for `it`. The block reader and its block is left intact. */
      +void block_iter_close(struct block_iter *it);
     @@ reftable/block.h (new)
      +
      +#endif
      
     + ## reftable/block_test.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "block.h"
     ++
     ++#include "system.h"
     ++
     ++#include "basics.h"
     ++#include "constants.h"
     ++#include "record.h"
     ++#include "reftable.h"
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
     ++
     ++struct binsearch_args {
     ++	int key;
     ++	int *arr;
     ++};
     ++
     ++static int binsearch_func(size_t i, void *void_args)
     ++{
     ++	struct binsearch_args *args = (struct binsearch_args *)void_args;
     ++
     ++	return args->key < args->arr[i];
     ++}
     ++
     ++static void test_binsearch(void)
     ++{
     ++	int arr[] = { 2, 4, 6, 8, 10 };
     ++	size_t sz = ARRAY_SIZE(arr);
     ++	struct binsearch_args args = {
     ++		.arr = arr,
     ++	};
     ++
     ++	int i = 0;
     ++	for (i = 1; i < 11; i++) {
     ++		int res;
     ++		args.key = i;
     ++		res = binsearch(sz, &binsearch_func, &args);
     ++
     ++		if (res < sz) {
     ++			assert(args.key < arr[res]);
     ++			if (res > 0) {
     ++				assert(args.key >= arr[res - 1]);
     ++			}
     ++		} else {
     ++			assert(args.key == 10 || args.key == 11);
     ++		}
     ++	}
     ++}
     ++
     ++static void test_block_read_write(void)
     ++{
     ++	const int header_off = 21; /* random */
     ++	char *names[30];
     ++	const int N = ARRAY_SIZE(names);
     ++	const int block_size = 1024;
     ++	struct reftable_block block = { 0 };
     ++	struct block_writer bw = {
     ++		.last_key = SLICE_INIT,
     ++	};
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_record rec = { 0 };
     ++	int i = 0;
     ++	int n;
     ++	struct block_reader br = { 0 };
     ++	struct block_iter it = { .last_key = SLICE_INIT };
     ++	int j = 0;
     ++	struct slice want = SLICE_INIT;
     ++
     ++	block.data = reftable_calloc(block_size);
     ++	block.len = block_size;
     ++	block.source = malloc_block_source();
     ++	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
     ++			  header_off, hash_size(SHA1_ID));
     ++	reftable_record_from_ref(&rec, &ref);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		byte hash[SHA1_SIZE];
     ++		snprintf(name, sizeof(name), "branch%02d", i);
     ++		memset(hash, i, sizeof(hash));
     ++
     ++		ref.ref_name = name;
     ++		ref.value = hash;
     ++		names[i] = xstrdup(name);
     ++		n = block_writer_add(&bw, &rec);
     ++		ref.ref_name = NULL;
     ++		ref.value = NULL;
     ++		assert(n == 0);
     ++	}
     ++
     ++	n = block_writer_finish(&bw);
     ++	assert(n > 0);
     ++
     ++	block_writer_clear(&bw);
     ++
     ++	block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
     ++
     ++	block_reader_start(&br, &it);
     ++
     ++	while (true) {
     ++		int r = block_iter_next(&it, &rec);
     ++		assert(r >= 0);
     ++		if (r > 0) {
     ++			break;
     ++		}
     ++		assert_streq(names[j], ref.ref_name);
     ++		j++;
     ++	}
     ++
     ++	reftable_record_clear(&rec);
     ++	block_iter_close(&it);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct block_iter it = { .last_key = SLICE_INIT };
     ++		slice_set_string(&want, names[i]);
     ++
     ++		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);
     ++	}
     ++
     ++	reftable_record_clear(&rec);
     ++	reftable_block_done(&br.block);
     ++	slice_release(&want);
     ++	for (i = 0; i < N; i++) {
     ++		reftable_free(names[i]);
     ++	}
     ++}
     ++
     ++int block_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("binsearch", &test_binsearch);
     ++	add_test_case("block_read_write", &test_block_read_write);
     ++	return test_main(argc, argv);
     ++}
     +
       ## reftable/constants.h (new) ##
      @@
      +/*
     @@ reftable/merged.c (new)
      +
      +		reftable_record_key(&top.rec, &k);
      +
     -+		cmp = slice_cmp(k, entry_key);
     ++		cmp = slice_cmp(&k, &entry_key);
      +		slice_release(&k);
      +
      +		if (cmp > 0) {
     @@ reftable/merged.h (new)
      +
      +#endif
      
     + ## reftable/merged_test.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "merged.h"
     ++
     ++#include "system.h"
     ++
     ++#include "basics.h"
     ++#include "block.h"
     ++#include "constants.h"
     ++#include "pq.h"
     ++#include "reader.h"
     ++#include "record.h"
     ++#include "reftable.h"
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
     ++
     ++static void test_pq(void)
     ++{
     ++	char *names[54] = { 0 };
     ++	int N = ARRAY_SIZE(names) - 1;
     ++
     ++	struct merged_iter_pqueue pq = { 0 };
     ++	const char *last = NULL;
     ++
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		snprintf(name, sizeof(name), "%02d", i);
     ++		names[i] = xstrdup(name);
     ++	}
     ++
     ++	i = 1;
     ++	do {
     ++		struct reftable_record rec =
     ++			reftable_new_record(BLOCK_TYPE_REF);
     ++		struct pq_entry e = { 0 };
     ++
     ++		reftable_record_as_ref(&rec)->ref_name = names[i];
     ++		e.rec = rec;
     ++		merged_iter_pqueue_add(&pq, e);
     ++		merged_iter_pqueue_check(pq);
     ++		i = (i * 7) % N;
     ++	} while (i != 1);
     ++
     ++	while (!merged_iter_pqueue_is_empty(pq)) {
     ++		struct pq_entry e = merged_iter_pqueue_remove(&pq);
     ++		struct reftable_ref_record *ref =
     ++			reftable_record_as_ref(&e.rec);
     ++
     ++		merged_iter_pqueue_check(pq);
     ++
     ++		if (last != NULL) {
     ++			assert(strcmp(last, ref->ref_name) < 0);
     ++		}
     ++		last = ref->ref_name;
     ++		ref->ref_name = NULL;
     ++		reftable_free(ref);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		reftable_free(names[i]);
     ++	}
     ++
     ++	merged_iter_pqueue_clear(&pq);
     ++}
     ++
     ++static void write_test_table(struct slice *buf,
     ++			     struct reftable_ref_record refs[], int n)
     ++{
     ++	int min = 0xffffffff;
     ++	int max = 0;
     ++	int i = 0;
     ++	int err;
     ++
     ++	struct reftable_write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++	struct reftable_writer *w = NULL;
     ++	for (i = 0; i < n; i++) {
     ++		uint64_t ui = refs[i].update_index;
     ++		if (ui > max) {
     ++			max = ui;
     ++		}
     ++		if (ui < min) {
     ++			min = ui;
     ++		}
     ++	}
     ++
     ++	w = reftable_new_writer(&slice_add_void, buf, &opts);
     ++	reftable_writer_set_limits(w, min, max);
     ++
     ++	for (i = 0; i < n; i++) {
     ++		uint64_t before = refs[i].update_index;
     ++		int n = reftable_writer_add_ref(w, &refs[i]);
     ++		assert(n == 0);
     ++		assert(before == refs[i].update_index);
     ++	}
     ++
     ++	err = reftable_writer_close(w);
     ++	assert_err(err);
     ++
     ++	reftable_writer_free(w);
     ++}
     ++
     ++static struct reftable_merged_table *
     ++merged_table_from_records(struct reftable_ref_record **refs,
     ++			  struct reftable_block_source **source, int *sizes,
     ++			  struct slice *buf, int n)
     ++{
     ++	struct reftable_reader **rd = reftable_calloc(n * sizeof(*rd));
     ++	int i = 0;
     ++	struct reftable_merged_table *mt = NULL;
     ++	int err;
     ++	*source = reftable_calloc(n * sizeof(**source));
     ++	for (i = 0; i < n; i++) {
     ++		write_test_table(&buf[i], refs[i], sizes[i]);
     ++		block_source_from_slice(&(*source)[i], &buf[i]);
     ++
     ++		err = reftable_new_reader(&rd[i], &(*source)[i], "name");
     ++		assert_err(err);
     ++	}
     ++
     ++	err = reftable_new_merged_table(&mt, rd, n, SHA1_ID);
     ++	assert_err(err);
     ++	return mt;
     ++}
     ++
     ++static void test_merged_between(void)
     ++{
     ++	byte hash1[SHA1_SIZE] = { 1, 2, 3, 0 };
     ++
     ++	struct reftable_ref_record r1[] = { {
     ++		.ref_name = "b",
     ++		.update_index = 1,
     ++		.value = hash1,
     ++	} };
     ++	struct reftable_ref_record r2[] = { {
     ++		.ref_name = "a",
     ++		.update_index = 2,
     ++	} };
     ++
     ++	struct reftable_ref_record *refs[] = { r1, r2 };
     ++	int sizes[] = { 1, 1 };
     ++	struct slice bufs[2] = { SLICE_INIT, SLICE_INIT };
     ++	struct reftable_block_source *bs = NULL;
     ++	struct reftable_merged_table *mt =
     ++		merged_table_from_records(refs, &bs, sizes, bufs, 2);
     ++	int i;
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_iterator it = { 0 };
     ++	int err = reftable_merged_table_seek_ref(mt, &it, "a");
     ++	assert_err(err);
     ++
     ++	err = reftable_iterator_next_ref(&it, &ref);
     ++	assert_err(err);
     ++	assert(ref.update_index == 2);
     ++	reftable_ref_record_clear(&ref);
     ++
     ++	reftable_iterator_destroy(&it);
     ++	reftable_merged_table_close(mt);
     ++	reftable_merged_table_free(mt);
     ++	for (i = 0; i < ARRAY_SIZE(bufs); i++) {
     ++		slice_release(&bufs[i]);
     ++	}
     ++	reftable_free(bs);
     ++}
     ++
     ++static void test_merged(void)
     ++{
     ++	byte hash1[SHA1_SIZE] = { 1 };
     ++	byte hash2[SHA1_SIZE] = { 2 };
     ++	struct reftable_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 reftable_ref_record r2[] = { {
     ++		.ref_name = "a",
     ++		.update_index = 2,
     ++	} };
     ++	struct reftable_ref_record r3[] = {
     ++		{
     ++			.ref_name = "c",
     ++			.update_index = 3,
     ++			.value = hash2,
     ++		},
     ++		{
     ++			.ref_name = "d",
     ++			.update_index = 3,
     ++			.value = hash1,
     ++		},
     ++	};
     ++
     ++	struct reftable_ref_record want[] = {
     ++		r2[0],
     ++		r1[1],
     ++		r3[0],
     ++		r3[1],
     ++	};
     ++
     ++	struct reftable_ref_record *refs[] = { r1, r2, r3 };
     ++	int sizes[3] = { 3, 1, 2 };
     ++	struct slice bufs[3] = { SLICE_INIT, SLICE_INIT, SLICE_INIT };
     ++	struct reftable_block_source *bs = NULL;
     ++
     ++	struct reftable_merged_table *mt =
     ++		merged_table_from_records(refs, &bs, sizes, bufs, 3);
     ++
     ++	struct reftable_iterator it = { 0 };
     ++	int err = reftable_merged_table_seek_ref(mt, &it, "a");
     ++	struct reftable_ref_record *out = NULL;
     ++	int len = 0;
     ++	int cap = 0;
     ++	int i = 0;
     ++
     ++	assert_err(err);
     ++	while (len < 100) { /* cap loops/recursion. */
     ++		struct reftable_ref_record ref = { 0 };
     ++		int err = reftable_iterator_next_ref(&it, &ref);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (len == cap) {
     ++			cap = 2 * cap + 1;
     ++			out = reftable_realloc(
     ++				out, sizeof(struct reftable_ref_record) * cap);
     ++		}
     ++		out[len++] = ref;
     ++	}
     ++	reftable_iterator_destroy(&it);
     ++
     ++	assert(ARRAY_SIZE(want) == len);
     ++	for (i = 0; i < len; i++) {
     ++		assert(reftable_ref_record_equal(&want[i], &out[i], SHA1_SIZE));
     ++	}
     ++	for (i = 0; i < len; i++) {
     ++		reftable_ref_record_clear(&out[i]);
     ++	}
     ++	reftable_free(out);
     ++
     ++	for (i = 0; i < 3; i++) {
     ++		slice_release(&bufs[i]);
     ++	}
     ++	reftable_merged_table_close(mt);
     ++	reftable_merged_table_free(mt);
     ++	reftable_free(bs);
     ++}
     ++
     ++/* XXX test refs_for(oid) */
     ++
     ++int merged_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("test_merged_between", &test_merged_between);
     ++	add_test_case("test_pq", &test_pq);
     ++	add_test_case("test_merged", &test_merged);
     ++	return test_main(argc, argv);
     ++}
     +
       ## reftable/pq.c (new) ##
      @@
      +/*
     @@ reftable/pq.c (new)
      +	reftable_record_key(&a.rec, &ak);
      +	reftable_record_key(&b.rec, &bk);
      +
     -+	cmp = slice_cmp(ak, bk);
     ++	cmp = slice_cmp(&ak, &bk);
      +
      +	slice_release(&ak);
      +	slice_release(&bk);
     @@ reftable/reader.c (new)
      +		if (err < 0)
      +			goto done;
      +
     -+		if (slice_cmp(got_key, want_key) > 0) {
     ++		if (slice_cmp(&got_key, &want_key) > 0) {
      +			table_iter_block_done(&next);
      +			break;
      +		}
     @@ reftable/reader.c (new)
      +		table_iter_copy_from(ti, &next);
      +	}
      +
     -+	err = block_iter_seek(&ti->bi, want_key);
     ++	err = block_iter_seek(&ti->bi, &want_key);
      +	if (err < 0)
      +		goto done;
      +	err = 0;
     @@ reftable/reader.c (new)
      +		if (err != 0)
      +			goto done;
      +
     -+		err = block_iter_seek(&next.bi, want_index.last_key);
     ++		err = block_iter_seek(&next.bi, &want_index.last_key);
      +		if (err < 0)
      +			goto done;
      +
     @@ reftable/record.c (new)
      +#include "constants.h"
      +#include "reftable.h"
      +
     -+int get_var_int(uint64_t *dest, struct slice in)
     ++int get_var_int(uint64_t *dest, struct slice *in)
      +{
      +	int ptr = 0;
      +	uint64_t val;
      +
     -+	if (in.len == 0)
     ++	if (in->len == 0)
      +		return -1;
     -+	val = in.buf[ptr] & 0x7f;
     ++	val = in->buf[ptr] & 0x7f;
      +
     -+	while (in.buf[ptr] & 0x80) {
     ++	while (in->buf[ptr] & 0x80) {
      +		ptr++;
     -+		if (ptr > in.len) {
     ++		if (ptr > in->len) {
      +			return -1;
      +		}
     -+		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
     ++		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)
     ++int put_var_int(struct slice *dest, uint64_t val)
      +{
      +	byte buf[10] = { 0 };
      +	int i = 9;
     @@ reftable/record.c (new)
      +	}
      +
      +	n = sizeof(buf) - i - 1;
     -+	if (dest.len < n)
     ++	if (dest->len < n)
      +		return -1;
     -+	memcpy(dest.buf, &buf[i + 1], n);
     ++	memcpy(dest->buf, &buf[i + 1], n);
      +	return n;
      +}
      +
     @@ reftable/record.c (new)
      +{
      +	int start_len = in.len;
      +	uint64_t tsize = 0;
     -+	int n = get_var_int(&tsize, in);
     ++	int n = get_var_int(&tsize, &in);
      +	if (n <= 0)
      +		return -1;
      +	slice_consume(&in, n);
     @@ reftable/record.c (new)
      +{
      +	struct slice start = s;
      +	int l = strlen(str);
     -+	int n = put_var_int(s, l);
     ++	int n = put_var_int(&s, l);
      +	if (n < 0)
      +		return -1;
      +	slice_consume(&s, n);
     @@ reftable/record.c (new)
      +			struct slice key, byte extra)
      +{
      +	struct slice start = dest;
     -+	int prefix_len = common_prefix_size(prev_key, key);
     ++	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);
     ++	int n = put_var_int(&dest, (uint64_t)prefix_len);
      +	if (n < 0)
      +		return -1;
      +	slice_consume(&dest, n);
      +
      +	*restart = (prefix_len == 0);
      +
     -+	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
     ++	n = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);
      +	if (n < 0)
      +		return -1;
      +	slice_consume(&dest, n);
     @@ reftable/record.c (new)
      +	int start_len = in.len;
      +	uint64_t prefix_len = 0;
      +	uint64_t suffix_len = 0;
     -+	int n = get_var_int(&prefix_len, in);
     ++	int n = get_var_int(&prefix_len, &in);
      +	if (n < 0)
      +		return -1;
      +	slice_consume(&in, n);
     @@ reftable/record.c (new)
      +	if (prefix_len > last_key.len)
      +		return -1;
      +
     -+	n = get_var_int(&suffix_len, in);
     ++	n = get_var_int(&suffix_len, &in);
      +	if (n <= 0)
      +		return -1;
      +	slice_consume(&in, n);
     @@ reftable/record.c (new)
      +	const struct reftable_ref_record *r =
      +		(const struct reftable_ref_record *)rec;
      +	struct slice start = s;
     -+	int n = put_var_int(s, r->update_index);
     ++	int n = put_var_int(&s, r->update_index);
      +	assert(hash_size > 0);
      +	if (n < 0)
      +		return -1;
     @@ reftable/record.c (new)
      +	bool seen_target_value = false;
      +	bool seen_target = false;
      +
     -+	int n = get_var_int(&r->update_index, in);
     ++	int n = get_var_int(&r->update_index, &in);
      +	if (n < 0)
      +		return n;
      +	assert(hash_size > 0);
     @@ reftable/record.c (new)
      +	int n = 0;
      +	uint64_t last = 0;
      +	if (r->offset_len == 0 || r->offset_len >= 8) {
     -+		n = put_var_int(s, r->offset_len);
     ++		n = put_var_int(&s, r->offset_len);
      +		if (n < 0) {
      +			return -1;
      +		}
     @@ reftable/record.c (new)
      +	}
      +	if (r->offset_len == 0)
      +		return start.len - s.len;
     -+	n = put_var_int(s, r->offsets[0]);
     ++	n = put_var_int(&s, r->offsets[0]);
      +	if (n < 0)
      +		return -1;
      +	slice_consume(&s, n);
      +
      +	last = r->offsets[0];
      +	for (i = 1; i < r->offset_len; i++) {
     -+		int n = put_var_int(s, r->offsets[i] - last);
     ++		int n = put_var_int(&s, r->offsets[i] - last);
      +		if (n < 0) {
      +			return -1;
      +		}
     @@ reftable/record.c (new)
      +	r->hash_prefix_len = key.len;
      +
      +	if (val_type == 0) {
     -+		n = get_var_int(&count, in);
     ++		n = get_var_int(&count, &in);
      +		if (n < 0) {
      +			return n;
      +		}
     @@ reftable/record.c (new)
      +	r->offsets = reftable_malloc(count * sizeof(uint64_t));
      +	r->offset_len = count;
      +
     -+	n = get_var_int(&r->offsets[0], in);
     ++	n = get_var_int(&r->offsets[0], &in);
      +	if (n < 0)
      +		return n;
      +	slice_consume(&in, n);
     @@ reftable/record.c (new)
      +	j = 1;
      +	while (j < count) {
      +		uint64_t delta = 0;
     -+		int n = get_var_int(&delta, in);
     ++		int n = get_var_int(&delta, &in);
      +		if (n < 0) {
      +			return n;
      +		}
     @@ reftable/record.c (new)
      +		return -1;
      +	slice_consume(&s, n);
      +
     -+	n = put_var_int(s, r->time);
     ++	n = put_var_int(&s, r->time);
      +	if (n < 0)
      +		return -1;
      +	slice_consume(&s, n);
     @@ reftable/record.c (new)
      +	r->email[dest.len] = 0;
      +
      +	ts = 0;
     -+	n = get_var_int(&ts, in);
     ++	n = get_var_int(&ts, &in);
      +	if (n < 0)
      +		goto done;
      +	slice_consume(&in, n);
     @@ reftable/record.c (new)
      +static void reftable_index_record_key(const void *r, struct slice *dest)
      +{
      +	struct reftable_index_record *rec = (struct reftable_index_record *)r;
     -+	slice_copy(dest, rec->last_key);
     ++	slice_copy(dest, &rec->last_key);
      +}
      +
      +static void reftable_index_record_copy_from(void *rec, const void *src_rec,
     @@ reftable/record.c (new)
      +	struct reftable_index_record *src =
      +		(struct reftable_index_record *)src_rec;
      +
     -+	slice_copy(&dst->last_key, src->last_key);
     ++	slice_copy(&dst->last_key, &src->last_key);
      +	dst->offset = src->offset;
      +}
      +
     @@ reftable/record.c (new)
      +		(const struct reftable_index_record *)rec;
      +	struct slice start = out;
      +
     -+	int n = put_var_int(out, r->offset);
     ++	int n = put_var_int(&out, r->offset);
      +	if (n < 0)
      +		return n;
      +
     @@ reftable/record.c (new)
      +	struct reftable_index_record *r = (struct reftable_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);
     ++	n = get_var_int(&r->offset, &in);
      +	if (n < 0)
      +		return n;
      +
     @@ reftable/record.h (new)
      +
      +/* utilities for de/encoding varints */
      +
     -+int get_var_int(uint64_t *dest, struct slice in);
     -+int put_var_int(struct slice dest, uint64_t val);
     ++int get_var_int(uint64_t *dest, struct slice *in);
     ++int put_var_int(struct slice *dest, uint64_t val);
      +
      +/* Methods for records. */
      +struct reftable_record_vtable {
     @@ reftable/record.h (new)
      +
      +#endif
      
     - ## reftable/refname.c (new) ##
     + ## reftable/record_test.c (new) ##
      @@
      +/*
     -+  Copyright 2020 Google LLC
     ++Copyright 2020 Google LLC
      +
     -+  Use of this source code is governed by a BSD-style
     -+  license that can be found in the LICENSE file or at
     -+  https://developers.google.com/open-source/licenses/bsd
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     ++#include "record.h"
     ++
      +#include "system.h"
     -+#include "reftable.h"
      +#include "basics.h"
     -+#include "refname.h"
     -+#include "slice.h"
     -+
     -+struct find_arg {
     -+	char **names;
     -+	const char *want;
     -+};
     ++#include "constants.h"
     ++#include "reftable.h"
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
      +
     -+static int find_name(size_t k, void *arg)
     ++static void test_copy(struct reftable_record *rec)
      +{
     -+	struct find_arg *f_arg = (struct find_arg *)arg;
     -+	return strcmp(f_arg->names[k], f_arg->want) >= 0;
     ++	struct reftable_record copy =
     ++		reftable_new_record(reftable_record_type(rec));
     ++	reftable_record_copy_from(&copy, rec, SHA1_SIZE);
     ++	/* do it twice to catch memory leaks */
     ++	reftable_record_copy_from(&copy, rec, SHA1_SIZE);
     ++	switch (reftable_record_type(&copy)) {
     ++	case BLOCK_TYPE_REF:
     ++		assert(reftable_ref_record_equal(reftable_record_as_ref(&copy),
     ++						 reftable_record_as_ref(rec),
     ++						 SHA1_SIZE));
     ++		break;
     ++	case BLOCK_TYPE_LOG:
     ++		assert(reftable_log_record_equal(reftable_record_as_log(&copy),
     ++						 reftable_record_as_log(rec),
     ++						 SHA1_SIZE));
     ++		break;
     ++	}
     ++	reftable_record_destroy(&copy);
      +}
      +
     -+int modification_has_ref(struct modification *mod, const char *name)
     ++static void test_varint_roundtrip(void)
      +{
     -+	struct reftable_ref_record ref = { 0 };
     -+	int err = 0;
     ++	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 < ARRAY_SIZE(inputs); i++) {
     ++		byte dest[10];
      +
     -+	if (mod->add_len > 0) {
     -+		struct find_arg arg = {
     -+			.names = mod->add,
     -+			.want = name,
     -+		};
     -+		int idx = binsearch(mod->add_len, find_name, &arg);
     -+		if (idx < mod->add_len && !strcmp(mod->add[idx], name)) {
     -+			return 0;
     -+		}
     -+	}
     ++		struct slice out = { .buf = dest, .len = 10, .cap = 10 };
      +
     -+	if (mod->del_len > 0) {
     -+		struct find_arg arg = {
     -+			.names = mod->del,
     -+			.want = name,
     -+		};
     -+		int idx = binsearch(mod->del_len, find_name, &arg);
     -+		if (idx < mod->del_len && !strcmp(mod->del[idx], name)) {
     -+			return 1;
     -+		}
     ++		uint64_t in = inputs[i];
     ++		int n = put_var_int(&out, in);
     ++		uint64_t got = 0;
     ++
     ++		assert(n > 0);
     ++		out.len = n;
     ++		n = get_var_int(&got, &out);
     ++		assert(n > 0);
     ++
     ++		assert(got == in);
      +	}
     ++}
      +
     -+	err = reftable_table_read_ref(&mod->tab, name, &ref);
     -+	reftable_ref_record_clear(&ref);
     -+	return err;
     ++static void test_common_prefix(void)
     ++{
     ++	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 < ARRAY_SIZE(cases); i++) {
     ++		struct slice a = SLICE_INIT;
     ++		struct slice b = SLICE_INIT;
     ++		slice_set_string(&a, cases[i].a);
     ++		slice_set_string(&b, cases[i].b);
     ++
     ++		assert(common_prefix_size(&a, &b) == cases[i].want);
     ++
     ++		slice_release(&a);
     ++		slice_release(&b);
     ++	}
      +}
      +
     -+static void modification_clear(struct modification *mod)
     ++static void set_hash(byte *h, int j)
      +{
     -+	/* don't delete the strings themselves; they're owned by ref records.
     -+	 */
     -+	FREE_AND_NULL(mod->add);
     -+	FREE_AND_NULL(mod->del);
     -+	mod->add_len = 0;
     -+	mod->del_len = 0;
     ++	int i = 0;
     ++	for (i = 0; i < hash_size(SHA1_ID); i++) {
     ++		h[i] = (j >> i) & 0xff;
     ++	}
      +}
      +
     -+int modification_has_ref_with_prefix(struct modification *mod,
     -+				     const char *prefix)
     ++static void test_reftable_ref_record_roundtrip(void)
      +{
     -+	struct reftable_iterator it = { NULL };
     -+	struct reftable_ref_record ref = { NULL };
     -+	int err = 0;
     ++	int i = 0;
     ++
     ++	for (i = 0; i <= 3; i++) {
     ++		struct reftable_ref_record in = { 0 };
     ++		struct reftable_ref_record out = {
     ++			.ref_name = xstrdup("old name"),
     ++			.value = reftable_calloc(SHA1_SIZE),
     ++			.target_value = reftable_calloc(SHA1_SIZE),
     ++			.target = xstrdup("old value"),
     ++		};
     ++		struct reftable_record rec_out = { 0 };
     ++		struct slice key = SLICE_INIT;
     ++		struct reftable_record rec = { 0 };
     ++		struct slice dest = SLICE_INIT;
     ++		int n, m;
     ++
     ++		switch (i) {
     ++		case 0:
     ++			break;
     ++		case 1:
     ++			in.value = reftable_malloc(SHA1_SIZE);
     ++			set_hash(in.value, 1);
     ++			break;
     ++		case 2:
     ++			in.value = reftable_malloc(SHA1_SIZE);
     ++			set_hash(in.value, 1);
     ++			in.target_value = reftable_malloc(SHA1_SIZE);
     ++			set_hash(in.target_value, 2);
     ++			break;
     ++		case 3:
     ++			in.target = xstrdup("target");
     ++			break;
     ++		}
     ++		in.ref_name = xstrdup("refs/heads/master");
     ++
     ++		reftable_record_from_ref(&rec, &in);
     ++		test_copy(&rec);
     ++
     ++		assert(reftable_record_val_type(&rec) == i);
     ++
     ++		reftable_record_key(&rec, &key);
     ++		slice_resize(&dest, 1024);
     ++		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     ++		assert(n > 0);
     ++
     ++		/* decode into a non-zero reftable_record to test for leaks. */
     ++
     ++		reftable_record_from_ref(&rec_out, &out);
     ++		m = reftable_record_decode(&rec_out, key, i, dest, SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		assert((out.value != NULL) == (in.value != NULL));
     ++		assert((out.target_value != NULL) == (in.target_value != NULL));
     ++		assert((out.target != NULL) == (in.target != NULL));
     ++		reftable_record_clear(&rec_out);
     ++
     ++		slice_release(&key);
     ++		slice_release(&dest);
     ++		reftable_ref_record_clear(&in);
     ++	}
     ++}
     ++
     ++static void test_reftable_log_record_equal(void)
     ++{
     ++	struct reftable_log_record in[2] = {
     ++		{
     ++			.ref_name = xstrdup("refs/heads/master"),
     ++			.update_index = 42,
     ++		},
     ++		{
     ++			.ref_name = xstrdup("refs/heads/master"),
     ++			.update_index = 22,
     ++		}
     ++	};
     ++
     ++	assert(!reftable_log_record_equal(&in[0], &in[1], SHA1_SIZE));
     ++	in[1].update_index = in[0].update_index;
     ++	assert(reftable_log_record_equal(&in[0], &in[1], SHA1_SIZE));
     ++	reftable_log_record_clear(&in[0]);
     ++	reftable_log_record_clear(&in[1]);
     ++}
     ++
     ++static void test_reftable_log_record_roundtrip(void)
     ++{
     ++	struct reftable_log_record in[2] = {
     ++		{
     ++			.ref_name = xstrdup("refs/heads/master"),
     ++			.old_hash = reftable_malloc(SHA1_SIZE),
     ++			.new_hash = reftable_malloc(SHA1_SIZE),
     ++			.name = xstrdup("han-wen"),
     ++			.email = xstrdup("[email protected]"),
     ++			.message = xstrdup("test"),
     ++			.update_index = 42,
     ++			.time = 1577123507,
     ++			.tz_offset = 100,
     ++		},
     ++		{
     ++			.ref_name = xstrdup("refs/heads/master"),
     ++			.update_index = 22,
     ++		}
     ++	};
     ++	set_test_hash(in[0].new_hash, 1);
     ++	set_test_hash(in[0].old_hash, 2);
     ++	for (int i = 0; i < ARRAY_SIZE(in); i++) {
     ++		struct reftable_record rec = { 0 };
     ++		struct slice key = SLICE_INIT;
     ++		struct slice dest = SLICE_INIT;
     ++		/* populate out, to check for leaks. */
     ++		struct reftable_log_record out = {
     ++			.ref_name = xstrdup("old name"),
     ++			.new_hash = reftable_calloc(SHA1_SIZE),
     ++			.old_hash = reftable_calloc(SHA1_SIZE),
     ++			.name = xstrdup("old name"),
     ++			.email = xstrdup("old@email"),
     ++			.message = xstrdup("old message"),
     ++		};
     ++		struct reftable_record rec_out = { 0 };
     ++		int n, m, valtype;
     ++
     ++		reftable_record_from_log(&rec, &in[i]);
     ++
     ++		test_copy(&rec);
     ++
     ++		reftable_record_key(&rec, &key);
     ++
     ++		slice_resize(&dest, 1024);
     ++
     ++		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     ++		assert(n >= 0);
     ++		reftable_record_from_log(&rec_out, &out);
     ++		valtype = reftable_record_val_type(&rec);
     ++		m = reftable_record_decode(&rec_out, key, valtype, dest,
     ++					   SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		assert(reftable_log_record_equal(&in[i], &out, SHA1_SIZE));
     ++		reftable_log_record_clear(&in[i]);
     ++		slice_release(&key);
     ++		slice_release(&dest);
     ++		reftable_record_clear(&rec_out);
     ++	}
     ++}
     ++
     ++static void test_u24_roundtrip(void)
     ++{
     ++	uint32_t in = 0x112233;
     ++	byte dest[3];
     ++	uint32_t out;
     ++	put_be24(dest, in);
     ++	out = get_be24(dest);
     ++	assert(in == out);
     ++}
     ++
     ++static void test_key_roundtrip(void)
     ++{
     ++	struct slice dest = SLICE_INIT;
     ++	struct slice last_key = SLICE_INIT;
     ++	struct slice key = SLICE_INIT;
     ++	struct slice roundtrip = SLICE_INIT;
     ++	bool restart;
     ++	byte extra;
     ++	int n, m;
     ++	byte rt_extra;
     ++
     ++	slice_resize(&dest, 1024);
     ++	slice_set_string(&last_key, "refs/heads/master");
     ++	slice_set_string(&key, "refs/tags/bla");
     ++
     ++	extra = 6;
     ++	n = reftable_encode_key(&restart, dest, last_key, key, extra);
     ++	assert(!restart);
     ++	assert(n > 0);
     ++
     ++	m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);
     ++	assert(n == m);
     ++	assert(slice_equal(&key, &roundtrip));
     ++	assert(rt_extra == extra);
     ++
     ++	slice_release(&last_key);
     ++	slice_release(&key);
     ++	slice_release(&dest);
     ++	slice_release(&roundtrip);
     ++}
     ++
     ++static void test_reftable_obj_record_roundtrip(void)
     ++{
     ++	byte testHash1[SHA1_SIZE] = { 1, 2, 3, 4, 0 };
     ++	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
     ++	struct reftable_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 < ARRAY_SIZE(recs); i++) {
     ++		struct reftable_obj_record in = recs[i];
     ++		struct slice dest = SLICE_INIT;
     ++		struct reftable_record rec = { 0 };
     ++		struct slice key = SLICE_INIT;
     ++		struct reftable_obj_record out = { 0 };
     ++		struct reftable_record rec_out = { 0 };
     ++		int n, m;
     ++		byte extra;
     ++
     ++		reftable_record_from_obj(&rec, &in);
     ++		test_copy(&rec);
     ++		reftable_record_key(&rec, &key);
     ++		slice_resize(&dest, 1024);
     ++		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     ++		assert(n > 0);
     ++		extra = reftable_record_val_type(&rec);
     ++		reftable_record_from_obj(&rec_out, &out);
     ++		m = reftable_record_decode(&rec_out, key, extra, dest,
     ++					   SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		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));
     ++		slice_release(&key);
     ++		slice_release(&dest);
     ++		reftable_record_clear(&rec_out);
     ++	}
     ++}
     ++
     ++static void test_reftable_index_record_roundtrip(void)
     ++{
     ++	struct reftable_index_record in = {
     ++		.offset = 42,
     ++		.last_key = SLICE_INIT,
     ++	};
     ++	struct slice dest = SLICE_INIT;
     ++	struct slice key = SLICE_INIT;
     ++	struct reftable_record rec = { 0 };
     ++	struct reftable_index_record out = { .last_key = SLICE_INIT };
     ++	struct reftable_record out_rec = { NULL };
     ++	int n, m;
     ++	byte extra;
     ++
     ++	slice_set_string(&in.last_key, "refs/heads/master");
     ++	reftable_record_from_index(&rec, &in);
     ++	reftable_record_key(&rec, &key);
     ++	test_copy(&rec);
     ++
     ++	assert(0 == slice_cmp(&key, &in.last_key));
     ++	slice_resize(&dest, 1024);
     ++	n = reftable_record_encode(&rec, dest, SHA1_SIZE);
     ++	assert(n > 0);
     ++
     ++	extra = reftable_record_val_type(&rec);
     ++	reftable_record_from_index(&out_rec, &out);
     ++	m = reftable_record_decode(&out_rec, key, extra, dest, SHA1_SIZE);
     ++	assert(m == n);
     ++
     ++	assert(in.offset == out.offset);
     ++
     ++	reftable_record_clear(&out_rec);
     ++	slice_release(&key);
     ++	slice_release(&in.last_key);
     ++	slice_release(&dest);
     ++}
     ++
     ++int record_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("test_reftable_log_record_equal",
     ++		      &test_reftable_log_record_equal);
     ++	add_test_case("test_reftable_log_record_roundtrip",
     ++		      &test_reftable_log_record_roundtrip);
     ++	add_test_case("test_reftable_ref_record_roundtrip",
     ++		      &test_reftable_ref_record_roundtrip);
     ++	add_test_case("test_varint_roundtrip", &test_varint_roundtrip);
     ++	add_test_case("test_key_roundtrip", &test_key_roundtrip);
     ++	add_test_case("test_common_prefix", &test_common_prefix);
     ++	add_test_case("test_reftable_obj_record_roundtrip",
     ++		      &test_reftable_obj_record_roundtrip);
     ++	add_test_case("test_reftable_index_record_roundtrip",
     ++		      &test_reftable_index_record_roundtrip);
     ++	add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
     ++	return test_main(argc, argv);
     ++}
     +
     + ## reftable/refname.c (new) ##
     +@@
     ++/*
     ++  Copyright 2020 Google LLC
     ++
     ++  Use of this source code is governed by a BSD-style
     ++  license that can be found in the LICENSE file or at
     ++  https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "system.h"
     ++#include "reftable.h"
     ++#include "basics.h"
     ++#include "refname.h"
     ++#include "slice.h"
     ++
     ++struct find_arg {
     ++	char **names;
     ++	const char *want;
     ++};
     ++
     ++static int find_name(size_t k, void *arg)
     ++{
     ++	struct find_arg *f_arg = (struct find_arg *)arg;
     ++	return strcmp(f_arg->names[k], f_arg->want) >= 0;
     ++}
     ++
     ++int modification_has_ref(struct modification *mod, const char *name)
     ++{
     ++	struct reftable_ref_record ref = { 0 };
     ++	int err = 0;
     ++
     ++	if (mod->add_len > 0) {
     ++		struct find_arg arg = {
     ++			.names = mod->add,
     ++			.want = name,
     ++		};
     ++		int idx = binsearch(mod->add_len, find_name, &arg);
     ++		if (idx < mod->add_len && !strcmp(mod->add[idx], name)) {
     ++			return 0;
     ++		}
     ++	}
     ++
     ++	if (mod->del_len > 0) {
     ++		struct find_arg arg = {
     ++			.names = mod->del,
     ++			.want = name,
     ++		};
     ++		int idx = binsearch(mod->del_len, find_name, &arg);
     ++		if (idx < mod->del_len && !strcmp(mod->del[idx], name)) {
     ++			return 1;
     ++		}
     ++	}
     ++
     ++	err = reftable_table_read_ref(&mod->tab, name, &ref);
     ++	reftable_ref_record_clear(&ref);
     ++	return err;
     ++}
     ++
     ++static void modification_clear(struct modification *mod)
     ++{
     ++	/* don't delete the strings themselves; they're owned by ref records.
     ++	 */
     ++	FREE_AND_NULL(mod->add);
     ++	FREE_AND_NULL(mod->del);
     ++	mod->add_len = 0;
     ++	mod->del_len = 0;
     ++}
     ++
     ++int modification_has_ref_with_prefix(struct modification *mod,
     ++				     const char *prefix)
     ++{
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_ref_record ref = { NULL };
     ++	int err = 0;
      +
      +	if (mod->add_len > 0) {
      +		struct find_arg arg = {
     @@ reftable/refname.h (new)
      +
      +int modification_validate(struct modification *mod);
      +
     ++#endif
     +
     + ## reftable/refname_test.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "reftable.h"
     ++
     ++#include "basics.h"
     ++#include "block.h"
     ++#include "constants.h"
     ++#include "reader.h"
     ++#include "record.h"
     ++#include "refname.h"
     ++#include "system.h"
     ++
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
     ++
     ++struct testcase {
     ++	char *add;
     ++	char *del;
     ++	int error_code;
     ++};
     ++
     ++static void test_conflict(void)
     ++{
     ++	struct reftable_write_options opts = { 0 };
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, &buf, &opts);
     ++	struct reftable_ref_record rec = {
     ++		.ref_name = "a/b",
     ++		.target = "destination", /* make sure it's not a symref. */
     ++		.update_index = 1,
     ++	};
     ++	int err;
     ++	int i;
     ++	struct reftable_block_source source = { 0 };
     ++	struct reftable_reader *rd = NULL;
     ++	struct reftable_table tab = { NULL };
     ++	struct testcase cases[] = {
     ++		{ "a/b/c", NULL, REFTABLE_NAME_CONFLICT },
     ++		{ "b", NULL, 0 },
     ++		{ "a", NULL, REFTABLE_NAME_CONFLICT },
     ++		{ "a", "a/b", 0 },
     ++
     ++		{ "p/", NULL, REFTABLE_REFNAME_ERROR },
     ++		{ "p//q", NULL, REFTABLE_REFNAME_ERROR },
     ++		{ "p/./q", NULL, REFTABLE_REFNAME_ERROR },
     ++		{ "p/../q", NULL, REFTABLE_REFNAME_ERROR },
     ++
     ++		{ "a/b/c", "a/b", 0 },
     ++		{ NULL, "a//b", 0 },
     ++	};
     ++	reftable_writer_set_limits(w, 1, 1);
     ++
     ++	err = reftable_writer_add_ref(w, &rec);
     ++	assert_err(err);
     ++
     ++	err = reftable_writer_close(w);
     ++	assert_err(err);
     ++	reftable_writer_free(w);
     ++
     ++	block_source_from_slice(&source, &buf);
     ++	err = reftable_new_reader(&rd, &source, "filename");
     ++	assert_err(err);
     ++
     ++	reftable_table_from_reader(&tab, rd);
     ++
     ++	for (i = 0; i < ARRAY_SIZE(cases); i++) {
     ++		struct modification mod = {
     ++			.tab = tab,
     ++		};
     ++
     ++		if (cases[i].add != NULL) {
     ++			mod.add = &cases[i].add;
     ++			mod.add_len = 1;
     ++		}
     ++		if (cases[i].del != NULL) {
     ++			mod.del = &cases[i].del;
     ++			mod.del_len = 1;
     ++		}
     ++
     ++		err = modification_validate(&mod);
     ++		assert(err == cases[i].error_code);
     ++	}
     ++
     ++	reftable_reader_free(rd);
     ++	slice_release(&buf);
     ++}
     ++
     ++int refname_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("test_conflict", &test_conflict);
     ++	return test_main(argc, argv);
     ++}
     +
     + ## reftable/reftable-tests.h (new) ##
     +@@
     ++#ifndef REFTABLE_TESTS_H
     ++#define REFTABLE_TESTS_H
     ++
     ++int block_test_main(int argc, const char **argv);
     ++int merged_test_main(int argc, const char **argv);
     ++int record_test_main(int argc, const char **argv);
     ++int refname_test_main(int argc, const char **argv);
     ++int reftable_test_main(int argc, const char **argv);
     ++int slice_test_main(int argc, const char **argv);
     ++int stack_test_main(int argc, const char **argv);
     ++int tree_test_main(int argc, const char **argv);
     ++
      +#endif
      
       ## reftable/reftable.c (new) ##
     @@ reftable/reftable.h (new)
      +
      +#endif
      
     - ## reftable/slice.c (new) ##
     + ## reftable/reftable_test.c (new) ##
      @@
      +/*
      +Copyright 2020 Google LLC
     @@ reftable/slice.c (new)
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include "slice.h"
     ++#include "reftable.h"
      +
      +#include "system.h"
      +
     -+#include "reftable.h"
     ++#include "basics.h"
     ++#include "block.h"
     ++#include "constants.h"
     ++#include "reader.h"
     ++#include "record.h"
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
      +
     -+struct slice reftable_empty_slice = SLICE_INIT;
     ++static const int update_index = 5;
      +
     -+void slice_set_string(struct slice *s, const char *str)
     ++static void test_buffer(void)
      +{
     -+	int l;
     -+	if (str == NULL) {
     -+		s->len = 0;
     -+		return;
     -+	}
     -+	assert(s->canary == SLICE_CANARY);
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_block_source source = { NULL };
     ++	struct reftable_block out = { 0 };
     ++	int n;
     ++	byte in[] = "hello";
     ++	slice_add(&buf, in, sizeof(in));
     ++	block_source_from_slice(&source, &buf);
     ++	assert(block_source_size(&source) == 6);
     ++	n = block_source_read_block(&source, &out, 0, sizeof(in));
     ++	assert(n == sizeof(in));
     ++	assert(!memcmp(in, out.data, n));
     ++	reftable_block_done(&out);
     ++
     ++	n = block_source_read_block(&source, &out, 1, 2);
     ++	assert(n == 2);
     ++	assert(!memcmp(out.data, "el", 2));
     ++
     ++	reftable_block_done(&out);
     ++	block_source_close(&source);
     ++	slice_release(&buf);
     ++}
     ++
     ++static void test_default_write_opts(void)
     ++{
     ++	struct reftable_write_options opts = { 0 };
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, &buf, &opts);
     ++
     ++	struct reftable_ref_record rec = {
     ++		.ref_name = "master",
     ++		.update_index = 1,
     ++	};
     ++	int err;
     ++	struct reftable_block_source source = { 0 };
     ++	struct reftable_reader **readers = malloc(sizeof(*readers) * 1);
     ++	uint32_t hash_id;
     ++	struct reftable_reader *rd = NULL;
     ++	struct reftable_merged_table *merged = NULL;
      +
     -+	l = strlen(str);
     -+	l++; /* \0 */
     -+	slice_resize(s, l);
     -+	memcpy(s->buf, str, l);
     -+	s->len = l - 1;
     -+}
     ++	reftable_writer_set_limits(w, 1, 1);
      +
     -+void slice_init(struct slice *s)
     -+{
     -+	struct slice empty = SLICE_INIT;
     -+	*s = empty;
     -+}
     ++	err = reftable_writer_add_ref(w, &rec);
     ++	assert_err(err);
      +
     -+void slice_resize(struct slice *s, int l)
     -+{
     -+	assert(s->canary == SLICE_CANARY);
     -+	if (s->cap < l) {
     -+		int c = s->cap * 2;
     -+		if (c < l) {
     -+			c = l;
     -+		}
     -+		s->cap = c;
     -+		s->buf = reftable_realloc(s->buf, s->cap);
     -+	}
     -+	s->len = l;
     -+}
     ++	err = reftable_writer_close(w);
     ++	assert_err(err);
     ++	reftable_writer_free(w);
      +
     -+void slice_addstr(struct slice *d, const char *s)
     -+{
     -+	int l1 = d->len;
     -+	int l2 = strlen(s);
     -+	assert(d->canary == SLICE_CANARY);
     ++	block_source_from_slice(&source, &buf);
      +
     -+	slice_resize(d, l2 + l1);
     -+	memcpy(d->buf + l1, s, l2);
     ++	err = reftable_new_reader(&rd, &source, "filename");
     ++	assert_err(err);
     ++
     ++	hash_id = reftable_reader_hash_id(rd);
     ++	assert(hash_id == SHA1_ID);
     ++
     ++	readers[0] = rd;
     ++
     ++	err = reftable_new_merged_table(&merged, readers, 1, SHA1_ID);
     ++	assert_err(err);
     ++
     ++	reftable_merged_table_close(merged);
     ++	reftable_merged_table_free(merged);
     ++	slice_release(&buf);
      +}
      +
     -+void slice_addbuf(struct slice *s, struct slice a)
     ++static void write_table(char ***names, struct slice *buf, int N, int block_size,
     ++			uint32_t hash_id)
      +{
     -+	int end = s->len;
     -+	assert(s->canary == SLICE_CANARY);
     -+	slice_resize(s, s->len + a.len);
     -+	memcpy(s->buf + end, a.buf, a.len);
     -+}
     -+
     -+void slice_consume(struct slice *s, int n)
     -+{
     -+	assert(s->canary == SLICE_CANARY);
     -+	s->buf += n;
     -+	s->len -= n;
     -+}
     ++	struct reftable_write_options opts = {
     ++		.block_size = block_size,
     ++		.hash_id = hash_id,
     ++	};
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, buf, &opts);
     ++	struct reftable_ref_record ref = { 0 };
     ++	int i = 0, n;
     ++	struct reftable_log_record log = { 0 };
     ++	const struct reftable_stats *stats = NULL;
     ++	*names = reftable_calloc(sizeof(char *) * (N + 1));
     ++	reftable_writer_set_limits(w, update_index, update_index);
     ++	for (i = 0; i < N; i++) {
     ++		byte hash[SHA256_SIZE] = { 0 };
     ++		char name[100];
     ++		int n;
      +
     -+byte *slice_detach(struct slice *s)
     -+{
     -+	byte *p = s->buf;
     -+	assert(s->canary == SLICE_CANARY);
     -+	s->buf = NULL;
     -+	s->cap = 0;
     -+	s->len = 0;
     -+	return p;
     -+}
     ++		set_test_hash(hash, i);
      +
     -+void slice_release(struct slice *s)
     -+{
     -+	assert(s->canary == SLICE_CANARY);
     -+	reftable_free(slice_detach(s));
     -+}
     ++		snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
      +
     -+void slice_copy(struct slice *dest, struct slice src)
     -+{
     -+	assert(dest->canary == SLICE_CANARY);
     -+	assert(src.canary == SLICE_CANARY);
     -+	slice_resize(dest, src.len);
     -+	memcpy(dest->buf, src.buf, src.len);
     -+}
     ++		ref.ref_name = name;
     ++		ref.value = hash;
     ++		ref.update_index = update_index;
     ++		(*names)[i] = xstrdup(name);
      +
     -+/* 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)
     -+{
     -+	assert(s->canary == SLICE_CANARY);
     -+	if (s->cap == s->len) {
     -+		int l = s->len;
     -+		slice_resize(s, l + 1);
     -+		s->len = l;
     ++		n = reftable_writer_add_ref(w, &ref);
     ++		assert(n == 0);
      +	}
     -+	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_INIT;
     -+	assert(in.canary == SLICE_CANARY);
     -+	slice_resize(&s, in.len + 1);
     -+	s.buf[in.len] = 0;
     -+	memcpy(s.buf, in.buf, in.len);
     -+	return (char *)slice_detach(&s);
     -+}
     ++	for (i = 0; i < N; i++) {
     ++		byte hash[SHA256_SIZE] = { 0 };
     ++		char name[100];
     ++		int n;
      +
     -+bool slice_equal(struct slice a, struct slice b)
     -+{
     -+	assert(a.canary == SLICE_CANARY);
     -+	assert(b.canary == SLICE_CANARY);
     -+	if (a.len != b.len)
     -+		return 0;
     -+	return memcmp(a.buf, b.buf, a.len) == 0;
     -+}
     ++		set_test_hash(hash, i);
      +
     -+int slice_cmp(struct slice a, struct slice b)
     -+{
     -+	int min = a.len < b.len ? a.len : b.len;
     -+	int res = memcmp(a.buf, b.buf, min);
     -+	assert(a.canary == SLICE_CANARY);
     -+	assert(b.canary == SLICE_CANARY);
     -+	if (res != 0)
     -+		return res;
     -+	if (a.len < b.len)
     -+		return -1;
     -+	else if (a.len > b.len)
     -+		return 1;
     -+	else
     -+		return 0;
     -+}
     ++		snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
      +
     -+int slice_add(struct slice *b, byte *data, size_t sz)
     -+{
     -+	assert(b->canary == SLICE_CANARY);
     -+	if (b->len + sz > b->cap) {
     -+		int newcap = 2 * b->cap + 1;
     -+		if (newcap < b->len + sz) {
     -+			newcap = (b->len + sz);
     ++		log.ref_name = name;
     ++		log.new_hash = hash;
     ++		log.update_index = update_index;
     ++		log.message = "message";
     ++
     ++		n = reftable_writer_add_log(w, &log);
     ++		assert(n == 0);
     ++	}
     ++
     ++	n = reftable_writer_close(w);
     ++	assert(n == 0);
     ++
     ++	stats = writer_stats(w);
     ++	for (i = 0; i < stats->ref_stats.blocks; i++) {
     ++		int off = i * opts.block_size;
     ++		if (off == 0) {
     ++			off = header_size((hash_id == SHA256_ID) ? 2 : 1);
      +		}
     -+		b->buf = reftable_realloc(b->buf, newcap);
     -+		b->cap = newcap;
     ++		assert(buf->buf[off] == 'r');
      +	}
      +
     -+	memcpy(b->buf + b->len, data, sz);
     -+	b->len += sz;
     -+	return sz;
     ++	assert(stats->log_stats.blocks > 0);
     ++	reftable_writer_free(w);
      +}
      +
     -+int slice_add_void(void *b, byte *data, size_t sz)
     ++static void test_log_buffer_size(void)
      +{
     -+	return slice_add((struct slice *)b, data, sz);
     -+}
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_write_options opts = {
     ++		.block_size = 4096,
     ++	};
     ++	int err;
     ++	struct reftable_log_record log = {
     ++		.ref_name = "refs/heads/master",
     ++		.name = "Han-Wen Nienhuys",
     ++		.email = "[email protected]",
     ++		.tz_offset = 100,
     ++		.time = 0x5e430672,
     ++		.update_index = 0xa,
     ++		.message = "commit: 9\n",
     ++	};
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, &buf, &opts);
      +
     -+static uint64_t slice_size(void *b)
     -+{
     -+	return ((struct slice *)b)->len;
     -+}
     ++	/* This tests buffer extension for log compression. Must use a random
     ++	   hash, to ensure that the compressed part is larger than the original.
     ++	*/
     ++	byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     ++	for (int i = 0; i < SHA1_SIZE; i++) {
     ++		hash1[i] = (byte)(rand() % 256);
     ++		hash2[i] = (byte)(rand() % 256);
     ++	}
     ++	log.old_hash = hash1;
     ++	log.new_hash = hash2;
     ++	reftable_writer_set_limits(w, update_index, update_index);
     ++	err = reftable_writer_add_log(w, &log);
     ++	assert_err(err);
     ++	err = reftable_writer_close(w);
     ++	assert_err(err);
     ++	reftable_writer_free(w);
     ++	slice_release(&buf);
     ++}
     ++
     ++static void test_log_write_read(void)
     ++{
     ++	int N = 2;
     ++	char **names = reftable_calloc(sizeof(char *) * (N + 1));
     ++	int err;
     ++	struct reftable_write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++	struct reftable_ref_record ref = { 0 };
     ++	int i = 0;
     ++	struct reftable_log_record log = { 0 };
     ++	int n;
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_reader rd = { 0 };
     ++	struct reftable_block_source source = { 0 };
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, &buf, &opts);
     ++	const struct reftable_stats *stats = NULL;
     ++	reftable_writer_set_limits(w, 0, N);
     ++	for (i = 0; i < N; i++) {
     ++		char name[256];
     ++		struct reftable_ref_record ref = { 0 };
     ++		snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
     ++		names[i] = xstrdup(name);
     ++		puts(name);
     ++		ref.ref_name = name;
     ++		ref.update_index = i;
      +
     -+static void slice_return_block(void *b, struct reftable_block *dest)
     -+{
     -+	memset(dest->data, 0xff, dest->len);
     -+	reftable_free(dest->data);
     -+}
     ++		err = reftable_writer_add_ref(w, &ref);
     ++		assert_err(err);
     ++	}
     ++	for (i = 0; i < N; i++) {
     ++		byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     ++		struct reftable_log_record log = { 0 };
     ++		set_test_hash(hash1, i);
     ++		set_test_hash(hash2, i + 1);
      +
     -+static void slice_close(void *b)
     -+{
     -+}
     ++		log.ref_name = names[i];
     ++		log.update_index = i;
     ++		log.old_hash = hash1;
     ++		log.new_hash = hash2;
      +
     -+static int slice_read_block(void *v, struct reftable_block *dest, uint64_t off,
     -+			    uint32_t size)
     -+{
     -+	struct slice *b = (struct slice *)v;
     -+	assert(off + size <= b->len);
     -+	dest->data = reftable_calloc(size);
     -+	memcpy(dest->data, b->buf + off, size);
     -+	dest->len = size;
     -+	return size;
     -+}
     ++		err = reftable_writer_add_log(w, &log);
     ++		assert_err(err);
     ++	}
      +
     -+struct reftable_block_source_vtable slice_vtable = {
     -+	.size = &slice_size,
     -+	.read_block = &slice_read_block,
     -+	.return_block = &slice_return_block,
     -+	.close = &slice_close,
     -+};
     ++	n = reftable_writer_close(w);
     ++	assert(n == 0);
      +
     -+void block_source_from_slice(struct reftable_block_source *bs,
     -+			     struct slice *buf)
     -+{
     -+	assert(bs->ops == NULL);
     -+	bs->ops = &slice_vtable;
     -+	bs->arg = buf;
     -+}
     ++	stats = writer_stats(w);
     ++	assert(stats->log_stats.blocks > 0);
     ++	reftable_writer_free(w);
     ++	w = NULL;
      +
     -+static void malloc_return_block(void *b, struct reftable_block *dest)
     -+{
     -+	memset(dest->data, 0xff, dest->len);
     -+	reftable_free(dest->data);
     -+}
     ++	block_source_from_slice(&source, &buf);
      +
     -+struct reftable_block_source_vtable malloc_vtable = {
     -+	.return_block = &malloc_return_block,
     -+};
     ++	err = init_reader(&rd, &source, "file.log");
     ++	assert_err(err);
      +
     -+struct reftable_block_source malloc_block_source_instance = {
     -+	.ops = &malloc_vtable,
     -+};
     ++	err = reftable_reader_seek_ref(&rd, &it, names[N - 1]);
     ++	assert_err(err);
      +
     -+struct reftable_block_source malloc_block_source(void)
     -+{
     -+	return malloc_block_source_instance;
     -+}
     ++	err = reftable_iterator_next_ref(&it, &ref);
     ++	assert_err(err);
      +
     -+int common_prefix_size(struct slice a, struct slice b)
     -+{
     -+	int p = 0;
     -+	assert(a.canary == SLICE_CANARY);
     -+	assert(b.canary == SLICE_CANARY);
     -+	while (p < a.len && p < b.len) {
     -+		if (a.buf[p] != b.buf[p]) {
     ++	/* end of iteration. */
     ++	err = reftable_iterator_next_ref(&it, &ref);
     ++	assert(0 < err);
     ++
     ++	reftable_iterator_destroy(&it);
     ++	reftable_ref_record_clear(&ref);
     ++
     ++	err = reftable_reader_seek_log(&rd, &it, "");
     ++	assert_err(err);
     ++
     ++	i = 0;
     ++	while (true) {
     ++		int err = reftable_iterator_next_log(&it, &log);
     ++		if (err > 0) {
      +			break;
      +		}
     -+		p++;
     ++
     ++		assert_err(err);
     ++		assert_streq(names[i], log.ref_name);
     ++		assert(i == log.update_index);
     ++		i++;
     ++		reftable_log_record_clear(&log);
      +	}
      +
     -+	return p;
     ++	assert(i == N);
     ++	reftable_iterator_destroy(&it);
     ++
     ++	/* cleanup. */
     ++	slice_release(&buf);
     ++	free_names(names);
     ++	reader_close(&rd);
      +}
     -
     - ## reftable/slice.h (new) ##
     -@@
     -+/*
     -+Copyright 2020 Google LLC
      +
     -+Use of this source code is governed by a BSD-style
     -+license that can be found in the LICENSE file or at
     -+https://developers.google.com/open-source/licenses/bsd
     -+*/
     ++static void test_table_read_write_sequential(void)
     ++{
     ++	char **names;
     ++	struct slice buf = SLICE_INIT;
     ++	int N = 50;
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_block_source source = { 0 };
     ++	struct reftable_reader rd = { 0 };
     ++	int err = 0;
     ++	int j = 0;
      +
     -+#ifndef SLICE_H
     -+#define SLICE_H
     ++	write_table(&names, &buf, N, 256, SHA1_ID);
      +
     -+#include "basics.h"
     -+#include "reftable.h"
     ++	block_source_from_slice(&source, &buf);
      +
     -+/*
     -+  provides bounds-checked byte ranges.
     -+  To use, initialize as "slice x = {0};"
     -+ */
     -+struct slice {
     -+	int len;
     -+	int cap;
     -+	byte *buf;
     -+	byte canary;
     -+};
     -+#define SLICE_CANARY 0x42
     -+#define SLICE_INIT                       \
     -+	{                                \
     -+		0, 0, NULL, SLICE_CANARY \
     ++	err = init_reader(&rd, &source, "file.ref");
     ++	assert_err(err);
     ++
     ++	err = reftable_reader_seek_ref(&rd, &it, "");
     ++	assert_err(err);
     ++
     ++	while (true) {
     ++		struct reftable_ref_record ref = { 0 };
     ++		int r = reftable_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++;
     ++		reftable_ref_record_clear(&ref);
      +	}
     -+extern struct slice reftable_empty_slice;
     ++	assert(j == N);
     ++	reftable_iterator_destroy(&it);
     ++	slice_release(&buf);
     ++	free_names(names);
      +
     -+void slice_set_string(struct slice *dest, const char *src);
     -+void slice_addstr(struct slice *dest, const char *src);
     ++	reader_close(&rd);
     ++}
      +
     -+/* Deallocate and clear slice */
     -+void slice_release(struct slice *slice);
     ++static void test_table_write_small_table(void)
     ++{
     ++	char **names;
     ++	struct slice buf = SLICE_INIT;
     ++	int N = 1;
     ++	write_table(&names, &buf, N, 4096, SHA1_ID);
     ++	assert(buf.len < 200);
     ++	slice_release(&buf);
     ++	free_names(names);
     ++}
      +
     -+/* Return a malloced string for `src` */
     -+char *slice_to_string(struct slice src);
     ++static void test_table_read_api(void)
     ++{
     ++	char **names;
     ++	struct slice buf = SLICE_INIT;
     ++	int N = 50;
     ++	struct reftable_reader rd = { 0 };
     ++	struct reftable_block_source source = { 0 };
     ++	int err;
     ++	int i;
     ++	struct reftable_log_record log = { 0 };
     ++	struct reftable_iterator it = { 0 };
      +
     -+/* Initializes a slice. Accepts a slice with random garbage. */
     -+void slice_init(struct slice *slice);
     ++	write_table(&names, &buf, N, 256, SHA1_ID);
      +
     -+/* Ensure that `buf` is \0 terminated. */
     -+const char *slice_as_string(struct slice *src);
     ++	block_source_from_slice(&source, &buf);
      +
     -+/* Compare slices */
     -+bool slice_equal(struct slice a, struct slice b);
     ++	err = init_reader(&rd, &source, "file.ref");
     ++	assert_err(err);
      +
     -+/* Return `buf`, clearing out `s` */
     -+byte *slice_detach(struct slice *s);
     ++	err = reftable_reader_seek_ref(&rd, &it, names[0]);
     ++	assert_err(err);
      +
     -+/* Copy bytes */
     -+void slice_copy(struct slice *dest, struct slice src);
     ++	err = reftable_iterator_next_log(&it, &log);
     ++	assert(err == REFTABLE_API_ERROR);
      +
     -+/* Advance `buf` by `n`, and decrease length. A copy of the slice
     -+   should be kept for deallocating the slice. */
     -+void slice_consume(struct slice *s, int n);
     ++	slice_release(&buf);
     ++	for (i = 0; i < N; i++) {
     ++		reftable_free(names[i]);
     ++	}
     ++	reftable_iterator_destroy(&it);
     ++	reftable_free(names);
     ++	reader_close(&rd);
     ++	slice_release(&buf);
     ++}
      +
     -+/* Set length of the slice to `l` */
     -+void slice_resize(struct slice *s, int l);
     ++static void test_table_read_write_seek(bool index, int hash_id)
     ++{
     ++	char **names;
     ++	struct slice buf = SLICE_INIT;
     ++	int N = 50;
     ++	struct reftable_reader rd = { 0 };
     ++	struct reftable_block_source source = { 0 };
     ++	int err;
     ++	int i = 0;
      +
     -+/* Signed comparison */
     -+int slice_cmp(struct slice a, struct slice b);
     ++	struct reftable_iterator it = { 0 };
     ++	struct slice pastLast = SLICE_INIT;
     ++	struct reftable_ref_record ref = { 0 };
      +
     -+/* Append `data` to the `dest` slice.  */
     -+int slice_add(struct slice *dest, byte *data, size_t sz);
     ++	write_table(&names, &buf, N, 256, hash_id);
      +
     -+/* Append `add` to `dest. */
     -+void slice_addbuf(struct slice *dest, struct slice add);
     ++	block_source_from_slice(&source, &buf);
      +
     -+/* Like slice_add, but suitable for passing to reftable_new_writer
     -+ */
     -+int slice_add_void(void *b, byte *data, size_t sz);
     ++	err = init_reader(&rd, &source, "file.ref");
     ++	assert_err(err);
     ++	assert(hash_id == reftable_reader_hash_id(&rd));
      +
     -+/* Find the longest shared prefix size of `a` and `b` */
     -+int common_prefix_size(struct slice a, struct slice b);
     ++	if (!index) {
     ++		rd.ref_offsets.index_offset = 0;
     ++	} else {
     ++		assert(rd.ref_offsets.index_offset > 0);
     ++	}
      +
     -+struct reftable_block_source;
     ++	for (i = 1; i < N; i++) {
     ++		int err = reftable_reader_seek_ref(&rd, &it, names[i]);
     ++		assert_err(err);
     ++		err = reftable_iterator_next_ref(&it, &ref);
     ++		assert_err(err);
     ++		assert(0 == strcmp(names[i], ref.ref_name));
     ++		assert(i == ref.value[0]);
      +
     -+/* Create an in-memory block source for reading reftables */
     -+void block_source_from_slice(struct reftable_block_source *bs,
     -+			     struct slice *buf);
     ++		reftable_ref_record_clear(&ref);
     ++		reftable_iterator_destroy(&it);
     ++	}
      +
     -+struct reftable_block_source malloc_block_source(void);
     ++	slice_set_string(&pastLast, names[N - 1]);
     ++	slice_addstr(&pastLast, "/");
      +
     -+#endif
     -
     - ## reftable/stack.c (new) ##
     -@@
     -+/*
     -+Copyright 2020 Google LLC
     ++	err = reftable_reader_seek_ref(&rd, &it, slice_as_string(&pastLast));
     ++	if (err == 0) {
     ++		struct reftable_ref_record ref = { 0 };
     ++		int err = reftable_iterator_next_ref(&it, &ref);
     ++		assert(err > 0);
     ++	} else {
     ++		assert(err > 0);
     ++	}
      +
     -+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
     -+*/
     ++	slice_release(&pastLast);
     ++	reftable_iterator_destroy(&it);
      +
     -+#include "stack.h"
     ++	slice_release(&buf);
     ++	for (i = 0; i < N; i++) {
     ++		reftable_free(names[i]);
     ++	}
     ++	reftable_free(names);
     ++	reader_close(&rd);
     ++}
      +
     -+#include "system.h"
     -+#include "merged.h"
     -+#include "reader.h"
     -+#include "refname.h"
     -+#include "reftable.h"
     -+#include "writer.h"
     ++static void test_table_read_write_seek_linear(void)
     ++{
     ++	test_table_read_write_seek(false, SHA1_ID);
     ++}
      +
     -+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
     -+		       struct reftable_write_options config)
     ++static void test_table_read_write_seek_linear_sha256(void)
      +{
     -+	struct reftable_stack *p =
     -+		reftable_calloc(sizeof(struct reftable_stack));
     -+	struct slice list_file_name = SLICE_INIT;
     -+	int err = 0;
     ++	test_table_read_write_seek(false, SHA256_ID);
     ++}
      +
     -+	if (config.hash_id == 0) {
     -+		config.hash_id = SHA1_ID;
     -+	}
     ++static void test_table_read_write_seek_index(void)
     ++{
     ++	test_table_read_write_seek(true, SHA1_ID);
     ++}
      +
     -+	*dest = NULL;
     ++static void test_table_refs_for(bool indexed)
     ++{
     ++	int N = 50;
     ++	char **want_names = reftable_calloc(sizeof(char *) * (N + 1));
     ++	int want_names_len = 0;
     ++	byte want_hash[SHA1_SIZE];
      +
     -+	slice_set_string(&list_file_name, dir);
     -+	slice_addstr(&list_file_name, "/tables.list");
     ++	struct reftable_write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++	struct reftable_ref_record ref = { 0 };
     ++	int i = 0;
     ++	int n;
     ++	int err;
     ++	struct reftable_reader rd;
     ++	struct reftable_block_source source = { 0 };
      +
     -+	p->list_file = slice_to_string(list_file_name);
     -+	slice_release(&list_file_name);
     -+	p->reftable_dir = xstrdup(dir);
     -+	p->config = config;
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, &buf, &opts);
      +
     -+	err = reftable_stack_reload_maybe_reuse(p, true);
     -+	if (err < 0) {
     -+		reftable_stack_destroy(p);
     -+	} else {
     -+		*dest = p;
     -+	}
     -+	return err;
     -+}
     ++	struct reftable_iterator it = { 0 };
     ++	int j;
      +
     -+static int fd_read_lines(int fd, char ***namesp)
     -+{
     -+	off_t size = lseek(fd, 0, SEEK_END);
     -+	char *buf = NULL;
     -+	int err = 0;
     -+	if (size < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		goto done;
     -+	}
     -+	err = lseek(fd, 0, SEEK_SET);
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		goto done;
     ++	set_test_hash(want_hash, 4);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		byte hash[SHA1_SIZE];
     ++		char fill[51] = { 0 };
     ++		char name[100];
     ++		byte hash1[SHA1_SIZE];
     ++		byte hash2[SHA1_SIZE];
     ++		struct reftable_ref_record ref = { 0 };
     ++
     ++		memset(hash, i, sizeof(hash));
     ++		memset(fill, 'x', 50);
     ++		/* Put the variable part in the start */
     ++		snprintf(name, sizeof(name), "br%02d%s", i, fill);
     ++		name[40] = 0;
     ++		ref.ref_name = name;
     ++
     ++		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. */
     ++		n = reftable_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++] = xstrdup(name);
     ++		}
      +	}
      +
     -+	buf = reftable_malloc(size + 1);
     -+	if (read(fd, buf, size) != size) {
     -+		err = REFTABLE_IO_ERROR;
     -+		goto done;
     ++	n = reftable_writer_close(w);
     ++	assert(n == 0);
     ++
     ++	reftable_writer_free(w);
     ++	w = NULL;
     ++
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	err = init_reader(&rd, &source, "file.ref");
     ++	assert_err(err);
     ++	if (!indexed) {
     ++		rd.obj_offsets.present = 0;
      +	}
     -+	buf[size] = 0;
      +
     -+	parse_names(buf, size, namesp);
     ++	err = reftable_reader_seek_ref(&rd, &it, "");
     ++	assert_err(err);
     ++	reftable_iterator_destroy(&it);
      +
     -+done:
     -+	reftable_free(buf);
     -+	return err;
     -+}
     ++	err = reftable_reader_refs_for(&rd, &it, want_hash);
     ++	assert_err(err);
      +
     -+int read_lines(const char *filename, char ***namesp)
     -+{
     -+	int fd = open(filename, O_RDONLY, 0644);
     -+	int err = 0;
     -+	if (fd < 0) {
     -+		if (errno == ENOENT) {
     -+			*namesp = reftable_calloc(sizeof(char *));
     -+			return 0;
     ++	j = 0;
     ++	while (true) {
     ++		int err = reftable_iterator_next_ref(&it, &ref);
     ++		assert(err >= 0);
     ++		if (err > 0) {
     ++			break;
      +		}
      +
     -+		return REFTABLE_IO_ERROR;
     ++		assert(j < want_names_len);
     ++		assert(0 == strcmp(ref.ref_name, want_names[j]));
     ++		j++;
     ++		reftable_ref_record_clear(&ref);
      +	}
     -+	err = fd_read_lines(fd, namesp);
     -+	close(fd);
     -+	return err;
     -+}
     ++	assert(j == want_names_len);
      +
     -+struct reftable_merged_table *
     -+reftable_stack_merged_table(struct reftable_stack *st)
     -+{
     -+	return st->merged;
     ++	slice_release(&buf);
     ++	free_names(want_names);
     ++	reftable_iterator_destroy(&it);
     ++	reader_close(&rd);
      +}
      +
     -+/* Close and free the stack */
     -+void reftable_stack_destroy(struct reftable_stack *st)
     ++static void test_table_refs_for_no_index(void)
      +{
     -+	if (st->merged != NULL) {
     -+		reftable_merged_table_close(st->merged);
     -+		reftable_merged_table_free(st->merged);
     -+		st->merged = NULL;
     -+	}
     -+	FREE_AND_NULL(st->list_file);
     -+	FREE_AND_NULL(st->reftable_dir);
     -+	reftable_free(st);
     ++	test_table_refs_for(false);
      +}
      +
     -+static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
     -+						   int cur_len)
     ++static void test_table_refs_for_obj_index(void)
      +{
     -+	struct reftable_reader **cur =
     -+		reftable_calloc(sizeof(struct reftable_reader *) * cur_len);
     -+	int i = 0;
     -+	for (i = 0; i < cur_len; i++) {
     -+		cur[i] = st->merged->stack[i];
     -+	}
     -+	return cur;
     ++	test_table_refs_for(true);
      +}
      +
     -+static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
     -+				      bool reuse_open)
     ++static void test_table_empty(void)
      +{
     -+	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     -+	struct reftable_reader **cur = stack_copy_readers(st, cur_len);
     -+	int err = 0;
     -+	int names_len = names_length(names);
     -+	struct reftable_reader **new_tables =
     -+		reftable_malloc(sizeof(struct reftable_reader *) * names_len);
     -+	int new_tables_len = 0;
     -+	struct reftable_merged_table *new_merged = NULL;
     -+	int i;
     -+	struct slice table_path = SLICE_INIT;
     ++	struct reftable_write_options opts = { 0 };
     ++	struct slice buf = SLICE_INIT;
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&slice_add_void, &buf, &opts);
     ++	struct reftable_block_source source = { 0 };
     ++	struct reftable_reader *rd = NULL;
     ++	struct reftable_ref_record rec = { 0 };
     ++	struct reftable_iterator it = { 0 };
     ++	int err;
      +
     -+	while (*names) {
     -+		struct reftable_reader *rd = NULL;
     -+		char *name = *names++;
     ++	reftable_writer_set_limits(w, 1, 1);
      +
     -+		/* 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;
     -+			}
     -+		}
     ++	err = reftable_writer_close(w);
     ++	assert(err == REFTABLE_EMPTY_TABLE_ERROR);
     ++	reftable_writer_free(w);
      +
     -+		if (rd == NULL) {
     -+			struct reftable_block_source src = { 0 };
     -+			slice_set_string(&table_path, st->reftable_dir);
     -+			slice_addstr(&table_path, "/");
     -+			slice_addstr(&table_path, name);
     ++	assert(buf.len == header_size(1) + footer_size(1));
      +
     -+			err = reftable_block_source_from_file(
     -+				&src, slice_as_string(&table_path));
     -+			if (err < 0)
     -+				goto done;
     ++	block_source_from_slice(&source, &buf);
      +
     -+			err = reftable_new_reader(&rd, &src, name);
     -+			if (err < 0)
     -+				goto done;
     -+		}
     ++	err = reftable_new_reader(&rd, &source, "filename");
     ++	assert_err(err);
      +
     -+		new_tables[new_tables_len++] = rd;
     -+	}
     ++	err = reftable_reader_seek_ref(rd, &it, "");
     ++	assert_err(err);
      +
     -+	/* success! */
     -+	err = reftable_new_merged_table(&new_merged, new_tables, new_tables_len,
     -+					st->config.hash_id);
     -+	if (err < 0)
     -+		goto done;
     ++	err = reftable_iterator_next_ref(&it, &rec);
     ++	assert(err > 0);
      +
     -+	new_tables = NULL;
     -+	new_tables_len = 0;
     -+	if (st->merged != NULL) {
     -+		merged_table_clear(st->merged);
     -+		reftable_merged_table_free(st->merged);
     -+	}
     -+	new_merged->suppress_deletions = true;
     -+	st->merged = new_merged;
     ++	reftable_iterator_destroy(&it);
     ++	reftable_reader_free(rd);
     ++	slice_release(&buf);
     ++}
     ++
     ++int reftable_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("test_default_write_opts", test_default_write_opts);
     ++	add_test_case("test_log_write_read", test_log_write_read);
     ++	add_test_case("test_table_read_write_seek_linear_sha256",
     ++		      &test_table_read_write_seek_linear_sha256);
     ++	add_test_case("test_log_buffer_size", test_log_buffer_size);
     ++	add_test_case("test_table_write_small_table",
     ++		      &test_table_write_small_table);
     ++	add_test_case("test_buffer", &test_buffer);
     ++	add_test_case("test_table_read_api", &test_table_read_api);
     ++	add_test_case("test_table_read_write_sequential",
     ++		      &test_table_read_write_sequential);
     ++	add_test_case("test_table_read_write_seek_linear",
     ++		      &test_table_read_write_seek_linear);
     ++	add_test_case("test_table_read_write_seek_index",
     ++		      &test_table_read_write_seek_index);
     ++	add_test_case("test_table_read_write_refs_for_no_index",
     ++		      &test_table_refs_for_no_index);
     ++	add_test_case("test_table_read_write_refs_for_obj_index",
     ++		      &test_table_refs_for_obj_index);
     ++	add_test_case("test_table_empty", &test_table_empty);
     ++	return test_main(argc, argv);
     ++}
     +
     + ## reftable/slice.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+	for (i = 0; i < cur_len; i++) {
     -+		if (cur[i] != NULL) {
     -+			reader_close(cur[i]);
     -+			reftable_reader_free(cur[i]);
     -+		}
     -+	}
     ++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
     ++*/
      +
     -+done:
     -+	slice_release(&table_path);
     -+	for (i = 0; i < new_tables_len; i++) {
     -+		reader_close(new_tables[i]);
     -+		reftable_reader_free(new_tables[i]);
     -+	}
     -+	reftable_free(new_tables);
     -+	reftable_free(cur);
     -+	return err;
     -+}
     ++#include "slice.h"
      +
     -+/* return negative if a before b. */
     -+static int tv_cmp(struct timeval *a, struct timeval *b)
     ++#include "system.h"
     ++
     ++#include "reftable.h"
     ++
     ++struct slice reftable_empty_slice = SLICE_INIT;
     ++
     ++void slice_set_string(struct slice *s, const char *str)
      +{
     -+	time_t diff = a->tv_sec - b->tv_sec;
     -+	int udiff = a->tv_usec - b->tv_usec;
     ++	int l;
     ++	if (str == NULL) {
     ++		s->len = 0;
     ++		return;
     ++	}
     ++	assert(s->canary == SLICE_CANARY);
      +
     -+	if (diff != 0)
     -+		return diff;
     ++	l = strlen(str);
     ++	l++; /* \0 */
     ++	slice_resize(s, l);
     ++	memcpy(s->buf, str, l);
     ++	s->len = l - 1;
     ++}
      +
     -+	return udiff;
     ++void slice_init(struct slice *s)
     ++{
     ++	struct slice empty = SLICE_INIT;
     ++	*s = empty;
      +}
      +
     -+int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     -+				      bool reuse_open)
     ++void slice_resize(struct slice *s, int l)
      +{
     -+	struct timeval deadline = { 0 };
     ++	assert(s->canary == SLICE_CANARY);
     ++	if (s->cap < l) {
     ++		int c = s->cap * 2;
     ++		if (c < l) {
     ++			c = l;
     ++		}
     ++		s->cap = c;
     ++		s->buf = reftable_realloc(s->buf, s->cap);
     ++	}
     ++	s->len = l;
     ++}
     ++
     ++void slice_addstr(struct slice *d, const char *s)
     ++{
     ++	int l1 = d->len;
     ++	int l2 = strlen(s);
     ++	assert(d->canary == SLICE_CANARY);
     ++
     ++	slice_resize(d, l2 + l1);
     ++	memcpy(d->buf + l1, s, l2);
     ++}
     ++
     ++void slice_addbuf(struct slice *s, struct slice *a)
     ++{
     ++	int end = s->len;
     ++	assert(s->canary == SLICE_CANARY);
     ++	slice_resize(s, s->len + a->len);
     ++	memcpy(s->buf + end, a->buf, a->len);
     ++}
     ++
     ++void slice_consume(struct slice *s, int n)
     ++{
     ++	assert(s->canary == SLICE_CANARY);
     ++	s->buf += n;
     ++	s->len -= n;
     ++}
     ++
     ++byte *slice_detach(struct slice *s)
     ++{
     ++	byte *p = s->buf;
     ++	assert(s->canary == SLICE_CANARY);
     ++	s->buf = NULL;
     ++	s->cap = 0;
     ++	s->len = 0;
     ++	return p;
     ++}
     ++
     ++void slice_release(struct slice *s)
     ++{
     ++	assert(s->canary == SLICE_CANARY);
     ++	reftable_free(slice_detach(s));
     ++}
     ++
     ++void slice_copy(struct slice *dest, struct slice *src)
     ++{
     ++	assert(dest->canary == SLICE_CANARY);
     ++	assert(src->canary == SLICE_CANARY);
     ++	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)
     ++{
     ++	assert(s->canary == SLICE_CANARY);
     ++	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_INIT;
     ++	assert(in->canary == SLICE_CANARY);
     ++	slice_resize(&s, in->len + 1);
     ++	s.buf[in->len] = 0;
     ++	memcpy(s.buf, in->buf, in->len);
     ++	return (char *)slice_detach(&s);
     ++}
     ++
     ++bool slice_equal(struct slice *a, struct slice *b)
     ++{
     ++	return slice_cmp(a, b) == 0;
     ++}
     ++
     ++int slice_cmp(const struct slice *a, const struct slice *b)
     ++{
     ++	int min = a->len < b->len ? a->len : b->len;
     ++	int res = memcmp(a->buf, b->buf, min);
     ++	assert(a->canary == SLICE_CANARY);
     ++	assert(b->canary == SLICE_CANARY);
     ++	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_add(struct slice *b, byte *data, size_t sz)
     ++{
     ++	assert(b->canary == SLICE_CANARY);
     ++	if (b->len + sz > b->cap) {
     ++		int newcap = 2 * b->cap + 1;
     ++		if (newcap < b->len + sz) {
     ++			newcap = (b->len + sz);
     ++		}
     ++		b->buf = reftable_realloc(b->buf, newcap);
     ++		b->cap = newcap;
     ++	}
     ++
     ++	memcpy(b->buf + b->len, data, sz);
     ++	b->len += sz;
     ++	return sz;
     ++}
     ++
     ++int slice_add_void(void *b, byte *data, size_t sz)
     ++{
     ++	return slice_add((struct slice *)b, data, sz);
     ++}
     ++
     ++static uint64_t slice_size(void *b)
     ++{
     ++	return ((struct slice *)b)->len;
     ++}
     ++
     ++static void slice_return_block(void *b, struct reftable_block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	reftable_free(dest->data);
     ++}
     ++
     ++static void slice_close(void *b)
     ++{
     ++}
     ++
     ++static int slice_read_block(void *v, struct reftable_block *dest, uint64_t off,
     ++			    uint32_t size)
     ++{
     ++	struct slice *b = (struct slice *)v;
     ++	assert(off + size <= b->len);
     ++	dest->data = reftable_calloc(size);
     ++	memcpy(dest->data, b->buf + off, size);
     ++	dest->len = size;
     ++	return size;
     ++}
     ++
     ++struct reftable_block_source_vtable slice_vtable = {
     ++	.size = &slice_size,
     ++	.read_block = &slice_read_block,
     ++	.return_block = &slice_return_block,
     ++	.close = &slice_close,
     ++};
     ++
     ++void block_source_from_slice(struct reftable_block_source *bs,
     ++			     struct slice *buf)
     ++{
     ++	assert(bs->ops == NULL);
     ++	bs->ops = &slice_vtable;
     ++	bs->arg = buf;
     ++}
     ++
     ++static void malloc_return_block(void *b, struct reftable_block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	reftable_free(dest->data);
     ++}
     ++
     ++struct reftable_block_source_vtable malloc_vtable = {
     ++	.return_block = &malloc_return_block,
     ++};
     ++
     ++struct reftable_block_source malloc_block_source_instance = {
     ++	.ops = &malloc_vtable,
     ++};
     ++
     ++struct reftable_block_source malloc_block_source(void)
     ++{
     ++	return malloc_block_source_instance;
     ++}
     ++
     ++int common_prefix_size(struct slice *a, struct slice *b)
     ++{
     ++	int p = 0;
     ++	assert(a->canary == SLICE_CANARY);
     ++	assert(b->canary == SLICE_CANARY);
     ++	while (p < a->len && p < b->len) {
     ++		if (a->buf[p] != b->buf[p]) {
     ++			break;
     ++		}
     ++		p++;
     ++	}
     ++
     ++	return p;
     ++}
     +
     + ## reftable/slice.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef SLICE_H
     ++#define SLICE_H
     ++
     ++#include "basics.h"
     ++#include "reftable.h"
     ++
     ++/*
     ++  provides bounds-checked byte ranges.
     ++  To use, initialize as "slice x = {0};"
     ++ */
     ++struct slice {
     ++	int len;
     ++	int cap;
     ++	byte *buf;
     ++	byte canary;
     ++};
     ++#define SLICE_CANARY 0x42
     ++#define SLICE_INIT                       \
     ++	{                                \
     ++		0, 0, NULL, SLICE_CANARY \
     ++	}
     ++extern struct slice reftable_empty_slice;
     ++
     ++void slice_set_string(struct slice *dest, const char *src);
     ++void slice_addstr(struct slice *dest, const char *src);
     ++
     ++/* Deallocate and clear slice */
     ++void slice_release(struct slice *slice);
     ++
     ++/* Return a malloced string for `src` */
     ++char *slice_to_string(struct slice *src);
     ++
     ++/* Initializes a slice. Accepts a slice with random garbage. */
     ++void slice_init(struct slice *slice);
     ++
     ++/* Ensure that `buf` is \0 terminated. */
     ++const char *slice_as_string(struct slice *src);
     ++
     ++/* Compare slices */
     ++bool slice_equal(struct slice *a, struct slice *b);
     ++
     ++/* Return `buf`, clearing out `s` */
     ++byte *slice_detach(struct slice *s);
     ++
     ++/* Copy bytes */
     ++void slice_copy(struct slice *dest, struct slice *src);
     ++
     ++/* Advance `buf` by `n`, and decrease length. A copy of the slice
     ++   should be kept for deallocating the slice. */
     ++void slice_consume(struct slice *s, int n);
     ++
     ++/* Set length of the slice to `l` */
     ++void slice_resize(struct slice *s, int l);
     ++
     ++/* Signed comparison */
     ++int slice_cmp(const struct slice *a, const struct slice *b);
     ++
     ++/* Append `data` to the `dest` slice.  */
     ++int slice_add(struct slice *dest, byte *data, size_t sz);
     ++
     ++/* Append `add` to `dest. */
     ++void slice_addbuf(struct slice *dest, struct slice *add);
     ++
     ++/* Like slice_add, but suitable for passing to reftable_new_writer
     ++ */
     ++int slice_add_void(void *b, byte *data, size_t sz);
     ++
     ++/* Find the longest shared prefix size of `a` and `b` */
     ++int common_prefix_size(struct slice *a, struct slice *b);
     ++
     ++struct reftable_block_source;
     ++
     ++/* Create an in-memory block source for reading reftables */
     ++void block_source_from_slice(struct reftable_block_source *bs,
     ++			     struct slice *buf);
     ++
     ++struct reftable_block_source malloc_block_source(void);
     ++
     ++#endif
     +
     + ## reftable/slice_test.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "slice.h"
     ++
     ++#include "system.h"
     ++
     ++#include "basics.h"
     ++#include "record.h"
     ++#include "reftable.h"
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
     ++
     ++static void test_slice(void)
     ++{
     ++	struct slice s = SLICE_INIT;
     ++	struct slice t = SLICE_INIT;
     ++
     ++	slice_set_string(&s, "abc");
     ++	assert(0 == strcmp("abc", slice_as_string(&s)));
     ++
     ++	slice_set_string(&t, "pqr");
     ++
     ++	slice_addbuf(&s, &t);
     ++	assert(0 == strcmp("abcpqr", slice_as_string(&s)));
     ++
     ++	slice_release(&s);
     ++	slice_release(&t);
     ++}
     ++
     ++int slice_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("test_slice", &test_slice);
     ++	return test_main(argc, argv);
     ++}
     +
     + ## reftable/stack.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "stack.h"
     ++
     ++#include "system.h"
     ++#include "merged.h"
     ++#include "reader.h"
     ++#include "refname.h"
     ++#include "reftable.h"
     ++#include "writer.h"
     ++
     ++int reftable_new_stack(struct reftable_stack **dest, const char *dir,
     ++		       struct reftable_write_options config)
     ++{
     ++	struct reftable_stack *p =
     ++		reftable_calloc(sizeof(struct reftable_stack));
     ++	struct slice list_file_name = SLICE_INIT;
     ++	int err = 0;
     ++
     ++	if (config.hash_id == 0) {
     ++		config.hash_id = SHA1_ID;
     ++	}
     ++
     ++	*dest = NULL;
     ++
     ++	slice_set_string(&list_file_name, dir);
     ++	slice_addstr(&list_file_name, "/tables.list");
     ++
     ++	p->list_file = slice_to_string(&list_file_name);
     ++	slice_release(&list_file_name);
     ++	p->reftable_dir = xstrdup(dir);
     ++	p->config = config;
     ++
     ++	err = reftable_stack_reload_maybe_reuse(p, true);
     ++	if (err < 0) {
     ++		reftable_stack_destroy(p);
     ++	} else {
     ++		*dest = p;
     ++	}
     ++	return err;
     ++}
     ++
     ++static int fd_read_lines(int fd, char ***namesp)
     ++{
     ++	off_t size = lseek(fd, 0, SEEK_END);
     ++	char *buf = NULL;
     ++	int err = 0;
     ++	if (size < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++	err = lseek(fd, 0, SEEK_SET);
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	buf = reftable_malloc(size + 1);
     ++	if (read(fd, buf, size) != size) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++	buf[size] = 0;
     ++
     ++	parse_names(buf, size, namesp);
     ++
     ++done:
     ++	reftable_free(buf);
     ++	return err;
     ++}
     ++
     ++int read_lines(const char *filename, char ***namesp)
     ++{
     ++	int fd = open(filename, O_RDONLY, 0644);
     ++	int err = 0;
     ++	if (fd < 0) {
     ++		if (errno == ENOENT) {
     ++			*namesp = reftable_calloc(sizeof(char *));
     ++			return 0;
     ++		}
     ++
     ++		return REFTABLE_IO_ERROR;
     ++	}
     ++	err = fd_read_lines(fd, namesp);
     ++	close(fd);
     ++	return err;
     ++}
     ++
     ++struct reftable_merged_table *
     ++reftable_stack_merged_table(struct reftable_stack *st)
     ++{
     ++	return st->merged;
     ++}
     ++
     ++/* Close and free the stack */
     ++void reftable_stack_destroy(struct reftable_stack *st)
     ++{
     ++	if (st->merged != NULL) {
     ++		reftable_merged_table_close(st->merged);
     ++		reftable_merged_table_free(st->merged);
     ++		st->merged = NULL;
     ++	}
     ++	FREE_AND_NULL(st->list_file);
     ++	FREE_AND_NULL(st->reftable_dir);
     ++	reftable_free(st);
     ++}
     ++
     ++static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
     ++						   int cur_len)
     ++{
     ++	struct reftable_reader **cur =
     ++		reftable_calloc(sizeof(struct reftable_reader *) * cur_len);
     ++	int i = 0;
     ++	for (i = 0; i < cur_len; i++) {
     ++		cur[i] = st->merged->stack[i];
     ++	}
     ++	return cur;
     ++}
     ++
     ++static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
     ++				      bool reuse_open)
     ++{
     ++	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     ++	struct reftable_reader **cur = stack_copy_readers(st, cur_len);
     ++	int err = 0;
     ++	int names_len = names_length(names);
     ++	struct reftable_reader **new_tables =
     ++		reftable_malloc(sizeof(struct reftable_reader *) * names_len);
     ++	int new_tables_len = 0;
     ++	struct reftable_merged_table *new_merged = NULL;
     ++	int i;
     ++	struct slice table_path = SLICE_INIT;
     ++
     ++	while (*names) {
     ++		struct reftable_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 reftable_block_source src = { 0 };
     ++			slice_set_string(&table_path, st->reftable_dir);
     ++			slice_addstr(&table_path, "/");
     ++			slice_addstr(&table_path, name);
     ++
     ++			err = reftable_block_source_from_file(
     ++				&src, slice_as_string(&table_path));
     ++			if (err < 0)
     ++				goto done;
     ++
     ++			err = reftable_new_reader(&rd, &src, name);
     ++			if (err < 0)
     ++				goto done;
     ++		}
     ++
     ++		new_tables[new_tables_len++] = rd;
     ++	}
     ++
     ++	/* success! */
     ++	err = reftable_new_merged_table(&new_merged, new_tables, new_tables_len,
     ++					st->config.hash_id);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	new_tables = NULL;
     ++	new_tables_len = 0;
     ++	if (st->merged != NULL) {
     ++		merged_table_clear(st->merged);
     ++		reftable_merged_table_free(st->merged);
     ++	}
     ++	new_merged->suppress_deletions = true;
     ++	st->merged = new_merged;
     ++
     ++	for (i = 0; i < cur_len; i++) {
     ++		if (cur[i] != NULL) {
     ++			reader_close(cur[i]);
     ++			reftable_reader_free(cur[i]);
     ++		}
     ++	}
     ++
     ++done:
     ++	slice_release(&table_path);
     ++	for (i = 0; i < new_tables_len; i++) {
     ++		reader_close(new_tables[i]);
     ++		reftable_reader_free(new_tables[i]);
     ++	}
     ++	reftable_free(new_tables);
     ++	reftable_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;
     ++}
     ++
     ++int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     ++				      bool reuse_open)
     ++{
     ++	struct timeval deadline = { 0 };
      +	int err = gettimeofday(&deadline, NULL);
      +	int64_t delay = 0;
      +	int tries = 0;
      +	if (err < 0)
     -+		return err;
     ++		return err;
     ++
     ++	deadline.tv_sec += 3;
     ++	while (true) {
     ++		char **names = NULL;
     ++		char **names_after = NULL;
     ++		struct timeval now = { 0 };
     ++		int err = gettimeofday(&now, NULL);
     ++		int err2 = 0;
     ++		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 = reftable_stack_reload_once(st, names, reuse_open);
     ++		if (err == 0) {
     ++			free_names(names);
     ++			break;
     ++		}
     ++		if (err != REFTABLE_NOT_EXIST_ERROR) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++
     ++		/* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
     ++		   writer. Check if there was one by checking if the name list
     ++		   changed.
     ++		*/
     ++		err2 = read_lines(st->list_file, &names_after);
     ++		if (err2 < 0) {
     ++			free_names(names);
     ++			return err2;
     ++		}
     ++
     ++		if (names_equal(names_after, names)) {
     ++			free_names(names);
     ++			free_names(names_after);
     ++			return err;
     ++		}
     ++		free_names(names);
     ++		free_names(names_after);
     ++
     ++		delay = delay + (delay * rand()) / RAND_MAX + 1;
     ++		sleep_millisec(delay);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++/* -1 = error
     ++ 0 = up to date
     ++ 1 = changed. */
     ++static int stack_uptodate(struct reftable_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 done;
     ++		}
     ++
     ++		if (strcmp(st->merged->stack[i]->name, names[i])) {
     ++			err = 1;
     ++			goto done;
     ++		}
     ++	}
     ++
     ++	if (names[st->merged->stack_len] != NULL) {
     ++		err = 1;
     ++		goto done;
     ++	}
     ++
     ++done:
     ++	free_names(names);
     ++	return err;
     ++}
     ++
     ++int reftable_stack_reload(struct reftable_stack *st)
     ++{
     ++	int err = stack_uptodate(st);
     ++	if (err > 0)
     ++		return reftable_stack_reload_maybe_reuse(st, true);
     ++	return err;
     ++}
     ++
     ++int reftable_stack_add(struct reftable_stack *st,
     ++		       int (*write)(struct reftable_writer *wr, void *arg),
     ++		       void *arg)
     ++{
     ++	int err = stack_try_add(st, write, arg);
     ++	if (err < 0) {
     ++		if (err == REFTABLE_LOCK_ERROR) {
     ++			/* Ignore error return, we want to propagate
     ++			   REFTABLE_LOCK_ERROR.
     ++			*/
     ++			reftable_stack_reload(st);
     ++		}
     ++		return err;
     ++	}
     ++
     ++	if (!st->disable_auto_compact)
     ++		return reftable_stack_auto_compact(st);
     ++
     ++	return 0;
     ++}
     ++
     ++static void format_name(struct slice *dest, uint64_t min, uint64_t max)
     ++{
     ++	char buf[100];
     ++	snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64, min, max);
     ++	slice_set_string(dest, buf);
     ++}
     ++
     ++struct reftable_addition {
     ++	int lock_file_fd;
     ++	struct slice lock_file_name;
     ++	struct reftable_stack *stack;
     ++	char **names;
     ++	char **new_tables;
     ++	int new_tables_len;
     ++	uint64_t next_update_index;
     ++};
     ++
     ++#define REFTABLE_ADDITION_INIT               \
     ++	{                                    \
     ++		.lock_file_name = SLICE_INIT \
     ++	}
     ++
     ++static int reftable_stack_init_addition(struct reftable_addition *add,
     ++					struct reftable_stack *st)
     ++{
     ++	int err = 0;
     ++	add->stack = st;
     ++
     ++	slice_set_string(&add->lock_file_name, st->list_file);
     ++	slice_addstr(&add->lock_file_name, ".lock");
     ++
     ++	add->lock_file_fd = open(slice_as_string(&add->lock_file_name),
     ++				 O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++	if (add->lock_file_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = REFTABLE_LOCK_ERROR;
     ++		} else {
     ++			err = REFTABLE_IO_ERROR;
     ++		}
     ++		goto done;
     ++	}
     ++	err = stack_uptodate(st);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	if (err > 1) {
     ++		err = REFTABLE_LOCK_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	add->next_update_index = reftable_stack_next_update_index(st);
     ++done:
     ++	if (err) {
     ++		reftable_addition_close(add);
     ++	}
     ++	return err;
     ++}
     ++
     ++void reftable_addition_close(struct reftable_addition *add)
     ++{
     ++	int i = 0;
     ++	struct slice nm = SLICE_INIT;
     ++	for (i = 0; i < add->new_tables_len; i++) {
     ++		slice_set_string(&nm, add->stack->list_file);
     ++		slice_addstr(&nm, "/");
     ++		slice_addstr(&nm, add->new_tables[i]);
     ++		unlink(slice_as_string(&nm));
     ++		reftable_free(add->new_tables[i]);
     ++		add->new_tables[i] = NULL;
     ++	}
     ++	reftable_free(add->new_tables);
     ++	add->new_tables = NULL;
     ++	add->new_tables_len = 0;
     ++
     ++	if (add->lock_file_fd > 0) {
     ++		close(add->lock_file_fd);
     ++		add->lock_file_fd = 0;
     ++	}
     ++	if (add->lock_file_name.len > 0) {
     ++		unlink(slice_as_string(&add->lock_file_name));
     ++		slice_release(&add->lock_file_name);
     ++	}
     ++
     ++	free_names(add->names);
     ++	add->names = NULL;
     ++	slice_release(&nm);
     ++}
     ++
     ++void reftable_addition_destroy(struct reftable_addition *add)
     ++{
     ++	if (add == NULL) {
     ++		return;
     ++	}
     ++	reftable_addition_close(add);
     ++	reftable_free(add);
     ++}
     ++
     ++int reftable_addition_commit(struct reftable_addition *add)
     ++{
     ++	struct slice table_list = SLICE_INIT;
     ++	int i = 0;
     ++	int err = 0;
     ++	if (add->new_tables_len == 0)
     ++		goto done;
     ++
     ++	for (i = 0; i < add->stack->merged->stack_len; i++) {
     ++		slice_addstr(&table_list, add->stack->merged->stack[i]->name);
     ++		slice_addstr(&table_list, "\n");
     ++	}
     ++	for (i = 0; i < add->new_tables_len; i++) {
     ++		slice_addstr(&table_list, add->new_tables[i]);
     ++		slice_addstr(&table_list, "\n");
     ++	}
     ++
     ++	err = write(add->lock_file_fd, table_list.buf, table_list.len);
     ++	slice_release(&table_list);
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	err = close(add->lock_file_fd);
     ++	add->lock_file_fd = 0;
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	err = rename(slice_as_string(&add->lock_file_name),
     ++		     add->stack->list_file);
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	err = reftable_stack_reload(add->stack);
     ++
     ++done:
     ++	reftable_addition_close(add);
     ++	return err;
     ++}
     ++
     ++int reftable_stack_new_addition(struct reftable_addition **dest,
     ++				struct reftable_stack *st)
     ++{
     ++	int err = 0;
     ++	struct reftable_addition empty = REFTABLE_ADDITION_INIT;
     ++	*dest = reftable_calloc(sizeof(**dest));
     ++	**dest = empty;
     ++	err = reftable_stack_init_addition(*dest, st);
     ++	if (err) {
     ++		reftable_free(*dest);
     ++		*dest = NULL;
     ++	}
     ++	return err;
     ++}
     ++
     ++int stack_try_add(struct reftable_stack *st,
     ++		  int (*write_table)(struct reftable_writer *wr, void *arg),
     ++		  void *arg)
     ++{
     ++	struct reftable_addition add = REFTABLE_ADDITION_INIT;
     ++	int err = reftable_stack_init_addition(&add, st);
     ++	if (err < 0)
     ++		goto done;
     ++	if (err > 0) {
     ++		err = REFTABLE_LOCK_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	err = reftable_addition_add(&add, write_table, arg);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	err = reftable_addition_commit(&add);
     ++done:
     ++	reftable_addition_close(&add);
     ++	return err;
     ++}
     ++
     ++int reftable_addition_add(struct reftable_addition *add,
     ++			  int (*write_table)(struct reftable_writer *wr,
     ++					     void *arg),
     ++			  void *arg)
     ++{
     ++	struct slice temp_tab_file_name = SLICE_INIT;
     ++	struct slice tab_file_name = SLICE_INIT;
     ++	struct slice next_name = SLICE_INIT;
     ++	struct reftable_writer *wr = NULL;
     ++	int err = 0;
     ++	int tab_fd = 0;
     ++
     ++	slice_resize(&next_name, 0);
     ++	format_name(&next_name, add->next_update_index, add->next_update_index);
     ++
     ++	slice_set_string(&temp_tab_file_name, add->stack->reftable_dir);
     ++	slice_addstr(&temp_tab_file_name, "/");
     ++	slice_addbuf(&temp_tab_file_name, &next_name);
     ++	slice_addstr(&temp_tab_file_name, ".temp.XXXXXX");
     ++
     ++	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_file_name));
     ++	if (tab_fd < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	wr = reftable_new_writer(reftable_fd_write, &tab_fd,
     ++				 &add->stack->config);
     ++	err = write_table(wr, arg);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	err = reftable_writer_close(wr);
     ++	if (err == REFTABLE_EMPTY_TABLE_ERROR) {
     ++		err = 0;
     ++		goto done;
     ++	}
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	err = close(tab_fd);
     ++	tab_fd = 0;
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	err = stack_check_addition(add->stack,
     ++				   slice_as_string(&temp_tab_file_name));
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	if (wr->min_update_index < add->next_update_index) {
     ++		err = REFTABLE_API_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	format_name(&next_name, wr->min_update_index, wr->max_update_index);
     ++	slice_addstr(&next_name, ".ref");
     ++
     ++	slice_set_string(&tab_file_name, add->stack->reftable_dir);
     ++	slice_addstr(&tab_file_name, "/");
     ++	slice_addbuf(&tab_file_name, &next_name);
     ++
     ++	/* TODO: should check destination out of paranoia */
     ++	err = rename(slice_as_string(&temp_tab_file_name),
     ++		     slice_as_string(&tab_file_name));
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		goto done;
     ++	}
     ++
     ++	add->new_tables = reftable_realloc(add->new_tables,
     ++					   sizeof(*add->new_tables) *
     ++						   (add->new_tables_len + 1));
     ++	add->new_tables[add->new_tables_len] = slice_to_string(&next_name);
     ++	add->new_tables_len++;
     ++done:
     ++	if (tab_fd > 0) {
     ++		close(tab_fd);
     ++		tab_fd = 0;
     ++	}
     ++	if (temp_tab_file_name.len > 0) {
     ++		unlink(slice_as_string(&temp_tab_file_name));
     ++	}
     ++
     ++	slice_release(&temp_tab_file_name);
     ++	slice_release(&tab_file_name);
     ++	slice_release(&next_name);
     ++	reftable_writer_free(wr);
     ++	return err;
     ++}
     ++
     ++uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
     ++{
     ++	int sz = st->merged->stack_len;
     ++	if (sz > 0)
     ++		return reftable_reader_max_update_index(
     ++			       st->merged->stack[sz - 1]) +
     ++		       1;
     ++	return 1;
     ++}
     ++
     ++static int stack_compact_locked(struct reftable_stack *st, int first, int last,
     ++				struct slice *temp_tab,
     ++				struct reftable_log_expiry_config *config)
     ++{
     ++	struct slice next_name = SLICE_INIT;
     ++	int tab_fd = -1;
     ++	struct reftable_writer *wr = NULL;
     ++	int err = 0;
     ++
     ++	format_name(&next_name,
     ++		    reftable_reader_min_update_index(st->merged->stack[first]),
     ++		    reftable_reader_max_update_index(st->merged->stack[first]));
     ++
     ++	slice_set_string(temp_tab, st->reftable_dir);
     ++	slice_addstr(temp_tab, "/");
     ++	slice_addbuf(temp_tab, &next_name);
     ++	slice_addstr(temp_tab, ".temp.XXXXXX");
     ++
     ++	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     ++	wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);
     ++
     ++	err = stack_write_compact(st, wr, first, last, config);
     ++	if (err < 0)
     ++		goto done;
     ++	err = reftable_writer_close(wr);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	err = close(tab_fd);
     ++	tab_fd = 0;
     ++
     ++done:
     ++	reftable_writer_free(wr);
     ++	if (tab_fd > 0) {
     ++		close(tab_fd);
     ++		tab_fd = 0;
     ++	}
     ++	if (err != 0 && temp_tab->len > 0) {
     ++		unlink(slice_as_string(temp_tab));
     ++		slice_release(temp_tab);
     ++	}
     ++	slice_release(&next_name);
     ++	return err;
     ++}
     ++
     ++int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr,
     ++			int first, int last,
     ++			struct reftable_log_expiry_config *config)
     ++{
     ++	int subtabs_len = last - first + 1;
     ++	struct reftable_reader **subtabs = reftable_calloc(
     ++		sizeof(struct reftable_reader *) * (last - first + 1));
     ++	struct reftable_merged_table *mt = NULL;
     ++	int err = 0;
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_log_record log = { 0 };
     ++
     ++	uint64_t entries = 0;
     ++
     ++	int i = 0, j = 0;
     ++	for (i = first, j = 0; i <= last; i++) {
     ++		struct reftable_reader *t = st->merged->stack[i];
     ++		subtabs[j++] = t;
     ++		st->stats.bytes += t->size;
     ++	}
     ++	reftable_writer_set_limits(wr,
     ++				   st->merged->stack[first]->min_update_index,
     ++				   st->merged->stack[last]->max_update_index);
     ++
     ++	err = reftable_new_merged_table(&mt, subtabs, subtabs_len,
     ++					st->config.hash_id);
     ++	if (err < 0) {
     ++		reftable_free(subtabs);
     ++		goto done;
     ++	}
     ++
     ++	err = reftable_merged_table_seek_ref(mt, &it, "");
     ++	if (err < 0)
     ++		goto done;
      +
     -+	deadline.tv_sec += 3;
      +	while (true) {
     -+		char **names = NULL;
     -+		char **names_after = NULL;
     -+		struct timeval now = { 0 };
     -+		int err = gettimeofday(&now, NULL);
     -+		int err2 = 0;
     ++		err = reftable_iterator_next_ref(&it, &ref);
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
      +		if (err < 0) {
     -+			return err;
     ++			break;
     ++		}
     ++		if (first == 0 && reftable_ref_record_is_deletion(&ref)) {
     ++			continue;
      +		}
      +
     -+		/* Only look at deadlines after the first few times. This
     -+		   simplifies debugging in GDB */
     -+		tries++;
     -+		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
     ++		err = reftable_writer_add_ref(wr, &ref);
     ++		if (err < 0) {
      +			break;
      +		}
     ++		entries++;
     ++	}
     ++	reftable_iterator_destroy(&it);
      +
     -+		err = read_lines(st->list_file, &names);
     -+		if (err < 0) {
     -+			free_names(names);
     -+			return err;
     ++	err = reftable_merged_table_seek_log(mt, &it, "");
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	while (true) {
     ++		err = reftable_iterator_next_log(&it, &log);
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
      +		}
     -+		err = reftable_stack_reload_once(st, names, reuse_open);
     -+		if (err == 0) {
     -+			free_names(names);
     ++		if (err < 0) {
      +			break;
      +		}
     -+		if (err != REFTABLE_NOT_EXIST_ERROR) {
     -+			free_names(names);
     -+			return err;
     ++		if (first == 0 && reftable_log_record_is_deletion(&log)) {
     ++			continue;
      +		}
      +
     -+		/* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
     -+		   writer. Check if there was one by checking if the name list
     -+		   changed.
     -+		*/
     -+		err2 = read_lines(st->list_file, &names_after);
     -+		if (err2 < 0) {
     -+			free_names(names);
     -+			return err2;
     ++		if (config != NULL && config->time > 0 &&
     ++		    log.time < config->time) {
     ++			continue;
      +		}
      +
     -+		if (names_equal(names_after, names)) {
     -+			free_names(names);
     -+			free_names(names_after);
     -+			return err;
     ++		if (config != NULL && config->min_update_index > 0 &&
     ++		    log.update_index < config->min_update_index) {
     ++			continue;
      +		}
     -+		free_names(names);
     -+		free_names(names_after);
      +
     -+		delay = delay + (delay * rand()) / RAND_MAX + 1;
     -+		sleep_millisec(delay);
     ++		err = reftable_writer_add_log(wr, &log);
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++		entries++;
     ++	}
     ++
     ++done:
     ++	reftable_iterator_destroy(&it);
     ++	if (mt != NULL) {
     ++		merged_table_clear(mt);
     ++		reftable_merged_table_free(mt);
     ++	}
     ++	reftable_ref_record_clear(&ref);
     ++	reftable_log_record_clear(&log);
     ++	st->stats.entries_written += entries;
     ++	return err;
     ++}
     ++
     ++/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
     ++static int stack_compact_range(struct reftable_stack *st, int first, int last,
     ++			       struct reftable_log_expiry_config *expiry)
     ++{
     ++	struct slice temp_tab_file_name = SLICE_INIT;
     ++	struct slice new_table_name = SLICE_INIT;
     ++	struct slice lock_file_name = SLICE_INIT;
     ++	struct slice ref_list_contents = SLICE_INIT;
     ++	struct slice new_table_path = SLICE_INIT;
     ++	int err = 0;
     ++	bool have_lock = false;
     ++	int lock_file_fd = 0;
     ++	int compact_count = last - first + 1;
     ++	char **listp = NULL;
     ++	char **delete_on_success =
     ++		reftable_calloc(sizeof(char *) * (compact_count + 1));
     ++	char **subtable_locks =
     ++		reftable_calloc(sizeof(char *) * (compact_count + 1));
     ++	int i = 0;
     ++	int j = 0;
     ++	bool is_empty_table = false;
     ++
     ++	if (first > last || (expiry == NULL && first == last)) {
     ++		err = 0;
     ++		goto done;
     ++	}
     ++
     ++	st->stats.attempts++;
     ++
     ++	slice_set_string(&lock_file_name, st->list_file);
     ++	slice_addstr(&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 = REFTABLE_IO_ERROR;
     ++		}
     ++		goto done;
     ++	}
     ++	/* Don't want to write to the lock for now.  */
     ++	close(lock_file_fd);
     ++	lock_file_fd = 0;
     ++
     ++	have_lock = true;
     ++	err = stack_uptodate(st);
     ++	if (err != 0)
     ++		goto done;
     ++
     ++	for (i = first, j = 0; i <= last; i++) {
     ++		struct slice subtab_file_name = SLICE_INIT;
     ++		struct slice subtab_lock = SLICE_INIT;
     ++		int sublock_file_fd = -1;
     ++
     ++		slice_set_string(&subtab_file_name, st->reftable_dir);
     ++		slice_addstr(&subtab_file_name, "/");
     ++		slice_addstr(&subtab_file_name,
     ++			     reader_name(st->merged->stack[i]));
     ++
     ++		slice_copy(&subtab_lock, &subtab_file_name);
     ++		slice_addstr(&subtab_lock, ".lock");
     ++
     ++		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;
     ++			} else {
     ++				err = REFTABLE_IO_ERROR;
     ++			}
     ++		}
     ++
     ++		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
     ++		delete_on_success[j] =
     ++			(char *)slice_as_string(&subtab_file_name);
     ++		j++;
     ++
     ++		if (err != 0)
     ++			goto done;
      +	}
      +
     -+	return 0;
     -+}
     ++	err = unlink(slice_as_string(&lock_file_name));
     ++	if (err < 0)
     ++		goto done;
     ++	have_lock = false;
      +
     -+/* -1 = error
     -+ 0 = up to date
     -+ 1 = changed. */
     -+static int stack_uptodate(struct reftable_stack *st)
     -+{
     -+	char **names = NULL;
     -+	int err = read_lines(st->list_file, &names);
     -+	int i = 0;
     ++	err = stack_compact_locked(st, first, last, &temp_tab_file_name,
     ++				   expiry);
     ++	/* Compaction + tombstones can create an empty table out of non-empty
     ++	 * tables. */
     ++	is_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);
     ++	if (is_empty_table) {
     ++		err = 0;
     ++	}
      +	if (err < 0)
     -+		return err;
     ++		goto done;
      +
     -+	for (i = 0; i < st->merged->stack_len; i++) {
     -+		if (names[i] == NULL) {
     ++	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;
     -+			goto done;
     ++		} else {
     ++			err = REFTABLE_IO_ERROR;
      +		}
     ++		goto done;
     ++	}
     ++	have_lock = true;
      +
     -+		if (strcmp(st->merged->stack[i]->name, names[i])) {
     -+			err = 1;
     ++	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
     ++		    st->merged->stack[last]->max_update_index);
     ++	slice_addstr(&new_table_name, ".ref");
     ++
     ++	slice_set_string(&new_table_path, st->reftable_dir);
     ++	slice_addstr(&new_table_path, "/");
     ++
     ++	slice_addbuf(&new_table_path, &new_table_name);
     ++
     ++	if (!is_empty_table) {
     ++		err = rename(slice_as_string(&temp_tab_file_name),
     ++			     slice_as_string(&new_table_path));
     ++		if (err < 0) {
     ++			err = REFTABLE_IO_ERROR;
      +			goto done;
      +		}
      +	}
      +
     -+	if (names[st->merged->stack_len] != NULL) {
     -+		err = 1;
     ++	for (i = 0; i < first; i++) {
     ++		slice_addstr(&ref_list_contents, st->merged->stack[i]->name);
     ++		slice_addstr(&ref_list_contents, "\n");
     ++	}
     ++	if (!is_empty_table) {
     ++		slice_addbuf(&ref_list_contents, &new_table_name);
     ++		slice_addstr(&ref_list_contents, "\n");
     ++	}
     ++	for (i = last + 1; i < st->merged->stack_len; i++) {
     ++		slice_addstr(&ref_list_contents, st->merged->stack[i]->name);
     ++		slice_addstr(&ref_list_contents, "\n");
     ++	}
     ++
     ++	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto done;
     ++	}
     ++	err = close(lock_file_fd);
     ++	lock_file_fd = 0;
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto done;
     ++	}
     ++
     ++	err = rename(slice_as_string(&lock_file_name), st->list_file);
     ++	if (err < 0) {
     ++		err = REFTABLE_IO_ERROR;
     ++		unlink(slice_as_string(&new_table_path));
      +		goto done;
      +	}
     ++	have_lock = false;
     ++
     ++	/* Reload the stack before deleting. On windows, we can only delete the
     ++	   files after we closed them.
     ++	*/
     ++	err = reftable_stack_reload_maybe_reuse(st, first < last);
     ++
     ++	listp = delete_on_success;
     ++	while (*listp) {
     ++		if (strcmp(*listp, slice_as_string(&new_table_path))) {
     ++			unlink(*listp);
     ++		}
     ++		listp++;
     ++	}
      +
      +done:
     -+	free_names(names);
     ++	free_names(delete_on_success);
     ++
     ++	listp = subtable_locks;
     ++	while (*listp) {
     ++		unlink(*listp);
     ++		listp++;
     ++	}
     ++	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));
     ++	}
     ++	slice_release(&new_table_name);
     ++	slice_release(&new_table_path);
     ++	slice_release(&ref_list_contents);
     ++	slice_release(&temp_tab_file_name);
     ++	slice_release(&lock_file_name);
      +	return err;
      +}
      +
     -+int reftable_stack_reload(struct reftable_stack *st)
     ++int reftable_stack_compact_all(struct reftable_stack *st,
     ++			       struct reftable_log_expiry_config *config)
      +{
     -+	int err = stack_uptodate(st);
     -+	if (err > 0)
     -+		return reftable_stack_reload_maybe_reuse(st, true);
     -+	return err;
     ++	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
      +}
      +
     -+int reftable_stack_add(struct reftable_stack *st,
     -+		       int (*write)(struct reftable_writer *wr, void *arg),
     -+		       void *arg)
     ++static int stack_compact_range_stats(struct reftable_stack *st, int first,
     ++				     int last,
     ++				     struct reftable_log_expiry_config *config)
      +{
     -+	int err = stack_try_add(st, write, arg);
     -+	if (err < 0) {
     -+		if (err == REFTABLE_LOCK_ERROR) {
     -+			/* Ignore error return, we want to propagate
     -+			   REFTABLE_LOCK_ERROR.
     -+			*/
     -+			reftable_stack_reload(st);
     -+		}
     -+		return err;
     ++	int err = stack_compact_range(st, first, last, config);
     ++	if (err > 0) {
     ++		st->stats.failures++;
      +	}
     -+
     -+	if (!st->disable_auto_compact)
     -+		return reftable_stack_auto_compact(st);
     -+
     -+	return 0;
     ++	return err;
      +}
      +
     -+static void format_name(struct slice *dest, uint64_t min, uint64_t max)
     ++static int segment_size(struct segment *s)
      +{
     -+	char buf[100];
     -+	snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64, min, max);
     -+	slice_set_string(dest, buf);
     ++	return s->end - s->start;
      +}
      +
     -+struct reftable_addition {
     -+	int lock_file_fd;
     -+	struct slice lock_file_name;
     -+	struct reftable_stack *stack;
     -+	char **names;
     -+	char **new_tables;
     -+	int new_tables_len;
     -+	uint64_t next_update_index;
     -+};
     -+
     -+#define REFTABLE_ADDITION_INIT               \
     -+	{                                    \
     -+		.lock_file_name = SLICE_INIT \
     ++int fastlog2(uint64_t sz)
     ++{
     ++	int l = 0;
     ++	if (sz == 0)
     ++		return 0;
     ++	for (; sz; sz /= 2) {
     ++		l++;
      +	}
     ++	return l - 1;
     ++}
      +
     -+static int reftable_stack_init_addition(struct reftable_addition *add,
     -+					struct reftable_stack *st)
     ++struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
      +{
     -+	int err = 0;
     -+	add->stack = st;
     ++	struct segment *segs = reftable_calloc(sizeof(struct segment) * n);
     ++	int next = 0;
     ++	struct segment cur = { 0 };
     ++	int i = 0;
      +
     -+	slice_set_string(&add->lock_file_name, st->list_file);
     -+	slice_addstr(&add->lock_file_name, ".lock");
     ++	if (n == 0) {
     ++		*seglen = 0;
     ++		return segs;
     ++	}
     ++	for (i = 0; i < n; i++) {
     ++		int log = fastlog2(sizes[i]);
     ++		if (cur.log != log && cur.bytes > 0) {
     ++			struct segment fresh = {
     ++				.start = i,
     ++			};
      +
     -+	add->lock_file_fd = open(slice_as_string(&add->lock_file_name),
     -+				 O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+	if (add->lock_file_fd < 0) {
     -+		if (errno == EEXIST) {
     -+			err = REFTABLE_LOCK_ERROR;
     -+		} else {
     -+			err = REFTABLE_IO_ERROR;
     ++			segs[next++] = cur;
     ++			cur = fresh;
      +		}
     -+		goto done;
     ++
     ++		cur.log = log;
     ++		cur.end = i + 1;
     ++		cur.bytes += sizes[i];
      +	}
     -+	err = stack_uptodate(st);
     -+	if (err < 0)
     -+		goto done;
     ++	segs[next++] = cur;
     ++	*seglen = next;
     ++	return segs;
     ++}
      +
     -+	if (err > 1) {
     -+		err = REFTABLE_LOCK_ERROR;
     -+		goto done;
     ++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];
     ++		}
      +	}
      +
     -+	add->next_update_index = reftable_stack_next_update_index(st);
     -+done:
     -+	if (err) {
     -+		reftable_addition_close(add);
     ++	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];
      +	}
     -+	return err;
     ++
     ++	reftable_free(segs);
     ++	return min_seg;
      +}
      +
     -+void reftable_addition_close(struct reftable_addition *add)
     ++static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)
      +{
     ++	uint64_t *sizes =
     ++		reftable_calloc(sizeof(uint64_t) * st->merged->stack_len);
     ++	int version = (st->config.hash_id == SHA1_ID) ? 1 : 2;
     ++	int overhead = header_size(version) - 1;
      +	int i = 0;
     -+	struct slice nm = SLICE_INIT;
     -+	for (i = 0; i < add->new_tables_len; i++) {
     -+		slice_set_string(&nm, add->stack->list_file);
     -+		slice_addstr(&nm, "/");
     -+		slice_addstr(&nm, add->new_tables[i]);
     -+		unlink(slice_as_string(&nm));
     -+		reftable_free(add->new_tables[i]);
     -+		add->new_tables[i] = NULL;
     ++	for (i = 0; i < st->merged->stack_len; i++) {
     ++		sizes[i] = st->merged->stack[i]->size - overhead;
      +	}
     -+	reftable_free(add->new_tables);
     -+	add->new_tables = NULL;
     -+	add->new_tables_len = 0;
     ++	return sizes;
     ++}
      +
     -+	if (add->lock_file_fd > 0) {
     -+		close(add->lock_file_fd);
     -+		add->lock_file_fd = 0;
     -+	}
     -+	if (add->lock_file_name.len > 0) {
     -+		unlink(slice_as_string(&add->lock_file_name));
     -+		slice_release(&add->lock_file_name);
     -+	}
     ++int reftable_stack_auto_compact(struct reftable_stack *st)
     ++{
     ++	uint64_t *sizes = stack_table_sizes_for_compaction(st);
     ++	struct segment seg =
     ++		suggest_compaction_segment(sizes, st->merged->stack_len);
     ++	reftable_free(sizes);
     ++	if (segment_size(&seg) > 0)
     ++		return stack_compact_range_stats(st, seg.start, seg.end - 1,
     ++						 NULL);
      +
     -+	free_names(add->names);
     -+	add->names = NULL;
     -+	slice_release(&nm);
     ++	return 0;
      +}
      +
     -+void reftable_addition_destroy(struct reftable_addition *add)
     ++struct reftable_compaction_stats *
     ++reftable_stack_compaction_stats(struct reftable_stack *st)
      +{
     -+	if (add == NULL) {
     -+		return;
     -+	}
     -+	reftable_addition_close(add);
     -+	reftable_free(add);
     ++	return &st->stats;
      +}
      +
     -+int reftable_addition_commit(struct reftable_addition *add)
     ++int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
     ++			    struct reftable_ref_record *ref)
      +{
     -+	struct slice table_list = SLICE_INIT;
     -+	int i = 0;
     -+	int err = 0;
     -+	if (add->new_tables_len == 0)
     -+		goto done;
     -+
     -+	for (i = 0; i < add->stack->merged->stack_len; i++) {
     -+		slice_addstr(&table_list, add->stack->merged->stack[i]->name);
     -+		slice_addstr(&table_list, "\n");
     -+	}
     -+	for (i = 0; i < add->new_tables_len; i++) {
     -+		slice_addstr(&table_list, add->new_tables[i]);
     -+		slice_addstr(&table_list, "\n");
     -+	}
     ++	struct reftable_table tab = { NULL };
     ++	reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));
     ++	return reftable_table_read_ref(&tab, refname, ref);
     ++}
      +
     -+	err = write(add->lock_file_fd, table_list.buf, table_list.len);
     -+	slice_release(&table_list);
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     ++int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
     ++			    struct reftable_log_record *log)
     ++{
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_merged_table *mt = reftable_stack_merged_table(st);
     ++	int err = reftable_merged_table_seek_log(mt, &it, refname);
     ++	if (err)
      +		goto done;
     -+	}
      +
     -+	err = close(add->lock_file_fd);
     -+	add->lock_file_fd = 0;
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     ++	err = reftable_iterator_next_log(&it, log);
     ++	if (err)
      +		goto done;
     -+	}
      +
     -+	err = rename(slice_as_string(&add->lock_file_name),
     -+		     add->stack->list_file);
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     ++	if (strcmp(log->ref_name, refname) ||
     ++	    reftable_log_record_is_deletion(log)) {
     ++		err = 1;
      +		goto done;
      +	}
      +
     -+	err = reftable_stack_reload(add->stack);
     -+
      +done:
     -+	reftable_addition_close(add);
     -+	return err;
     -+}
     -+
     -+int reftable_stack_new_addition(struct reftable_addition **dest,
     -+				struct reftable_stack *st)
     -+{
     -+	int err = 0;
     -+	struct reftable_addition empty = REFTABLE_ADDITION_INIT;
     -+	*dest = reftable_calloc(sizeof(**dest));
     -+	**dest = empty;
     -+	err = reftable_stack_init_addition(*dest, st);
      +	if (err) {
     -+		reftable_free(*dest);
     -+		*dest = NULL;
     ++		reftable_log_record_clear(log);
      +	}
     ++	reftable_iterator_destroy(&it);
      +	return err;
      +}
      +
     -+int stack_try_add(struct reftable_stack *st,
     -+		  int (*write_table)(struct reftable_writer *wr, void *arg),
     -+		  void *arg)
     ++int stack_check_addition(struct reftable_stack *st, const char *new_tab_name)
      +{
     -+	struct reftable_addition add = REFTABLE_ADDITION_INIT;
     -+	int err = reftable_stack_init_addition(&add, st);
     ++	int err = 0;
     ++	struct reftable_block_source src = { 0 };
     ++	struct reftable_reader *rd = NULL;
     ++	struct reftable_table tab = { NULL };
     ++	struct reftable_ref_record *refs = NULL;
     ++	struct reftable_iterator it = { NULL };
     ++	int cap = 0;
     ++	int len = 0;
     ++	int i = 0;
     ++
     ++	if (st->config.skip_name_check)
     ++		return 0;
     ++
     ++	err = reftable_block_source_from_file(&src, new_tab_name);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	err = reftable_new_reader(&rd, &src, new_tab_name);
      +	if (err < 0)
      +		goto done;
     ++
     ++	err = reftable_reader_seek_ref(rd, &it, "");
      +	if (err > 0) {
     -+		err = REFTABLE_LOCK_ERROR;
     ++		err = 0;
      +		goto done;
      +	}
     -+
     -+	err = reftable_addition_add(&add, write_table, arg);
      +	if (err < 0)
      +		goto done;
      +
     -+	err = reftable_addition_commit(&add);
     ++	while (true) {
     ++		struct reftable_ref_record ref = { 0 };
     ++		err = reftable_iterator_next_ref(&it, &ref);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (err < 0)
     ++			goto done;
     ++
     ++		if (len >= cap) {
     ++			cap = 2 * cap + 1;
     ++			refs = reftable_realloc(refs, cap * sizeof(refs[0]));
     ++		}
     ++
     ++		refs[len++] = ref;
     ++	}
     ++
     ++	reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));
     ++
     ++	err = validate_ref_record_addition(tab, refs, len);
     ++
      +done:
     -+	reftable_addition_close(&add);
     ++	for (i = 0; i < len; i++) {
     ++		reftable_ref_record_clear(&refs[i]);
     ++	}
     ++
     ++	free(refs);
     ++	reftable_iterator_destroy(&it);
     ++	reftable_reader_free(rd);
      +	return err;
      +}
     +
     + ## reftable/stack.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+int reftable_addition_add(struct reftable_addition *add,
     -+			  int (*write_table)(struct reftable_writer *wr,
     -+					     void *arg),
     -+			  void *arg)
     -+{
     -+	struct slice temp_tab_file_name = SLICE_INIT;
     -+	struct slice tab_file_name = SLICE_INIT;
     -+	struct slice next_name = SLICE_INIT;
     -+	struct reftable_writer *wr = NULL;
     -+	int err = 0;
     -+	int tab_fd = 0;
     ++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
     ++*/
      +
     -+	slice_resize(&next_name, 0);
     -+	format_name(&next_name, add->next_update_index, add->next_update_index);
     ++#ifndef STACK_H
     ++#define STACK_H
      +
     -+	slice_set_string(&temp_tab_file_name, add->stack->reftable_dir);
     -+	slice_addstr(&temp_tab_file_name, "/");
     -+	slice_addbuf(&temp_tab_file_name, next_name);
     -+	slice_addstr(&temp_tab_file_name, ".temp.XXXXXX");
     ++#include "reftable.h"
     ++#include "system.h"
      +
     -+	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_file_name));
     -+	if (tab_fd < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		goto done;
     -+	}
     ++struct reftable_stack {
     ++	char *list_file;
     ++	char *reftable_dir;
     ++	bool disable_auto_compact;
      +
     -+	wr = reftable_new_writer(reftable_fd_write, &tab_fd,
     -+				 &add->stack->config);
     -+	err = write_table(wr, arg);
     -+	if (err < 0)
     -+		goto done;
     ++	struct reftable_write_options config;
      +
     -+	err = reftable_writer_close(wr);
     -+	if (err == REFTABLE_EMPTY_TABLE_ERROR) {
     -+		err = 0;
     -+		goto done;
     -+	}
     -+	if (err < 0)
     -+		goto done;
     ++	struct reftable_merged_table *merged;
     ++	struct reftable_compaction_stats stats;
     ++};
      +
     -+	err = close(tab_fd);
     -+	tab_fd = 0;
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		goto done;
     -+	}
     ++int read_lines(const char *filename, char ***lines);
     ++int stack_try_add(struct reftable_stack *st,
     ++		  int (*write_table)(struct reftable_writer *wr, void *arg),
     ++		  void *arg);
     ++int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr,
     ++			int first, int last,
     ++			struct reftable_log_expiry_config *config);
     ++int fastlog2(uint64_t sz);
     ++int stack_check_addition(struct reftable_stack *st, const char *new_tab_name);
     ++void reftable_addition_close(struct reftable_addition *add);
     ++int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     ++				      bool reuse_open);
      +
     -+	err = stack_check_addition(add->stack,
     -+				   slice_as_string(&temp_tab_file_name));
     -+	if (err < 0)
     -+		goto done;
     ++struct segment {
     ++	int start, end;
     ++	int log;
     ++	uint64_t bytes;
     ++};
      +
     -+	if (wr->min_update_index < add->next_update_index) {
     -+		err = REFTABLE_API_ERROR;
     -+		goto done;
     -+	}
     ++struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
     ++struct segment suggest_compaction_segment(uint64_t *sizes, int n);
      +
     -+	format_name(&next_name, wr->min_update_index, wr->max_update_index);
     -+	slice_addstr(&next_name, ".ref");
     ++#endif
     +
     + ## reftable/stack_test.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+	slice_set_string(&tab_file_name, add->stack->reftable_dir);
     -+	slice_addstr(&tab_file_name, "/");
     -+	slice_addbuf(&tab_file_name, next_name);
     ++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
     ++*/
      +
     -+	/* TODO: should check destination out of paranoia */
     -+	err = rename(slice_as_string(&temp_tab_file_name),
     -+		     slice_as_string(&tab_file_name));
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		goto done;
     -+	}
     ++#include "stack.h"
      +
     -+	add->new_tables = reftable_realloc(add->new_tables,
     -+					   sizeof(*add->new_tables) *
     -+						   (add->new_tables_len + 1));
     -+	add->new_tables[add->new_tables_len] = slice_to_string(next_name);
     -+	add->new_tables_len++;
     -+done:
     -+	if (tab_fd > 0) {
     -+		close(tab_fd);
     -+		tab_fd = 0;
     -+	}
     -+	if (temp_tab_file_name.len > 0) {
     -+		unlink(slice_as_string(&temp_tab_file_name));
     -+	}
     ++#include "system.h"
      +
     -+	slice_release(&temp_tab_file_name);
     -+	slice_release(&tab_file_name);
     -+	slice_release(&next_name);
     -+	reftable_writer_free(wr);
     -+	return err;
     -+}
     ++#include "merged.h"
     ++#include "basics.h"
     ++#include "constants.h"
     ++#include "record.h"
     ++#include "reftable.h"
     ++#include "test_framework.h"
     ++#include "reftable-tests.h"
      +
     -+uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
     -+{
     -+	int sz = st->merged->stack_len;
     -+	if (sz > 0)
     -+		return reftable_reader_max_update_index(
     -+			       st->merged->stack[sz - 1]) +
     -+		       1;
     -+	return 1;
     -+}
     ++#include <sys/types.h>
     ++#include <dirent.h>
      +
     -+static int stack_compact_locked(struct reftable_stack *st, int first, int last,
     -+				struct slice *temp_tab,
     -+				struct reftable_log_expiry_config *config)
     ++static void clear_dir(const char *dirname)
      +{
     -+	struct slice next_name = SLICE_INIT;
     -+	int tab_fd = -1;
     -+	struct reftable_writer *wr = NULL;
     -+	int err = 0;
     ++	int fd = open(dirname, O_DIRECTORY, 0);
     ++	DIR *dir = fdopendir(fd);
     ++	struct dirent *ent = NULL;
      +
     -+	format_name(&next_name,
     -+		    reftable_reader_min_update_index(st->merged->stack[first]),
     -+		    reftable_reader_max_update_index(st->merged->stack[first]));
     ++	assert(fd >= 0);
      +
     -+	slice_set_string(temp_tab, st->reftable_dir);
     -+	slice_addstr(temp_tab, "/");
     -+	slice_addbuf(temp_tab, next_name);
     -+	slice_addstr(temp_tab, ".temp.XXXXXX");
     ++	while ((ent = readdir(dir)) != NULL) {
     ++		unlinkat(fd, ent->d_name, 0);
     ++	}
     ++	closedir(dir);
     ++	rmdir(dirname);
     ++}
      +
     -+	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     -+	wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);
     ++static void test_read_file(void)
     ++{
     ++	char fn[256] = "/tmp/stack.test_read_file.XXXXXX";
     ++	int fd = mkstemp(fn);
     ++	char out[1024] = "line1\n\nline2\nline3";
     ++	int n, err;
     ++	char **names = NULL;
     ++	char *want[] = { "line1", "line2", "line3" };
     ++	int i = 0;
      +
     -+	err = stack_write_compact(st, wr, first, last, config);
     -+	if (err < 0)
     -+		goto done;
     -+	err = reftable_writer_close(wr);
     -+	if (err < 0)
     -+		goto done;
     ++	assert(fd > 0);
     ++	n = write(fd, out, strlen(out));
     ++	assert(n == strlen(out));
     ++	err = close(fd);
     ++	assert(err >= 0);
      +
     -+	err = close(tab_fd);
     -+	tab_fd = 0;
     ++	err = read_lines(fn, &names);
     ++	assert_err(err);
      +
     -+done:
     -+	reftable_writer_free(wr);
     -+	if (tab_fd > 0) {
     -+		close(tab_fd);
     -+		tab_fd = 0;
     ++	for (i = 0; names[i] != NULL; i++) {
     ++		assert(0 == strcmp(want[i], names[i]));
      +	}
     -+	if (err != 0 && temp_tab->len > 0) {
     -+		unlink(slice_as_string(temp_tab));
     -+		slice_release(temp_tab);
     -+	}
     -+	slice_release(&next_name);
     -+	return err;
     ++	free_names(names);
     ++	remove(fn);
      +}
      +
     -+int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr,
     -+			int first, int last,
     -+			struct reftable_log_expiry_config *config)
     ++static void test_parse_names(void)
      +{
     -+	int subtabs_len = last - first + 1;
     -+	struct reftable_reader **subtabs = reftable_calloc(
     -+		sizeof(struct reftable_reader *) * (last - first + 1));
     -+	struct reftable_merged_table *mt = NULL;
     -+	int err = 0;
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_ref_record ref = { 0 };
     -+	struct reftable_log_record log = { 0 };
     ++	char buf[] = "line\n";
     ++	char **names = NULL;
     ++	parse_names(buf, strlen(buf), &names);
      +
     -+	uint64_t entries = 0;
     ++	assert(NULL != names[0]);
     ++	assert(0 == strcmp(names[0], "line"));
     ++	assert(NULL == names[1]);
     ++	free_names(names);
     ++}
      +
     -+	int i = 0, j = 0;
     -+	for (i = first, j = 0; i <= last; i++) {
     -+		struct reftable_reader *t = st->merged->stack[i];
     -+		subtabs[j++] = t;
     -+		st->stats.bytes += t->size;
     -+	}
     -+	reftable_writer_set_limits(wr,
     -+				   st->merged->stack[first]->min_update_index,
     -+				   st->merged->stack[last]->max_update_index);
     ++static void test_names_equal(void)
     ++{
     ++	char *a[] = { "a", "b", "c", NULL };
     ++	char *b[] = { "a", "b", "d", NULL };
     ++	char *c[] = { "a", "b", NULL };
      +
     -+	err = reftable_new_merged_table(&mt, subtabs, subtabs_len,
     -+					st->config.hash_id);
     -+	if (err < 0) {
     -+		reftable_free(subtabs);
     -+		goto done;
     -+	}
     ++	assert(names_equal(a, a));
     ++	assert(!names_equal(a, b));
     ++	assert(!names_equal(a, c));
     ++}
      +
     -+	err = reftable_merged_table_seek_ref(mt, &it, "");
     -+	if (err < 0)
     -+		goto done;
     ++static int write_test_ref(struct reftable_writer *wr, void *arg)
     ++{
     ++	struct reftable_ref_record *ref = arg;
     ++	reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
     ++	return reftable_writer_add_ref(wr, ref);
     ++}
      +
     -+	while (true) {
     -+		err = reftable_iterator_next_ref(&it, &ref);
     -+		if (err > 0) {
     -+			err = 0;
     -+			break;
     -+		}
     -+		if (err < 0) {
     -+			break;
     -+		}
     -+		if (first == 0 && reftable_ref_record_is_deletion(&ref)) {
     -+			continue;
     -+		}
     ++struct write_log_arg {
     ++	struct reftable_log_record *log;
     ++	uint64_t update_index;
     ++};
      +
     -+		err = reftable_writer_add_ref(wr, &ref);
     -+		if (err < 0) {
     -+			break;
     -+		}
     -+		entries++;
     -+	}
     -+	reftable_iterator_destroy(&it);
     ++static int write_test_log(struct reftable_writer *wr, void *arg)
     ++{
     ++	struct write_log_arg *wla = arg;
      +
     -+	err = reftable_merged_table_seek_log(mt, &it, "");
     -+	if (err < 0)
     -+		goto done;
     ++	reftable_writer_set_limits(wr, wla->update_index, wla->update_index);
     ++	return reftable_writer_add_log(wr, wla->log);
     ++}
      +
     -+	while (true) {
     -+		err = reftable_iterator_next_log(&it, &log);
     -+		if (err > 0) {
     -+			err = 0;
     -+			break;
     -+		}
     -+		if (err < 0) {
     -+			break;
     -+		}
     -+		if (first == 0 && reftable_log_record_is_deletion(&log)) {
     -+			continue;
     -+		}
     ++static void test_reftable_stack_add_one(void)
     ++{
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++	struct reftable_ref_record ref = {
     ++		.ref_name = "HEAD",
     ++		.update_index = 1,
     ++		.target = "master",
     ++	};
     ++	struct reftable_ref_record dest = { 0 };
      +
     -+		if (config != NULL && config->time > 0 &&
     -+		    log.time < config->time) {
     -+			continue;
     -+		}
     ++	assert(mkdtemp(dir));
      +
     -+		if (config != NULL && config->min_update_index > 0 &&
     -+		    log.update_index < config->min_update_index) {
     -+			continue;
     -+		}
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
      +
     -+		err = reftable_writer_add_log(wr, &log);
     -+		if (err < 0) {
     -+			break;
     -+		}
     -+		entries++;
     -+	}
     ++	err = reftable_stack_add(st, &write_test_ref, &ref);
     ++	assert_err(err);
      +
     -+done:
     -+	reftable_iterator_destroy(&it);
     -+	if (mt != NULL) {
     -+		merged_table_clear(mt);
     -+		reftable_merged_table_free(mt);
     -+	}
     -+	reftable_ref_record_clear(&ref);
     -+	reftable_log_record_clear(&log);
     -+	st->stats.entries_written += entries;
     -+	return err;
     ++	err = reftable_stack_read_ref(st, ref.ref_name, &dest);
     ++	assert_err(err);
     ++	assert(0 == strcmp("master", dest.target));
     ++
     ++	reftable_ref_record_clear(&dest);
     ++	reftable_stack_destroy(st);
     ++	clear_dir(dir);
      +}
      +
     -+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
     -+static int stack_compact_range(struct reftable_stack *st, int first, int last,
     -+			       struct reftable_log_expiry_config *expiry)
     ++static void test_reftable_stack_uptodate(void)
      +{
     -+	struct slice temp_tab_file_name = SLICE_INIT;
     -+	struct slice new_table_name = SLICE_INIT;
     -+	struct slice lock_file_name = SLICE_INIT;
     -+	struct slice ref_list_contents = SLICE_INIT;
     -+	struct slice new_table_path = SLICE_INIT;
     -+	int err = 0;
     -+	bool have_lock = false;
     -+	int lock_file_fd = 0;
     -+	int compact_count = last - first + 1;
     -+	char **listp = NULL;
     -+	char **delete_on_success =
     -+		reftable_calloc(sizeof(char *) * (compact_count + 1));
     -+	char **subtable_locks =
     -+		reftable_calloc(sizeof(char *) * (compact_count + 1));
     -+	int i = 0;
     -+	int j = 0;
     -+	bool is_empty_table = false;
     -+
     -+	if (first > last || (expiry == NULL && first == last)) {
     -+		err = 0;
     -+		goto done;
     -+	}
     -+
     -+	st->stats.attempts++;
     -+
     -+	slice_set_string(&lock_file_name, st->list_file);
     -+	slice_addstr(&lock_file_name, ".lock");
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st1 = NULL;
     ++	struct reftable_stack *st2 = NULL;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	int err;
     ++	struct reftable_ref_record ref1 = {
     ++		.ref_name = "HEAD",
     ++		.update_index = 1,
     ++		.target = "master",
     ++	};
     ++	struct reftable_ref_record ref2 = {
     ++		.ref_name = "branch2",
     ++		.update_index = 2,
     ++		.target = "master",
     ++	};
      +
     -+	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 = REFTABLE_IO_ERROR;
     -+		}
     -+		goto done;
     -+	}
     -+	/* Don't want to write to the lock for now.  */
     -+	close(lock_file_fd);
     -+	lock_file_fd = 0;
     ++	assert(mkdtemp(dir));
      +
     -+	have_lock = true;
     -+	err = stack_uptodate(st);
     -+	if (err != 0)
     -+		goto done;
     ++	err = reftable_new_stack(&st1, dir, cfg);
     ++	assert_err(err);
      +
     -+	for (i = first, j = 0; i <= last; i++) {
     -+		struct slice subtab_file_name = SLICE_INIT;
     -+		struct slice subtab_lock = SLICE_INIT;
     -+		int sublock_file_fd = -1;
     ++	err = reftable_new_stack(&st2, dir, cfg);
     ++	assert_err(err);
      +
     -+		slice_set_string(&subtab_file_name, st->reftable_dir);
     -+		slice_addstr(&subtab_file_name, "/");
     -+		slice_addstr(&subtab_file_name,
     -+			     reader_name(st->merged->stack[i]));
     ++	err = reftable_stack_add(st1, &write_test_ref, &ref1);
     ++	assert_err(err);
      +
     -+		slice_copy(&subtab_lock, subtab_file_name);
     -+		slice_addstr(&subtab_lock, ".lock");
     ++	err = reftable_stack_add(st2, &write_test_ref, &ref2);
     ++	assert(err == REFTABLE_LOCK_ERROR);
      +
     -+		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;
     -+			} else {
     -+				err = REFTABLE_IO_ERROR;
     -+			}
     -+		}
     ++	err = reftable_stack_reload(st2);
     ++	assert_err(err);
      +
     -+		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
     -+		delete_on_success[j] =
     -+			(char *)slice_as_string(&subtab_file_name);
     -+		j++;
     ++	err = reftable_stack_add(st2, &write_test_ref, &ref2);
     ++	assert_err(err);
     ++	reftable_stack_destroy(st1);
     ++	reftable_stack_destroy(st2);
     ++	clear_dir(dir);
     ++}
      +
     -+		if (err != 0)
     -+			goto done;
     -+	}
     ++static void test_reftable_stack_transaction_api(void)
     ++{
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++	struct reftable_addition *add = NULL;
      +
     -+	err = unlink(slice_as_string(&lock_file_name));
     -+	if (err < 0)
     -+		goto done;
     -+	have_lock = false;
     ++	struct reftable_ref_record ref = {
     ++		.ref_name = "HEAD",
     ++		.update_index = 1,
     ++		.target = "master",
     ++	};
     ++	struct reftable_ref_record dest = { 0 };
      +
     -+	err = stack_compact_locked(st, first, last, &temp_tab_file_name,
     -+				   expiry);
     -+	/* Compaction + tombstones can create an empty table out of non-empty
     -+	 * tables. */
     -+	is_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);
     -+	if (is_empty_table) {
     -+		err = 0;
     -+	}
     -+	if (err < 0)
     -+		goto done;
     ++	assert(mkdtemp(dir));
      +
     -+	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 = REFTABLE_IO_ERROR;
     -+		}
     -+		goto done;
     -+	}
     -+	have_lock = true;
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
      +
     -+	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
     -+		    st->merged->stack[last]->max_update_index);
     -+	slice_addstr(&new_table_name, ".ref");
     ++	reftable_addition_destroy(add);
      +
     -+	slice_set_string(&new_table_path, st->reftable_dir);
     -+	slice_addstr(&new_table_path, "/");
     ++	err = reftable_stack_new_addition(&add, st);
     ++	assert_err(err);
      +
     -+	slice_addbuf(&new_table_path, new_table_name);
     ++	err = reftable_addition_add(add, &write_test_ref, &ref);
     ++	assert_err(err);
      +
     -+	if (!is_empty_table) {
     -+		err = rename(slice_as_string(&temp_tab_file_name),
     -+			     slice_as_string(&new_table_path));
     -+		if (err < 0) {
     -+			err = REFTABLE_IO_ERROR;
     -+			goto done;
     -+		}
     -+	}
     ++	err = reftable_addition_commit(add);
     ++	assert_err(err);
      +
     -+	for (i = 0; i < first; i++) {
     -+		slice_addstr(&ref_list_contents, st->merged->stack[i]->name);
     -+		slice_addstr(&ref_list_contents, "\n");
     -+	}
     -+	if (!is_empty_table) {
     -+		slice_addbuf(&ref_list_contents, new_table_name);
     -+		slice_addstr(&ref_list_contents, "\n");
     -+	}
     -+	for (i = last + 1; i < st->merged->stack_len; i++) {
     -+		slice_addstr(&ref_list_contents, st->merged->stack[i]->name);
     -+		slice_addstr(&ref_list_contents, "\n");
     -+	}
     ++	reftable_addition_destroy(add);
      +
     -+	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		unlink(slice_as_string(&new_table_path));
     -+		goto done;
     -+	}
     -+	err = close(lock_file_fd);
     -+	lock_file_fd = 0;
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		unlink(slice_as_string(&new_table_path));
     -+		goto done;
     -+	}
     ++	err = reftable_stack_read_ref(st, ref.ref_name, &dest);
     ++	assert_err(err);
     ++	assert(0 == strcmp("master", dest.target));
      +
     -+	err = rename(slice_as_string(&lock_file_name), st->list_file);
     -+	if (err < 0) {
     -+		err = REFTABLE_IO_ERROR;
     -+		unlink(slice_as_string(&new_table_path));
     -+		goto done;
     -+	}
     -+	have_lock = false;
     ++	reftable_ref_record_clear(&dest);
     ++	reftable_stack_destroy(st);
     ++	clear_dir(dir);
     ++}
      +
     -+	/* Reload the stack before deleting. On windows, we can only delete the
     -+	   files after we closed them.
     -+	*/
     -+	err = reftable_stack_reload_maybe_reuse(st, first < last);
     ++static void test_reftable_stack_validate_refname(void)
     ++{
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	int i;
     ++	struct reftable_ref_record ref = {
     ++		.ref_name = "a/b",
     ++		.update_index = 1,
     ++		.target = "master",
     ++	};
     ++	char *additions[] = { "a", "a/b/c" };
      +
     -+	listp = delete_on_success;
     -+	while (*listp) {
     -+		if (strcmp(*listp, slice_as_string(&new_table_path))) {
     -+			unlink(*listp);
     -+		}
     -+		listp++;
     -+	}
     ++	assert(mkdtemp(dir));
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
      +
     -+done:
     -+	free_names(delete_on_success);
     ++	err = reftable_stack_add(st, &write_test_ref, &ref);
     ++	assert_err(err);
      +
     -+	listp = subtable_locks;
     -+	while (*listp) {
     -+		unlink(*listp);
     -+		listp++;
     -+	}
     -+	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));
     ++	for (i = 0; i < ARRAY_SIZE(additions); i++) {
     ++		struct reftable_ref_record ref = {
     ++			.ref_name = additions[i],
     ++			.update_index = 1,
     ++			.target = "master",
     ++		};
     ++
     ++		err = reftable_stack_add(st, &write_test_ref, &ref);
     ++		assert(err == REFTABLE_NAME_CONFLICT);
      +	}
     -+	slice_release(&new_table_name);
     -+	slice_release(&new_table_path);
     -+	slice_release(&ref_list_contents);
     -+	slice_release(&temp_tab_file_name);
     -+	slice_release(&lock_file_name);
     -+	return err;
     -+}
      +
     -+int reftable_stack_compact_all(struct reftable_stack *st,
     -+			       struct reftable_log_expiry_config *config)
     -+{
     -+	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     ++	reftable_stack_destroy(st);
     ++	clear_dir(dir);
      +}
      +
     -+static int stack_compact_range_stats(struct reftable_stack *st, int first,
     -+				     int last,
     -+				     struct reftable_log_expiry_config *config)
     ++static int write_error(struct reftable_writer *wr, void *arg)
      +{
     -+	int err = stack_compact_range(st, first, last, config);
     -+	if (err > 0) {
     -+		st->stats.failures++;
     -+	}
     -+	return err;
     ++	return *((int *)arg);
      +}
      +
     -+static int segment_size(struct segment *s)
     ++static void test_reftable_stack_update_index_check(void)
      +{
     -+	return s->end - s->start;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++	struct reftable_ref_record ref1 = {
     ++		.ref_name = "name1",
     ++		.update_index = 1,
     ++		.target = "master",
     ++	};
     ++	struct reftable_ref_record ref2 = {
     ++		.ref_name = "name2",
     ++		.update_index = 1,
     ++		.target = "master",
     ++	};
     ++	assert(mkdtemp(dir));
     ++
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++
     ++	err = reftable_stack_add(st, &write_test_ref, &ref1);
     ++	assert_err(err);
     ++
     ++	err = reftable_stack_add(st, &write_test_ref, &ref2);
     ++	assert(err == REFTABLE_API_ERROR);
     ++	reftable_stack_destroy(st);
     ++	clear_dir(dir);
      +}
      +
     -+int fastlog2(uint64_t sz)
     ++static void test_reftable_stack_lock_failure(void)
      +{
     -+	int l = 0;
     -+	if (sz == 0)
     -+		return 0;
     -+	for (; sz; sz /= 2) {
     -+		l++;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err, i;
     ++	assert(mkdtemp(dir));
     ++
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++	for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
     ++		err = reftable_stack_add(st, &write_error, &i);
     ++		assert(err == i);
      +	}
     -+	return l - 1;
     ++
     ++	reftable_stack_destroy(st);
     ++	clear_dir(dir);
      +}
      +
     -+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
     ++static void test_reftable_stack_add(void)
      +{
     -+	struct segment *segs = reftable_calloc(sizeof(struct segment) * n);
     -+	int next = 0;
     -+	struct segment cur = { 0 };
      +	int i = 0;
     ++	int err = 0;
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_ref_record refs[2] = { 0 };
     ++	struct reftable_log_record logs[2] = { 0 };
     ++	int N = ARRAY_SIZE(refs);
     ++
     ++	assert(mkdtemp(dir));
     ++
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++	st->disable_auto_compact = true;
     ++
     ++	for (i = 0; i < N; i++) {
     ++		char buf[256];
     ++		snprintf(buf, sizeof(buf), "branch%02d", i);
     ++		refs[i].ref_name = xstrdup(buf);
     ++		refs[i].value = reftable_malloc(SHA1_SIZE);
     ++		refs[i].update_index = i + 1;
     ++		set_test_hash(refs[i].value, i);
     ++
     ++		logs[i].ref_name = xstrdup(buf);
     ++		logs[i].update_index = N + i + 1;
     ++		logs[i].new_hash = reftable_malloc(SHA1_SIZE);
     ++		logs[i].email = xstrdup("identity@invalid");
     ++		set_test_hash(logs[i].new_hash, i);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct write_log_arg arg = {
     ++			.log = &logs[i],
     ++			.update_index = reftable_stack_next_update_index(st),
     ++		};
     ++		int err = reftable_stack_add(st, &write_test_log, &arg);
     ++		assert_err(err);
     ++	}
      +
     -+	if (n == 0) {
     -+		*seglen = 0;
     -+		return segs;
     ++	err = reftable_stack_compact_all(st, NULL);
     ++	assert_err(err);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct reftable_ref_record dest = { 0 };
     ++		int err = reftable_stack_read_ref(st, refs[i].ref_name, &dest);
     ++		assert_err(err);
     ++		assert(reftable_ref_record_equal(&dest, refs + i, SHA1_SIZE));
     ++		reftable_ref_record_clear(&dest);
      +	}
     -+	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;
     -+		}
     ++	for (i = 0; i < N; i++) {
     ++		struct reftable_log_record dest = { 0 };
     ++		int err = reftable_stack_read_log(st, refs[i].ref_name, &dest);
     ++		assert_err(err);
     ++		assert(reftable_log_record_equal(&dest, logs + i, SHA1_SIZE));
     ++		reftable_log_record_clear(&dest);
     ++	}
      +
     -+		cur.log = log;
     -+		cur.end = i + 1;
     -+		cur.bytes += sizes[i];
     ++	/* cleanup */
     ++	reftable_stack_destroy(st);
     ++	for (i = 0; i < N; i++) {
     ++		reftable_ref_record_clear(&refs[i]);
     ++		reftable_log_record_clear(&logs[i]);
      +	}
     -+	segs[next++] = cur;
     -+	*seglen = next;
     -+	return segs;
     ++	clear_dir(dir);
      +}
      +
     -+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
     ++static void test_reftable_stack_tombstone(void)
      +{
     -+	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;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++	struct reftable_ref_record refs[2] = { 0 };
     ++	struct reftable_log_record logs[2] = { 0 };
     ++	int N = ARRAY_SIZE(refs);
     ++	struct reftable_ref_record dest = { 0 };
     ++	struct reftable_log_record log_dest = { 0 };
     ++
     ++	assert(mkdtemp(dir));
     ++
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		const char *buf = "branch";
     ++		refs[i].ref_name = xstrdup(buf);
     ++		refs[i].update_index = i + 1;
     ++		if (i % 2 == 0) {
     ++			refs[i].value = reftable_malloc(SHA1_SIZE);
     ++			set_test_hash(refs[i].value, i);
      +		}
     -+
     -+		if (segs[i].log < min_seg.log) {
     -+			min_seg = segs[i];
     ++		logs[i].ref_name = xstrdup(buf);
     ++		/* update_index is part of the key. */
     ++		logs[i].update_index = 42;
     ++		if (i % 2 == 0) {
     ++			logs[i].new_hash = reftable_malloc(SHA1_SIZE);
     ++			set_test_hash(logs[i].new_hash, i);
     ++			logs[i].email = xstrdup("identity@invalid");
      +		}
      +	}
     ++	for (i = 0; i < N; i++) {
     ++		int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
     ++		assert_err(err);
     ++	}
     ++	for (i = 0; i < N; i++) {
     ++		struct write_log_arg arg = {
     ++			.log = &logs[i],
     ++			.update_index = reftable_stack_next_update_index(st),
     ++		};
     ++		int err = reftable_stack_add(st, &write_test_log, &arg);
     ++		assert_err(err);
     ++	}
      +
     -+	while (min_seg.start > 0) {
     -+		int prev = min_seg.start - 1;
     -+		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
     -+			break;
     -+		}
     ++	err = reftable_stack_read_ref(st, "branch", &dest);
     ++	assert(err == 1);
     ++	reftable_ref_record_clear(&dest);
      +
     -+		min_seg.start = prev;
     -+		min_seg.bytes += sizes[prev];
     -+	}
     ++	err = reftable_stack_read_log(st, "branch", &log_dest);
     ++	assert(err == 1);
     ++	reftable_log_record_clear(&log_dest);
      +
     -+	reftable_free(segs);
     -+	return min_seg;
     -+}
     ++	err = reftable_stack_compact_all(st, NULL);
     ++	assert_err(err);
      +
     -+static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st)
     -+{
     -+	uint64_t *sizes =
     -+		reftable_calloc(sizeof(uint64_t) * st->merged->stack_len);
     -+	int version = (st->config.hash_id == SHA1_ID) ? 1 : 2;
     -+	int overhead = header_size(version) - 1;
     -+	int i = 0;
     -+	for (i = 0; i < st->merged->stack_len; i++) {
     -+		sizes[i] = st->merged->stack[i]->size - overhead;
     ++	err = reftable_stack_read_ref(st, "branch", &dest);
     ++	assert(err == 1);
     ++
     ++	err = reftable_stack_read_log(st, "branch", &log_dest);
     ++	assert(err == 1);
     ++	reftable_ref_record_clear(&dest);
     ++	reftable_log_record_clear(&log_dest);
     ++
     ++	/* cleanup */
     ++	reftable_stack_destroy(st);
     ++	for (i = 0; i < N; i++) {
     ++		reftable_ref_record_clear(&refs[i]);
     ++		reftable_log_record_clear(&logs[i]);
      +	}
     -+	return sizes;
     ++	clear_dir(dir);
      +}
      +
     -+int reftable_stack_auto_compact(struct reftable_stack *st)
     ++static void test_reftable_stack_hash_id(void)
      +{
     -+	uint64_t *sizes = stack_table_sizes_for_compaction(st);
     -+	struct segment seg =
     -+		suggest_compaction_segment(sizes, st->merged->stack_len);
     -+	reftable_free(sizes);
     -+	if (segment_size(&seg) > 0)
     -+		return stack_compact_range_stats(st, seg.start, seg.end - 1,
     -+						 NULL);
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++
     ++	struct reftable_ref_record ref = {
     ++		.ref_name = "master",
     ++		.target = "target",
     ++		.update_index = 1,
     ++	};
     ++	struct reftable_write_options cfg32 = { .hash_id = SHA256_ID };
     ++	struct reftable_stack *st32 = NULL;
     ++	struct reftable_write_options cfg_default = { 0 };
     ++	struct reftable_stack *st_default = NULL;
     ++	struct reftable_ref_record dest = { 0 };
     ++
     ++	assert(mkdtemp(dir));
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++
     ++	err = reftable_stack_add(st, &write_test_ref, &ref);
     ++	assert_err(err);
     ++
     ++	/* can't read it with the wrong hash ID. */
     ++	err = reftable_new_stack(&st32, dir, cfg32);
     ++	assert(err == REFTABLE_FORMAT_ERROR);
     ++
     ++	/* check that we can read it back with default config too. */
     ++	err = reftable_new_stack(&st_default, dir, cfg_default);
     ++	assert_err(err);
     ++
     ++	err = reftable_stack_read_ref(st_default, "master", &dest);
     ++	assert_err(err);
     ++
     ++	assert(!strcmp(dest.target, ref.target));
     ++	reftable_ref_record_clear(&dest);
     ++	reftable_stack_destroy(st);
     ++	reftable_stack_destroy(st_default);
     ++	clear_dir(dir);
     ++}
      +
     -+	return 0;
     ++static void test_log2(void)
     ++{
     ++	assert(1 == fastlog2(3));
     ++	assert(2 == fastlog2(4));
     ++	assert(2 == fastlog2(5));
      +}
      +
     -+struct reftable_compaction_stats *
     -+reftable_stack_compaction_stats(struct reftable_stack *st)
     ++static void test_sizes_to_segments(void)
      +{
     -+	return &st->stats;
     ++	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, ARRAY_SIZE(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);
     ++	reftable_free(segs);
      +}
      +
     -+int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
     -+			    struct reftable_ref_record *ref)
     ++static void test_sizes_to_segments_empty(void)
      +{
     -+	struct reftable_table tab = { NULL };
     -+	reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));
     -+	return reftable_table_read_ref(&tab, refname, ref);
     ++	uint64_t sizes[0];
     ++
     ++	int seglen = 0;
     ++	struct segment *segs =
     ++		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
     ++	assert(seglen == 0);
     ++	reftable_free(segs);
      +}
      +
     -+int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
     -+			    struct reftable_log_record *log)
     ++static void test_sizes_to_segments_all_equal(void)
      +{
     -+	struct reftable_iterator it = { 0 };
     -+	struct reftable_merged_table *mt = reftable_stack_merged_table(st);
     -+	int err = reftable_merged_table_seek_log(mt, &it, refname);
     -+	if (err)
     -+		goto done;
     ++	uint64_t sizes[] = { 5, 5 };
      +
     -+	err = reftable_iterator_next_log(&it, log);
     -+	if (err)
     -+		goto done;
     ++	int seglen = 0;
     ++	struct segment *segs =
     ++		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
     ++	assert(seglen == 1);
     ++	assert(segs[0].start == 0);
     ++	assert(segs[0].end == 2);
     ++	reftable_free(segs);
     ++}
      +
     -+	if (strcmp(log->ref_name, refname) ||
     -+	    reftable_log_record_is_deletion(log)) {
     -+		err = 1;
     -+		goto done;
     -+	}
     ++static 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, ARRAY_SIZE(sizes));
     ++	assert(min.start == 2);
     ++	assert(min.end == 7);
     ++}
      +
     -+done:
     -+	if (err) {
     -+		reftable_log_record_clear(log);
     -+	}
     -+	reftable_iterator_destroy(&it);
     -+	return err;
     ++static void test_suggest_compaction_segment_nothing(void)
     ++{
     ++	uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
     ++	struct segment result =
     ++		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
     ++	assert(result.start == result.end);
      +}
      +
     -+int stack_check_addition(struct reftable_stack *st, const char *new_tab_name)
     ++static void test_reflog_expire(void)
      +{
     -+	int err = 0;
     -+	struct reftable_block_source src = { 0 };
     -+	struct reftable_reader *rd = NULL;
     -+	struct reftable_table tab = { NULL };
     -+	struct reftable_ref_record *refs = NULL;
     -+	struct reftable_iterator it = { NULL };
     -+	int cap = 0;
     -+	int len = 0;
     ++	char dir[256] = "/tmp/stack.test_reflog_expire.XXXXXX";
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	struct reftable_log_record logs[20] = { 0 };
     ++	int N = ARRAY_SIZE(logs) - 1;
      +	int i = 0;
     ++	int err;
     ++	struct reftable_log_expiry_config expiry = {
     ++		.time = 10,
     ++	};
     ++	struct reftable_log_record log = { 0 };
      +
     -+	if (st->config.skip_name_check)
     -+		return 0;
     ++	assert(mkdtemp(dir));
      +
     -+	err = reftable_block_source_from_file(&src, new_tab_name);
     -+	if (err < 0)
     -+		goto done;
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
      +
     -+	err = reftable_new_reader(&rd, &src, new_tab_name);
     -+	if (err < 0)
     -+		goto done;
     ++	for (i = 1; i <= N; i++) {
     ++		char buf[256];
     ++		snprintf(buf, sizeof(buf), "branch%02d", i);
      +
     -+	err = reftable_reader_seek_ref(rd, &it, "");
     -+	if (err > 0) {
     -+		err = 0;
     -+		goto done;
     ++		logs[i].ref_name = xstrdup(buf);
     ++		logs[i].update_index = i;
     ++		logs[i].time = i;
     ++		logs[i].new_hash = reftable_malloc(SHA1_SIZE);
     ++		logs[i].email = xstrdup("identity@invalid");
     ++		set_test_hash(logs[i].new_hash, i);
      +	}
     -+	if (err < 0)
     -+		goto done;
      +
     -+	while (true) {
     -+		struct reftable_ref_record ref = { 0 };
     -+		err = reftable_iterator_next_ref(&it, &ref);
     -+		if (err > 0) {
     -+			break;
     -+		}
     -+		if (err < 0)
     -+			goto done;
     ++	for (i = 1; i <= N; i++) {
     ++		struct write_log_arg arg = {
     ++			.log = &logs[i],
     ++			.update_index = reftable_stack_next_update_index(st),
     ++		};
     ++		int err = reftable_stack_add(st, &write_test_log, &arg);
     ++		assert_err(err);
     ++	}
      +
     -+		if (len >= cap) {
     -+			cap = 2 * cap + 1;
     -+			refs = reftable_realloc(refs, cap * sizeof(refs[0]));
     -+		}
     ++	err = reftable_stack_compact_all(st, NULL);
     ++	assert_err(err);
      +
     -+		refs[len++] = ref;
     -+	}
     ++	err = reftable_stack_compact_all(st, &expiry);
     ++	assert_err(err);
      +
     -+	reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st));
     ++	err = reftable_stack_read_log(st, logs[9].ref_name, &log);
     ++	assert(err == 1);
      +
     -+	err = validate_ref_record_addition(tab, refs, len);
     ++	err = reftable_stack_read_log(st, logs[11].ref_name, &log);
     ++	assert_err(err);
      +
     -+done:
     -+	for (i = 0; i < len; i++) {
     -+		reftable_ref_record_clear(&refs[i]);
     -+	}
     ++	expiry.min_update_index = 15;
     ++	err = reftable_stack_compact_all(st, &expiry);
     ++	assert_err(err);
      +
     -+	free(refs);
     -+	reftable_iterator_destroy(&it);
     -+	reftable_reader_free(rd);
     -+	return err;
     -+}
     -
     - ## reftable/stack.h (new) ##
     -@@
     -+/*
     -+Copyright 2020 Google LLC
     ++	err = reftable_stack_read_log(st, logs[14].ref_name, &log);
     ++	assert(err == 1);
      +
     -+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
     -+*/
     ++	err = reftable_stack_read_log(st, logs[16].ref_name, &log);
     ++	assert_err(err);
      +
     -+#ifndef STACK_H
     -+#define STACK_H
     ++	/* cleanup */
     ++	reftable_stack_destroy(st);
     ++	for (i = 0; i <= N; i++) {
     ++		reftable_log_record_clear(&logs[i]);
     ++	}
     ++	clear_dir(dir);
     ++	reftable_log_record_clear(&log);
     ++}
      +
     -+#include "reftable.h"
     -+#include "system.h"
     ++static int write_nothing(struct reftable_writer *wr, void *arg)
     ++{
     ++	reftable_writer_set_limits(wr, 1, 1);
     ++	return 0;
     ++}
      +
     -+struct reftable_stack {
     -+	char *list_file;
     -+	char *reftable_dir;
     -+	bool disable_auto_compact;
     ++static void test_empty_add(void)
     ++{
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	int err;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	struct reftable_stack *st2 = NULL;
      +
     -+	struct reftable_write_options config;
     ++	assert(mkdtemp(dir));
      +
     -+	struct reftable_merged_table *merged;
     -+	struct reftable_compaction_stats stats;
     -+};
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
      +
     -+int read_lines(const char *filename, char ***lines);
     -+int stack_try_add(struct reftable_stack *st,
     -+		  int (*write_table)(struct reftable_writer *wr, void *arg),
     -+		  void *arg);
     -+int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr,
     -+			int first, int last,
     -+			struct reftable_log_expiry_config *config);
     -+int fastlog2(uint64_t sz);
     -+int stack_check_addition(struct reftable_stack *st, const char *new_tab_name);
     -+void reftable_addition_close(struct reftable_addition *add);
     -+int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
     -+				      bool reuse_open);
     ++	err = reftable_stack_add(st, &write_nothing, NULL);
     ++	assert_err(err);
      +
     -+struct segment {
     -+	int start, end;
     -+	int log;
     -+	uint64_t bytes;
     -+};
     ++	err = reftable_new_stack(&st2, dir, cfg);
     ++	assert_err(err);
     ++	clear_dir(dir);
     ++	reftable_stack_destroy(st);
     ++	reftable_stack_destroy(st2);
     ++}
      +
     -+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
     -+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
     ++static void test_reftable_stack_auto_compaction(void)
     ++{
     ++	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_stack *st = NULL;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++	int err, i;
     ++	int N = 100;
     ++	assert(mkdtemp(dir));
      +
     -+#endif
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		struct reftable_ref_record ref = {
     ++			.ref_name = name,
     ++			.update_index = reftable_stack_next_update_index(st),
     ++			.target = "master",
     ++		};
     ++		snprintf(name, sizeof(name), "branch%04d", i);
     ++
     ++		err = reftable_stack_add(st, &write_test_ref, &ref);
     ++		assert_err(err);
     ++
     ++		assert(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
     ++	}
     ++
     ++	assert(reftable_stack_compaction_stats(st)->entries_written <
     ++	       (uint64_t)(N * fastlog2(N)));
     ++
     ++	reftable_stack_destroy(st);
     ++	clear_dir(dir);
     ++}
     ++
     ++int stack_test_main(int argc, const char *argv[])
     ++{
     ++	add_test_case("test_reftable_stack_uptodate",
     ++		      &test_reftable_stack_uptodate);
     ++	add_test_case("test_reftable_stack_transaction_api",
     ++		      &test_reftable_stack_transaction_api);
     ++	add_test_case("test_reftable_stack_hash_id",
     ++		      &test_reftable_stack_hash_id);
     ++	add_test_case("test_sizes_to_segments_all_equal",
     ++		      &test_sizes_to_segments_all_equal);
     ++	add_test_case("test_reftable_stack_auto_compaction",
     ++		      &test_reftable_stack_auto_compaction);
     ++	add_test_case("test_reftable_stack_validate_refname",
     ++		      &test_reftable_stack_validate_refname);
     ++	add_test_case("test_reftable_stack_update_index_check",
     ++		      &test_reftable_stack_update_index_check);
     ++	add_test_case("test_reftable_stack_lock_failure",
     ++		      &test_reftable_stack_lock_failure);
     ++	add_test_case("test_reftable_stack_tombstone",
     ++		      &test_reftable_stack_tombstone);
     ++	add_test_case("test_reftable_stack_add_one",
     ++		      &test_reftable_stack_add_one);
     ++	add_test_case("test_empty_add", test_empty_add);
     ++	add_test_case("test_reflog_expire", test_reflog_expire);
     ++	add_test_case("test_suggest_compaction_segment",
     ++		      &test_suggest_compaction_segment);
     ++	add_test_case("test_suggest_compaction_segment_nothing",
     ++		      &test_suggest_compaction_segment_nothing);
     ++	add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     ++	add_test_case("test_sizes_to_segments_empty",
     ++		      &test_sizes_to_segments_empty);
     ++	add_test_case("test_log2", &test_log2);
     ++	add_test_case("test_parse_names", &test_parse_names);
     ++	add_test_case("test_read_file", &test_read_file);
     ++	add_test_case("test_names_equal", &test_names_equal);
     ++	add_test_case("test_reftable_stack_add", &test_reftable_stack_add);
     ++	return test_main(argc, argv);
     ++}
      
       ## reftable/system.h (new) ##
      @@
     @@ reftable/system.h (new)
      +			       const Bytef *source, uLong *sourceLen);
      +int hash_size(uint32_t id);
      +
     ++#endif
     +
     + ## reftable/test_framework.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style

344 lines not shown · raw message

18 lines not shown · raw message

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

#293 lore raw

PATCH v16 5/14 Treat REVERT_HEAD as a pseudo ref

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

---
 path.c      |  1 -
 path.h      |  8 +++-----
 sequencer.c | 16 +++++++++-------
 wt-status.c |  2 +-
 4 files changed, 13 insertions(+), 14 deletions(-)
path.c +0 -1
index 783cc2ae819..7b385e5eb28 100644
@@ -1528,7 +1528,6 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
-REPO_GIT_PATH_FUNC(revert_head, "REVERT_HEAD")
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
path.h +3 -5
index 8941c018a99..e7e77da6aaa 100644
@@ -170,7 +170,6 @@ void report_linked_checkout_garbage(void);
 	}
 
 struct path_cache {
-	const char *revert_head;
 	const char *squash_msg;
 	const char *merge_msg;
 	const char *merge_rr;
@@ -181,12 +180,11 @@ struct path_cache {
 	const char *shallow;
 };
 
-#define PATH_CACHE_INIT                                              \
-	{                                                            \
-		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
+#define PATH_CACHE_INIT                                        \
+	{                                                      \
+		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
 	}
 
-const char *git_path_revert_head(struct repository *r);
 const char *git_path_squash_msg(struct repository *r);
 const char *git_path_merge_msg(struct repository *r);
 const char *git_path_merge_rr(struct repository *r);
sequencer.c +9 -7
index 26286ec8d08..25ef9fc773d 100644
@@ -2317,8 +2317,10 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
 		need_cleanup = 1;
 	}
 
-	if (file_exists(git_path_revert_head(r))) {
-		if (!unlink(git_path_revert_head(r)) && verbose)
+	if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
+		if (!refs_delete_pseudoref(get_main_ref_store(r), "REVERT_HEAD",
+					   NULL) &&
+		    verbose)
 			warning(_("cancelling a revert in progress"));
 		opts.action = REPLAY_REVERT;
 		need_cleanup = 1;
@@ -2677,7 +2679,7 @@ static int create_seq_dir(struct repository *r)
 	const char *in_progress_error = NULL;
 	const char *in_progress_advice = NULL;
 	unsigned int advise_skip =
-		file_exists(git_path_revert_head(r)) ||
+		refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") ||
 		refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
 
 	if (!sequencer_get_last_command(r, &action)) {
@@ -2778,7 +2780,7 @@ static int rollback_single_pick(struct repository *r)
 	struct object_id head_oid;
 
 	if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
-	    !file_exists(git_path_revert_head(r)))
+	    !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
 		return error(_("no cherry-pick or revert in progress"));
 	if (read_ref_full("HEAD", 0, &head_oid, NULL))
 		return error(_("cannot resolve HEAD"));
@@ -2872,7 +2874,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
 	 */
 	switch (opts->action) {
 	case REPLAY_REVERT:
-		if (!file_exists(git_path_revert_head(r))) {
+		if (!refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
 			if (action != REPLAY_REVERT)
 				return error(_("no revert in progress"));
 			if (!rollback_is_safe())
@@ -4210,7 +4212,7 @@ static int continue_single_pick(struct repository *r)
 	const char *argv[] = { "commit", NULL };
 
 	if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
-	    !file_exists(git_path_revert_head(r)))
+	    !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
 		return error(_("no cherry-pick or revert in progress"));
 	return run_command_v_opt(argv, RUN_GIT_CMD);
 }
@@ -4390,7 +4392,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
 		/* Verify that the conflict has been resolved */
 		if (refs_ref_exists(get_main_ref_store(r),
 				    "CHERRY_PICK_HEAD") ||
-		    file_exists(git_path_revert_head(r))) {
+		    refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
 			res = continue_single_pick(r);
 			if (res)
 				goto release_todo_list;
wt-status.c +1 -1
index 96302be030b..81b768504a1 100644
@@ -1642,7 +1642,7 @@ void wt_status_get_state(struct repository *r,
 		oidcpy(&state->cherry_pick_head_oid, &oid);
 	}
 	wt_status_check_bisect(NULL, state);
-	if (!stat(git_path_revert_head(r), &st) &&
+	if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") &&
 	    !get_oid("REVERT_HEAD", &oid)) {
 		state->revert_in_progress = 1;
 		oidcpy(&state->revert_head_oid, &oid);
Signature · 1 line
gitgitgadget
#292 lore raw

PATCH v16 4/14 Treat CHERRY_PICK_HEAD as a pseudo ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

Check for existence and delete CHERRY_PICK_HEAD through pseudo ref functions. This will help cherry-pick work with alternate ref storage backends.

---
 builtin/commit.c | 34 +++++++++++++++++++---------------
 builtin/merge.c  |  2 +-
 path.c           |  1 -
 path.h           |  7 ++++---
 sequencer.c      | 42 ++++++++++++++++++++++++++----------------
 wt-status.c      |  4 ++--
 6 files changed, 52 insertions(+), 38 deletions(-)
builtin/commit.c +19 -15
index d1b7396052a..e27120b982b 100644
@@ -847,21 +847,25 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 			if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
 				!merge_contains_scissors)
 				wt_status_add_cut_line(s->fp);
-			status_printf_ln(s, GIT_COLOR_NORMAL,
-			    whence == FROM_MERGE
-				? _("\n"
-					"It looks like you may be committing a merge.\n"
-					"If this is not correct, please remove the file\n"
-					"	%s\n"
-					"and try again.\n")
-				: _("\n"
-					"It looks like you may be committing a cherry-pick.\n"
-					"If this is not correct, please remove the file\n"
-					"	%s\n"
-					"and try again.\n"),
-				whence == FROM_MERGE ?
-					git_path_merge_head(the_repository) :
-					git_path_cherry_pick_head(the_repository));
+			if (whence == FROM_MERGE)
+				status_printf_ln(
+					s, GIT_COLOR_NORMAL,
+
+					_("\n"
+					  "It looks like you may be committing a merge.\n"
+					  "If this is not correct, please remove the file\n"
+					  "	%s\n"
+					  "and try again.\n"),
+					git_path_merge_head(the_repository));
+			else
+				status_printf_ln(
+					s, GIT_COLOR_NORMAL,
+
+					_("\n"
+					  "It looks like you may be committing a cherry-pick.\n"
+					  "If this is not correct, please run\n"
+					  "	git cherry-pick --abort\n"
+					  "and try again.\n"));
 		}
 
 		fprintf(s->fp, "\n");
builtin/merge.c +1 -1
index 7da707bf55d..93b0a7b6eda 100644
@@ -1352,7 +1352,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		else
 			die(_("You have not concluded your merge (MERGE_HEAD exists)."));
 	}
-	if (file_exists(git_path_cherry_pick_head(the_repository))) {
+	if (ref_exists("CHERRY_PICK_HEAD")) {
 		if (advice_resolve_conflict)
 			die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
 			    "Please, commit your changes before you merge."));
path.c +0 -1
index 8b2c7531919..783cc2ae819 100644
@@ -1528,7 +1528,6 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
-REPO_GIT_PATH_FUNC(cherry_pick_head, "CHERRY_PICK_HEAD")
 REPO_GIT_PATH_FUNC(revert_head, "REVERT_HEAD")
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
path.h +4 -3
index 1f1bf8f87a8..8941c018a99 100644
@@ -170,7 +170,6 @@ void report_linked_checkout_garbage(void);
 	}
 
 struct path_cache {
-	const char *cherry_pick_head;
 	const char *revert_head;
 	const char *squash_msg;
 	const char *merge_msg;
@@ -182,9 +181,11 @@ struct path_cache {
 	const char *shallow;
 };
 
-#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+#define PATH_CACHE_INIT                                              \
+	{                                                            \
+		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
+	}
 
-const char *git_path_cherry_pick_head(struct repository *r);
 const char *git_path_revert_head(struct repository *r);
 const char *git_path_squash_msg(struct repository *r);
 const char *git_path_merge_msg(struct repository *r);
sequencer.c +26 -16

114 lines not on this page · raw message

wt-status.c +2 -2
index 98dfa6f73f9..96302be030b 100644
@@ -1636,8 +1636,8 @@ void wt_status_get_state(struct repository *r,
 		state->merge_in_progress = 1;
 	} else if (wt_status_check_rebase(NULL, state)) {
 		;		/* all set */
-	} else if (!stat(git_path_cherry_pick_head(r), &st) &&
-			!get_oid("CHERRY_PICK_HEAD", &oid)) {
+	} else if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
+		   !get_oid("CHERRY_PICK_HEAD", &oid)) {
 		state->cherry_pick_in_progress = 1;
 		oidcpy(&state->cherry_pick_head_oid, &oid);
 	}
Signature · 1 line
gitgitgadget
#294 lore raw

PATCH v16 6/14 Move REF_LOG_ONLY to refs-internal.h

In reply to Han-Wen Nienhuys via GitGitGadget

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

REF_LOG_ONLY is used in the transaction preparation: if a symref is involved in a transaction, the referent of the symref should be updated, and the symref itself should only be updated in the reflog. Other ref backends will need to duplicate this logic.

---
 refs/files-backend.c | 7 -------
 refs/refs-internal.h | 7 +++++++
 2 files changed, 7 insertions(+), 7 deletions(-)
refs/files-backend.c +0 -7
index df7553f4cc3..141b6b08816 100644
@@ -38,13 +38,6 @@
  */
 #define REF_NEEDS_COMMIT (1 << 6)
 
-/*
- * Used as a flag in ref_update::flags when we want to log a ref
- * update but not actually perform it.  This is used when a symbolic
- * ref update is split up.
- */
-#define REF_LOG_ONLY (1 << 7)
-
 /*
  * Used as a flag in ref_update::flags when the ref_update was via an
  * update to HEAD.
refs/refs-internal.h +7 -0
index 59b053d53a2..dc9e8d3a92b 100644
@@ -31,6 +31,13 @@ struct ref_transaction;
  */
 #define REF_HAVE_OLD (1 << 3)
 
+/*
+ * Used as a flag in ref_update::flags when we want to log a ref
+ * update but not actually perform it.  This is used when a symbolic
+ * ref update is split up.
+ */
+#define REF_LOG_ONLY (1 << 7)
+
 /*
  * Return the length of time to retry acquiring a loose reference lock
  * before giving up, in milliseconds:
Signature · 1 line
gitgitgadget
#295 lore raw

PATCH v16 7/14 Iterate over the "refs/" namespace in for_each_[raw]ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

This happens implicitly in the files/packed ref backend; making it explicit simplifies adding alternate ref storage backends, such as reftable.

---
 refs.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
refs.c +3 -3
index 812fee47108..29e710a29e9 100644
@@ -1450,7 +1450,7 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,
 
 int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(refs, "", fn, 0, 0, cb_data);
+	return do_for_each_ref(refs, "refs/", fn, 0, 0, cb_data);
 }
 
 int for_each_ref(each_ref_fn fn, void *cb_data)
@@ -1510,8 +1510,8 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(refs, "", fn, 0,
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+	return do_for_each_ref(refs, "refs/", fn, 0, DO_FOR_EACH_INCLUDE_BROKEN,
+			       cb_data);
 }
 
 int for_each_rawref(each_ref_fn fn, void *cb_data)
Signature · 1 line
gitgitgadget
#296 lore raw

PATCH v16 8/14 Add .gitattributes for the reftable/ directory

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 reftable/.gitattributes | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 reftable/.gitattributes
reftable/.gitattributes new file +1 -0
new file mode 100644
index 00000000000..f44451a3795
@@ -0,0 +1 @@
+/zlib-compat.c	whitespace=-indent-with-non-tab,-trailing-space
Signature · 1 line
gitgitgadget
#299 lore raw

PATCH v16 11/14 Hookup unittests for the reftable library.

In reply to Han-Wen Nienhuys via GitGitGadget

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

The unittests are under reftable/*_test.c, so all of the reftable code stays in one directory. They are called from t/helpers/test-reftable.c in t0031-reftable.sh

---
 Makefile             | 17 ++++++++++++++++-
 t/helper/test-tool.c |  1 +
 t/helper/test-tool.h |  1 +
 t/t0031-reftable.sh  |  5 +++++
 4 files changed, 23 insertions(+), 1 deletion(-)
Makefile +16 -1
index 6b21c247b12..a39896191c7 100644
@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o
 TEST_BUILTINS_OBJS += test-read-graph.o
 TEST_BUILTINS_OBJS += test-read-midx.o
 TEST_BUILTINS_OBJS += test-ref-store.o
+TEST_BUILTINS_OBJS += test-reftable.o
 TEST_BUILTINS_OBJS += test-regex.o
 TEST_BUILTINS_OBJS += test-repository.o
 TEST_BUILTINS_OBJS += test-revision-walking.o
@@ -808,6 +809,7 @@ LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
 VCSSVN_LIB = vcs-svn/lib.a
 REFTABLE_LIB = reftable/libreftable.a
+REFTABLE_TEST_LIB = reftable/libreftable_test.a
 
 GENERATED_H += config-list.h
 GENERATED_H += command-list.h
@@ -2370,6 +2372,15 @@ REFTABLE_OBJS += reftable/tree.o
 REFTABLE_OBJS += reftable/writer.o
 REFTABLE_OBJS += reftable/zlib-compat.o
 
+REFTABLE_TEST_OBJS += reftable/block_test.o
+REFTABLE_TEST_OBJS += reftable/merged_test.o
+REFTABLE_TEST_OBJS += reftable/record_test.o
+REFTABLE_TEST_OBJS += reftable/refname_test.o
+REFTABLE_TEST_OBJS += reftable/reftable_test.o
+REFTABLE_TEST_OBJS += reftable/slice_test.o
+REFTABLE_TEST_OBJS += reftable/stack_test.o
+REFTABLE_TEST_OBJS += reftable/tree_test.o
+REFTABLE_TEST_OBJS += reftable/test_framework.o
 
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
@@ -2377,6 +2388,7 @@ OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 	$(VCSSVN_OBJS) \
 	$(FUZZ_OBJS) \
 	$(REFTABLE_OBJS) \
+	$(REFTABLE_TEST_OBJS) \
 	common-main.o \
 	git.o
 ifndef NO_CURL
@@ -2520,6 +2532,9 @@ $(VCSSVN_LIB): $(VCSSVN_OBJS)
 $(REFTABLE_LIB): $(REFTABLE_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+$(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -2802,7 +2817,7 @@ t/helper/test-svn-fe$X: $(VCSSVN_LIB)
 
 t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: t/helper/test-tool$X
t/helper/test-tool.c +1 -0
index 590b2efca70..10366b7b762 100644
@@ -52,6 +52,7 @@ static struct test_cmd cmds[] = {
 	{ "read-graph", cmd__read_graph },
 	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
+	{ "reftable", cmd__reftable },
 	{ "regex", cmd__regex },
 	{ "repository", cmd__repository },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index ddc8e990e91..d52ba2f5e57 100644
@@ -41,6 +41,7 @@ int cmd__read_cache(int argc, const char **argv);
 int cmd__read_graph(int argc, const char **argv);
 int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
+int cmd__reftable(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
 int cmd__repository(int argc, const char **argv);
 int cmd__revision_walking(int argc, const char **argv);
t/t0031-reftable.sh +5 -0
index 221ef51447c..c1f3abfda9f 100755
@@ -15,6 +15,11 @@ initialize ()  {
 	mv .git/hooks .git/hooks-disabled
 }
 
+test_expect_success 'unittests' '
+	test-tool reftable
+'
+
+
 test_expect_success 'delete ref' '
 	initialize &&
 	test_commit file &&
Signature · 1 line
gitgitgadget
#298 lore raw

PATCH v16 10/14 Reftable support for git-core

In reply to Han-Wen Nienhuys via GitGitGadget

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

For background, see the previous commit introducing the library.

This introduces the refs/reftable-backend.c containing reftable powered ref storage backend.

It can be activated by passing --ref-storage=reftable to "git init".

TODO:

* Fix worktree commands

* Spots marked XXX

Example use: see t/t0031-reftable.sh

---
 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   27 +-
 builtin/clone.c                               |    3 +-
 builtin/init-db.c                             |   56 +-
 cache.h                                       |    6 +-
 refs.c                                        |   27 +-
 refs.h                                        |    3 +
 refs/refs-internal.h                          |    1 +
 refs/reftable-backend.c                       | 1331 +++++++++++++++++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 setup.c                                       |   12 +-
 t/t0031-reftable.sh                           |  142 ++
 13 files changed, 1590 insertions(+), 30 deletions(-)
 create mode 100644 refs/reftable-backend.c
 create mode 100755 t/t0031-reftable.sh
Documentation/technical/repository-version.txt +7 -0
index 7844ef30ffd..72576235833 100644
@@ -100,3 +100,10 @@ If set, by default "git config" reads from both "config" and
 multiple working directory mode, "config" file is shared while
 "config.worktree" is per-working directory (i.e., it's in
 GIT_COMMON_DIR/worktrees/<id>/config.worktree)
+
+==== `refStorage`
+
+Specifies the file format for the ref database. Values are `files`
+(for the traditional packed + loose ref format) and `reftable` for the
+binary reftable format. See https://github.com/google/reftable for
+more information.
Makefile +25 -2
index 372139f1f24..6b21c247b12 100644
@@ -807,6 +807,7 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
 VCSSVN_LIB = vcs-svn/lib.a
+REFTABLE_LIB = reftable/libreftable.a
 
 GENERATED_H += config-list.h
 GENERATED_H += command-list.h
@@ -959,6 +960,7 @@ LIB_OBJS += ref-filter.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
 LIB_OBJS += refs/files-backend.o
+LIB_OBJS += refs/reftable-backend.o
 LIB_OBJS += refs/iterator.o
 LIB_OBJS += refs/packed-backend.o
 LIB_OBJS += refs/ref-cache.o
@@ -1162,7 +1164,7 @@ THIRD_PARTY_SOURCES += compat/regex/%
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB)
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2352,11 +2354,29 @@ VCSSVN_OBJS += vcs-svn/sliding_window.o
 VCSSVN_OBJS += vcs-svn/svndiff.o
 VCSSVN_OBJS += vcs-svn/svndump.o
 
+REFTABLE_OBJS += reftable/basics.o
+REFTABLE_OBJS += reftable/block.o
+REFTABLE_OBJS += reftable/file.o
+REFTABLE_OBJS += reftable/iter.o
+REFTABLE_OBJS += reftable/merged.o
+REFTABLE_OBJS += reftable/pq.o
+REFTABLE_OBJS += reftable/reader.o
+REFTABLE_OBJS += reftable/record.o
+REFTABLE_OBJS += reftable/refname.o
+REFTABLE_OBJS += reftable/reftable.o
+REFTABLE_OBJS += reftable/slice.o
+REFTABLE_OBJS += reftable/stack.o
+REFTABLE_OBJS += reftable/tree.o
+REFTABLE_OBJS += reftable/writer.o
+REFTABLE_OBJS += reftable/zlib-compat.o
+
+
 TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 	$(XDIFF_OBJS) \
 	$(VCSSVN_OBJS) \
 	$(FUZZ_OBJS) \
+	$(REFTABLE_OBJS) \
 	common-main.o \
 	git.o
 ifndef NO_CURL
@@ -2497,6 +2517,9 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
 $(VCSSVN_LIB): $(VCSSVN_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+$(REFTABLE_LIB): $(REFTABLE_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
 export DEFAULT_EDITOR DEFAULT_PAGER
 
 Documentation/GIT-EXCLUDED-PROGRAMS: FORCE
@@ -3112,7 +3135,7 @@ cocciclean:
 clean: profile-clean coverage-clean cocciclean
 	$(RM) *.res
 	$(RM) $(OBJECTS)
-	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
+	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(REFTABLE_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
builtin/clone.c +2 -1
index cb48a291caf..4d0cf065e4a 100644
@@ -1108,7 +1108,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET);
+	init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
+		DEFAULT_REF_STORAGE, INIT_DB_QUIET);
 
 	if (real_git_dir)
 		git_dir = real_git_dir;
builtin/init-db.c +42 -14

132 lines not on this page · raw message

cache.h +4 -2
index 0f0485ecfe2..8cb884773c3 100644
@@ -628,8 +628,9 @@ int path_inside_repo(const char *prefix, const char *path);
 
 int init_db(const char *git_dir, const char *real_git_dir,
 	    const char *template_dir, int hash_algo,
-	    unsigned int flags);
-void initialize_repository_version(int hash_algo);
+	    const char *ref_storage_format, unsigned int flags);
+void initialize_repository_version(int hash_algo,
+				   const char *ref_storage_format);
 
 void sanitize_stdfds(void);
 int daemonize(void);
@@ -1043,6 +1044,7 @@ struct repository_format {
 	int is_bare;
 	int hash_algo;
 	char *work_tree;
+	char *ref_storage;
 	struct string_list unknown_extensions;
 };
 
refs.c +19 -8
index 29e710a29e9..4409080dfd8 100644
@@ -17,10 +17,16 @@
 #include "argv-array.h"
 #include "repository.h"
 
+const char *default_ref_storage(void)
+{
+	const char *test = getenv("GIT_TEST_REFTABLE");
+	return test ? "reftable" : "files";
+}
+
 /*
  * List of all available backends
  */
-static struct ref_storage_be *refs_backends = &refs_be_files;
+static struct ref_storage_be *refs_backends = &refs_be_reftable;
 
 static struct ref_storage_be *find_ref_storage_backend(const char *name)
 {
@@ -1717,13 +1723,13 @@ static struct ref_store *lookup_ref_store_map(struct hashmap *map,
  * Create, record, and return a ref_store instance for the specified
  * gitdir.
  */
-static struct ref_store *ref_store_init(const char *gitdir,
+static struct ref_store *ref_store_init(const char *gitdir, const char *be_name,
 					unsigned int flags)
 {
-	const char *be_name = "files";
-	struct ref_storage_be *be = find_ref_storage_backend(be_name);
+	struct ref_storage_be *be;
 	struct ref_store *refs;
 
+	be = find_ref_storage_backend(be_name);
 	if (!be)
 		BUG("reference backend %s is unknown", be_name);
 
@@ -1739,7 +1745,11 @@ struct ref_store *get_main_ref_store(struct repository *r)
 	if (!r->gitdir)
 		BUG("attempting to get main_ref_store outside of repository");
 
-	r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+	r->refs_private = ref_store_init(r->gitdir,
+					 r->ref_storage_format ?
+						 r->ref_storage_format :
+						 DEFAULT_REF_STORAGE,
+					 REF_STORE_ALL_CAPS);
 	return r->refs_private;
 }
 
@@ -1794,7 +1804,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 		goto done;
 
 	/* assume that add_submodule_odb() has been called */
-	refs = ref_store_init(submodule_sb.buf,
+	refs = ref_store_init(submodule_sb.buf, DEFAULT_REF_STORAGE, /* XXX */
 			      REF_STORE_READ | REF_STORE_ODB);
 	register_ref_store_map(&submodule_ref_stores, "submodule",
 			       refs, submodule);
@@ -1808,6 +1818,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 
 struct ref_store *get_worktree_ref_store(const struct worktree *wt)
 {
+	const char *format = DEFAULT_REF_STORAGE; /* XXX */
 	struct ref_store *refs;
 	const char *id;
 
@@ -1821,9 +1832,9 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt)
 
 	if (wt->id)
 		refs = ref_store_init(git_common_path("worktrees/%s", wt->id),
-				      REF_STORE_ALL_CAPS);
+				      format, REF_STORE_ALL_CAPS);
 	else
-		refs = ref_store_init(get_git_common_dir(),
+		refs = ref_store_init(get_git_common_dir(), format,
 				      REF_STORE_ALL_CAPS);
 
 	if (refs)
refs.h +3 -0
index 7aaa1226551..ddcc940d5c7 100644
@@ -9,6 +9,9 @@ struct string_list;
 struct string_list_item;
 struct worktree;
 
+/* Returns the ref storage backend to use by default. */
+const char *default_ref_storage(void);
+
 /*
  * Resolve a reference, recursively following symbolic refererences.
  *
refs/refs-internal.h +1 -0
index dc9e8d3a92b..7afe4c28310 100644
@@ -693,6 +693,7 @@ struct ref_storage_be {
 };
 
 extern struct ref_storage_be refs_be_files;
+extern struct ref_storage_be refs_be_reftable;
 extern struct ref_storage_be refs_be_packed;
 
 /*
refs/reftable-backend.c new file +1331 -0

1,331 lines not on this page · raw message

repository.c +2 -0
index 6f7f6f002b1..087760bc184 100644
@@ -178,6 +178,8 @@ int repo_init(struct repository *repo,
 	if (worktree)
 		repo_set_worktree(repo, worktree);
 
+	repo->ref_storage_format = xstrdup_or_null(format.ref_storage);
+
 	clear_repository_format(&format);
 	return 0;
 
repository.h +3 -0
index 6534fbb7b31..f57b73f4a27 100644
@@ -74,6 +74,9 @@ struct repository {
 	 */
 	struct ref_store *refs_private;
 
+	/* The format to use for the ref database. */
+	char *ref_storage_format;
+
 	/*
 	 * Contains path to often used file names.
 	 */
setup.c +9 -3
index 65fe5ecefbe..2ef970f9f88 100644
@@ -468,9 +468,11 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
 			if (!value)
 				return config_error_nonbool(var);
 			data->partial_clone = xstrdup(value);
-		} else if (!strcmp(ext, "worktreeconfig"))
+		} else if (!strcmp(ext, "worktreeconfig")) {
 			data->worktree_config = git_config_bool(var, value);
-		else
+		} else if (!strcmp(ext, "refstorage")) {
+			data->ref_storage = xstrdup(value);
+		} else
 			string_list_append(&data->unknown_extensions, ext);
 	}
 
@@ -559,6 +561,7 @@ void clear_repository_format(struct repository_format *format)
 	string_list_clear(&format->unknown_extensions, 0);
 	free(format->work_tree);
 	free(format->partial_clone);
+	free(format->ref_storage);
 	init_repository_format(format);
 }
 
@@ -1204,8 +1207,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
 				gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
 			setup_git_env(gitdir);
 		}
-		if (startup_info->have_repository)
+		if (startup_info->have_repository) {
 			repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
+			the_repository->ref_storage_format =
+				xstrdup_or_null(repo_fmt.ref_storage);
+		}
 	}
 
 	strbuf_release(&dir);
t/t0031-reftable.sh new file +142 -0

142 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#304 lore raw

PATCH v16 13/14 vcxproj: adjust for the reftable changes

In reply to Han-Wen Nienhuys via GitGitGadget

From: Johannes Schindelin <[email protected]>

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

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

PATCH v16 9/14 Add reftable library

In reply to Han-Wen Nienhuys via GitGitGadget

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

Reftable is a format for storing the ref database. Its rationale and specification is in the preceding commit.

This imports the upstream library as one big commit. For understanding the code, it is suggested to read the code in the following order:

* The specification under Documentation/technical/reftable.txt

* reftable.h - the public API

* record.{c,h} - reading and writing records

* block.{c,h} - reading and writing blocks.

* writer.{c,h} - writing a complete reftable file.

* merged.{c,h} and pq.{c,h} - reading a stack of reftables

* stack.{c,h} - writing and compacting stacks of reftable on the filesystem.

---
 reftable/LICENSE          |   31 +
 reftable/README.md        |   11 +
 reftable/VERSION          |    1 +
 reftable/basics.c         |  215 +++++++
 reftable/basics.h         |   53 ++
 reftable/block.c          |  433 +++++++++++++
 reftable/block.h          |  129 ++++
 reftable/block_test.c     |  156 +++++
 reftable/constants.h      |   21 +
 reftable/file.c           |   95 +++
 reftable/iter.c           |  243 ++++++++
 reftable/iter.h           |   72 +++
 reftable/merged.c         |  320 ++++++++++
 reftable/merged.h         |   39 ++
 reftable/merged_test.c    |  273 +++++++++
 reftable/pq.c             |  113 ++++
 reftable/pq.h             |   34 ++
 reftable/reader.c         |  742 +++++++++++++++++++++++
 reftable/reader.h         |   65 ++
 reftable/record.c         | 1113 ++++++++++++++++++++++++++++++++++
 reftable/record.h         |  128 ++++
 reftable/record_test.c    |  400 ++++++++++++
 reftable/refname.c        |  209 +++++++
 reftable/refname.h        |   38 ++
 reftable/refname_test.c   |   99 +++
 reftable/reftable-tests.h |   13 +
 reftable/reftable.c       |   90 +++
 reftable/reftable.h       |  564 +++++++++++++++++
 reftable/reftable_test.c  |  631 +++++++++++++++++++
 reftable/slice.c          |  243 ++++++++
 reftable/slice.h          |   87 +++
 reftable/slice_test.c     |   40 ++
 reftable/stack.c          | 1207 +++++++++++++++++++++++++++++++++++++
 reftable/stack.h          |   48 ++
 reftable/stack_test.c     |  743 +++++++++++++++++++++++
 reftable/system.h         |   54 ++
 reftable/test_framework.c |   69 +++
 reftable/test_framework.h |   64 ++
 reftable/tree.c           |   63 ++
 reftable/tree.h           |   34 ++
 reftable/tree_test.c      |   62 ++
 reftable/update.sh        |   25 +
 reftable/writer.c         |  644 ++++++++++++++++++++
 reftable/writer.h         |   60 ++
 reftable/zlib-compat.c    |   92 +++
 45 files changed, 9866 insertions(+)
 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/constants.h
 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/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/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 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
reftable/LICENSE new file +31 -0
new file mode 100644
index 00000000000..402e0f9356b
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
reftable/README.md new file +11 -0
new file mode 100644
index 00000000000..21500563fd4
@@ -0,0 +1,11 @@
+
+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:
+
+   sh reftable/update.sh
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
reftable/VERSION new file +1 -0
new file mode 100644
index 00000000000..c8bb0952506
@@ -0,0 +1 @@
+0c164275ea8f8aa7d099f7425ddcef1affe137e9 C: compile framework with Git options
reftable/basics.c new file +215 -0

215 lines not on this page · raw message

reftable/basics.h new file +53 -0

53 lines not on this page · raw message

reftable/block.c new file +433 -0

433 lines not on this page · raw message

reftable/block.h new file +129 -0

129 lines not on this page · raw message

reftable/block_test.c new file +156 -0

156 lines not on this page · raw message

reftable/constants.h new file +21 -0
new file mode 100644
index 00000000000..5eee72c4c11
@@ -0,0 +1,21 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
reftable/file.c new file +95 -0

95 lines not on this page · raw message

reftable/iter.c new file +243 -0

243 lines not on this page · raw message

reftable/iter.h new file +72 -0

72 lines not on this page · raw message

reftable/merged.c new file +320 -0

320 lines not on this page · raw message

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

273 lines not on this page · raw message

reftable/pq.c new file +113 -0

113 lines not on this page · raw message

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

742 lines not on this page · raw message

reftable/reader.h new file +65 -0

19 lines not on this page · raw message

reftable/record.c new file +1113 -0

1,113 lines not on this page · raw message

reftable/record.h new file +128 -0

128 lines not on this page · raw message

reftable/record_test.c new file +400 -0

400 lines not on this page · raw message

reftable/refname.c new file +209 -0

209 lines not on this page · raw message

reftable/refname.h new file +38 -0
new file mode 100644
index 00000000000..f335287fcdb

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

reftable/refname_test.c new file +99 -0

99 lines not on this page · raw message

reftable/reftable-tests.h new file +13 -0
new file mode 100644
index 00000000000..031e1ef2c5b

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

reftable/reftable.c new file +90 -0

90 lines not on this page · raw message

reftable/reftable.h new file +564 -0

564 lines not on this page · raw message

reftable/reftable_test.c new file +631 -0

631 lines not on this page · raw message

reftable/slice.c new file +243 -0

243 lines not on this page · raw message

reftable/slice.h new file +87 -0

87 lines not on this page · raw message

reftable/slice_test.c new file +40 -0
new file mode 100644
index 00000000000..f95790c7643

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

reftable/stack.c new file +1207 -0

1,207 lines not on this page · raw message

reftable/stack.h new file +48 -0

48 lines not on this page · raw message

reftable/stack_test.c new file +743 -0

743 lines not on this page · raw message

reftable/system.h new file +54 -0

54 lines not on this page · raw message

reftable/test_framework.c new file +69 -0

69 lines not on this page · raw message

reftable/test_framework.h new file +64 -0

64 lines not on this page · raw message

reftable/tree.c new file +63 -0

63 lines not on this page · raw message

reftable/tree.h new file +34 -0
new file mode 100644
index 00000000000..954512e9a3a

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

reftable/tree_test.c new file +62 -0

62 lines not on this page · raw message

reftable/update.sh new file +25 -0
new file mode 100755
index 00000000000..b1d0c649f1f

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

reftable/writer.c new file +644 -0

644 lines not on this page · raw message

reftable/writer.h new file +60 -0

60 lines not on this page · raw message

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

92 lines not on this page · raw message

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

Signature · 1 line
gitgitgadget

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

#305 lore raw

PATCH v16 14/14 Add reftable testing infrastructure

In reply to Han-Wen Nienhuys via GitGitGadget

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

* Add GIT_TEST_REFTABLE environment var to control default ref storage

* Add test_prerequisite REFTABLE.

* Skip some tests that are incompatible:

* t3210-pack-refs.sh - does not apply
* t9903-bash-prompt - The bash mode reads .git/HEAD directly
* t1450-fsck.sh - manipulates .git/ directly to create invalid state

Major test failures:

* t1400-update-ref.sh - Reads from .git/{refs,logs} directly
* t1404-update-ref-errors.sh - Manipulates .git/refs/ directly
* t1405 - inspecs .git/ directly.

Worst offenders:

t1400-update-ref.sh                      - 82 of 185
t2400-worktree-add.sh                    - 58 of 69
t1404-update-ref-errors.sh               - 44 of 53
t3514-cherry-pick-revert-gpg.sh          - 36 of 36
t5541-http-push-smart.sh                 - 29 of 38
t6003-rev-list-topo-order.sh             - 29 of 35
t3420-rebase-autostash.sh                - 28 of 42
t6120-describe.sh                        - 21 of 82
t3430-rebase-merges.sh                   - 18 of 24
t2018-checkout-branch.sh                 - 15 of 22

..

---
 builtin/clone.c               | 2 +-
 refs.c                        | 6 +++---
 t/t1409-avoid-packing-refs.sh | 6 ++++++
 t/t1450-fsck.sh               | 6 ++++++
 t/t3210-pack-refs.sh          | 6 ++++++
 t/t9903-bash-prompt.sh        | 6 ++++++
 t/test-lib.sh                 | 5 +++++
 7 files changed, 33 insertions(+), 4 deletions(-)
builtin/clone.c +1 -1
index 4d0cf065e4a..780c5807415 100644
@@ -1109,7 +1109,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
-		DEFAULT_REF_STORAGE, INIT_DB_QUIET);
+		default_ref_storage(), INIT_DB_QUIET);
 
 	if (real_git_dir)
 		git_dir = real_git_dir;
refs.c +3 -3
index bd1f3cc0e45..ba5239b8421 100644
@@ -1748,7 +1748,7 @@ struct ref_store *get_main_ref_store(struct repository *r)
 	r->refs_private = ref_store_init(r->gitdir,
 					 r->ref_storage_format ?
 						 r->ref_storage_format :
-						 DEFAULT_REF_STORAGE,
+						 default_ref_storage(),
 					 REF_STORE_ALL_CAPS);
 	if (getenv("GIT_DEBUG_REFS")) {
 		r->refs_private = debug_wrap(r->refs_private);
@@ -1807,7 +1807,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 		goto done;
 
 	/* assume that add_submodule_odb() has been called */
-	refs = ref_store_init(submodule_sb.buf, DEFAULT_REF_STORAGE, /* XXX */
+	refs = ref_store_init(submodule_sb.buf, default_ref_storage(),
 			      REF_STORE_READ | REF_STORE_ODB);
 	register_ref_store_map(&submodule_ref_stores, "submodule",
 			       refs, submodule);
@@ -1821,7 +1821,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
 
 struct ref_store *get_worktree_ref_store(const struct worktree *wt)
 {
-	const char *format = DEFAULT_REF_STORAGE; /* XXX */
+	const char *format = default_ref_storage();
 	struct ref_store *refs;
 	const char *id;
 
t/t1409-avoid-packing-refs.sh +6 -0
index be12fb63506..c6f78325563 100755
@@ -4,6 +4,12 @@ test_description='avoid rewriting packed-refs unnecessarily'
 
 . ./test-lib.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping pack-refs tests; incompatible with reftable'
+  test_done
+fi
+
 # Add an identifying mark to the packed-refs file header line. This
 # shouldn't upset readers, and it should be omitted if the file is
 # ever rewritten.
t/t1450-fsck.sh +6 -0
index 344a2aad82f..09669203249 100755
@@ -8,6 +8,12 @@ test_description='git fsck random collection of tests
 
 . ./test-lib.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping tests; incompatible with reftable'
+  test_done
+fi
+
 test_expect_success setup '
 	test_oid_init &&
 	git config gc.auto 0 &&
t/t3210-pack-refs.sh +6 -0
index f41b2afb996..edaef2c175a 100755
@@ -11,6 +11,12 @@ semantic is still the same.
 '
 . ./test-lib.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping pack-refs tests; incompatible with reftable'
+  test_done
+fi
+
 test_expect_success 'enable reflogs' '
 	git config core.logallrefupdates true
 '
t/t9903-bash-prompt.sh +6 -0
index ab5da2cabc4..5deaaf968b0 100755
@@ -7,6 +7,12 @@ test_description='test git-specific bash prompt functions'
 
 . ./lib-bash.sh
 
+if test_have_prereq REFTABLE
+then
+  skip_all='skipping tests; incompatible with reftable'
+  test_done
+fi
+
 . "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
 
 actual="$TRASH_DIRECTORY/actual"
t/test-lib.sh +5 -0
index dbc027ff267..3ce9b957b1b 100644
@@ -1504,6 +1504,11 @@ parisc* | hppa*)
 	;;
 esac
 
+if test -n "$GIT_TEST_REFTABLE"
+then
+  test_set_prereq REFTABLE
+fi
+
 ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
Signature · 1 line
gitgitgadget
#303 lore raw

PATCH v16 12/14 Add GIT_DEBUG_REFS debugging mechanism

In reply to Han-Wen Nienhuys via GitGitGadget

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

When set in the environment, GIT_DEBUG_REFS makes git print operations and results as they flow through the ref storage backend. This helps debug discrepancies between different ref backends.

---
 Makefile              |   1 +
 refs.c                |   3 +
 refs/debug.c          | 309 ++++++++++++++++++++++++++++++++++++++++++
 refs/refs-internal.h  |   6 +
 t/t0033-debug-refs.sh |  18 +++
 5 files changed, 337 insertions(+)
 create mode 100644 refs/debug.c
 create mode 100755 t/t0033-debug-refs.sh
Makefile +1 -0
index a39896191c7..1d09af7fd8d 100644
@@ -961,6 +961,7 @@ LIB_OBJS += rebase.o
 LIB_OBJS += ref-filter.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
+LIB_OBJS += refs/debug.o
 LIB_OBJS += refs/files-backend.o
 LIB_OBJS += refs/reftable-backend.o
 LIB_OBJS += refs/iterator.o
refs.c +3 -0
index 4409080dfd8..bd1f3cc0e45 100644
@@ -1750,6 +1750,9 @@ struct ref_store *get_main_ref_store(struct repository *r)
 						 r->ref_storage_format :
 						 DEFAULT_REF_STORAGE,
 					 REF_STORE_ALL_CAPS);
+	if (getenv("GIT_DEBUG_REFS")) {
+		r->refs_private = debug_wrap(r->refs_private);
+	}
 	return r->refs_private;
 }
 
refs/debug.c new file +309 -0

309 lines not on this page · raw message

refs/refs-internal.h +6 -0
index 7afe4c28310..5417623c86e 100644
@@ -713,4 +713,10 @@ struct ref_store {
 void base_ref_store_init(struct ref_store *refs,
 			 const struct ref_storage_be *be);
 
+/*
+ * Print out ref operations as they occur. Useful for debugging alternate ref
+ * backends.
+ */
+struct ref_store *debug_wrap(struct ref_store *store);
+
 #endif /* REFS_REFS_INTERNAL_H */
t/t0033-debug-refs.sh new file +18 -0
new file mode 100755
index 00000000000..96266a3ee3a
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Google LLC
+#
+
+test_description='cross-check reftable with files, using GIT_DEBUG_REFS output'
+
+. ./test-lib.sh
+
+test_expect_success 'GIT_DEBUG_REFS' '
+	git init --ref-storage=files files &&
+	git init --ref-storage=reftable reftable &&
+	(cd files && GIT_DEBUG_REFS=1 test_commit message file) > files.txt && 
+	(cd reftable && GIT_DEBUG_REFS=1 test_commit message file) > reftable.txt &&
+	test_cmp files.txt reftable.txt
+'
+
+test_done
Signature · 1 line
gitgitgadget
#300 lore raw

Re: [PATCH v16 11/14] Hookup unittests for the reftable library.

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 7 lines · Han-Wen Nienhuys via GitGitGadget

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

The unittests are under reftable/*_test.c, so all of the reftable code stays in one directory. They are called from t/helpers/test-reftable.c in t0031-reftable.sh

---

Hmph,

make: *** No rule to make target 't/helper/test-reftable.c', needed by 't/helper/test-reftable.o'. Stop.
make: *** Waiting for unfinished jobs....

Quoted 5 lines
 Makefile             | 17 ++++++++++++++++-
 t/helper/test-tool.c |  1 +
 t/helper/test-tool.h |  1 +
 t/t0031-reftable.sh  |  5 +++++
 4 files changed, 23 insertions(+), 1 deletion(-)

Forgot to add a source file?

#284 lore raw

Re: [PATCH v16 01/14] Write pseudorefs through ref backends.

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 5 lines · Han-Wen Nienhuys via GitGitGadget

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

Pseudorefs store transient data in in the repository. Examples are HEAD, CHERRY_PICK_HEAD, etc.

Quoted 7 lines

These refs have always been read through the ref backends, but they were written in a one-off routine that wrote an object ID or symref directly into .git/<pseudo_ref_name>.

This causes problems when introducing a new ref storage backend. To remedy this, extend the ref backend implementation with a write_pseudoref_fn and update_pseudoref_fn.

I think this patch is looking better now, I've had a couple more thoughts though

Quoted 10 lines

[...]

refs.h +1 -0
index e010f8aec28..4dad8f24914 100644
@@ -732,6 +732,17 @@ int update_ref(const char *msg, const char *refname,
 	       const struct object_id *new_oid, const struct object_id *old_oid,
 	       unsigned int flags, enum action_on_err onerr);
 
+/* Pseudorefs (eg. HEAD, CHERRY_PICK_HEAD) have a separate routines for updating

refs.h int update_ref(const char *msg, const char *refname, · line 745

Quoted 10 lines
refs.h +10 -0
+   and deletion as they cannot take part in normal transactional updates.
+   Pseudorefs should only be written for the main repository.
+*/
+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref,
+			 const struct object_id *oid,
+			 const struct object_id *old_oid, struct strbuf *err);
+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref,
+			  const struct object_id *old_oid);
+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid);
+

Do we need to expose these functions to the rest of git? In sequencer.c we write the pseudo ref REBASE_HEAD when a rebase stops due to conflicts. It is created with update_ref() and deleted with delete_ref(), is there a reason can't we just use update_ref()/delete ref() for the other pseudo refs?

Quoted 100 lines
refs.h +0 -0
 int parse_hide_refs_config(const char *var, const char *value, const char *);
 
 /*
refs/files-backend.c +89 -0
index 6516c7bc8c8..df7553f4cc3 100644
@@ -731,6 +731,115 @@ static int lock_raw_ref(struct files_ref_store *refs,
 	return ret;
 }
 
+static int files_write_pseudoref(struct ref_store *ref_store,
+				 const char *pseudoref,
+				 const struct object_id *oid,
+				 const struct object_id *old_oid,
+				 struct strbuf *err)
+{
+	struct files_ref_store *refs =
+		files_downcast(ref_store, REF_STORE_READ, "write_pseudoref");
+	int fd;
+	struct lock_file lock = LOCK_INIT;
+	struct strbuf filename = STRBUF_INIT;
+	struct strbuf buf = STRBUF_INIT;
+	int ret = -1;
+
+	if (!oid)
+		return 0;
+
+	strbuf_addf(&filename, "%s/%s", refs->gitdir, pseudoref);
+	fd = hold_lock_file_for_update_timeout(&lock, filename.buf, 0,
+					       get_files_ref_lock_timeout_ms());
+	if (fd < 0) {
+		strbuf_addf(err, _("could not open '%s' for writing: %s"),
+			    buf.buf, strerror(errno));
+		goto done;
+	}
+
+	if (old_oid) {
+		struct object_id actual_old_oid;
+
+		if (read_ref(pseudoref, &actual_old_oid)) {
+			if (!is_null_oid(old_oid)) {
+				strbuf_addf(err, _("could not read ref '%s'"),
+					    pseudoref);
+				rollback_lock_file(&lock);
+				goto done;
+			}
+		} else if (is_null_oid(old_oid)) {
+			strbuf_addf(err, _("ref '%s' already exists"),
+				    pseudoref);
+			rollback_lock_file(&lock);
+			goto done;
+		} else if (!oideq(&actual_old_oid, old_oid)) {
+			strbuf_addf(err,
+				    _("unexpected object ID when writing '%s'"),
+				    pseudoref);
+			rollback_lock_file(&lock);
+			goto done;
+		}
+	}
+
+	strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
+	if (write_in_full(fd, buf.buf, buf.len) < 0) {
+		strbuf_addf(err, _("could not write to '%s'"), filename.buf);
+		rollback_lock_file(&lock);
+		goto done;
+	}
+
+	commit_lock_file(&lock);
+	ret = 0;
+done:
+	strbuf_release(&buf);
+	strbuf_release(&filename);
+	return ret;
+}
+
+static int files_delete_pseudoref(struct ref_store *ref_store,
+				  const char *pseudoref,
+				  const struct object_id *old_oid)
+{
+	struct files_ref_store *refs =
+		files_downcast(ref_store, REF_STORE_READ, "delete_pseudoref");
+	struct strbuf filename = STRBUF_INIT;
+	int ret = -1;
+
+	strbuf_addf(&filename, "%s/%s", refs->gitdir, pseudoref);
+
+	if (old_oid && !is_null_oid(old_oid)) {
+		struct lock_file lock = LOCK_INIT;
+		int fd;
+		struct object_id actual_old_oid;
+
+		fd = hold_lock_file_for_update_timeout(
+			&lock, filename.buf, 0,
+			get_files_ref_lock_timeout_ms());
+		if (fd < 0) {
+			error_errno(_("could not open '%s' for writing"),
+				    filename.buf);
+			goto done;
+		}

refs/files-backend.c static int lock_raw_ref(struct files_ref_store *refs, · line 832

Quoted 10 lines
refs/files-backend.c +10 -0
+		if (read_ref(pseudoref, &actual_old_oid))
+			die(_("could not read ref '%s'"), pseudoref);
+		if (!oideq(&actual_old_oid, old_oid)) {
+			error(_("unexpected object ID when deleting '%s'"),
+			      pseudoref);
+			rollback_lock_file(&lock);
+			goto done;
+		}
+
+		unlink(filename.buf);

This could fail (especially on windows I think) and there are one or two places in sequencer.c where we do actually check that the ref has actually been deleted. Switching to calling delete_pseudoref() as it stands will break those checks. What are the error semantics of normal ref deletion where the file cannot be unlinked?

Best Wishes

Phillip

Quoted 111 lines
refs/files-backend.c +14 -1
+		rollback_lock_file(&lock);
+	} else {
+		unlink(filename.buf);
+	}
+	ret = 0;
+done:
+	strbuf_release(&filename);
+	return ret;
+}
+
 struct files_ref_iterator {
 	struct ref_iterator base;
 
@@ -3189,6 +3298,9 @@ struct ref_storage_be refs_be_files = {
 	files_rename_ref,
 	files_copy_ref,
 
+	files_write_pseudoref,
+	files_delete_pseudoref,
+
 	files_ref_iterator_begin,
 	files_read_raw_ref,
 
@@ -3198,5 +3310,5 @@ struct ref_storage_be refs_be_files = {
 	files_reflog_exists,
 	files_create_reflog,
 	files_delete_reflog,
-	files_reflog_expire
+	files_reflog_expire,
 };
refs/packed-backend.c +20 -1
index 4458a0f69cc..08e8253a893 100644
@@ -1590,6 +1590,22 @@ static int packed_copy_ref(struct ref_store *ref_store,
 	BUG("packed reference store does not support copying references");
 }
 
+static int packed_write_pseudoref(struct ref_store *ref_store,
+				  const char *pseudoref,
+				  const struct object_id *oid,
+				  const struct object_id *old_oid,
+				  struct strbuf *err)
+{
+	BUG("packed reference store does not support writing pseudo-references");
+}
+
+static int packed_delete_pseudoref(struct ref_store *ref_store,
+				   const char *pseudoref,
+				   const struct object_id *old_oid)
+{
+	BUG("packed reference store does not support deleting pseudo-references");
+}
+
 static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
 {
 	return empty_ref_iterator_begin();
@@ -1656,6 +1672,9 @@ struct ref_storage_be refs_be_packed = {
 	packed_rename_ref,
 	packed_copy_ref,
 
+	packed_write_pseudoref,
+	packed_delete_pseudoref,
+
 	packed_ref_iterator_begin,
 	packed_read_raw_ref,
 
@@ -1665,5 +1684,5 @@ struct ref_storage_be refs_be_packed = {
 	packed_reflog_exists,
 	packed_create_reflog,
 	packed_delete_reflog,
-	packed_reflog_expire
+	packed_reflog_expire,
 };
refs/refs-internal.h +18 -0
index 4271362d264..59b053d53a2 100644
@@ -556,6 +556,21 @@ typedef int copy_ref_fn(struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
 
+typedef int write_pseudoref_fn(struct ref_store *ref_store,
+			       const char *pseudoref,
+			       const struct object_id *oid,
+			       const struct object_id *old_oid,
+			       struct strbuf *err);
+
+/*
+ * Deletes a pseudoref. Deletion always succeeds (even if the pseudoref doesn't
+ * exist.), except if old_oid is specified. If it is, it can fail due to lock
+ * failure, failure reading the old OID, or an OID mismatch
+ */
+typedef int delete_pseudoref_fn(struct ref_store *ref_store,
+				const char *pseudoref,
+				const struct object_id *old_oid);
+
 /*
  * Iterate over the references in `ref_store` whose names start with
  * `prefix`. `prefix` is matched as a literal string, without regard
@@ -655,6 +670,9 @@ struct ref_storage_be {
 	rename_ref_fn *rename_ref;
 	copy_ref_fn *copy_ref;
 
+	write_pseudoref_fn *write_pseudoref;
+	delete_pseudoref_fn *delete_pseudoref;
+
 	ref_iterator_begin_fn *iterator_begin;
 	read_raw_ref_fn *read_raw_ref;
 
#286 lore raw

Re: [PATCH v16 02/14] Make refs_ref_exists public

In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 13 lines · Han-Wen Nienhuys via GitGitGadget

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

---
 refs.c | 2 +-
 refs.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +0 -0
index 12908066b13..812fee47108 100644

refs.c int read_ref(const char *refname, struct object_id *oid) · line 317

Quoted 8 lines
refs.c +1 -1
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct object_id *oid)
 	return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
 }
 
-static int refs_ref_exists(struct ref_store *refs, const char *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
 {
 	return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
 }

It is a shame that ref_exists() does not take a struct repository. The code in the following patches would be pleasanter if we could write
ref_exists(r, ref)
rather than
refs_ref_exists(get_main_ref_store(r), ref)
all the time

Maybe we should think about updating the existing callers to ref_exists() to pass a struct repository.

The existing code is inconsistent about its repository handling - we create the refs with update_ref() which operates on the main repository but when checking their existence and deleting them we use a path which depends on the repository. I've realized now the answer to my question about using delete_ref() in my reply to the previous patch - it does not take a repository - maybe it should along with update_ref() but that might be more work than you want to do though.

Best Wishes

Phillip

Quoted 14 lines
refs.h +2 -0
index 4dad8f24914..7aaa1226551 100644
@@ -105,6 +105,8 @@ int refs_verify_refname_available(struct ref_store *refs,
 				  const struct string_list *skip,
 				  struct strbuf *err);
 
+int refs_ref_exists(struct ref_store *refs, const char *refname);
+
 int ref_exists(const char *refname);
 
 int should_autocreate_reflog(const char *refname);
#301 lore raw

PATCH 1/1 Fixup! Add t/helper/test-reftable.c

In reply to Junio C Hamano

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

---
 t/helper/test-reftable.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 t/helper/test-reftable.c
t/helper/test-reftable.c new file +15 -0
new file mode 100644
index 0000000000..95d18ba1fa
@@ -0,0 +1,15 @@
+#include "reftable/reftable-tests.h"
+#include "test-tool.h"
+
+int cmd__reftable(int argc, const char **argv)
+{
+	block_test_main(argc, argv);
+	merged_test_main(argc, argv);
+	record_test_main(argc, argv);
+	refname_test_main(argc, argv);
+	reftable_test_main(argc, argv);
+	slice_test_main(argc, argv);
+	stack_test_main(argc, argv);
+	tree_test_main(argc, argv);
+	return 0;
+}
Signature · 1 line
2.27.0.278.ge193c7cf3a9-goog
#302 lore raw

Re: [PATCH] Fixup! Add t/helper/test-reftable.c

Quoted 1 line · [email protected] writes:

Subject: Re: [PATCH] Fixup! Add t/helper/test-reftable.c

I do not see a step with "Add t/helper/test-reftable.c" as its subject; is this supposed to be squashed into "Hookup unittests for the reftable library." or somewhere else?

I also notice, with or without this squashed in, 775c540f (Hookup unittests for the reftable library., 2020-06-05) does not compile cleanly. Since you are relatively new to this project (even though you are very experienced and capable), I probably should let you know that in this project each patch in a series is expected to cleanly compile and pass the testsuite, not just the endstate.

Thanks.

#306 lore raw
In reply to Han-Wen Nienhuys via GitGitGadget
Quoted 6 lines · Han-Wen Nienhuys via GitGitGadget

This adds the reftable library, and hooks it up as a ref backend. Based on hn/refs-cleanup.

Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1

Summary 20479 tests pass 1299 tests fail

Thanks.

Quoted 4 lines

Some issues:

* many tests inspect .git/{logs,heads}/ directly.
* worktrees broken.

Are these with or without GIT_TEST_REFTABLE=1?

The patches (with the fix-up I saw today) applied on top of your refs clean-up topic seems to pass the tests (without setting GIT_TEST_REFTABLE) as the whole, but when merged at the tip of 'pu', many tests fail (please see [*1*], for examples). I suspect that there may be some topics in flight that add more direct access to the .git/refs/ hierarchy, and/or there may be stupid mismerges (I'd especially appreciate it if you can spot any).

[Reference]

*1* https://travis-ci.org/github/git/git/builds/696638569

#307 lore raw
Quoted 6 lines · Junio C Hamano
Quoted 4 lines

Some issues:

* many tests inspect .git/{logs,heads}/ directly.
* worktrees broken.

Are these with or without GIT_TEST_REFTABLE=1?

worktrees are broken with reftable, because HEAD is per-worktree ref, and I haven't decided how to handle those for reftable.

Quoted 7 lines

The patches (with the fix-up I saw today) applied on top of your refs clean-up topic seems to pass the tests (without setting GIT_TEST_REFTABLE) as the whole, but when merged at the tip of 'pu', many tests fail (please see [*1*], for examples). I suspect that there may be some topics in flight that add more direct access to the .git/refs/ hierarchy, and/or there may be stupid mismerges (I'd especially appreciate it if you can spot any).

Curious; I'll have a look.

There is also:

reftable/stack_test.c:27:7: error: incompatible pointer types initializing 'PREC_DIR *' with an expression of type 'DIR *' [-Werror,-Wincompatible-pointer-types]

        DIR *dir = fdopendir(fd);

on OSX. What is the proper dialect for reading out a directory within the git source code? opendir and fdopendir are POSIX, so I'm surprised this fails.

Signature · 1 line
Han-Wen Nienhuys - [email protected] - http://www.xs4all.nl/~hanwen
#313 lore raw
In reply to Junio C Hamano
Quoted 5 lines · Junio C Hamano

he patches (with the fix-up I saw today) applied on top of your refs clean-up topic seems to pass the tests (without setting GIT_TEST_REFTABLE) as the whole, but when merged at the tip of 'pu', many tests fail (please see [*1*], for examples). I suspect that

they all say

warning: unable to upgrade repository format from 0 to 1:
fatal: unable to upgrade repository format to support XXX

maybe a mismerge in the code in init-db.c that deals with the repository format version?

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

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

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
#308 lore raw
In reply to Han-Wen Nienhuys
Quoted 10 lines · Han-Wen Nienhuys

There is also:

reftable/stack_test.c:27:7: error: incompatible pointer types initializing 'PREC_DIR *' with an expression of type 'DIR *' [-Werror,-Wincompatible-pointer-types]

        DIR *dir = fdopendir(fd);

on OSX. What is the proper dialect for reading out a directory within the git source code? opendir and fdopendir are POSIX, so I'm surprised this fails.

I am reasonably sure we use opendir() to iterate over existing files and subdirectories in a directory (e.g. in the codepaths to enumerate loose object files in .git/objects/??/ directories).

I do not offhand know we also use fdopendir() elsewhere. I strongly suspect we do not. Perhaps some platforms do POSIX.1-2001 but not ready for POSIX.1-2008 or something silly like that?

#309 lore raw
Quoted 3 lines · Junio C Hamano

I do not offhand know we also use fdopendir() elsewhere. I strongly suspect we do not. Perhaps some platforms do POSIX.1-2001 but not ready for POSIX.1-2008 or something silly like that?

Searching "macos fdopendir" gives a few hits from other projects, all saying that before macOS 10.10 they did not have fdopendir() and their code failed to build on macs.

#287 lore raw

Re: [PATCH v16 02/14] Make refs_ref_exists public

In reply to Phillip Wood
Quoted 18 lines · Phillip Wood
Quoted 16 lines · Han-Wen Nienhuys via GitGitGadget

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

---
 refs.c | 2 +-
 refs.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +0 -0
index 12908066b13..812fee47108 100644
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct object_id *oid)
      return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
 }
 
Quoted 7 lines

refs.c int read_ref(const char *refname, struct object_id *oid) · line 317

Quoted 5 lines
refs.c +1 -1
-static int refs_ref_exists(struct ref_store *refs, const char *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
 {
      return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
 }

It is a shame that ref_exists() does not take a struct repository. The

I'm trying to follow the pattern that the rest of the code establishes; you're right that the code isn't very consistent (the fact that it uses unlink() rather than go through the ref store in the first place is an indication of that). I want to avoid starting a general cleanup tour of the code base, the reftable patch is hard enough to pull off without.

Quoted 7 lines

The existing code is inconsistent about its repository handling - we create the refs with update_ref() which operates on the main repository but when checking their existence and deleting them we use a path which depends on the repository. I've realized now the answer to my question about using delete_ref() in my reply to the previous patch - it does not take a repository - maybe it should along with update_ref() but that might be more work than you want to do though.

Why do they take repository arguments anyway? Is it because rebase/cherry-pick supports recursion into submodules?

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

Hi Junio,

Quoted 13 lines · Junio C Hamano
Quoted 10 lines · Han-Wen Nienhuys

There is also:

reftable/stack_test.c:27:7: error: incompatible pointer types initializing 'PREC_DIR *' with an expression of type 'DIR *' [-Werror,-Wincompatible-pointer-types]

        DIR *dir = fdopendir(fd);

on OSX. What is the proper dialect for reading out a directory within the git source code? opendir and fdopendir are POSIX, so I'm surprised this fails.

Quoted 7 lines

I am reasonably sure we use opendir() to iterate over existing files and subdirectories in a directory (e.g. in the codepaths to enumerate loose object files in .git/objects/??/ directories).

I do not offhand know we also use fdopendir() elsewhere. I strongly suspect we do not. Perhaps some platforms do POSIX.1-2001 but not ready for POSIX.1-2008 or something silly like that?

We don't. We also do not use unlinkat(). And we generally use remove_dir_recursively() instead of implementing a separate version of it that only handles one directory level ;-)

This is what I needed in Git for Windows' shears/pu branch to make it compile again:

-- snipsnap --
Subject: [PATCH] fixup??? Add reftable library

Rather than causing a build failure e.g. on Windows, where fdopendir() and unlinkat() just is not a thing, and rather than re-inventing remove_dir_recursively(), let's just use the perfectly fine library function we already have for that purpose.

---
 reftable/stack_test.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)
reftable/stack_test.c +6 -9
index ffcc1e42819a..4dadc39bf45f 100644
@@ -17,23 +17,20 @@ license that can be found in the LICENSE file or at
 #include "reftable.h"
 #include "test_framework.h"
 #include "reftable-tests.h"
+#include "dir.h"
 
 #include <sys/types.h>
 #include <dirent.h>
 
 static void clear_dir(const char *dirname)
 {
-	int fd = open(dirname, O_DIRECTORY, 0);
-	DIR *dir = fdopendir(fd);
-	struct dirent *ent = NULL;
+	struct strbuf buf = STRBUF_INIT;
 
-	assert(fd >= 0);
+	strbuf_addstr(&buf, dirname);
+	if (remove_dir_recursively(&buf, 0) < 0)
+		die("Could not remove '%s'", dirname);
 
-	while ((ent = readdir(dir)) != NULL) {
-		unlinkat(fd, ent->d_name, 0);
-	}
-	closedir(dir);
-	rmdir(dirname);
+	strbuf_release(&buf);
 }
 
 static void test_read_file(void)

--
2.27.0.windows.1

#311 lore raw
Quoted 8 lines · Johannes Schindelin
Quoted 7 lines

I am reasonably sure we use opendir() to iterate over existing files and subdirectories in a directory (e.g. in the codepaths to enumerate loose object files in .git/objects/??/ directories).

I do not offhand know we also use fdopendir() elsewhere. I strongly suspect we do not. Perhaps some platforms do POSIX.1-2001 but not ready for POSIX.1-2008 or something silly like that?

Quoted 6 lines

We don't. We also do not use unlinkat(). And we generally use remove_dir_recursively() instead of implementing a separate version of it that only handles one directory level ;-)

This is what I needed in Git for Windows' shears/pu branch to make it compile again:

Thanks Dscho. Last time I folded a fix into github.com/google/reftable, you seemed quite upset. How should I proceed with this fixup?

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

Hi Han-Wen,

Quoted 15 lines · Han-Wen Nienhuys
Quoted 12 lines · Johannes Schindelin
Quoted 7 lines

I am reasonably sure we use opendir() to iterate over existing files and subdirectories in a directory (e.g. in the codepaths to enumerate loose object files in .git/objects/??/ directories).

I do not offhand know we also use fdopendir() elsewhere. I strongly suspect we do not. Perhaps some platforms do POSIX.1-2001 but not ready for POSIX.1-2008 or something silly like that?

We don't. We also do not use unlinkat(). And we generally use remove_dir_recursively() instead of implementing a separate version of it that only handles one directory level ;-)

Quoted 5 lines
Quoted 2 lines

This is what I needed in Git for Windows' shears/pu branch to make it compile again:

Thanks Dscho. Last time I folded a fix into github.com/google/reftable, you seemed quite upset.

I was not upset. I just pointed out how unnecessarily difficult and cumbersome it is to have this ever-growing patch, with the _actual_ commit history completely elsewhere.

My assessment has not changed, and the situation has not changed, and does not look like it will change, so I'll just keep quiet from now on.

:-)

Quoted 1 line

How should I proceed with this fixup?

That's for you to decide, not for me. My suggestions in the past seem not to have pleased you, so in the interest of keeping our relationship friendly, I will refrain from adding more suggestions.

Ciao,
Johannes

#288 lore raw

Re: [PATCH v16 02/14] Make refs_ref_exists public

In reply to Han-Wen Nienhuys
Quoted 27 lines · Han-Wen Nienhuys
Quoted 18 lines · Phillip Wood
Quoted 16 lines · Han-Wen Nienhuys via GitGitGadget

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

---
  refs.c | 2 +-
  refs.h | 2 ++
  2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +0 -0
index 12908066b13..812fee47108 100644
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct object_id *oid)
       return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
  }
 
Quoted 7 lines

refs.c int read_ref(const char *refname, struct object_id *oid) · line 317

Quoted 5 lines
refs.c +1 -1
-static int refs_ref_exists(struct ref_store *refs, const char *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
  {
       return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
  }

It is a shame that ref_exists() does not take a struct repository. The

Quoted 6 lines

I'm trying to follow the pattern that the rest of the code establishes; you're right that the code isn't very consistent (the fact that it uses unlink() rather than go through the ref store in the first place is an indication of that). I want to avoid starting a general cleanup tour of the code base, the reftable patch is hard enough to pull off without.

Sure

Quoted 10 lines
Quoted 7 lines

The existing code is inconsistent about its repository handling - we create the refs with update_ref() which operates on the main repository but when checking their existence and deleting them we use a path which depends on the repository. I've realized now the answer to my question about using delete_ref() in my reply to the previous patch - it does not take a repository - maybe it should along with update_ref() but that might be more work than you want to do though.

Why do they take repository arguments anyway? Is it because rebase/cherry-pick supports recursion into submodules?

It was a stepping stone towards that, the git_path mechanism that is used to create git_path_cherry_pick_head() etc was changed to take a struct repository so it could support submodules without forking a separate process. However are still plenty of places where the sequencer code assumes a single repository (it calls update_ref(), delete_ref(), commit_tree_extended(), ...) and the two contributors who did a lot of that work have moved on. With that in mind perhaps we'd be better off just using ref_exists() and delete_ref() in this conversion. The call sites will be easy enough to fixup if those functions are converted to take a struct repository in the future and the result of this patch series will be nicer. I've cc'd dscho and Junio to see what they think.

Best Wishes

Phillip

#289 lore raw

Re: [PATCH v16 02/14] Make refs_ref_exists public

Quoted 52 lines · Phillip Wood
Quoted 31 lines · Han-Wen Nienhuys
Quoted 19 lines · Phillip Wood
Quoted 17 lines · Han-Wen Nienhuys via GitGitGadget

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

---
  refs.c | 2 +-
  refs.h | 2 ++

  2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +0 -0
index 12908066b13..812fee47108 100644
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct
 object_id *oid)
        return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
   }
 
Quoted 9 lines
Quoted 7 lines
refs.c +1 -1
-static int refs_ref_exists(struct ref_store *refs, const char
 *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
   {

       return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
  }

It is a shame that ref_exists() does not take a struct repository. The

Quoted 6 lines

I'm trying to follow the pattern that the rest of the code establishes; you're right that the code isn't very consistent (the fact that it uses unlink() rather than go through the ref store in the first place is an indication of that). I want to avoid starting a general cleanup tour of the code base, the reftable patch is hard enough to pull off without.

Sure

Quoted 10 lines
Quoted 7 lines

The existing code is inconsistent about its repository handling - we create the refs with update_ref() which operates on the main repository but when checking their existence and deleting them we use a path which depends on the repository. I've realized now the answer to my question about using delete_ref() in my reply to the previous patch - it does not take a repository - maybe it should along with update_ref() but that might be more work than you want to do though.

Why do they take repository arguments anyway? Is it because rebase/cherry-pick supports recursion into submodules?

Quoted 11 lines

It was a stepping stone towards that, the git_path mechanism that is used to create git_path_cherry_pick_head() etc was changed to take a struct repository so it could support submodules without forking a separate process. However are still plenty of places where the sequencer code assumes a single repository (it calls update_ref(), delete_ref(), commit_tree_extended(), ...) and the two contributors who did a lot of that work have moved on. With that in mind perhaps we'd be better off just using ref_exists() and delete_ref() in this conversion. The call sites will be easy enough to fixup if those functions are converted to take a struct repository in the future and the result of this patch series will be nicer. I've cc'd dscho and Junio to see what they think.

In the end I put some patches together that change delete_ref(), update_ref() and ref_exists() to take a struct repository*. I'll clean them up and post them next week. Hopefully that will mean that this series can then use those functions when converting unlink() etc which will avoid having to expose a separate api for pseudo refs.

Best Wishes

Phillip

#290 lore raw

Re: [PATCH v16 02/14] Make refs_ref_exists public

Quoted 12 lines · Phillip Wood
Quoted 11 lines

It was a stepping stone towards that, the git_path mechanism that is used to create git_path_cherry_pick_head() etc was changed to take a struct repository so it could support submodules without forking a separate process. However are still plenty of places where the sequencer code assumes a single repository (it calls update_ref(), delete_ref(), commit_tree_extended(), ...) and the two contributors who did a lot of that work have moved on. With that in mind perhaps we'd be better off just using ref_exists() and delete_ref() in this conversion. The call sites will be easy enough to fixup if those functions are converted to take a struct repository in the future and the result of this patch series will be nicer. I've cc'd dscho and Junio to see what they think.

Quoted 5 lines

In the end I put some patches together that change delete_ref(), update_ref() and ref_exists() to take a struct repository*. I'll clean them up and post them next week. Hopefully that will mean that this series can then use those functions when converting unlink() etc which will avoid having to expose a separate api for pseudo refs.

Sounds good. Can you CC me on the patches?

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

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

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

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

PATCH v17 1/17 lib-t6000.sh: write tag using git-update-ref

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 t/lib-t6000.sh | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
t/lib-t6000.sh +2 -3
index b0ed4767e32..fba6778ca35 100644
@@ -1,7 +1,5 @@
 : included from 6002 and others
 
-mkdir -p .git/refs/tags
-
 >sed.script
 
 # Answer the sha1 has associated with the tag. The tag must exist under refs/tags
@@ -26,7 +24,8 @@ save_tag () {
 	_tag=$1
 	test -n "$_tag" || error "usage: save_tag tag commit-args ..."
 	shift 1
-	"$@" >".git/refs/tags/$_tag"
+
+	git update-ref "refs/tags/$_tag" $("$@")
 
 	echo "s/$(tag $_tag)/$_tag/g" >sed.script.tmp
 	cat sed.script >>sed.script.tmp
Signature · 1 line
gitgitgadget
#316 lore raw

PATCH v17 2/17 checkout: add '\n' to reflog message

In reply to Han-Wen Nienhuys via GitGitGadget

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

Reftable precisely reproduces the given message. This leads to differences, because the files backend implicitly adds a trailing '\n' to all messages.

---
 builtin/checkout.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
builtin/checkout.c +3 -2
index af849c644fe..bb11fcc4e99 100644
@@ -884,8 +884,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 
 	reflog_msg = getenv("GIT_REFLOG_ACTION");
 	if (!reflog_msg)
-		strbuf_addf(&msg, "checkout: moving from %s to %s",
-			old_desc ? old_desc : "(invalid)", new_branch_info->name);
+		strbuf_addf(&msg, "checkout: moving from %s to %s\n",
+			    old_desc ? old_desc : "(invalid)",
+			    new_branch_info->name);
 	else
 		strbuf_insertstr(&msg, 0, reflog_msg);
 
Signature · 1 line
gitgitgadget
#318 lore raw

PATCH v17 4/17 Make refs_ref_exists public

In reply to Han-Wen Nienhuys via GitGitGadget

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

---
 refs.c | 2 +-
 refs.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
refs.c +1 -1
index 12908066b13..812fee47108 100644
@@ -311,7 +311,7 @@ int read_ref(const char *refname, struct object_id *oid)
 	return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
 }
 
-static int refs_ref_exists(struct ref_store *refs, const char *refname)
+int refs_ref_exists(struct ref_store *refs, const char *refname)
 {
 	return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL);
 }
refs.h +2 -0
index 4dad8f24914..7aaa1226551 100644
@@ -105,6 +105,8 @@ int refs_verify_refname_available(struct ref_store *refs,
 				  const struct string_list *skip,
 				  struct strbuf *err);
 
+int refs_ref_exists(struct ref_store *refs, const char *refname);
+
 int ref_exists(const char *refname);
 
 int should_autocreate_reflog(const char *refname);
Signature · 1 line
gitgitgadget
#314 lore raw

v17 cover Reftable support git-core

In reply to Han-Wen Nienhuys via GitGitGadget

This adds the reftable library, and hooks it up as a ref backend. Based on hn/refs-cleanup.

Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1

Summary 20816 tests pass 1122 tests fail

Some issues:

* many tests inspect .git/{logs,heads}/ directly.
* worktrees broken.

v20

* various reflog fixes; @{-1} now works
* more debug output for GIT_DEBUG_REFS
* reftable's slice class is now equivalent to strbuf
* add test-tool dump-reftable.

Han-Wen Nienhuys (16):
lib-t6000.sh: write tag using git-update-ref
checkout: add '\n' to reflog message
Write pseudorefs through ref backends.
Make refs_ref_exists public
Treat BISECT_HEAD as a pseudo ref
Treat CHERRY_PICK_HEAD as a pseudo ref
Treat REVERT_HEAD as a pseudo ref
Move REF_LOG_ONLY to refs-internal.h
Iterate over the "refs/" namespace in for_each_[raw]ref Add .gitattributes for the reftable/ directory Add reftable library
Reftable support for git-core
Hookup unittests for the reftable library.
Add GIT_DEBUG_REFS debugging mechanism
Add reftable testing infrastructure
Add "test-tool dump-reftable" command.

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

 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   47 +-
 builtin/bisect--helper.c                      |    3 +-
 builtin/checkout.c                            |    5 +-
 builtin/clone.c                               |    3 +-
 builtin/commit.c                              |   34 +-
 builtin/init-db.c                             |   56 +-
 builtin/merge.c                               |    2 +-
 cache.h                                       |    6 +-
 config.mak.uname                              |    2 +-
 contrib/buildsystems/Generators/Vcxproj.pm    |   11 +-
 git-bisect.sh                                 |    4 +-
 path.c                                        |    2 -
 path.h                                        |    9 +-
 refs.c                                        |  157 +-
 refs.h                                        |   16 +
 refs/debug.c                                  |  358 +++++
 refs/files-backend.c                          |  121 +-
 refs/packed-backend.c                         |   21 +-
 refs/refs-internal.h                          |   32 +
 refs/reftable-backend.c                       | 1327 +++++++++++++++++
 reftable/.gitattributes                       |    1 +
 reftable/LICENSE                              |   31 +
 reftable/README.md                            |   11 +
 reftable/VERSION                              |    1 +
 reftable/basics.c                             |  215 +++
 reftable/basics.h                             |   53 +
 reftable/block.c                              |  437 ++++++
 reftable/block.h                              |  129 ++
 reftable/block_test.c                         |  157 ++
 reftable/compat.c                             |   97 ++
 reftable/compat.h                             |   48 +
 reftable/constants.h                          |   21 +
 reftable/dump.c                               |  212 +++
 reftable/file.c                               |   95 ++
 reftable/iter.c                               |  242 +++
 reftable/iter.h                               |   72 +
 reftable/merged.c                             |  320 ++++
 reftable/merged.h                             |   39 +
 reftable/merged_test.c                        |  273 ++++
 reftable/pq.c                                 |  113 ++
 reftable/pq.h                                 |   34 +
 reftable/reader.c                             |  742 +++++++++
 reftable/reader.h                             |   65 +
 reftable/record.c                             | 1118 ++++++++++++++
 reftable/record.h                             |  128 ++
 reftable/record_test.c                        |  403 +++++
 reftable/refname.c                            |  211 +++
 reftable/refname.h                            |   38 +
 reftable/refname_test.c                       |   99 ++
 reftable/reftable-tests.h                     |   22 +
 reftable/reftable.c                           |   90 ++
 reftable/reftable.h                           |  571 +++++++
 reftable/reftable_test.c                      |  631 ++++++++
 reftable/slice.c                              |  217 +++
 reftable/slice.h                              |   85 ++
 reftable/slice_test.c                         |   39 +
 reftable/stack.c                              | 1214 +++++++++++++++
 reftable/stack.h                              |   48 +
 reftable/stack_test.c                         |  787 ++++++++++
 reftable/system.h                             |   55 +
 reftable/test_framework.c                     |   69 +
 reftable/test_framework.h                     |   64 +
 reftable/tree.c                               |   63 +
 reftable/tree.h                               |   34 +
 reftable/tree_test.c                          |   62 +
 reftable/update.sh                            |   22 +
 reftable/writer.c                             |  661 ++++++++
 reftable/writer.h                             |   60 +
 reftable/zlib-compat.c                        |   92 ++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 sequencer.c                                   |   56 +-
 setup.c                                       |   12 +-
 t/helper/test-reftable.c                      |   20 +
 t/helper/test-tool.c                          |    2 +
 t/helper/test-tool.h                          |    2 +
 t/lib-t6000.sh                                |    5 +-
 t/t0031-reftable.sh                           |  160 ++
 t/t0033-debug-refs.sh                         |   18 +
 t/t1409-avoid-packing-refs.sh                 |    6 +
 t/t1450-fsck.sh                               |    6 +
 t/t3210-pack-refs.sh                          |    6 +
 t/t9903-bash-prompt.sh                        |    6 +
 t/test-lib.sh                                 |    5 +
 wt-status.c                                   |    6 +-
 86 files changed, 12629 insertions(+), 200 deletions(-)
 create mode 100644 refs/debug.c
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/.gitattributes
 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/compat.c
 create mode 100644 reftable/compat.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/refname.c
 create mode 100644 reftable/refname.h
 create mode 100644 reftable/refname_test.c
 create mode 100644 reftable/reftable-tests.h
 create mode 100644 reftable/reftable.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/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 100755 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100644 t/helper/test-reftable.c
 create mode 100755 t/t0031-reftable.sh
 create mode 100755 t/t0033-debug-refs.sh

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

Range-diff vs v16:

  -:  ----------- >  1:  8304c3d6379 lib-t6000.sh: write tag using git-update-ref
  -:  ----------- >  2:  4012d801e3c checkout: add '\n' to reflog message
  1:  4a1f600fb85 =  3:  95a6a1d968e Write pseudorefs through ref backends.
  2:  a4a67ce9635 =  4:  1f8865f4b3e Make refs_ref_exists public
  3:  ecd6591d86f =  5:  7f376a76d84 Treat BISECT_HEAD as a pseudo ref
  4:  7c31727de69 =  6:  959c69b5ee4 Treat CHERRY_PICK_HEAD as a pseudo ref
  5:  fbdcee5208b =  7:  3f18475d0d3 Treat REVERT_HEAD as a pseudo ref
  6:  d8801367f7d =  8:  4981e5395c6 Move REF_LOG_ONLY to refs-internal.h
  7:  556d57610cc =  9:  f452c48ae44 Iterate over the "refs/" namespace in for_each_[raw]ref
  8:  78cb1930172 = 10:  1fa68d5d34f Add .gitattributes for the reftable/ directory
  9:  0bc28ac610f ! 11:  86646c834c2 Add reftable library
     @@ reftable/README.md (new)
      
       ## reftable/VERSION (new) ##
      @@
     -+0c164275ea8f8aa7d099f7425ddcef1affe137e9 C: compile framework with Git options
     ++2c91c4b305dcbf6500c0806bb1a7fbcfc668510c C: include system.h in compat.h
      
       ## reftable/basics.c (new) ##
      @@
     @@ reftable/block.c (new)
      +
      +	w->next += n;
      +
     -+	slice_copy(&w->last_key, key);
     ++	slice_reset(&w->last_key);
     ++	slice_addbuf(&w->last_key, key);
      +	w->entries++;
      +	return 0;
      +}
     @@ reftable/block.c (new)
      +
      +	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
      +		int block_header_skip = 4 + w->header_off;
     -+		struct slice compressed = SLICE_INIT;
     ++		byte *compressed = NULL;
      +		int zresult = 0;
      +		uLongf src_len = w->next - block_header_skip;
     -+		slice_resize(&compressed, src_len);
     ++		size_t dest_cap = src_len;
      +
     ++		compressed = reftable_malloc(dest_cap);
      +		while (1) {
     -+			uLongf dest_len = compressed.len;
     ++			uLongf out_dest_len = dest_cap;
      +
     -+			zresult = compress2(compressed.buf, &dest_len,
     ++			zresult = compress2(compressed, &out_dest_len,
      +					    w->buf + block_header_skip, src_len,
      +					    9);
      +			if (zresult == Z_BUF_ERROR) {
     -+				slice_resize(&compressed, 2 * compressed.len);
     ++				dest_cap *= 2;
     ++				compressed =
     ++					reftable_realloc(compressed, dest_cap);
      +				continue;
      +			}
      +
      +			if (Z_OK != zresult) {
     -+				slice_release(&compressed);
     ++				reftable_free(compressed);
      +				return REFTABLE_ZLIB_ERROR;
      +			}
      +
     -+			memcpy(w->buf + block_header_skip, compressed.buf,
     -+			       dest_len);
     -+			w->next = dest_len + block_header_skip;
     -+			slice_release(&compressed);
     ++			memcpy(w->buf + block_header_skip, compressed,
     ++			       out_dest_len);
     ++			w->next = out_dest_len + block_header_skip;
     ++			reftable_free(compressed);
      +			break;
      +		}
      +	}
     @@ reftable/block.c (new)
      +		return REFTABLE_FORMAT_ERROR;
      +
      +	if (typ == BLOCK_TYPE_LOG) {
     -+		struct slice uncompressed = SLICE_INIT;
      +		int block_header_skip = 4 + header_off;
      +		uLongf dst_len = sz - block_header_skip; /* total size of dest
      +							    buffer. */
      +		uLongf src_len = block->len - block_header_skip;
     -+
      +		/* Log blocks specify the *uncompressed* size in their header.
      +		 */
     -+		slice_resize(&uncompressed, sz);
     ++		byte *uncompressed = reftable_malloc(sz);
      +
      +		/* Copy over the block header verbatim. It's not compressed. */
     -+		memcpy(uncompressed.buf, block->data, block_header_skip);
     ++		memcpy(uncompressed, block->data, block_header_skip);
      +
      +		/* Uncompress */
      +		if (Z_OK != uncompress_return_consumed(
     -+				    uncompressed.buf + block_header_skip,
     -+				    &dst_len, block->data + block_header_skip,
     ++				    uncompressed + block_header_skip, &dst_len,
     ++				    block->data + block_header_skip,
      +				    &src_len)) {
     -+			slice_release(&uncompressed);
     ++			reftable_free(uncompressed);
      +			return REFTABLE_ZLIB_ERROR;
      +		}
      +
     @@ reftable/block.c (new)
      +
      +		/* We're done with the input data. */
      +		reftable_block_done(block);
     -+		block->data = uncompressed.buf;
     ++		block->data = uncompressed;
      +		block->len = sz;
      +		block->source = malloc_block_source();
      +		full_block_size = src_len + block_header_skip;
     @@ reftable/block.c (new)
      +void block_reader_start(struct block_reader *br, struct block_iter *it)
      +{
      +	it->br = br;
     -+	slice_resize(&it->last_key, 0);
     ++	slice_reset(&it->last_key);
      +	it->next_off = br->header_off + 4;
      +}
      +
     @@ reftable/block.c (new)
      +{
      +	dest->br = src->br;
      +	dest->next_off = src->next_off;
     -+	slice_copy(&dest->last_key, &src->last_key);
     ++	slice_reset(&dest->last_key);
     ++	slice_addbuf(&dest->last_key, &src->last_key);
      +}
      +
      +int block_iter_next(struct block_iter *it, struct reftable_record *rec)
     @@ reftable/block.c (new)
      +		return -1;
      +	slice_consume(&in, n);
      +
     -+	slice_copy(&it->last_key, &key);
     ++	slice_reset(&it->last_key);
     ++	slice_addbuf(&it->last_key, &key);
      +	it->next_off += start.len - in.len;
      +	slice_release(&key);
      +	return 0;
     @@ reftable/block_test.c (new)
      +
      +	for (i = 0; i < N; i++) {
      +		struct block_iter it = { .last_key = SLICE_INIT };
     -+		slice_set_string(&want, names[i]);
     ++		slice_reset(&want);
     ++		slice_addstr(&want, names[i]);
      +
      +		n = block_reader_seek(&br, &it, &want);
      +		assert(n == 0);
     @@ reftable/block_test.c (new)
      +	return test_main(argc, argv);
      +}
      
     + ## reftable/compat.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++
     ++*/
     ++
     ++/* compat.c - compatibility functions for standalone compilation */
     ++
     ++#include "system.h"
     ++#include "basics.h"
     ++
     ++#ifndef REFTABLE_IN_GITCORE
     ++
     ++#include <dirent.h>
     ++
     ++void put_be32(void *p, uint32_t i)
     ++{
     ++	byte *out = (byte *)p;
     ++
     ++	out[0] = (uint8_t)((i >> 24) & 0xff);
     ++	out[1] = (uint8_t)((i >> 16) & 0xff);
     ++	out[2] = (uint8_t)((i >> 8) & 0xff);
     ++	out[3] = (uint8_t)((i)&0xff);
     ++}
     ++
     ++uint32_t get_be32(uint8_t *in)
     ++{
     ++	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     ++	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
     ++}
     ++
     ++void put_be64(void *p, uint64_t v)
     ++{
     ++	byte *out = (byte *)p;
     ++	int i = sizeof(uint64_t);
     ++	while (i--) {
     ++		out[i] = (uint8_t)(v & 0xff);
     ++		v >>= 8;
     ++	}
     ++}
     ++
     ++uint64_t get_be64(uint8_t *out)
     ++{
     ++	uint64_t v = 0;
     ++	int i = 0;
     ++	for (i = 0; i < sizeof(uint64_t); i++) {
     ++		v = (v << 8) | (uint8_t)(out[i] & 0xff);
     ++	}
     ++	return v;
     ++}
     ++
     ++uint16_t get_be16(uint8_t *in)
     ++{
     ++	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
     ++}
     ++
     ++char *xstrdup(const char *s)
     ++{
     ++	int l = strlen(s);
     ++	char *dest = (char *)reftable_malloc(l + 1);
     ++	strncpy(dest, s, l + 1);
     ++	return dest;
     ++}
     ++
     ++void sleep_millisec(int millisecs)
     ++{
     ++	usleep(millisecs * 1000);
     ++}
     ++
     ++void reftable_clear_dir(const char *dirname)
     ++{
     ++	DIR *dir = opendir(dirname);
     ++	struct dirent *ent = NULL;
     ++	assert(dir);
     ++	while ((ent = readdir(dir)) != NULL) {
     ++		unlinkat(dirfd(dir), ent->d_name, 0);
     ++	}
     ++	closedir(dir);
     ++	rmdir(dirname);
     ++}
     ++
     ++#else
     ++
     ++#include "../dir.h"
     ++
     ++void reftable_clear_dir(const char *dirname)
     ++{
     ++	struct strbuf path = STRBUF_INIT;
     ++	strbuf_addstr(&path, dirname);
     ++	remove_dir_recursively(&path, 0);
     ++	strbuf_release(&path);
     ++}
     ++
     ++#endif
     +
     + ## reftable/compat.h (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#ifndef COMPAT_H
     ++#define COMPAT_H
     ++
     ++#include "system.h"
     ++
     ++#ifndef REFTABLE_IN_GITCORE
     ++
     ++/* functions that git-core provides, for standalone compilation */
     ++#include <stdint.h>
     ++
     ++uint64_t get_be64(uint8_t *in);
     ++void put_be64(void *out, uint64_t i);
     ++
     ++void put_be32(void *out, uint32_t i);
     ++uint32_t get_be32(uint8_t *in);
     ++
     ++uint16_t get_be16(uint8_t *in);
     ++
     ++#define ARRAY_SIZE(a) sizeof((a)) / sizeof((a)[0])
     ++#define FREE_AND_NULL(x)          \
     ++	do {                      \
     ++		reftable_free(x); \
     ++		(x) = NULL;       \
     ++	} while (0)
     ++#define QSORT(arr, n, cmp) qsort(arr, n, sizeof(arr[0]), cmp)
     ++#define SWAP(a, b)                              \
     ++	{                                       \
     ++		char tmp[sizeof(a)];            \
     ++		assert(sizeof(a) == sizeof(b)); \
     ++		memcpy(&tmp[0], &a, sizeof(a)); \
     ++		memcpy(&a, &b, sizeof(a));      \
     ++		memcpy(&b, &tmp[0], sizeof(a)); \
     ++	}
     ++
     ++char *xstrdup(const char *s);
     ++
     ++void sleep_millisec(int millisecs);
     ++
     ++#endif
     ++#endif
     +
       ## reftable/constants.h (new) ##
      @@
      +/*
     @@ reftable/constants.h (new)
      +
      +#endif
      
     + ## reftable/dump.c (new) ##
     +@@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include <stddef.h>
     ++#include <stdio.h>
     ++#include <stdlib.h>
     ++#include <unistd.h>
     ++#include <string.h>
     ++
     ++#include "reftable.h"
     ++#include "reftable-tests.h"
     ++
     ++static uint32_t hash_id;
     ++
     ++static int dump_table(const char *tablename)
     ++{
     ++	struct reftable_block_source src = { 0 };
     ++	int err = reftable_block_source_from_file(&src, tablename);
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_log_record log = { 0 };
     ++	struct reftable_reader *r = NULL;
     ++
     ++	if (err < 0)
     ++		return err;
     ++
     ++	err = reftable_new_reader(&r, &src, tablename);
     ++	if (err < 0)
     ++		return err;
     ++
     ++	err = reftable_reader_seek_ref(r, &it, "");
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	while (1) {
     ++		err = reftable_iterator_next_ref(&it, &ref);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		reftable_ref_record_print(&ref, hash_id);
     ++	}
     ++	reftable_iterator_destroy(&it);
     ++	reftable_ref_record_clear(&ref);
     ++
     ++	err = reftable_reader_seek_log(r, &it, "");
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++	while (1) {
     ++		err = reftable_iterator_next_log(&it, &log);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		reftable_log_record_print(&log, hash_id);
     ++	}
     ++	reftable_iterator_destroy(&it);
     ++	reftable_log_record_clear(&log);
     ++
     ++	reftable_reader_free(r);
     ++	return 0;
     ++}
     ++
     ++static int compact_stack(const char *stackdir)
     ++{
     ++	struct reftable_stack *stack = NULL;
     ++	struct reftable_write_options cfg = {};
     ++
     ++	int err = reftable_new_stack(&stack, stackdir, cfg);
     ++	if (err < 0)
     ++		goto done;
     ++
     ++	err = reftable_stack_compact_all(stack, NULL);
     ++	if (err < 0)
     ++		goto done;
     ++done:
     ++	if (stack != NULL) {
     ++		reftable_stack_destroy(stack);
     ++	}
     ++	return err;
     ++}
     ++
     ++static int dump_stack(const char *stackdir)
     ++{
     ++	struct reftable_stack *stack = NULL;
     ++	struct reftable_write_options cfg = {};
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_ref_record ref = { 0 };
     ++	struct reftable_log_record log = { 0 };
     ++	struct reftable_merged_table *merged = NULL;
     ++
     ++	int err = reftable_new_stack(&stack, stackdir, cfg);
     ++	if (err < 0)
     ++		return err;
     ++
     ++	merged = reftable_stack_merged_table(stack);
     ++
     ++	err = reftable_merged_table_seek_ref(merged, &it, "");
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	while (1) {
     ++		err = reftable_iterator_next_ref(&it, &ref);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		reftable_ref_record_print(&ref, hash_id);
     ++	}
     ++	reftable_iterator_destroy(&it);
     ++	reftable_ref_record_clear(&ref);
     ++
     ++	err = reftable_merged_table_seek_log(merged, &it, "");
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++	while (1) {
     ++		err = reftable_iterator_next_log(&it, &log);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		reftable_log_record_print(&log, hash_id);
     ++	}
     ++	reftable_iterator_destroy(&it);
     ++	reftable_log_record_clear(&log);
     ++
     ++	reftable_stack_destroy(stack);
     ++	return 0;
     ++}
     ++
     ++static void print_help(void)
     ++{
     ++	printf("usage: dump [-cst] arg\n\n"
     ++	       "options: \n"
     ++	       "  -c compact\n"
     ++	       "  -t dump table\n"
     ++	       "  -s dump stack\n"
     ++	       "  -h this help\n"
     ++	       "  -2 use SHA256\n"
     ++	       "\n");
     ++}
     ++
     ++int reftable_dump_main(int argc, char *const *argv)
     ++{
     ++	int err = 0;
     ++	int opt;
     ++	int opt_dump_table = 0;
     ++	int opt_dump_stack = 0;
     ++	int opt_compact = 0;
     ++	const char *arg = NULL;
     ++	while ((opt = getopt(argc, argv, "2chts")) != -1) {
     ++		switch (opt) {
     ++		case '2':
     ++			hash_id = 0x73323536;
     ++			break;
     ++		case 't':
     ++			opt_dump_table = 1;
     ++			break;
     ++		case 's':
     ++			opt_dump_stack = 1;
     ++			break;
     ++		case 'c':
     ++			opt_compact = 1;
     ++			break;
     ++		case '?':
     ++		case 'h':
     ++			print_help();
     ++			return 2;
     ++			break;
     ++		}
     ++	}
     ++
     ++	if (argv[optind] == NULL) {
     ++		fprintf(stderr, "need argument\n");
     ++		print_help();
     ++		return 2;
     ++	}
     ++
     ++	arg = argv[optind];
     ++
     ++	if (opt_dump_table) {
     ++		err = dump_table(arg);
     ++	} else if (opt_dump_stack) {
     ++		err = dump_stack(arg);
     ++	} else if (opt_compact) {
     ++		err = compact_stack(arg);
     ++	}
     ++
     ++	if (err < 0) {
     ++		fprintf(stderr, "%s: %s: %s\n", argv[0], arg,
     ++			reftable_error_str(err));
     ++		return 1;
     ++	}
     ++	return 0;
     ++}
     +
       ## reftable/file.c (new) ##
      @@
      +/*
     @@ reftable/iter.c (new)
      +
      +	*itr = empty;
      +	itr->r = r;
     -+	slice_resize(&itr->oid, oid_len);
     -+	memcpy(itr->oid.buf, oid, oid_len);
     ++	slice_add(&itr->oid, oid, oid_len);
      +
      +	itr->offsets = offsets;
      +	itr->offset_len = offset_len;
     @@ reftable/reader.c (new)
      +
      +	filter = reftable_malloc(sizeof(struct filtering_ref_iterator));
      +	*filter = empty;
     -+	slice_resize(&filter->oid, oid_len);
     -+	memcpy(filter->oid.buf, oid, oid_len);
     ++
     ++	slice_add(&filter->oid, oid, oid_len);
      +	reftable_table_from_reader(&filter->tab, r);
      +	filter->double_check = false;
      +	iterator_from_table_iter(&filter->it, ti);
     @@ reftable/record.c (new)
      +	if (in.len < tsize)
      +		return -1;
      +
     -+	slice_resize(dest, tsize + 1);
     -+	dest->buf[tsize] = 0;
     -+	memcpy(dest->buf, in.buf, tsize);
     ++	slice_reset(dest);
     ++	slice_add(dest, in.buf, tsize);
      +	slice_consume(&in, tsize);
      +
      +	return start_len - in.len;
     @@ reftable/record.c (new)
      +	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);
     ++	slice_reset(key);
     ++	slice_add(key, last_key.buf, prefix_len);
     ++	slice_add(key, in.buf, suffix_len);
      +	slice_consume(&in, suffix_len);
      +
      +	return start_len - in.len;
     @@ reftable/record.c (new)
      +{
      +	const struct reftable_ref_record *rec =
      +		(const struct reftable_ref_record *)r;
     -+	slice_set_string(dest, rec->ref_name);
     ++	slice_reset(dest);
     ++	slice_addstr(dest, rec->ref_name);
      +}
      +
      +static void reftable_ref_record_copy_from(void *rec, const void *src_rec,
     @@ reftable/record.c (new)
      +{
      +	const struct reftable_obj_record *rec =
      +		(const struct reftable_obj_record *)r;
     -+	slice_resize(dest, rec->hash_prefix_len);
     -+	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
     ++	slice_reset(dest);
     ++	slice_add(dest, rec->hash_prefix, rec->hash_prefix_len);
      +}
      +
      +static void reftable_obj_record_clear(void *rec)
     @@ reftable/record.c (new)
      +	const struct reftable_log_record *rec =
      +		(const struct reftable_log_record *)r;
      +	int len = strlen(rec->ref_name);
     ++	byte i64[8];
      +	uint64_t ts = 0;
     -+	slice_resize(dest, len + 9);
     -+	memcpy(dest->buf, rec->ref_name, len + 1);
     ++	slice_reset(dest);
     ++	slice_add(dest, (byte *)rec->ref_name, len + 1);
     ++
      +	ts = (~ts) - rec->update_index;
     -+	put_be64(dest->buf + 1 + len, ts);
     ++	put_be64(&i64[0], ts);
     ++	slice_add(dest, i64, sizeof(i64));
      +}
      +
      +static void reftable_log_record_copy_from(void *rec, const void *src_rec,
     @@ reftable/record.c (new)
      +	memcpy(r->name, dest.buf, dest.len);
      +	r->name[dest.len] = 0;
      +
     -+	slice_resize(&dest, 0);
     ++	slice_reset(&dest);
      +	n = decode_string(&dest, in);
      +	if (n < 0)
      +		goto done;
     @@ reftable/record.c (new)
      +	r->tz_offset = get_be16(in.buf);
      +	slice_consume(&in, 2);
      +
     -+	slice_resize(&dest, 0);
     ++	slice_reset(&dest);
      +	n = decode_string(&dest, in);
      +	if (n < 0)
      +		goto done;
     @@ reftable/record.c (new)
      +static void reftable_index_record_key(const void *r, struct slice *dest)
      +{
      +	struct reftable_index_record *rec = (struct reftable_index_record *)r;
     -+	slice_copy(dest, &rec->last_key);
     ++	slice_reset(dest);
     ++	slice_addbuf(dest, &rec->last_key);
      +}
      +
      +static void reftable_index_record_copy_from(void *rec, const void *src_rec,
     @@ reftable/record.c (new)
      +	struct reftable_index_record *src =
      +		(struct reftable_index_record *)src_rec;
      +
     -+	slice_copy(&dst->last_key, &src->last_key);
     ++	slice_reset(&dst->last_key);
     ++	slice_addbuf(&dst->last_key, &src->last_key);
      +	dst->offset = src->offset;
      +}
      +
     @@ reftable/record.c (new)
      +	struct reftable_index_record *r = (struct reftable_index_record *)rec;
      +	int n = 0;
      +
     -+	slice_copy(&r->last_key, &key);
     ++	slice_reset(&r->last_key);
     ++	slice_addbuf(&r->last_key, &key);
      +
      +	n = get_var_int(&r->offset, &in);
      +	if (n < 0)
     @@ reftable/record_test.c (new)
      +	for (i = 0; i < ARRAY_SIZE(cases); i++) {
      +		struct slice a = SLICE_INIT;
      +		struct slice b = SLICE_INIT;
     -+		slice_set_string(&a, cases[i].a);
     -+		slice_set_string(&b, cases[i].b);
     -+
     ++		slice_addstr(&a, cases[i].a);
     ++		slice_addstr(&b, cases[i].b);
      +		assert(common_prefix_size(&a, &b) == cases[i].want);
      +
      +		slice_release(&a);
     @@ reftable/record_test.c (new)
      +		assert(reftable_record_val_type(&rec) == i);
      +
      +		reftable_record_key(&rec, &key);
     -+		slice_resize(&dest, 1024);
     ++		slice_grow(&dest, 1024);
     ++		slice_setlen(&dest, 1024);
      +		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
      +		assert(n > 0);
      +
     @@ reftable/record_test.c (new)
      +
      +		reftable_record_key(&rec, &key);
      +
     -+		slice_resize(&dest, 1024);
     ++		slice_grow(&dest, 1024);
     ++		slice_setlen(&dest, 1024);
      +
      +		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
      +		assert(n >= 0);
     @@ reftable/record_test.c (new)
      +	int n, m;
      +	byte rt_extra;
      +
     -+	slice_resize(&dest, 1024);
     -+	slice_set_string(&last_key, "refs/heads/master");
     -+	slice_set_string(&key, "refs/tags/bla");
     -+
     ++	slice_grow(&dest, 1024);
     ++	slice_setlen(&dest, 1024);
     ++	slice_addstr(&last_key, "refs/heads/master");
     ++	slice_addstr(&key, "refs/tags/bla");
      +	extra = 6;
      +	n = reftable_encode_key(&restart, dest, last_key, key, extra);
      +	assert(!restart);
     @@ reftable/record_test.c (new)
      +
      +	m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);
      +	assert(n == m);
     -+	assert(slice_equal(&key, &roundtrip));
     ++	assert(0 == slice_cmp(&key, &roundtrip));
      +	assert(rt_extra == extra);
      +
      +	slice_release(&last_key);
     @@ reftable/record_test.c (new)
      +		reftable_record_from_obj(&rec, &in);
      +		test_copy(&rec);
      +		reftable_record_key(&rec, &key);
     -+		slice_resize(&dest, 1024);
     ++		slice_grow(&dest, 1024);
     ++		slice_setlen(&dest, 1024);
      +		n = reftable_record_encode(&rec, dest, SHA1_SIZE);
      +		assert(n > 0);
      +		extra = reftable_record_val_type(&rec);
     @@ reftable/record_test.c (new)
      +	int n, m;
      +	byte extra;
      +
     -+	slice_set_string(&in.last_key, "refs/heads/master");
     ++	slice_addstr(&in.last_key, "refs/heads/master");
      +	reftable_record_from_index(&rec, &in);
      +	reftable_record_key(&rec, &key);
      +	test_copy(&rec);
      +
      +	assert(0 == slice_cmp(&key, &in.last_key));
     -+	slice_resize(&dest, 1024);
     ++	slice_grow(&dest, 1024);
     ++	slice_setlen(&dest, 1024);
      +	n = reftable_record_encode(&rec, dest, SHA1_SIZE);
      +	assert(n > 0);
      +
     @@ reftable/refname.c (new)
      +{
      +	while (sl->len > 0) {
      +		bool is_slash = (sl->buf[sl->len - 1] == '/');
     -+		sl->len--;
     ++		slice_setlen(sl, sl->len - 1);
      +		if (is_slash)
      +			break;
      +	}
     @@ reftable/refname.c (new)
      +		err = validate_ref_name(mod->add[i]);
      +		if (err)
      +			goto done;
     -+		slice_set_string(&slashed, mod->add[i]);
     ++		slice_reset(&slashed);
     ++		slice_addstr(&slashed, mod->add[i]);
      +		slice_addstr(&slashed, "/");
      +
      +		err = modification_has_ref_with_prefix(
     @@ reftable/refname.c (new)
      +		if (err < 0)
      +			goto done;
      +
     -+		slice_set_string(&slashed, mod->add[i]);
     ++		slice_reset(&slashed);
     ++		slice_addstr(&slashed, mod->add[i]);
      +		while (slashed.len) {
      +			slice_trim_component(&slashed);
      +			err = modification_has_ref(mod,
     @@ reftable/refname_test.c (new)
      
       ## reftable/reftable-tests.h (new) ##
      @@
     ++/*
     ++Copyright 2020 Google LLC
     ++
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
      +#ifndef REFTABLE_TESTS_H
      +#define REFTABLE_TESTS_H
      +
     @@ reftable/reftable-tests.h (new)
      +int slice_test_main(int argc, const char **argv);
      +int stack_test_main(int argc, const char **argv);
      +int tree_test_main(int argc, const char **argv);
     ++int reftable_dump_main(int argc, char *const *argv);
      +
      +#endif
      
     @@ reftable/reftable.h (new)
      +	   - on writing a record with NULL ref_name.
      +	   - on writing a reftable_ref_record outside the table limits
      +	   - on writing a ref or log record before the stack's next_update_index
     ++	   - on writing a log record with multiline message with
     ++	   exact_log_message unset
      +	   - on reading a reftable_ref_record from log iterator, or vice versa.
      +	*/
      +	REFTABLE_API_ERROR = -6,
     @@ reftable/reftable.h (new)
      +	/* boolean: do not check ref names for validity or dir/file conflicts.
      +	 */
      +	int skip_name_check;
     ++
     ++	/* boolean: copy log messages exactly. If unset, check that the message
     ++	 *   is a single line, and add '\n' if missing.
     ++	 */
     ++	int exact_log_message;
      +};
      +
      +/* reftable_block_stats holds statistics for a single block type */
     @@ reftable/reftable_test.c (new)
      +		reftable_iterator_destroy(&it);
      +	}
      +
     -+	slice_set_string(&pastLast, names[N - 1]);
     ++	slice_addstr(&pastLast, names[N - 1]);
      +	slice_addstr(&pastLast, "/");
      +
      +	err = reftable_reader_seek_ref(&rd, &it, slice_as_string(&pastLast));
     @@ reftable/slice.c (new)
      +
      +struct slice reftable_empty_slice = SLICE_INIT;
      +
     -+void slice_set_string(struct slice *s, const char *str)
     -+{
     -+	int l;
     -+	if (str == NULL) {
     -+		s->len = 0;
     -+		return;
     -+	}
     -+	assert(s->canary == SLICE_CANARY);
     -+
     -+	l = strlen(str);
     -+	l++; /* \0 */
     -+	slice_resize(s, l);
     -+	memcpy(s->buf, str, l);
     -+	s->len = l - 1;
     -+}
     -+
      +void slice_init(struct slice *s)
      +{
      +	struct slice empty = SLICE_INIT;
      +	*s = empty;
      +}
      +
     -+void slice_resize(struct slice *s, int l)
     ++void slice_grow(struct slice *s, size_t extra)
      +{
     ++	size_t newcap = s->len + extra + 1;
     ++	if (newcap > s->cap) {
     ++		s->buf = reftable_realloc(s->buf, newcap);
     ++		s->cap = newcap;
     ++	}
     ++}
     ++
     ++static void slice_resize(struct slice *s, int l)
     ++{
     ++	int zl = l + 1; /* one byte for 0 termination. */
      +	assert(s->canary == SLICE_CANARY);
     -+	if (s->cap < l) {
     ++	if (s->cap < zl) {
      +		int c = s->cap * 2;
     -+		if (c < l) {
     -+			c = l;
     ++		if (c < zl) {
     ++			c = zl;
      +		}
      +		s->cap = c;
      +		s->buf = reftable_realloc(s->buf, s->cap);
      +	}
      +	s->len = l;
     ++	s->buf[l] = 0;
     ++}
     ++
     ++void slice_setlen(struct slice *s, size_t l)
     ++{
     ++	assert(s->cap >= l + 1);
     ++	s->len = l;
     ++	s->buf[l] = 0;
     ++}
     ++
     ++void slice_reset(struct slice *s)
     ++{
     ++	slice_resize(s, 0);
      +}
      +
      +void slice_addstr(struct slice *d, const char *s)
     @@ reftable/slice.c (new)
      +	s->len -= n;
      +}
      +
     -+byte *slice_detach(struct slice *s)
     ++char *slice_detach(struct slice *s)
      +{
     -+	byte *p = s->buf;
     -+	assert(s->canary == SLICE_CANARY);
     ++	char *p = NULL;
     ++	slice_as_string(s);
     ++	p = (char *)s->buf;
      +	s->buf = NULL;
      +	s->cap = 0;
      +	s->len = 0;
     @@ reftable/slice.c (new)
      +
      +void slice_release(struct slice *s)
      +{
     ++	byte *ptr = s->buf;
      +	assert(s->canary == SLICE_CANARY);
     -+	reftable_free(slice_detach(s));
     -+}
     -+
     -+void slice_copy(struct slice *dest, struct slice *src)
     -+{
     -+	assert(dest->canary == SLICE_CANARY);
     -+	assert(src->canary == SLICE_CANARY);
     -+	slice_resize(dest, src->len);
     -+	memcpy(dest->buf, src->buf, src->len);
     ++	s->buf = NULL;
     ++	s->cap = 0;
     ++	s->len = 0;
     ++	reftable_free(ptr);
      +}
      +
      +/* 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)
      +{
     -+	assert(s->canary == SLICE_CANARY);
     -+	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_INIT;
     -+	assert(in->canary == SLICE_CANARY);
     -+	slice_resize(&s, in->len + 1);
     -+	s.buf[in->len] = 0;
     -+	memcpy(s.buf, in->buf, in->len);
     -+	return (char *)slice_detach(&s);
     -+}
     -+
     -+bool slice_equal(struct slice *a, struct slice *b)
     -+{
     -+	return slice_cmp(a, b) == 0;
     -+}
     -+
      +int slice_cmp(const struct slice *a, const struct slice *b)
      +{
      +	int min = a->len < b->len ? a->len : b->len;
     @@ reftable/slice.c (new)
      +		return 0;
      +}
      +
     -+int slice_add(struct slice *b, byte *data, size_t sz)
     ++int slice_add(struct slice *b, const byte *data, size_t sz)
      +{
      +	assert(b->canary == SLICE_CANARY);
     -+	if (b->len + sz > b->cap) {
     -+		int newcap = 2 * b->cap + 1;
     -+		if (newcap < b->len + sz) {
     -+			newcap = (b->len + sz);
     -+		}
     -+		b->buf = reftable_realloc(b->buf, newcap);
     -+		b->cap = newcap;
     -+	}
     -+
     ++	slice_grow(b, sz);
      +	memcpy(b->buf + b->len, data, sz);
      +	b->len += sz;
     ++	b->buf[b->len] = 0;
      +	return sz;
      +}
      +
     @@ reftable/slice.h (new)
      +#include "reftable.h"
      +
      +/*
     -+  provides bounds-checked byte ranges.
     -+  To use, initialize as "slice x = {0};"
     ++  Provides a bounds-checked, growable byte ranges. To use, initialize as "slice
     ++  x = SLICE_INIT;"
      + */
      +struct slice {
      +	int len;
      +	int cap;
      +	byte *buf;
     ++
     ++	/* Used to enforce initialization with SLICE_INIT */
      +	byte canary;
      +};
      +#define SLICE_CANARY 0x42
     @@ reftable/slice.h (new)
      +	}
      +extern struct slice reftable_empty_slice;
      +
     -+void slice_set_string(struct slice *dest, const char *src);
      +void slice_addstr(struct slice *dest, const char *src);
      +
      +/* Deallocate and clear slice */
      +void slice_release(struct slice *slice);
      +
     -+/* Return a malloced string for `src` */
     -+char *slice_to_string(struct slice *src);
     ++/* Set slice to 0 length, but retain buffer. */
     ++void slice_reset(struct slice *slice);
      +
      +/* Initializes a slice. Accepts a slice with random garbage. */
      +void slice_init(struct slice *slice);
     @@ reftable/slice.h (new)
      +/* Ensure that `buf` is \0 terminated. */
      +const char *slice_as_string(struct slice *src);
      +
     -+/* Compare slices */
     -+bool slice_equal(struct slice *a, struct slice *b);
     -+
      +/* Return `buf`, clearing out `s` */
     -+byte *slice_detach(struct slice *s);
     ++char *slice_detach(struct slice *s);
      +
     -+/* Copy bytes */
     -+void slice_copy(struct slice *dest, struct slice *src);
     ++/* Set length of the slace to `l`, but don't reallocated. */
     ++void slice_setlen(struct slice *s, size_t l);
      +
     -+/* Advance `buf` by `n`, and decrease length. A copy of the slice
     -+   should be kept for deallocating the slice. */
     -+void slice_consume(struct slice *s, int n);
     -+
     -+/* Set length of the slice to `l` */
     -+void slice_resize(struct slice *s, int l);
     ++/* Ensure `l` bytes beyond current length are available */
     ++void slice_grow(struct slice *s, size_t l);
      +
      +/* Signed comparison */
      +int slice_cmp(const struct slice *a, const struct slice *b);
      +
      +/* Append `data` to the `dest` slice.  */
     -+int slice_add(struct slice *dest, byte *data, size_t sz);
     ++int slice_add(struct slice *dest, const byte *data, size_t sz);
      +
      +/* Append `add` to `dest. */
      +void slice_addbuf(struct slice *dest, struct slice *add);
     @@ reftable/slice.h (new)
      +
      +struct reftable_block_source malloc_block_source(void);
      +
     ++/* Advance `buf` by `n`, and decrease length. A copy of the slice
     ++   should be kept for deallocating the slice. */
     ++void slice_consume(struct slice *s, int n);
     ++
      +#endif
      
       ## reftable/slice_test.c (new) ##
     @@ reftable/slice_test.c (new)
      +	struct slice s = SLICE_INIT;
      +	struct slice t = SLICE_INIT;
      +
     -+	slice_set_string(&s, "abc");
     ++	slice_addstr(&s, "abc");
      +	assert(0 == strcmp("abc", slice_as_string(&s)));
      +
     -+	slice_set_string(&t, "pqr");
     -+
     ++	slice_addstr(&t, "pqr");
      +	slice_addbuf(&s, &t);
      +	assert(0 == strcmp("abcpqr", slice_as_string(&s)));
      +
     @@ reftable/stack.c (new)
      +
      +	*dest = NULL;
      +
     -+	slice_set_string(&list_file_name, dir);
     ++	slice_reset(&list_file_name);
     ++	slice_addstr(&list_file_name, dir);
      +	slice_addstr(&list_file_name, "/tables.list");
      +
     -+	p->list_file = slice_to_string(&list_file_name);
     -+	slice_release(&list_file_name);
     ++	p->list_file = slice_detach(&list_file_name);
      +	p->reftable_dir = xstrdup(dir);
      +	p->config = config;
      +
     @@ reftable/stack.c (new)
      +	int new_tables_len = 0;
      +	struct reftable_merged_table *new_merged = NULL;
      +	int i;
     -+	struct slice table_path = SLICE_INIT;
      +
      +	while (*names) {
      +		struct reftable_reader *rd = NULL;
     @@ reftable/stack.c (new)
      +
      +		if (rd == NULL) {
      +			struct reftable_block_source src = { 0 };
     -+			slice_set_string(&table_path, st->reftable_dir);
     ++			struct slice table_path = SLICE_INIT;
     ++			slice_addstr(&table_path, st->reftable_dir);
      +			slice_addstr(&table_path, "/");
      +			slice_addstr(&table_path, name);
      +
      +			err = reftable_block_source_from_file(
      +				&src, slice_as_string(&table_path));
     ++			slice_release(&table_path);
     ++
      +			if (err < 0)
      +				goto done;
      +
     @@ reftable/stack.c (new)
      +	}
      +
      +done:
     -+	slice_release(&table_path);
      +	for (i = 0; i < new_tables_len; i++) {
      +		reader_close(new_tables[i]);
      +		reftable_reader_free(new_tables[i]);
     @@ reftable/stack.c (new)
      +{
      +	char buf[100];
      +	snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64, min, max);
     -+	slice_set_string(dest, buf);
     ++	slice_reset(dest);
     ++	slice_addstr(dest, buf);
      +}
      +
      +struct reftable_addition {
     @@ reftable/stack.c (new)
      +	int err = 0;
      +	add->stack = st;
      +
     -+	slice_set_string(&add->lock_file_name, st->list_file);
     ++	slice_reset(&add->lock_file_name);
     ++	slice_addstr(&add->lock_file_name, st->list_file);
      +	slice_addstr(&add->lock_file_name, ".lock");
      +
      +	add->lock_file_fd = open(slice_as_string(&add->lock_file_name),
     @@ reftable/stack.c (new)
      +	int i = 0;
      +	struct slice nm = SLICE_INIT;
      +	for (i = 0; i < add->new_tables_len; i++) {
     -+		slice_set_string(&nm, add->stack->list_file);
     ++		slice_reset(&nm);
     ++		slice_addstr(&nm, add->stack->list_file);
      +		slice_addstr(&nm, "/");
      +		slice_addstr(&nm, add->new_tables[i]);
      +		unlink(slice_as_string(&nm));
     @@ reftable/stack.c (new)
      +	int err = 0;
      +	int tab_fd = 0;
      +
     -+	slice_resize(&next_name, 0);
     ++	slice_reset(&next_name);
      +	format_name(&next_name, add->next_update_index, add->next_update_index);
      +
     -+	slice_set_string(&temp_tab_file_name, add->stack->reftable_dir);
     ++	slice_addstr(&temp_tab_file_name, add->stack->reftable_dir);
      +	slice_addstr(&temp_tab_file_name, "/");
      +	slice_addbuf(&temp_tab_file_name, &next_name);
      +	slice_addstr(&temp_tab_file_name, ".temp.XXXXXX");
     @@ reftable/stack.c (new)
      +	format_name(&next_name, wr->min_update_index, wr->max_update_index);
      +	slice_addstr(&next_name, ".ref");
      +
     -+	slice_set_string(&tab_file_name, add->stack->reftable_dir);
     ++	slice_addstr(&tab_file_name, add->stack->reftable_dir);
      +	slice_addstr(&tab_file_name, "/");
      +	slice_addbuf(&tab_file_name, &next_name);
      +
     @@ reftable/stack.c (new)
      +	add->new_tables = reftable_realloc(add->new_tables,
      +					   sizeof(*add->new_tables) *
      +						   (add->new_tables_len + 1));
     -+	add->new_tables[add->new_tables_len] = slice_to_string(&next_name);
     ++	add->new_tables[add->new_tables_len] = slice_detach(&next_name);
      +	add->new_tables_len++;
      +done:
      +	if (tab_fd > 0) {
     @@ reftable/stack.c (new)
      +		    reftable_reader_min_update_index(st->merged->stack[first]),
      +		    reftable_reader_max_update_index(st->merged->stack[first]));
      +
     -+	slice_set_string(temp_tab, st->reftable_dir);
     ++	slice_reset(temp_tab);
     ++	slice_addstr(temp_tab, st->reftable_dir);
      +	slice_addstr(temp_tab, "/");
      +	slice_addbuf(temp_tab, &next_name);
      +	slice_addstr(temp_tab, ".temp.XXXXXX");
     @@ reftable/stack.c (new)
      +
      +	st->stats.attempts++;
      +
     -+	slice_set_string(&lock_file_name, st->list_file);
     ++	slice_reset(&lock_file_name);
     ++	slice_addstr(&lock_file_name, st->list_file);
      +	slice_addstr(&lock_file_name, ".lock");
      +
      +	lock_file_fd = open(slice_as_string(&lock_file_name),
     @@ reftable/stack.c (new)
      +		struct slice subtab_lock = SLICE_INIT;
      +		int sublock_file_fd = -1;
      +
     -+		slice_set_string(&subtab_file_name, st->reftable_dir);
     ++		slice_addstr(&subtab_file_name, st->reftable_dir);
      +		slice_addstr(&subtab_file_name, "/");
      +		slice_addstr(&subtab_file_name,
      +			     reader_name(st->merged->stack[i]));
      +
     -+		slice_copy(&subtab_lock, &subtab_file_name);
     ++		slice_reset(&subtab_lock);
     ++		slice_addbuf(&subtab_lock, &subtab_file_name);
      +		slice_addstr(&subtab_lock, ".lock");
      +
      +		sublock_file_fd = open(slice_as_string(&subtab_lock),
     @@ reftable/stack.c (new)
      +		    st->merged->stack[last]->max_update_index);
      +	slice_addstr(&new_table_name, ".ref");
      +
     -+	slice_set_string(&new_table_path, st->reftable_dir);
     ++	slice_reset(&new_table_path);
     ++	slice_addstr(&new_table_path, st->reftable_dir);
      +	slice_addstr(&new_table_path, "/");
     -+
      +	slice_addbuf(&new_table_path, &new_table_name);
      +
      +	if (!is_empty_table) {
     @@ reftable/stack_test.c (new)
      +#include <sys/types.h>
      +#include <dirent.h>
      +
     -+static void clear_dir(const char *dirname)
     -+{
     -+	int fd = open(dirname, O_DIRECTORY, 0);
     -+	DIR *dir = fdopendir(fd);
     -+	struct dirent *ent = NULL;
     -+
     -+	assert(fd >= 0);
     -+
     -+	while ((ent = readdir(dir)) != NULL) {
     -+		unlinkat(fd, ent->d_name, 0);
     -+	}
     -+	closedir(dir);
     -+	rmdir(dirname);
     -+}
     -+
      +static void test_read_file(void)
      +{
      +	char fn[256] = "/tmp/stack.test_read_file.XXXXXX";
     @@ reftable/stack_test.c (new)
      +
      +	reftable_ref_record_clear(&dest);
      +	reftable_stack_destroy(st);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_uptodate(void)
     @@ reftable/stack_test.c (new)
      +	assert_err(err);
      +	reftable_stack_destroy(st1);
      +	reftable_stack_destroy(st2);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_transaction_api(void)
     @@ reftable/stack_test.c (new)
      +
      +	reftable_ref_record_clear(&dest);
      +	reftable_stack_destroy(st);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_validate_refname(void)
     @@ reftable/stack_test.c (new)
      +	}
      +
      +	reftable_stack_destroy(st);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static int write_error(struct reftable_writer *wr, void *arg)
     @@ reftable/stack_test.c (new)
      +	err = reftable_stack_add(st, &write_test_ref, &ref2);
      +	assert(err == REFTABLE_API_ERROR);
      +	reftable_stack_destroy(st);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_lock_failure(void)
     @@ reftable/stack_test.c (new)
      +	}
      +
      +	reftable_stack_destroy(st);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_add(void)
      +{
      +	int i = 0;
      +	int err = 0;
     -+	struct reftable_write_options cfg = { 0 };
     ++	struct reftable_write_options cfg = {
     ++		.exact_log_message = true,
     ++	};
      +	struct reftable_stack *st = NULL;
      +	char dir[256] = "/tmp/stack_test.XXXXXX";
     -+	struct reftable_ref_record refs[2] = { 0 };
     -+	struct reftable_log_record logs[2] = { 0 };
     ++	struct reftable_ref_record refs[2] = { { 0 } };
     ++	struct reftable_log_record logs[2] = { { 0 } };
      +	int N = ARRAY_SIZE(refs);
      +
      +	assert(mkdtemp(dir));
     @@ reftable/stack_test.c (new)
      +		reftable_ref_record_clear(&refs[i]);
      +		reftable_log_record_clear(&logs[i]);
      +	}
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
     ++}
     ++
     ++static void test_reftable_stack_log_normalize(void)
     ++{
     ++	int err = 0;
     ++	struct reftable_write_options cfg = {
     ++		0,
     ++	};
     ++	struct reftable_stack *st = NULL;
     ++	char dir[256] = "/tmp/stack_test.XXXXXX";
     ++
     ++	byte h1[SHA1_SIZE] = { 0x01 }, h2[SHA1_SIZE] = { 0x02 };
     ++
     ++	struct reftable_log_record input = {
     ++		.ref_name = "branch",
     ++		.update_index = 1,
     ++		.new_hash = h1,
     ++		.old_hash = h2,
     ++	};
     ++	struct reftable_log_record dest = {
     ++		.update_index = 0,
     ++	};
     ++	struct write_log_arg arg = {
     ++		.log = &input,
     ++		.update_index = 1,
     ++	};
     ++
     ++	assert(mkdtemp(dir));
     ++	err = reftable_new_stack(&st, dir, cfg);
     ++	assert_err(err);
     ++
     ++	input.message = "one\ntwo";
     ++	err = reftable_stack_add(st, &write_test_log, &arg);
     ++	assert(err == REFTABLE_API_ERROR);
     ++
     ++	input.message = "one";
     ++	err = reftable_stack_add(st, &write_test_log, &arg);
     ++	assert_err(err);
     ++
     ++	err = reftable_stack_read_log(st, input.ref_name, &dest);
     ++	assert_err(err);
     ++	assert(0 == strcmp(dest.message, "one\n"));
     ++
     ++	input.message = "two\n";
     ++	arg.update_index = 2;
     ++	err = reftable_stack_add(st, &write_test_log, &arg);
     ++	assert_err(err);
     ++	err = reftable_stack_read_log(st, input.ref_name, &dest);
     ++	assert_err(err);
     ++	assert(0 == strcmp(dest.message, "two\n"));
     ++
     ++	/* cleanup */
     ++	reftable_stack_destroy(st);
     ++	reftable_log_record_clear(&dest);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_tombstone(void)
     @@ reftable/stack_test.c (new)
      +	struct reftable_write_options cfg = { 0 };
      +	struct reftable_stack *st = NULL;
      +	int err;
     -+	struct reftable_ref_record refs[2] = { 0 };
     -+	struct reftable_log_record logs[2] = { 0 };
     ++	struct reftable_ref_record refs[2] = { { 0 } };
     ++	struct reftable_log_record logs[2] = { { 0 } };
      +	int N = ARRAY_SIZE(refs);
      +	struct reftable_ref_record dest = { 0 };
      +	struct reftable_log_record log_dest = { 0 };
     @@ reftable/stack_test.c (new)
      +		reftable_ref_record_clear(&refs[i]);
      +		reftable_log_record_clear(&logs[i]);
      +	}
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_reftable_stack_hash_id(void)
     @@ reftable/stack_test.c (new)
      +	reftable_ref_record_clear(&dest);
      +	reftable_stack_destroy(st);
      +	reftable_stack_destroy(st_default);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +static void test_log2(void)
     @@ reftable/stack_test.c (new)
      +	char dir[256] = "/tmp/stack.test_reflog_expire.XXXXXX";
      +	struct reftable_write_options cfg = { 0 };
      +	struct reftable_stack *st = NULL;
     -+	struct reftable_log_record logs[20] = { 0 };
     ++	struct reftable_log_record logs[20] = { { 0 } };
      +	int N = ARRAY_SIZE(logs) - 1;
      +	int i = 0;
      +	int err;
     @@ reftable/stack_test.c (new)
      +	for (i = 0; i <= N; i++) {
      +		reftable_log_record_clear(&logs[i]);
      +	}
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +	reftable_log_record_clear(&log);
      +}
      +
     @@ reftable/stack_test.c (new)
      +
      +	err = reftable_new_stack(&st2, dir, cfg);
      +	assert_err(err);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +	reftable_stack_destroy(st);
      +	reftable_stack_destroy(st2);
      +}
     @@ reftable/stack_test.c (new)
      +	       (uint64_t)(N * fastlog2(N)));
      +
      +	reftable_stack_destroy(st);
     -+	clear_dir(dir);
     ++	reftable_clear_dir(dir);
      +}
      +
      +int stack_test_main(int argc, const char *argv[])
     @@ reftable/stack_test.c (new)
      +		      &test_reftable_stack_update_index_check);
      +	add_test_case("test_reftable_stack_lock_failure",
      +		      &test_reftable_stack_lock_failure);
     ++	add_test_case("test_reftable_stack_log_normalize",
     ++		      &test_reftable_stack_log_normalize);
      +	add_test_case("test_reftable_stack_tombstone",
      +		      &test_reftable_stack_tombstone);
      +	add_test_case("test_reftable_stack_add_one",
     @@ reftable/system.h (new)
      +#define SYSTEM_H
      +
      +#if 1 /* REFTABLE_IN_GITCORE */
     ++#define REFTABLE_IN_GITCORE
      +
      +#include "git-compat-util.h"
      +#include "cache.h"
     @@ reftable/system.h (new)
      +
      +#endif /* REFTABLE_IN_GITCORE */
      +
     ++void reftable_clear_dir(const char *dirname);
     ++
      +#define SHA1_ID 0x73686131
      +#define SHA256_ID 0x73323536
      +#define SHA1_SIZE 20
     @@ reftable/system.h (new)
      +typedef int bool;
      +
      +/* This is uncompress2, which is only available in zlib as of 2017.
     -+ *
     -+ * TODO: in git-core, this should fallback to uncompress2 if it is available.
      + */
      +int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
      +			       const Bytef *source, uLong *sourceLen);
     @@ reftable/update.sh (new)
      +mv reftable/system.h reftable/system.h~
      +sed 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|'  < reftable/system.h~ > reftable/system.h
      +
     -+# Remove compatibility hacks we don't need here.
     -+rm reftable/compat.*
     -+
      +git add reftable/*.[ch] reftable/LICENSE reftable/VERSION
      
       ## reftable/writer.c (new) ##
     @@ reftable/writer.c (new)
      +		key = reftable_malloc(sizeof(struct obj_index_tree_node));
      +		*key = empty;
      +
     -+		slice_copy(&key->hash, hash);
     ++		slice_reset(&key->hash);
     ++		slice_addbuf(&key->hash, hash);
      +		tree_search((void *)key, &w->obj_index_tree,
      +			    &obj_index_tree_node_compare, 1);
      +	} else {
     @@ reftable/writer.c (new)
      +	if (slice_cmp(&w->last_key, &key) >= 0)
      +		goto done;
      +
     -+	slice_copy(&w->last_key, &key);
     ++	slice_reset(&w->last_key);
     ++	slice_addbuf(&w->last_key, &key);
      +	if (w->block_writer == NULL) {
      +		writer_reinit_block_writer(w, reftable_record_type(rec));
      +	}
     @@ reftable/writer.c (new)
      +		return err;
      +
      +	if (!w->opts.skip_index_objects && ref->value != NULL) {
     -+		struct slice h = {
     -+			.buf = ref->value,
     -+			.len = hash_size(w->opts.hash_id),
     -+			.canary = SLICE_CANARY,
     -+		};
     -+
     ++		struct slice h = SLICE_INIT;
     ++		slice_add(&h, ref->value, hash_size(w->opts.hash_id));
      +		writer_index_hash(w, &h);
     ++		slice_release(&h);
      +	}
      +
      +	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
     -+		struct slice h = {
     -+			.buf = ref->target_value,
     -+			.len = hash_size(w->opts.hash_id),
     -+			.canary = SLICE_CANARY,
     -+		};
     ++		struct slice h = SLICE_INIT;
     ++		slice_add(&h, ref->target_value, hash_size(w->opts.hash_id));
      +		writer_index_hash(w, &h);
      +	}
      +	return 0;
     @@ reftable/writer.c (new)
      +			    struct reftable_log_record *log)
      +{
      +	struct reftable_record rec = { 0 };
     ++	char *input_log_message = log->message;
     ++	struct slice cleaned_message = SLICE_INIT;
      +	int err;
      +	if (log->ref_name == NULL)
      +		return REFTABLE_API_ERROR;
     @@ reftable/writer.c (new)
      +			return err;
      +	}
      +
     ++	if (!w->opts.exact_log_message && log->message != NULL) {
     ++		slice_addstr(&cleaned_message, log->message);
     ++		while (cleaned_message.len &&
     ++		       cleaned_message.buf[cleaned_message.len - 1] == '\n')
     ++			slice_setlen(&cleaned_message, cleaned_message.len - 1);
     ++		if (strchr(slice_as_string(&cleaned_message), '\n')) {
     ++			// multiple lines not allowed.
     ++			err = REFTABLE_API_ERROR;
     ++			goto done;
     ++		}
     ++		slice_addstr(&cleaned_message, "\n");
     ++		log->message = (char *)slice_as_string(&cleaned_message);
     ++	}
     ++
      +	w->next -= w->pending_padding;
      +	w->pending_padding = 0;
      +
      +	reftable_record_from_log(&rec, log);
      +	err = writer_add_record(w, &rec);
     ++
     ++done:
     ++	log->message = input_log_message;
     ++	slice_release(&cleaned_message);
      +	return err;
      +}
      +
     @@ reftable/writer.c (new)
      +	}
      +
      +	ir.offset = w->next;
     -+	slice_copy(&ir.last_key, &w->block_writer->last_key);
     ++	slice_reset(&ir.last_key);
     ++	slice_addbuf(&ir.last_key, &w->block_writer->last_key);
      +	w->index[w->index_len] = ir;
      +
      +	w->index_len++;
 10:  f3d74b78135 ! 12:  e1b01927454 Reftable support for git-core
     @@ Makefile: VCSSVN_OBJS += vcs-svn/sliding_window.o
       
      +REFTABLE_OBJS += reftable/basics.o
      +REFTABLE_OBJS += reftable/block.o
     ++REFTABLE_OBJS += reftable/compat.o
      +REFTABLE_OBJS += reftable/file.o
      +REFTABLE_OBJS += reftable/iter.o
      +REFTABLE_OBJS += reftable/merged.o
     @@ refs/reftable-backend.c (new)
      +	}
      +
      +	for (i = 0; i < arg->refnames->nr; i++) {
     -+		struct reftable_log_record log = { NULL };
     ++		struct reftable_log_record log = {
     ++			.update_index = ts,
     ++		};
      +		struct reftable_ref_record current = { NULL };
      +		fill_reftable_log_record(&log);
      +		log.message = xstrdup(arg->logmsg);
     @@ refs/reftable-backend.c (new)
      +	};
      +	reftable_writer_set_limits(writer, ts, ts);
      +	err = reftable_writer_add_ref(writer, &ref);
     -+	if (err < 0) {
     -+		return err;
     -+	}
     -+
     -+	{
     ++	if (err == 0) {
      +		struct reftable_log_record log = { NULL };
      +		struct object_id new_oid;
      +		struct object_id old_oid;
     -+		struct reftable_ref_record current = { NULL };
     -+		reftable_stack_read_ref(create->refs->stack, create->refname,
     -+					&current);
      +
      +		fill_reftable_log_record(&log);
     -+		log.ref_name = current.ref_name;
     ++		log.ref_name = (char *)create->refname;
     ++		log.message = (char *)create->logmsg;
     ++		log.update_index = ts;
      +		if (refs_resolve_ref_unsafe(
      +			    (struct ref_store *)create->refs, create->refname,
      +			    RESOLVE_REF_READING, &old_oid, NULL) != NULL) {
     @@ refs/reftable-backend.c (new)
      +		}
      +
      +		if (log.old_hash != NULL || log.new_hash != NULL) {
     -+			reftable_writer_add_log(writer, &log);
     ++			err = reftable_writer_add_log(writer, &log);
      +		}
      +		log.ref_name = NULL;
     ++		log.message = NULL;
      +		log.old_hash = NULL;
      +		log.new_hash = NULL;
      +		clear_reftable_log_record(&log);
      +	}
     -+	return 0;
     ++	return err;
      +}
      +
      +static int reftable_create_symref(struct ref_store *ref_store,
     @@ refs/reftable-backend.c (new)
      +	mt = reftable_stack_merged_table(refs->stack);
      +	err = reftable_merged_table_seek_log(mt, &it, refname);
      +	while (err == 0) {
     ++		struct object_id old_oid;
     ++		struct object_id new_oid;
     ++		const char *full_committer = "";
     ++
      +		err = reftable_iterator_next_log(&it, &log);
     -+		if (err != 0) {
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
     ++		if (err < 0) {
      +			break;
      +		}
      +
     @@ refs/reftable-backend.c (new)
      +			break;
      +		}
      +
     -+		{
     -+			struct object_id old_oid;
     -+			struct object_id new_oid;
     -+			const char *full_committer = "";
     -+
     -+			hashcpy(old_oid.hash, log.old_hash);
     -+			hashcpy(new_oid.hash, log.new_hash);
     -+
     -+			full_committer = fmt_ident(log.name, log.email,
     -+						   WANT_COMMITTER_IDENT,
     -+						   /*date*/ NULL,
     -+						   IDENT_NO_DATE);
     -+			if (fn(&old_oid, &new_oid, full_committer, log.time,
     -+			       log.tz_offset, log.message, cb_data)) {
     -+				err = -1;
     -+				break;
     -+			}
     -+		}
     ++		hashcpy(old_oid.hash, log.old_hash);
     ++		hashcpy(new_oid.hash, log.new_hash);
     ++
     ++		full_committer = fmt_ident(log.name, log.email,
     ++					   WANT_COMMITTER_IDENT,
     ++					   /*date*/ NULL, IDENT_NO_DATE);
     ++		err = fn(&old_oid, &new_oid, full_committer, log.time,
     ++			 log.tz_offset, log.message, cb_data);
     ++		if (err)
     ++			break;
      +	}
      +
      +	reftable_log_record_clear(&log);
      +	reftable_iterator_destroy(&it);
     -+	if (err > 0) {
     -+		err = 0;
     -+	}
      +	return err;
      +}
      +
     @@ refs/reftable-backend.c (new)
      +	while (err == 0) {
      +		struct reftable_log_record log = { NULL };
      +		err = reftable_iterator_next_log(&it, &log);
     -+		if (err != 0) {
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
     ++		if (err < 0) {
      +			break;
      +		}
      +
     @@ refs/reftable-backend.c (new)
      +		full_committer = fmt_ident(log->name, log->email,
      +					   WANT_COMMITTER_IDENT, NULL,
      +					   IDENT_NO_DATE);
     -+		if (!fn(&old_oid, &new_oid, full_committer, log->time,
     -+			log->tz_offset, log->message, cb_data)) {
     -+			err = -1;
     ++		err = fn(&old_oid, &new_oid, full_committer, log->time,
     ++			 log->tz_offset, log->message, cb_data);
     ++		if (err) {
      +			break;
      +		}
      +	}
     @@ refs/reftable-backend.c (new)
      +	free(logs);
      +
      +	reftable_iterator_destroy(&it);
     -+	if (err > 0) {
     -+		err = 0;
     -+	}
      +	return err;
      +}
      +
     @@ refs/reftable-backend.c (new)
      +	reftable_read_raw_ref,
      +
      +	reftable_reflog_iterator_begin,
     -+	reftable_for_each_reflog_ent_newest_first,
      +	reftable_for_each_reflog_ent_oldest_first,
     ++	reftable_for_each_reflog_ent_newest_first,
      +	reftable_reflog_exists,
      +	reftable_create_reflog,
      +	reftable_delete_reflog,
     @@ t/t0031-reftable.sh (new)
      +	test_line_count = 0 output
      +'
      +
     ++test_expect_success 'branch switch in reflog output' '
     ++	initialize &&
     ++	test_commit file1 &&
     ++	git checkout -b branch1 &&
     ++	test_commit file2 &&
     ++	git checkout -b branch2 &&
     ++	git switch - &&
     ++	git rev-parse --symbolic-full-name HEAD > actual &&
     ++	echo refs/heads/branch1 > expect &&
     ++	test_cmp actual expect
     ++'
     ++
     ++
      +# This matches show-ref's output
      +print_ref() {
      +	echo "$(git rev-parse "$1") $1"
 11:  3e7868ee409 ! 13:  0359fe416fa Hookup unittests for the reftable library.
     @@ Makefile: t/helper/test-svn-fe$X: $(VCSSVN_LIB)
       
       check-sha1:: t/helper/test-tool$X
      
     + ## t/helper/test-reftable.c (new) ##
     +@@
     ++#include "reftable/reftable-tests.h"
     ++#include "test-tool.h"
     ++
     ++int cmd__reftable(int argc, const char **argv)
     ++{
     ++	block_test_main(argc, argv);
     ++	merged_test_main(argc, argv);
     ++	record_test_main(argc, argv);
     ++	refname_test_main(argc, argv);
     ++	reftable_test_main(argc, argv);
     ++	slice_test_main(argc, argv);
     ++	stack_test_main(argc, argv);
     ++	tree_test_main(argc, argv);
     ++	return 0;
     ++}
     +
       ## t/helper/test-tool.c ##
      @@ t/helper/test-tool.c: static struct test_cmd cmds[] = {
       	{ "read-graph", cmd__read_graph },
 12:  1f193b6da23 ! 14:  88640ea13f9 Add GIT_DEBUG_REFS debugging mechanism
     @@ refs/debug.c (new)
      +static void print_update(int i, const char *refname,
      +			 const struct object_id *old_oid,
      +			 const struct object_id *new_oid, unsigned int flags,
     -+			 unsigned int type)
     ++			 unsigned int type, const char *msg)
      +{
      +	char o[200] = "null";
      +	char n[200] = "null";
     @@ refs/debug.c (new)
      +	type &= 0xf; /* see refs.h REF_* */
      +	flags &= REF_HAVE_NEW | REF_HAVE_OLD | REF_NO_DEREF |
      +		 REF_FORCE_CREATE_REFLOG | REF_LOG_ONLY;
     -+	printf("%d: %s %s -> %s (F=0x%x, T=0x%x)\n", i, refname, o, n, flags,
     -+	       type);
     ++	printf("%d: %s %s -> %s (F=0x%x, T=0x%x) \"%s\"\n", i, refname, o, n,
     ++	       flags, type, msg);
      +}
      +
      +static void print_transaction(struct ref_transaction *transaction)
     @@ refs/debug.c (new)
      +	for (int i = 0; i < transaction->nr; i++) {
      +		struct ref_update *u = transaction->updates[i];
      +		print_update(i, u->refname, &u->old_oid, &u->new_oid, u->flags,
     -+			     u->type);
     ++			     u->type, u->msg);
      +	}
      +	printf("}\n");
      +}
     @@ refs/debug.c (new)
      +}
      +
      +static int debug_create_symref(struct ref_store *ref_store,
     -+			       const char *ref_target,
     -+			       const char *refs_heads_master,
     ++			       const char *ref_name, const char *target,
      +			       const char *logmsg)
      +{
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
     -+	int res = drefs->refs->be->create_symref(drefs->refs, ref_target,
     -+						 refs_heads_master, logmsg);
     ++	int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
     ++						 logmsg);
     ++	printf("create_symref: %s -> %s \"%s\": %d\n", ref_name, target, logmsg,
     ++	       res);
      +	return res;
      +}
      +
     @@ refs/debug.c (new)
      +		drefs->refs->be->delete_refs(drefs->refs, msg, refnames, flags);
      +	return res;
      +}
     ++
      +static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
      +			    const char *newref, const char *logmsg)
      +{
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
      +	int res = drefs->refs->be->rename_ref(drefs->refs, oldref, newref,
      +					      logmsg);
     ++	printf("rename_ref: %s -> %s \"%s\": %d\n", oldref, newref, logmsg,
     ++	       res);
      +	return res;
      +}
     ++
      +static int debug_copy_ref(struct ref_store *ref_store, const char *oldref,
      +			  const char *newref, const char *logmsg)
      +{
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
      +	int res =
      +		drefs->refs->be->copy_ref(drefs->refs, oldref, newref, logmsg);
     ++	printf("copy_ref: %s -> %s \"%s\": %d\n", oldref, newref, logmsg, res);
      +	return res;
      +}
      +
     @@ refs/debug.c (new)
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
      +	struct ref_iterator *res =
      +		drefs->refs->be->reflog_iterator_begin(drefs->refs);
     ++	printf("for_each_reflog_iterator_begin\n");
      +	return res;
      +}
      +
     ++struct debug_reflog {
     ++	const char *refname;
     ++	each_reflog_ent_fn *fn;
     ++	void *cb_data;
     ++};
     ++
     ++static int debug_print_reflog_ent(struct object_id *old_oid,
     ++				  struct object_id *new_oid,
     ++				  const char *committer, timestamp_t timestamp,
     ++				  int tz, const char *msg, void *cb_data)
     ++{
     ++	struct debug_reflog *dbg = (struct debug_reflog *)cb_data;
     ++	int ret;
     ++	char o[100] = "null";
     ++	char n[100] = "null";
     ++	if (old_oid)
     ++		oid_to_hex_r(o, old_oid);
     ++	if (new_oid)
     ++		oid_to_hex_r(n, new_oid);
     ++
     ++	ret = dbg->fn(old_oid, new_oid, committer, timestamp, tz, msg,
     ++		      dbg->cb_data);
     ++	printf("reflog_ent %s (ret %d): %s -> %s, %s %ld \"%s\"\n",
     ++	       dbg->refname, ret, o, n, committer, (long int)timestamp, msg);
     ++	return ret;
     ++}
     ++
      +static int debug_for_each_reflog_ent(struct ref_store *ref_store,
      +				     const char *refname, each_reflog_ent_fn fn,
      +				     void *cb_data)
      +{
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
     -+	int res = drefs->refs->be->for_each_reflog_ent(drefs->refs, refname, fn,
     -+						       cb_data);
     ++	struct debug_reflog dbg = {
     ++		.refname = refname,
     ++		.fn = fn,
     ++		.cb_data = cb_data,
     ++	};
     ++
     ++	int res = drefs->refs->be->for_each_reflog_ent(
     ++		drefs->refs, refname, &debug_print_reflog_ent, &dbg);
     ++	printf("for_each_reflog: %s: %d\n", refname, res);
      +	return res;
      +}
     ++
      +static int debug_for_each_reflog_ent_reverse(struct ref_store *ref_store,
      +					     const char *refname,
      +					     each_reflog_ent_fn fn,
      +					     void *cb_data)
      +{
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
     ++	struct debug_reflog dbg = {
     ++		.refname = refname,
     ++		.fn = fn,
     ++		.cb_data = cb_data,
     ++	};
      +	int res = drefs->refs->be->for_each_reflog_ent_reverse(
     -+		drefs->refs, refname, fn, cb_data);
     ++		drefs->refs, refname, &debug_print_reflog_ent, &dbg);
     ++	printf("for_each_reflog_reverse: %s: %d\n", refname, res);
      +	return res;
      +}
      +
     @@ refs/debug.c (new)
      +{
      +	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
      +	int res = drefs->refs->be->reflog_exists(drefs->refs, refname);
     ++	printf("reflog_exists: %s: %d\n", refname, res);
      +	return res;
      +}
      +
     @@ t/t0033-debug-refs.sh (new)
      +test_expect_success 'GIT_DEBUG_REFS' '
      +	git init --ref-storage=files files &&
      +	git init --ref-storage=reftable reftable &&
     -+	(cd files && GIT_DEBUG_REFS=1 test_commit message file) > files.txt && 
     ++	(cd files && GIT_DEBUG_REFS=1 test_commit message file) > files.txt &&
      +	(cd reftable && GIT_DEBUG_REFS=1 test_commit message file) > reftable.txt &&
      +	test_cmp files.txt reftable.txt
      +'
 13:  0471f7e3570 = 15:  1c0cc646084 vcxproj: adjust for the reftable changes
 14:  a03c882b075 ! 16:  4f24b5f73de Add reftable testing infrastructure

@@ Commit message
* t1404-update-ref-errors.sh - Manipulates .git/refs/ directly
* t1405 - inspecs .git/ directly.

- Worst offenders:
-
- t1400-update-ref.sh - 82 of 185
- t2400-worktree-add.sh - 58 of 69
- t1404-update-ref-errors.sh - 44 of 53
- t3514-cherry-pick-revert-gpg.sh - 36 of 36
- t5541-http-push-smart.sh - 29 of 38
- t6003-rev-list-topo-order.sh - 29 of 35
- t3420-rebase-autostash.sh - 28 of 42
- t6120-describe.sh - 21 of 82
- t3430-rebase-merges.sh - 18 of 24
- t2018-checkout-branch.sh - 15 of 22
- ..
-

## builtin/clone.c ##
-: ----------- > 17: ad5658ffc51 Add "test-tool dump-reftable" command.

Signature · 1 line
gitgitgadget