This adds the reftable library, and hooks it up as a ref backend. Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1 Summary 18816 tests pass 2994 tests fail Some issues: * worktree support looks broken * no detection for file/dir conflicts. You can have branch {a, a/b} simultaneously. * submodules (eg. t1013) * many tests inspect .git/logs/ directly. v13 * Avoid exposing tombstones from reftable_stack to git * Write peeled tags * Fix GC issue: use git-refpack in test * Fix unborn branches: must propagate errno from read_raw_ref. * add GIT_TEST_REFTABLE environment to control default. * add REFTABLE test prerequisite. * fix & test update-ref -d. * print error message for failed update-ref transactions. Han-Wen Nienhuys (11): refs.h: clarify reflog iteration order Iterate over the "refs/" namespace in for_each_[raw]ref create .git/refs in files-backend.c refs: document how ref_iterator_advance_fn should handle symrefs Add .gitattributes for the reftable/ directory reftable: define version 2 of the spec to accomodate SHA256 reftable: clarify how empty tables should be written Add reftable library Reftable support for git-core Add some reftable testing infrastructure t: use update-ref and show-ref to reading/writing refs Jonathan Nieder (1): reftable: file format documentation Documentation/Makefile | 1 + Documentation/technical/reftable.txt | 1080 ++++++++++++++++ .../technical/repository-version.txt | 7 + Makefile | 26 +- builtin/clone.c | 3 +- builtin/init-db.c | 63 +- cache.h | 6 +- refs.c | 33 +- refs.h | 8 +- refs/files-backend.c | 6 + refs/refs-internal.h | 6 + refs/reftable-backend.c | 1045 +++++++++++++++ reftable/.gitattributes | 1 + reftable/LICENSE | 31 + reftable/README.md | 11 + reftable/VERSION | 5 + reftable/basics.c | 209 +++ reftable/basics.h | 53 + reftable/block.c | 425 ++++++ reftable/block.h | 124 ++ reftable/blocksource.h | 22 + reftable/bytes.c | 0 reftable/config.h | 1 + reftable/constants.h | 21 + reftable/dump.c | 97 ++ reftable/file.c | 98 ++ reftable/iter.c | 234 ++++ reftable/iter.h | 60 + reftable/merged.c | 327 +++++ reftable/merged.h | 36 + reftable/pq.c | 115 ++ reftable/pq.h | 34 + reftable/reader.c | 754 +++++++++++ reftable/reader.h | 68 + reftable/record.c | 1126 ++++++++++++++++ reftable/record.h | 121 ++ reftable/reftable.h | 527 ++++++++ reftable/slice.c | 224 ++++ reftable/slice.h | 76 ++ reftable/stack.c | 1151 +++++++++++++++++ reftable/stack.h | 44 + reftable/system.h | 53 + reftable/tree.c | 67 + reftable/tree.h | 34 + reftable/update.sh | 23 + reftable/writer.c | 661 ++++++++++ reftable/writer.h | 60 + reftable/zlib-compat.c | 92 ++ repository.c | 2 + repository.h | 3 + setup.c | 12 +- t/t0002-gitfile.sh | 2 +- t/t0031-reftable.sh | 99 ++ t/t1400-update-ref.sh | 32 +- t/t1506-rev-parse-diagnosis.sh | 2 +- t/t3210-pack-refs.sh | 6 + t/t6050-replace.sh | 2 +- t/t9020-remote-svn.sh | 4 +- t/test-lib.sh | 5 + 59 files changed, 9378 insertions(+), 60 deletions(-) create mode 100644 Documentation/technical/reftable.txt create mode 100644 refs/reftable-backend.c create mode 100644 reftable/.gitattributes create mode 100644 reftable/LICENSE create mode 100644 reftable/README.md create mode 100644 reftable/VERSION create mode 100644 reftable/basics.c create mode 100644 reftable/basics.h create mode 100644 reftable/block.c create mode 100644 reftable/block.h create mode 100644 reftable/blocksource.h create mode 100644 reftable/bytes.c create mode 100644 reftable/config.h create mode 100644 reftable/constants.h create mode 100644 reftable/dump.c create mode 100644 reftable/file.c create mode 100644 reftable/iter.c create mode 100644 reftable/iter.h create mode 100644 reftable/merged.c create mode 100644 reftable/merged.h create mode 100644 reftable/pq.c create mode 100644 reftable/pq.h create mode 100644 reftable/reader.c create mode 100644 reftable/reader.h create mode 100644 reftable/record.c create mode 100644 reftable/record.h create mode 100644 reftable/reftable.h create mode 100644 reftable/slice.c create mode 100644 reftable/slice.h create mode 100644 reftable/stack.c create mode 100644 reftable/stack.h create mode 100644 reftable/system.h create mode 100644 reftable/tree.c create mode 100644 reftable/tree.h create mode 100755 reftable/update.sh create mode 100644 reftable/writer.c create mode 100644 reftable/writer.h create mode 100644 reftable/zlib-compat.c create mode 100755 t/t0031-reftable.sh base-commit: e870325ee8575d5c3d7afe0ba2c9be072c692b65 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-539%2Fhanwen%2Freftable-v10 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-539/hanwen/reftable-v10 Pull-Request: https://github.com/gitgitgadget/git/pull/539 Range-diff vs v9: 1: b600d0bc6dd = 1: d3d8ed2032c refs.h: clarify reflog iteration order 2: 89b68145e8f = 2: 45fd65f72e0 Iterate over the "refs/" namespace in for_each_[raw]ref 3: 91f1efe24ec = 3: bc89bcd9c8c create .git/refs in files-backend.c 4: 56f65a2a0d7 = 4: fd67cdff0cd refs: document how ref_iterator_advance_fn should handle symrefs 5: b432c1cc2ae = 5: 5373828f854 Add .gitattributes for the reftable/ directory 6: b1d94be691a = 6: 8eeed29ebbf reftable: file format documentation 7: 3e418d27f67 = 7: 5ebc272e23d reftable: define version 2 of the spec to accomodate SHA256 8: 6f62be4067b = 8: 95aae041613 reftable: clarify how empty tables should be written 9: a30001ad1e8 ! 9: 59209a5ad39 Add reftable library @@ Commit message * reftable.h - the public API + * record.{c,h} - reading and writing records + * block.{c,h} - reading and writing blocks. * writer.{c,h} - writing a complete reftable file. @@ reftable/README.md (new) ## reftable/VERSION (new) ## @@ -+commit b68690acad769d59e80cbb4f79c442ece133e6bd ++commit a6d6b31bea256044621d789df64a8159647f21bc +Author: Han-Wen Nienhuys <hanwen@xxxxxxxxxx> -+Date: Mon Apr 20 21:33:11 2020 +0200 ++Date: Mon Apr 27 20:46:21 2020 +0200 + -+ C: fix search/replace error in dump.c ++ C: prefix error codes with REFTABLE_ ## reftable/basics.c (new) ## @@ @@ reftable/basics.c (new) +const char *reftable_error_str(int err) +{ + switch (err) { -+ case IO_ERROR: ++ case REFTABLE_IO_ERROR: + return "I/O error"; -+ case FORMAT_ERROR: -+ return "FORMAT_ERROR"; -+ case NOT_EXIST_ERROR: -+ return "NOT_EXIST_ERROR"; -+ case LOCK_ERROR: -+ return "LOCK_ERROR"; -+ case API_ERROR: -+ return "API_ERROR"; -+ case ZLIB_ERROR: -+ return "ZLIB_ERROR"; ++ case 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 -1: + return "general error"; + default: @@ reftable/basics.c (new) + } +} + ++int reftable_error_to_errno(int err) { ++ switch (err) { ++ case REFTABLE_IO_ERROR: ++ return EIO; ++ case REFTABLE_FORMAT_ERROR: ++ return EFAULT; ++ case REFTABLE_NOT_EXIST_ERROR: ++ return ENOENT; ++ case REFTABLE_LOCK_ERROR: ++ return EBUSY; ++ case REFTABLE_API_ERROR: ++ return EINVAL; ++ case REFTABLE_ZLIB_ERROR: ++ return EDOM; ++ default: ++ return ERANGE; ++ } ++} ++ ++ +void *(*reftable_malloc_ptr)(size_t sz) = &malloc; +void *(*reftable_realloc_ptr)(void *, size_t) = &realloc; +void (*reftable_free_ptr)(void *) = &free; @@ reftable/block.c (new) + + if (Z_OK != zresult) { + slice_clear(&compressed); -+ return ZLIB_ERROR; ++ return REFTABLE_ZLIB_ERROR; + } + + memcpy(w->buf + block_header_skip, compressed.buf, @@ reftable/block.c (new) + uint32_t sz = get_be24(block->data + header_off + 1); + + if (!is_block_type(typ)) { -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; + } + + if (typ == BLOCK_TYPE_LOG) { @@ reftable/block.c (new) + &dst_len, block->data + block_header_skip, + &src_len)) { + slice_clear(&uncompressed); -+ return ZLIB_ERROR; ++ return REFTABLE_ZLIB_ERROR; + } + + block_source_return_block(block->source, block); @@ reftable/file.c (new) + int fd = open(name, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) { -+ return NOT_EXIST_ERROR; ++ return REFTABLE_NOT_EXIST_ERROR; + } + return -1; + } @@ reftable/iter.c (new) + } + if (err > 0) { + /* indexed block does not exist. */ -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; + } + } + block_reader_start(&it->block_reader, &it->cur); @@ reftable/merged.c (new) + return 0; +} + -+static int merged_iter_next(struct merged_iter *mi, struct record rec) ++static int merged_iter_next_entry(struct merged_iter *mi, struct record rec) +{ + struct slice entry_key = { 0 }; -+ struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq); -+ int err = merged_iter_advance_subiter(mi, entry.index); ++ 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. -+ */ ++ /* ++ 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. ++ */ + record_key(entry.rec, &entry_key); + while (!merged_iter_pqueue_is_empty(mi->pq)) { + struct pq_entry top = merged_iter_pqueue_top(mi->pq); @@ reftable/merged.c (new) + return 0; +} + ++static int merged_iter_next(struct merged_iter *mi, struct record rec) ++{ ++ while (true) { ++ int err = merged_iter_next_entry(mi, rec); ++ if (err == 0 && mi->suppress_deletions && ++ record_is_deletion(rec)) { ++ continue; ++ } ++ ++ return err; ++ } ++} ++ +static int merged_iter_next_void(void *p, struct record rec) +{ + struct merged_iter *mi = (struct merged_iter *)p; @@ reftable/merged.c (new) + for (i = 0; i < n; i++) { + struct reftable_reader *r = stack[i]; + if (r->hash_id != hash_id) { -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; + } + if (i > 0 && last_max >= reftable_reader_min_update_index(r)) { -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; + } + if (i == 0) { + first_min = reftable_reader_min_update_index(r); @@ reftable/merged.c (new) + .stack = iters, + .typ = record_type(rec), + .hash_id = mt->hash_id, ++ .suppress_deletions = mt->suppress_deletions, + }; + int n = 0; + int err = 0; @@ reftable/merged.h (new) + struct reftable_reader **stack; + int stack_len; + uint32_t hash_id; ++ bool suppress_deletions; + + uint64_t min; + uint64_t max; @@ reftable/merged.h (new) + uint32_t hash_id; + int stack_len; + byte typ; ++ bool suppress_deletions; + struct merged_iter_pqueue pq; +}; + @@ reftable/reader.c (new) + byte *f = footer; + int err = 0; + if (memcmp(f, "REFT", 4)) { -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + goto exit; + } + f += 4; + + if (memcmp(footer, header, header_size(r->version))) { -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + goto exit; + } + @@ reftable/reader.c (new) + case SHA256_ID: + break; + default: -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + goto exit; + } + f += 4; @@ reftable/reader.c (new) + uint32_t file_crc = get_be32(f); + f += 4; + if (computed_crc != file_crc) { -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + goto exit; + } + } @@ reftable/reader.c (new) + /* Need +1 to read type of first block. */ + err = block_source_read_block(source, &header, 0, header_size(2) + 1); + if (err != header_size(2) + 1) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + + if (memcmp(header.data, "REFT", 4)) { -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + goto exit; + } + r->version = header.data[4]; + if (r->version != 1 && r->version != 2) { -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + goto exit; + } + @@ reftable/reader.c (new) + err = block_source_read_block(source, &footer, r->size, + footer_size(r->version)); + if (err != footer_size(r->version)) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + @@ reftable/reader.c (new) +static int table_iter_next(struct table_iter *ti, struct record rec) +{ + if (record_type(rec) != ti->typ) { -+ return API_ERROR; ++ return REFTABLE_API_ERROR; + } + + while (true) { @@ reftable/reader.c (new) + } + + if (next.typ != BLOCK_TYPE_INDEX) { -+ err = FORMAT_ERROR; ++ err = REFTABLE_FORMAT_ERROR; + break; + } + @@ reftable/record.c (new) + return start_len - in.len; +} + -+static byte reftable_ref_record_type(void) -+{ -+ return BLOCK_TYPE_REF; -+} -+ +static void reftable_ref_record_key(const void *r, struct slice *dest) +{ + const struct reftable_ref_record *rec = @@ reftable/record.c (new) + } +} + -+void reftable_ref_record_print(struct reftable_ref_record *ref, int hash_size) ++void reftable_ref_record_print(struct reftable_ref_record *ref, ++ uint32_t hash_id) +{ + char hex[SHA256_SIZE + 1] = { 0 }; -+ + printf("ref{%s(%" PRIu64 ") ", ref->ref_name, ref->update_index); + if (ref->value != NULL) { -+ hex_format(hex, ref->value, hash_size); ++ hex_format(hex, ref->value, hash_size(hash_id)); + printf("%s", hex); + } + if (ref->target_value != NULL) { -+ hex_format(hex, ref->target_value, hash_size); ++ hex_format(hex, ref->target_value, hash_size(hash_id)); + printf(" (T %s)", hex); + } + if (ref->target != NULL) { @@ reftable/record.c (new) + return start.len - in.len; +} + ++static bool reftable_ref_record_is_deletion_void(const void *p) ++{ ++ return reftable_ref_record_is_deletion( ++ (const struct reftable_ref_record *)p); ++} ++ +struct record_vtable reftable_ref_record_vtable = { + .key = &reftable_ref_record_key, -+ .type = &reftable_ref_record_type, ++ .type = BLOCK_TYPE_REF, + .copy_from = &reftable_ref_record_copy_from, + .val_type = &reftable_ref_record_val_type, + .encode = &reftable_ref_record_encode, + .decode = &reftable_ref_record_decode, + .clear = &reftable_ref_record_clear_void, ++ .is_deletion = &reftable_ref_record_is_deletion_void, +}; + -+static byte obj_record_type(void) -+{ -+ return BLOCK_TYPE_OBJ; -+} -+ +static void obj_record_key(const void *r, struct slice *dest) +{ + const struct obj_record *rec = (const struct obj_record *)r; @@ reftable/record.c (new) + return start.len - in.len; +} + ++static bool not_a_deletion(const void *p) ++{ ++ return false; ++} ++ +struct record_vtable obj_record_vtable = { + .key = &obj_record_key, -+ .type = &obj_record_type, ++ .type = BLOCK_TYPE_OBJ, + .copy_from = &obj_record_copy_from, + .val_type = &obj_record_val_type, + .encode = &obj_record_encode, + .decode = &obj_record_decode, + .clear = &obj_record_clear, ++ .is_deletion = not_a_deletion, +}; + -+void reftable_log_record_print(struct reftable_log_record *log, int hash_size) ++void reftable_log_record_print(struct reftable_log_record *log, ++ uint32_t hash_id) +{ + char hex[SHA256_SIZE + 1] = { 0 }; + + printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", log->ref_name, + log->update_index, log->name, log->email, log->time, + log->tz_offset); -+ hex_format(hex, log->old_hash, hash_size); ++ hex_format(hex, log->old_hash, hash_size(hash_id)); + printf("%s => ", hex); -+ hex_format(hex, log->new_hash, hash_size); ++ hex_format(hex, log->new_hash, hash_size(hash_id)); + printf("%s\n\n%s\n}\n", hex, log->message); +} + -+static byte reftable_log_record_type(void) -+{ -+ return BLOCK_TYPE_LOG; -+} -+ +static void reftable_log_record_key(const void *r, struct slice *dest) +{ + const struct reftable_log_record *rec = @@ reftable/record.c (new) + int n; + + if (key.len <= 9 || key.buf[key.len - 9] != 0) { -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; + } + + r->ref_name = reftable_realloc(r->ref_name, key.len - 8); @@ reftable/record.c (new) + } + + if (in.len < 2 * hash_size) { -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; + } + + r->old_hash = reftable_realloc(r->old_hash, hash_size); @@ reftable/record.c (new) + +error: + slice_clear(&dest); -+ return FORMAT_ERROR; ++ return REFTABLE_FORMAT_ERROR; +} + +static bool null_streq(char *a, char *b) @@ reftable/record.c (new) + a->update_index == b->update_index; +} + ++static bool reftable_log_record_is_deletion_void(const void *p) ++{ ++ return reftable_log_record_is_deletion( ++ (const struct reftable_log_record *)p); ++} ++ +struct record_vtable reftable_log_record_vtable = { + .key = &reftable_log_record_key, -+ .type = &reftable_log_record_type, ++ .type = BLOCK_TYPE_LOG, + .copy_from = &reftable_log_record_copy_from, + .val_type = &reftable_log_record_val_type, + .encode = &reftable_log_record_encode, + .decode = &reftable_log_record_decode, + .clear = &reftable_log_record_clear_void, ++ .is_deletion = &reftable_log_record_is_deletion_void, +}; + +struct record new_record(byte typ) @@ reftable/record.c (new) + reftable_free(record_yield(rec)); +} + -+static byte index_record_type(void) -+{ -+ return BLOCK_TYPE_INDEX; -+} -+ +static void index_record_key(const void *r, struct slice *dest) +{ + struct index_record *rec = (struct index_record *)r; @@ reftable/record.c (new) + +struct record_vtable index_record_vtable = { + .key = &index_record_key, -+ .type = &index_record_type, ++ .type = BLOCK_TYPE_INDEX, + .copy_from = &index_record_copy_from, + .val_type = &index_record_val_type, + .encode = &index_record_encode, + .decode = &index_record_decode, + .clear = &index_record_clear, ++ .is_deletion = ¬_a_deletion, +}; + +void record_key(struct record rec, struct slice *dest) @@ reftable/record.c (new) + +byte record_type(struct record rec) +{ -+ return rec.ops->type(); ++ return rec.ops->type; +} + +int record_encode(struct record rec, struct slice dest, int hash_size) @@ reftable/record.c (new) + +void record_copy_from(struct record rec, struct record src, int hash_size) +{ -+ assert(src.ops->type() == rec.ops->type()); ++ assert(src.ops->type == rec.ops->type); + + rec.ops->copy_from(rec.data, src.data, hash_size); +} @@ reftable/record.c (new) + return rec.ops->clear(rec.data); +} + ++bool record_is_deletion(struct record rec) ++{ ++ return rec.ops->is_deletion(rec.data); ++} ++ +void record_from_ref(struct record *rec, struct reftable_ref_record *ref_rec) +{ + rec->data = ref_rec; @@ reftable/record.h (new) + void (*key)(const void *rec, struct slice *dest); + + /* The record type of ('r' for ref). */ -+ byte (*type)(void); ++ byte type; + + void (*copy_from)(void *dest, const void *src, int hash_size); + @@ reftable/record.h (new) + + /* deallocate and null the record. */ + void (*clear)(void *rec); ++ ++ /* is this a tombstone? */ ++ bool (*is_deletion)(const void *rec); +}; + +/* record is a generic wrapper for different types of records. */ @@ reftable/record.h (new) +int record_encode(struct record rec, struct slice dest, int hash_size); +int record_decode(struct record rec, struct slice key, byte extra, + struct slice src, int hash_size); ++bool record_is_deletion(struct record rec); + +/* zeroes out the embedded record */ +void record_clear(struct record rec); @@ reftable/reftable.h (new) +int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref); + +/* prints a reftable_ref_record onto stdout */ -+void reftable_ref_record_print(struct reftable_ref_record *ref, int hash_size); ++void reftable_ref_record_print(struct reftable_ref_record *ref, ++ uint32_t hash_id); + +/* frees and nulls all pointer values. */ +void reftable_ref_record_clear(struct reftable_ref_record *ref); @@ reftable/reftable.h (new) + 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, int hash_size); ++void reftable_log_record_print(struct reftable_log_record *log, ++ uint32_t hash_id); + +/**************************************************************** + Error handling @@ reftable/reftable.h (new) +/* different types of errors */ +enum reftable_error { + /* Unexpected file system behavior */ -+ IO_ERROR = -2, ++ REFTABLE_IO_ERROR = -2, + + /* Format inconsistency on reading data + */ -+ FORMAT_ERROR = -3, ++ REFTABLE_FORMAT_ERROR = -3, + + /* File does not exist. Returned from block_source_from_file(), because + it needs special handling in stack. + */ -+ NOT_EXIST_ERROR = -4, ++ REFTABLE_NOT_EXIST_ERROR = -4, + + /* Trying to write out-of-date data. */ -+ LOCK_ERROR = -5, ++ REFTABLE_LOCK_ERROR = -5, + + /* Misuse of the API: + - on writing a record with NULL ref_name. @@ reftable/reftable.h (new) + - on writing a ref or log record before the stack's next_update_index + - on reading a reftable_ref_record from log iterator, or vice versa. + */ -+ API_ERROR = -6, ++ REFTABLE_API_ERROR = -6, + + /* Decompression error */ -+ ZLIB_ERROR = -7, ++ REFTABLE_ZLIB_ERROR = -7, + + /* Wrote a table without blocks. */ -+ EMPTY_TABLE_ERROR = -8, ++ REFTABLE_EMPTY_TABLE_ERROR = -8, +}; + +/* convert the numeric error code to a string. The string should not be + * deallocated. */ +const char *reftable_error_str(int err); + ++/* ++ * Convert the numeric error code to an equivalent errno code. ++ */ ++int reftable_error_to_errno(int err); ++ +/**************************************************************** + Writing + @@ reftable/reftable.h (new) + +/* 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 API_ERROR is returned. ++ reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned. + + For transactional updates, typically min==max. When converting an existing + ref database into a single reftable, this would be a range of update-index @@ reftable/reftable.h (new) + +/* adds a reftable_ref_record. Must be called in ascending + order. The update_index must be within the limits set by -+ reftable_writer_set_limits(), or API_ERROR is returned. ++ reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. + + It is an error to write a ref record after a log record. + */ @@ reftable/reftable.h (new) + struct reftable_reader **stack, int n, + uint32_t hash_id); + -+/* returns the hash id used in this merged table. */ -+uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt); -+ +/* returns an iterator positioned just before 'name' */ +int reftable_merged_table_seek_ref(struct reftable_merged_table *mt, + struct reftable_iterator *it, @@ reftable/stack.c (new) + reftable_calloc(sizeof(struct reftable_stack)); + struct slice list_file_name = {}; + int err = 0; ++ ++ if (config.hash_id == 0) { ++ config.hash_id = SHA1_ID; ++ } ++ + *dest = NULL; + + slice_set_string(&list_file_name, dir); -+ slice_append_string(&list_file_name, "/reftables.list"); ++ slice_append_string(&list_file_name, "/tables.list"); + + p->list_file = slice_to_string(list_file_name); + slice_clear(&list_file_name); @@ reftable/stack.c (new) + return err; +} + -+static int fread_lines(FILE *f, char ***namesp) ++static int fd_read_lines(int fd, char ***namesp) +{ -+ long size = 0; -+ int err = fseek(f, 0, SEEK_END); ++ off_t size = lseek(fd, 0, SEEK_END); + char *buf = NULL; -+ if (err < 0) { -+ err = IO_ERROR; -+ goto exit; -+ } -+ size = ftell(f); ++ int err = 0; + if (size < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } -+ err = fseek(f, 0, SEEK_SET); ++ err = lseek(fd, 0, SEEK_SET); + if (err < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + + buf = reftable_malloc(size + 1); -+ if (fread(buf, 1, size, f) != size) { -+ err = IO_ERROR; ++ if (read(fd, buf, size) != size) { ++ err = REFTABLE_IO_ERROR; + goto exit; + } + buf[size] = 0; + + parse_names(buf, size, namesp); ++ +exit: + reftable_free(buf); + return err; @@ reftable/stack.c (new) + +int read_lines(const char *filename, char ***namesp) +{ -+ FILE *f = fopen(filename, "r"); ++ int fd = open(filename, O_RDONLY, 0644); + int err = 0; -+ if (f == NULL) { ++ if (fd < 0) { + if (errno == ENOENT) { + *namesp = reftable_calloc(sizeof(char *)); + return 0; + } + -+ return IO_ERROR; ++ return REFTABLE_IO_ERROR; + } -+ err = fread_lines(f, namesp); -+ fclose(f); ++ err = fd_read_lines(fd, namesp); ++ close(fd); + return err; +} + @@ reftable/stack.c (new) + merged_table_clear(st->merged); + reftable_merged_table_free(st->merged); + } ++ new_merged->suppress_deletions = true; + st->merged = new_merged; + + { @@ reftable/stack.c (new) + char **names_after = NULL; + struct timeval now = { 0 }; + int err = gettimeofday(&now, NULL); ++ int err2 = 0; + if (err < 0) { + return err; + } @@ reftable/stack.c (new) + free_names(names); + break; + } -+ if (err != NOT_EXIST_ERROR) { ++ if (err != REFTABLE_NOT_EXIST_ERROR) { + free_names(names); + return err; + } + -+ err = read_lines(st->list_file, &names_after); -+ if (err < 0) { ++ /* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent ++ writer. Check if there was one by checking if the name list ++ changed. ++ */ ++ err2 = read_lines(st->list_file, &names_after); ++ if (err2 < 0) { + free_names(names); -+ return err; ++ return err2; + } + + if (names_equal(names_after, names)) { + free_names(names); + free_names(names_after); -+ return -1; ++ return err; + } + free_names(names); + free_names(names_after); @@ reftable/stack.c (new) +{ + int err = stack_try_add(st, write, arg); + if (err < 0) { -+ if (err == LOCK_ERROR) { -+ err = reftable_stack_reload(st); ++ if (err == REFTABLE_LOCK_ERROR) { ++ // Ignore error return, we want to propagate ++ // REFTABLE_LOCK_ERROR. ++ reftable_stack_reload(st); + } + return err; + } + -+ return reftable_stack_auto_compact(st); ++ if (!st->disable_auto_compact) { ++ return reftable_stack_auto_compact(st); ++ } ++ ++ return 0; +} + +static void format_name(struct slice *dest, uint64_t min, uint64_t max) +{ + char buf[100]; -+ snprintf(buf, sizeof(buf), "%012" PRIx64 "-%012" PRIx64, min, max); ++ snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64, min, max); + slice_set_string(dest, buf); +} + @@ reftable/stack.c (new) + O_EXCL | O_CREAT | O_WRONLY, 0644); + if (add->lock_file_fd < 0) { + if (errno == EEXIST) { -+ err = LOCK_ERROR; ++ err = REFTABLE_LOCK_ERROR; + } else { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + } + goto exit; + } @@ reftable/stack.c (new) + } + + if (err > 1) { -+ err = LOCK_ERROR; ++ err = REFTABLE_LOCK_ERROR; + goto exit; + } + @@ reftable/stack.c (new) + err = write(add->lock_file_fd, table_list.buf, table_list.len); + slice_clear(&table_list); + if (err < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + + err = close(add->lock_file_fd); + add->lock_file_fd = 0; + if (err < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + + err = rename(slice_as_string(&add->lock_file_name), + add->stack->list_file); + if (err < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + @@ reftable/stack.c (new) + + tab_fd = mkstemp((char *)slice_as_string(&temp_tab_file_name)); + if (tab_fd < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + @@ reftable/stack.c (new) + } + + err = reftable_writer_close(wr); -+ if (err == EMPTY_TABLE_ERROR) { ++ if (err == REFTABLE_EMPTY_TABLE_ERROR) { + err = 0; + goto exit; + } @@ reftable/stack.c (new) + err = close(tab_fd); + tab_fd = 0; + if (err < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + + if (wr->min_update_index < next_update_index) { -+ err = API_ERROR; ++ err = REFTABLE_API_ERROR; + goto exit; + } + @@ reftable/stack.c (new) + err = rename(slice_as_string(&temp_tab_file_name), + slice_as_string(&tab_file_name)); + if (err < 0) { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + goto exit; + } + @@ reftable/stack.c (new) + if (errno == EEXIST) { + err = 1; + } else { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + } + goto exit; + } @@ reftable/stack.c (new) + } else if (sublock_file_fd < 0) { + if (errno == EEXIST) { + err = 1; ++ } else { ++ err = REFTABLE_IO_ERROR; + } -+ err = IO_ERROR; + } + } + @@ reftable/stack.c (new) + expiry); + /* Compaction + tombstones can create an empty table out of non-empty + * tables. */ -+ is_empty_table = (err == EMPTY_TABLE_ERROR); ++ is_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR); + if (is_empty_table) { + err = 0; + } @@ reftable/stack.c (new) + if (errno == EEXIST) { + err = 1; + } else { -+ err = IO_ERROR; ++ err = REFTABLE_IO_ERROR; + } + goto exit; + } @@ reftable/stack.c (new) + err = rename(slice_as_string(&temp_tab_file_name), + slice_as_string(&new_table_path)); + if (err < 0) { ++ err = REFTABLE_IO_ERROR; + goto exit; + } + } @@ reftable/stack.c (new) + + err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len); + if (err < 0) { ++ err = REFTABLE_IO_ERROR; + unlink(slice_as_string(&new_table_path)); + goto exit; + } + err = close(lock_file_fd); + lock_file_fd = 0; + if (err < 0) { ++ err = REFTABLE_IO_ERROR; + unlink(slice_as_string(&new_table_path)); + goto exit; + } + + err = rename(slice_as_string(&lock_file_name), st->list_file); + if (err < 0) { ++ err = REFTABLE_IO_ERROR; + unlink(slice_as_string(&new_table_path)); + goto exit; + } @@ reftable/stack.c (new) +{ + uint64_t *sizes = + reftable_calloc(sizeof(uint64_t) * st->merged->stack_len); ++ int version = (st->config.hash_id == SHA1_ID) ? 1 : 2; ++ int overhead = footer_size(version) + header_size(version) - 1; + int i = 0; + for (i = 0; i < st->merged->stack_len; i++) { -+ /* overhead is 24 + 68 = 92. */ -+ sizes[i] = st->merged->stack[i]->size - 91; ++ sizes[i] = st->merged->stack[i]->size - overhead; + } + return sizes; +} @@ reftable/stack.h (new) +#define STACK_H + +#include "reftable.h" ++#include "system.h" + +struct reftable_stack { + char *list_file; + char *reftable_dir; ++ bool disable_auto_compact; + + struct reftable_write_options config; + @@ reftable/update.sh (new) @@ +#!/bin/sh + -+set -eux ++set -eu + -+((cd reftable-repo && git fetch origin && git checkout origin/master ) || -+git clone https://github.com/google/reftable reftable-repo) && \ -+cp reftable-repo/c/*.[ch] reftable/ && \ -+cp reftable-repo/c/include/*.[ch] reftable/ && \ -+cp reftable-repo/LICENSE reftable/ && ++# Override this to import from somewhere else, say "../reftable". ++SRC=${SRC:-origin} BRANCH=${BRANCH:-origin/master} ++ ++((git --git-dir reftable-repo/.git fetch ${SRC} && cd reftable-repo && git checkout ${BRANCH} ) || ++ git clone https://github.com/google/reftable reftable-repo) ++ ++cp reftable-repo/c/*.[ch] reftable/ ++cp reftable-repo/c/include/*.[ch] reftable/ ++cp reftable-repo/LICENSE reftable/ +git --git-dir reftable-repo/.git show --no-patch origin/master \ -+> reftable/VERSION && \ -+sed -i~ 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|' reftable/system.h ++ > reftable/VERSION ++ ++mv reftable/system.h reftable/system.h~ ++sed 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|' < reftable/system.h~ > reftable/system.h ++ ++# Remove unittests and compatibility hacks we don't need here. +rm reftable/*_test.c reftable/test_framework.* reftable/compat.* -+git add reftable/*.[ch] ++ ++git add reftable/*.[ch] reftable/LICENSE reftable/VERSION ## reftable/writer.c (new) ## @@ @@ reftable/writer.c (new) + int err = 0; + + if (ref->ref_name == NULL) { -+ return API_ERROR; ++ return REFTABLE_API_ERROR; + } + if (ref->update_index < w->min_update_index || + ref->update_index > w->max_update_index) { -+ return API_ERROR; ++ return REFTABLE_API_ERROR; + } + + record_from_ref(&rec, ©); @@ reftable/writer.c (new) + struct reftable_log_record *log) +{ + if (log->ref_name == NULL) { -+ return API_ERROR; ++ return REFTABLE_API_ERROR; + } + + if (w->block_writer != NULL && @@ reftable/writer.c (new) + } + + if (empty_table) { -+ err = EMPTY_TABLE_ERROR; ++ err = REFTABLE_EMPTY_TABLE_ERROR; + goto exit; + } + 10: ad72edbcfd4 ! 10: be2371cd6e4 Reftable support for git-core @@ Commit message TODO: * Resolve spots marked with XXX - * Test strategy? + + * Detect and prevent directory/file conflicts in naming. + + * Support worktrees (t0002-gitfile "linked repo" testcase) Example use: see t/t0031-reftable.sh @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix) } - init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET); -+ init_db(git_dir, real_git_dir, option_template, -+ GIT_HASH_UNKNOWN, -+ DEFAULT_REF_STORAGE, /* XXX */ -+ INIT_DB_QUIET); ++ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, ++ DEFAULT_REF_STORAGE, INIT_DB_QUIET); if (real_git_dir) git_dir = real_git_dir; @@ builtin/init-db.c: static int needs_work_tree_config(const char *git_dir, const } -void initialize_repository_version(int hash_algo) -+void initialize_repository_version(int hash_algo, const char *ref_storage_format) ++void initialize_repository_version(int hash_algo, ++ const char *ref_storage_format) { char repo_version_string[10]; int repo_version = GIT_REPO_VERSION; @@ builtin/init-db.c: void initialize_repository_version(int hash_algo) #endif - if (hash_algo != GIT_HASH_SHA1) -+ if (hash_algo != GIT_HASH_SHA1 || !strcmp(ref_storage_format, "reftable")) ++ if (hash_algo != GIT_HASH_SHA1 || ++ !strcmp(ref_storage_format, "reftable")) repo_version = GIT_REPO_VERSION_READ; /* This forces creation of new config file */ @@ builtin/init-db.c: static int create_default_files(const char *template_path, + path = git_path_buf(&buf, "HEAD"); + reinit = (!access(path, R_OK) || + readlink(path, junk, sizeof(junk) - 1) != -1); ++ ++ /* ++ * refs/heads is a file when using reftable. We can't reinitialize with ++ * a reftable because it will overwrite HEAD ++ */ ++ if (reinit && (!strcmp(fmt->ref_storage, "reftable")) == ++ is_directory(git_path_buf(&buf, "refs/heads"))) { ++ die("cannot switch ref storage format."); ++ } + /* * We need to create a "refs" dir in any case so that older * versions of git can tell that this is a repository. + */ +- + if (refs_init_db(&err)) + die("failed to set up refs db: %s", err.buf); + @@ builtin/init-db.c: static int create_default_files(const char *template_path, * Create the default symlink from ".git/HEAD" to the "master" * branch, if it does not exist yet. @@ builtin/init-db.c: static int create_default_files(const char *template_path, if (!reinit) { if (create_symref("HEAD", "refs/heads/master", NULL) < 0) exit(1); -+ } else { -+ /* -+ * XXX should check whether our ref backend matches the -+ * original one; if not, either die() or convert -+ */ - } - +- } +- - initialize_repository_version(fmt->hash_algo); -+ initialize_repository_version(fmt->hash_algo, fmt->ref_storage); ++ } ++ initialize_repository_version(fmt->hash_algo, fmt->ref_storage); ++ /* Check filemode trustability */ path = git_path_buf(&buf, "config"); + filemode = TEST_FILEMODE; @@ builtin/init-db.c: static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash } int init_db(const char *git_dir, const char *real_git_dir, - const char *template_dir, int hash, unsigned int flags) + const char *template_dir, int hash, const char *ref_storage_format, -+ unsigned int flags) ++ unsigned int flags) { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; @@ builtin/init-db.c: int init_db(const char *git_dir, const char *real_git_dir, * is an attempt to reinitialize new repository with an old tool. */ check_repository_format(&repo_fmt); -+ repo_fmt.ref_storage = xstrdup(ref_storage_format); ++ repo_fmt.ref_storage = xstrdup(ref_storage_format); validate_hash_algorithm(&repo_fmt, hash); @@ builtin/init-db.c: int cmd_init_db(int argc, const char **argv, const char *pref flags |= INIT_DB_EXIST_OK; - return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags); -+ return init_db(git_dir, real_git_dir, template_dir, hash_algo, ref_storage_format, flags); ++ return init_db(git_dir, real_git_dir, template_dir, hash_algo, ++ ref_storage_format, flags); } ## cache.h ## @@ cache.h: int path_inside_repo(const char *prefix, const char *path); int init_db(const char *git_dir, const char *real_git_dir, const char *template_dir, int hash_algo, -+ const char *ref_storage_format, - unsigned int flags); +- unsigned int flags); -void initialize_repository_version(int hash_algo); -+void initialize_repository_version(int hash_algo, const char *ref_storage_format); ++ const char *ref_storage_format, unsigned int flags); ++void initialize_repository_version(int hash_algo, ++ const char *ref_storage_format); void sanitize_stdfds(void); int daemonize(void); @@ cache.h: struct repository_format { ## refs.c ## @@ + #include "argv-array.h" + #include "repository.h" + ++const char *default_ref_storage(void) ++{ ++ const char *test = getenv("GIT_TEST_REFTABLE"); ++ return test ? "reftable" : "files"; ++} ++ /* * List of all available backends */ @@ refs.c: struct ref_store *get_main_ref_store(struct repository *r) if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); -- r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS); -+ r->refs = ref_store_init(r->gitdir, -+ r->ref_storage_format ? r->ref_storage_format : -+ DEFAULT_REF_STORAGE, -+ REF_STORE_ALL_CAPS); - return r->refs; +- r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS); ++ r->refs_private = ref_store_init( ++ r->gitdir, ++ r->ref_storage_format ? r->ref_storage_format : DEFAULT_REF_STORAGE, ++ REF_STORE_ALL_CAPS); + return r->refs_private; } @@ refs.c: struct ref_store *get_submodule_ref_store(const char *submodule) @@ refs.h: struct string_list; struct string_list_item; struct worktree; -+#define DEFAULT_REF_STORAGE "files" ++/* Returns the ref storage backend to use by default. */ ++const char *default_ref_storage(void); + /* * Resolve a reference, recursively following symbolic refererences. @@ refs/reftable-backend.c (new) + unsigned int store_flags; + + int err; ++ char *repo_dir; + char *reftable_dir; + struct reftable_stack *stack; +}; @@ refs/reftable-backend.c (new) + + base_ref_store_init(ref_store, &refs_be_reftable); + refs->store_flags = store_flags; -+ ++ refs->repo_dir = xstrdup(path); + strbuf_addf(&sb, "%s/reftable", path); + refs->reftable_dir = xstrdup(sb.buf); + strbuf_reset(&sb); + -+ strbuf_addf(&sb, "%s/refs", path); -+ safe_create_dir(sb.buf, 1); -+ strbuf_reset(&sb); -+ -+ strbuf_addf(&sb, "%s/HEAD", path); -+ write_file(sb.buf, "ref: refs/.invalid"); -+ strbuf_reset(&sb); -+ -+ strbuf_addf(&sb, "%s/refs/heads", path); -+ write_file(sb.buf, "this repository uses the reftable format"); -+ + refs->err = reftable_new_stack(&refs->stack, refs->reftable_dir, cfg); + strbuf_release(&sb); + return ref_store; @@ refs/reftable-backend.c (new) +{ + struct git_reftable_ref_store *refs = + (struct git_reftable_ref_store *)ref_store; ++ struct strbuf sb = STRBUF_INIT; ++ + safe_create_dir(refs->reftable_dir, 1); -+ return 0; ++ ++ strbuf_addf(&sb, "%s/HEAD", refs->repo_dir); ++ write_file(sb.buf, "ref: refs/.invalid"); ++ strbuf_reset(&sb); ++ ++ strbuf_addf(&sb, "%s/refs", refs->repo_dir); ++ safe_create_dir(sb.buf, 1); ++ strbuf_reset(&sb); ++ ++ strbuf_addf(&sb, "%s/refs/heads", refs->repo_dir); ++ write_file(sb.buf, "this repository uses the reftable format"); ++ ++ return 0; +} + +struct git_reftable_iterator { @@ refs/reftable-backend.c (new) + } + + base_ref_iterator_init(&ri->base, &reftable_ref_iterator_vtable, 1); -+ ri->prefix = prefix; ++ ri->prefix = prefix; + ri->base.oid = &ri->oid; + ri->flags = flags; + ri->ref_store = ref_store; @@ refs/reftable-backend.c (new) + const char *resolved = refs_resolve_ref_unsafe( + refs, refname, RESOLVE_REF_READING, &out_oid, &out_flags); + if (is_null_oid(want_oid) != (resolved == NULL)) { -+ return LOCK_ERROR; ++ return REFTABLE_LOCK_ERROR; + } + + if (resolved != NULL && !oideq(&out_oid, want_oid)) { -+ return LOCK_ERROR; ++ return REFTABLE_LOCK_ERROR; + } + + return 0; @@ refs/reftable-backend.c (new) + (struct git_reftable_ref_store *)transaction->ref_store; + uint64_t ts = reftable_stack_next_update_index(refs->stack); + int err = 0; ++ int i = 0; + struct reftable_log_record *logs = + calloc(transaction->nr, sizeof(*logs)); + struct ref_update **sorted = @@ refs/reftable-backend.c (new) + QSORT(sorted, transaction->nr, ref_update_cmp); + reftable_writer_set_limits(writer, ts, ts); + -+ for (int i = 0; i < transaction->nr; i++) { ++ for (i = 0; i < transaction->nr; i++) { + struct ref_update *u = sorted[i]; + if (u->flags & REF_HAVE_OLD) { + err = reftable_check_old_oid(transaction->ref_store, @@ refs/reftable-backend.c (new) + } + } + -+ for (int i = 0; i < transaction->nr; i++) { ++ for (i = 0; i < transaction->nr; i++) { + struct ref_update *u = sorted[i]; + struct reftable_log_record *log = &logs[i]; + fill_reftable_log_record(log); @@ refs/reftable-backend.c (new) + transaction->ref_store, u->refname, 0, &out_oid, + &out_flags); + struct reftable_ref_record ref = { NULL }; ++ struct object_id peeled; ++ int peel_error = peel_object(&u->new_oid, &peeled); ++ + ref.ref_name = + (char *)(resolved ? resolved : u->refname); + log->ref_name = ref.ref_name; -+ ref.value = u->new_oid.hash; ++ ++ if (!is_null_oid(&u->new_oid)) { ++ ref.value = u->new_oid.hash; ++ } + ref.update_index = ts; ++ if (!peel_error) { ++ ref.target_value = peeled.hash; ++ } ++ + err = reftable_writer_add_ref(writer, &ref); + if (err < 0) { + goto exit; @@ refs/reftable-backend.c (new) + } + } + -+ for (int i = 0; i < transaction->nr; i++) { ++ for (i = 0; i < transaction->nr; i++) { + err = reftable_writer_add_log(writer, &logs[i]); + clear_reftable_log_record(&logs[i]); + if (err < 0) { @@ refs/reftable-backend.c (new) + err = reftable_stack_add(refs->stack, &write_transaction_table, + transaction); + if (err < 0) { -+ strbuf_addf(errmsg, "reftable: transaction failure %s", ++ strbuf_addf(errmsg, "reftable: transaction failure: %s", + reftable_error_str(err)); + return -1; + } @@ refs/reftable-backend.c (new) + (struct write_delete_refs_arg *)argv; + uint64_t ts = reftable_stack_next_update_index(arg->stack); + int err = 0; ++ int i = 0; + + reftable_writer_set_limits(writer, ts, ts); -+ for (int i = 0; i < arg->refnames->nr; i++) { ++ for (i = 0; i < arg->refnames->nr; i++) { + struct reftable_ref_record ref = { + .ref_name = (char *)arg->refnames->items[i].string, + .update_index = ts, @@ refs/reftable-backend.c (new) + } + } + -+ for (int i = 0; i < arg->refnames->nr; i++) { ++ for (i = 0; i < arg->refnames->nr; i++) { + struct reftable_log_record log = { NULL }; + struct reftable_ref_record current = { NULL }; + fill_reftable_log_record(&log); @@ refs/reftable-backend.c (new) + return ITER_ERROR; + } + -+ if (reftable_log_record_is_deletion(&ri->log)) { -+ /* XXX - Why does the reftable_stack filter these? */ -+ continue; -+ } + ri->base.refname = ri->log.ref_name; + if (ri->last_name != NULL && + !strcmp(ri->log.ref_name, ri->last_name)) { -+ /* we want the refnames that we have reflogs for, so we -+ * skip if we've already produced this name. This could -+ * be faster by seeking directly to -+ * reflog@update_index==0. -+ */ ++ /* we want the refnames that we have reflogs for, so we ++ * skip if we've already produced this name. This could ++ * be faster by seeking directly to ++ * reflog@update_index==0. ++ */ + continue; + } + @@ refs/reftable-backend.c (new) + int cap = 0; + int len = 0; + int err = 0; ++ int i = 0; + + if (refs->err < 0) { + return refs->err; @@ refs/reftable-backend.c (new) + logs[len++] = log; + } + -+ for (int i = len; i--;) { ++ for (i = len; i--;) { + struct reftable_log_record *log = &logs[i]; + struct object_id old_oid; + struct object_id new_oid; @@ refs/reftable-backend.c (new) + } + } + -+ for (int i = 0; i < len; i++) { ++ for (i = 0; i < len; i++) { + reftable_log_record_clear(&logs[i]); + } + free(logs); @@ refs/reftable-backend.c (new) + } + + err = reftable_stack_read_ref(refs->stack, refname, &ref); -+ if (err) { ++ if (err > 0) { ++ errno = ENOENT; ++ err = -1; ++ goto exit; ++ } ++ if (err < 0) { ++ errno = reftable_error_to_errno(err); ++ err = -1; + goto exit; + } + if (ref.target != NULL) { @@ refs/reftable-backend.c (new) + strbuf_reset(referent); + strbuf_addstr(referent, ref.target); + *type |= REF_ISSYMREF; -+ } else { ++ } else if (ref.value != NULL) { + hashcpy(oid->hash, ref.value); -+ } ++ } else { ++ *type |= REF_ISBROKEN; ++ errno = EINVAL; ++ err = -1; ++ } +exit: + reftable_ref_record_clear(&ref); + return err; @@ refs/reftable-backend.c (new) + reftable_reflog_expire +}; + ## reftable/update.sh ## +@@ reftable/update.sh: SRC=${SRC:-origin} BRANCH=${BRANCH:-origin/master} + cp reftable-repo/c/*.[ch] reftable/ + cp reftable-repo/c/include/*.[ch] reftable/ + cp reftable-repo/LICENSE reftable/ +-git --git-dir reftable-repo/.git show --no-patch origin/master \ ++git --git-dir reftable-repo/.git show --no-patch ${BRANCH} \ + > reftable/VERSION + + mv reftable/system.h reftable/system.h~ + ## repository.c ## @@ repository.c: int repo_init(struct repository *repo, if (worktree) @@ repository.c: int repo_init(struct repository *repo, ## repository.h ## @@ repository.h: struct repository { - /* The store in which the refs are held. */ - struct ref_store *refs; + */ + struct ref_store *refs_private; + /* The format to use for the ref database. */ + char *ref_storage_format; @@ t/t0031-reftable.sh (new) + +. ./test-lib.sh + -+# XXX - fix GC -+test_expect_success 'basic operation of reftable storage' ' ++INVALID_SHA1=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ ++initialize () { + rm -rf .git && + git init --ref-storage=reftable && -+ mv .git/hooks .git/hooks-disabled && ++ mv .git/hooks .git/hooks-disabled ++} ++ ++test_expect_success 'delete ref' ' ++ initialize && ++ test_commit file && ++ SHA=$(git show-ref -s --verify HEAD) && ++ test_write_lines "$SHA refs/heads/master" "$SHA refs/tags/file" >expect && ++ git show-ref > actual && ++ ! git update-ref -d refs/tags/file $INVALID_SHA1 && ++ test_cmp expect actual && ++ git update-ref -d refs/tags/file $SHA && ++ test_write_lines "$SHA refs/heads/master" >expect && ++ git show-ref > actual && ++ test_cmp expect actual ++' ++ ++test_expect_success 'basic operation of reftable storage: commit, reflog, repack' ' ++ initialize && + test_commit file && + test_write_lines refs/heads/master refs/tags/file >expect && + git show-ref && @@ t/t0031-reftable.sh (new) + for count in $(test_seq 1 10) + do + test_commit "number $count" file.t $count number-$count || -+ return 1 ++ return 1 + done && -+(true || (test_pause && -+ git gc && ++ git pack-refs && + ls -1 .git/reftable >table-files && + test_line_count = 2 table-files && -+ git reflog refs/heads/master >output && ++ git reflog refs/heads/master >output && + test_line_count = 11 output && -+ grep "commit (initial): first post" output && -+ grep "commit: number 10" output )) ++ grep "commit (initial): file" output && ++ grep "commit: number 10" output && ++ git gc && ++ git reflog refs/heads/master >output && ++ test_line_count = 0 output ++' ++ ++# This matches show-ref's output ++print_ref() { ++ echo "$(git rev-parse "$1") $1" ++} ++ ++test_expect_success 'peeled tags are stored' ' ++ initialize && ++ test_commit file && ++ git tag -m "annotated tag" test_tag HEAD && ++ { ++ print_ref "refs/heads/master" && ++ print_ref "refs/tags/file" && ++ print_ref "refs/tags/test_tag" && ++ print_ref "refs/tags/test_tag^{}" ++ } >expect && ++ git show-ref -d >actual && ++ test_cmp expect actual ++' ++ ++test_expect_success 'show-ref works on fresh repo' ' ++ initialize && ++ rm -rf .git && ++ git init --ref-storage=reftable && ++ >expect && ++ ! git show-ref > actual && ++ test_cmp expect actual ++' ++ ++test_expect_success 'checkout unborn branch' ' ++ initialize && ++ git checkout -b master ++' ++ ++test_expect_success 'do not clobber existing repo' ' ++ rm -rf .git && ++ git init --ref-storage=files && ++ cat .git/HEAD > expect && ++ test_commit file && ++ (git init --ref-storage=reftable || true) && ++ cat .git/HEAD > actual && ++ test_cmp expect actual +' + +test_done -: ----------- > 11: 8af67b85e49 Add some reftable testing infrastructure -: ----------- > 12: b02b83a92ad t: use update-ref and show-ref to reading/writing refs -- gitgitgadget