[PATCH v5 00/15] reftable library
- From
Han-Wen Nienhuys via GitGitGadget <gitgitgadget@gmail.com>
- Date
- Mar 12, 2021, 20:19 UTC
- Message-ID
- <pull.847.v5.git.git.1615580397.gitgitgadget@gmail.com>
- In-Reply-To
- <pull.847.v4.git.git.1607522429.gitgitgadget@gmail.com>
This splits the giant commit from https://github.com/gitgitgadget/git/pull/539 into a series of smaller commits, which build and have unittests.
The final commit should also be split up, but I want to wait until we have consensus that the bottom commits look good.
version 12 Mar 2021:
* tagged unions for reftable_log_record * some fixups by jun
Based on github.com/hanwen/reftable at c33a40ad23dd622d8c72139c41a2be1cfa063e5f Add random suffix to table names
Han-Wen Nienhuys (14): init-db: set the_repository->hash_algo early on reftable: add LICENSE reftable: add error related functionality reftable: utility functions reftable: add blocksource, an abstraction for random access reads reftable: (de)serialization for the polymorphic record type. reftable: reading/writing blocks reftable: a generic binary tree implementation reftable: write reftable files reftable: read reftable files reftable: reftable file level tests reftable: rest of library Reftable support for git-core Add "test-tool dump-reftable" command.
SZEDER Gábor (1): git-prompt: prepare for reftable refs backend
Documentation/config/extensions.txt | 9 + .../technical/repository-version.txt | 7 + Makefile | 46 +- builtin/clone.c | 5 +- builtin/init-db.c | 76 +- builtin/worktree.c | 27 +- cache.h | 8 +- config.mak.uname | 2 +- contrib/buildsystems/CMakeLists.txt | 14 +- contrib/buildsystems/Generators/Vcxproj.pm | 11 +- contrib/completion/git-prompt.sh | 7 +- refs.c | 27 +- refs.h | 3 + refs/refs-internal.h | 1 + refs/reftable-backend.c | 1446 +++++++++++++++++ reftable/.gitattributes | 1 + reftable/LICENSE | 31 + reftable/VERSION | 1 + reftable/basics.c | 128 ++ reftable/basics.h | 60 + reftable/basics_test.c | 98 ++ reftable/block.c | 440 +++++ reftable/block.h | 127 ++ reftable/block_test.c | 121 ++ reftable/blocksource.c | 148 ++ reftable/blocksource.h | 22 + reftable/constants.h | 21 + reftable/dump.c | 213 +++ reftable/error.c | 41 + reftable/iter.c | 248 +++ reftable/iter.h | 75 + reftable/merged.c | 366 +++++ reftable/merged.h | 35 + reftable/merged_test.c | 343 ++++ reftable/pq.c | 115 ++ reftable/pq.h | 32 + reftable/publicbasics.c | 58 + reftable/reader.c | 733 +++++++++ reftable/reader.h | 75 + reftable/record.c | 1203 ++++++++++++++ reftable/record.h | 139 ++ reftable/record_test.c | 405 +++++ reftable/refname.c | 209 +++ reftable/refname.h | 29 + reftable/refname_test.c | 102 ++ reftable/reftable-blocksource.h | 49 + reftable/reftable-error.h | 62 + reftable/reftable-generic.h | 48 + reftable/reftable-iterator.h | 37 + reftable/reftable-malloc.h | 18 + reftable/reftable-merged.h | 68 + reftable/reftable-reader.h | 89 + reftable/reftable-record.h | 114 ++ reftable/reftable-stack.h | 120 ++ reftable/reftable-tests.h | 22 + reftable/reftable-writer.h | 147 ++ reftable/reftable.c | 98 ++ reftable/reftable_test.c | 583 +++++++ reftable/stack.c | 1260 ++++++++++++++ reftable/stack.h | 41 + reftable/stack_test.c | 809 +++++++++ reftable/system.h | 32 + reftable/test_framework.c | 23 + reftable/test_framework.h | 53 + reftable/tree.c | 63 + reftable/tree.h | 34 + reftable/tree_test.c | 61 + reftable/writer.c | 691 ++++++++ reftable/writer.h | 50 + reftable/zlib-compat.c | 92 ++ repository.c | 2 + repository.h | 3 + setup.c | 9 +- t/helper/test-reftable.c | 20 + t/helper/test-tool.c | 4 +- t/helper/test-tool.h | 2 + t/t0031-reftable.sh | 203 +++ t/t0032-reftable-unittest.sh | 15 + t/t1409-avoid-packing-refs.sh | 6 + t/t1450-fsck.sh | 6 + t/t3210-pack-refs.sh | 6 + t/test-lib.sh | 5 + 82 files changed, 12213 insertions(+), 40 deletions(-) create mode 100644 refs/reftable-backend.c create mode 100644 reftable/.gitattributes create mode 100644 reftable/LICENSE create mode 100644 reftable/VERSION create mode 100644 reftable/basics.c create mode 100644 reftable/basics.h create mode 100644 reftable/basics_test.c create mode 100644 reftable/block.c create mode 100644 reftable/block.h create mode 100644 reftable/block_test.c create mode 100644 reftable/blocksource.c create mode 100644 reftable/blocksource.h create mode 100644 reftable/constants.h create mode 100644 reftable/dump.c create mode 100644 reftable/error.c create mode 100644 reftable/iter.c create mode 100644 reftable/iter.h create mode 100644 reftable/merged.c create mode 100644 reftable/merged.h create mode 100644 reftable/merged_test.c create mode 100644 reftable/pq.c create mode 100644 reftable/pq.h create mode 100644 reftable/publicbasics.c create mode 100644 reftable/reader.c create mode 100644 reftable/reader.h create mode 100644 reftable/record.c create mode 100644 reftable/record.h create mode 100644 reftable/record_test.c create mode 100644 reftable/refname.c create mode 100644 reftable/refname.h create mode 100644 reftable/refname_test.c create mode 100644 reftable/reftable-blocksource.h create mode 100644 reftable/reftable-error.h create mode 100644 reftable/reftable-generic.h create mode 100644 reftable/reftable-iterator.h create mode 100644 reftable/reftable-malloc.h create mode 100644 reftable/reftable-merged.h create mode 100644 reftable/reftable-reader.h create mode 100644 reftable/reftable-record.h create mode 100644 reftable/reftable-stack.h create mode 100644 reftable/reftable-tests.h create mode 100644 reftable/reftable-writer.h create mode 100644 reftable/reftable.c create mode 100644 reftable/reftable_test.c create mode 100644 reftable/stack.c create mode 100644 reftable/stack.h create mode 100644 reftable/stack_test.c create mode 100644 reftable/system.h create mode 100644 reftable/test_framework.c create mode 100644 reftable/test_framework.h create mode 100644 reftable/tree.c create mode 100644 reftable/tree.h create mode 100644 reftable/tree_test.c create mode 100644 reftable/writer.c create mode 100644 reftable/writer.h create mode 100644 reftable/zlib-compat.c create mode 100644 t/helper/test-reftable.c create mode 100755 t/t0031-reftable.sh create mode 100755 t/t0032-reftable-unittest.sh
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-847%2Fhanwen%2Flibreftable-v5 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-847/hanwen/libreftable-v5 Pull-Request: https://github.com/git/git/pull/847
Range-diff vs v4:
1: 40ac041d0efe ! 1: e1d1d9f49807 init-db: set the_repository->hash_algo early on
@@ builtin/init-db.c: int init_db(const char *git_dir, const char *real_git_dir,
+ repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
+
reinit = create_default_files(template_dir, original_git_dir,
- initial_branch, &repo_fmt);
- if (reinit && initial_branch)
+ initial_branch, &repo_fmt,
+ flags & INIT_DB_QUIET);
2: cc0fe58bd333 = 2: 502a66befabe reftable: add LICENSE
3: 798f680b800b = 3: 1bd136f6a6df reftable: add error related functionality
4: 2f55ff6d2406 ! 4: 5bbbafb428e9 reftable: utility functions
@@ t/helper/test-reftable.c (new)
## t/helper/test-tool.c ##
@@ t/helper/test-tool.c: static struct test_cmd cmds[] = {
- { "path-utils", cmd__path_utils },
+ { "pcre2-config", cmd__pcre2_config },
{ "pkt-line", cmd__pkt_line },
{ "prio-queue", cmd__prio_queue },
- { "proc-receive", cmd__proc_receive},
5: aaa817f8598b = 5: e41fa780882d reftable: add blocksource, an abstraction for random access reads
6: e13afe315258 ! 6: 390eaca4fc8c reftable: (de)serialization for the polymorphic record type.
@@ reftable/record.c (new)
+{
+ char hex[SHA256_SIZE + 1] = { 0 };
+
-+ printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", log->refname,
-+ log->update_index, log->name, log->email, log->time,
-+ log->tz_offset);
-+ hex_format(hex, log->old_hash, hash_size(hash_id));
-+ printf("%s => ", hex);
-+ hex_format(hex, log->new_hash, hash_size(hash_id));
-+ printf("%s\n\n%s\n}\n", hex, log->message);
++ switch (log->value_type) {
++ case REFTABLE_LOG_DELETION:
++ printf("log{%s(%" PRIu64 ") delete", log->refname,
++ log->update_index);
++ break;
++ case REFTABLE_LOG_UPDATE:
++ printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
++ log->refname, log->update_index, log->update.name,
++ log->update.email, log->update.time,
++ log->update.tz_offset);
++ hex_format(hex, log->update.old_hash, hash_size(hash_id));
++ printf("%s => ", hex);
++ hex_format(hex, log->update.new_hash, hash_size(hash_id));
++ printf("%s\n\n%s\n}\n", hex, log->update.message);
++ break;
++ }
+}
+
+static void reftable_log_record_key(const void *r, struct strbuf *dest)
@@ reftable/record.c (new)
+ if (dst->refname != NULL) {
+ dst->refname = xstrdup(dst->refname);
+ }
-+ if (dst->email != NULL) {
-+ dst->email = xstrdup(dst->email);
-+ }
-+ if (dst->name != NULL) {
-+ dst->name = xstrdup(dst->name);
-+ }
-+ if (dst->message != NULL) {
-+ dst->message = xstrdup(dst->message);
-+ }
++ switch (dst->value_type) {
++ case REFTABLE_LOG_DELETION:
++ break;
++ case REFTABLE_LOG_UPDATE:
++ if (dst->update.email != NULL) {
++ dst->update.email = xstrdup(dst->update.email);
++ }
++ if (dst->update.name != NULL) {
++ dst->update.name = xstrdup(dst->update.name);
++ }
++ if (dst->update.message != NULL) {
++ dst->update.message = xstrdup(dst->update.message);
++ }
+
-+ if (dst->new_hash != NULL) {
-+ dst->new_hash = reftable_malloc(hash_size);
-+ memcpy(dst->new_hash, src->new_hash, hash_size);
-+ }
-+ if (dst->old_hash != NULL) {
-+ dst->old_hash = reftable_malloc(hash_size);
-+ memcpy(dst->old_hash, src->old_hash, hash_size);
++ if (dst->update.new_hash != NULL) {
++ dst->update.new_hash = reftable_malloc(hash_size);
++ memcpy(dst->update.new_hash, src->update.new_hash,
++ hash_size);
++ }
++ if (dst->update.old_hash != NULL) {
++ dst->update.old_hash = reftable_malloc(hash_size);
++ memcpy(dst->update.old_hash, src->update.old_hash,
++ hash_size);
++ }
++ break;
+ }
+}
+
@@ reftable/record.c (new)
+void reftable_log_record_release(struct reftable_log_record *r)
+{
+ reftable_free(r->refname);
-+ reftable_free(r->new_hash);
-+ reftable_free(r->old_hash);
-+ reftable_free(r->name);
-+ reftable_free(r->email);
-+ reftable_free(r->message);
++ switch (r->value_type) {
++ case REFTABLE_LOG_DELETION:
++ break;
++ case REFTABLE_LOG_UPDATE:
++ reftable_free(r->update.new_hash);
++ reftable_free(r->update.old_hash);
++ reftable_free(r->update.name);
++ reftable_free(r->update.email);
++ reftable_free(r->update.message);
++ break;
++ }
+ memset(r, 0, sizeof(struct reftable_log_record));
+}
+
@@ reftable/record.c (new)
+ struct reftable_log_record *r = (struct reftable_log_record *)rec;
+ struct string_view start = s;
+ int n = 0;
-+ uint8_t *oldh = r->old_hash;
-+ uint8_t *newh = r->new_hash;
++ uint8_t *oldh = NULL;
++ uint8_t *newh = NULL;
+ if (reftable_log_record_is_deletion(r))
+ return 0;
+
++ oldh = r->update.old_hash;
++ newh = r->update.new_hash;
+ if (oldh == NULL) {
+ oldh = zero;
+ }
@@ reftable/record.c (new)
+ memcpy(s.buf + hash_size, newh, hash_size);
+ string_view_consume(&s, 2 * hash_size);
+
-+ n = encode_string(r->name ? r->name : "", s);
++ n = encode_string(r->update.name ? r->update.name : "", s);
+ if (n < 0)
+ return -1;
+ string_view_consume(&s, n);
+
-+ n = encode_string(r->email ? r->email : "", s);
++ n = encode_string(r->update.email ? r->update.email : "", s);
+ if (n < 0)
+ return -1;
+ string_view_consume(&s, n);
+
-+ n = put_var_int(&s, r->time);
++ n = put_var_int(&s, r->update.time);
+ if (n < 0)
+ return -1;
+ string_view_consume(&s, n);
@@ reftable/record.c (new)
+ if (s.len < 2)
+ return -1;
+
-+ put_be16(s.buf, r->tz_offset);
++ put_be16(s.buf, r->update.tz_offset);
+ string_view_consume(&s, 2);
+
-+ n = encode_string(r->message ? r->message : "", s);
++ n = encode_string(r->update.message ? r->update.message : "", s);
+ if (n < 0)
+ return -1;
+ string_view_consume(&s, n);
@@ reftable/record.c (new)
+
+ r->update_index = (~max) - ts;
+
-+ if (val_type == 0) {
-+ FREE_AND_NULL(r->old_hash);
-+ FREE_AND_NULL(r->new_hash);
-+ FREE_AND_NULL(r->message);
-+ FREE_AND_NULL(r->email);
-+ FREE_AND_NULL(r->name);
-+ return 0;
++ if (val_type != r->value_type) {
++ switch (r->value_type) {
++ case REFTABLE_LOG_UPDATE:
++ FREE_AND_NULL(r->update.old_hash);
++ FREE_AND_NULL(r->update.new_hash);
++ FREE_AND_NULL(r->update.message);
++ FREE_AND_NULL(r->update.email);
++ FREE_AND_NULL(r->update.name);
++ break;
++ case REFTABLE_LOG_DELETION:
++ break;
++ }
+ }
+
++ r->value_type = val_type;
++ if (val_type == REFTABLE_LOG_DELETION)
++ return 0;
++
+ 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);
++ r->update.old_hash = reftable_realloc(r->update.old_hash, hash_size);
++ r->update.new_hash = reftable_realloc(r->update.new_hash, hash_size);
+
-+ memcpy(r->old_hash, in.buf, hash_size);
-+ memcpy(r->new_hash, in.buf + hash_size, hash_size);
++ memcpy(r->update.old_hash, in.buf, hash_size);
++ memcpy(r->update.new_hash, in.buf + hash_size, hash_size);
+
+ string_view_consume(&in, 2 * hash_size);
+
@@ reftable/record.c (new)
+ goto done;
+ string_view_consume(&in, n);
+
-+ r->name = reftable_realloc(r->name, dest.len + 1);
-+ memcpy(r->name, dest.buf, dest.len);
-+ r->name[dest.len] = 0;
++ r->update.name = reftable_realloc(r->update.name, dest.len + 1);
++ memcpy(r->update.name, dest.buf, dest.len);
++ r->update.name[dest.len] = 0;
+
+ strbuf_reset(&dest);
+ n = decode_string(&dest, in);
@@ reftable/record.c (new)
+ goto done;
+ string_view_consume(&in, n);
+
-+ r->email = reftable_realloc(r->email, dest.len + 1);
-+ memcpy(r->email, dest.buf, dest.len);
-+ r->email[dest.len] = 0;
++ r->update.email = reftable_realloc(r->update.email, dest.len + 1);
++ memcpy(r->update.email, dest.buf, dest.len);
++ r->update.email[dest.len] = 0;
+
+ ts = 0;
+ n = get_var_int(&ts, &in);
+ if (n < 0)
+ goto done;
+ string_view_consume(&in, n);
-+ r->time = ts;
++ r->update.time = ts;
+ if (in.len < 2)
+ goto done;
+
-+ r->tz_offset = get_be16(in.buf);
++ r->update.tz_offset = get_be16(in.buf);
+ string_view_consume(&in, 2);
+
+ strbuf_reset(&dest);
@@ reftable/record.c (new)
+ goto done;
+ string_view_consume(&in, n);
+
-+ r->message = reftable_realloc(r->message, dest.len + 1);
-+ memcpy(r->message, dest.buf, dest.len);
-+ r->message[dest.len] = 0;
++ r->update.message = reftable_realloc(r->update.message, dest.len + 1);
++ memcpy(r->update.message, dest.buf, dest.len);
++ r->update.message[dest.len] = 0;
+
+ strbuf_release(&dest);
+ return start.len - in.len;
@@ reftable/record.c (new)
+int reftable_log_record_equal(struct reftable_log_record *a,
+ struct reftable_log_record *b, int hash_size)
+{
-+ return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
-+ null_streq(a->message, b->message) &&
-+ zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
-+ zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
-+ a->time == b->time && a->tz_offset == b->tz_offset &&
-+ a->update_index == b->update_index;
++ if (!(null_streq(a->refname, b->refname) &&
++ a->update_index == b->update_index &&
++ a->value_type == b->value_type))
++ return 0;
++
++ switch (a->value_type) {
++ case REFTABLE_LOG_DELETION:
++ return 1;
++ case REFTABLE_LOG_UPDATE:
++ return null_streq(a->update.name, b->update.name) &&
++ a->update.time == b->update.time &&
++ a->update.tz_offset == b->update.tz_offset &&
++ null_streq(a->update.email, b->update.email) &&
++ null_streq(a->update.message, b->update.message) &&
++ zero_hash_eq(a->update.old_hash, b->update.old_hash,
++ hash_size) &&
++ zero_hash_eq(a->update.new_hash, b->update.new_hash,
++ hash_size);
++ }
++
++ abort();
+}
+
+static int reftable_log_record_is_deletion_void(const void *p)
@@ reftable/record.c (new)
+
+int reftable_log_record_is_deletion(const struct reftable_log_record *log)
+{
-+ return (log->new_hash == NULL && log->old_hash == NULL &&
-+ log->name == NULL && log->email == NULL &&
-+ log->message == NULL && log->time == 0 && log->tz_offset == 0 &&
-+ log->message == NULL);
++ return (log->value_type == REFTABLE_LOG_DELETION);
+}
+
+void string_view_consume(struct string_view *s, int n)
@@ reftable/record.h (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"
@@ reftable/record_test.c (new)
+ struct reftable_log_record in[2] = {
+ {
+ .refname = xstrdup("refs/heads/master"),
-+ .old_hash = reftable_malloc(SHA1_SIZE),
-+ .new_hash = reftable_malloc(SHA1_SIZE),
-+ .name = xstrdup("han-wen"),
-+ .email = xstrdup("hanwen@google.com"),
-+ .message = xstrdup("test"),
+ .update_index = 42,
-+ .time = 1577123507,
-+ .tz_offset = 100,
++ .value_type = REFTABLE_LOG_UPDATE,
++ .update = {
++ .old_hash = reftable_malloc(SHA1_SIZE),
++ .new_hash = reftable_malloc(SHA1_SIZE),
++ .name = xstrdup("han-wen"),
++ .email = xstrdup("hanwen@google.com"),
++ .message = xstrdup("test"),
++ .time = 1577123507,
++ .tz_offset = 100,
++ }
+ },
+ {
+ .refname = xstrdup("refs/heads/master"),
+ .update_index = 22,
++ .value_type = REFTABLE_LOG_DELETION,
+ }
+ };
-+ set_test_hash(in[0].new_hash, 1);
-+ set_test_hash(in[0].old_hash, 2);
++ set_test_hash(in[0].update.new_hash, 1);
++ set_test_hash(in[0].update.old_hash, 2);
+ for (int i = 0; i < ARRAY_SIZE(in); i++) {
+ struct reftable_record rec = { NULL };
+ struct strbuf key = STRBUF_INIT;
@@ reftable/record_test.c (new)
+ /* populate out, to check for leaks. */
+ struct reftable_log_record out = {
+ .refname = 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"),
++ .value_type = REFTABLE_LOG_UPDATE,
++ .update = {
++ .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 = { NULL };
+ int n, m, valtype;
@@ reftable/reftable-record.h (new)
+ char *refname;
+ uint64_t update_index; /* logical timestamp of a transactional update.
+ */
-+ uint8_t *new_hash;
-+ uint8_t *old_hash;
-+ char *name;
-+ char *email;
-+ uint64_t time;
-+ int16_t tz_offset;
-+ char *message;
++
++ enum {
++ /* tombstone to hide deletions from earlier tables */
++ REFTABLE_LOG_DELETION = 0x0,
++
++ /* a simple update */
++ REFTABLE_LOG_UPDATE = 0x1,
++#define REFTABLE_NR_LOG_VALUETYPES 2
++ } value_type;
++
++ union {
++ struct {
++ uint8_t *new_hash;
++ uint8_t *old_hash;
++ char *name;
++ char *email;
++ uint64_t time;
++ int16_t tz_offset;
++ char *message;
++ } update;
++ };
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
7: a48b9937642e = 7: b108525009d9 reftable: reading/writing blocks
8: 6968dbc3828f = 8: b6eed7283aac reftable: a generic binary tree implementation
9: ff5b424d12fb ! 9: f2f005afca19 reftable: write reftable files
@@ reftable/writer.c (new)
+ return err;
+}
+
-+int reftable_writer_add_log(struct reftable_writer *w,
-+ struct reftable_log_record *log)
++static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
++ struct reftable_log_record *log)
+{
+ struct reftable_record rec = { NULL };
-+ char *input_log_message = log->message;
-+ struct strbuf cleaned_message = STRBUF_INIT;
-+ int err;
-+ if (log->refname == 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);
@@ reftable/writer.c (new)
+ return err;
+ }
+
-+ if (!w->opts.exact_log_message && log->message != NULL) {
-+ strbuf_addstr(&cleaned_message, log->message);
++ w->next -= w->pending_padding;
++ w->pending_padding = 0;
++
++ reftable_record_from_log(&rec, log);
++ return writer_add_record(w, &rec);
++}
++
++int reftable_writer_add_log(struct reftable_writer *w,
++ struct reftable_log_record *log)
++{
++ char *input_log_message = NULL;
++ struct strbuf cleaned_message = STRBUF_INIT;
++ int err = 0;
++
++ if (log->value_type == REFTABLE_LOG_DELETION)
++ return reftable_writer_add_log_verbatim(w, log);
++
++ if (log->refname == NULL)
++ return REFTABLE_API_ERROR;
++
++ input_log_message = log->update.message;
++ if (!w->opts.exact_log_message && log->update.message != NULL) {
++ strbuf_addstr(&cleaned_message, log->update.message);
+ while (cleaned_message.len &&
+ cleaned_message.buf[cleaned_message.len - 1] == '\n')
+ strbuf_setlen(&cleaned_message,
@@ reftable/writer.c (new)
+ goto done;
+ }
+ strbuf_addstr(&cleaned_message, "\n");
-+ log->message = cleaned_message.buf;
++ log->update.message = cleaned_message.buf;
+ }
+
-+ w->next -= w->pending_padding;
-+ w->pending_padding = 0;
-+
-+ reftable_record_from_log(&rec, log);
-+ err = writer_add_record(w, &rec);
-+
++ err = reftable_writer_add_log_verbatim(w, log);
++ log->update.message = input_log_message;
+done:
-+ log->message = input_log_message;
+ strbuf_release(&cleaned_message);
+ return err;
+}
10: 3c2de7dcc65c = 10: 1f17d5edab52 reftable: read reftable files
11: 03681b820d13 ! 11: c916629c562a reftable: reftable file level tests
@@ reftable/reftable_test.c (new)
+ snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
+
+ log.refname = name;
-+ log.new_hash = hash;
+ log.update_index = update_index;
-+ log.message = "message";
++ log.value_type = REFTABLE_LOG_UPDATE;
++ log.update.new_hash = hash;
++ log.update.message = "message";
+
+ n = reftable_writer_add_log(w, &log);
+ EXPECT(n == 0);
@@ reftable/reftable_test.c (new)
+ .block_size = 4096,
+ };
+ int err;
-+ struct reftable_log_record log = {
-+ .refname = "refs/heads/master",
-+ .name = "Han-Wen Nienhuys",
-+ .email = "hanwen@google.com",
-+ .tz_offset = 100,
-+ .time = 0x5e430672,
-+ .update_index = 0xa,
-+ .message = "commit: 9\n",
-+ };
++ struct reftable_log_record log = { .refname = "refs/heads/master",
++ .update_index = 0xa,
++ .value_type = REFTABLE_LOG_UPDATE,
++ .update = {
++ .name = "Han-Wen Nienhuys",
++ .email = "hanwen@google.com",
++ .tz_offset = 100,
++ .time = 0x5e430672,
++ .message = "commit: 9\n",
++ } };
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+
@@ reftable/reftable_test.c (new)
+ hash1[i] = (uint8_t)(rand() % 256);
+ hash2[i] = (uint8_t)(rand() % 256);
+ }
-+ log.old_hash = hash1;
-+ log.new_hash = hash2;
++ log.update.old_hash = hash1;
++ log.update.new_hash = hash2;
+ reftable_writer_set_limits(w, update_index, update_index);
+ err = reftable_writer_add_log(w, &log);
+ EXPECT_ERR(err);
@@ reftable/reftable_test.c (new)
+
+ log.refname = names[i];
+ log.update_index = i;
-+ log.old_hash = hash1;
-+ log.new_hash = hash2;
++ log.value_type = REFTABLE_LOG_UPDATE;
++ log.update.old_hash = hash1;
++ log.update.new_hash = hash2;
+
+ err = reftable_writer_add_log(w, &log);
+ EXPECT_ERR(err);
12: 557183d3e3e2 ! 12: 28aa69f7bbcb reftable: rest of library
@@ Makefile: REFTABLE_OBJS += reftable/error.o
## reftable/VERSION (new) ##
@@
-+9b4a54059db9a05c270c0a0587f245bc6868d576 C: use rand() rather than cobbled together random generator.
++a337d48d4d42d513d6baa33addc172f0e0e36288 C: use tagged union for reftable_log_record
## reftable/dump.c (new) ##
@@
@@ reftable/stack.c (new)
+ continue;
+ }
+
-+ if (config != NULL && config->time > 0 &&
-+ log.time < config->time) {
++ if (config != NULL && config->min_update_index > 0 &&
++ log.update_index < config->min_update_index) {
+ continue;
+ }
+
-+ if (config != NULL && config->min_update_index > 0 &&
-+ log.update_index < config->min_update_index) {
++ if (config != NULL && config->time > 0 &&
++ log.update.time < config->time) {
+ continue;
+ }
+
@@ reftable/stack_test.c (new)
+
+ logs[i].refname = 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);
++ logs[i].value_type = REFTABLE_LOG_UPDATE;
++
++ logs[i].update.new_hash = reftable_malloc(SHA1_SIZE);
++ logs[i].update.email = xstrdup("identity@invalid");
++ set_test_hash(logs[i].update.new_hash, i);
+ }
+
+ for (i = 0; i < N; i++) {
@@ reftable/stack_test.c (new)
+
+ uint8_t h1[SHA1_SIZE] = { 0x01 }, h2[SHA1_SIZE] = { 0x02 };
+
-+ struct reftable_log_record input = {
-+ .refname = "branch",
-+ .update_index = 1,
-+ .new_hash = h1,
-+ .old_hash = h2,
-+ };
++ struct reftable_log_record input = { .refname = "branch",
++ .update_index = 1,
++ .value_type = REFTABLE_LOG_UPDATE,
++ .update = {
++ .new_hash = h1,
++ .old_hash = h2,
++ } };
+ struct reftable_log_record dest = {
+ .update_index = 0,
+ };
@@ reftable/stack_test.c (new)
+ err = reftable_new_stack(&st, dir, cfg);
+ EXPECT_ERR(err);
+
-+ input.message = "one\ntwo";
++ input.update.message = "one\ntwo";
+ err = reftable_stack_add(st, &write_test_log, &arg);
+ EXPECT(err == REFTABLE_API_ERROR);
+
-+ input.message = "one";
++ input.update.message = "one";
+ err = reftable_stack_add(st, &write_test_log, &arg);
+ EXPECT_ERR(err);
+
+ err = reftable_stack_read_log(st, input.refname, &dest);
+ EXPECT_ERR(err);
-+ EXPECT(0 == strcmp(dest.message, "one\n"));
++ EXPECT(0 == strcmp(dest.update.message, "one\n"));
+
-+ input.message = "two\n";
++ input.update.message = "two\n";
+ arg.update_index = 2;
+ err = reftable_stack_add(st, &write_test_log, &arg);
+ EXPECT_ERR(err);
+ err = reftable_stack_read_log(st, input.refname, &dest);
+ EXPECT_ERR(err);
-+ EXPECT(0 == strcmp(dest.message, "two\n"));
++ EXPECT(0 == strcmp(dest.update.message, "two\n"));
+
+ /* cleanup */
+ reftable_stack_destroy(st);
@@ reftable/stack_test.c (new)
+ refs[i].value.val1 = reftable_malloc(SHA1_SIZE);
+ set_test_hash(refs[i].value.val1, i);
+ }
++
+ logs[i].refname = 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");
++ logs[i].value_type = REFTABLE_LOG_UPDATE;
++ logs[i].update.new_hash = reftable_malloc(SHA1_SIZE);
++ set_test_hash(logs[i].update.new_hash, i);
++ logs[i].update.email = xstrdup("identity@invalid");
+ }
+ }
+ for (i = 0; i < N; i++) {
@@ reftable/stack_test.c (new)
+
+ logs[i].refname = 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);
++ logs[i].value_type = REFTABLE_LOG_UPDATE;
++ logs[i].update.time = i;
++ logs[i].update.new_hash = reftable_malloc(SHA1_SIZE);
++ logs[i].update.email = xstrdup("identity@invalid");
++ set_test_hash(logs[i].update.new_hash, i);
+ }
+
+ for (i = 1; i <= N; i++) {
13: d57023d9f13d ! 13: bdb19af22cc7 Reftable support for git-core
@@ Commit message
Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+ Helped-by: Junio Hamano <gitster@pobox.com>
Co-authored-by: Jeff King <peff@peff.net>
## Documentation/config/extensions.txt ##
@@ builtin/init-db.c: int cmd_init_db(int argc, const char **argv, const char *pref
## builtin/worktree.c ##
@@
- #include "submodule.h"
#include "utf8.h"
#include "worktree.h"
+ #include "quote.h"
+#include "../refs/refs-internal.h"
static const char * const worktree_usage[] = {
@@ refs/reftable-backend.c (new)
+
+static void clear_reftable_log_record(struct reftable_log_record *log)
+{
-+ log->old_hash = NULL;
-+ log->new_hash = NULL;
-+ log->message = NULL;
+ log->refname = NULL;
++ switch (log->value_type) {
++ case REFTABLE_LOG_UPDATE:
++ log->update.old_hash = NULL;
++ log->update.new_hash = NULL;
++ log->update.message = NULL;
++ break;
++ case REFTABLE_LOG_DELETION:
++ break;
++ }
+ reftable_log_record_release(log);
+}
+
@@ refs/reftable-backend.c (new)
+ assert(0 == result);
+
+ reftable_log_record_release(log);
-+ log->name =
++ log->value_type = REFTABLE_LOG_UPDATE;
++ log->update.name =
+ xstrndup(split.name_begin, split.name_end - split.name_begin);
-+ log->email =
++ log->update.email =
+ xstrndup(split.mail_begin, split.mail_end - split.mail_begin);
-+ log->time = atol(split.date_begin);
++ log->update.time = atol(split.date_begin);
+ if (*split.tz_begin == '-') {
+ sign = -1;
+ split.tz_begin++;
@@ refs/reftable-backend.c (new)
+ split.tz_begin++;
+ }
+
-+ log->tz_offset = sign * atoi(split.tz_begin);
++ log->update.tz_offset = sign * atoi(split.tz_begin);
+}
+
+static int has_suffix(struct strbuf *b, const char *suffix)
@@ refs/reftable-backend.c (new)
+ struct ref_update *u = sorted[i];
+ struct reftable_log_record *log = &logs[i];
+ fill_reftable_log_record(log);
-+ log->refname = (char *)u->refname;
-+ log->old_hash = u->old_oid.hash;
-+ log->new_hash = u->new_oid.hash;
+ log->update_index = ts;
-+ log->message = u->msg;
++ log->value_type = REFTABLE_LOG_UPDATE;
++ log->refname = (char *)u->refname;
++ log->update.old_hash = u->old_oid.hash;
++ log->update.new_hash = u->new_oid.hash;
++ log->update.message = u->msg;
+
+ if (u->flags & REF_LOG_ONLY) {
+ continue;
@@ refs/reftable-backend.c (new)
+ };
+ struct reftable_ref_record current = { NULL };
+ fill_reftable_log_record(&log);
-+ log.message = xstrdup(arg->logmsg);
-+ log.new_hash = NULL;
-+ log.old_hash = NULL;
+ log.update_index = ts;
+ log.refname = (char *)arg->refnames->items[i].string;
+
++ log.update.message = xstrdup(arg->logmsg);
++ log.update.new_hash = NULL;
++ log.update.old_hash = NULL;
+ if (reftable_stack_read_ref(arg->stack, log.refname,
+ ¤t) == 0) {
-+ log.old_hash = reftable_ref_record_val1(¤t);
++ log.update.old_hash =
++ reftable_ref_record_val1(¤t);
+ }
+ err = reftable_writer_add_log(writer, &log);
-+ log.old_hash = NULL;
++ log.update.old_hash = NULL;
+ reftable_ref_record_release(¤t);
+
+ clear_reftable_log_record(&log);
@@ refs/reftable-backend.c (new)
+
+ fill_reftable_log_record(&log);
+ log.refname = (char *)create->refname;
-+ log.message = (char *)create->logmsg;
+ log.update_index = ts;
++ log.update.message = (char *)create->logmsg;
+ if (refs_resolve_ref_unsafe(
+ (struct ref_store *)create->refs, create->refname,
+ RESOLVE_REF_READING, &old_oid, NULL) != NULL) {
-+ log.old_hash = old_oid.hash;
++ log.update.old_hash = old_oid.hash;
+ }
+
+ if (refs_resolve_ref_unsafe((struct ref_store *)create->refs,
+ create->target, RESOLVE_REF_READING,
+ &new_oid, NULL) != NULL) {
-+ log.new_hash = new_oid.hash;
++ log.update.new_hash = new_oid.hash;
+ }
+
-+ if (log.old_hash != NULL || log.new_hash != NULL) {
++ if (log.update.old_hash != NULL ||
++ log.update.new_hash != NULL) {
+ err = reftable_writer_add_log(writer, &log);
+ }
+ log.refname = NULL;
-+ log.message = NULL;
-+ log.old_hash = NULL;
-+ log.new_hash = NULL;
++ log.update.message = NULL;
++ log.update.old_hash = NULL;
++ log.update.new_hash = NULL;
+ clear_reftable_log_record(&log);
+ }
+ return err;
@@ refs/reftable-backend.c (new)
+
+ todo[0].refname = (char *)arg->oldname;
+ todo[0].update_index = ts;
-+ todo[0].message = (char *)arg->logmsg;
-+ todo[0].old_hash = val1;
-+ todo[0].new_hash = NULL;
++ todo[0].update.message = (char *)arg->logmsg;
++ todo[0].update.old_hash = val1;
++ todo[0].update.new_hash = NULL;
+
+ todo[1].refname = (char *)arg->newname;
+ todo[1].update_index = ts;
-+ todo[1].old_hash = NULL;
-+ todo[1].new_hash = val1;
-+ todo[1].message = (char *)arg->logmsg;
++ todo[1].update.old_hash = NULL;
++ todo[1].update.new_hash = val1;
++ todo[1].update.message = (char *)arg->logmsg;
+
+ err = reftable_writer_add_logs(writer, todo, 2);
+
@@ refs/reftable-backend.c (new)
+
+ free(ri->last_name);
+ ri->last_name = xstrdup(ri->log.refname);
-+ hashcpy(ri->oid.hash, ri->log.new_hash);
++ hashcpy(ri->oid.hash, ri->log.update.new_hash);
+ return ITER_OK;
+ }
+}
@@ refs/reftable-backend.c (new)
+static struct ref_iterator *
+git_reftable_reflog_iterator_begin(struct ref_store *ref_store)
+{
-+ struct git_reftable_reflog_ref_iterator *ri = xcalloc(sizeof(*ri), 1);
++ struct git_reftable_reflog_ref_iterator *ri = xcalloc(1, sizeof(*ri));
+ struct git_reftable_ref_store *refs =
+ (struct git_reftable_ref_store *)ref_store;
+
@@ refs/reftable-backend.c (new)
+ break;
+ }
+
-+ hashcpy(old_oid.hash, log.old_hash);
-+ hashcpy(new_oid.hash, log.new_hash);
++ hashcpy(old_oid.hash, log.update.old_hash);
++ hashcpy(new_oid.hash, log.update.new_hash);
+
-+ full_committer = fmt_ident(log.name, log.email,
++ full_committer = fmt_ident(log.update.name, log.update.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);
++ err = fn(&old_oid, &new_oid, full_committer, log.update.time,
++ log.update.tz_offset, log.update.message, cb_data);
+ if (err)
+ break;
+ }
@@ refs/reftable-backend.c (new)
+ struct object_id new_oid;
+ const char *full_committer = "";
+
-+ hashcpy(old_oid.hash, log->old_hash);
-+ hashcpy(new_oid.hash, log->new_hash);
++ hashcpy(old_oid.hash, log->update.old_hash);
++ hashcpy(new_oid.hash, log->update.new_hash);
+
-+ full_committer = fmt_ident(log->name, log->email,
++ full_committer = fmt_ident(log->update.name, log->update.email,
+ WANT_COMMITTER_IDENT, NULL,
+ IDENT_NO_DATE);
-+ err = fn(&old_oid, &new_oid, full_committer, log->time,
-+ log->tz_offset, log->message, cb_data);
++ err = fn(&old_oid, &new_oid, full_committer, log->update.time,
++ log->update.tz_offset, log->update.message, cb_data);
+ if (err) {
+ break;
+ }
@@ refs/reftable-backend.c (new)
+ if (err > 0 || strcmp(log.refname, refname)) {
+ break;
+ }
-+ hashcpy(ooid.hash, log.old_hash);
-+ hashcpy(noid.hash, log.new_hash);
++ hashcpy(ooid.hash, log.update.old_hash);
++ hashcpy(noid.hash, log.update.new_hash);
+
-+ if (should_prune_fn(&ooid, &noid, log.email,
-+ (timestamp_t)log.time, log.tz_offset,
-+ log.message, policy_cb_data)) {
++ if (should_prune_fn(&ooid, &noid, log.update.email,
++ (timestamp_t)log.update.time,
++ log.update.tz_offset, log.update.message,
++ policy_cb_data)) {
+ add_log_tombstone(&arg, refname, log.update_index);
+ }
+ }
@@ t/t0031-reftable.sh (new)
+
+INVALID_SHA1=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
++git_init () {
++ git init -b primary "$@"
++}
++
+initialize () {
+ rm -rf .git &&
-+ git init --ref-storage=reftable &&
++ git_init --ref-storage=reftable &&
+ mv .git/hooks .git/hooks-disabled
+}
+
+test_expect_success 'SHA256 support, env' '
+ rm -rf .git &&
+ GIT_DEFAULT_HASH=sha256 && export GIT_DEFAULT_HASH &&
-+ git init --ref-storage=reftable &&
++ git_init --ref-storage=reftable &&
+ mv .git/hooks .git/hooks-disabled &&
+ test_commit file
+'
+
+test_expect_success 'SHA256 support, option' '
+ rm -rf .git &&
-+ git init --ref-storage=reftable --object-format=sha256 &&
++ git_init --ref-storage=reftable --object-format=sha256 &&
+ mv .git/hooks .git/hooks-disabled &&
+ test_commit file
+'
@@ t/t0031-reftable.sh (new)
+ initialize &&
+ test_commit file &&
+ SHA=$(git show-ref -s --verify HEAD) &&
-+ test_write_lines "$SHA refs/heads/master" "$SHA refs/tags/file" >expect &&
-+ git show-ref > actual &&
++ test_write_lines "$SHA refs/heads/primary" "$SHA refs/tags/file" >expect &&
++ git show-ref >actual &&
+ ! git update-ref -d refs/tags/file $INVALID_SHA1 &&
+ test_cmp expect actual &&
+ git update-ref -d refs/tags/file $SHA &&
-+ test_write_lines "$SHA refs/heads/master" >expect &&
-+ git show-ref > actual &&
++ test_write_lines "$SHA refs/heads/primary" >expect &&
++ git show-ref >actual &&
+ test_cmp expect actual
+'
+
@@ t/t0031-reftable.sh (new)
+test_expect_success 'basic operation of reftable storage: commit, show-ref' '
+ initialize &&
+ test_commit file &&
-+ test_write_lines refs/heads/master refs/tags/file >expect &&
++ test_write_lines refs/heads/primary refs/tags/file >expect &&
+ git show-ref &&
-+ git show-ref | cut -f2 -d" " > actual &&
++ git show-ref | cut -f2 -d" " >actual &&
+ test_cmp actual expect
+'
+
@@ t/t0031-reftable.sh (new)
+ git pack-refs &&
+ ls -1 .git/reftable >table-files &&
+ test_line_count = 2 table-files &&
-+ git reflog refs/heads/master >output &&
++ git reflog refs/heads/primary >output &&
+ test_line_count = 10 output &&
+ grep "commit (initial): number 1" output &&
+ grep "commit: number 10" output &&
+ git gc &&
-+ git reflog refs/heads/master >output &&
++ git reflog refs/heads/primary >output &&
+ test_line_count = 0 output
+'
+
@@ t/t0031-reftable.sh (new)
+ test_commit file2 &&
+ git checkout -b branch2 &&
+ git switch - &&
-+ git rev-parse --symbolic-full-name HEAD > actual &&
-+ echo refs/heads/branch1 > expect &&
++ git rev-parse --symbolic-full-name HEAD >actual &&
++ echo refs/heads/branch1 >expect &&
+ test_cmp actual expect
+'
+
@@ t/t0031-reftable.sh (new)
+ test_commit file &&
+ git tag -m "annotated tag" test_tag HEAD &&
+ {
-+ print_ref "refs/heads/master" &&
++ print_ref "refs/heads/primary" &&
+ print_ref "refs/tags/file" &&
+ print_ref "refs/tags/test_tag" &&
+ print_ref "refs/tags/test_tag^{}"
@@ t/t0031-reftable.sh (new)
+test_expect_success 'show-ref works on fresh repo' '
+ initialize &&
+ rm -rf .git &&
-+ git init --ref-storage=reftable &&
++ git_init --ref-storage=reftable &&
+ >expect &&
-+ ! git show-ref > actual &&
++ ! git show-ref >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout unborn branch' '
+ initialize &&
-+ git checkout -b master
++ git checkout -b primary
+'
+
+
+test_expect_success 'dir/file conflict' '
+ initialize &&
+ test_commit file &&
-+ ! git branch master/forbidden
++ ! git branch primary/forbidden
+'
+
+
+test_expect_success 'do not clobber existing repo' '
+ rm -rf .git &&
-+ git init --ref-storage=files &&
-+ cat .git/HEAD > expect &&
++ git_init --ref-storage=files &&
++ cat .git/HEAD >expect &&
+ test_commit file &&
-+ (git init --ref-storage=reftable || true) &&
-+ cat .git/HEAD > actual &&
++ (git_init --ref-storage=reftable || true) &&
++ cat .git/HEAD >actual &&
+ test_cmp expect actual
+'
+
@@ t/t0031-reftable.sh (new)
+'
+
+test_expect_success 'worktrees' '
-+ git init --ref-storage=reftable start &&
++ git_init --ref-storage=reftable start &&
+ (cd start && test_commit file1 && git checkout -b branch1 &&
+ git checkout -b branch2 &&
+ git worktree add ../wt
@@ t/t0031-reftable.sh (new)
+ initialize &&
+ test_commit file1 &&
+ mkdir existing_empty &&
-+ git worktree add --detach existing_empty master
++ git worktree add --detach existing_empty primary
+'
+
+test_expect_success 'FETCH_HEAD' '
+ initialize &&
+ test_commit one &&
-+ (git init sub && cd sub && test_commit two) &&
++ (git_init sub && cd sub && test_commit two) &&
+ git --git-dir sub/.git rev-parse HEAD >expect &&
+ git fetch sub &&
+ git checkout FETCH_HEAD &&
@@ t/t1450-fsck.sh: test_description='git fsck random collection of tests
git config i18n.commitencoding ISO-8859-1 &&
## t/t3210-pack-refs.sh ##
-@@ t/t3210-pack-refs.sh: semantic is still the same.
- '
+@@ t/t3210-pack-refs.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
. ./test-lib.sh
+if test_have_prereq REFTABLE
14: 9df5bc69f971 = 14: 0d9477941678 git-prompt: prepare for reftable refs backend
15: 4076ef5d20b4 = 15: 59f432522230 Add "test-tool dump-reftable" command.-- gitgitgadget