зеркало из https://github.com/microsoft/git.git
Merge branch 'hn/reftable'
The "reftable" backend for the refs API, without integrating into the refs subsystem, has been added. * hn/reftable: Add "test-tool dump-reftable" command. reftable: add dump utility reftable: implement stack, a mutable database of reftable files. reftable: implement refname validation reftable: add merged table view reftable: add a heap-based priority queue for reftable records reftable: reftable file level tests reftable: read reftable files reftable: generic interface to tables reftable: write reftable files reftable: a generic binary tree implementation reftable: reading/writing blocks Provide zlib's uncompress2 from compat/zlib-compat.c reftable: (de)serialization for the polymorphic record type. reftable: add blocksource, an abstraction for random access reads reftable: utility functions reftable: add error related functionality reftable: add LICENSE hash.h: provide constants for the hash IDs
This commit is contained in:
Коммит
a4bbd13be3
53
Makefile
53
Makefile
|
@ -256,6 +256,8 @@ all::
|
|||
#
|
||||
# Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
|
||||
#
|
||||
# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.
|
||||
#
|
||||
# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
|
||||
# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
|
||||
#
|
||||
|
@ -732,6 +734,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
|
||||
|
@ -810,6 +813,8 @@ TEST_SHELL_PATH = $(SHELL_PATH)
|
|||
|
||||
LIB_FILE = libgit.a
|
||||
XDIFF_LIB = xdiff/lib.a
|
||||
REFTABLE_LIB = reftable/libreftable.a
|
||||
REFTABLE_TEST_LIB = reftable/libreftable_test.a
|
||||
|
||||
GENERATED_H += command-list.h
|
||||
GENERATED_H += config-list.h
|
||||
|
@ -1189,7 +1194,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)
|
||||
|
@ -1720,6 +1725,11 @@ ifdef NO_DEFLATE_BOUND
|
|||
BASIC_CFLAGS += -DNO_DEFLATE_BOUND
|
||||
endif
|
||||
|
||||
ifdef NO_UNCOMPRESS2
|
||||
BASIC_CFLAGS += -DNO_UNCOMPRESS2
|
||||
REFTABLE_OBJS += compat/zlib-uncompress2.o
|
||||
endif
|
||||
|
||||
ifdef NO_POSIX_GOODIES
|
||||
BASIC_CFLAGS += -DNO_POSIX_GOODIES
|
||||
endif
|
||||
|
@ -2431,7 +2441,36 @@ XDIFF_OBJS += xdiff/xutils.o
|
|||
.PHONY: xdiff-objs
|
||||
xdiff-objs: $(XDIFF_OBJS)
|
||||
|
||||
REFTABLE_OBJS += reftable/basics.o
|
||||
REFTABLE_OBJS += reftable/error.o
|
||||
REFTABLE_OBJS += reftable/block.o
|
||||
REFTABLE_OBJS += reftable/blocksource.o
|
||||
REFTABLE_OBJS += reftable/iter.o
|
||||
REFTABLE_OBJS += reftable/publicbasics.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/generic.o
|
||||
REFTABLE_OBJS += reftable/stack.o
|
||||
REFTABLE_OBJS += reftable/tree.o
|
||||
REFTABLE_OBJS += reftable/writer.o
|
||||
|
||||
REFTABLE_TEST_OBJS += reftable/basics_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/block_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/dump.o
|
||||
REFTABLE_TEST_OBJS += reftable/merged_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/pq_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/record_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/readwrite_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/refname_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/stack_test.o
|
||||
REFTABLE_TEST_OBJS += reftable/test_framework.o
|
||||
REFTABLE_TEST_OBJS += reftable/tree_test.o
|
||||
|
||||
TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
|
||||
|
||||
.PHONY: test-objs
|
||||
test-objs: $(TEST_OBJS)
|
||||
|
||||
|
@ -2447,6 +2486,8 @@ OBJECTS += $(PROGRAM_OBJS)
|
|||
OBJECTS += $(TEST_OBJS)
|
||||
OBJECTS += $(XDIFF_OBJS)
|
||||
OBJECTS += $(FUZZ_OBJS)
|
||||
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
|
||||
|
||||
ifndef NO_CURL
|
||||
OBJECTS += http.o http-walker.o remote-curl.o
|
||||
endif
|
||||
|
@ -2589,6 +2630,12 @@ $(LIB_FILE): $(LIB_OBJS)
|
|||
$(XDIFF_LIB): $(XDIFF_OBJS)
|
||||
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
$(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
|
||||
|
@ -2887,7 +2934,7 @@ perf: all
|
|||
|
||||
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
|
||||
|
@ -3225,7 +3272,7 @@ cocciclean:
|
|||
clean: profile-clean coverage-clean cocciclean
|
||||
$(RM) *.res
|
||||
$(RM) $(OBJECTS)
|
||||
$(RM) $(LIB_FILE) $(XDIFF_LIB)
|
||||
$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
|
||||
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
|
||||
$(RM) $(TEST_PROGRAMS)
|
||||
$(RM) $(FUZZ_PROGRAMS)
|
||||
|
|
|
@ -224,6 +224,7 @@ linux-gcc-default)
|
|||
;;
|
||||
Linux32)
|
||||
CC=gcc
|
||||
MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1"
|
||||
;;
|
||||
linux-musl)
|
||||
CC=gcc
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/zlib-uncompress2.c whitespace=-indent-with-non-tab,-trailing-space
|
|
@ -0,0 +1,95 @@
|
|||
/* taken from zlib's uncompr.c
|
||||
|
||||
commit cacf7f1d4e3d44d871b605da3b647f07d718623f
|
||||
Author: Mark Adler <madler@alumni.caltech.edu>
|
||||
Date: Sun Jan 15 09:18:46 2017 -0800
|
||||
|
||||
zlib 1.2.11
|
||||
|
||||
*/
|
||||
|
||||
#include "../reftable/system.h"
|
||||
#define z_const
|
||||
|
||||
/*
|
||||
* Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
/* ===========================================================================
|
||||
Decompresses the source buffer into the destination buffer. *sourceLen is
|
||||
the byte length of the source buffer. Upon entry, *destLen is the total size
|
||||
of the destination buffer, which must be large enough to hold the entire
|
||||
uncompressed data. (The size of the uncompressed data must have been saved
|
||||
previously by the compressor and transmitted to the decompressor by some
|
||||
mechanism outside the scope of this compression library.) Upon exit,
|
||||
*destLen is the size of the decompressed data and *sourceLen is the number
|
||||
of source bytes consumed. Upon return, source + *sourceLen points to the
|
||||
first unused input byte.
|
||||
|
||||
uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
|
||||
memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
|
||||
Z_DATA_ERROR if the input data was corrupted, including if the input data is
|
||||
an incomplete zlib stream.
|
||||
*/
|
||||
int ZEXPORT uncompress2 (
|
||||
Bytef *dest,
|
||||
uLongf *destLen,
|
||||
const Bytef *source,
|
||||
uLong *sourceLen) {
|
||||
z_stream stream;
|
||||
int err;
|
||||
const uInt max = (uInt)-1;
|
||||
uLong len, left;
|
||||
Byte buf[1]; /* for detection of incomplete stream when *destLen == 0 */
|
||||
|
||||
len = *sourceLen;
|
||||
if (*destLen) {
|
||||
left = *destLen;
|
||||
*destLen = 0;
|
||||
}
|
||||
else {
|
||||
left = 1;
|
||||
dest = buf;
|
||||
}
|
||||
|
||||
stream.next_in = (z_const Bytef *)source;
|
||||
stream.avail_in = 0;
|
||||
stream.zalloc = (alloc_func)0;
|
||||
stream.zfree = (free_func)0;
|
||||
stream.opaque = (voidpf)0;
|
||||
|
||||
err = inflateInit(&stream);
|
||||
if (err != Z_OK) return err;
|
||||
|
||||
stream.next_out = dest;
|
||||
stream.avail_out = 0;
|
||||
|
||||
do {
|
||||
if (stream.avail_out == 0) {
|
||||
stream.avail_out = left > (uLong)max ? max : (uInt)left;
|
||||
left -= stream.avail_out;
|
||||
}
|
||||
if (stream.avail_in == 0) {
|
||||
stream.avail_in = len > (uLong)max ? max : (uInt)len;
|
||||
len -= stream.avail_in;
|
||||
}
|
||||
err = inflate(&stream, Z_NO_FLUSH);
|
||||
} while (err == Z_OK);
|
||||
|
||||
*sourceLen -= len + stream.avail_in;
|
||||
if (dest != buf)
|
||||
*destLen = stream.total_out;
|
||||
else if (stream.total_out && err == Z_BUF_ERROR)
|
||||
left = 1;
|
||||
|
||||
inflateEnd(&stream);
|
||||
return err == Z_STREAM_END ? Z_OK :
|
||||
err == Z_NEED_DICT ? Z_DATA_ERROR :
|
||||
err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
|
||||
err;
|
||||
}
|
|
@ -261,6 +261,10 @@ ifeq ($(uname_S),FreeBSD)
|
|||
FILENO_IS_A_MACRO = UnfortunatelyYes
|
||||
endif
|
||||
ifeq ($(uname_S),OpenBSD)
|
||||
# Versions < 7.0 need compatibility layer
|
||||
ifeq ($(shell expr "$(uname_R)" : "[1-6]\."),2)
|
||||
NO_UNCOMPRESS2 = UnfortunatelyYes
|
||||
endif
|
||||
NO_STRCASESTR = YesPlease
|
||||
NO_MEMMEM = YesPlease
|
||||
USE_ST_TIMESPEC = YesPlease
|
||||
|
@ -516,6 +520,7 @@ ifeq ($(uname_S),Interix)
|
|||
endif
|
||||
endif
|
||||
ifeq ($(uname_S),Minix)
|
||||
NO_UNCOMPRESS2 = YesPlease
|
||||
NO_IPV6 = YesPlease
|
||||
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
|
||||
NO_NSEC = YesPlease
|
||||
|
|
13
configure.ac
13
configure.ac
|
@ -664,9 +664,22 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],
|
|||
NO_DEFLATE_BOUND=yes])
|
||||
LIBS="$old_LIBS"
|
||||
|
||||
AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [
|
||||
AC_LANG_PROGRAM([#include <zlib.h>],
|
||||
[uncompress2(NULL,NULL,NULL,NULL);])])
|
||||
AC_MSG_CHECKING([for uncompress2 in -lz])
|
||||
old_LIBS="$LIBS"
|
||||
LIBS="$LIBS -lz"
|
||||
AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[AC_MSG_RESULT([no])
|
||||
NO_UNCOMPRESS2=yes])
|
||||
LIBS="$old_LIBS"
|
||||
|
||||
GIT_UNSTASH_FLAGS($ZLIB_PATH)
|
||||
|
||||
GIT_CONF_SUBST([NO_DEFLATE_BOUND])
|
||||
GIT_CONF_SUBST([NO_UNCOMPRESS2])
|
||||
|
||||
#
|
||||
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
|
||||
|
|
|
@ -647,6 +647,12 @@ parse_makefile_for_sources(libxdiff_SOURCES "XDIFF_OBJS")
|
|||
list(TRANSFORM libxdiff_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
|
||||
add_library(xdiff STATIC ${libxdiff_SOURCES})
|
||||
|
||||
#reftable
|
||||
parse_makefile_for_sources(reftable_SOURCES "REFTABLE_OBJS")
|
||||
|
||||
list(TRANSFORM reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
|
||||
add_library(reftable STATIC ${reftable_SOURCES})
|
||||
|
||||
if(WIN32)
|
||||
if(NOT MSVC)#use windres when compiling with gcc and clang
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res
|
||||
|
@ -669,7 +675,7 @@ endif()
|
|||
#link all required libraries to common-main
|
||||
add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)
|
||||
|
||||
target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})
|
||||
target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES})
|
||||
if(Intl_FOUND)
|
||||
target_link_libraries(common-main ${Intl_LIBRARIES})
|
||||
endif()
|
||||
|
@ -908,11 +914,15 @@ if(BUILD_TESTING)
|
|||
add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)
|
||||
target_link_libraries(test-fake-ssh common-main)
|
||||
|
||||
#reftable-tests
|
||||
parse_makefile_for_sources(test-reftable_SOURCES "REFTABLE_TEST_OBJS")
|
||||
list(TRANSFORM test-reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
|
||||
|
||||
#test-tool
|
||||
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
|
||||
|
||||
list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/")
|
||||
add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})
|
||||
add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})
|
||||
target_link_libraries(test-tool common-main)
|
||||
|
||||
set_target_properties(test-fake-ssh test-tool
|
||||
|
|
|
@ -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/libexpat\.lib/libexpatd\.lib/g;
|
||||
|
@ -232,6 +232,7 @@ EOM
|
|||
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";
|
||||
|
@ -241,6 +242,14 @@ EOM
|
|||
<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">
|
||||
|
|
6
hash.h
6
hash.h
|
@ -95,12 +95,18 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s
|
|||
/* Number of algorithms supported (including unknown). */
|
||||
#define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)
|
||||
|
||||
/* "sha1", big-endian */
|
||||
#define GIT_SHA1_FORMAT_ID 0x73686131
|
||||
|
||||
/* The length in bytes and in hex digits of an object name (SHA-1 value). */
|
||||
#define GIT_SHA1_RAWSZ 20
|
||||
#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
|
||||
/* The block size of SHA-1. */
|
||||
#define GIT_SHA1_BLKSZ 64
|
||||
|
||||
/* "s256", big-endian */
|
||||
#define GIT_SHA256_FORMAT_ID 0x73323536
|
||||
|
||||
/* The length in bytes and in hex digits of an object name (SHA-256 value). */
|
||||
#define GIT_SHA256_RAWSZ 32
|
||||
#define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)
|
||||
|
|
|
@ -165,7 +165,6 @@ static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx)
|
|||
BUG("trying to finalize unknown hash");
|
||||
}
|
||||
|
||||
|
||||
const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
|
||||
{
|
||||
NULL,
|
||||
|
@ -184,8 +183,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
|
|||
},
|
||||
{
|
||||
"sha1",
|
||||
/* "sha1", big-endian */
|
||||
0x73686131,
|
||||
GIT_SHA1_FORMAT_ID,
|
||||
GIT_SHA1_RAWSZ,
|
||||
GIT_SHA1_HEXSZ,
|
||||
GIT_SHA1_BLKSZ,
|
||||
|
@ -200,8 +198,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
|
|||
},
|
||||
{
|
||||
"sha256",
|
||||
/* "s256", big-endian */
|
||||
0x73323536,
|
||||
GIT_SHA256_FORMAT_ID,
|
||||
GIT_SHA256_RAWSZ,
|
||||
GIT_SHA256_HEXSZ,
|
||||
GIT_SHA256_BLKSZ,
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
|
||||
void put_be24(uint8_t *out, uint32_t i)
|
||||
{
|
||||
out[0] = (uint8_t)((i >> 16) & 0xff);
|
||||
out[1] = (uint8_t)((i >> 8) & 0xff);
|
||||
out[2] = (uint8_t)(i & 0xff);
|
||||
}
|
||||
|
||||
uint32_t get_be24(uint8_t *in)
|
||||
{
|
||||
return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
|
||||
(uint32_t)(in[2]);
|
||||
}
|
||||
|
||||
void put_be16(uint8_t *out, uint16_t i)
|
||||
{
|
||||
out[0] = (uint8_t)((i >> 8) & 0xff);
|
||||
out[1] = (uint8_t)(i & 0xff);
|
||||
}
|
||||
|
||||
int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)
|
||||
{
|
||||
size_t lo = 0;
|
||||
size_t hi = sz;
|
||||
|
||||
/* Invariants:
|
||||
*
|
||||
* (hi == sz) || f(hi) == true
|
||||
* (lo == 0 && f(0) == true) || fi(lo) == false
|
||||
*/
|
||||
while (hi - lo > 1) {
|
||||
size_t mid = lo + (hi - lo) / 2;
|
||||
|
||||
if (f(mid, args))
|
||||
hi = mid;
|
||||
else
|
||||
lo = mid;
|
||||
}
|
||||
|
||||
if (lo)
|
||||
return hi;
|
||||
|
||||
return f(0, args) ? 0 : 1;
|
||||
}
|
||||
|
||||
void free_names(char **a)
|
||||
{
|
||||
char **p;
|
||||
if (!a) {
|
||||
return;
|
||||
}
|
||||
for (p = a; *p; p++) {
|
||||
reftable_free(*p);
|
||||
}
|
||||
reftable_free(a);
|
||||
}
|
||||
|
||||
int names_length(char **names)
|
||||
{
|
||||
char **p = names;
|
||||
for (; *p; p++) {
|
||||
/* empty */
|
||||
}
|
||||
return p - names;
|
||||
}
|
||||
|
||||
void parse_names(char *buf, int size, char ***namesp)
|
||||
{
|
||||
char **names = NULL;
|
||||
size_t names_cap = 0;
|
||||
size_t names_len = 0;
|
||||
|
||||
char *p = buf;
|
||||
char *end = buf + size;
|
||||
while (p < end) {
|
||||
char *next = strchr(p, '\n');
|
||||
if (next && next < end) {
|
||||
*next = 0;
|
||||
} else {
|
||||
next = end;
|
||||
}
|
||||
if (p < next) {
|
||||
if (names_len == names_cap) {
|
||||
names_cap = 2 * names_cap + 1;
|
||||
names = reftable_realloc(
|
||||
names, names_cap * sizeof(*names));
|
||||
}
|
||||
names[names_len++] = xstrdup(p);
|
||||
}
|
||||
p = next + 1;
|
||||
}
|
||||
|
||||
names = reftable_realloc(names, (names_len + 1) * sizeof(*names));
|
||||
names[names_len] = NULL;
|
||||
*namesp = names;
|
||||
}
|
||||
|
||||
int names_equal(char **a, char **b)
|
||||
{
|
||||
int i = 0;
|
||||
for (; a[i] && b[i]; i++) {
|
||||
if (strcmp(a[i], b[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return a[i] == b[i];
|
||||
}
|
||||
|
||||
int common_prefix_size(struct strbuf *a, struct strbuf *b)
|
||||
{
|
||||
int p = 0;
|
||||
for (; p < a->len && p < b->len; p++) {
|
||||
if (a->buf[p] != b->buf[p])
|
||||
break;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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 BASICS_H
|
||||
#define BASICS_H
|
||||
|
||||
/*
|
||||
* miscellaneous utilities that are not provided by Git.
|
||||
*/
|
||||
|
||||
#include "system.h"
|
||||
|
||||
/* Bigendian en/decoding of integers */
|
||||
|
||||
void put_be24(uint8_t *out, uint32_t i);
|
||||
uint32_t get_be24(uint8_t *in);
|
||||
void put_be16(uint8_t *out, uint16_t i);
|
||||
|
||||
/*
|
||||
* find smallest index i in [0, sz) at which f(i) is true, assuming
|
||||
* that f is ascending. Return sz if f(i) is false for all indices.
|
||||
*
|
||||
* Contrary to bsearch(3), this returns something useful if the argument is not
|
||||
* found.
|
||||
*/
|
||||
int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);
|
||||
|
||||
/*
|
||||
* Frees a NULL terminated array of malloced strings. The array itself is also
|
||||
* freed.
|
||||
*/
|
||||
void free_names(char **a);
|
||||
|
||||
/* parse a newline separated list of names. `size` is the length of the buffer,
|
||||
* without terminating '\0'. Empty names are discarded. */
|
||||
void parse_names(char *buf, int size, char ***namesp);
|
||||
|
||||
/* compares two NULL-terminated arrays of strings. */
|
||||
int names_equal(char **a, char **b);
|
||||
|
||||
/* returns the array size of a NULL-terminated array of strings. */
|
||||
int names_length(char **names);
|
||||
|
||||
/* Allocation routines; they invoke the functions set through
|
||||
* reftable_set_alloc() */
|
||||
void *reftable_malloc(size_t sz);
|
||||
void *reftable_realloc(void *p, size_t sz);
|
||||
void reftable_free(void *p);
|
||||
void *reftable_calloc(size_t sz);
|
||||
|
||||
/* Find the longest shared prefix size of `a` and `b` */
|
||||
struct strbuf;
|
||||
int common_prefix_size(struct strbuf *a, struct strbuf *b);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 "basics.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 = 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) {
|
||||
EXPECT(args.key < arr[res]);
|
||||
if (res > 0) {
|
||||
EXPECT(args.key >= arr[res - 1]);
|
||||
}
|
||||
} else {
|
||||
EXPECT(args.key == 10 || args.key == 11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_names_length(void)
|
||||
{
|
||||
char *a[] = { "a", "b", NULL };
|
||||
EXPECT(names_length(a) == 2);
|
||||
}
|
||||
|
||||
static void test_parse_names_normal(void)
|
||||
{
|
||||
char in[] = "a\nb\n";
|
||||
char **out = NULL;
|
||||
parse_names(in, strlen(in), &out);
|
||||
EXPECT(!strcmp(out[0], "a"));
|
||||
EXPECT(!strcmp(out[1], "b"));
|
||||
EXPECT(!out[2]);
|
||||
free_names(out);
|
||||
}
|
||||
|
||||
static void test_parse_names_drop_empty(void)
|
||||
{
|
||||
char in[] = "a\n\n";
|
||||
char **out = NULL;
|
||||
parse_names(in, strlen(in), &out);
|
||||
EXPECT(!strcmp(out[0], "a"));
|
||||
EXPECT(!out[1]);
|
||||
free_names(out);
|
||||
}
|
||||
|
||||
static void test_common_prefix(void)
|
||||
{
|
||||
struct strbuf s1 = STRBUF_INIT;
|
||||
struct strbuf s2 = STRBUF_INIT;
|
||||
strbuf_addstr(&s1, "abcdef");
|
||||
strbuf_addstr(&s2, "abc");
|
||||
EXPECT(common_prefix_size(&s1, &s2) == 3);
|
||||
strbuf_release(&s1);
|
||||
strbuf_release(&s2);
|
||||
}
|
||||
|
||||
int basics_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_common_prefix);
|
||||
RUN_TEST(test_parse_names_normal);
|
||||
RUN_TEST(test_parse_names_drop_empty);
|
||||
RUN_TEST(test_binsearch);
|
||||
RUN_TEST(test_names_length);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
/*
|
||||
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 "blocksource.h"
|
||||
#include "constants.h"
|
||||
#include "record.h"
|
||||
#include "reftable-error.h"
|
||||
#include "system.h"
|
||||
#include <zlib.h>
|
||||
|
||||
int header_size(int version)
|
||||
{
|
||||
switch (version) {
|
||||
case 1:
|
||||
return 24;
|
||||
case 2:
|
||||
return 28;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
int footer_size(int version)
|
||||
{
|
||||
switch (version) {
|
||||
case 1:
|
||||
return 68;
|
||||
case 2:
|
||||
return 72;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
static int block_writer_register_restart(struct block_writer *w, int n,
|
||||
int is_restart, struct strbuf *key)
|
||||
{
|
||||
int rlen = w->restart_len;
|
||||
if (rlen >= MAX_RESTARTS) {
|
||||
is_restart = 0;
|
||||
}
|
||||
|
||||
if (is_restart) {
|
||||
rlen++;
|
||||
}
|
||||
if (2 + 3 * rlen + n > w->block_size - w->next)
|
||||
return -1;
|
||||
if (is_restart) {
|
||||
if (w->restart_len == w->restart_cap) {
|
||||
w->restart_cap = w->restart_cap * 2 + 1;
|
||||
w->restarts = reftable_realloc(
|
||||
w->restarts, sizeof(uint32_t) * w->restart_cap);
|
||||
}
|
||||
|
||||
w->restarts[w->restart_len++] = w->next;
|
||||
}
|
||||
|
||||
w->next += n;
|
||||
|
||||
strbuf_reset(&w->last_key);
|
||||
strbuf_addbuf(&w->last_key, key);
|
||||
w->entries++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,
|
||||
uint32_t block_size, uint32_t header_off, int hash_size)
|
||||
{
|
||||
bw->buf = buf;
|
||||
bw->hash_size = hash_size;
|
||||
bw->block_size = block_size;
|
||||
bw->header_off = header_off;
|
||||
bw->buf[header_off] = typ;
|
||||
bw->next = header_off + 4;
|
||||
bw->restart_interval = 16;
|
||||
bw->entries = 0;
|
||||
bw->restart_len = 0;
|
||||
bw->last_key.len = 0;
|
||||
}
|
||||
|
||||
uint8_t block_writer_type(struct block_writer *bw)
|
||||
{
|
||||
return bw->buf[bw->header_off];
|
||||
}
|
||||
|
||||
/* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on
|
||||
success */
|
||||
int block_writer_add(struct block_writer *w, struct reftable_record *rec)
|
||||
{
|
||||
struct strbuf empty = STRBUF_INIT;
|
||||
struct strbuf last =
|
||||
w->entries % w->restart_interval == 0 ? empty : w->last_key;
|
||||
struct string_view out = {
|
||||
.buf = w->buf + w->next,
|
||||
.len = w->block_size - w->next,
|
||||
};
|
||||
|
||||
struct string_view start = out;
|
||||
|
||||
int is_restart = 0;
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
int n = 0;
|
||||
|
||||
reftable_record_key(rec, &key);
|
||||
n = reftable_encode_key(&is_restart, out, last, key,
|
||||
reftable_record_val_type(rec));
|
||||
if (n < 0)
|
||||
goto done;
|
||||
string_view_consume(&out, n);
|
||||
|
||||
n = reftable_record_encode(rec, out, w->hash_size);
|
||||
if (n < 0)
|
||||
goto done;
|
||||
string_view_consume(&out, n);
|
||||
|
||||
if (block_writer_register_restart(w, start.len - out.len, is_restart,
|
||||
&key) < 0)
|
||||
goto done;
|
||||
|
||||
strbuf_release(&key);
|
||||
return 0;
|
||||
|
||||
done:
|
||||
strbuf_release(&key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int block_writer_finish(struct block_writer *w)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < w->restart_len; i++) {
|
||||
put_be24(w->buf + w->next, w->restarts[i]);
|
||||
w->next += 3;
|
||||
}
|
||||
|
||||
put_be16(w->buf + w->next, w->restart_len);
|
||||
w->next += 2;
|
||||
put_be24(w->buf + 1 + w->header_off, w->next);
|
||||
|
||||
if (block_writer_type(w) == BLOCK_TYPE_LOG) {
|
||||
int block_header_skip = 4 + w->header_off;
|
||||
uLongf src_len = w->next - block_header_skip;
|
||||
uLongf dest_cap = src_len * 1.001 + 12;
|
||||
|
||||
uint8_t *compressed = reftable_malloc(dest_cap);
|
||||
while (1) {
|
||||
uLongf out_dest_len = dest_cap;
|
||||
int zresult = compress2(compressed, &out_dest_len,
|
||||
w->buf + block_header_skip,
|
||||
src_len, 9);
|
||||
if (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) {
|
||||
dest_cap *= 2;
|
||||
compressed =
|
||||
reftable_realloc(compressed, dest_cap);
|
||||
if (compressed)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Z_OK != zresult) {
|
||||
reftable_free(compressed);
|
||||
return REFTABLE_ZLIB_ERROR;
|
||||
}
|
||||
|
||||
memcpy(w->buf + block_header_skip, compressed,
|
||||
out_dest_len);
|
||||
w->next = out_dest_len + block_header_skip;
|
||||
reftable_free(compressed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return w->next;
|
||||
}
|
||||
|
||||
uint8_t block_reader_type(struct block_reader *r)
|
||||
{
|
||||
return r->block.data[r->header_off];
|
||||
}
|
||||
|
||||
int block_reader_init(struct block_reader *br, struct reftable_block *block,
|
||||
uint32_t header_off, uint32_t table_block_size,
|
||||
int hash_size)
|
||||
{
|
||||
uint32_t full_block_size = table_block_size;
|
||||
uint8_t typ = block->data[header_off];
|
||||
uint32_t sz = get_be24(block->data + header_off + 1);
|
||||
|
||||
uint16_t restart_count = 0;
|
||||
uint32_t restart_start = 0;
|
||||
uint8_t *restart_bytes = NULL;
|
||||
|
||||
if (!reftable_is_block_type(typ))
|
||||
return REFTABLE_FORMAT_ERROR;
|
||||
|
||||
if (typ == BLOCK_TYPE_LOG) {
|
||||
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.
|
||||
*/
|
||||
uint8_t *uncompressed = reftable_malloc(sz);
|
||||
|
||||
/* Copy over the block header verbatim. It's not compressed. */
|
||||
memcpy(uncompressed, block->data, block_header_skip);
|
||||
|
||||
/* Uncompress */
|
||||
if (Z_OK !=
|
||||
uncompress2(uncompressed + block_header_skip, &dst_len,
|
||||
block->data + block_header_skip, &src_len)) {
|
||||
reftable_free(uncompressed);
|
||||
return REFTABLE_ZLIB_ERROR;
|
||||
}
|
||||
|
||||
if (dst_len + block_header_skip != sz)
|
||||
return REFTABLE_FORMAT_ERROR;
|
||||
|
||||
/* We're done with the input data. */
|
||||
reftable_block_done(block);
|
||||
block->data = uncompressed;
|
||||
block->len = sz;
|
||||
block->source = malloc_block_source();
|
||||
full_block_size = src_len + block_header_skip;
|
||||
} else if (full_block_size == 0) {
|
||||
full_block_size = sz;
|
||||
} else if (sz < full_block_size && sz < block->len &&
|
||||
block->data[sz] != 0) {
|
||||
/* If the block is smaller than the full block size, it is
|
||||
padded (data followed by '\0') or the next block is
|
||||
unaligned. */
|
||||
full_block_size = sz;
|
||||
}
|
||||
|
||||
restart_count = get_be16(block->data + sz - 2);
|
||||
restart_start = sz - 2 - 3 * restart_count;
|
||||
restart_bytes = block->data + restart_start;
|
||||
|
||||
/* transfer ownership. */
|
||||
br->block = *block;
|
||||
block->data = NULL;
|
||||
block->len = 0;
|
||||
|
||||
br->hash_size = hash_size;
|
||||
br->block_len = restart_start;
|
||||
br->full_block_size = full_block_size;
|
||||
br->header_off = header_off;
|
||||
br->restart_count = restart_count;
|
||||
br->restart_bytes = restart_bytes;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
|
||||
{
|
||||
return get_be24(br->restart_bytes + 3 * i);
|
||||
}
|
||||
|
||||
void block_reader_start(struct block_reader *br, struct block_iter *it)
|
||||
{
|
||||
it->br = br;
|
||||
strbuf_reset(&it->last_key);
|
||||
it->next_off = br->header_off + 4;
|
||||
}
|
||||
|
||||
struct restart_find_args {
|
||||
int error;
|
||||
struct strbuf key;
|
||||
struct block_reader *r;
|
||||
};
|
||||
|
||||
static int restart_key_less(size_t idx, void *args)
|
||||
{
|
||||
struct restart_find_args *a = args;
|
||||
uint32_t off = block_reader_restart_offset(a->r, idx);
|
||||
struct string_view in = {
|
||||
.buf = a->r->block.data + off,
|
||||
.len = a->r->block_len - off,
|
||||
};
|
||||
|
||||
/* the restart key is verbatim in the block, so this could avoid the
|
||||
alloc for decoding the key */
|
||||
struct strbuf rkey = STRBUF_INIT;
|
||||
struct strbuf last_key = STRBUF_INIT;
|
||||
uint8_t unused_extra;
|
||||
int n = reftable_decode_key(&rkey, &unused_extra, last_key, in);
|
||||
int result;
|
||||
if (n < 0) {
|
||||
a->error = 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = strbuf_cmp(&a->key, &rkey);
|
||||
strbuf_release(&rkey);
|
||||
return result;
|
||||
}
|
||||
|
||||
void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
|
||||
{
|
||||
dest->br = src->br;
|
||||
dest->next_off = src->next_off;
|
||||
strbuf_reset(&dest->last_key);
|
||||
strbuf_addbuf(&dest->last_key, &src->last_key);
|
||||
}
|
||||
|
||||
int block_iter_next(struct block_iter *it, struct reftable_record *rec)
|
||||
{
|
||||
struct string_view in = {
|
||||
.buf = it->br->block.data + it->next_off,
|
||||
.len = it->br->block_len - it->next_off,
|
||||
};
|
||||
struct string_view start = in;
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
uint8_t extra = 0;
|
||||
int n = 0;
|
||||
|
||||
if (it->next_off >= it->br->block_len)
|
||||
return 1;
|
||||
|
||||
n = reftable_decode_key(&key, &extra, it->last_key, in);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
|
||||
string_view_consume(&in, n);
|
||||
n = reftable_record_decode(rec, key, extra, in, it->br->hash_size);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
string_view_consume(&in, n);
|
||||
|
||||
strbuf_reset(&it->last_key);
|
||||
strbuf_addbuf(&it->last_key, &key);
|
||||
it->next_off += start.len - in.len;
|
||||
strbuf_release(&key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int block_reader_first_key(struct block_reader *br, struct strbuf *key)
|
||||
{
|
||||
struct strbuf empty = STRBUF_INIT;
|
||||
int off = br->header_off + 4;
|
||||
struct string_view in = {
|
||||
.buf = br->block.data + off,
|
||||
.len = br->block_len - off,
|
||||
};
|
||||
|
||||
uint8_t extra = 0;
|
||||
int n = reftable_decode_key(key, &extra, empty, in);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int block_iter_seek(struct block_iter *it, struct strbuf *want)
|
||||
{
|
||||
return block_reader_seek(it->br, it, want);
|
||||
}
|
||||
|
||||
void block_iter_close(struct block_iter *it)
|
||||
{
|
||||
strbuf_release(&it->last_key);
|
||||
}
|
||||
|
||||
int block_reader_seek(struct block_reader *br, struct block_iter *it,
|
||||
struct strbuf *want)
|
||||
{
|
||||
struct restart_find_args args = {
|
||||
.key = *want,
|
||||
.r = br,
|
||||
};
|
||||
struct reftable_record rec = reftable_new_record(block_reader_type(br));
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
int err = 0;
|
||||
struct block_iter next = {
|
||||
.last_key = STRBUF_INIT,
|
||||
};
|
||||
|
||||
int i = binsearch(br->restart_count, &restart_key_less, &args);
|
||||
if (args.error) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
it->br = br;
|
||||
if (i > 0) {
|
||||
i--;
|
||||
it->next_off = block_reader_restart_offset(br, i);
|
||||
} else {
|
||||
it->next_off = br->header_off + 4;
|
||||
}
|
||||
|
||||
/* We're looking for the last entry less/equal than the wanted key, so
|
||||
we have to go one entry too far and then back up.
|
||||
*/
|
||||
while (1) {
|
||||
block_iter_copy_from(&next, it);
|
||||
err = block_iter_next(&next, &rec);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
reftable_record_key(&rec, &key);
|
||||
if (err > 0 || strbuf_cmp(&key, want) >= 0) {
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
block_iter_copy_from(it, &next);
|
||||
}
|
||||
|
||||
done:
|
||||
strbuf_release(&key);
|
||||
strbuf_release(&next.last_key);
|
||||
reftable_record_destroy(&rec);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void block_writer_release(struct block_writer *bw)
|
||||
{
|
||||
FREE_AND_NULL(bw->restarts);
|
||||
strbuf_release(&bw->last_key);
|
||||
/* the block is not owned. */
|
||||
}
|
||||
|
||||
void reftable_block_done(struct reftable_block *blockp)
|
||||
{
|
||||
struct reftable_block_source source = blockp->source;
|
||||
if (blockp && source.ops)
|
||||
source.ops->return_block(source.arg, blockp);
|
||||
blockp->data = NULL;
|
||||
blockp->len = 0;
|
||||
blockp->source.ops = NULL;
|
||||
blockp->source.arg = NULL;
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
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 BLOCK_H
|
||||
#define BLOCK_H
|
||||
|
||||
#include "basics.h"
|
||||
#include "record.h"
|
||||
#include "reftable-blocksource.h"
|
||||
|
||||
/*
|
||||
* Writes reftable blocks. The block_writer is reused across blocks to minimize
|
||||
* allocation overhead.
|
||||
*/
|
||||
struct block_writer {
|
||||
uint8_t *buf;
|
||||
uint32_t block_size;
|
||||
|
||||
/* Offset ofof the global header. Nonzero in the first block only. */
|
||||
uint32_t header_off;
|
||||
|
||||
/* How often to restart keys. */
|
||||
int restart_interval;
|
||||
int hash_size;
|
||||
|
||||
/* Offset of next uint8_t to write. */
|
||||
uint32_t next;
|
||||
uint32_t *restarts;
|
||||
uint32_t restart_len;
|
||||
uint32_t restart_cap;
|
||||
|
||||
struct strbuf last_key;
|
||||
int entries;
|
||||
};
|
||||
|
||||
/*
|
||||
* initializes the blockwriter to write `typ` entries, using `buf` as temporary
|
||||
* storage. `buf` is not owned by the block_writer. */
|
||||
void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,
|
||||
uint32_t block_size, uint32_t header_off, int hash_size);
|
||||
|
||||
/* returns the block type (eg. 'r' for ref records. */
|
||||
uint8_t block_writer_type(struct block_writer *bw);
|
||||
|
||||
/* appends the record, or -1 if it doesn't fit. */
|
||||
int block_writer_add(struct block_writer *w, struct reftable_record *rec);
|
||||
|
||||
/* appends the key restarts, and compress the block if necessary. */
|
||||
int block_writer_finish(struct block_writer *w);
|
||||
|
||||
/* clears out internally allocated block_writer members. */
|
||||
void block_writer_release(struct block_writer *bw);
|
||||
|
||||
/* Read a block. */
|
||||
struct block_reader {
|
||||
/* offset of the block header; nonzero for the first block in a
|
||||
* reftable. */
|
||||
uint32_t header_off;
|
||||
|
||||
/* the memory block */
|
||||
struct reftable_block block;
|
||||
int hash_size;
|
||||
|
||||
/* size of the data, excluding restart data. */
|
||||
uint32_t block_len;
|
||||
uint8_t *restart_bytes;
|
||||
uint16_t restart_count;
|
||||
|
||||
/* size of the data in the file. For log blocks, this is the compressed
|
||||
* size. */
|
||||
uint32_t full_block_size;
|
||||
};
|
||||
|
||||
/* Iterate over entries in a block */
|
||||
struct block_iter {
|
||||
/* offset within the block of the next entry to read. */
|
||||
uint32_t next_off;
|
||||
struct block_reader *br;
|
||||
|
||||
/* key for last entry we read. */
|
||||
struct strbuf last_key;
|
||||
};
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Position `it` at start of the block */
|
||||
void block_reader_start(struct block_reader *br, struct block_iter *it);
|
||||
|
||||
/* Position `it` to the `want` key in the block */
|
||||
int block_reader_seek(struct block_reader *br, struct block_iter *it,
|
||||
struct strbuf *want);
|
||||
|
||||
/* Returns the block type (eg. 'r' for refs) */
|
||||
uint8_t block_reader_type(struct block_reader *r);
|
||||
|
||||
/* Decodes the first key in the block */
|
||||
int block_reader_first_key(struct block_reader *br, struct strbuf *key);
|
||||
|
||||
void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);
|
||||
|
||||
/* return < 0 for error, 0 for OK, > 0 for EOF. */
|
||||
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 strbuf *want);
|
||||
|
||||
/* deallocate memory for `it`. The block reader and its block is left intact. */
|
||||
void block_iter_close(struct block_iter *it);
|
||||
|
||||
/* size of file header, depending on format version */
|
||||
int header_size(int version);
|
||||
|
||||
/* size of file footer, depending on format version */
|
||||
int footer_size(int version);
|
||||
|
||||
/* returns a block to its source. */
|
||||
void reftable_block_done(struct reftable_block *ret);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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 "blocksource.h"
|
||||
#include "basics.h"
|
||||
#include "constants.h"
|
||||
#include "record.h"
|
||||
#include "test_framework.h"
|
||||
#include "reftable-tests.h"
|
||||
|
||||
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 = { NULL };
|
||||
struct block_writer bw = {
|
||||
.last_key = STRBUF_INIT,
|
||||
};
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
struct reftable_record rec = { NULL };
|
||||
int i = 0;
|
||||
int n;
|
||||
struct block_reader br = { 0 };
|
||||
struct block_iter it = { .last_key = STRBUF_INIT };
|
||||
int j = 0;
|
||||
struct strbuf want = STRBUF_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(GIT_SHA1_FORMAT_ID));
|
||||
reftable_record_from_ref(&rec, &ref);
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
char name[100];
|
||||
uint8_t hash[GIT_SHA1_RAWSZ];
|
||||
snprintf(name, sizeof(name), "branch%02d", i);
|
||||
memset(hash, i, sizeof(hash));
|
||||
|
||||
ref.refname = name;
|
||||
ref.value_type = REFTABLE_REF_VAL1;
|
||||
ref.value.val1 = hash;
|
||||
|
||||
names[i] = xstrdup(name);
|
||||
n = block_writer_add(&bw, &rec);
|
||||
ref.refname = NULL;
|
||||
ref.value_type = REFTABLE_REF_DELETION;
|
||||
EXPECT(n == 0);
|
||||
}
|
||||
|
||||
n = block_writer_finish(&bw);
|
||||
EXPECT(n > 0);
|
||||
|
||||
block_writer_release(&bw);
|
||||
|
||||
block_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);
|
||||
|
||||
block_reader_start(&br, &it);
|
||||
|
||||
while (1) {
|
||||
int r = block_iter_next(&it, &rec);
|
||||
EXPECT(r >= 0);
|
||||
if (r > 0) {
|
||||
break;
|
||||
}
|
||||
EXPECT_STREQ(names[j], ref.refname);
|
||||
j++;
|
||||
}
|
||||
|
||||
reftable_record_release(&rec);
|
||||
block_iter_close(&it);
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
struct block_iter it = { .last_key = STRBUF_INIT };
|
||||
strbuf_reset(&want);
|
||||
strbuf_addstr(&want, names[i]);
|
||||
|
||||
n = block_reader_seek(&br, &it, &want);
|
||||
EXPECT(n == 0);
|
||||
|
||||
n = block_iter_next(&it, &rec);
|
||||
EXPECT(n == 0);
|
||||
|
||||
EXPECT_STREQ(names[i], ref.refname);
|
||||
|
||||
want.len--;
|
||||
n = block_reader_seek(&br, &it, &want);
|
||||
EXPECT(n == 0);
|
||||
|
||||
n = block_iter_next(&it, &rec);
|
||||
EXPECT(n == 0);
|
||||
EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
|
||||
|
||||
block_iter_close(&it);
|
||||
}
|
||||
|
||||
reftable_record_release(&rec);
|
||||
reftable_block_done(&br.block);
|
||||
strbuf_release(&want);
|
||||
for (i = 0; i < N; i++) {
|
||||
reftable_free(names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int block_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_block_read_write);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
#include "blocksource.h"
|
||||
#include "reftable-blocksource.h"
|
||||
#include "reftable-error.h"
|
||||
|
||||
static void strbuf_return_block(void *b, struct reftable_block *dest)
|
||||
{
|
||||
memset(dest->data, 0xff, dest->len);
|
||||
reftable_free(dest->data);
|
||||
}
|
||||
|
||||
static void strbuf_close(void *b)
|
||||
{
|
||||
}
|
||||
|
||||
static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off,
|
||||
uint32_t size)
|
||||
{
|
||||
struct strbuf *b = v;
|
||||
assert(off + size <= b->len);
|
||||
dest->data = reftable_calloc(size);
|
||||
memcpy(dest->data, b->buf + off, size);
|
||||
dest->len = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
static uint64_t strbuf_size(void *b)
|
||||
{
|
||||
return ((struct strbuf *)b)->len;
|
||||
}
|
||||
|
||||
static struct reftable_block_source_vtable strbuf_vtable = {
|
||||
.size = &strbuf_size,
|
||||
.read_block = &strbuf_read_block,
|
||||
.return_block = &strbuf_return_block,
|
||||
.close = &strbuf_close,
|
||||
};
|
||||
|
||||
void block_source_from_strbuf(struct reftable_block_source *bs,
|
||||
struct strbuf *buf)
|
||||
{
|
||||
assert(!bs->ops);
|
||||
bs->ops = &strbuf_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);
|
||||
}
|
||||
|
||||
static struct reftable_block_source_vtable malloc_vtable = {
|
||||
.return_block = &malloc_return_block,
|
||||
};
|
||||
|
||||
static struct reftable_block_source malloc_block_source_instance = {
|
||||
.ops = &malloc_vtable,
|
||||
};
|
||||
|
||||
struct reftable_block_source malloc_block_source(void)
|
||||
{
|
||||
return malloc_block_source_instance;
|
||||
}
|
||||
|
||||
struct file_block_source {
|
||||
int fd;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
static uint64_t file_size(void *b)
|
||||
{
|
||||
return ((struct file_block_source *)b)->size;
|
||||
}
|
||||
|
||||
static void file_return_block(void *b, struct reftable_block *dest)
|
||||
{
|
||||
memset(dest->data, 0xff, dest->len);
|
||||
reftable_free(dest->data);
|
||||
}
|
||||
|
||||
static void file_close(void *b)
|
||||
{
|
||||
int fd = ((struct file_block_source *)b)->fd;
|
||||
if (fd > 0) {
|
||||
close(fd);
|
||||
((struct file_block_source *)b)->fd = 0;
|
||||
}
|
||||
|
||||
reftable_free(b);
|
||||
}
|
||||
|
||||
static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,
|
||||
uint32_t size)
|
||||
{
|
||||
struct file_block_source *b = v;
|
||||
assert(off + size <= b->size);
|
||||
dest->data = reftable_malloc(size);
|
||||
if (pread(b->fd, dest->data, size, off) != size)
|
||||
return -1;
|
||||
dest->len = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct reftable_block_source_vtable file_vtable = {
|
||||
.size = &file_size,
|
||||
.read_block = &file_read_block,
|
||||
.return_block = &file_return_block,
|
||||
.close = &file_close,
|
||||
};
|
||||
|
||||
int reftable_block_source_from_file(struct reftable_block_source *bs,
|
||||
const char *name)
|
||||
{
|
||||
struct stat st = { 0 };
|
||||
int err = 0;
|
||||
int fd = open(name, O_RDONLY);
|
||||
struct file_block_source *p = NULL;
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT) {
|
||||
return REFTABLE_NOT_EXIST_ERROR;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = fstat(fd, &st);
|
||||
if (err < 0)
|
||||
return -1;
|
||||
|
||||
p = reftable_calloc(sizeof(struct file_block_source));
|
||||
p->size = st.st_size;
|
||||
p->fd = fd;
|
||||
|
||||
assert(!bs->ops);
|
||||
bs->ops = &file_vtable;
|
||||
bs->arg = p;
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
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 BLOCKSOURCE_H
|
||||
#define BLOCKSOURCE_H
|
||||
|
||||
#include "system.h"
|
||||
|
||||
struct reftable_block_source;
|
||||
|
||||
/* Create an in-memory block source for reading reftables */
|
||||
void block_source_from_strbuf(struct reftable_block_source *bs,
|
||||
struct strbuf *buf);
|
||||
|
||||
struct reftable_block_source malloc_block_source(void);
|
||||
|
||||
#endif
|
|
@ -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
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
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 "git-compat-util.h"
|
||||
#include "hash.h"
|
||||
|
||||
#include "reftable-blocksource.h"
|
||||
#include "reftable-error.h"
|
||||
#include "reftable-merged.h"
|
||||
#include "reftable-record.h"
|
||||
#include "reftable-tests.h"
|
||||
#include "reftable-writer.h"
|
||||
#include "reftable-iterator.h"
|
||||
#include "reftable-reader.h"
|
||||
#include "reftable-stack.h"
|
||||
#include "reftable-generic.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
static int compact_stack(const char *stackdir)
|
||||
{
|
||||
struct reftable_stack *stack = NULL;
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
|
||||
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) {
|
||||
reftable_stack_destroy(stack);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
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"
|
||||
" -6 sha256 hash format\n"
|
||||
" -h this help\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
int reftable_dump_main(int argc, char *const *argv)
|
||||
{
|
||||
int err = 0;
|
||||
int opt_dump_table = 0;
|
||||
int opt_dump_stack = 0;
|
||||
int opt_compact = 0;
|
||||
uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;
|
||||
const char *arg = NULL, *argv0 = argv[0];
|
||||
|
||||
for (; argc > 1; argv++, argc--)
|
||||
if (*argv[1] != '-')
|
||||
break;
|
||||
else if (!strcmp("-t", argv[1]))
|
||||
opt_dump_table = 1;
|
||||
else if (!strcmp("-6", argv[1]))
|
||||
opt_hash_id = GIT_SHA256_FORMAT_ID;
|
||||
else if (!strcmp("-s", argv[1]))
|
||||
opt_dump_stack = 1;
|
||||
else if (!strcmp("-c", argv[1]))
|
||||
opt_compact = 1;
|
||||
else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) {
|
||||
print_help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "need argument\n");
|
||||
print_help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
arg = argv[1];
|
||||
|
||||
if (opt_dump_table) {
|
||||
err = reftable_reader_print_file(arg);
|
||||
} else if (opt_dump_stack) {
|
||||
err = reftable_stack_print_directory(arg, opt_hash_id);
|
||||
} else if (opt_compact) {
|
||||
err = compact_stack(arg);
|
||||
}
|
||||
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "%s: %s: %s\n", argv0, arg,
|
||||
reftable_error_str(err));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
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-error.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
const char *reftable_error_str(int err)
|
||||
{
|
||||
static char buf[250];
|
||||
switch (err) {
|
||||
case REFTABLE_IO_ERROR:
|
||||
return "I/O error";
|
||||
case REFTABLE_FORMAT_ERROR:
|
||||
return "corrupt reftable file";
|
||||
case REFTABLE_NOT_EXIST_ERROR:
|
||||
return "file does not exist";
|
||||
case REFTABLE_LOCK_ERROR:
|
||||
return "data is outdated";
|
||||
case REFTABLE_API_ERROR:
|
||||
return "misuse of the reftable API";
|
||||
case REFTABLE_ZLIB_ERROR:
|
||||
return "zlib failure";
|
||||
case REFTABLE_NAME_CONFLICT:
|
||||
return "file/directory conflict";
|
||||
case REFTABLE_EMPTY_TABLE_ERROR:
|
||||
return "wrote empty table";
|
||||
case REFTABLE_REFNAME_ERROR:
|
||||
return "invalid refname";
|
||||
case -1:
|
||||
return "general error";
|
||||
default:
|
||||
snprintf(buf, sizeof(buf), "unknown error code %d", err);
|
||||
return buf;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
#include "record.h"
|
||||
#include "generic.h"
|
||||
#include "reftable-iterator.h"
|
||||
#include "reftable-generic.h"
|
||||
|
||||
int reftable_table_seek_ref(struct reftable_table *tab,
|
||||
struct reftable_iterator *it, const char *name)
|
||||
{
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = (char *)name,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_ref(&rec, &ref);
|
||||
return tab->ops->seek_record(tab->table_arg, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_table_seek_log(struct reftable_table *tab,
|
||||
struct reftable_iterator *it, const char *name)
|
||||
{
|
||||
struct reftable_log_record log = {
|
||||
.refname = (char *)name,
|
||||
.update_index = ~((uint64_t)0),
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_log(&rec, &log);
|
||||
return tab->ops->seek_record(tab->table_arg, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_table_read_ref(struct reftable_table *tab, const char *name,
|
||||
struct reftable_ref_record *ref)
|
||||
{
|
||||
struct reftable_iterator it = { NULL };
|
||||
int err = reftable_table_seek_ref(tab, &it, name);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
err = reftable_iterator_next_ref(&it, ref);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
if (strcmp(ref->refname, name) ||
|
||||
reftable_ref_record_is_deletion(ref)) {
|
||||
reftable_ref_record_release(ref);
|
||||
err = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
reftable_iterator_destroy(&it);
|
||||
return err;
|
||||
}
|
||||
|
||||
int reftable_table_print(struct reftable_table *tab) {
|
||||
struct reftable_iterator it = { NULL };
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
struct reftable_log_record log = { NULL };
|
||||
uint32_t hash_id = reftable_table_hash_id(tab);
|
||||
int err = reftable_table_seek_ref(tab, &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_release(&ref);
|
||||
|
||||
err = reftable_table_seek_log(tab, &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_release(&log);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t reftable_table_max_update_index(struct reftable_table *tab)
|
||||
{
|
||||
return tab->ops->max_update_index(tab->table_arg);
|
||||
}
|
||||
|
||||
uint64_t reftable_table_min_update_index(struct reftable_table *tab)
|
||||
{
|
||||
return tab->ops->min_update_index(tab->table_arg);
|
||||
}
|
||||
|
||||
uint32_t reftable_table_hash_id(struct reftable_table *tab)
|
||||
{
|
||||
return tab->ops->hash_id(tab->table_arg);
|
||||
}
|
||||
|
||||
void reftable_iterator_destroy(struct reftable_iterator *it)
|
||||
{
|
||||
if (!it->ops) {
|
||||
return;
|
||||
}
|
||||
it->ops->close(it->iter_arg);
|
||||
it->ops = NULL;
|
||||
FREE_AND_NULL(it->iter_arg);
|
||||
}
|
||||
|
||||
int reftable_iterator_next_ref(struct reftable_iterator *it,
|
||||
struct reftable_ref_record *ref)
|
||||
{
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_ref(&rec, ref);
|
||||
return iterator_next(it, &rec);
|
||||
}
|
||||
|
||||
int reftable_iterator_next_log(struct reftable_iterator *it,
|
||||
struct reftable_log_record *log)
|
||||
{
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_log(&rec, log);
|
||||
return iterator_next(it, &rec);
|
||||
}
|
||||
|
||||
int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
|
||||
{
|
||||
return it->ops->next(it->iter_arg, rec);
|
||||
}
|
||||
|
||||
static int empty_iterator_next(void *arg, struct reftable_record *rec)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void empty_iterator_close(void *arg)
|
||||
{
|
||||
}
|
||||
|
||||
static struct reftable_iterator_vtable empty_vtable = {
|
||||
.next = &empty_iterator_next,
|
||||
.close = &empty_iterator_close,
|
||||
};
|
||||
|
||||
void iterator_set_empty(struct reftable_iterator *it)
|
||||
{
|
||||
assert(!it->ops);
|
||||
it->iter_arg = NULL;
|
||||
it->ops = &empty_vtable;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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 GENERIC_H
|
||||
#define GENERIC_H
|
||||
|
||||
#include "record.h"
|
||||
#include "reftable-generic.h"
|
||||
|
||||
/* generic interface to reftables */
|
||||
struct reftable_table_vtable {
|
||||
int (*seek_record)(void *tab, struct reftable_iterator *it,
|
||||
struct reftable_record *);
|
||||
uint32_t (*hash_id)(void *tab);
|
||||
uint64_t (*min_update_index)(void *tab);
|
||||
uint64_t (*max_update_index)(void *tab);
|
||||
};
|
||||
|
||||
struct reftable_iterator_vtable {
|
||||
int (*next)(void *iter_arg, struct reftable_record *rec);
|
||||
void (*close)(void *iter_arg);
|
||||
};
|
||||
|
||||
void iterator_set_empty(struct reftable_iterator *it);
|
||||
int iterator_next(struct reftable_iterator *it, struct reftable_record *rec);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
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 "iter.h"
|
||||
|
||||
#include "system.h"
|
||||
|
||||
#include "block.h"
|
||||
#include "generic.h"
|
||||
#include "constants.h"
|
||||
#include "reader.h"
|
||||
#include "reftable-error.h"
|
||||
|
||||
int iterator_is_null(struct reftable_iterator *it)
|
||||
{
|
||||
return !it->ops;
|
||||
}
|
||||
|
||||
static void filtering_ref_iterator_close(void *iter_arg)
|
||||
{
|
||||
struct filtering_ref_iterator *fri = iter_arg;
|
||||
strbuf_release(&fri->oid);
|
||||
reftable_iterator_destroy(&fri->it);
|
||||
}
|
||||
|
||||
static int filtering_ref_iterator_next(void *iter_arg,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
struct filtering_ref_iterator *fri = iter_arg;
|
||||
struct reftable_ref_record *ref = rec->data;
|
||||
int err = 0;
|
||||
while (1) {
|
||||
err = reftable_iterator_next_ref(&fri->it, ref);
|
||||
if (err != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fri->double_check) {
|
||||
struct reftable_iterator it = { NULL };
|
||||
|
||||
err = reftable_table_seek_ref(&fri->tab, &it,
|
||||
ref->refname);
|
||||
if (err == 0) {
|
||||
err = reftable_iterator_next_ref(&it, ref);
|
||||
}
|
||||
|
||||
reftable_iterator_destroy(&it);
|
||||
|
||||
if (err < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (err > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ref->value_type == REFTABLE_REF_VAL2 &&
|
||||
(!memcmp(fri->oid.buf, ref->value.val2.target_value,
|
||||
fri->oid.len) ||
|
||||
!memcmp(fri->oid.buf, ref->value.val2.value,
|
||||
fri->oid.len)))
|
||||
return 0;
|
||||
|
||||
if (ref->value_type == REFTABLE_REF_VAL1 &&
|
||||
!memcmp(fri->oid.buf, ref->value.val1, fri->oid.len)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
reftable_ref_record_release(ref);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct reftable_iterator_vtable filtering_ref_iterator_vtable = {
|
||||
.next = &filtering_ref_iterator_next,
|
||||
.close = &filtering_ref_iterator_close,
|
||||
};
|
||||
|
||||
void iterator_from_filtering_ref_iterator(struct reftable_iterator *it,
|
||||
struct filtering_ref_iterator *fri)
|
||||
{
|
||||
assert(!it->ops);
|
||||
it->iter_arg = fri;
|
||||
it->ops = &filtering_ref_iterator_vtable;
|
||||
}
|
||||
|
||||
static void indexed_table_ref_iter_close(void *p)
|
||||
{
|
||||
struct indexed_table_ref_iter *it = p;
|
||||
block_iter_close(&it->cur);
|
||||
reftable_block_done(&it->block_reader.block);
|
||||
reftable_free(it->offsets);
|
||||
strbuf_release(&it->oid);
|
||||
}
|
||||
|
||||
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->is_finished = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
reftable_block_done(&it->block_reader.block);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
|
||||
{
|
||||
struct indexed_table_ref_iter *it = p;
|
||||
struct reftable_ref_record *ref = rec->data;
|
||||
|
||||
while (1) {
|
||||
int err = block_iter_next(&it->cur, rec);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err > 0) {
|
||||
err = indexed_table_ref_iter_next_block(it);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (it->is_finished) {
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* BUG */
|
||||
if (!memcmp(it->oid.buf, ref->value.val2.target_value,
|
||||
it->oid.len) ||
|
||||
!memcmp(it->oid.buf, ref->value.val2.value, it->oid.len)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
|
||||
struct reftable_reader *r, uint8_t *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;
|
||||
strbuf_add(&itr->oid, oid, oid_len);
|
||||
|
||||
itr->offsets = offsets;
|
||||
itr->offset_len = offset_len;
|
||||
|
||||
err = indexed_table_ref_iter_next_block(itr);
|
||||
if (err < 0) {
|
||||
reftable_free(itr);
|
||||
} else {
|
||||
*dest = itr;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = {
|
||||
.next = &indexed_table_ref_iter_next,
|
||||
.close = &indexed_table_ref_iter_close,
|
||||
};
|
||||
|
||||
void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,
|
||||
struct indexed_table_ref_iter *itr)
|
||||
{
|
||||
assert(!it->ops);
|
||||
it->iter_arg = itr;
|
||||
it->ops = &indexed_table_ref_iter_vtable;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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 ITER_H
|
||||
#define ITER_H
|
||||
|
||||
#include "system.h"
|
||||
#include "block.h"
|
||||
#include "record.h"
|
||||
|
||||
#include "reftable-iterator.h"
|
||||
#include "reftable-generic.h"
|
||||
|
||||
/* Returns true for a zeroed out iterator, such as the one returned from
|
||||
* iterator_destroy. */
|
||||
int iterator_is_null(struct reftable_iterator *it);
|
||||
|
||||
/* iterator that produces only ref records that point to `oid` */
|
||||
struct filtering_ref_iterator {
|
||||
int double_check;
|
||||
struct reftable_table tab;
|
||||
struct strbuf oid;
|
||||
struct reftable_iterator it;
|
||||
};
|
||||
#define FILTERING_REF_ITERATOR_INIT \
|
||||
{ \
|
||||
.oid = STRBUF_INIT \
|
||||
}
|
||||
|
||||
void iterator_from_filtering_ref_iterator(struct reftable_iterator *,
|
||||
struct filtering_ref_iterator *);
|
||||
|
||||
/* iterator that produces only ref records that point to `oid`,
|
||||
* but using the object index.
|
||||
*/
|
||||
struct indexed_table_ref_iter {
|
||||
struct reftable_reader *r;
|
||||
struct strbuf oid;
|
||||
|
||||
/* mutable */
|
||||
uint64_t *offsets;
|
||||
|
||||
/* Points to the next offset to read. */
|
||||
int offset_idx;
|
||||
int offset_len;
|
||||
struct block_reader block_reader;
|
||||
struct block_iter cur;
|
||||
int is_finished;
|
||||
};
|
||||
|
||||
#define INDEXED_TABLE_REF_ITER_INIT \
|
||||
{ \
|
||||
.cur = { .last_key = STRBUF_INIT }, .oid = STRBUF_INIT, \
|
||||
}
|
||||
|
||||
void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it,
|
||||
struct indexed_table_ref_iter *itr);
|
||||
|
||||
/* Takes ownership of `offsets` */
|
||||
int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
|
||||
struct reftable_reader *r, uint8_t *oid,
|
||||
int oid_len, uint64_t *offsets, int offset_len);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
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 "constants.h"
|
||||
#include "iter.h"
|
||||
#include "pq.h"
|
||||
#include "reader.h"
|
||||
#include "record.h"
|
||||
#include "generic.h"
|
||||
#include "reftable-merged.h"
|
||||
#include "reftable-error.h"
|
||||
#include "system.h"
|
||||
|
||||
static int merged_iter_init(struct merged_iter *mi)
|
||||
{
|
||||
int i = 0;
|
||||
for (i = 0; i < mi->stack_len; i++) {
|
||||
struct reftable_record rec = reftable_new_record(mi->typ);
|
||||
int err = iterator_next(&mi->stack[i], &rec);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err > 0) {
|
||||
reftable_iterator_destroy(&mi->stack[i]);
|
||||
reftable_record_destroy(&rec);
|
||||
} else {
|
||||
struct pq_entry e = {
|
||||
.rec = rec,
|
||||
.index = i,
|
||||
};
|
||||
merged_iter_pqueue_add(&mi->pq, e);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void merged_iter_close(void *p)
|
||||
{
|
||||
struct merged_iter *mi = p;
|
||||
int i = 0;
|
||||
merged_iter_pqueue_release(&mi->pq);
|
||||
for (i = 0; i < mi->stack_len; i++) {
|
||||
reftable_iterator_destroy(&mi->stack[i]);
|
||||
}
|
||||
reftable_free(mi->stack);
|
||||
}
|
||||
|
||||
static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
|
||||
size_t idx)
|
||||
{
|
||||
struct reftable_record rec = reftable_new_record(mi->typ);
|
||||
struct pq_entry e = {
|
||||
.rec = rec,
|
||||
.index = idx,
|
||||
};
|
||||
int err = iterator_next(&mi->stack[idx], &rec);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (err > 0) {
|
||||
reftable_iterator_destroy(&mi->stack[idx]);
|
||||
reftable_record_destroy(&rec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
merged_iter_pqueue_add(&mi->pq, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int merged_iter_advance_subiter(struct merged_iter *mi, size_t 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 strbuf entry_key = STRBUF_INIT;
|
||||
struct pq_entry entry = { 0 };
|
||||
int err = 0;
|
||||
|
||||
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)
|
||||
return err;
|
||||
|
||||
/*
|
||||
One can also use reftable as datacenter-local storage, where the ref
|
||||
database is maintained in globally consistent database (eg.
|
||||
CockroachDB or Spanner). In this scenario, replication delays together
|
||||
with compaction may cause newer tables to contain older entries. In
|
||||
such a deployment, the loop below must be changed to collect all
|
||||
entries for the same key, and return new the newest one.
|
||||
*/
|
||||
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 strbuf k = STRBUF_INIT;
|
||||
int err = 0, cmp = 0;
|
||||
|
||||
reftable_record_key(&top.rec, &k);
|
||||
|
||||
cmp = strbuf_cmp(&k, &entry_key);
|
||||
strbuf_release(&k);
|
||||
|
||||
if (cmp > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
merged_iter_pqueue_remove(&mi->pq);
|
||||
err = merged_iter_advance_subiter(mi, top.index);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
reftable_record_destroy(&top.rec);
|
||||
}
|
||||
|
||||
reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
|
||||
reftable_record_destroy(&entry.rec);
|
||||
strbuf_release(&entry_key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)
|
||||
{
|
||||
while (1) {
|
||||
int err = merged_iter_next_entry(mi, rec);
|
||||
if (err == 0 && mi->suppress_deletions &&
|
||||
reftable_record_is_deletion(rec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
static int merged_iter_next_void(void *p, struct reftable_record *rec)
|
||||
{
|
||||
struct merged_iter *mi = p;
|
||||
if (merged_iter_pqueue_is_empty(mi->pq))
|
||||
return 1;
|
||||
|
||||
return merged_iter_next(mi, rec);
|
||||
}
|
||||
|
||||
static struct reftable_iterator_vtable merged_iter_vtable = {
|
||||
.next = &merged_iter_next_void,
|
||||
.close = &merged_iter_close,
|
||||
};
|
||||
|
||||
static void iterator_from_merged_iter(struct reftable_iterator *it,
|
||||
struct merged_iter *mi)
|
||||
{
|
||||
assert(!it->ops);
|
||||
it->iter_arg = mi;
|
||||
it->ops = &merged_iter_vtable;
|
||||
}
|
||||
|
||||
int reftable_new_merged_table(struct reftable_merged_table **dest,
|
||||
struct reftable_table *stack, int n,
|
||||
uint32_t hash_id)
|
||||
{
|
||||
struct reftable_merged_table *m = NULL;
|
||||
uint64_t last_max = 0;
|
||||
uint64_t first_min = 0;
|
||||
int i = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
uint64_t min = reftable_table_min_update_index(&stack[i]);
|
||||
uint64_t max = reftable_table_max_update_index(&stack[i]);
|
||||
|
||||
if (reftable_table_hash_id(&stack[i]) != hash_id) {
|
||||
return REFTABLE_FORMAT_ERROR;
|
||||
}
|
||||
if (i == 0 || min < first_min) {
|
||||
first_min = min;
|
||||
}
|
||||
if (i == 0 || max > last_max) {
|
||||
last_max = max;
|
||||
}
|
||||
}
|
||||
|
||||
m = reftable_calloc(sizeof(struct reftable_merged_table));
|
||||
m->stack = stack;
|
||||
m->stack_len = n;
|
||||
m->min = first_min;
|
||||
m->max = last_max;
|
||||
m->hash_id = hash_id;
|
||||
*dest = m;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* clears the list of subtable, without affecting the readers themselves. */
|
||||
void merged_table_release(struct reftable_merged_table *mt)
|
||||
{
|
||||
FREE_AND_NULL(mt->stack);
|
||||
mt->stack_len = 0;
|
||||
}
|
||||
|
||||
void reftable_merged_table_free(struct reftable_merged_table *mt)
|
||||
{
|
||||
if (!mt) {
|
||||
return;
|
||||
}
|
||||
merged_table_release(mt);
|
||||
reftable_free(mt);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
reftable_merged_table_max_update_index(struct reftable_merged_table *mt)
|
||||
{
|
||||
return mt->max;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
reftable_merged_table_min_update_index(struct reftable_merged_table *mt)
|
||||
{
|
||||
return mt->min;
|
||||
}
|
||||
|
||||
static int reftable_table_seek_record(struct reftable_table *tab,
|
||||
struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
return tab->ops->seek_record(tab->table_arg, it, rec);
|
||||
}
|
||||
|
||||
static int merged_table_seek_record(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
struct reftable_iterator *iters = reftable_calloc(
|
||||
sizeof(struct reftable_iterator) * mt->stack_len);
|
||||
struct merged_iter merged = {
|
||||
.stack = iters,
|
||||
.typ = reftable_record_type(rec),
|
||||
.hash_id = mt->hash_id,
|
||||
.suppress_deletions = mt->suppress_deletions,
|
||||
};
|
||||
int n = 0;
|
||||
int err = 0;
|
||||
int i = 0;
|
||||
for (i = 0; i < mt->stack_len && err == 0; i++) {
|
||||
int e = reftable_table_seek_record(&mt->stack[i], &iters[n],
|
||||
rec);
|
||||
if (e < 0) {
|
||||
err = e;
|
||||
}
|
||||
if (e == 0) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (err < 0) {
|
||||
int i = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
reftable_iterator_destroy(&iters[i]);
|
||||
}
|
||||
reftable_free(iters);
|
||||
return err;
|
||||
}
|
||||
|
||||
merged.stack_len = n;
|
||||
err = merged_iter_init(&merged);
|
||||
if (err < 0) {
|
||||
merged_iter_close(&merged);
|
||||
return err;
|
||||
} else {
|
||||
struct merged_iter *p =
|
||||
reftable_malloc(sizeof(struct merged_iter));
|
||||
*p = merged;
|
||||
iterator_from_merged_iter(it, p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
const char *name)
|
||||
{
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = (char *)name,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_ref(&rec, &ref);
|
||||
return merged_table_seek_record(mt, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
const char *name, uint64_t update_index)
|
||||
{
|
||||
struct reftable_log_record log = {
|
||||
.refname = (char *)name,
|
||||
.update_index = update_index,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_log(&rec, &log);
|
||||
return merged_table_seek_record(mt, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
const char *name)
|
||||
{
|
||||
uint64_t max = ~((uint64_t)0);
|
||||
return reftable_merged_table_seek_log_at(mt, it, name, max);
|
||||
}
|
||||
|
||||
uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt)
|
||||
{
|
||||
return mt->hash_id;
|
||||
}
|
||||
|
||||
static int reftable_merged_table_seek_void(void *tab,
|
||||
struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
return merged_table_seek_record(tab, it, rec);
|
||||
}
|
||||
|
||||
static uint32_t reftable_merged_table_hash_id_void(void *tab)
|
||||
{
|
||||
return reftable_merged_table_hash_id(tab);
|
||||
}
|
||||
|
||||
static uint64_t reftable_merged_table_min_update_index_void(void *tab)
|
||||
{
|
||||
return reftable_merged_table_min_update_index(tab);
|
||||
}
|
||||
|
||||
static uint64_t reftable_merged_table_max_update_index_void(void *tab)
|
||||
{
|
||||
return reftable_merged_table_max_update_index(tab);
|
||||
}
|
||||
|
||||
static struct reftable_table_vtable merged_table_vtable = {
|
||||
.seek_record = reftable_merged_table_seek_void,
|
||||
.hash_id = reftable_merged_table_hash_id_void,
|
||||
.min_update_index = reftable_merged_table_min_update_index_void,
|
||||
.max_update_index = reftable_merged_table_max_update_index_void,
|
||||
};
|
||||
|
||||
void reftable_table_from_merged_table(struct reftable_table *tab,
|
||||
struct reftable_merged_table *merged)
|
||||
{
|
||||
assert(!tab->ops);
|
||||
tab->ops = &merged_table_vtable;
|
||||
tab->table_arg = merged;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
struct reftable_merged_table {
|
||||
struct reftable_table *stack;
|
||||
size_t stack_len;
|
||||
uint32_t hash_id;
|
||||
|
||||
/* If unset, produce deletions. This is useful for compaction. For the
|
||||
* full stack, deletions should be produced. */
|
||||
int suppress_deletions;
|
||||
|
||||
uint64_t min;
|
||||
uint64_t max;
|
||||
};
|
||||
|
||||
struct merged_iter {
|
||||
struct reftable_iterator *stack;
|
||||
uint32_t hash_id;
|
||||
size_t stack_len;
|
||||
uint8_t typ;
|
||||
int suppress_deletions;
|
||||
struct merged_iter_pqueue pq;
|
||||
};
|
||||
|
||||
void merged_table_release(struct reftable_merged_table *mt);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,468 @@
|
|||
/*
|
||||
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 "blocksource.h"
|
||||
#include "constants.h"
|
||||
#include "reader.h"
|
||||
#include "record.h"
|
||||
#include "test_framework.h"
|
||||
#include "reftable-merged.h"
|
||||
#include "reftable-tests.h"
|
||||
#include "reftable-generic.h"
|
||||
#include "reftable-writer.h"
|
||||
|
||||
static void write_test_table(struct strbuf *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(&strbuf_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]);
|
||||
EXPECT(n == 0);
|
||||
EXPECT(before == refs[i].update_index);
|
||||
}
|
||||
|
||||
err = reftable_writer_close(w);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
reftable_writer_free(w);
|
||||
}
|
||||
|
||||
static void write_test_log_table(struct strbuf *buf,
|
||||
struct reftable_log_record logs[], int n,
|
||||
uint64_t update_index)
|
||||
{
|
||||
int i = 0;
|
||||
int err;
|
||||
|
||||
struct reftable_write_options opts = {
|
||||
.block_size = 256,
|
||||
.exact_log_message = 1,
|
||||
};
|
||||
struct reftable_writer *w = NULL;
|
||||
w = reftable_new_writer(&strbuf_add_void, buf, &opts);
|
||||
reftable_writer_set_limits(w, update_index, update_index);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
int err = reftable_writer_add_log(w, &logs[i]);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
err = reftable_writer_close(w);
|
||||
EXPECT_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,
|
||||
struct reftable_reader ***readers, int *sizes,
|
||||
struct strbuf *buf, int n)
|
||||
{
|
||||
int i = 0;
|
||||
struct reftable_merged_table *mt = NULL;
|
||||
int err;
|
||||
struct reftable_table *tabs =
|
||||
reftable_calloc(n * sizeof(struct reftable_table));
|
||||
*readers = reftable_calloc(n * sizeof(struct reftable_reader *));
|
||||
*source = reftable_calloc(n * sizeof(**source));
|
||||
for (i = 0; i < n; i++) {
|
||||
write_test_table(&buf[i], refs[i], sizes[i]);
|
||||
block_source_from_strbuf(&(*source)[i], &buf[i]);
|
||||
|
||||
err = reftable_new_reader(&(*readers)[i], &(*source)[i],
|
||||
"name");
|
||||
EXPECT_ERR(err);
|
||||
reftable_table_from_reader(&tabs[i], (*readers)[i]);
|
||||
}
|
||||
|
||||
err = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);
|
||||
EXPECT_ERR(err);
|
||||
return mt;
|
||||
}
|
||||
|
||||
static void readers_destroy(struct reftable_reader **readers, size_t n)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < n; i++)
|
||||
reftable_reader_free(readers[i]);
|
||||
reftable_free(readers);
|
||||
}
|
||||
|
||||
static void test_merged_between(void)
|
||||
{
|
||||
uint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };
|
||||
|
||||
struct reftable_ref_record r1[] = { {
|
||||
.refname = "b",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_VAL1,
|
||||
.value.val1 = hash1,
|
||||
} };
|
||||
struct reftable_ref_record r2[] = { {
|
||||
.refname = "a",
|
||||
.update_index = 2,
|
||||
.value_type = REFTABLE_REF_DELETION,
|
||||
} };
|
||||
|
||||
struct reftable_ref_record *refs[] = { r1, r2 };
|
||||
int sizes[] = { 1, 1 };
|
||||
struct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT };
|
||||
struct reftable_block_source *bs = NULL;
|
||||
struct reftable_reader **readers = NULL;
|
||||
struct reftable_merged_table *mt =
|
||||
merged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);
|
||||
int i;
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
struct reftable_iterator it = { NULL };
|
||||
int err = reftable_merged_table_seek_ref(mt, &it, "a");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(ref.update_index == 2);
|
||||
reftable_ref_record_release(&ref);
|
||||
reftable_iterator_destroy(&it);
|
||||
readers_destroy(readers, 2);
|
||||
reftable_merged_table_free(mt);
|
||||
for (i = 0; i < ARRAY_SIZE(bufs); i++) {
|
||||
strbuf_release(&bufs[i]);
|
||||
}
|
||||
reftable_free(bs);
|
||||
}
|
||||
|
||||
static void test_merged(void)
|
||||
{
|
||||
uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
|
||||
uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
|
||||
struct reftable_ref_record r1[] = {
|
||||
{
|
||||
.refname = "a",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_VAL1,
|
||||
.value.val1 = hash1,
|
||||
},
|
||||
{
|
||||
.refname = "b",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_VAL1,
|
||||
.value.val1 = hash1,
|
||||
},
|
||||
{
|
||||
.refname = "c",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_VAL1,
|
||||
.value.val1 = hash1,
|
||||
}
|
||||
};
|
||||
struct reftable_ref_record r2[] = { {
|
||||
.refname = "a",
|
||||
.update_index = 2,
|
||||
.value_type = REFTABLE_REF_DELETION,
|
||||
} };
|
||||
struct reftable_ref_record r3[] = {
|
||||
{
|
||||
.refname = "c",
|
||||
.update_index = 3,
|
||||
.value_type = REFTABLE_REF_VAL1,
|
||||
.value.val1 = hash2,
|
||||
},
|
||||
{
|
||||
.refname = "d",
|
||||
.update_index = 3,
|
||||
.value_type = REFTABLE_REF_VAL1,
|
||||
.value.val1 = 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 strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
|
||||
struct reftable_block_source *bs = NULL;
|
||||
struct reftable_reader **readers = NULL;
|
||||
struct reftable_merged_table *mt =
|
||||
merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
|
||||
|
||||
struct reftable_iterator it = { NULL };
|
||||
int err = reftable_merged_table_seek_ref(mt, &it, "a");
|
||||
struct reftable_ref_record *out = NULL;
|
||||
size_t len = 0;
|
||||
size_t cap = 0;
|
||||
int i = 0;
|
||||
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);
|
||||
EXPECT(reftable_merged_table_min_update_index(mt) == 1);
|
||||
|
||||
while (len < 100) { /* cap loops/recursion. */
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
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);
|
||||
|
||||
EXPECT(ARRAY_SIZE(want) == len);
|
||||
for (i = 0; i < len; i++) {
|
||||
EXPECT(reftable_ref_record_equal(&want[i], &out[i],
|
||||
GIT_SHA1_RAWSZ));
|
||||
}
|
||||
for (i = 0; i < len; i++) {
|
||||
reftable_ref_record_release(&out[i]);
|
||||
}
|
||||
reftable_free(out);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
strbuf_release(&bufs[i]);
|
||||
}
|
||||
readers_destroy(readers, 3);
|
||||
reftable_merged_table_free(mt);
|
||||
reftable_free(bs);
|
||||
}
|
||||
|
||||
static struct reftable_merged_table *
|
||||
merged_table_from_log_records(struct reftable_log_record **logs,
|
||||
struct reftable_block_source **source,
|
||||
struct reftable_reader ***readers, int *sizes,
|
||||
struct strbuf *buf, int n)
|
||||
{
|
||||
int i = 0;
|
||||
struct reftable_merged_table *mt = NULL;
|
||||
int err;
|
||||
struct reftable_table *tabs =
|
||||
reftable_calloc(n * sizeof(struct reftable_table));
|
||||
*readers = reftable_calloc(n * sizeof(struct reftable_reader *));
|
||||
*source = reftable_calloc(n * sizeof(**source));
|
||||
for (i = 0; i < n; i++) {
|
||||
write_test_log_table(&buf[i], logs[i], sizes[i], i + 1);
|
||||
block_source_from_strbuf(&(*source)[i], &buf[i]);
|
||||
|
||||
err = reftable_new_reader(&(*readers)[i], &(*source)[i],
|
||||
"name");
|
||||
EXPECT_ERR(err);
|
||||
reftable_table_from_reader(&tabs[i], (*readers)[i]);
|
||||
}
|
||||
|
||||
err = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);
|
||||
EXPECT_ERR(err);
|
||||
return mt;
|
||||
}
|
||||
|
||||
static void test_merged_logs(void)
|
||||
{
|
||||
uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
|
||||
uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
|
||||
uint8_t hash3[GIT_SHA1_RAWSZ] = { 3 };
|
||||
struct reftable_log_record r1[] = {
|
||||
{
|
||||
.refname = "a",
|
||||
.update_index = 2,
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value.update = {
|
||||
.old_hash = hash2,
|
||||
/* deletion */
|
||||
.name = "jane doe",
|
||||
.email = "jane@invalid",
|
||||
.message = "message2",
|
||||
}
|
||||
},
|
||||
{
|
||||
.refname = "a",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value.update = {
|
||||
.old_hash = hash1,
|
||||
.new_hash = hash2,
|
||||
.name = "jane doe",
|
||||
.email = "jane@invalid",
|
||||
.message = "message1",
|
||||
}
|
||||
},
|
||||
};
|
||||
struct reftable_log_record r2[] = {
|
||||
{
|
||||
.refname = "a",
|
||||
.update_index = 3,
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value.update = {
|
||||
.new_hash = hash3,
|
||||
.name = "jane doe",
|
||||
.email = "jane@invalid",
|
||||
.message = "message3",
|
||||
}
|
||||
},
|
||||
};
|
||||
struct reftable_log_record r3[] = {
|
||||
{
|
||||
.refname = "a",
|
||||
.update_index = 2,
|
||||
.value_type = REFTABLE_LOG_DELETION,
|
||||
},
|
||||
};
|
||||
struct reftable_log_record want[] = {
|
||||
r2[0],
|
||||
r3[0],
|
||||
r1[1],
|
||||
};
|
||||
|
||||
struct reftable_log_record *logs[] = { r1, r2, r3 };
|
||||
int sizes[3] = { 2, 1, 1 };
|
||||
struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
|
||||
struct reftable_block_source *bs = NULL;
|
||||
struct reftable_reader **readers = NULL;
|
||||
struct reftable_merged_table *mt = merged_table_from_log_records(
|
||||
logs, &bs, &readers, sizes, bufs, 3);
|
||||
|
||||
struct reftable_iterator it = { NULL };
|
||||
int err = reftable_merged_table_seek_log(mt, &it, "a");
|
||||
struct reftable_log_record *out = NULL;
|
||||
size_t len = 0;
|
||||
size_t cap = 0;
|
||||
int i = 0;
|
||||
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);
|
||||
EXPECT(reftable_merged_table_min_update_index(mt) == 1);
|
||||
|
||||
while (len < 100) { /* cap loops/recursion. */
|
||||
struct reftable_log_record log = { NULL };
|
||||
int err = reftable_iterator_next_log(&it, &log);
|
||||
if (err > 0) {
|
||||
break;
|
||||
}
|
||||
if (len == cap) {
|
||||
cap = 2 * cap + 1;
|
||||
out = reftable_realloc(
|
||||
out, sizeof(struct reftable_log_record) * cap);
|
||||
}
|
||||
out[len++] = log;
|
||||
}
|
||||
reftable_iterator_destroy(&it);
|
||||
|
||||
EXPECT(ARRAY_SIZE(want) == len);
|
||||
for (i = 0; i < len; i++) {
|
||||
EXPECT(reftable_log_record_equal(&want[i], &out[i],
|
||||
GIT_SHA1_RAWSZ));
|
||||
}
|
||||
|
||||
err = reftable_merged_table_seek_log_at(mt, &it, "a", 2);
|
||||
EXPECT_ERR(err);
|
||||
reftable_log_record_release(&out[0]);
|
||||
err = reftable_iterator_next_log(&it, &out[0]);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(reftable_log_record_equal(&out[0], &r3[0], GIT_SHA1_RAWSZ));
|
||||
reftable_iterator_destroy(&it);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
reftable_log_record_release(&out[i]);
|
||||
}
|
||||
reftable_free(out);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
strbuf_release(&bufs[i]);
|
||||
}
|
||||
readers_destroy(readers, 3);
|
||||
reftable_merged_table_free(mt);
|
||||
reftable_free(bs);
|
||||
}
|
||||
|
||||
static void test_default_write_opts(void)
|
||||
{
|
||||
struct reftable_write_options opts = { 0 };
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, &buf, &opts);
|
||||
|
||||
struct reftable_ref_record rec = {
|
||||
.refname = "master",
|
||||
.update_index = 1,
|
||||
};
|
||||
int err;
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);
|
||||
uint32_t hash_id;
|
||||
struct reftable_reader *rd = NULL;
|
||||
struct reftable_merged_table *merged = NULL;
|
||||
|
||||
reftable_writer_set_limits(w, 1, 1);
|
||||
|
||||
err = reftable_writer_add_ref(w, &rec);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_writer_close(w);
|
||||
EXPECT_ERR(err);
|
||||
reftable_writer_free(w);
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = reftable_new_reader(&rd, &source, "filename");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
hash_id = reftable_reader_hash_id(rd);
|
||||
EXPECT(hash_id == GIT_SHA1_FORMAT_ID);
|
||||
|
||||
reftable_table_from_reader(&tab[0], rd);
|
||||
err = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
reftable_reader_free(rd);
|
||||
reftable_merged_table_free(merged);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
/* XXX test refs_for(oid) */
|
||||
|
||||
int merged_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_merged_logs);
|
||||
RUN_TEST(test_merged_between);
|
||||
RUN_TEST(test_merged);
|
||||
RUN_TEST(test_default_write_opts);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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 "pq.h"
|
||||
|
||||
#include "reftable-record.h"
|
||||
#include "system.h"
|
||||
#include "basics.h"
|
||||
|
||||
int pq_less(struct pq_entry *a, struct pq_entry *b)
|
||||
{
|
||||
struct strbuf ak = STRBUF_INIT;
|
||||
struct strbuf bk = STRBUF_INIT;
|
||||
int cmp = 0;
|
||||
reftable_record_key(&a->rec, &ak);
|
||||
reftable_record_key(&b->rec, &bk);
|
||||
|
||||
cmp = strbuf_cmp(&ak, &bk);
|
||||
|
||||
strbuf_release(&ak);
|
||||
strbuf_release(&bk);
|
||||
|
||||
if (cmp == 0)
|
||||
return a->index > b->index;
|
||||
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
|
||||
{
|
||||
return pq.heap[0];
|
||||
}
|
||||
|
||||
int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
|
||||
{
|
||||
return pq.len == 0;
|
||||
}
|
||||
|
||||
struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
|
||||
{
|
||||
int i = 0;
|
||||
struct pq_entry e = pq->heap[0];
|
||||
pq->heap[0] = pq->heap[pq->len - 1];
|
||||
pq->len--;
|
||||
|
||||
i = 0;
|
||||
while (i < pq->len) {
|
||||
int min = i;
|
||||
int j = 2 * i + 1;
|
||||
int k = 2 * i + 2;
|
||||
if (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) {
|
||||
min = j;
|
||||
}
|
||||
if (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) {
|
||||
min = k;
|
||||
}
|
||||
|
||||
if (min == i) {
|
||||
break;
|
||||
}
|
||||
|
||||
SWAP(pq->heap[i], pq->heap[min]);
|
||||
i = min;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
|
||||
{
|
||||
int i = 0;
|
||||
if (pq->len == pq->cap) {
|
||||
pq->cap = 2 * pq->cap + 1;
|
||||
pq->heap = reftable_realloc(pq->heap,
|
||||
pq->cap * sizeof(struct pq_entry));
|
||||
}
|
||||
|
||||
pq->heap[pq->len++] = e;
|
||||
i = pq->len - 1;
|
||||
while (i > 0) {
|
||||
int j = (i - 1) / 2;
|
||||
if (pq_less(&pq->heap[j], &pq->heap[i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
SWAP(pq->heap[j], pq->heap[i]);
|
||||
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
|
||||
{
|
||||
int i = 0;
|
||||
for (i = 0; i < pq->len; i++) {
|
||||
reftable_record_destroy(&pq->heap[i].rec);
|
||||
}
|
||||
FREE_AND_NULL(pq->heap);
|
||||
pq->len = pq->cap = 0;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
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;
|
||||
};
|
||||
|
||||
struct merged_iter_pqueue {
|
||||
struct pq_entry *heap;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
|
||||
int 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_release(struct merged_iter_pqueue *pq);
|
||||
int pq_less(struct pq_entry *a, struct pq_entry *b);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
#include "constants.h"
|
||||
#include "pq.h"
|
||||
#include "record.h"
|
||||
#include "reftable-tests.h"
|
||||
#include "test_framework.h"
|
||||
|
||||
void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < pq.len; i++) {
|
||||
int parent = (i - 1) / 2;
|
||||
|
||||
EXPECT(pq_less(&pq.heap[parent], &pq.heap[i]));
|
||||
}
|
||||
}
|
||||
|
||||
static void test_pq(void)
|
||||
{
|
||||
char *names[54] = { NULL };
|
||||
int N = ARRAY_SIZE(names) - 1;
|
||||
|
||||
struct merged_iter_pqueue pq = { NULL };
|
||||
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)->refname = 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) {
|
||||
EXPECT(strcmp(last, ref->refname) < 0);
|
||||
}
|
||||
last = ref->refname;
|
||||
ref->refname = NULL;
|
||||
reftable_free(ref);
|
||||
}
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
reftable_free(names[i]);
|
||||
}
|
||||
|
||||
merged_iter_pqueue_release(&pq);
|
||||
}
|
||||
|
||||
int pq_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_pq);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
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-malloc.h"
|
||||
|
||||
#include "basics.h"
|
||||
#include "system.h"
|
||||
|
||||
static void *(*reftable_malloc_ptr)(size_t sz);
|
||||
static void *(*reftable_realloc_ptr)(void *, size_t);
|
||||
static void (*reftable_free_ptr)(void *);
|
||||
|
||||
void *reftable_malloc(size_t sz)
|
||||
{
|
||||
if (reftable_malloc_ptr)
|
||||
return (*reftable_malloc_ptr)(sz);
|
||||
return malloc(sz);
|
||||
}
|
||||
|
||||
void *reftable_realloc(void *p, size_t sz)
|
||||
{
|
||||
if (reftable_realloc_ptr)
|
||||
return (*reftable_realloc_ptr)(p, sz);
|
||||
return realloc(p, sz);
|
||||
}
|
||||
|
||||
void reftable_free(void *p)
|
||||
{
|
||||
if (reftable_free_ptr)
|
||||
reftable_free_ptr(p);
|
||||
else
|
||||
free(p);
|
||||
}
|
||||
|
||||
void *reftable_calloc(size_t sz)
|
||||
{
|
||||
void *p = reftable_malloc(sz);
|
||||
memset(p, 0, sz);
|
||||
return p;
|
||||
}
|
||||
|
||||
void reftable_set_alloc(void *(*malloc)(size_t),
|
||||
void *(*realloc)(void *, size_t), void (*free)(void *))
|
||||
{
|
||||
reftable_malloc_ptr = malloc;
|
||||
reftable_realloc_ptr = realloc;
|
||||
reftable_free_ptr = free;
|
||||
}
|
||||
|
||||
int hash_size(uint32_t id)
|
||||
{
|
||||
switch (id) {
|
||||
case 0:
|
||||
case GIT_SHA1_FORMAT_ID:
|
||||
return GIT_SHA1_RAWSZ;
|
||||
case GIT_SHA256_FORMAT_ID:
|
||||
return GIT_SHA256_RAWSZ;
|
||||
}
|
||||
abort();
|
||||
}
|
|
@ -0,0 +1,801 @@
|
|||
/*
|
||||
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 "reader.h"
|
||||
|
||||
#include "system.h"
|
||||
#include "block.h"
|
||||
#include "constants.h"
|
||||
#include "generic.h"
|
||||
#include "iter.h"
|
||||
#include "record.h"
|
||||
#include "reftable-error.h"
|
||||
#include "reftable-generic.h"
|
||||
#include "tree.h"
|
||||
|
||||
uint64_t block_source_size(struct reftable_block_source *source)
|
||||
{
|
||||
return source->ops->size(source->arg);
|
||||
}
|
||||
|
||||
int block_source_read_block(struct reftable_block_source *source,
|
||||
struct reftable_block *dest, uint64_t off,
|
||||
uint32_t size)
|
||||
{
|
||||
int result = source->ops->read_block(source->arg, dest, off, size);
|
||||
dest->source = *source;
|
||||
return result;
|
||||
}
|
||||
|
||||
void block_source_close(struct reftable_block_source *source)
|
||||
{
|
||||
if (!source->ops) {
|
||||
return;
|
||||
}
|
||||
|
||||
source->ops->close(source->arg);
|
||||
source->ops = NULL;
|
||||
}
|
||||
|
||||
static struct reftable_reader_offsets *
|
||||
reader_offsets_for(struct reftable_reader *r, uint8_t typ)
|
||||
{
|
||||
switch (typ) {
|
||||
case BLOCK_TYPE_REF:
|
||||
return &r->ref_offsets;
|
||||
case BLOCK_TYPE_LOG:
|
||||
return &r->log_offsets;
|
||||
case BLOCK_TYPE_OBJ:
|
||||
return &r->obj_offsets;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
static int reader_get_block(struct reftable_reader *r,
|
||||
struct reftable_block *dest, uint64_t off,
|
||||
uint32_t sz)
|
||||
{
|
||||
if (off >= r->size)
|
||||
return 0;
|
||||
|
||||
if (off + sz > r->size) {
|
||||
sz = r->size - off;
|
||||
}
|
||||
|
||||
return block_source_read_block(&r->source, dest, off, sz);
|
||||
}
|
||||
|
||||
uint32_t reftable_reader_hash_id(struct reftable_reader *r)
|
||||
{
|
||||
return r->hash_id;
|
||||
}
|
||||
|
||||
const char *reader_name(struct reftable_reader *r)
|
||||
{
|
||||
return r->name;
|
||||
}
|
||||
|
||||
static int parse_footer(struct reftable_reader *r, uint8_t *footer,
|
||||
uint8_t *header)
|
||||
{
|
||||
uint8_t *f = footer;
|
||||
uint8_t first_block_typ;
|
||||
int err = 0;
|
||||
uint32_t computed_crc;
|
||||
uint32_t file_crc;
|
||||
|
||||
if (memcmp(f, "REFT", 4)) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
f += 4;
|
||||
|
||||
if (memcmp(footer, header, header_size(r->version))) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
f++;
|
||||
r->block_size = get_be24(f);
|
||||
|
||||
f += 3;
|
||||
r->min_update_index = get_be64(f);
|
||||
f += 8;
|
||||
r->max_update_index = get_be64(f);
|
||||
f += 8;
|
||||
|
||||
if (r->version == 1) {
|
||||
r->hash_id = GIT_SHA1_FORMAT_ID;
|
||||
} else {
|
||||
r->hash_id = get_be32(f);
|
||||
switch (r->hash_id) {
|
||||
case GIT_SHA1_FORMAT_ID:
|
||||
break;
|
||||
case GIT_SHA256_FORMAT_ID:
|
||||
break;
|
||||
default:
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
f += 4;
|
||||
}
|
||||
|
||||
r->ref_offsets.index_offset = get_be64(f);
|
||||
f += 8;
|
||||
|
||||
r->obj_offsets.offset = get_be64(f);
|
||||
f += 8;
|
||||
|
||||
r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
|
||||
r->obj_offsets.offset >>= 5;
|
||||
|
||||
r->obj_offsets.index_offset = get_be64(f);
|
||||
f += 8;
|
||||
r->log_offsets.offset = get_be64(f);
|
||||
f += 8;
|
||||
r->log_offsets.index_offset = get_be64(f);
|
||||
f += 8;
|
||||
|
||||
computed_crc = crc32(0, footer, f - footer);
|
||||
file_crc = get_be32(f);
|
||||
f += 4;
|
||||
if (computed_crc != file_crc) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
first_block_typ = header[header_size(r->version)];
|
||||
r->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF);
|
||||
r->ref_offsets.offset = 0;
|
||||
r->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG ||
|
||||
r->log_offsets.offset > 0);
|
||||
r->obj_offsets.is_present = r->obj_offsets.offset > 0;
|
||||
err = 0;
|
||||
done:
|
||||
return err;
|
||||
}
|
||||
|
||||
int init_reader(struct reftable_reader *r, struct reftable_block_source *source,
|
||||
const char *name)
|
||||
{
|
||||
struct reftable_block footer = { NULL };
|
||||
struct reftable_block header = { NULL };
|
||||
int err = 0;
|
||||
uint64_t file_size = block_source_size(source);
|
||||
|
||||
/* Need +1 to read type of first block. */
|
||||
uint32_t read_size = header_size(2) + 1; /* read v2 because it's larger. */
|
||||
memset(r, 0, sizeof(struct reftable_reader));
|
||||
|
||||
if (read_size > file_size) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = block_source_read_block(source, &header, 0, read_size);
|
||||
if (err != read_size) {
|
||||
err = REFTABLE_IO_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (memcmp(header.data, "REFT", 4)) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
r->version = header.data[4];
|
||||
if (r->version != 1 && r->version != 2) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
r->size = file_size - footer_size(r->version);
|
||||
r->source = *source;
|
||||
r->name = xstrdup(name);
|
||||
r->hash_id = 0;
|
||||
|
||||
err = block_source_read_block(source, &footer, r->size,
|
||||
footer_size(r->version));
|
||||
if (err != footer_size(r->version)) {
|
||||
err = REFTABLE_IO_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = parse_footer(r, footer.data, header.data);
|
||||
done:
|
||||
reftable_block_done(&footer);
|
||||
reftable_block_done(&header);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct table_iter {
|
||||
struct reftable_reader *r;
|
||||
uint8_t typ;
|
||||
uint64_t block_off;
|
||||
struct block_iter bi;
|
||||
int is_finished;
|
||||
};
|
||||
#define TABLE_ITER_INIT \
|
||||
{ \
|
||||
.bi = {.last_key = STRBUF_INIT } \
|
||||
}
|
||||
|
||||
static void table_iter_copy_from(struct table_iter *dest,
|
||||
struct table_iter *src)
|
||||
{
|
||||
dest->r = src->r;
|
||||
dest->typ = src->typ;
|
||||
dest->block_off = src->block_off;
|
||||
dest->is_finished = src->is_finished;
|
||||
block_iter_copy_from(&dest->bi, &src->bi);
|
||||
}
|
||||
|
||||
static int table_iter_next_in_block(struct table_iter *ti,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
int res = block_iter_next(&ti->bi, rec);
|
||||
if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
|
||||
((struct reftable_ref_record *)rec->data)->update_index +=
|
||||
ti->r->min_update_index;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void table_iter_block_done(struct table_iter *ti)
|
||||
{
|
||||
if (!ti->bi.br) {
|
||||
return;
|
||||
}
|
||||
reftable_block_done(&ti->bi.br->block);
|
||||
FREE_AND_NULL(ti->bi.br);
|
||||
|
||||
ti->bi.last_key.len = 0;
|
||||
ti->bi.next_off = 0;
|
||||
}
|
||||
|
||||
static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off,
|
||||
int version)
|
||||
{
|
||||
int32_t result = 0;
|
||||
|
||||
if (off == 0) {
|
||||
data += header_size(version);
|
||||
}
|
||||
|
||||
*typ = data[0];
|
||||
if (reftable_is_block_type(*typ)) {
|
||||
result = get_be24(data + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
|
||||
uint64_t next_off, uint8_t want_typ)
|
||||
{
|
||||
int32_t guess_block_size = r->block_size ? r->block_size :
|
||||
DEFAULT_BLOCK_SIZE;
|
||||
struct reftable_block block = { NULL };
|
||||
uint8_t block_typ = 0;
|
||||
int err = 0;
|
||||
uint32_t header_off = next_off ? 0 : header_size(r->version);
|
||||
int32_t block_size = 0;
|
||||
|
||||
if (next_off >= r->size)
|
||||
return 1;
|
||||
|
||||
err = reader_get_block(r, &block, next_off, guess_block_size);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
block_size = extract_block_size(block.data, &block_typ, next_off,
|
||||
r->version);
|
||||
if (block_size < 0)
|
||||
return block_size;
|
||||
|
||||
if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
|
||||
reftable_block_done(&block);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (block_size > guess_block_size) {
|
||||
reftable_block_done(&block);
|
||||
err = reader_get_block(r, &block, next_off, block_size);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return block_reader_init(br, &block, header_off, r->block_size,
|
||||
hash_size(r->hash_id));
|
||||
}
|
||||
|
||||
static int table_iter_next_block(struct table_iter *dest,
|
||||
struct table_iter *src)
|
||||
{
|
||||
uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
|
||||
struct block_reader br = { 0 };
|
||||
int err = 0;
|
||||
|
||||
dest->r = src->r;
|
||||
dest->typ = src->typ;
|
||||
dest->block_off = next_block_off;
|
||||
|
||||
err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
|
||||
if (err > 0) {
|
||||
dest->is_finished = 1;
|
||||
return 1;
|
||||
}
|
||||
if (err != 0)
|
||||
return err;
|
||||
else {
|
||||
struct block_reader *brp =
|
||||
reftable_malloc(sizeof(struct block_reader));
|
||||
*brp = br;
|
||||
|
||||
dest->is_finished = 0;
|
||||
block_reader_start(brp, &dest->bi);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)
|
||||
{
|
||||
if (reftable_record_type(rec) != ti->typ)
|
||||
return REFTABLE_API_ERROR;
|
||||
|
||||
while (1) {
|
||||
struct table_iter next = TABLE_ITER_INIT;
|
||||
int err = 0;
|
||||
if (ti->is_finished) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = table_iter_next_in_block(ti, rec);
|
||||
if (err <= 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = table_iter_next_block(&next, ti);
|
||||
if (err != 0) {
|
||||
ti->is_finished = 1;
|
||||
}
|
||||
table_iter_block_done(ti);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
table_iter_copy_from(ti, &next);
|
||||
block_iter_close(&next.bi);
|
||||
}
|
||||
}
|
||||
|
||||
static int table_iter_next_void(void *ti, struct reftable_record *rec)
|
||||
{
|
||||
return table_iter_next(ti, rec);
|
||||
}
|
||||
|
||||
static void table_iter_close(void *p)
|
||||
{
|
||||
struct table_iter *ti = p;
|
||||
table_iter_block_done(ti);
|
||||
block_iter_close(&ti->bi);
|
||||
}
|
||||
|
||||
static struct reftable_iterator_vtable table_iter_vtable = {
|
||||
.next = &table_iter_next_void,
|
||||
.close = &table_iter_close,
|
||||
};
|
||||
|
||||
static void iterator_from_table_iter(struct reftable_iterator *it,
|
||||
struct table_iter *ti)
|
||||
{
|
||||
assert(!it->ops);
|
||||
it->iter_arg = ti;
|
||||
it->ops = &table_iter_vtable;
|
||||
}
|
||||
|
||||
static int reader_table_iter_at(struct reftable_reader *r,
|
||||
struct table_iter *ti, uint64_t off,
|
||||
uint8_t typ)
|
||||
{
|
||||
struct block_reader br = { 0 };
|
||||
struct block_reader *brp = NULL;
|
||||
|
||||
int err = reader_init_block_reader(r, &br, off, typ);
|
||||
if (err != 0)
|
||||
return err;
|
||||
|
||||
brp = reftable_malloc(sizeof(struct block_reader));
|
||||
*brp = br;
|
||||
ti->r = r;
|
||||
ti->typ = block_reader_type(brp);
|
||||
ti->block_off = off;
|
||||
block_reader_start(brp, &ti->bi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reader_start(struct reftable_reader *r, struct table_iter *ti,
|
||||
uint8_t typ, int index)
|
||||
{
|
||||
struct reftable_reader_offsets *offs = reader_offsets_for(r, typ);
|
||||
uint64_t off = offs->offset;
|
||||
if (index) {
|
||||
off = offs->index_offset;
|
||||
if (off == 0) {
|
||||
return 1;
|
||||
}
|
||||
typ = BLOCK_TYPE_INDEX;
|
||||
}
|
||||
|
||||
return reader_table_iter_at(r, ti, off, typ);
|
||||
}
|
||||
|
||||
static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
|
||||
struct reftable_record *want)
|
||||
{
|
||||
struct reftable_record rec =
|
||||
reftable_new_record(reftable_record_type(want));
|
||||
struct strbuf want_key = STRBUF_INIT;
|
||||
struct strbuf got_key = STRBUF_INIT;
|
||||
struct table_iter next = TABLE_ITER_INIT;
|
||||
int err = -1;
|
||||
|
||||
reftable_record_key(want, &want_key);
|
||||
|
||||
while (1) {
|
||||
err = table_iter_next_block(&next, ti);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
if (err > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
err = block_reader_first_key(next.bi.br, &got_key);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
if (strbuf_cmp(&got_key, &want_key) > 0) {
|
||||
table_iter_block_done(&next);
|
||||
break;
|
||||
}
|
||||
|
||||
table_iter_block_done(ti);
|
||||
table_iter_copy_from(ti, &next);
|
||||
}
|
||||
|
||||
err = block_iter_seek(&ti->bi, &want_key);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
err = 0;
|
||||
|
||||
done:
|
||||
block_iter_close(&next.bi);
|
||||
reftable_record_destroy(&rec);
|
||||
strbuf_release(&want_key);
|
||||
strbuf_release(&got_key);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int reader_seek_indexed(struct reftable_reader *r,
|
||||
struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
|
||||
struct reftable_record want_index_rec = { NULL };
|
||||
struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
|
||||
struct reftable_record index_result_rec = { NULL };
|
||||
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_record_from_index(&want_index_rec, &want_index);
|
||||
reftable_record_from_index(&index_result_rec, &index_result);
|
||||
|
||||
err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
err = reader_seek_linear(r, &index_iter, &want_index_rec);
|
||||
while (1) {
|
||||
err = table_iter_next(&index_iter, &index_result_rec);
|
||||
table_iter_block_done(&index_iter);
|
||||
if (err != 0)
|
||||
goto done;
|
||||
|
||||
err = reader_table_iter_at(r, &next, index_result.offset, 0);
|
||||
if (err != 0)
|
||||
goto done;
|
||||
|
||||
err = block_iter_seek(&next.bi, &want_index.last_key);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
if (next.typ == reftable_record_type(rec)) {
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (next.typ != BLOCK_TYPE_INDEX) {
|
||||
err = REFTABLE_FORMAT_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
table_iter_copy_from(&index_iter, &next);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
done:
|
||||
block_iter_close(&next.bi);
|
||||
table_iter_close(&index_iter);
|
||||
reftable_record_release(&want_index_rec);
|
||||
reftable_record_release(&index_result_rec);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int reader_seek_internal(struct reftable_reader *r,
|
||||
struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
struct reftable_reader_offsets *offs =
|
||||
reader_offsets_for(r, reftable_record_type(rec));
|
||||
uint64_t idx = offs->index_offset;
|
||||
struct table_iter ti = TABLE_ITER_INIT;
|
||||
int err = 0;
|
||||
if (idx > 0)
|
||||
return reader_seek_indexed(r, it, rec);
|
||||
|
||||
err = reader_start(r, &ti, reftable_record_type(rec), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = reader_seek_linear(r, &ti, rec);
|
||||
if (err < 0)
|
||||
return err;
|
||||
else {
|
||||
struct table_iter *p =
|
||||
reftable_malloc(sizeof(struct table_iter));
|
||||
*p = ti;
|
||||
iterator_from_table_iter(it, p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
uint8_t typ = reftable_record_type(rec);
|
||||
|
||||
struct reftable_reader_offsets *offs = reader_offsets_for(r, typ);
|
||||
if (!offs->is_present) {
|
||||
iterator_set_empty(it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reader_seek_internal(r, it, rec);
|
||||
}
|
||||
|
||||
int reftable_reader_seek_ref(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, const char *name)
|
||||
{
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = (char *)name,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_ref(&rec, &ref);
|
||||
return reader_seek(r, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_reader_seek_log_at(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, const char *name,
|
||||
uint64_t update_index)
|
||||
{
|
||||
struct reftable_log_record log = {
|
||||
.refname = (char *)name,
|
||||
.update_index = update_index,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_log(&rec, &log);
|
||||
return reader_seek(r, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_reader_seek_log(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, const char *name)
|
||||
{
|
||||
uint64_t max = ~((uint64_t)0);
|
||||
return reftable_reader_seek_log_at(r, it, name, max);
|
||||
}
|
||||
|
||||
void reader_close(struct reftable_reader *r)
|
||||
{
|
||||
block_source_close(&r->source);
|
||||
FREE_AND_NULL(r->name);
|
||||
}
|
||||
|
||||
int reftable_new_reader(struct reftable_reader **p,
|
||||
struct reftable_block_source *src, char const *name)
|
||||
{
|
||||
struct reftable_reader *rd =
|
||||
reftable_calloc(sizeof(struct reftable_reader));
|
||||
int err = init_reader(rd, src, name);
|
||||
if (err == 0) {
|
||||
*p = rd;
|
||||
} else {
|
||||
block_source_close(src);
|
||||
reftable_free(rd);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void reftable_reader_free(struct reftable_reader *r)
|
||||
{
|
||||
reader_close(r);
|
||||
reftable_free(r);
|
||||
}
|
||||
|
||||
static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
|
||||
struct reftable_iterator *it,
|
||||
uint8_t *oid)
|
||||
{
|
||||
struct reftable_obj_record want = {
|
||||
.hash_prefix = oid,
|
||||
.hash_prefix_len = r->object_id_len,
|
||||
};
|
||||
struct reftable_record want_rec = { NULL };
|
||||
struct reftable_iterator oit = { NULL };
|
||||
struct reftable_obj_record got = { NULL };
|
||||
struct reftable_record got_rec = { NULL };
|
||||
int err = 0;
|
||||
struct indexed_table_ref_iter *itr = NULL;
|
||||
|
||||
/* Look through the reverse index. */
|
||||
reftable_record_from_obj(&want_rec, &want);
|
||||
err = reader_seek(r, &oit, &want_rec);
|
||||
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 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 done;
|
||||
}
|
||||
|
||||
err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
|
||||
got.offsets, got.offset_len);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
got.offsets = NULL;
|
||||
iterator_from_indexed_table_ref_iter(it, itr);
|
||||
|
||||
done:
|
||||
reftable_iterator_destroy(&oit);
|
||||
reftable_record_release(&got_rec);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int reftable_reader_refs_for_unindexed(struct reftable_reader *r,
|
||||
struct reftable_iterator *it,
|
||||
uint8_t *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;
|
||||
|
||||
*ti = ti_empty;
|
||||
err = reader_start(r, ti, BLOCK_TYPE_REF, 0);
|
||||
if (err < 0) {
|
||||
reftable_free(ti);
|
||||
return err;
|
||||
}
|
||||
|
||||
filter = reftable_malloc(sizeof(struct filtering_ref_iterator));
|
||||
*filter = empty;
|
||||
|
||||
strbuf_add(&filter->oid, oid, oid_len);
|
||||
reftable_table_from_reader(&filter->tab, r);
|
||||
filter->double_check = 0;
|
||||
iterator_from_table_iter(&filter->it, ti);
|
||||
|
||||
iterator_from_filtering_ref_iterator(it, filter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reftable_reader_refs_for(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, uint8_t *oid)
|
||||
{
|
||||
if (r->obj_offsets.is_present)
|
||||
return reftable_reader_refs_for_indexed(r, it, oid);
|
||||
return reftable_reader_refs_for_unindexed(r, it, oid);
|
||||
}
|
||||
|
||||
uint64_t reftable_reader_max_update_index(struct reftable_reader *r)
|
||||
{
|
||||
return r->max_update_index;
|
||||
}
|
||||
|
||||
uint64_t reftable_reader_min_update_index(struct reftable_reader *r)
|
||||
{
|
||||
return r->min_update_index;
|
||||
}
|
||||
|
||||
/* generic table interface. */
|
||||
|
||||
static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
return reader_seek(tab, it, rec);
|
||||
}
|
||||
|
||||
static uint32_t reftable_reader_hash_id_void(void *tab)
|
||||
{
|
||||
return reftable_reader_hash_id(tab);
|
||||
}
|
||||
|
||||
static uint64_t reftable_reader_min_update_index_void(void *tab)
|
||||
{
|
||||
return reftable_reader_min_update_index(tab);
|
||||
}
|
||||
|
||||
static uint64_t reftable_reader_max_update_index_void(void *tab)
|
||||
{
|
||||
return reftable_reader_max_update_index(tab);
|
||||
}
|
||||
|
||||
static struct reftable_table_vtable reader_vtable = {
|
||||
.seek_record = reftable_reader_seek_void,
|
||||
.hash_id = reftable_reader_hash_id_void,
|
||||
.min_update_index = reftable_reader_min_update_index_void,
|
||||
.max_update_index = reftable_reader_max_update_index_void,
|
||||
};
|
||||
|
||||
void reftable_table_from_reader(struct reftable_table *tab,
|
||||
struct reftable_reader *reader)
|
||||
{
|
||||
assert(!tab->ops);
|
||||
tab->ops = &reader_vtable;
|
||||
tab->table_arg = reader;
|
||||
}
|
||||
|
||||
|
||||
int reftable_reader_print_file(const char *tablename)
|
||||
{
|
||||
struct reftable_block_source src = { NULL };
|
||||
int err = reftable_block_source_from_file(&src, tablename);
|
||||
struct reftable_reader *r = NULL;
|
||||
struct reftable_table tab = { NULL };
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
err = reftable_new_reader(&r, &src, tablename);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
reftable_table_from_reader(&tab, r);
|
||||
err = reftable_table_print(&tab);
|
||||
done:
|
||||
reftable_reader_free(r);
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
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 READER_H
|
||||
#define READER_H
|
||||
|
||||
#include "block.h"
|
||||
#include "record.h"
|
||||
#include "reftable-iterator.h"
|
||||
#include "reftable-reader.h"
|
||||
|
||||
uint64_t block_source_size(struct reftable_block_source *source);
|
||||
|
||||
int block_source_read_block(struct reftable_block_source *source,
|
||||
struct reftable_block *dest, uint64_t off,
|
||||
uint32_t size);
|
||||
void block_source_close(struct reftable_block_source *source);
|
||||
|
||||
/* metadata for a block type */
|
||||
struct reftable_reader_offsets {
|
||||
int is_present;
|
||||
uint64_t offset;
|
||||
uint64_t index_offset;
|
||||
};
|
||||
|
||||
/* The state for reading a reftable file. */
|
||||
struct reftable_reader {
|
||||
/* for convience, associate a name with the instance. */
|
||||
char *name;
|
||||
struct reftable_block_source source;
|
||||
|
||||
/* Size of the file, excluding the footer. */
|
||||
uint64_t size;
|
||||
|
||||
/* 'sha1' for SHA1, 's256' for SHA-256 */
|
||||
uint32_t hash_id;
|
||||
|
||||
uint32_t block_size;
|
||||
uint64_t min_update_index;
|
||||
uint64_t max_update_index;
|
||||
/* Length of the OID keys in the 'o' section */
|
||||
int object_id_len;
|
||||
int version;
|
||||
|
||||
struct reftable_reader_offsets ref_offsets;
|
||||
struct reftable_reader_offsets obj_offsets;
|
||||
struct reftable_reader_offsets log_offsets;
|
||||
};
|
||||
|
||||
int init_reader(struct reftable_reader *r, struct reftable_block_source *source,
|
||||
const char *name);
|
||||
void reader_close(struct reftable_reader *r);
|
||||
const char *reader_name(struct reftable_reader *r);
|
||||
|
||||
/* initialize a block reader to read from `r` */
|
||||
int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
|
||||
uint64_t next_off, uint8_t want_typ);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
#include "block.h"
|
||||
#include "blocksource.h"
|
||||
#include "constants.h"
|
||||
#include "reader.h"
|
||||
#include "record.h"
|
||||
#include "test_framework.h"
|
||||
#include "reftable-tests.h"
|
||||
#include "reftable-writer.h"
|
||||
|
||||
static const int update_index = 5;
|
||||
|
||||
static void test_buffer(void)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct reftable_block out = { NULL };
|
||||
int n;
|
||||
uint8_t in[] = "hello";
|
||||
strbuf_add(&buf, in, sizeof(in));
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
EXPECT(block_source_size(&source) == 6);
|
||||
n = block_source_read_block(&source, &out, 0, sizeof(in));
|
||||
EXPECT(n == sizeof(in));
|
||||
EXPECT(!memcmp(in, out.data, n));
|
||||
reftable_block_done(&out);
|
||||
|
||||
n = block_source_read_block(&source, &out, 1, 2);
|
||||
EXPECT(n == 2);
|
||||
EXPECT(!memcmp(out.data, "el", 2));
|
||||
|
||||
reftable_block_done(&out);
|
||||
block_source_close(&source);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static void write_table(char ***names, struct strbuf *buf, int N,
|
||||
int block_size, uint32_t hash_id)
|
||||
{
|
||||
struct reftable_write_options opts = {
|
||||
.block_size = block_size,
|
||||
.hash_id = hash_id,
|
||||
};
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, buf, &opts);
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
int i = 0, n;
|
||||
struct reftable_log_record log = { NULL };
|
||||
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++) {
|
||||
uint8_t hash[GIT_SHA256_RAWSZ] = { 0 };
|
||||
char name[100];
|
||||
int n;
|
||||
|
||||
set_test_hash(hash, i);
|
||||
|
||||
snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
|
||||
|
||||
ref.refname = name;
|
||||
ref.update_index = update_index;
|
||||
ref.value_type = REFTABLE_REF_VAL1;
|
||||
ref.value.val1 = hash;
|
||||
(*names)[i] = xstrdup(name);
|
||||
|
||||
n = reftable_writer_add_ref(w, &ref);
|
||||
EXPECT(n == 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
uint8_t hash[GIT_SHA256_RAWSZ] = { 0 };
|
||||
char name[100];
|
||||
int n;
|
||||
|
||||
set_test_hash(hash, i);
|
||||
|
||||
snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
|
||||
|
||||
log.refname = name;
|
||||
log.update_index = update_index;
|
||||
log.value_type = REFTABLE_LOG_UPDATE;
|
||||
log.value.update.new_hash = hash;
|
||||
log.value.update.message = "message";
|
||||
|
||||
n = reftable_writer_add_log(w, &log);
|
||||
EXPECT(n == 0);
|
||||
}
|
||||
|
||||
n = reftable_writer_close(w);
|
||||
EXPECT(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 == GIT_SHA256_FORMAT_ID) ? 2 : 1);
|
||||
}
|
||||
EXPECT(buf->buf[off] == 'r');
|
||||
}
|
||||
|
||||
EXPECT(stats->log_stats.blocks > 0);
|
||||
reftable_writer_free(w);
|
||||
}
|
||||
|
||||
static void test_log_buffer_size(void)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_write_options opts = {
|
||||
.block_size = 4096,
|
||||
};
|
||||
int err;
|
||||
int i;
|
||||
struct reftable_log_record
|
||||
log = { .refname = "refs/heads/master",
|
||||
.update_index = 0xa,
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value = { .update = {
|
||||
.name = "Han-Wen Nienhuys",
|
||||
.email = "hanwen@google.com",
|
||||
.tz_offset = 100,
|
||||
.time = 0x5e430672,
|
||||
.message = "commit: 9\n",
|
||||
} } };
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, &buf, &opts);
|
||||
|
||||
/* This tests buffer extension for log compression. Must use a random
|
||||
hash, to ensure that the compressed part is larger than the original.
|
||||
*/
|
||||
uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];
|
||||
for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
|
||||
hash1[i] = (uint8_t)(rand() % 256);
|
||||
hash2[i] = (uint8_t)(rand() % 256);
|
||||
}
|
||||
log.value.update.old_hash = hash1;
|
||||
log.value.update.new_hash = hash2;
|
||||
reftable_writer_set_limits(w, update_index, update_index);
|
||||
err = reftable_writer_add_log(w, &log);
|
||||
EXPECT_ERR(err);
|
||||
err = reftable_writer_close(w);
|
||||
EXPECT_ERR(err);
|
||||
reftable_writer_free(w);
|
||||
strbuf_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 = { NULL };
|
||||
int i = 0;
|
||||
struct reftable_log_record log = { NULL };
|
||||
int n;
|
||||
struct reftable_iterator it = { NULL };
|
||||
struct reftable_reader rd = { NULL };
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_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 = { NULL };
|
||||
snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
|
||||
names[i] = xstrdup(name);
|
||||
ref.refname = name;
|
||||
ref.update_index = i;
|
||||
|
||||
err = reftable_writer_add_ref(w, &ref);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
for (i = 0; i < N; i++) {
|
||||
uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];
|
||||
struct reftable_log_record log = { NULL };
|
||||
set_test_hash(hash1, i);
|
||||
set_test_hash(hash2, i + 1);
|
||||
|
||||
log.refname = names[i];
|
||||
log.update_index = i;
|
||||
log.value_type = REFTABLE_LOG_UPDATE;
|
||||
log.value.update.old_hash = hash1;
|
||||
log.value.update.new_hash = hash2;
|
||||
|
||||
err = reftable_writer_add_log(w, &log);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
n = reftable_writer_close(w);
|
||||
EXPECT(n == 0);
|
||||
|
||||
stats = writer_stats(w);
|
||||
EXPECT(stats->log_stats.blocks > 0);
|
||||
reftable_writer_free(w);
|
||||
w = NULL;
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = init_reader(&rd, &source, "file.log");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_reader_seek_ref(&rd, &it, names[N - 1]);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
/* end of iteration. */
|
||||
err = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT(0 < err);
|
||||
|
||||
reftable_iterator_destroy(&it);
|
||||
reftable_ref_record_release(&ref);
|
||||
|
||||
err = reftable_reader_seek_log(&rd, &it, "");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
i = 0;
|
||||
while (1) {
|
||||
int err = reftable_iterator_next_log(&it, &log);
|
||||
if (err > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
EXPECT_ERR(err);
|
||||
EXPECT_STREQ(names[i], log.refname);
|
||||
EXPECT(i == log.update_index);
|
||||
i++;
|
||||
reftable_log_record_release(&log);
|
||||
}
|
||||
|
||||
EXPECT(i == N);
|
||||
reftable_iterator_destroy(&it);
|
||||
|
||||
/* cleanup. */
|
||||
strbuf_release(&buf);
|
||||
free_names(names);
|
||||
reader_close(&rd);
|
||||
}
|
||||
|
||||
static void test_table_read_write_sequential(void)
|
||||
{
|
||||
char **names;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int N = 50;
|
||||
struct reftable_iterator it = { NULL };
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct reftable_reader rd = { NULL };
|
||||
int err = 0;
|
||||
int j = 0;
|
||||
|
||||
write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = init_reader(&rd, &source, "file.ref");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_reader_seek_ref(&rd, &it, "");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
while (1) {
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
int r = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT(r >= 0);
|
||||
if (r > 0) {
|
||||
break;
|
||||
}
|
||||
EXPECT(0 == strcmp(names[j], ref.refname));
|
||||
EXPECT(update_index == ref.update_index);
|
||||
|
||||
j++;
|
||||
reftable_ref_record_release(&ref);
|
||||
}
|
||||
EXPECT(j == N);
|
||||
reftable_iterator_destroy(&it);
|
||||
strbuf_release(&buf);
|
||||
free_names(names);
|
||||
|
||||
reader_close(&rd);
|
||||
}
|
||||
|
||||
static void test_table_write_small_table(void)
|
||||
{
|
||||
char **names;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int N = 1;
|
||||
write_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);
|
||||
EXPECT(buf.len < 200);
|
||||
strbuf_release(&buf);
|
||||
free_names(names);
|
||||
}
|
||||
|
||||
static void test_table_read_api(void)
|
||||
{
|
||||
char **names;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int N = 50;
|
||||
struct reftable_reader rd = { NULL };
|
||||
struct reftable_block_source source = { NULL };
|
||||
int err;
|
||||
int i;
|
||||
struct reftable_log_record log = { NULL };
|
||||
struct reftable_iterator it = { NULL };
|
||||
|
||||
write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = init_reader(&rd, &source, "file.ref");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_reader_seek_ref(&rd, &it, names[0]);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_iterator_next_log(&it, &log);
|
||||
EXPECT(err == REFTABLE_API_ERROR);
|
||||
|
||||
strbuf_release(&buf);
|
||||
for (i = 0; i < N; i++) {
|
||||
reftable_free(names[i]);
|
||||
}
|
||||
reftable_iterator_destroy(&it);
|
||||
reftable_free(names);
|
||||
reader_close(&rd);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static void test_table_read_write_seek(int index, int hash_id)
|
||||
{
|
||||
char **names;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int N = 50;
|
||||
struct reftable_reader rd = { NULL };
|
||||
struct reftable_block_source source = { NULL };
|
||||
int err;
|
||||
int i = 0;
|
||||
|
||||
struct reftable_iterator it = { NULL };
|
||||
struct strbuf pastLast = STRBUF_INIT;
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
|
||||
write_table(&names, &buf, N, 256, hash_id);
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = init_reader(&rd, &source, "file.ref");
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(hash_id == reftable_reader_hash_id(&rd));
|
||||
|
||||
if (!index) {
|
||||
rd.ref_offsets.index_offset = 0;
|
||||
} else {
|
||||
EXPECT(rd.ref_offsets.index_offset > 0);
|
||||
}
|
||||
|
||||
for (i = 1; i < N; i++) {
|
||||
int err = reftable_reader_seek_ref(&rd, &it, names[i]);
|
||||
EXPECT_ERR(err);
|
||||
err = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(0 == strcmp(names[i], ref.refname));
|
||||
EXPECT(REFTABLE_REF_VAL1 == ref.value_type);
|
||||
EXPECT(i == ref.value.val1[0]);
|
||||
|
||||
reftable_ref_record_release(&ref);
|
||||
reftable_iterator_destroy(&it);
|
||||
}
|
||||
|
||||
strbuf_addstr(&pastLast, names[N - 1]);
|
||||
strbuf_addstr(&pastLast, "/");
|
||||
|
||||
err = reftable_reader_seek_ref(&rd, &it, pastLast.buf);
|
||||
if (err == 0) {
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
int err = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT(err > 0);
|
||||
} else {
|
||||
EXPECT(err > 0);
|
||||
}
|
||||
|
||||
strbuf_release(&pastLast);
|
||||
reftable_iterator_destroy(&it);
|
||||
|
||||
strbuf_release(&buf);
|
||||
for (i = 0; i < N; i++) {
|
||||
reftable_free(names[i]);
|
||||
}
|
||||
reftable_free(names);
|
||||
reader_close(&rd);
|
||||
}
|
||||
|
||||
static void test_table_read_write_seek_linear(void)
|
||||
{
|
||||
test_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);
|
||||
}
|
||||
|
||||
static void test_table_read_write_seek_linear_sha256(void)
|
||||
{
|
||||
test_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);
|
||||
}
|
||||
|
||||
static void test_table_read_write_seek_index(void)
|
||||
{
|
||||
test_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);
|
||||
}
|
||||
|
||||
static void test_table_refs_for(int indexed)
|
||||
{
|
||||
int N = 50;
|
||||
char **want_names = reftable_calloc(sizeof(char *) * (N + 1));
|
||||
int want_names_len = 0;
|
||||
uint8_t want_hash[GIT_SHA1_RAWSZ];
|
||||
|
||||
struct reftable_write_options opts = {
|
||||
.block_size = 256,
|
||||
};
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
int i = 0;
|
||||
int n;
|
||||
int err;
|
||||
struct reftable_reader rd;
|
||||
struct reftable_block_source source = { NULL };
|
||||
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, &buf, &opts);
|
||||
|
||||
struct reftable_iterator it = { NULL };
|
||||
int j;
|
||||
|
||||
set_test_hash(want_hash, 4);
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
uint8_t hash[GIT_SHA1_RAWSZ];
|
||||
char fill[51] = { 0 };
|
||||
char name[100];
|
||||
uint8_t hash1[GIT_SHA1_RAWSZ];
|
||||
uint8_t hash2[GIT_SHA1_RAWSZ];
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
|
||||
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.refname = name;
|
||||
|
||||
set_test_hash(hash1, i / 4);
|
||||
set_test_hash(hash2, 3 + i / 4);
|
||||
ref.value_type = REFTABLE_REF_VAL2;
|
||||
ref.value.val2.value = hash1;
|
||||
ref.value.val2.target_value = hash2;
|
||||
|
||||
/* 80 bytes / entry, so 3 entries per block. Yields 17
|
||||
*/
|
||||
/* blocks. */
|
||||
n = reftable_writer_add_ref(w, &ref);
|
||||
EXPECT(n == 0);
|
||||
|
||||
if (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||
|
||||
!memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {
|
||||
want_names[want_names_len++] = xstrdup(name);
|
||||
}
|
||||
}
|
||||
|
||||
n = reftable_writer_close(w);
|
||||
EXPECT(n == 0);
|
||||
|
||||
reftable_writer_free(w);
|
||||
w = NULL;
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = init_reader(&rd, &source, "file.ref");
|
||||
EXPECT_ERR(err);
|
||||
if (!indexed) {
|
||||
rd.obj_offsets.is_present = 0;
|
||||
}
|
||||
|
||||
err = reftable_reader_seek_ref(&rd, &it, "");
|
||||
EXPECT_ERR(err);
|
||||
reftable_iterator_destroy(&it);
|
||||
|
||||
err = reftable_reader_refs_for(&rd, &it, want_hash);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
j = 0;
|
||||
while (1) {
|
||||
int err = reftable_iterator_next_ref(&it, &ref);
|
||||
EXPECT(err >= 0);
|
||||
if (err > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
EXPECT(j < want_names_len);
|
||||
EXPECT(0 == strcmp(ref.refname, want_names[j]));
|
||||
j++;
|
||||
reftable_ref_record_release(&ref);
|
||||
}
|
||||
EXPECT(j == want_names_len);
|
||||
|
||||
strbuf_release(&buf);
|
||||
free_names(want_names);
|
||||
reftable_iterator_destroy(&it);
|
||||
reader_close(&rd);
|
||||
}
|
||||
|
||||
static void test_table_refs_for_no_index(void)
|
||||
{
|
||||
test_table_refs_for(0);
|
||||
}
|
||||
|
||||
static void test_table_refs_for_obj_index(void)
|
||||
{
|
||||
test_table_refs_for(1);
|
||||
}
|
||||
|
||||
static void test_write_empty_table(void)
|
||||
{
|
||||
struct reftable_write_options opts = { 0 };
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, &buf, &opts);
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct reftable_reader *rd = NULL;
|
||||
struct reftable_ref_record rec = { NULL };
|
||||
struct reftable_iterator it = { NULL };
|
||||
int err;
|
||||
|
||||
reftable_writer_set_limits(w, 1, 1);
|
||||
|
||||
err = reftable_writer_close(w);
|
||||
EXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);
|
||||
reftable_writer_free(w);
|
||||
|
||||
EXPECT(buf.len == header_size(1) + footer_size(1));
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
|
||||
err = reftable_new_reader(&rd, &source, "filename");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_reader_seek_ref(rd, &it, "");
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_iterator_next_ref(&it, &rec);
|
||||
EXPECT(err > 0);
|
||||
|
||||
reftable_iterator_destroy(&it);
|
||||
reftable_reader_free(rd);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static void test_write_key_order(void)
|
||||
{
|
||||
struct reftable_write_options opts = { 0 };
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, &buf, &opts);
|
||||
struct reftable_ref_record refs[2] = {
|
||||
{
|
||||
.refname = "b",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value = {
|
||||
.symref = "target",
|
||||
},
|
||||
}, {
|
||||
.refname = "a",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value = {
|
||||
.symref = "target",
|
||||
},
|
||||
}
|
||||
};
|
||||
int err;
|
||||
|
||||
reftable_writer_set_limits(w, 1, 1);
|
||||
err = reftable_writer_add_ref(w, &refs[0]);
|
||||
EXPECT_ERR(err);
|
||||
err = reftable_writer_add_ref(w, &refs[1]);
|
||||
printf("%d\n", err);
|
||||
EXPECT(err == REFTABLE_API_ERROR);
|
||||
reftable_writer_close(w);
|
||||
reftable_writer_free(w);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static void test_corrupt_table_empty(void)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct reftable_reader rd = { NULL };
|
||||
int err;
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
err = init_reader(&rd, &source, "file.log");
|
||||
EXPECT(err == REFTABLE_FORMAT_ERROR);
|
||||
}
|
||||
|
||||
static void test_corrupt_table(void)
|
||||
{
|
||||
uint8_t zeros[1024] = { 0 };
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct reftable_block_source source = { NULL };
|
||||
struct reftable_reader rd = { NULL };
|
||||
int err;
|
||||
strbuf_add(&buf, zeros, sizeof(zeros));
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
err = init_reader(&rd, &source, "file.log");
|
||||
EXPECT(err == REFTABLE_FORMAT_ERROR);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
int readwrite_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_corrupt_table);
|
||||
RUN_TEST(test_corrupt_table_empty);
|
||||
RUN_TEST(test_log_write_read);
|
||||
RUN_TEST(test_write_key_order);
|
||||
RUN_TEST(test_table_read_write_seek_linear_sha256);
|
||||
RUN_TEST(test_log_buffer_size);
|
||||
RUN_TEST(test_table_write_small_table);
|
||||
RUN_TEST(test_buffer);
|
||||
RUN_TEST(test_table_read_api);
|
||||
RUN_TEST(test_table_read_write_sequential);
|
||||
RUN_TEST(test_table_read_write_seek_linear);
|
||||
RUN_TEST(test_table_read_write_seek_index);
|
||||
RUN_TEST(test_table_refs_for_no_index);
|
||||
RUN_TEST(test_table_refs_for_obj_index);
|
||||
RUN_TEST(test_write_empty_table);
|
||||
return 0;
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
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 RECORD_H
|
||||
#define RECORD_H
|
||||
|
||||
#include "system.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "reftable-record.h"
|
||||
|
||||
/*
|
||||
* A substring of existing string data. This structure takes no responsibility
|
||||
* for the lifetime of the data it points to.
|
||||
*/
|
||||
struct string_view {
|
||||
uint8_t *buf;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/* Advance `s.buf` by `n`, and decrease length. */
|
||||
void string_view_consume(struct string_view *s, int n);
|
||||
|
||||
/* utilities for de/encoding varints */
|
||||
|
||||
int get_var_int(uint64_t *dest, struct string_view *in);
|
||||
int put_var_int(struct string_view *dest, uint64_t val);
|
||||
|
||||
/* Methods for records. */
|
||||
struct reftable_record_vtable {
|
||||
/* encode the key of to a uint8_t strbuf. */
|
||||
void (*key)(const void *rec, struct strbuf *dest);
|
||||
|
||||
/* The record type of ('r' for ref). */
|
||||
uint8_t type;
|
||||
|
||||
void (*copy_from)(void *dest, const void *src, int hash_size);
|
||||
|
||||
/* a value of [0..7], indicating record subvariants (eg. ref vs. symref
|
||||
* vs ref deletion) */
|
||||
uint8_t (*val_type)(const void *rec);
|
||||
|
||||
/* encodes rec into dest, returning how much space was used. */
|
||||
int (*encode)(const void *rec, struct string_view dest, int hash_size);
|
||||
|
||||
/* decode data from `src` into the record. */
|
||||
int (*decode)(void *rec, struct strbuf key, uint8_t extra,
|
||||
struct string_view src, int hash_size);
|
||||
|
||||
/* deallocate and null the record. */
|
||||
void (*release)(void *rec);
|
||||
|
||||
/* is this a tombstone? */
|
||||
int (*is_deletion)(const void *rec);
|
||||
};
|
||||
|
||||
/* record is a generic wrapper for different types of records. */
|
||||
struct reftable_record {
|
||||
void *data;
|
||||
struct reftable_record_vtable *ops;
|
||||
};
|
||||
|
||||
/* returns true for recognized block types. Block start with the block type. */
|
||||
int reftable_is_block_type(uint8_t typ);
|
||||
|
||||
/* creates a malloced record of the given type. Dispose with record_destroy */
|
||||
struct reftable_record reftable_new_record(uint8_t typ);
|
||||
|
||||
/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
|
||||
* number of bytes written. */
|
||||
int reftable_encode_key(int *is_restart, struct string_view dest,
|
||||
struct strbuf prev_key, struct strbuf key,
|
||||
uint8_t extra);
|
||||
|
||||
/* Decode into `key` and `extra` from `in` */
|
||||
int reftable_decode_key(struct strbuf *key, uint8_t *extra,
|
||||
struct strbuf last_key, struct string_view in);
|
||||
|
||||
/* reftable_index_record are used internally to speed up lookups. */
|
||||
struct reftable_index_record {
|
||||
uint64_t offset; /* Offset of block */
|
||||
struct strbuf last_key; /* Last key of the block. */
|
||||
};
|
||||
|
||||
/* reftable_obj_record stores an object ID => ref mapping. */
|
||||
struct reftable_obj_record {
|
||||
uint8_t *hash_prefix; /* leading bytes of the object ID */
|
||||
int hash_prefix_len; /* number of leading bytes. Constant
|
||||
* across a single table. */
|
||||
uint64_t *offsets; /* a vector of file offsets. */
|
||||
int offset_len;
|
||||
};
|
||||
|
||||
/* see struct record_vtable */
|
||||
|
||||
void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
|
||||
uint8_t reftable_record_type(struct reftable_record *rec);
|
||||
void reftable_record_copy_from(struct reftable_record *rec,
|
||||
struct reftable_record *src, int hash_size);
|
||||
uint8_t reftable_record_val_type(struct reftable_record *rec);
|
||||
int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
|
||||
int hash_size);
|
||||
int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
|
||||
uint8_t extra, struct string_view src,
|
||||
int hash_size);
|
||||
int reftable_record_is_deletion(struct reftable_record *rec);
|
||||
|
||||
/* zeroes out the embedded record */
|
||||
void reftable_record_release(struct reftable_record *rec);
|
||||
|
||||
/* clear and deallocate embedded record, and zero `rec`. */
|
||||
void reftable_record_destroy(struct reftable_record *rec);
|
||||
|
||||
/* initialize generic records from concrete records. The generic record should
|
||||
* be zeroed out. */
|
||||
void reftable_record_from_obj(struct reftable_record *rec,
|
||||
struct reftable_obj_record *objrec);
|
||||
void reftable_record_from_index(struct reftable_record *rec,
|
||||
struct reftable_index_record *idxrec);
|
||||
void reftable_record_from_ref(struct reftable_record *rec,
|
||||
struct reftable_ref_record *refrec);
|
||||
void reftable_record_from_log(struct reftable_record *rec,
|
||||
struct reftable_log_record *logrec);
|
||||
struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
|
||||
struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
|
||||
|
||||
/* for qsort. */
|
||||
int reftable_ref_record_compare_name(const void *a, const void *b);
|
||||
|
||||
/* for qsort. */
|
||||
int reftable_log_record_compare_key(const void *a, const void *b);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
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 "record.h"
|
||||
|
||||
#include "system.h"
|
||||
#include "basics.h"
|
||||
#include "constants.h"
|
||||
#include "test_framework.h"
|
||||
#include "reftable-tests.h"
|
||||
|
||||
static void test_copy(struct reftable_record *rec)
|
||||
{
|
||||
struct reftable_record copy =
|
||||
reftable_new_record(reftable_record_type(rec));
|
||||
reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ);
|
||||
/* do it twice to catch memory leaks */
|
||||
reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ);
|
||||
switch (reftable_record_type(©)) {
|
||||
case BLOCK_TYPE_REF:
|
||||
EXPECT(reftable_ref_record_equal(reftable_record_as_ref(©),
|
||||
reftable_record_as_ref(rec),
|
||||
GIT_SHA1_RAWSZ));
|
||||
break;
|
||||
case BLOCK_TYPE_LOG:
|
||||
EXPECT(reftable_log_record_equal(reftable_record_as_log(©),
|
||||
reftable_record_as_log(rec),
|
||||
GIT_SHA1_RAWSZ));
|
||||
break;
|
||||
}
|
||||
reftable_record_destroy(©);
|
||||
}
|
||||
|
||||
static void test_varint_roundtrip(void)
|
||||
{
|
||||
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++) {
|
||||
uint8_t dest[10];
|
||||
|
||||
struct string_view out = {
|
||||
.buf = dest,
|
||||
.len = sizeof(dest),
|
||||
};
|
||||
uint64_t in = inputs[i];
|
||||
int n = put_var_int(&out, in);
|
||||
uint64_t got = 0;
|
||||
|
||||
EXPECT(n > 0);
|
||||
out.len = n;
|
||||
n = get_var_int(&got, &out);
|
||||
EXPECT(n > 0);
|
||||
|
||||
EXPECT(got == in);
|
||||
}
|
||||
}
|
||||
|
||||
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 strbuf a = STRBUF_INIT;
|
||||
struct strbuf b = STRBUF_INIT;
|
||||
strbuf_addstr(&a, cases[i].a);
|
||||
strbuf_addstr(&b, cases[i].b);
|
||||
EXPECT(common_prefix_size(&a, &b) == cases[i].want);
|
||||
|
||||
strbuf_release(&a);
|
||||
strbuf_release(&b);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_hash(uint8_t *h, int j)
|
||||
{
|
||||
int i = 0;
|
||||
for (i = 0; i < hash_size(GIT_SHA1_FORMAT_ID); i++) {
|
||||
h[i] = (j >> i) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_reftable_ref_record_roundtrip(void)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
|
||||
struct reftable_ref_record in = { NULL };
|
||||
struct reftable_ref_record out = { NULL };
|
||||
struct reftable_record rec_out = { NULL };
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
struct reftable_record rec = { NULL };
|
||||
uint8_t buffer[1024] = { 0 };
|
||||
struct string_view dest = {
|
||||
.buf = buffer,
|
||||
.len = sizeof(buffer),
|
||||
};
|
||||
|
||||
int n, m;
|
||||
|
||||
in.value_type = i;
|
||||
switch (i) {
|
||||
case REFTABLE_REF_DELETION:
|
||||
break;
|
||||
case REFTABLE_REF_VAL1:
|
||||
in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
set_hash(in.value.val1, 1);
|
||||
break;
|
||||
case REFTABLE_REF_VAL2:
|
||||
in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
set_hash(in.value.val2.value, 1);
|
||||
in.value.val2.target_value =
|
||||
reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
set_hash(in.value.val2.target_value, 2);
|
||||
break;
|
||||
case REFTABLE_REF_SYMREF:
|
||||
in.value.symref = xstrdup("target");
|
||||
break;
|
||||
}
|
||||
in.refname = xstrdup("refs/heads/master");
|
||||
|
||||
reftable_record_from_ref(&rec, &in);
|
||||
test_copy(&rec);
|
||||
|
||||
EXPECT(reftable_record_val_type(&rec) == i);
|
||||
|
||||
reftable_record_key(&rec, &key);
|
||||
n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
|
||||
EXPECT(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,
|
||||
GIT_SHA1_RAWSZ);
|
||||
EXPECT(n == m);
|
||||
|
||||
EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
|
||||
reftable_record_release(&rec_out);
|
||||
|
||||
strbuf_release(&key);
|
||||
reftable_ref_record_release(&in);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_reftable_log_record_equal(void)
|
||||
{
|
||||
struct reftable_log_record in[2] = {
|
||||
{
|
||||
.refname = xstrdup("refs/heads/master"),
|
||||
.update_index = 42,
|
||||
},
|
||||
{
|
||||
.refname = xstrdup("refs/heads/master"),
|
||||
.update_index = 22,
|
||||
}
|
||||
};
|
||||
|
||||
EXPECT(!reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));
|
||||
in[1].update_index = in[0].update_index;
|
||||
EXPECT(reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ));
|
||||
reftable_log_record_release(&in[0]);
|
||||
reftable_log_record_release(&in[1]);
|
||||
}
|
||||
|
||||
static void test_reftable_log_record_roundtrip(void)
|
||||
{
|
||||
int i;
|
||||
struct reftable_log_record in[2] = {
|
||||
{
|
||||
.refname = xstrdup("refs/heads/master"),
|
||||
.update_index = 42,
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value = {
|
||||
.update = {
|
||||
.old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
|
||||
.new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
|
||||
.name = xstrdup("han-wen"),
|
||||
.email = xstrdup("hanwen@google.com"),
|
||||
.message = xstrdup("test"),
|
||||
.time = 1577123507,
|
||||
.tz_offset = 100,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
.refname = xstrdup("refs/heads/master"),
|
||||
.update_index = 22,
|
||||
.value_type = REFTABLE_LOG_DELETION,
|
||||
}
|
||||
};
|
||||
set_test_hash(in[0].value.update.new_hash, 1);
|
||||
set_test_hash(in[0].value.update.old_hash, 2);
|
||||
for (i = 0; i < ARRAY_SIZE(in); i++) {
|
||||
struct reftable_record rec = { NULL };
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
uint8_t buffer[1024] = { 0 };
|
||||
struct string_view dest = {
|
||||
.buf = buffer,
|
||||
.len = sizeof(buffer),
|
||||
};
|
||||
/* populate out, to check for leaks. */
|
||||
struct reftable_log_record out = {
|
||||
.refname = xstrdup("old name"),
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value = {
|
||||
.update = {
|
||||
.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
|
||||
.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
|
||||
.name = xstrdup("old name"),
|
||||
.email = xstrdup("old@email"),
|
||||
.message = xstrdup("old message"),
|
||||
},
|
||||
},
|
||||
};
|
||||
struct reftable_record rec_out = { NULL };
|
||||
int n, m, valtype;
|
||||
|
||||
reftable_record_from_log(&rec, &in[i]);
|
||||
|
||||
test_copy(&rec);
|
||||
|
||||
reftable_record_key(&rec, &key);
|
||||
|
||||
n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
|
||||
EXPECT(n >= 0);
|
||||
reftable_record_from_log(&rec_out, &out);
|
||||
valtype = reftable_record_val_type(&rec);
|
||||
m = reftable_record_decode(&rec_out, key, valtype, dest,
|
||||
GIT_SHA1_RAWSZ);
|
||||
EXPECT(n == m);
|
||||
|
||||
EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
|
||||
reftable_log_record_release(&in[i]);
|
||||
strbuf_release(&key);
|
||||
reftable_record_release(&rec_out);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_u24_roundtrip(void)
|
||||
{
|
||||
uint32_t in = 0x112233;
|
||||
uint8_t dest[3];
|
||||
uint32_t out;
|
||||
put_be24(dest, in);
|
||||
out = get_be24(dest);
|
||||
EXPECT(in == out);
|
||||
}
|
||||
|
||||
static void test_key_roundtrip(void)
|
||||
{
|
||||
uint8_t buffer[1024] = { 0 };
|
||||
struct string_view dest = {
|
||||
.buf = buffer,
|
||||
.len = sizeof(buffer),
|
||||
};
|
||||
struct strbuf last_key = STRBUF_INIT;
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
struct strbuf roundtrip = STRBUF_INIT;
|
||||
int restart;
|
||||
uint8_t extra;
|
||||
int n, m;
|
||||
uint8_t rt_extra;
|
||||
|
||||
strbuf_addstr(&last_key, "refs/heads/master");
|
||||
strbuf_addstr(&key, "refs/tags/bla");
|
||||
extra = 6;
|
||||
n = reftable_encode_key(&restart, dest, last_key, key, extra);
|
||||
EXPECT(!restart);
|
||||
EXPECT(n > 0);
|
||||
|
||||
m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);
|
||||
EXPECT(n == m);
|
||||
EXPECT(0 == strbuf_cmp(&key, &roundtrip));
|
||||
EXPECT(rt_extra == extra);
|
||||
|
||||
strbuf_release(&last_key);
|
||||
strbuf_release(&key);
|
||||
strbuf_release(&roundtrip);
|
||||
}
|
||||
|
||||
static void test_reftable_obj_record_roundtrip(void)
|
||||
{
|
||||
uint8_t testHash1[GIT_SHA1_RAWSZ] = { 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];
|
||||
uint8_t buffer[1024] = { 0 };
|
||||
struct string_view dest = {
|
||||
.buf = buffer,
|
||||
.len = sizeof(buffer),
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
struct reftable_obj_record out = { NULL };
|
||||
struct reftable_record rec_out = { NULL };
|
||||
int n, m;
|
||||
uint8_t extra;
|
||||
|
||||
reftable_record_from_obj(&rec, &in);
|
||||
test_copy(&rec);
|
||||
reftable_record_key(&rec, &key);
|
||||
n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
|
||||
EXPECT(n > 0);
|
||||
extra = reftable_record_val_type(&rec);
|
||||
reftable_record_from_obj(&rec_out, &out);
|
||||
m = reftable_record_decode(&rec_out, key, extra, dest,
|
||||
GIT_SHA1_RAWSZ);
|
||||
EXPECT(n == m);
|
||||
|
||||
EXPECT(in.hash_prefix_len == out.hash_prefix_len);
|
||||
EXPECT(in.offset_len == out.offset_len);
|
||||
|
||||
EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
|
||||
in.hash_prefix_len));
|
||||
EXPECT(0 == memcmp(in.offsets, out.offsets,
|
||||
sizeof(uint64_t) * in.offset_len));
|
||||
strbuf_release(&key);
|
||||
reftable_record_release(&rec_out);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_reftable_index_record_roundtrip(void)
|
||||
{
|
||||
struct reftable_index_record in = {
|
||||
.offset = 42,
|
||||
.last_key = STRBUF_INIT,
|
||||
};
|
||||
uint8_t buffer[1024] = { 0 };
|
||||
struct string_view dest = {
|
||||
.buf = buffer,
|
||||
.len = sizeof(buffer),
|
||||
};
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
struct reftable_record rec = { NULL };
|
||||
struct reftable_index_record out = { .last_key = STRBUF_INIT };
|
||||
struct reftable_record out_rec = { NULL };
|
||||
int n, m;
|
||||
uint8_t extra;
|
||||
|
||||
strbuf_addstr(&in.last_key, "refs/heads/master");
|
||||
reftable_record_from_index(&rec, &in);
|
||||
reftable_record_key(&rec, &key);
|
||||
test_copy(&rec);
|
||||
|
||||
EXPECT(0 == strbuf_cmp(&key, &in.last_key));
|
||||
n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
|
||||
EXPECT(n > 0);
|
||||
|
||||
extra = reftable_record_val_type(&rec);
|
||||
reftable_record_from_index(&out_rec, &out);
|
||||
m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
|
||||
EXPECT(m == n);
|
||||
|
||||
EXPECT(in.offset == out.offset);
|
||||
|
||||
reftable_record_release(&out_rec);
|
||||
strbuf_release(&key);
|
||||
strbuf_release(&in.last_key);
|
||||
}
|
||||
|
||||
int record_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_reftable_log_record_equal);
|
||||
RUN_TEST(test_reftable_log_record_roundtrip);
|
||||
RUN_TEST(test_reftable_ref_record_roundtrip);
|
||||
RUN_TEST(test_varint_roundtrip);
|
||||
RUN_TEST(test_key_roundtrip);
|
||||
RUN_TEST(test_common_prefix);
|
||||
RUN_TEST(test_reftable_obj_record_roundtrip);
|
||||
RUN_TEST(test_reftable_index_record_roundtrip);
|
||||
RUN_TEST(test_u24_roundtrip);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
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-error.h"
|
||||
#include "basics.h"
|
||||
#include "refname.h"
|
||||
#include "reftable-iterator.h"
|
||||
|
||||
struct find_arg {
|
||||
char **names;
|
||||
const char *want;
|
||||
};
|
||||
|
||||
static int find_name(size_t k, void *arg)
|
||||
{
|
||||
struct find_arg *f_arg = arg;
|
||||
return strcmp(f_arg->names[k], f_arg->want) >= 0;
|
||||
}
|
||||
|
||||
static int modification_has_ref(struct modification *mod, const char *name)
|
||||
{
|
||||
struct reftable_ref_record ref = { NULL };
|
||||
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_release(&ref);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void modification_release(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;
|
||||
}
|
||||
|
||||
static 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 = {
|
||||
.names = mod->add,
|
||||
.want = prefix,
|
||||
};
|
||||
int idx = binsearch(mod->add_len, find_name, &arg);
|
||||
if (idx < mod->add_len &&
|
||||
!strncmp(prefix, mod->add[idx], strlen(prefix)))
|
||||
goto done;
|
||||
}
|
||||
err = reftable_table_seek_ref(&mod->tab, &it, prefix);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
while (1) {
|
||||
err = reftable_iterator_next_ref(&it, &ref);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
if (mod->del_len > 0) {
|
||||
struct find_arg arg = {
|
||||
.names = mod->del,
|
||||
.want = ref.refname,
|
||||
};
|
||||
int idx = binsearch(mod->del_len, find_name, &arg);
|
||||
if (idx < mod->del_len &&
|
||||
!strcmp(ref.refname, mod->del[idx])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp(ref.refname, prefix, strlen(prefix))) {
|
||||
err = 1;
|
||||
goto done;
|
||||
}
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
reftable_ref_record_release(&ref);
|
||||
reftable_iterator_destroy(&it);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int validate_refname(const char *name)
|
||||
{
|
||||
while (1) {
|
||||
char *next = strchr(name, '/');
|
||||
if (!*name) {
|
||||
return REFTABLE_REFNAME_ERROR;
|
||||
}
|
||||
if (!next) {
|
||||
return 0;
|
||||
}
|
||||
if (next - name == 0 || (next - name == 1 && *name == '.') ||
|
||||
(next - name == 2 && name[0] == '.' && name[1] == '.'))
|
||||
return REFTABLE_REFNAME_ERROR;
|
||||
name = next + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int validate_ref_record_addition(struct reftable_table tab,
|
||||
struct reftable_ref_record *recs, size_t sz)
|
||||
{
|
||||
struct modification mod = {
|
||||
.tab = tab,
|
||||
.add = reftable_calloc(sizeof(char *) * sz),
|
||||
.del = reftable_calloc(sizeof(char *) * sz),
|
||||
};
|
||||
int i = 0;
|
||||
int err = 0;
|
||||
for (; i < sz; i++) {
|
||||
if (reftable_ref_record_is_deletion(&recs[i])) {
|
||||
mod.del[mod.del_len++] = recs[i].refname;
|
||||
} else {
|
||||
mod.add[mod.add_len++] = recs[i].refname;
|
||||
}
|
||||
}
|
||||
|
||||
err = modification_validate(&mod);
|
||||
modification_release(&mod);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void strbuf_trim_component(struct strbuf *sl)
|
||||
{
|
||||
while (sl->len > 0) {
|
||||
int is_slash = (sl->buf[sl->len - 1] == '/');
|
||||
strbuf_setlen(sl, sl->len - 1);
|
||||
if (is_slash)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int modification_validate(struct modification *mod)
|
||||
{
|
||||
struct strbuf slashed = STRBUF_INIT;
|
||||
int err = 0;
|
||||
int i = 0;
|
||||
for (; i < mod->add_len; i++) {
|
||||
err = validate_refname(mod->add[i]);
|
||||
if (err)
|
||||
goto done;
|
||||
strbuf_reset(&slashed);
|
||||
strbuf_addstr(&slashed, mod->add[i]);
|
||||
strbuf_addstr(&slashed, "/");
|
||||
|
||||
err = modification_has_ref_with_prefix(mod, slashed.buf);
|
||||
if (err == 0) {
|
||||
err = REFTABLE_NAME_CONFLICT;
|
||||
goto done;
|
||||
}
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
strbuf_reset(&slashed);
|
||||
strbuf_addstr(&slashed, mod->add[i]);
|
||||
while (slashed.len) {
|
||||
strbuf_trim_component(&slashed);
|
||||
err = modification_has_ref(mod, slashed.buf);
|
||||
if (err == 0) {
|
||||
err = REFTABLE_NAME_CONFLICT;
|
||||
goto done;
|
||||
}
|
||||
if (err < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
err = 0;
|
||||
done:
|
||||
strbuf_release(&slashed);
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
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 REFNAME_H
|
||||
#define REFNAME_H
|
||||
|
||||
#include "reftable-record.h"
|
||||
#include "reftable-generic.h"
|
||||
|
||||
struct modification {
|
||||
struct reftable_table tab;
|
||||
|
||||
char **add;
|
||||
size_t add_len;
|
||||
|
||||
char **del;
|
||||
size_t del_len;
|
||||
};
|
||||
|
||||
int validate_ref_record_addition(struct reftable_table tab,
|
||||
struct reftable_ref_record *recs, size_t sz);
|
||||
|
||||
int modification_validate(struct modification *mod);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
#include "block.h"
|
||||
#include "blocksource.h"
|
||||
#include "constants.h"
|
||||
#include "reader.h"
|
||||
#include "record.h"
|
||||
#include "refname.h"
|
||||
#include "reftable-error.h"
|
||||
#include "reftable-writer.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 strbuf buf = STRBUF_INIT;
|
||||
struct reftable_writer *w =
|
||||
reftable_new_writer(&strbuf_add_void, &buf, &opts);
|
||||
struct reftable_ref_record rec = {
|
||||
.refname = "a/b",
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "destination", /* make sure it's not a symref.
|
||||
*/
|
||||
.update_index = 1,
|
||||
};
|
||||
int err;
|
||||
int i;
|
||||
struct reftable_block_source source = { NULL };
|
||||
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);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_writer_close(w);
|
||||
EXPECT_ERR(err);
|
||||
reftable_writer_free(w);
|
||||
|
||||
block_source_from_strbuf(&source, &buf);
|
||||
err = reftable_new_reader(&rd, &source, "filename");
|
||||
EXPECT_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) {
|
||||
mod.add = &cases[i].add;
|
||||
mod.add_len = 1;
|
||||
}
|
||||
if (cases[i].del) {
|
||||
mod.del = &cases[i].del;
|
||||
mod.del_len = 1;
|
||||
}
|
||||
|
||||
err = modification_validate(&mod);
|
||||
EXPECT(err == cases[i].error_code);
|
||||
}
|
||||
|
||||
reftable_reader_free(rd);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
int refname_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_conflict);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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_BLOCKSOURCE_H
|
||||
#define REFTABLE_BLOCKSOURCE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* block_source is a generic wrapper for a seekable readable file.
|
||||
*/
|
||||
struct reftable_block_source {
|
||||
struct reftable_block_source_vtable *ops;
|
||||
void *arg;
|
||||
};
|
||||
|
||||
/* a contiguous segment of bytes. It keeps track of its generating block_source
|
||||
* so it can return itself into the pool. */
|
||||
struct reftable_block {
|
||||
uint8_t *data;
|
||||
int len;
|
||||
struct reftable_block_source source;
|
||||
};
|
||||
|
||||
/* block_source_vtable are the operations that make up block_source */
|
||||
struct reftable_block_source_vtable {
|
||||
/* returns the size of a block source */
|
||||
uint64_t (*size)(void *source);
|
||||
|
||||
/* reads a segment from the block source. It is an error to read
|
||||
beyond the end of the block */
|
||||
int (*read_block)(void *source, struct reftable_block *dest,
|
||||
uint64_t off, uint32_t size);
|
||||
/* mark the block as read; may return the data back to malloc */
|
||||
void (*return_block)(void *source, struct reftable_block *blockp);
|
||||
|
||||
/* release all resources associated with the block source */
|
||||
void (*close)(void *source);
|
||||
};
|
||||
|
||||
/* opens a file on the file system as a block_source */
|
||||
int reftable_block_source_from_file(struct reftable_block_source *block_src,
|
||||
const char *name);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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_ERROR_H
|
||||
#define REFTABLE_ERROR_H
|
||||
|
||||
/*
|
||||
* Errors in reftable calls are signaled with negative integer return values. 0
|
||||
* means success.
|
||||
*/
|
||||
enum reftable_error {
|
||||
/* Unexpected file system behavior */
|
||||
REFTABLE_IO_ERROR = -2,
|
||||
|
||||
/* Format inconsistency on reading data */
|
||||
REFTABLE_FORMAT_ERROR = -3,
|
||||
|
||||
/* File does not exist. Returned from block_source_from_file(), because
|
||||
* it needs special handling in stack.
|
||||
*/
|
||||
REFTABLE_NOT_EXIST_ERROR = -4,
|
||||
|
||||
/* Trying to write out-of-date data. */
|
||||
REFTABLE_LOCK_ERROR = -5,
|
||||
|
||||
/* Misuse of the API:
|
||||
* - on writing a record with NULL refname.
|
||||
* - on writing a reftable_ref_record outside the table limits
|
||||
* - on writing a ref or log record before the stack's
|
||||
* next_update_inde*x
|
||||
* - 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.
|
||||
*
|
||||
* When a call misuses the API, the internal state of the library is
|
||||
* kept unchanged.
|
||||
*/
|
||||
REFTABLE_API_ERROR = -6,
|
||||
|
||||
/* Decompression error */
|
||||
REFTABLE_ZLIB_ERROR = -7,
|
||||
|
||||
/* Wrote a table without blocks. */
|
||||
REFTABLE_EMPTY_TABLE_ERROR = -8,
|
||||
|
||||
/* Dir/file conflict. */
|
||||
REFTABLE_NAME_CONFLICT = -9,
|
||||
|
||||
/* Invalid ref name. */
|
||||
REFTABLE_REFNAME_ERROR = -10,
|
||||
};
|
||||
|
||||
/* convert the numeric error code to a string. The string should not be
|
||||
* deallocated. */
|
||||
const char *reftable_error_str(int err);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
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_GENERIC_H
|
||||
#define REFTABLE_GENERIC_H
|
||||
|
||||
#include "reftable-iterator.h"
|
||||
|
||||
struct reftable_table_vtable;
|
||||
|
||||
/*
|
||||
* Provides a unified API for reading tables, either merged tables, or single
|
||||
* readers. */
|
||||
struct reftable_table {
|
||||
struct reftable_table_vtable *ops;
|
||||
void *table_arg;
|
||||
};
|
||||
|
||||
int reftable_table_seek_log(struct reftable_table *tab,
|
||||
struct reftable_iterator *it, const char *name);
|
||||
|
||||
int reftable_table_seek_ref(struct reftable_table *tab,
|
||||
struct reftable_iterator *it, const char *name);
|
||||
|
||||
/* returns the hash ID from a generic reftable_table */
|
||||
uint32_t reftable_table_hash_id(struct reftable_table *tab);
|
||||
|
||||
/* returns the max update_index covered by this table. */
|
||||
uint64_t reftable_table_max_update_index(struct reftable_table *tab);
|
||||
|
||||
/* returns the min update_index covered by this table. */
|
||||
uint64_t reftable_table_min_update_index(struct reftable_table *tab);
|
||||
|
||||
/* convenience function to read a single ref. Returns < 0 for error, 0
|
||||
for success, and 1 if ref not found. */
|
||||
int reftable_table_read_ref(struct reftable_table *tab, const char *name,
|
||||
struct reftable_ref_record *ref);
|
||||
|
||||
/* dump table contents onto stdout for debugging */
|
||||
int reftable_table_print(struct reftable_table *tab);
|
||||
|
||||
#endif
|
|
@ -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 REFTABLE_ITERATOR_H
|
||||
#define REFTABLE_ITERATOR_H
|
||||
|
||||
#include "reftable-record.h"
|
||||
|
||||
struct reftable_iterator_vtable;
|
||||
|
||||
/* iterator is the generic interface for walking over data stored in a
|
||||
* reftable.
|
||||
*/
|
||||
struct reftable_iterator {
|
||||
struct reftable_iterator_vtable *ops;
|
||||
void *iter_arg;
|
||||
};
|
||||
|
||||
/* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0:
|
||||
* end of iteration.
|
||||
*/
|
||||
int reftable_iterator_next_ref(struct reftable_iterator *it,
|
||||
struct reftable_ref_record *ref);
|
||||
|
||||
/* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0:
|
||||
* end of iteration.
|
||||
*/
|
||||
int reftable_iterator_next_log(struct reftable_iterator *it,
|
||||
struct reftable_log_record *log);
|
||||
|
||||
/* releases resources associated with an iterator. */
|
||||
void reftable_iterator_destroy(struct reftable_iterator *it);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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_H
|
||||
#define REFTABLE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* Overrides the functions to use for memory management. */
|
||||
void reftable_set_alloc(void *(*malloc)(size_t),
|
||||
void *(*realloc)(void *, size_t), void (*free)(void *));
|
||||
|
||||
#endif
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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_MERGED_H
|
||||
#define REFTABLE_MERGED_H
|
||||
|
||||
#include "reftable-iterator.h"
|
||||
|
||||
/*
|
||||
* Merged tables
|
||||
*
|
||||
* A ref database kept in a sequence of table files. The merged_table presents a
|
||||
* unified view to reading (seeking, iterating) a sequence of immutable tables.
|
||||
*
|
||||
* The merged tables are on purpose kept disconnected from their actual storage
|
||||
* (eg. files on disk), because it is useful to merge tables aren't files. For
|
||||
* example, the per-workspace and global ref namespace can be implemented as a
|
||||
* merged table of two stacks of file-backed reftables.
|
||||
*/
|
||||
|
||||
/* A merged table is implements seeking/iterating over a stack of tables. */
|
||||
struct reftable_merged_table;
|
||||
|
||||
/* A generic reftable; see below. */
|
||||
struct reftable_table;
|
||||
|
||||
/* reftable_new_merged_table creates a new merged table. It takes ownership of
|
||||
the stack array.
|
||||
*/
|
||||
int reftable_new_merged_table(struct reftable_merged_table **dest,
|
||||
struct reftable_table *stack, int n,
|
||||
uint32_t hash_id);
|
||||
|
||||
/* returns an iterator positioned just before 'name' */
|
||||
int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
const char *name);
|
||||
|
||||
/* returns an iterator for log entry, at given update_index */
|
||||
int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
const char *name, uint64_t update_index);
|
||||
|
||||
/* like reftable_merged_table_seek_log_at but look for the newest entry. */
|
||||
int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
|
||||
struct reftable_iterator *it,
|
||||
const char *name);
|
||||
|
||||
/* returns the max update_index covered by this merged table. */
|
||||
uint64_t
|
||||
reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
|
||||
|
||||
/* returns the min update_index covered by this merged table. */
|
||||
uint64_t
|
||||
reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
|
||||
|
||||
/* releases memory for the merged_table */
|
||||
void reftable_merged_table_free(struct reftable_merged_table *m);
|
||||
|
||||
/* return the hash ID of the merged table. */
|
||||
uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);
|
||||
|
||||
/* create a generic table from reftable_merged_table */
|
||||
void reftable_table_from_merged_table(struct reftable_table *tab,
|
||||
struct reftable_merged_table *table);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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_READER_H
|
||||
#define REFTABLE_READER_H
|
||||
|
||||
#include "reftable-iterator.h"
|
||||
#include "reftable-blocksource.h"
|
||||
|
||||
/*
|
||||
* Reading single tables
|
||||
*
|
||||
* The follow routines are for reading single files. For an
|
||||
* application-level interface, skip ahead to struct
|
||||
* reftable_merged_table and struct reftable_stack.
|
||||
*/
|
||||
|
||||
/* The reader struct is a handle to an open reftable file. */
|
||||
struct reftable_reader;
|
||||
|
||||
/* Generic table. */
|
||||
struct reftable_table;
|
||||
|
||||
/* reftable_new_reader opens a reftable for reading. If successful,
|
||||
* returns 0 code and sets pp. The name is used for creating a
|
||||
* stack. Typically, it is the basename of the file. The block source
|
||||
* `src` is owned by the reader, and is closed on calling
|
||||
* reftable_reader_destroy(). On error, the block source `src` is
|
||||
* closed as well.
|
||||
*/
|
||||
int reftable_new_reader(struct reftable_reader **pp,
|
||||
struct reftable_block_source *src, const char *name);
|
||||
|
||||
/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted
|
||||
in the table. To seek to the start of the table, use name = "".
|
||||
|
||||
example:
|
||||
|
||||
struct reftable_reader *r = NULL;
|
||||
int err = reftable_new_reader(&r, &src, "filename");
|
||||
if (err < 0) { ... }
|
||||
struct reftable_iterator it = {0};
|
||||
err = reftable_reader_seek_ref(r, &it, "refs/heads/master");
|
||||
if (err < 0) { ... }
|
||||
struct reftable_ref_record ref = {0};
|
||||
while (1) {
|
||||
err = reftable_iterator_next_ref(&it, &ref);
|
||||
if (err > 0) {
|
||||
break;
|
||||
}
|
||||
if (err < 0) {
|
||||
..error handling..
|
||||
}
|
||||
..found..
|
||||
}
|
||||
reftable_iterator_destroy(&it);
|
||||
reftable_ref_record_release(&ref);
|
||||
*/
|
||||
int reftable_reader_seek_ref(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, const char *name);
|
||||
|
||||
/* returns the hash ID used in this table. */
|
||||
uint32_t reftable_reader_hash_id(struct reftable_reader *r);
|
||||
|
||||
/* seek to logs for the given name, older than update_index. To seek to the
|
||||
start of the table, use name = "".
|
||||
*/
|
||||
int reftable_reader_seek_log_at(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, const char *name,
|
||||
uint64_t update_index);
|
||||
|
||||
/* seek to newest log entry for given name. */
|
||||
int reftable_reader_seek_log(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, const char *name);
|
||||
|
||||
/* closes and deallocates a reader. */
|
||||
void reftable_reader_free(struct reftable_reader *);
|
||||
|
||||
/* return an iterator for the refs pointing to `oid`. */
|
||||
int reftable_reader_refs_for(struct reftable_reader *r,
|
||||
struct reftable_iterator *it, uint8_t *oid);
|
||||
|
||||
/* return the max_update_index for a table */
|
||||
uint64_t reftable_reader_max_update_index(struct reftable_reader *r);
|
||||
|
||||
/* return the min_update_index for a table */
|
||||
uint64_t reftable_reader_min_update_index(struct reftable_reader *r);
|
||||
|
||||
/* creates a generic table from a file reader. */
|
||||
void reftable_table_from_reader(struct reftable_table *tab,
|
||||
struct reftable_reader *reader);
|
||||
|
||||
/* print table onto stdout for debugging. */
|
||||
int reftable_reader_print_file(const char *tablename);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
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_RECORD_H
|
||||
#define REFTABLE_RECORD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Basic data types
|
||||
*
|
||||
* Reftables store the state of each ref in struct reftable_ref_record, and they
|
||||
* store a sequence of reflog updates in struct reftable_log_record.
|
||||
*/
|
||||
|
||||
/* reftable_ref_record holds a ref database entry target_value */
|
||||
struct reftable_ref_record {
|
||||
char *refname; /* Name of the ref, malloced. */
|
||||
uint64_t update_index; /* Logical timestamp at which this value is
|
||||
* written */
|
||||
|
||||
enum {
|
||||
/* tombstone to hide deletions from earlier tables */
|
||||
REFTABLE_REF_DELETION = 0x0,
|
||||
|
||||
/* a simple ref */
|
||||
REFTABLE_REF_VAL1 = 0x1,
|
||||
/* a tag, plus its peeled hash */
|
||||
REFTABLE_REF_VAL2 = 0x2,
|
||||
|
||||
/* a symbolic reference */
|
||||
REFTABLE_REF_SYMREF = 0x3,
|
||||
#define REFTABLE_NR_REF_VALUETYPES 4
|
||||
} value_type;
|
||||
union {
|
||||
uint8_t *val1; /* malloced hash. */
|
||||
struct {
|
||||
uint8_t *value; /* first value, malloced hash */
|
||||
uint8_t *target_value; /* second value, malloced hash */
|
||||
} val2;
|
||||
char *symref; /* referent, malloced 0-terminated string */
|
||||
} value;
|
||||
};
|
||||
|
||||
/* Returns the first hash, or NULL if `rec` is not of type
|
||||
* REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */
|
||||
uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);
|
||||
|
||||
/* Returns the second hash, or NULL if `rec` is not of type
|
||||
* REFTABLE_REF_VAL2. */
|
||||
uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);
|
||||
|
||||
/* returns whether 'ref' represents a deletion */
|
||||
int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
|
||||
|
||||
/* prints a reftable_ref_record onto stdout. Useful for debugging. */
|
||||
void reftable_ref_record_print(struct reftable_ref_record *ref,
|
||||
uint32_t hash_id);
|
||||
|
||||
/* frees and nulls all pointer values inside `ref`. */
|
||||
void reftable_ref_record_release(struct reftable_ref_record *ref);
|
||||
|
||||
/* returns whether two reftable_ref_records are the same. Useful for testing. */
|
||||
int reftable_ref_record_equal(struct reftable_ref_record *a,
|
||||
struct reftable_ref_record *b, int hash_size);
|
||||
|
||||
/* reftable_log_record holds a reflog entry */
|
||||
struct reftable_log_record {
|
||||
char *refname;
|
||||
uint64_t update_index; /* logical timestamp of a transactional update.
|
||||
*/
|
||||
|
||||
enum {
|
||||
/* tombstone to hide deletions from earlier tables */
|
||||
REFTABLE_LOG_DELETION = 0x0,
|
||||
|
||||
/* a simple update */
|
||||
REFTABLE_LOG_UPDATE = 0x1,
|
||||
#define REFTABLE_NR_LOG_VALUETYPES 2
|
||||
} value_type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t *new_hash;
|
||||
uint8_t *old_hash;
|
||||
char *name;
|
||||
char *email;
|
||||
uint64_t time;
|
||||
int16_t tz_offset;
|
||||
char *message;
|
||||
} update;
|
||||
} value;
|
||||
};
|
||||
|
||||
/* returns whether 'ref' represents the deletion of a log record. */
|
||||
int reftable_log_record_is_deletion(const struct reftable_log_record *log);
|
||||
|
||||
/* frees and nulls all pointer values. */
|
||||
void reftable_log_record_release(struct reftable_log_record *log);
|
||||
|
||||
/* returns whether two records are equal. Useful for testing. */
|
||||
int reftable_log_record_equal(struct reftable_log_record *a,
|
||||
struct reftable_log_record *b, int hash_size);
|
||||
|
||||
/* dumps a reftable_log_record on stdout, for debugging/testing. */
|
||||
void reftable_log_record_print(struct reftable_log_record *log,
|
||||
uint32_t hash_id);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
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_STACK_H
|
||||
#define REFTABLE_STACK_H
|
||||
|
||||
#include "reftable-writer.h"
|
||||
|
||||
/*
|
||||
* The stack presents an interface to a mutable sequence of reftables.
|
||||
|
||||
* A stack can be mutated by pushing a table to the top of the stack.
|
||||
|
||||
* The reftable_stack automatically compacts files on disk to ensure good
|
||||
* amortized performance.
|
||||
*
|
||||
* For windows and other platforms that cannot have open files as rename
|
||||
* destinations, concurrent access from multiple processes needs the rand()
|
||||
* random seed to be randomized.
|
||||
*/
|
||||
struct reftable_stack;
|
||||
|
||||
/* open a new reftable stack. The tables along with the table list will be
|
||||
* stored in 'dir'. Typically, this should be .git/reftables.
|
||||
*/
|
||||
int reftable_new_stack(struct reftable_stack **dest, const char *dir,
|
||||
struct reftable_write_options config);
|
||||
|
||||
/* returns the update_index at which a next table should be written. */
|
||||
uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
|
||||
|
||||
/* holds a transaction to add tables at the top of a stack. */
|
||||
struct reftable_addition;
|
||||
|
||||
/*
|
||||
* returns a new transaction to add reftables to the given stack. As a side
|
||||
* effect, the ref database is locked.
|
||||
*/
|
||||
int reftable_stack_new_addition(struct reftable_addition **dest,
|
||||
struct reftable_stack *st);
|
||||
|
||||
/* Adds a reftable to transaction. */
|
||||
int reftable_addition_add(struct reftable_addition *add,
|
||||
int (*write_table)(struct reftable_writer *wr,
|
||||
void *arg),
|
||||
void *arg);
|
||||
|
||||
/* Commits the transaction, releasing the lock. After calling this,
|
||||
* reftable_addition_destroy should still be called.
|
||||
*/
|
||||
int reftable_addition_commit(struct reftable_addition *add);
|
||||
|
||||
/* Release all non-committed data from the transaction, and deallocate the
|
||||
* transaction. Releases the lock if held. */
|
||||
void reftable_addition_destroy(struct reftable_addition *add);
|
||||
|
||||
/* add a new table to the stack. The write_table function must call
|
||||
* reftable_writer_set_limits, add refs and return an error value. */
|
||||
int reftable_stack_add(struct reftable_stack *st,
|
||||
int (*write_table)(struct reftable_writer *wr,
|
||||
void *write_arg),
|
||||
void *write_arg);
|
||||
|
||||
/* returns the merged_table for seeking. This table is valid until the
|
||||
* next write or reload, and should not be closed or deleted.
|
||||
*/
|
||||
struct reftable_merged_table *
|
||||
reftable_stack_merged_table(struct reftable_stack *st);
|
||||
|
||||
/* frees all resources associated with the stack. */
|
||||
void reftable_stack_destroy(struct reftable_stack *st);
|
||||
|
||||
/* Reloads the stack if necessary. This is very cheap to run if the stack was up
|
||||
* to date */
|
||||
int reftable_stack_reload(struct reftable_stack *st);
|
||||
|
||||
/* Policy for expiring reflog entries. */
|
||||
struct reftable_log_expiry_config {
|
||||
/* Drop entries older than this timestamp */
|
||||
uint64_t time;
|
||||
|
||||
/* Drop older entries */
|
||||
uint64_t min_update_index;
|
||||
};
|
||||
|
||||
/* compacts all reftables into a giant table. Expire reflog entries if config is
|
||||
* non-NULL */
|
||||
int reftable_stack_compact_all(struct reftable_stack *st,
|
||||
struct reftable_log_expiry_config *config);
|
||||
|
||||
/* heuristically compact unbalanced table stack. */
|
||||
int reftable_stack_auto_compact(struct reftable_stack *st);
|
||||
|
||||
/* delete stale .ref tables. */
|
||||
int reftable_stack_clean(struct reftable_stack *st);
|
||||
|
||||
/* convenience function to read a single ref. Returns < 0 for error, 0 for
|
||||
* success, and 1 if ref not found. */
|
||||
int reftable_stack_read_ref(struct reftable_stack *st, const char *refname,
|
||||
struct reftable_ref_record *ref);
|
||||
|
||||
/* convenience function to read a single log. Returns < 0 for error, 0 for
|
||||
* success, and 1 if ref not found. */
|
||||
int reftable_stack_read_log(struct reftable_stack *st, const char *refname,
|
||||
struct reftable_log_record *log);
|
||||
|
||||
/* statistics on past compactions. */
|
||||
struct reftable_compaction_stats {
|
||||
uint64_t bytes; /* total number of bytes written */
|
||||
uint64_t entries_written; /* total number of entries written, including
|
||||
failures. */
|
||||
int attempts; /* how often we tried to compact */
|
||||
int failures; /* failures happen on concurrent updates */
|
||||
};
|
||||
|
||||
/* return statistics for compaction up till now. */
|
||||
struct reftable_compaction_stats *
|
||||
reftable_stack_compaction_stats(struct reftable_stack *st);
|
||||
|
||||
/* print the entire stack represented by the directory */
|
||||
int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
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
|
||||
|
||||
int basics_test_main(int argc, const char **argv);
|
||||
int block_test_main(int argc, const char **argv);
|
||||
int merged_test_main(int argc, const char **argv);
|
||||
int pq_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 readwrite_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
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
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_WRITER_H
|
||||
#define REFTABLE_WRITER_H
|
||||
|
||||
#include "reftable-record.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h> /* ssize_t */
|
||||
|
||||
/* Writing single reftables */
|
||||
|
||||
/* reftable_write_options sets options for writing a single reftable. */
|
||||
struct reftable_write_options {
|
||||
/* boolean: do not pad out blocks to block size. */
|
||||
unsigned unpadded : 1;
|
||||
|
||||
/* the blocksize. Should be less than 2^24. */
|
||||
uint32_t block_size;
|
||||
|
||||
/* boolean: do not generate a SHA1 => ref index. */
|
||||
unsigned skip_index_objects : 1;
|
||||
|
||||
/* how often to write complete keys in each block. */
|
||||
int restart_interval;
|
||||
|
||||
/* 4-byte identifier ("sha1", "s256") of the hash.
|
||||
* Defaults to SHA1 if unset
|
||||
*/
|
||||
uint32_t hash_id;
|
||||
|
||||
/* boolean: do not check ref names for validity or dir/file conflicts.
|
||||
*/
|
||||
unsigned skip_name_check : 1;
|
||||
|
||||
/* boolean: copy log messages exactly. If unset, check that the message
|
||||
* is a single line, and add '\n' if missing.
|
||||
*/
|
||||
unsigned exact_log_message : 1;
|
||||
};
|
||||
|
||||
/* reftable_block_stats holds statistics for a single block type */
|
||||
struct reftable_block_stats {
|
||||
/* total number of entries written */
|
||||
int entries;
|
||||
/* total number of key restarts */
|
||||
int restarts;
|
||||
/* total number of blocks */
|
||||
int blocks;
|
||||
/* total number of index blocks */
|
||||
int index_blocks;
|
||||
/* depth of the index */
|
||||
int max_index_level;
|
||||
|
||||
/* offset of the first block for this type */
|
||||
uint64_t offset;
|
||||
/* offset of the top level index block for this type, or 0 if not
|
||||
* present */
|
||||
uint64_t index_offset;
|
||||
};
|
||||
|
||||
/* stats holds overall statistics for a single reftable */
|
||||
struct reftable_stats {
|
||||
/* total number of blocks written. */
|
||||
int blocks;
|
||||
/* stats for ref data */
|
||||
struct reftable_block_stats ref_stats;
|
||||
/* stats for the SHA1 to ref map. */
|
||||
struct reftable_block_stats obj_stats;
|
||||
/* stats for index blocks */
|
||||
struct reftable_block_stats idx_stats;
|
||||
/* stats for log blocks */
|
||||
struct reftable_block_stats log_stats;
|
||||
|
||||
/* disambiguation length of shortened object IDs. */
|
||||
int object_id_len;
|
||||
};
|
||||
|
||||
/* reftable_new_writer creates a new writer */
|
||||
struct reftable_writer *
|
||||
reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
|
||||
void *writer_arg, struct reftable_write_options *opts);
|
||||
|
||||
/* Set the range of update indices for the records we will add. When writing a
|
||||
table into a stack, the min should be at least
|
||||
reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
|
||||
|
||||
For transactional updates to a stack, typically min==max, and the
|
||||
update_index can be obtained by inspeciting the stack. When converting an
|
||||
existing ref database into a single reftable, this would be a range of
|
||||
update-index timestamps.
|
||||
*/
|
||||
void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
|
||||
uint64_t max);
|
||||
|
||||
/*
|
||||
Add a reftable_ref_record. The record should have names that come after
|
||||
already added records.
|
||||
|
||||
The update_index must be within the limits set by
|
||||
reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an
|
||||
REFTABLE_API_ERROR error to write a ref record after a log record.
|
||||
*/
|
||||
int reftable_writer_add_ref(struct reftable_writer *w,
|
||||
struct reftable_ref_record *ref);
|
||||
|
||||
/*
|
||||
Convenience function to add multiple reftable_ref_records; the function sorts
|
||||
the records before adding them, reordering the records array passed in.
|
||||
*/
|
||||
int reftable_writer_add_refs(struct reftable_writer *w,
|
||||
struct reftable_ref_record *refs, int n);
|
||||
|
||||
/*
|
||||
adds reftable_log_records. Log records are keyed by (refname, decreasing
|
||||
update_index). The key for the record added must come after the already added
|
||||
log records.
|
||||
*/
|
||||
int reftable_writer_add_log(struct reftable_writer *w,
|
||||
struct reftable_log_record *log);
|
||||
|
||||
/*
|
||||
Convenience function to add multiple reftable_log_records; the function sorts
|
||||
the records before adding them, reordering records array passed in.
|
||||
*/
|
||||
int reftable_writer_add_logs(struct reftable_writer *w,
|
||||
struct reftable_log_record *logs, int n);
|
||||
|
||||
/* reftable_writer_close finalizes the reftable. The writer is retained so
|
||||
* statistics can be inspected. */
|
||||
int reftable_writer_close(struct reftable_writer *w);
|
||||
|
||||
/* writer_stats returns the statistics on the reftable being written.
|
||||
|
||||
This struct becomes invalid when the writer is freed.
|
||||
*/
|
||||
const struct reftable_stats *writer_stats(struct reftable_writer *w);
|
||||
|
||||
/* reftable_writer_free deallocates memory for the writer */
|
||||
void reftable_writer_free(struct reftable_writer *w);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
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 "basics.h"
|
||||
#include "record.h"
|
||||
#include "generic.h"
|
||||
#include "reftable-iterator.h"
|
||||
#include "reftable-generic.h"
|
||||
|
||||
int reftable_table_seek_ref(struct reftable_table *tab,
|
||||
struct reftable_iterator *it, const char *name)
|
||||
{
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = (char *)name,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_ref(&rec, &ref);
|
||||
return tab->ops->seek_record(tab->table_arg, it, &rec);
|
||||
}
|
||||
|
||||
int reftable_table_read_ref(struct reftable_table *tab, const char *name,
|
||||
struct reftable_ref_record *ref)
|
||||
{
|
||||
struct reftable_iterator it = { NULL };
|
||||
int err = reftable_table_seek_ref(tab, &it, name);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
err = reftable_iterator_next_ref(&it, ref);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
if (strcmp(ref->refname, name) ||
|
||||
reftable_ref_record_is_deletion(ref)) {
|
||||
reftable_ref_record_release(ref);
|
||||
err = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
reftable_iterator_destroy(&it);
|
||||
return err;
|
||||
}
|
||||
|
||||
uint64_t reftable_table_max_update_index(struct reftable_table *tab)
|
||||
{
|
||||
return tab->ops->max_update_index(tab->table_arg);
|
||||
}
|
||||
|
||||
uint64_t reftable_table_min_update_index(struct reftable_table *tab)
|
||||
{
|
||||
return tab->ops->min_update_index(tab->table_arg);
|
||||
}
|
||||
|
||||
uint32_t reftable_table_hash_id(struct reftable_table *tab)
|
||||
{
|
||||
return tab->ops->hash_id(tab->table_arg);
|
||||
}
|
||||
|
||||
void reftable_iterator_destroy(struct reftable_iterator *it)
|
||||
{
|
||||
if (!it->ops) {
|
||||
return;
|
||||
}
|
||||
it->ops->close(it->iter_arg);
|
||||
it->ops = NULL;
|
||||
FREE_AND_NULL(it->iter_arg);
|
||||
}
|
||||
|
||||
int reftable_iterator_next_ref(struct reftable_iterator *it,
|
||||
struct reftable_ref_record *ref)
|
||||
{
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_ref(&rec, ref);
|
||||
return iterator_next(it, &rec);
|
||||
}
|
||||
|
||||
int reftable_iterator_next_log(struct reftable_iterator *it,
|
||||
struct reftable_log_record *log)
|
||||
{
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_log(&rec, log);
|
||||
return iterator_next(it, &rec);
|
||||
}
|
||||
|
||||
int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
|
||||
{
|
||||
return it->ops->next(it->iter_arg, rec);
|
||||
}
|
||||
|
||||
static int empty_iterator_next(void *arg, struct reftable_record *rec)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void empty_iterator_close(void *arg)
|
||||
{
|
||||
}
|
||||
|
||||
static struct reftable_iterator_vtable empty_vtable = {
|
||||
.next = &empty_iterator_next,
|
||||
.close = &empty_iterator_close,
|
||||
};
|
||||
|
||||
void iterator_set_empty(struct reftable_iterator *it)
|
||||
{
|
||||
assert(!it->ops);
|
||||
it->iter_arg = NULL;
|
||||
it->ops = &empty_vtable;
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
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 STACK_H
|
||||
#define STACK_H
|
||||
|
||||
#include "system.h"
|
||||
#include "reftable-writer.h"
|
||||
#include "reftable-stack.h"
|
||||
|
||||
struct reftable_stack {
|
||||
char *list_file;
|
||||
char *reftable_dir;
|
||||
int disable_auto_compact;
|
||||
|
||||
struct reftable_write_options config;
|
||||
|
||||
struct reftable_reader **readers;
|
||||
size_t readers_len;
|
||||
struct reftable_merged_table *merged;
|
||||
struct reftable_compaction_stats stats;
|
||||
};
|
||||
|
||||
int read_lines(const char *filename, char ***lines);
|
||||
|
||||
struct segment {
|
||||
int start, end;
|
||||
int log;
|
||||
uint64_t bytes;
|
||||
};
|
||||
|
||||
int fastlog2(uint64_t sz);
|
||||
struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
|
||||
struct segment suggest_compaction_segment(uint64_t *sizes, int n);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,953 @@
|
|||
/*
|
||||
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 "reftable-reader.h"
|
||||
#include "merged.h"
|
||||
#include "basics.h"
|
||||
#include "constants.h"
|
||||
#include "record.h"
|
||||
#include "test_framework.h"
|
||||
#include "reftable-tests.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
static void clear_dir(const char *dirname)
|
||||
{
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
strbuf_addstr(&path, dirname);
|
||||
remove_dir_recursively(&path, 0);
|
||||
strbuf_release(&path);
|
||||
}
|
||||
|
||||
static int count_dir_entries(const char *dirname)
|
||||
{
|
||||
DIR *dir = opendir(dirname);
|
||||
int len = 0;
|
||||
struct dirent *d;
|
||||
if (dir == NULL)
|
||||
return 0;
|
||||
|
||||
while ((d = readdir(dir))) {
|
||||
if (!strcmp(d->d_name, "..") || !strcmp(d->d_name, "."))
|
||||
continue;
|
||||
len++;
|
||||
}
|
||||
closedir(dir);
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work linenumber into the tempdir, so we can see which tests forget to
|
||||
* cleanup.
|
||||
*/
|
||||
static char *get_tmp_template(int linenumber)
|
||||
{
|
||||
const char *tmp = getenv("TMPDIR");
|
||||
static char template[1024];
|
||||
snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
|
||||
tmp ? tmp : "/tmp", linenumber);
|
||||
return template;
|
||||
}
|
||||
|
||||
static char *get_tmp_dir(int linenumber)
|
||||
{
|
||||
char *dir = get_tmp_template(linenumber);
|
||||
EXPECT(mkdtemp(dir));
|
||||
return dir;
|
||||
}
|
||||
|
||||
static void test_read_file(void)
|
||||
{
|
||||
char *fn = get_tmp_template(__LINE__);
|
||||
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;
|
||||
|
||||
EXPECT(fd > 0);
|
||||
n = write(fd, out, strlen(out));
|
||||
EXPECT(n == strlen(out));
|
||||
err = close(fd);
|
||||
EXPECT(err >= 0);
|
||||
|
||||
err = read_lines(fn, &names);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
for (i = 0; names[i]; i++) {
|
||||
EXPECT(0 == strcmp(want[i], names[i]));
|
||||
}
|
||||
free_names(names);
|
||||
remove(fn);
|
||||
}
|
||||
|
||||
static void test_parse_names(void)
|
||||
{
|
||||
char buf[] = "line\n";
|
||||
char **names = NULL;
|
||||
parse_names(buf, strlen(buf), &names);
|
||||
|
||||
EXPECT(NULL != names[0]);
|
||||
EXPECT(0 == strcmp(names[0], "line"));
|
||||
EXPECT(NULL == names[1]);
|
||||
free_names(names);
|
||||
}
|
||||
|
||||
static void test_names_equal(void)
|
||||
{
|
||||
char *a[] = { "a", "b", "c", NULL };
|
||||
char *b[] = { "a", "b", "d", NULL };
|
||||
char *c[] = { "a", "b", NULL };
|
||||
|
||||
EXPECT(names_equal(a, a));
|
||||
EXPECT(!names_equal(a, b));
|
||||
EXPECT(!names_equal(a, c));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
struct write_log_arg {
|
||||
struct reftable_log_record *log;
|
||||
uint64_t update_index;
|
||||
};
|
||||
|
||||
static int write_test_log(struct reftable_writer *wr, void *arg)
|
||||
{
|
||||
struct write_log_arg *wla = arg;
|
||||
|
||||
reftable_writer_set_limits(wr, wla->update_index, wla->update_index);
|
||||
return reftable_writer_add_log(wr, wla->log);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_add_one(void)
|
||||
{
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = "HEAD",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
struct reftable_ref_record dest = { NULL };
|
||||
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_read_ref(st, ref.refname, &dest);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(0 == strcmp("master", dest.value.symref));
|
||||
|
||||
printf("testing print functionality:\n");
|
||||
err = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);
|
||||
EXPECT(err == REFTABLE_FORMAT_ERROR);
|
||||
|
||||
reftable_ref_record_release(&dest);
|
||||
reftable_stack_destroy(st);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_uptodate(void)
|
||||
{
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st1 = NULL;
|
||||
struct reftable_stack *st2 = NULL;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
int err;
|
||||
struct reftable_ref_record ref1 = {
|
||||
.refname = "HEAD",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
struct reftable_ref_record ref2 = {
|
||||
.refname = "branch2",
|
||||
.update_index = 2,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
|
||||
|
||||
/* simulate multi-process access to the same stack
|
||||
by creating two stacks for the same directory.
|
||||
*/
|
||||
err = reftable_new_stack(&st1, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_new_stack(&st2, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st1, &write_test_ref, &ref1);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st2, &write_test_ref, &ref2);
|
||||
EXPECT(err == REFTABLE_LOCK_ERROR);
|
||||
|
||||
err = reftable_stack_reload(st2);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st2, &write_test_ref, &ref2);
|
||||
EXPECT_ERR(err);
|
||||
reftable_stack_destroy(st1);
|
||||
reftable_stack_destroy(st2);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_transaction_api(void)
|
||||
{
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
struct reftable_addition *add = NULL;
|
||||
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = "HEAD",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
struct reftable_ref_record dest = { NULL };
|
||||
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
reftable_addition_destroy(add);
|
||||
|
||||
err = reftable_stack_new_addition(&add, st);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_addition_add(add, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_addition_commit(add);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
reftable_addition_destroy(add);
|
||||
|
||||
err = reftable_stack_read_ref(st, ref.refname, &dest);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(REFTABLE_REF_SYMREF == dest.value_type);
|
||||
EXPECT(0 == strcmp("master", dest.value.symref));
|
||||
|
||||
reftable_ref_record_release(&dest);
|
||||
reftable_stack_destroy(st);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_validate_refname(void)
|
||||
{
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
int i;
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = "a/b",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
char *additions[] = { "a", "a/b/c" };
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(additions); i++) {
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = additions[i],
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref);
|
||||
EXPECT(err == REFTABLE_NAME_CONFLICT);
|
||||
}
|
||||
|
||||
reftable_stack_destroy(st);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static int write_error(struct reftable_writer *wr, void *arg)
|
||||
{
|
||||
return *((int *)arg);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_update_index_check(void)
|
||||
{
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
struct reftable_ref_record ref1 = {
|
||||
.refname = "name1",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
struct reftable_ref_record ref2 = {
|
||||
.refname = "name2",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref1);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref2);
|
||||
EXPECT(err == REFTABLE_API_ERROR);
|
||||
reftable_stack_destroy(st);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_lock_failure(void)
|
||||
{
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err, i;
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
|
||||
err = reftable_stack_add(st, &write_error, &i);
|
||||
EXPECT(err == i);
|
||||
}
|
||||
|
||||
reftable_stack_destroy(st);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_add(void)
|
||||
{
|
||||
int i = 0;
|
||||
int err = 0;
|
||||
struct reftable_write_options cfg = {
|
||||
.exact_log_message = 1,
|
||||
};
|
||||
struct reftable_stack *st = NULL;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_ref_record refs[2] = { { NULL } };
|
||||
struct reftable_log_record logs[2] = { { NULL } };
|
||||
int N = ARRAY_SIZE(refs);
|
||||
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
st->disable_auto_compact = 1;
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "branch%02d", i);
|
||||
refs[i].refname = xstrdup(buf);
|
||||
refs[i].update_index = i + 1;
|
||||
refs[i].value_type = REFTABLE_REF_VAL1;
|
||||
refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
set_test_hash(refs[i].value.val1, i);
|
||||
|
||||
logs[i].refname = xstrdup(buf);
|
||||
logs[i].update_index = N + i + 1;
|
||||
logs[i].value_type = REFTABLE_LOG_UPDATE;
|
||||
|
||||
logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
logs[i].value.update.email = xstrdup("identity@invalid");
|
||||
set_test_hash(logs[i].value.update.new_hash, i);
|
||||
}
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
|
||||
EXPECT_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);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
err = reftable_stack_compact_all(st, NULL);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
struct reftable_ref_record dest = { NULL };
|
||||
|
||||
int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(reftable_ref_record_equal(&dest, refs + i,
|
||||
GIT_SHA1_RAWSZ));
|
||||
reftable_ref_record_release(&dest);
|
||||
}
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
struct reftable_log_record dest = { NULL };
|
||||
int err = reftable_stack_read_log(st, refs[i].refname, &dest);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(reftable_log_record_equal(&dest, logs + i,
|
||||
GIT_SHA1_RAWSZ));
|
||||
reftable_log_record_release(&dest);
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
reftable_stack_destroy(st);
|
||||
for (i = 0; i < N; i++) {
|
||||
reftable_ref_record_release(&refs[i]);
|
||||
reftable_log_record_release(&logs[i]);
|
||||
}
|
||||
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 = get_tmp_dir(__LINE__);
|
||||
|
||||
uint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };
|
||||
|
||||
struct reftable_log_record input = { .refname = "branch",
|
||||
.update_index = 1,
|
||||
.value_type = REFTABLE_LOG_UPDATE,
|
||||
.value = { .update = {
|
||||
.new_hash = h1,
|
||||
.old_hash = h2,
|
||||
} } };
|
||||
struct reftable_log_record dest = {
|
||||
.update_index = 0,
|
||||
};
|
||||
struct write_log_arg arg = {
|
||||
.log = &input,
|
||||
.update_index = 1,
|
||||
};
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
input.value.update.message = "one\ntwo";
|
||||
err = reftable_stack_add(st, &write_test_log, &arg);
|
||||
EXPECT(err == REFTABLE_API_ERROR);
|
||||
|
||||
input.value.update.message = "one";
|
||||
err = reftable_stack_add(st, &write_test_log, &arg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_read_log(st, input.refname, &dest);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
|
||||
|
||||
input.value.update.message = "two\n";
|
||||
arg.update_index = 2;
|
||||
err = reftable_stack_add(st, &write_test_log, &arg);
|
||||
EXPECT_ERR(err);
|
||||
err = reftable_stack_read_log(st, input.refname, &dest);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(0 == strcmp(dest.value.update.message, "two\n"));
|
||||
|
||||
/* cleanup */
|
||||
reftable_stack_destroy(st);
|
||||
reftable_log_record_release(&dest);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_tombstone(void)
|
||||
{
|
||||
int i = 0;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
struct reftable_ref_record refs[2] = { { NULL } };
|
||||
struct reftable_log_record logs[2] = { { NULL } };
|
||||
int N = ARRAY_SIZE(refs);
|
||||
struct reftable_ref_record dest = { NULL };
|
||||
struct reftable_log_record log_dest = { NULL };
|
||||
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
/* even entries add the refs, odd entries delete them. */
|
||||
for (i = 0; i < N; i++) {
|
||||
const char *buf = "branch";
|
||||
refs[i].refname = xstrdup(buf);
|
||||
refs[i].update_index = i + 1;
|
||||
if (i % 2 == 0) {
|
||||
refs[i].value_type = REFTABLE_REF_VAL1;
|
||||
refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
set_test_hash(refs[i].value.val1, i);
|
||||
}
|
||||
|
||||
logs[i].refname = xstrdup(buf);
|
||||
/* update_index is part of the key. */
|
||||
logs[i].update_index = 42;
|
||||
if (i % 2 == 0) {
|
||||
logs[i].value_type = REFTABLE_LOG_UPDATE;
|
||||
logs[i].value.update.new_hash =
|
||||
reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
set_test_hash(logs[i].value.update.new_hash, i);
|
||||
logs[i].value.update.email =
|
||||
xstrdup("identity@invalid");
|
||||
}
|
||||
}
|
||||
for (i = 0; i < N; i++) {
|
||||
int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
|
||||
EXPECT_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);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
err = reftable_stack_read_ref(st, "branch", &dest);
|
||||
EXPECT(err == 1);
|
||||
reftable_ref_record_release(&dest);
|
||||
|
||||
err = reftable_stack_read_log(st, "branch", &log_dest);
|
||||
EXPECT(err == 1);
|
||||
reftable_log_record_release(&log_dest);
|
||||
|
||||
err = reftable_stack_compact_all(st, NULL);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_read_ref(st, "branch", &dest);
|
||||
EXPECT(err == 1);
|
||||
|
||||
err = reftable_stack_read_log(st, "branch", &log_dest);
|
||||
EXPECT(err == 1);
|
||||
reftable_ref_record_release(&dest);
|
||||
reftable_log_record_release(&log_dest);
|
||||
|
||||
/* cleanup */
|
||||
reftable_stack_destroy(st);
|
||||
for (i = 0; i < N; i++) {
|
||||
reftable_ref_record_release(&refs[i]);
|
||||
reftable_log_record_release(&logs[i]);
|
||||
}
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_hash_id(void)
|
||||
{
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = "master",
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "target",
|
||||
.update_index = 1,
|
||||
};
|
||||
struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
|
||||
struct reftable_stack *st32 = NULL;
|
||||
struct reftable_write_options cfg_default = { 0 };
|
||||
struct reftable_stack *st_default = NULL;
|
||||
struct reftable_ref_record dest = { NULL };
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
/* can't read it with the wrong hash ID. */
|
||||
err = reftable_new_stack(&st32, dir, cfg32);
|
||||
EXPECT(err == REFTABLE_FORMAT_ERROR);
|
||||
|
||||
/* check that we can read it back with default config too. */
|
||||
err = reftable_new_stack(&st_default, dir, cfg_default);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_read_ref(st_default, "master", &dest);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
EXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
|
||||
reftable_ref_record_release(&dest);
|
||||
reftable_stack_destroy(st);
|
||||
reftable_stack_destroy(st_default);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_log2(void)
|
||||
{
|
||||
EXPECT(1 == fastlog2(3));
|
||||
EXPECT(2 == fastlog2(4));
|
||||
EXPECT(2 == fastlog2(5));
|
||||
}
|
||||
|
||||
static void test_sizes_to_segments(void)
|
||||
{
|
||||
uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };
|
||||
/* .................0 1 2 3 4 5 */
|
||||
|
||||
int seglen = 0;
|
||||
struct segment *segs =
|
||||
sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
|
||||
EXPECT(segs[2].log == 3);
|
||||
EXPECT(segs[2].start == 5);
|
||||
EXPECT(segs[2].end == 6);
|
||||
|
||||
EXPECT(segs[1].log == 2);
|
||||
EXPECT(segs[1].start == 2);
|
||||
EXPECT(segs[1].end == 5);
|
||||
reftable_free(segs);
|
||||
}
|
||||
|
||||
static void test_sizes_to_segments_empty(void)
|
||||
{
|
||||
int seglen = 0;
|
||||
struct segment *segs = sizes_to_segments(&seglen, NULL, 0);
|
||||
EXPECT(seglen == 0);
|
||||
reftable_free(segs);
|
||||
}
|
||||
|
||||
static void test_sizes_to_segments_all_equal(void)
|
||||
{
|
||||
uint64_t sizes[] = { 5, 5 };
|
||||
|
||||
int seglen = 0;
|
||||
struct segment *segs =
|
||||
sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
|
||||
EXPECT(seglen == 1);
|
||||
EXPECT(segs[0].start == 0);
|
||||
EXPECT(segs[0].end == 2);
|
||||
reftable_free(segs);
|
||||
}
|
||||
|
||||
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));
|
||||
EXPECT(min.start == 2);
|
||||
EXPECT(min.end == 7);
|
||||
}
|
||||
|
||||
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));
|
||||
EXPECT(result.start == result.end);
|
||||
}
|
||||
|
||||
static void test_reflog_expire(void)
|
||||
{
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
struct reftable_log_record logs[20] = { { NULL } };
|
||||
int N = ARRAY_SIZE(logs) - 1;
|
||||
int i = 0;
|
||||
int err;
|
||||
struct reftable_log_expiry_config expiry = {
|
||||
.time = 10,
|
||||
};
|
||||
struct reftable_log_record log = { NULL };
|
||||
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
for (i = 1; i <= N; i++) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "branch%02d", i);
|
||||
|
||||
logs[i].refname = xstrdup(buf);
|
||||
logs[i].update_index = i;
|
||||
logs[i].value_type = REFTABLE_LOG_UPDATE;
|
||||
logs[i].value.update.time = i;
|
||||
logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);
|
||||
logs[i].value.update.email = xstrdup("identity@invalid");
|
||||
set_test_hash(logs[i].value.update.new_hash, i);
|
||||
}
|
||||
|
||||
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);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
err = reftable_stack_compact_all(st, NULL);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_compact_all(st, &expiry);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_read_log(st, logs[9].refname, &log);
|
||||
EXPECT(err == 1);
|
||||
|
||||
err = reftable_stack_read_log(st, logs[11].refname, &log);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
expiry.min_update_index = 15;
|
||||
err = reftable_stack_compact_all(st, &expiry);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_read_log(st, logs[14].refname, &log);
|
||||
EXPECT(err == 1);
|
||||
|
||||
err = reftable_stack_read_log(st, logs[16].refname, &log);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
/* cleanup */
|
||||
reftable_stack_destroy(st);
|
||||
for (i = 0; i <= N; i++) {
|
||||
reftable_log_record_release(&logs[i]);
|
||||
}
|
||||
clear_dir(dir);
|
||||
reftable_log_record_release(&log);
|
||||
}
|
||||
|
||||
static int write_nothing(struct reftable_writer *wr, void *arg)
|
||||
{
|
||||
reftable_writer_set_limits(wr, 1, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_empty_add(void)
|
||||
{
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
int err;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
struct reftable_stack *st2 = NULL;
|
||||
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_add(st, &write_nothing, NULL);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_new_stack(&st2, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
clear_dir(dir);
|
||||
reftable_stack_destroy(st);
|
||||
reftable_stack_destroy(st2);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_auto_compaction(void)
|
||||
{
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st = NULL;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
int err, i;
|
||||
int N = 100;
|
||||
|
||||
err = reftable_new_stack(&st, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
st->disable_auto_compact = 1; /* call manually below for coverage. */
|
||||
for (i = 0; i < N; i++) {
|
||||
char name[100];
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = name,
|
||||
.update_index = reftable_stack_next_update_index(st),
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
snprintf(name, sizeof(name), "branch%04d", i);
|
||||
|
||||
err = reftable_stack_add(st, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_auto_compact(st);
|
||||
EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
|
||||
}
|
||||
|
||||
EXPECT(reftable_stack_compaction_stats(st)->entries_written <
|
||||
(uint64_t)(N * fastlog2(N)));
|
||||
|
||||
reftable_stack_destroy(st);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_compaction_concurrent(void)
|
||||
{
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st1 = NULL, *st2 = NULL;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
int err, i;
|
||||
int N = 3;
|
||||
|
||||
err = reftable_new_stack(&st1, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
char name[100];
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = name,
|
||||
.update_index = reftable_stack_next_update_index(st1),
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
snprintf(name, sizeof(name), "branch%04d", i);
|
||||
|
||||
err = reftable_stack_add(st1, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
err = reftable_new_stack(&st2, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_compact_all(st1, NULL);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
reftable_stack_destroy(st1);
|
||||
reftable_stack_destroy(st2);
|
||||
|
||||
EXPECT(count_dir_entries(dir) == 2);
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
static void unclean_stack_close(struct reftable_stack *st)
|
||||
{
|
||||
/* break abstraction boundary to simulate unclean shutdown. */
|
||||
int i = 0;
|
||||
for (; i < st->readers_len; i++) {
|
||||
reftable_reader_free(st->readers[i]);
|
||||
}
|
||||
st->readers_len = 0;
|
||||
FREE_AND_NULL(st->readers);
|
||||
}
|
||||
|
||||
static void test_reftable_stack_compaction_concurrent_clean(void)
|
||||
{
|
||||
struct reftable_write_options cfg = { 0 };
|
||||
struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
|
||||
char *dir = get_tmp_dir(__LINE__);
|
||||
|
||||
int err, i;
|
||||
int N = 3;
|
||||
|
||||
err = reftable_new_stack(&st1, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
for (i = 0; i < N; i++) {
|
||||
char name[100];
|
||||
struct reftable_ref_record ref = {
|
||||
.refname = name,
|
||||
.update_index = reftable_stack_next_update_index(st1),
|
||||
.value_type = REFTABLE_REF_SYMREF,
|
||||
.value.symref = "master",
|
||||
};
|
||||
snprintf(name, sizeof(name), "branch%04d", i);
|
||||
|
||||
err = reftable_stack_add(st1, &write_test_ref, &ref);
|
||||
EXPECT_ERR(err);
|
||||
}
|
||||
|
||||
err = reftable_new_stack(&st2, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_compact_all(st1, NULL);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
unclean_stack_close(st1);
|
||||
unclean_stack_close(st2);
|
||||
|
||||
err = reftable_new_stack(&st3, dir, cfg);
|
||||
EXPECT_ERR(err);
|
||||
|
||||
err = reftable_stack_clean(st3);
|
||||
EXPECT_ERR(err);
|
||||
EXPECT(count_dir_entries(dir) == 2);
|
||||
|
||||
reftable_stack_destroy(st1);
|
||||
reftable_stack_destroy(st2);
|
||||
reftable_stack_destroy(st3);
|
||||
|
||||
clear_dir(dir);
|
||||
}
|
||||
|
||||
int stack_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_empty_add);
|
||||
RUN_TEST(test_log2);
|
||||
RUN_TEST(test_names_equal);
|
||||
RUN_TEST(test_parse_names);
|
||||
RUN_TEST(test_read_file);
|
||||
RUN_TEST(test_reflog_expire);
|
||||
RUN_TEST(test_reftable_stack_add);
|
||||
RUN_TEST(test_reftable_stack_add_one);
|
||||
RUN_TEST(test_reftable_stack_auto_compaction);
|
||||
RUN_TEST(test_reftable_stack_compaction_concurrent);
|
||||
RUN_TEST(test_reftable_stack_compaction_concurrent_clean);
|
||||
RUN_TEST(test_reftable_stack_hash_id);
|
||||
RUN_TEST(test_reftable_stack_lock_failure);
|
||||
RUN_TEST(test_reftable_stack_log_normalize);
|
||||
RUN_TEST(test_reftable_stack_tombstone);
|
||||
RUN_TEST(test_reftable_stack_transaction_api);
|
||||
RUN_TEST(test_reftable_stack_update_index_check);
|
||||
RUN_TEST(test_reftable_stack_uptodate);
|
||||
RUN_TEST(test_reftable_stack_validate_refname);
|
||||
RUN_TEST(test_sizes_to_segments);
|
||||
RUN_TEST(test_sizes_to_segments_all_equal);
|
||||
RUN_TEST(test_sizes_to_segments_empty);
|
||||
RUN_TEST(test_suggest_compaction_segment);
|
||||
RUN_TEST(test_suggest_compaction_segment_nothing);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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 SYSTEM_H
|
||||
#define SYSTEM_H
|
||||
|
||||
/* This header glues the reftable library to the rest of Git */
|
||||
|
||||
#include "git-compat-util.h"
|
||||
#include "strbuf.h"
|
||||
#include "hash.h" /* hash ID, sizes.*/
|
||||
#include "dir.h" /* remove_dir_recursively, for tests.*/
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#ifdef NO_UNCOMPRESS2
|
||||
/*
|
||||
* This is uncompress2, which is only available in zlib >= 1.2.9
|
||||
* (released as of early 2017)
|
||||
*/
|
||||
int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
|
||||
uLong *sourceLen);
|
||||
#endif
|
||||
|
||||
int hash_size(uint32_t id);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
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 "test_framework.h"
|
||||
|
||||
#include "basics.h"
|
||||
|
||||
void set_test_hash(uint8_t *p, int i)
|
||||
{
|
||||
memset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID));
|
||||
}
|
||||
|
||||
ssize_t strbuf_add_void(void *b, const void *data, size_t sz)
|
||||
{
|
||||
strbuf_add(b, data, sz);
|
||||
return sz;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
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 TEST_FRAMEWORK_H
|
||||
#define TEST_FRAMEWORK_H
|
||||
|
||||
#include "system.h"
|
||||
#include "reftable-error.h"
|
||||
|
||||
#define EXPECT_ERR(c) \
|
||||
if (c != 0) { \
|
||||
fflush(stderr); \
|
||||
fflush(stdout); \
|
||||
fprintf(stderr, "%s: %d: error == %d (%s), want 0\n", \
|
||||
__FILE__, __LINE__, c, reftable_error_str(c)); \
|
||||
abort(); \
|
||||
}
|
||||
|
||||
#define EXPECT_STREQ(a, b) \
|
||||
if (strcmp(a, b)) { \
|
||||
fflush(stderr); \
|
||||
fflush(stdout); \
|
||||
fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \
|
||||
__LINE__, #a, a, #b, b); \
|
||||
abort(); \
|
||||
}
|
||||
|
||||
#define EXPECT(c) \
|
||||
if (!(c)) { \
|
||||
fflush(stderr); \
|
||||
fflush(stdout); \
|
||||
fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \
|
||||
__LINE__, #c); \
|
||||
abort(); \
|
||||
}
|
||||
|
||||
#define RUN_TEST(f) \
|
||||
fprintf(stderr, "running %s\n", #f); \
|
||||
fflush(stderr); \
|
||||
f();
|
||||
|
||||
void set_test_hash(uint8_t *p, int i);
|
||||
|
||||
/* Like strbuf_add, but suitable for passing to reftable_new_writer
|
||||
*/
|
||||
ssize_t strbuf_add_void(void *b, const void *data, size_t sz);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 "tree.h"
|
||||
|
||||
#include "basics.h"
|
||||
#include "system.h"
|
||||
|
||||
struct tree_node *tree_search(void *key, struct tree_node **rootp,
|
||||
int (*compare)(const void *, const void *),
|
||||
int insert)
|
||||
{
|
||||
int res;
|
||||
if (*rootp == NULL) {
|
||||
if (!insert) {
|
||||
return NULL;
|
||||
} else {
|
||||
struct tree_node *n =
|
||||
reftable_calloc(sizeof(struct tree_node));
|
||||
n->key = key;
|
||||
*rootp = n;
|
||||
return *rootp;
|
||||
}
|
||||
}
|
||||
|
||||
res = compare(key, (*rootp)->key);
|
||||
if (res < 0)
|
||||
return tree_search(key, &(*rootp)->left, compare, insert);
|
||||
else if (res > 0)
|
||||
return tree_search(key, &(*rootp)->right, compare, insert);
|
||||
return *rootp;
|
||||
}
|
||||
|
||||
void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
|
||||
void *arg)
|
||||
{
|
||||
if (t->left) {
|
||||
infix_walk(t->left, action, arg);
|
||||
}
|
||||
action(arg, t->key);
|
||||
if (t->right) {
|
||||
infix_walk(t->right, action, arg);
|
||||
}
|
||||
}
|
||||
|
||||
void tree_free(struct tree_node *t)
|
||||
{
|
||||
if (t == NULL) {
|
||||
return;
|
||||
}
|
||||
if (t->left) {
|
||||
tree_free(t->left);
|
||||
}
|
||||
if (t->right) {
|
||||
tree_free(t->right);
|
||||
}
|
||||
reftable_free(t);
|
||||
}
|
|
@ -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 TREE_H
|
||||
#define TREE_H
|
||||
|
||||
/* tree_node is a generic binary search tree. */
|
||||
struct tree_node {
|
||||
void *key;
|
||||
struct tree_node *left, *right;
|
||||
};
|
||||
|
||||
/* looks for `key` in `rootp` using `compare` as comparison function. If insert
|
||||
* is set, insert the key if it's not found. Else, return NULL.
|
||||
*/
|
||||
struct tree_node *tree_search(void *key, struct tree_node **rootp,
|
||||
int (*compare)(const void *, const void *),
|
||||
int insert);
|
||||
|
||||
/* performs an infix walk of the tree. */
|
||||
void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
|
||||
void *arg);
|
||||
|
||||
/*
|
||||
* deallocates the tree nodes recursively. Keys should be deallocated separately
|
||||
* by walking over the tree. */
|
||||
void tree_free(struct tree_node *t);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
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 "tree.h"
|
||||
|
||||
#include "basics.h"
|
||||
#include "record.h"
|
||||
#include "test_framework.h"
|
||||
#include "reftable-tests.h"
|
||||
|
||||
static int test_compare(const void *a, const void *b)
|
||||
{
|
||||
return (char *)a - (char *)b;
|
||||
}
|
||||
|
||||
struct curry {
|
||||
void *last;
|
||||
};
|
||||
|
||||
static void check_increasing(void *arg, void *key)
|
||||
{
|
||||
struct curry *c = arg;
|
||||
if (c->last) {
|
||||
EXPECT(test_compare(c->last, key) < 0);
|
||||
}
|
||||
c->last = key;
|
||||
}
|
||||
|
||||
static void test_tree(void)
|
||||
{
|
||||
struct tree_node *root = NULL;
|
||||
|
||||
void *values[11] = { NULL };
|
||||
struct tree_node *nodes[11] = { NULL };
|
||||
int i = 1;
|
||||
struct curry c = { NULL };
|
||||
do {
|
||||
nodes[i] = tree_search(values + i, &root, &test_compare, 1);
|
||||
i = (i * 7) % 11;
|
||||
} while (i != 1);
|
||||
|
||||
for (i = 1; i < ARRAY_SIZE(nodes); i++) {
|
||||
EXPECT(values + i == nodes[i]->key);
|
||||
EXPECT(nodes[i] ==
|
||||
tree_search(values + i, &root, &test_compare, 0));
|
||||
}
|
||||
|
||||
infix_walk(root, check_increasing, &c);
|
||||
tree_free(root);
|
||||
}
|
||||
|
||||
int tree_test_main(int argc, const char *argv[])
|
||||
{
|
||||
RUN_TEST(test_tree);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
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 "writer.h"
|
||||
|
||||
#include "system.h"
|
||||
|
||||
#include "block.h"
|
||||
#include "constants.h"
|
||||
#include "record.h"
|
||||
#include "tree.h"
|
||||
#include "reftable-error.h"
|
||||
|
||||
/* finishes a block, and writes it to storage */
|
||||
static int writer_flush_block(struct reftable_writer *w);
|
||||
|
||||
/* deallocates memory related to the index */
|
||||
static void writer_clear_index(struct reftable_writer *w);
|
||||
|
||||
/* finishes writing a 'r' (refs) or 'g' (reflogs) section */
|
||||
static int writer_finish_public_section(struct reftable_writer *w);
|
||||
|
||||
static struct reftable_block_stats *
|
||||
writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ)
|
||||
{
|
||||
switch (typ) {
|
||||
case 'r':
|
||||
return &w->stats.ref_stats;
|
||||
case 'o':
|
||||
return &w->stats.obj_stats;
|
||||
case 'i':
|
||||
return &w->stats.idx_stats;
|
||||
case 'g':
|
||||
return &w->stats.log_stats;
|
||||
}
|
||||
abort();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* write data, queuing the padding for the next write. Returns negative for
|
||||
* error. */
|
||||
static int padded_write(struct reftable_writer *w, uint8_t *data, size_t len,
|
||||
int padding)
|
||||
{
|
||||
int n = 0;
|
||||
if (w->pending_padding > 0) {
|
||||
uint8_t *zeroed = reftable_calloc(w->pending_padding);
|
||||
int n = w->write(w->write_arg, zeroed, w->pending_padding);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
w->pending_padding = 0;
|
||||
reftable_free(zeroed);
|
||||
}
|
||||
|
||||
w->pending_padding = padding;
|
||||
n = w->write(w->write_arg, data, len);
|
||||
if (n < 0)
|
||||
return n;
|
||||
n += padding;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void options_set_defaults(struct reftable_write_options *opts)
|
||||
{
|
||||
if (opts->restart_interval == 0) {
|
||||
opts->restart_interval = 16;
|
||||
}
|
||||
|
||||
if (opts->hash_id == 0) {
|
||||
opts->hash_id = GIT_SHA1_FORMAT_ID;
|
||||
}
|
||||
if (opts->block_size == 0) {
|
||||
opts->block_size = DEFAULT_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
static int writer_version(struct reftable_writer *w)
|
||||
{
|
||||
return (w->opts.hash_id == 0 || w->opts.hash_id == GIT_SHA1_FORMAT_ID) ?
|
||||
1 :
|
||||
2;
|
||||
}
|
||||
|
||||
static int writer_write_header(struct reftable_writer *w, uint8_t *dest)
|
||||
{
|
||||
memcpy(dest, "REFT", 4);
|
||||
|
||||
dest[4] = writer_version(w);
|
||||
|
||||
put_be24(dest + 5, w->opts.block_size);
|
||||
put_be64(dest + 8, w->min_update_index);
|
||||
put_be64(dest + 16, w->max_update_index);
|
||||
if (writer_version(w) == 2) {
|
||||
put_be32(dest + 24, w->opts.hash_id);
|
||||
}
|
||||
return header_size(writer_version(w));
|
||||
}
|
||||
|
||||
static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ)
|
||||
{
|
||||
int block_start = 0;
|
||||
if (w->next == 0) {
|
||||
block_start = header_size(writer_version(w));
|
||||
}
|
||||
|
||||
strbuf_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));
|
||||
w->block_writer = &w->block_writer_data;
|
||||
w->block_writer->restart_interval = w->opts.restart_interval;
|
||||
}
|
||||
|
||||
static struct strbuf reftable_empty_strbuf = STRBUF_INIT;
|
||||
|
||||
struct reftable_writer *
|
||||
reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
|
||||
void *writer_arg, struct reftable_write_options *opts)
|
||||
{
|
||||
struct reftable_writer *wp =
|
||||
reftable_calloc(sizeof(struct reftable_writer));
|
||||
strbuf_init(&wp->block_writer_data.last_key, 0);
|
||||
options_set_defaults(opts);
|
||||
if (opts->block_size >= (1 << 24)) {
|
||||
/* TODO - error return? */
|
||||
abort();
|
||||
}
|
||||
wp->last_key = reftable_empty_strbuf;
|
||||
wp->block = reftable_calloc(opts->block_size);
|
||||
wp->write = writer_func;
|
||||
wp->write_arg = writer_arg;
|
||||
wp->opts = *opts;
|
||||
writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
|
||||
|
||||
return wp;
|
||||
}
|
||||
|
||||
void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
|
||||
uint64_t max)
|
||||
{
|
||||
w->min_update_index = min;
|
||||
w->max_update_index = max;
|
||||
}
|
||||
|
||||
void reftable_writer_free(struct reftable_writer *w)
|
||||
{
|
||||
reftable_free(w->block);
|
||||
reftable_free(w);
|
||||
}
|
||||
|
||||
struct obj_index_tree_node {
|
||||
struct strbuf hash;
|
||||
uint64_t *offsets;
|
||||
size_t offset_len;
|
||||
size_t offset_cap;
|
||||
};
|
||||
|
||||
#define OBJ_INDEX_TREE_NODE_INIT \
|
||||
{ \
|
||||
.hash = STRBUF_INIT \
|
||||
}
|
||||
|
||||
static int obj_index_tree_node_compare(const void *a, const void *b)
|
||||
{
|
||||
return strbuf_cmp(&((const struct obj_index_tree_node *)a)->hash,
|
||||
&((const struct obj_index_tree_node *)b)->hash);
|
||||
}
|
||||
|
||||
static void writer_index_hash(struct reftable_writer *w, struct strbuf *hash)
|
||||
{
|
||||
uint64_t off = w->next;
|
||||
|
||||
struct obj_index_tree_node want = { .hash = *hash };
|
||||
|
||||
struct tree_node *node = tree_search(&want, &w->obj_index_tree,
|
||||
&obj_index_tree_node_compare, 0);
|
||||
struct obj_index_tree_node *key = NULL;
|
||||
if (node == NULL) {
|
||||
struct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT;
|
||||
key = reftable_malloc(sizeof(struct obj_index_tree_node));
|
||||
*key = empty;
|
||||
|
||||
strbuf_reset(&key->hash);
|
||||
strbuf_addbuf(&key->hash, hash);
|
||||
tree_search((void *)key, &w->obj_index_tree,
|
||||
&obj_index_tree_node_compare, 1);
|
||||
} else {
|
||||
key = node->key;
|
||||
}
|
||||
|
||||
if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key->offset_len == key->offset_cap) {
|
||||
key->offset_cap = 2 * key->offset_cap + 1;
|
||||
key->offsets = reftable_realloc(
|
||||
key->offsets, sizeof(uint64_t) * key->offset_cap);
|
||||
}
|
||||
|
||||
key->offsets[key->offset_len++] = off;
|
||||
}
|
||||
|
||||
static int writer_add_record(struct reftable_writer *w,
|
||||
struct reftable_record *rec)
|
||||
{
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
int err = -1;
|
||||
reftable_record_key(rec, &key);
|
||||
if (strbuf_cmp(&w->last_key, &key) >= 0) {
|
||||
err = REFTABLE_API_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
strbuf_reset(&w->last_key);
|
||||
strbuf_addbuf(&w->last_key, &key);
|
||||
if (w->block_writer == NULL) {
|
||||
writer_reinit_block_writer(w, reftable_record_type(rec));
|
||||
}
|
||||
|
||||
assert(block_writer_type(w->block_writer) == reftable_record_type(rec));
|
||||
|
||||
if (block_writer_add(w->block_writer, rec) == 0) {
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = writer_flush_block(w);
|
||||
if (err < 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
writer_reinit_block_writer(w, reftable_record_type(rec));
|
||||
err = block_writer_add(w->block_writer, rec);
|
||||
if (err < 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
strbuf_release(&key);
|
||||
return err;
|
||||
}
|
||||
|
||||
int reftable_writer_add_ref(struct reftable_writer *w,
|
||||
struct reftable_ref_record *ref)
|
||||
{
|
||||
struct reftable_record rec = { NULL };
|
||||
struct reftable_ref_record copy = *ref;
|
||||
int err = 0;
|
||||
|
||||
if (ref->refname == NULL)
|
||||
return REFTABLE_API_ERROR;
|
||||
if (ref->update_index < w->min_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)
|
||||
return err;
|
||||
|
||||
if (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) {
|
||||
struct strbuf h = STRBUF_INIT;
|
||||
strbuf_add(&h, (char *)reftable_ref_record_val1(ref),
|
||||
hash_size(w->opts.hash_id));
|
||||
writer_index_hash(w, &h);
|
||||
strbuf_release(&h);
|
||||
}
|
||||
|
||||
if (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) {
|
||||
struct strbuf h = STRBUF_INIT;
|
||||
strbuf_add(&h, reftable_ref_record_val2(ref),
|
||||
hash_size(w->opts.hash_id));
|
||||
writer_index_hash(w, &h);
|
||||
strbuf_release(&h);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reftable_writer_add_refs(struct reftable_writer *w,
|
||||
struct reftable_ref_record *refs, int n)
|
||||
{
|
||||
int err = 0;
|
||||
int i = 0;
|
||||
QSORT(refs, n, reftable_ref_record_compare_name);
|
||||
for (i = 0; err == 0 && i < n; i++) {
|
||||
err = reftable_writer_add_ref(w, &refs[i]);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
|
||||
struct reftable_log_record *log)
|
||||
{
|
||||
struct reftable_record rec = { NULL };
|
||||
if (w->block_writer &&
|
||||
block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
|
||||
int err = writer_finish_public_section(w);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
w->next -= w->pending_padding;
|
||||
w->pending_padding = 0;
|
||||
|
||||
reftable_record_from_log(&rec, log);
|
||||
return writer_add_record(w, &rec);
|
||||
}
|
||||
|
||||
int reftable_writer_add_log(struct reftable_writer *w,
|
||||
struct reftable_log_record *log)
|
||||
{
|
||||
char *input_log_message = NULL;
|
||||
struct strbuf cleaned_message = STRBUF_INIT;
|
||||
int err = 0;
|
||||
|
||||
if (log->value_type == REFTABLE_LOG_DELETION)
|
||||
return reftable_writer_add_log_verbatim(w, log);
|
||||
|
||||
if (log->refname == NULL)
|
||||
return REFTABLE_API_ERROR;
|
||||
|
||||
input_log_message = log->value.update.message;
|
||||
if (!w->opts.exact_log_message && log->value.update.message) {
|
||||
strbuf_addstr(&cleaned_message, log->value.update.message);
|
||||
while (cleaned_message.len &&
|
||||
cleaned_message.buf[cleaned_message.len - 1] == '\n')
|
||||
strbuf_setlen(&cleaned_message,
|
||||
cleaned_message.len - 1);
|
||||
if (strchr(cleaned_message.buf, '\n')) {
|
||||
/* multiple lines not allowed. */
|
||||
err = REFTABLE_API_ERROR;
|
||||
goto done;
|
||||
}
|
||||
strbuf_addstr(&cleaned_message, "\n");
|
||||
log->value.update.message = cleaned_message.buf;
|
||||
}
|
||||
|
||||
err = reftable_writer_add_log_verbatim(w, log);
|
||||
log->value.update.message = input_log_message;
|
||||
done:
|
||||
strbuf_release(&cleaned_message);
|
||||
return err;
|
||||
}
|
||||
|
||||
int reftable_writer_add_logs(struct reftable_writer *w,
|
||||
struct reftable_log_record *logs, int n)
|
||||
{
|
||||
int err = 0;
|
||||
int i = 0;
|
||||
QSORT(logs, n, reftable_log_record_compare_key);
|
||||
|
||||
for (i = 0; err == 0 && i < n; i++) {
|
||||
err = reftable_writer_add_log(w, &logs[i]);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int writer_finish_section(struct reftable_writer *w)
|
||||
{
|
||||
uint8_t typ = block_writer_type(w->block_writer);
|
||||
uint64_t index_start = 0;
|
||||
int max_level = 0;
|
||||
int threshold = w->opts.unpadded ? 1 : 3;
|
||||
int before_blocks = w->stats.idx_stats.blocks;
|
||||
int err = writer_flush_block(w);
|
||||
int i = 0;
|
||||
struct reftable_block_stats *bstats = NULL;
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
while (w->index_len > threshold) {
|
||||
struct reftable_index_record *idx = NULL;
|
||||
int idx_len = 0;
|
||||
|
||||
max_level++;
|
||||
index_start = w->next;
|
||||
writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
|
||||
|
||||
idx = w->index;
|
||||
idx_len = w->index_len;
|
||||
|
||||
w->index = NULL;
|
||||
w->index_len = 0;
|
||||
w->index_cap = 0;
|
||||
for (i = 0; i < idx_len; i++) {
|
||||
struct reftable_record rec = { NULL };
|
||||
reftable_record_from_index(&rec, idx + i);
|
||||
if (block_writer_add(w->block_writer, &rec) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
err = writer_flush_block(w);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
|
||||
|
||||
err = block_writer_add(w->block_writer, &rec);
|
||||
if (err != 0) {
|
||||
/* write into fresh block should always succeed
|
||||
*/
|
||||
abort();
|
||||
}
|
||||
}
|
||||
for (i = 0; i < idx_len; i++) {
|
||||
strbuf_release(&idx[i].last_key);
|
||||
}
|
||||
reftable_free(idx);
|
||||
}
|
||||
|
||||
writer_clear_index(w);
|
||||
|
||||
err = writer_flush_block(w);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
bstats = writer_reftable_block_stats(w, typ);
|
||||
bstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;
|
||||
bstats->index_offset = index_start;
|
||||
bstats->max_index_level = max_level;
|
||||
|
||||
/* Reinit lastKey, as the next section can start with any key. */
|
||||
w->last_key.len = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct common_prefix_arg {
|
||||
struct strbuf *last;
|
||||
int max;
|
||||
};
|
||||
|
||||
static void update_common(void *void_arg, void *key)
|
||||
{
|
||||
struct common_prefix_arg *arg = void_arg;
|
||||
struct obj_index_tree_node *entry = key;
|
||||
if (arg->last) {
|
||||
int n = common_prefix_size(&entry->hash, arg->last);
|
||||
if (n > arg->max) {
|
||||
arg->max = n;
|
||||
}
|
||||
}
|
||||
arg->last = &entry->hash;
|
||||
}
|
||||
|
||||
struct write_record_arg {
|
||||
struct reftable_writer *w;
|
||||
int err;
|
||||
};
|
||||
|
||||
static void write_object_record(void *void_arg, void *key)
|
||||
{
|
||||
struct write_record_arg *arg = void_arg;
|
||||
struct obj_index_tree_node *entry = key;
|
||||
struct reftable_obj_record obj_rec = {
|
||||
.hash_prefix = (uint8_t *)entry->hash.buf,
|
||||
.hash_prefix_len = arg->w->stats.object_id_len,
|
||||
.offsets = entry->offsets,
|
||||
.offset_len = entry->offset_len,
|
||||
};
|
||||
struct reftable_record rec = { NULL };
|
||||
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 done;
|
||||
|
||||
arg->err = writer_flush_block(arg->w);
|
||||
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 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);
|
||||
|
||||
done:;
|
||||
}
|
||||
|
||||
static void object_record_free(void *void_arg, void *key)
|
||||
{
|
||||
struct obj_index_tree_node *entry = key;
|
||||
|
||||
FREE_AND_NULL(entry->offsets);
|
||||
strbuf_release(&entry->hash);
|
||||
reftable_free(entry);
|
||||
}
|
||||
|
||||
static int writer_dump_object_index(struct reftable_writer *w)
|
||||
{
|
||||
struct write_record_arg closure = { .w = w };
|
||||
struct common_prefix_arg common = { NULL };
|
||||
if (w->obj_index_tree) {
|
||||
infix_walk(w->obj_index_tree, &update_common, &common);
|
||||
}
|
||||
w->stats.object_id_len = common.max + 1;
|
||||
|
||||
writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
|
||||
|
||||
if (w->obj_index_tree) {
|
||||
infix_walk(w->obj_index_tree, &write_object_record, &closure);
|
||||
}
|
||||
|
||||
if (closure.err < 0)
|
||||
return closure.err;
|
||||
return writer_finish_section(w);
|
||||
}
|
||||
|
||||
static int writer_finish_public_section(struct reftable_writer *w)
|
||||
{
|
||||
uint8_t typ = 0;
|
||||
int err = 0;
|
||||
|
||||
if (w->block_writer == NULL)
|
||||
return 0;
|
||||
|
||||
typ = block_writer_type(w->block_writer);
|
||||
err = writer_finish_section(w);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
|
||||
w->stats.ref_stats.index_blocks > 0) {
|
||||
err = writer_dump_object_index(w);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (w->obj_index_tree) {
|
||||
infix_walk(w->obj_index_tree, &object_record_free, NULL);
|
||||
tree_free(w->obj_index_tree);
|
||||
w->obj_index_tree = NULL;
|
||||
}
|
||||
|
||||
w->block_writer = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reftable_writer_close(struct reftable_writer *w)
|
||||
{
|
||||
uint8_t footer[72];
|
||||
uint8_t *p = footer;
|
||||
int err = writer_finish_public_section(w);
|
||||
int empty_table = w->next == 0;
|
||||
if (err != 0)
|
||||
goto done;
|
||||
w->pending_padding = 0;
|
||||
if (empty_table) {
|
||||
/* Empty tables need a header anyway. */
|
||||
uint8_t header[28];
|
||||
int n = writer_write_header(w, header);
|
||||
err = padded_write(w, header, n, 0);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
p += writer_write_header(w, footer);
|
||||
put_be64(p, w->stats.ref_stats.index_offset);
|
||||
p += 8;
|
||||
put_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
|
||||
p += 8;
|
||||
put_be64(p, w->stats.obj_stats.index_offset);
|
||||
p += 8;
|
||||
|
||||
put_be64(p, w->stats.log_stats.offset);
|
||||
p += 8;
|
||||
put_be64(p, w->stats.log_stats.index_offset);
|
||||
p += 8;
|
||||
|
||||
put_be32(p, crc32(0, footer, p - footer));
|
||||
p += 4;
|
||||
|
||||
err = padded_write(w, footer, footer_size(writer_version(w)), 0);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
if (empty_table) {
|
||||
err = REFTABLE_EMPTY_TABLE_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
/* free up memory. */
|
||||
block_writer_release(&w->block_writer_data);
|
||||
writer_clear_index(w);
|
||||
strbuf_release(&w->last_key);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void writer_clear_index(struct reftable_writer *w)
|
||||
{
|
||||
int i = 0;
|
||||
for (i = 0; i < w->index_len; i++) {
|
||||
strbuf_release(&w->index[i].last_key);
|
||||
}
|
||||
|
||||
FREE_AND_NULL(w->index);
|
||||
w->index_len = 0;
|
||||
w->index_cap = 0;
|
||||
}
|
||||
|
||||
static const int debug = 0;
|
||||
|
||||
static int writer_flush_nonempty_block(struct reftable_writer *w)
|
||||
{
|
||||
uint8_t typ = block_writer_type(w->block_writer);
|
||||
struct reftable_block_stats *bstats =
|
||||
writer_reftable_block_stats(w, typ);
|
||||
uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
|
||||
int raw_bytes = block_writer_finish(w->block_writer);
|
||||
int padding = 0;
|
||||
int err = 0;
|
||||
struct reftable_index_record ir = { .last_key = STRBUF_INIT };
|
||||
if (raw_bytes < 0)
|
||||
return raw_bytes;
|
||||
|
||||
if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
|
||||
padding = w->opts.block_size - raw_bytes;
|
||||
}
|
||||
|
||||
if (block_typ_off > 0) {
|
||||
bstats->offset = block_typ_off;
|
||||
}
|
||||
|
||||
bstats->entries += w->block_writer->entries;
|
||||
bstats->restarts += w->block_writer->restart_len;
|
||||
bstats->blocks++;
|
||||
w->stats.blocks++;
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ,
|
||||
w->next, raw_bytes,
|
||||
get_be24(w->block + w->block_writer->header_off + 1));
|
||||
}
|
||||
|
||||
if (w->next == 0) {
|
||||
writer_write_header(w, w->block);
|
||||
}
|
||||
|
||||
err = padded_write(w, w->block, raw_bytes, padding);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (w->index_cap == w->index_len) {
|
||||
w->index_cap = 2 * w->index_cap + 1;
|
||||
w->index = reftable_realloc(
|
||||
w->index,
|
||||
sizeof(struct reftable_index_record) * w->index_cap);
|
||||
}
|
||||
|
||||
ir.offset = w->next;
|
||||
strbuf_reset(&ir.last_key);
|
||||
strbuf_addbuf(&ir.last_key, &w->block_writer->last_key);
|
||||
w->index[w->index_len] = ir;
|
||||
|
||||
w->index_len++;
|
||||
w->next += padding + raw_bytes;
|
||||
w->block_writer = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int writer_flush_block(struct reftable_writer *w)
|
||||
{
|
||||
if (w->block_writer == NULL)
|
||||
return 0;
|
||||
if (w->block_writer->entries == 0)
|
||||
return 0;
|
||||
return writer_flush_nonempty_block(w);
|
||||
}
|
||||
|
||||
const struct reftable_stats *writer_stats(struct reftable_writer *w)
|
||||
{
|
||||
return &w->stats;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 WRITER_H
|
||||
#define WRITER_H
|
||||
|
||||
#include "basics.h"
|
||||
#include "block.h"
|
||||
#include "tree.h"
|
||||
#include "reftable-writer.h"
|
||||
|
||||
struct reftable_writer {
|
||||
ssize_t (*write)(void *, const void *, size_t);
|
||||
void *write_arg;
|
||||
int pending_padding;
|
||||
struct strbuf last_key;
|
||||
|
||||
/* offset of next block to write. */
|
||||
uint64_t next;
|
||||
uint64_t min_update_index, max_update_index;
|
||||
struct reftable_write_options opts;
|
||||
|
||||
/* memory buffer for writing */
|
||||
uint8_t *block;
|
||||
|
||||
/* writer for the current section. NULL or points to
|
||||
* block_writer_data */
|
||||
struct block_writer *block_writer;
|
||||
|
||||
struct block_writer block_writer_data;
|
||||
|
||||
/* pending index records for the current section */
|
||||
struct reftable_index_record *index;
|
||||
size_t index_len;
|
||||
size_t index_cap;
|
||||
|
||||
/*
|
||||
* tree for use with tsearch; used to populate the 'o' inverse OID
|
||||
* map */
|
||||
struct tree_node *obj_index_tree;
|
||||
|
||||
struct reftable_stats stats;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,21 @@
|
|||
#include "reftable/reftable-tests.h"
|
||||
#include "test-tool.h"
|
||||
|
||||
int cmd__reftable(int argc, const char **argv)
|
||||
{
|
||||
basics_test_main(argc, argv);
|
||||
block_test_main(argc, argv);
|
||||
merged_test_main(argc, argv);
|
||||
pq_test_main(argc, argv);
|
||||
record_test_main(argc, argv);
|
||||
refname_test_main(argc, argv);
|
||||
readwrite_test_main(argc, argv);
|
||||
stack_test_main(argc, argv);
|
||||
tree_test_main(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd__dump_reftable(int argc, const char **argv)
|
||||
{
|
||||
return reftable_dump_main(argc, (char *const *)argv);
|
||||
}
|
|
@ -53,13 +53,15 @@ static struct test_cmd cmds[] = {
|
|||
{ "pcre2-config", cmd__pcre2_config },
|
||||
{ "pkt-line", cmd__pkt_line },
|
||||
{ "prio-queue", cmd__prio_queue },
|
||||
{ "proc-receive", cmd__proc_receive},
|
||||
{ "proc-receive", cmd__proc_receive },
|
||||
{ "progress", cmd__progress },
|
||||
{ "reach", cmd__reach },
|
||||
{ "read-cache", cmd__read_cache },
|
||||
{ "read-graph", cmd__read_graph },
|
||||
{ "read-midx", cmd__read_midx },
|
||||
{ "ref-store", cmd__ref_store },
|
||||
{ "reftable", cmd__reftable },
|
||||
{ "dump-reftable", cmd__dump_reftable },
|
||||
{ "regex", cmd__regex },
|
||||
{ "repository", cmd__repository },
|
||||
{ "revision-walking", cmd__revision_walking },
|
||||
|
|
|
@ -19,6 +19,7 @@ int cmd__dump_cache_tree(int argc, const char **argv);
|
|||
int cmd__dump_fsmonitor(int argc, const char **argv);
|
||||
int cmd__dump_split_index(int argc, const char **argv);
|
||||
int cmd__dump_untracked_cache(int argc, const char **argv);
|
||||
int cmd__dump_reftable(int argc, const char **argv);
|
||||
int cmd__example_decorate(int argc, const char **argv);
|
||||
int cmd__fast_rebase(int argc, const char **argv);
|
||||
int cmd__genrandom(int argc, const char **argv);
|
||||
|
@ -49,6 +50,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);
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2020 Google LLC
|
||||
#
|
||||
|
||||
test_description='reftable unittests'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'unittests' '
|
||||
TMPDIR=$(pwd) && export TMPDIR &&
|
||||
test-tool reftable
|
||||
'
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче