Reftable support git-core
Subsystems (2)
PATCH v14 1/9 Write pseudorefs through ref backends.
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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 a object ID or symref directly wrote 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 | 114 +++++++----------------------------------- refs.h | 11 ++++ refs/files-backend.c | 114 +++++++++++++++++++++++++++++++++++++++++- refs/packed-backend.c | 21 +++++++- refs/refs-internal.h | 12 +++++ 5 files changed, 173 insertions(+), 99 deletions(-)
refs.c +17 -97
@@ -739,101 +739,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 +750,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 ref_store_delete_pseudoref (refs, refname, old_oid); } transaction =ref_store_transaction_begin (refs, &err);
@@ -1172,7 +1077,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 =ref_store_write_pseudoref (refs, refname, new_oid, old_oid, + &err); }else { t =ref_store_transaction_begin (refs, &err); if (!t ||
@@ -1441,6 +1347,20 @@ 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) +{ +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) +{ +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
@@ -728,6 +728,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 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 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
@@ -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
@@ -549,6 +549,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
@@ -648,6 +657,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
PATCH v14 2/9 Move REF_LOG_ONLY to refs-internal.h
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v14 3/9 Iterate over the "refs/" namespace in for_each_[raw]ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -1445,7 +1445,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)
@@ -1505,8 +1505,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
PATCH v14 4/9 Add .gitattributes for the reftable/ directory
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- reftable/.gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 reftable/.gitattributes
reftable/.gitattributes new file +1 -0
@@ -0,0 +1 @@ +/zlib-compat.c whitespace=-indent-with-non-tab,-trailing-space
Signature · 1 line
gitgitgadget
PATCH v14 5/9 Add reftable library
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/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/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.
PATCH v14 6/9 Reftable support for git-core
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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:
* Resolve spots marked with 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 | 1313 +++++++++++++++++ repository.c | 2 + repository.h | 3 + setup.c | 12 +- t/t0031-reftable.sh | 136 ++ 13 files changed, 1566 insertions(+), 30 deletions(-) create mode 100644 refs/reftable-backend.c create mode 100755 t/t0031-reftable.sh
Documentation/technical/repository-version.txt +7 -0
@@ -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
@@ -810,6 +810,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
@@ -962,6 +963,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
@@ -1165,7 +1167,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)
@@ -2360,11 +2362,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
@@ -2505,6 +2525,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
@@ -3120,7 +3143,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
@@ -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
@@ -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
@@ -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) {
@@ -1712,13 +1718,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);
@@ -1734,7 +1740,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; }
@@ -1789,7 +1799,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);
@@ -1803,6 +1813,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;
@@ -1816,9 +1827,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
@@ -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
@@ -680,6 +680,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 +1313 -0
1,313 lines not on this page · raw message
repository.c +2 -0
@@ -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
@@ -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
@@ -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 +136 -0
136 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v14 7/9 Add GIT_DEBUG_REFS debugging mechanism
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -962,6 +962,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
@@ -1745,6 +1745,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
@@ -700,4 +700,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
@@ -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
PATCH v14 8/9 vcxproj: adjust for the reftable changes
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -710,7 +710,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
@@ -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
PATCH v14 9/9 Add reftable testing infrastructure
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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.
t6030-bisect-porcelain.sh - 62 of 72 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
..
--- 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
@@ -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
@@ -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
@@ -1743,7 +1743,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);
@@ -1802,7 +1802,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);
@@ -1816,7 +1816,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1514,6 +1514,11 @@ FreeBSD) ;; 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
v15 cover Reftable support git-core
In reply to Han-Wen Nienhuys via GitGitGadgetThis 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.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
PATCH v15 1/13 Write pseudorefs through ref backends.
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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v15 2/13 Make refs_ref_exists public
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- refs.c | 2 +- refs.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)
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 ); }
refs.h +2 -0
@@ -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
PATCH v15 3/13 Treat BISECT_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
Re: [PATCH v15 03/13] Treat BISECT_HEAD as a pseudo ref
Quoted 9 lines · Han-Wen Nienhuys via GitGitGadget
From: Han-Wen Nienhuys <[email protected]>
Both the
git-bisect.shas bisect--helper inspected the file system directly.Signed-off-by Han-Wen Nienhuys <[email protected]>--- 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.
PATCH v15 4/13 Treat CHERRY_PICK_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v15 5/13 Treat REVERT_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v15 6/13 Move REF_LOG_ONLY to refs-internal.h
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v15 7/13 Iterate over the "refs/" namespace in for_each_[raw]ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
PATCH v15 8/13 Add .gitattributes for the reftable/ directory
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- reftable/.gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 reftable/.gitattributes
reftable/.gitattributes new file +1 -0
@@ -0,0 +1 @@ +/zlib-compat.c whitespace=-indent-with-non-tab,-trailing-space
Signature · 1 line
gitgitgadget
PATCH v15 9/13 Add reftable library
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/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/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.
PATCH v15 10/13 Reftable support for git-core
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v15 11/13 Add GIT_DEBUG_REFS debugging mechanism
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v15 12/13 vcxproj: adjust for the reftable changes
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v15 13/13 Add reftable testing infrastructure
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
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
v16 cover Reftable support git-core
In reply to Han-Wen Nienhuys via GitGitGadgetThis 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(©, rec, SHA1_SIZE);
++ /* do it twice to catch memory leaks */
++ reftable_record_copy_from(©, rec, SHA1_SIZE);
++ switch (reftable_record_type(©)) {
++ case BLOCK_TYPE_REF:
++ assert(reftable_ref_record_equal(reftable_record_as_ref(©),
++ reftable_record_as_ref(rec),
++ SHA1_SIZE));
++ break;
++ case BLOCK_TYPE_LOG:
++ assert(reftable_log_record_equal(reftable_record_as_log(©),
++ reftable_record_as_log(rec),
++ SHA1_SIZE));
++ break;
++ }
++ reftable_record_destroy(©);
+}
+
-+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-style344 lines not shown · raw message
18 lines not shown · raw message
Part of this message is not shown here. View the raw message.
PATCH v16 1/14 Write pseudorefs through ref backends.
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
@@ -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
@@ -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
@@ -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
@@ -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
Re: [PATCH v16 01/14] Write pseudorefs through ref backends.
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_fnandupdate_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@@ -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
-0int parse_hide_refs_config (const char *var,const char *value,const char *);/* refs/files-backend.c +89
-0@@ -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@@ -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@@ -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;
PATCH v16 2/14 Make refs_ref_exists public
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- refs.c | 2 +- refs.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)
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 ); }
refs.h +2 -0
@@ -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
Re: [PATCH v16 02/14] Make refs_ref_exists public
Quoted 13 lines · Han-Wen Nienhuys via GitGitGadget
From: Han-Wen Nienhuys <[email protected]>
Signed-off-by Han-Wen Nienhuys <[email protected]>--- refs.c | 2 +- refs.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)refs.c +0
-0
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@@ -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);
Re: [PATCH v16 02/14] Make refs_ref_exists public
Quoted 18 lines · Phillip Wood
Quoted 16 lines · Han-Wen Nienhuys via GitGitGadget
From: Han-Wen Nienhuys <[email protected]>
Signed-off-by Han-Wen Nienhuys <[email protected]>--- refs.c | 2 +- refs.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)refs.c +0
-0@@ -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 usingdelete_ref()in my reply to the previous patch - it does not take a repository - maybe it should along withupdate_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
Re: [PATCH v16 02/14] Make refs_ref_exists public
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]>
Signed-off-by Han-Wen Nienhuys <[email protected]>--- refs.c | 2 +- refs.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)refs.c +0
-0@@ -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 usingdelete_ref()in my reply to the previous patch - it does not take a repository - maybe it should along withupdate_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
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]>
Signed-off-by Han-Wen Nienhuys <[email protected]>---
refs.c| 2 +-
refs.h| 2 ++2 files changed, 3 insertions(+), 1 deletion(-)refs.c +0
-0@@ -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. TheQuoted 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 usingdelete_ref()in my reply to the previous patch - it does not take a repository - maybe it should along withupdate_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_pathmechanism that is used to creategit_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 callsupdate_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 usingref_exists()anddelete_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
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_pathmechanism that is used to creategit_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 callsupdate_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 usingref_exists()anddelete_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()andref_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 convertingunlink()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
PATCH v16 3/14 Treat BISECT_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v16 4/14 Treat CHERRY_PICK_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v16 5/14 Treat REVERT_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v16 6/14 Move REF_LOG_ONLY to refs-internal.h
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v16 7/14 Iterate over the "refs/" namespace in for_each_[raw]ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
PATCH v16 8/14 Add .gitattributes for the reftable/ directory
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- reftable/.gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 reftable/.gitattributes
reftable/.gitattributes new file +1 -0
@@ -0,0 +1 @@ +/zlib-compat.c whitespace=-indent-with-non-tab,-trailing-space
Signature · 1 line
gitgitgadget
PATCH v16 9/14 Add reftable library
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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_test.c new file +99 -0
99 lines not on this page · 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/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_test.c new file +62 -0
62 lines not on this page · 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.
PATCH v16 10/14 Reftable support for git-core
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v16 11/14 Hookup unittests for the reftable library.
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
Re: [PATCH v16 11/14] Hookup unittests for the reftable library.
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 fromt/helpers/test-reftable.cint0031-reftable.shSigned-off-by Han-Wen Nienhuys <[email protected]>---
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?
PATCH 1/1 Fixup! Add t/helper/test-reftable.c
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
@@ -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
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.
PATCH v16 12/14 Add GIT_DEBUG_REFS debugging mechanism
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v16 13/14 vcxproj: adjust for the reftable changes
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v16 14/14 Add reftable testing infrastructure
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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]
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
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?
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.
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
@@ -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
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 useremove_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/pubranch 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
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 useremove_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/pubranch 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
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
v17 cover Reftable support git-core
In reply to Han-Wen Nienhuys via GitGitGadgetThis 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,
-+ ¤t);
+
+ 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
PATCH v17 1/17 lib-t6000.sh: write tag using git-update-ref
From: Han-Wen Nienhuys <[email protected]>
--- t/lib-t6000.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
t/lib-t6000.sh +2 -3
@@ -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
PATCH v17 2/17 checkout: add '\n' to reflog message
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
PATCH v17 3/17 Write pseudorefs through ref backends.
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v17 4/17 Make refs_ref_exists public
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- refs.c | 2 +- refs.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-)
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 ); }
refs.h +2 -0
@@ -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
PATCH v17 5/17 Treat BISECT_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v17 6/17 Treat CHERRY_PICK_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v17 7/17 Treat REVERT_HEAD as a pseudo ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v17 8/17 Move REF_LOG_ONLY to refs-internal.h
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
@@ -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
PATCH v17 9/17 Iterate over the "refs/" namespace in for_each_[raw]ref
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: 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
@@ -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
PATCH v17 10/17 Add .gitattributes for the reftable/ directory
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
--- reftable/.gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 reftable/.gitattributes
reftable/.gitattributes new file +1 -0
@@ -0,0 +1 @@ +/zlib-compat.c whitespace=-indent-with-non-tab,-trailing-space
Signature · 1 line
gitgitgadget