This adds the reftable library, and hooks it up as a ref backend. This is still based on next, as there are conflicts basing on master. Includes testing support, to test: make -C t/ GIT_TEST_REFTABLE=1 Summary 455 tests fail v24 * rebase * FETCH_HEAD special casing Han-Wen Nienhuys (19): refs: add \t to reflog in the files backend Split off reading loose ref data in separate function t1400: use git rev-parse for testing PSEUDOREF existence Modify pseudo refs through ref backend storage Make HEAD a PSEUDOREF rather than PER_WORKTREE. Make refs_ref_exists public Treat CHERRY_PICK_HEAD as a pseudo ref Treat REVERT_HEAD as a pseudo ref Move REF_LOG_ONLY to refs-internal.h Iteration over entire ref namespace is iterating over "refs/" Add .gitattributes for the reftable/ directory Add reftable library Add standalone build infrastructure for reftable Reftable support for git-core Read FETCH_HEAD as loose ref Hookup unittests for the reftable library. Add GIT_DEBUG_REFS debugging mechanism Add reftable testing infrastructure Add "test-tool dump-reftable" command. Johannes Schindelin (1): vcxproj: adjust for the reftable changes SZEDER Gábor (1): git-prompt: prepare for reftable refs backend Documentation/git-update-ref.txt | 13 +- .../technical/repository-version.txt | 7 + Makefile | 47 +- builtin/clone.c | 4 +- builtin/commit.c | 34 +- builtin/init-db.c | 55 +- builtin/merge.c | 2 +- builtin/worktree.c | 29 +- cache.h | 8 +- config.mak.uname | 2 +- contrib/buildsystems/Generators/Vcxproj.pm | 11 +- contrib/completion/git-prompt.sh | 7 +- path.c | 2 - path.h | 9 +- ref-filter.c | 7 +- refs.c | 171 +- refs.h | 6 + refs/debug.c | 387 +++++ refs/files-backend.c | 45 +- refs/refs-internal.h | 20 + refs/reftable-backend.c | 1415 +++++++++++++++++ reftable/.gitattributes | 1 + reftable/BUILD | 203 +++ reftable/LICENSE | 31 + reftable/README.md | 33 + reftable/VERSION | 1 + reftable/WORKSPACE | 14 + reftable/basics.c | 215 +++ reftable/basics.h | 53 + reftable/block.c | 432 +++++ reftable/block.h | 129 ++ reftable/block_test.c | 157 ++ reftable/compat.c | 98 ++ reftable/compat.h | 48 + reftable/constants.h | 21 + reftable/dump.c | 212 +++ reftable/file.c | 95 ++ reftable/iter.c | 242 +++ reftable/iter.h | 72 + reftable/merged.c | 317 ++++ reftable/merged.h | 43 + reftable/merged_test.c | 286 ++++ reftable/pq.c | 115 ++ reftable/pq.h | 34 + reftable/reader.c | 744 +++++++++ reftable/reader.h | 65 + reftable/record.c | 1126 +++++++++++++ reftable/record.h | 143 ++ reftable/record_test.c | 410 +++++ reftable/refname.c | 209 +++ reftable/refname.h | 38 + reftable/refname_test.c | 99 ++ reftable/reftable-tests.h | 22 + reftable/reftable.c | 154 ++ reftable/reftable.h | 585 +++++++ reftable/reftable_test.c | 632 ++++++++ reftable/stack.c | 1225 ++++++++++++++ reftable/stack.h | 50 + reftable/stack_test.c | 787 +++++++++ reftable/strbuf.c | 206 +++ reftable/strbuf.h | 88 + reftable/strbuf_test.c | 39 + reftable/system.h | 53 + reftable/test_framework.c | 69 + reftable/test_framework.h | 64 + reftable/tree.c | 63 + reftable/tree.h | 34 + reftable/tree_test.c | 62 + reftable/update.sh | 19 + reftable/writer.c | 663 ++++++++ reftable/writer.h | 60 + reftable/zlib-compat.c | 92 ++ reftable/zlib.BUILD | 36 + repository.c | 2 + repository.h | 3 + sequencer.c | 56 +- setup.c | 10 +- t/helper/test-reftable.c | 20 + t/helper/test-tool.c | 2 + t/helper/test-tool.h | 2 + t/t0031-reftable.sh | 185 +++ t/t0033-debug-refs.sh | 18 + t/t1400-update-ref.sh | 30 +- t/t1405-main-ref-store.sh | 5 +- t/t1409-avoid-packing-refs.sh | 6 + t/t1450-fsck.sh | 6 + t/t3210-pack-refs.sh | 6 + t/test-lib.sh | 5 + wt-status.c | 6 +- 89 files changed, 13076 insertions(+), 256 deletions(-) create mode 100644 refs/debug.c create mode 100644 refs/reftable-backend.c create mode 100644 reftable/.gitattributes create mode 100644 reftable/BUILD create mode 100644 reftable/LICENSE create mode 100644 reftable/README.md create mode 100644 reftable/VERSION create mode 100644 reftable/WORKSPACE create mode 100644 reftable/basics.c create mode 100644 reftable/basics.h create mode 100644 reftable/block.c create mode 100644 reftable/block.h create mode 100644 reftable/block_test.c create mode 100644 reftable/compat.c create mode 100644 reftable/compat.h create mode 100644 reftable/constants.h create mode 100644 reftable/dump.c create mode 100644 reftable/file.c create mode 100644 reftable/iter.c create mode 100644 reftable/iter.h create mode 100644 reftable/merged.c create mode 100644 reftable/merged.h create mode 100644 reftable/merged_test.c create mode 100644 reftable/pq.c create mode 100644 reftable/pq.h create mode 100644 reftable/reader.c create mode 100644 reftable/reader.h create mode 100644 reftable/record.c create mode 100644 reftable/record.h create mode 100644 reftable/record_test.c create mode 100644 reftable/refname.c create mode 100644 reftable/refname.h create mode 100644 reftable/refname_test.c create mode 100644 reftable/reftable-tests.h create mode 100644 reftable/reftable.c create mode 100644 reftable/reftable.h create mode 100644 reftable/reftable_test.c create mode 100644 reftable/stack.c create mode 100644 reftable/stack.h create mode 100644 reftable/stack_test.c create mode 100644 reftable/strbuf.c create mode 100644 reftable/strbuf.h create mode 100644 reftable/strbuf_test.c create mode 100644 reftable/system.h create mode 100644 reftable/test_framework.c create mode 100644 reftable/test_framework.h create mode 100644 reftable/tree.c create mode 100644 reftable/tree.h create mode 100644 reftable/tree_test.c create mode 100755 reftable/update.sh create mode 100644 reftable/writer.c create mode 100644 reftable/writer.h create mode 100644 reftable/zlib-compat.c create mode 100644 reftable/zlib.BUILD create mode 100644 t/helper/test-reftable.c create mode 100755 t/t0031-reftable.sh create mode 100755 t/t0033-debug-refs.sh base-commit: e8ab941b671da6890181aea5b5755d1d9eea24ec Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-539%2Fhanwen%2Freftable-v20 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-539/hanwen/reftable-v20 Pull-Request: https://github.com/gitgitgadget/git/pull/539 Range-diff vs v19: 1: 596c316da4 < -: ---------- lib-t6000.sh: write tag using git-update-ref 2: 277da0cf7e < -: ---------- t3432: use git-reflog to inspect the reflog for HEAD 3: 125695ce92 < -: ---------- checkout: add '\n' to reflog message -: ---------- > 1: 05ced51683 refs: add \t to reflog in the files backend -: ---------- > 2: d0f0680c0e Split off reading loose ref data in separate function -: ---------- > 3: 716cb30c1b t1400: use git rev-parse for testing PSEUDOREF existence 4: 5715681b3f ! 4: 400ffe531f Write pseudorefs through ref backends. @@ Metadata Author: Han-Wen Nienhuys <hanwen@xxxxxxxxxx> ## Commit message ## - Write pseudorefs through ref backends. + Modify pseudo refs through ref backend storage - Pseudorefs store transient data in in the repository. Examples are HEAD, - CHERRY_PICK_HEAD, etc. + The previous behavior was introduced in commit 74ec19d4be + ("pseudorefs: create and use pseudoref update and delete functions", + Jul 31, 2015), with the justification "alternate ref backends still + need to store pseudorefs in GIT_DIR". - These refs have always been read through the ref backends, but they were written - in a one-off routine that wrote an object ID or symref directly into - .git/<pseudo_ref_name>. + Refs such as REBASE_HEAD are read through the ref backend. This can + only work consistently if they are written through the ref backend as + well. Tooling that works directly on files under .git should be + updated to use git commands to read refs instead. - This causes problems when introducing a new ref storage backend. To remedy this, - extend the ref backend implementation with a write_pseudoref_fn and - update_pseudoref_fn. + The following behaviors change: + + * Updates to pseudorefs (eg. ORIG_HEAD) with + core.logAllRefUpdates=always will create reflogs for the pseudoref. + + * non-HEAD pseudoref symrefs are also dereferenced on deletion. Update + t1405 accordingly. Signed-off-by: Han-Wen Nienhuys <hanwen@xxxxxxxxxx> - ## refs.c ## -@@ refs.c: int ref_exists(const char *refname) - return refs_ref_exists(get_main_ref_store(the_repository), refname); - } + ## Documentation/git-update-ref.txt ## +@@ Documentation/git-update-ref.txt: still see a subset of the modifications. + + LOGGING UPDATES + --------------- +-If config parameter "core.logAllRefUpdates" is true and the ref is one under +-"refs/heads/", "refs/remotes/", "refs/notes/", or the symbolic ref HEAD; or +-the file "$GIT_DIR/logs/<ref>" exists then `git update-ref` will append +-a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all +-symbolic refs before creating the log name) describing the change +-in ref value. Log lines are formatted as: ++If config parameter "core.logAllRefUpdates" is true and the ref is one ++under "refs/heads/", "refs/remotes/", "refs/notes/", or a pseudoref ++like HEAD or ORIG_HEAD; or the file "$GIT_DIR/logs/<ref>" exists then ++`git update-ref` will append a line to the log file ++"$GIT_DIR/logs/<ref>" (dereferencing all symbolic refs before creating ++the log name) describing the change in ref value. Log lines are ++formatted as: + + oldsha1 SP newsha1 SP committer LF -+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid) -+{ -+ return refs_delete_pseudoref(get_main_ref_store(the_repository), -+ pseudoref, old_oid); -+} -+ - static int filter_refs(const char *refname, const struct object_id *oid, - int flags, void *data) - { + + ## refs.c ## @@ refs.c: long get_files_ref_lock_timeout_ms(void) return timeout_ms; } @@ refs.c: long get_files_ref_lock_timeout_ms(void) - - return 0; -} - +- int refs_delete_ref(struct ref_store *refs, const char *msg, const char *refname, + const struct object_id *old_oid, @@ refs.c: int refs_delete_ref(struct ref_store *refs, const char *msg, + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; - if (ref_type(refname) == REF_TYPE_PSEUDOREF) { - assert(refs == get_main_ref_store(the_repository)); +- if (ref_type(refname) == REF_TYPE_PSEUDOREF) { +- assert(refs == get_main_ref_store(the_repository)); - return delete_pseudoref(refname, old_oid); -+ return refs_delete_pseudoref(refs, refname, old_oid); - } - +- } +- transaction = ref_store_transaction_begin(refs, &err); + if (!transaction || + ref_transaction_delete(transaction, refname, old_oid, @@ refs.c: int refs_update_ref(struct ref_store *refs, const char *msg, + struct strbuf err = STRBUF_INIT; + int ret = 0; - if (ref_type(refname) == REF_TYPE_PSEUDOREF) { - assert(refs == get_main_ref_store(the_repository)); +- if (ref_type(refname) == REF_TYPE_PSEUDOREF) { +- assert(refs == get_main_ref_store(the_repository)); - ret = write_pseudoref(refname, new_oid, old_oid, &err); -+ ret = refs_write_pseudoref(refs, refname, new_oid, old_oid, -+ &err); - } else { - t = ref_store_transaction_begin(refs, &err); - if (!t || -@@ refs.c: int head_ref(each_ref_fn fn, void *cb_data) - return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data); - } - -+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, struct strbuf *err) -+{ -+ return refs->be->write_pseudoref(refs, pseudoref, oid, old_oid, err); -+} -+ -+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref, -+ const struct object_id *old_oid) -+{ -+ return refs->be->delete_pseudoref(refs, pseudoref, old_oid); -+} -+ - struct ref_iterator *refs_ref_iterator_begin( - struct ref_store *refs, - const char *prefix, int trim, int flags) - - ## refs.h ## -@@ refs.h: int update_ref(const char *msg, const char *refname, - const struct object_id *new_oid, const struct object_id *old_oid, - unsigned int flags, enum action_on_err onerr); - -+/* Pseudorefs (eg. HEAD, CHERRY_PICK_HEAD) have a separate routines for updating -+ and deletion as they cannot take part in normal transactional updates. -+ Pseudorefs should only be written for the main repository. -+*/ -+int refs_write_pseudoref(struct ref_store *refs, const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, struct strbuf *err); -+int refs_delete_pseudoref(struct ref_store *refs, const char *pseudoref, -+ const struct object_id *old_oid); -+int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid); -+ - int parse_hide_refs_config(const char *var, const char *value, const char *); - - /* - - ## refs/files-backend.c ## -@@ refs/files-backend.c: static int lock_raw_ref(struct files_ref_store *refs, - return ret; - } - -+static int files_write_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, -+ struct strbuf *err) -+{ -+ struct files_ref_store *refs = -+ files_downcast(ref_store, REF_STORE_READ, "write_pseudoref"); -+ int fd; -+ struct lock_file lock = LOCK_INIT; -+ struct strbuf filename = STRBUF_INIT; -+ struct strbuf buf = STRBUF_INIT; -+ int ret = -1; -+ -+ if (!oid) -+ return 0; -+ -+ strbuf_addf(&filename, "%s/%s", refs->gitdir, pseudoref); -+ fd = hold_lock_file_for_update_timeout(&lock, filename.buf, 0, -+ get_files_ref_lock_timeout_ms()); -+ if (fd < 0) { -+ strbuf_addf(err, _("could not open '%s' for writing: %s"), -+ buf.buf, strerror(errno)); -+ goto done; -+ } -+ -+ if (old_oid) { -+ struct object_id actual_old_oid; -+ -+ if (read_ref(pseudoref, &actual_old_oid)) { -+ if (!is_null_oid(old_oid)) { -+ strbuf_addf(err, _("could not read ref '%s'"), -+ pseudoref); -+ rollback_lock_file(&lock); -+ goto done; -+ } -+ } else if (is_null_oid(old_oid)) { -+ strbuf_addf(err, _("ref '%s' already exists"), -+ pseudoref); -+ rollback_lock_file(&lock); -+ goto done; -+ } else if (!oideq(&actual_old_oid, old_oid)) { -+ strbuf_addf(err, -+ _("unexpected object ID when writing '%s'"), -+ pseudoref); -+ rollback_lock_file(&lock); -+ goto done; -+ } -+ } -+ -+ strbuf_addf(&buf, "%s\n", oid_to_hex(oid)); -+ if (write_in_full(fd, buf.buf, buf.len) < 0) { -+ strbuf_addf(err, _("could not write to '%s'"), filename.buf); -+ rollback_lock_file(&lock); -+ goto done; -+ } -+ -+ commit_lock_file(&lock); -+ ret = 0; -+done: -+ strbuf_release(&buf); -+ strbuf_release(&filename); -+ return ret; -+} -+ -+static int files_delete_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *old_oid) -+{ -+ struct files_ref_store *refs = -+ files_downcast(ref_store, REF_STORE_READ, "delete_pseudoref"); -+ struct strbuf filename = STRBUF_INIT; -+ int ret = -1; -+ -+ strbuf_addf(&filename, "%s/%s", refs->gitdir, pseudoref); -+ -+ if (old_oid && !is_null_oid(old_oid)) { -+ struct lock_file lock = LOCK_INIT; -+ int fd; -+ struct object_id actual_old_oid; -+ -+ fd = hold_lock_file_for_update_timeout( -+ &lock, filename.buf, 0, -+ get_files_ref_lock_timeout_ms()); -+ if (fd < 0) { -+ error_errno(_("could not open '%s' for writing"), -+ filename.buf); -+ goto done; -+ } -+ if (read_ref(pseudoref, &actual_old_oid)) -+ die(_("could not read ref '%s'"), pseudoref); -+ if (!oideq(&actual_old_oid, old_oid)) { -+ error(_("unexpected object ID when deleting '%s'"), -+ pseudoref); -+ rollback_lock_file(&lock); -+ goto done; -+ } -+ -+ unlink(filename.buf); -+ rollback_lock_file(&lock); -+ } else { -+ unlink(filename.buf); -+ } -+ ret = 0; -+done: -+ strbuf_release(&filename); -+ return ret; -+} -+ - struct files_ref_iterator { - struct ref_iterator base; - -@@ refs/files-backend.c: struct ref_storage_be refs_be_files = { - files_rename_ref, - files_copy_ref, - -+ files_write_pseudoref, -+ files_delete_pseudoref, -+ - files_ref_iterator_begin, - files_read_raw_ref, - -@@ refs/files-backend.c: struct ref_storage_be refs_be_files = { - files_reflog_exists, - files_create_reflog, - files_delete_reflog, -- files_reflog_expire -+ files_reflog_expire, - }; +- } else { +- t = ref_store_transaction_begin(refs, &err); +- if (!t || +- ref_transaction_update(t, refname, new_oid, old_oid, +- flags, msg, &err) || +- ref_transaction_commit(t, &err)) { +- ret = 1; +- ref_transaction_free(t); +- } ++ t = ref_store_transaction_begin(refs, &err); ++ if (!t || ++ ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, ++ &err) || ++ ref_transaction_commit(t, &err)) { ++ ret = 1; ++ ref_transaction_free(t); + } + if (ret) { + const char *str = _("update_ref failed for ref '%s': %s"); - ## refs/packed-backend.c ## -@@ refs/packed-backend.c: static int packed_copy_ref(struct ref_store *ref_store, - BUG("packed reference store does not support copying references"); - } - -+static int packed_write_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, -+ struct strbuf *err) -+{ -+ BUG("packed reference store does not support writing pseudo-references"); -+} -+ -+static int packed_delete_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *old_oid) -+{ -+ BUG("packed reference store does not support deleting pseudo-references"); -+} -+ - static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store) - { - return empty_ref_iterator_begin(); -@@ refs/packed-backend.c: struct ref_storage_be refs_be_packed = { - packed_rename_ref, - packed_copy_ref, - -+ packed_write_pseudoref, -+ packed_delete_pseudoref, -+ - packed_ref_iterator_begin, - packed_read_raw_ref, - -@@ refs/packed-backend.c: struct ref_storage_be refs_be_packed = { - packed_reflog_exists, - packed_create_reflog, - packed_delete_reflog, -- packed_reflog_expire -+ packed_reflog_expire, - }; + ## t/t1400-update-ref.sh ## +@@ t/t1400-update-ref.sh: test_expect_success 'core.logAllRefUpdates=always creates reflog by default' ' + git reflog exists $outside + ' + +-test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' ' ++test_expect_success 'core.logAllRefUpdates=always creates reflog for ORIG_HEAD' ' + test_config core.logAllRefUpdates always && + git update-ref ORIG_HEAD $A && +- test_must_fail git reflog exists ORIG_HEAD ++ git reflog exists ORIG_HEAD + ' + + test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' ' +@@ t/t1400-update-ref.sh: test_expect_success 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER + test_expect_success 'given old value for missing pseudoref, do not create' ' + test_must_fail git update-ref PSEUDOREF $A $B 2>err && + test_must_fail git rev-parse PSEUDOREF && +- test_i18ngrep "could not read ref" err ++ test_i18ngrep "unable to resolve reference" err + ' + + test_expect_success 'create pseudoref' ' +@@ t/t1400-update-ref.sh: test_expect_success 'overwrite pseudoref with correct old value' ' + test_expect_success 'do not overwrite pseudoref with wrong old value' ' + test_must_fail git update-ref PSEUDOREF $D $E 2>err && + test $C = $(git rev-parse PSEUDOREF) && +- test_i18ngrep "unexpected object ID" err ++ test_i18ngrep "cannot lock ref.*expected" err + ' + + test_expect_success 'delete pseudoref' ' +@@ t/t1400-update-ref.sh: test_expect_success 'do not delete pseudoref with wrong old value' ' + git update-ref PSEUDOREF $A && + test_must_fail git update-ref -d PSEUDOREF $B 2>err && + test $A = $(git rev-parse PSEUDOREF) && +- test_i18ngrep "unexpected object ID" err ++ test_i18ngrep "cannot lock ref.*expected" err + ' + + test_expect_success 'delete pseudoref with correct old value' ' - ## refs/refs-internal.h ## -@@ refs/refs-internal.h: typedef int copy_ref_fn(struct ref_store *ref_store, - const char *oldref, const char *newref, - const char *logmsg); - -+typedef int write_pseudoref_fn(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, -+ struct strbuf *err); -+ -+/* -+ * Deletes a pseudoref. Deletion always succeeds (even if the pseudoref doesn't -+ * exist.), except if old_oid is specified. If it is, it can fail due to lock -+ * failure, failure reading the old OID, or an OID mismatch -+ */ -+typedef int delete_pseudoref_fn(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *old_oid); -+ - /* - * Iterate over the references in `ref_store` whose names start with - * `prefix`. `prefix` is matched as a literal string, without regard -@@ refs/refs-internal.h: struct ref_storage_be { - rename_ref_fn *rename_ref; - copy_ref_fn *copy_ref; - -+ write_pseudoref_fn *write_pseudoref; -+ delete_pseudoref_fn *delete_pseudoref; -+ - ref_iterator_begin_fn *iterator_begin; - read_raw_ref_fn *read_raw_ref; - + ## t/t1405-main-ref-store.sh ## +@@ t/t1405-main-ref-store.sh: test_expect_success 'create_symref(FOO, refs/heads/master)' ' + test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' ' + git rev-parse FOO -- && + git rev-parse refs/tags/new-tag -- && +- $RUN delete-refs 0 nothing FOO refs/tags/new-tag && ++ m=$(git rev-parse master) && ++ REF_NO_DEREF=1 && ++ $RUN delete-refs $REF_NO_DEREF nothing FOO refs/tags/new-tag && ++ test_must_fail git rev-parse --symbolic-full-name FOO && + test_must_fail git rev-parse FOO -- && + test_must_fail git rev-parse refs/tags/new-tag -- + ' -: ---------- > 5: e9df96925b Make HEAD a PSEUDOREF rather than PER_WORKTREE. 5: c8cf2d2ce1 = 6: d33ea20fba Make refs_ref_exists public 6: e36b29de79 < -: ---------- Treat BISECT_HEAD as a pseudo ref 7: 1676df9851 ! 7: d12f6e905c Treat CHERRY_PICK_HEAD as a pseudo ref @@ Metadata ## Commit message ## Treat CHERRY_PICK_HEAD as a pseudo ref - Check for existence and delete CHERRY_PICK_HEAD through pseudo ref functions. + Check for existence and delete CHERRY_PICK_HEAD through ref functions. This will help cherry-pick work with alternate ref storage backends. Signed-off-by: Han-Wen Nienhuys <hanwen@xxxxxxxxxx> @@ sequencer.c: static void print_advice(struct repository *r, int show_hint, * of the commit itself so remove CHERRY_PICK_HEAD */ - unlink(git_path_cherry_pick_head(r)); -+ refs_delete_pseudoref(get_main_ref_store(r), "CHERRY_PICK_HEAD", -+ NULL); ++ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD", ++ NULL, 0); return; } @@ sequencer.c: static int do_commit(struct repository *r, strbuf_release(&sb); if (!res) { - unlink(git_path_cherry_pick_head(r)); -+ refs_delete_pseudoref(get_main_ref_store(r), -+ "CHERRY_PICK_HEAD", NULL); ++ refs_delete_ref(get_main_ref_store(r), "", ++ "CHERRY_PICK_HEAD", NULL, 0); unlink(git_path_merge_msg(r)); if (!is_rebase_i(opts)) print_commit_summary(r, NULL, &oid, @@ sequencer.c: static int do_pick_commit(struct repository *r, } else if (allow == 2) { drop_commit = 1; - unlink(git_path_cherry_pick_head(r)); -+ refs_delete_pseudoref(get_main_ref_store(r), "CHERRY_PICK_HEAD", -+ NULL); ++ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD", ++ NULL, 0); unlink(git_path_merge_msg(r)); fprintf(stderr, _("dropping %s %s -- patch contents already upstream\n"), @@ sequencer.c: void sequencer_post_commit_cleanup(struct repository *r, int verbos - if (file_exists(git_path_cherry_pick_head(r))) { - if (!unlink(git_path_cherry_pick_head(r)) && verbose) + if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) { -+ if (!refs_delete_pseudoref(get_main_ref_store(r), -+ "CHERRY_PICK_HEAD", NULL) && ++ if (!refs_delete_ref(get_main_ref_store(r), "", ++ "CHERRY_PICK_HEAD", NULL, 0) && + verbose) warning(_("cancelling a cherry picking in progress")); opts.action = REPLAY_PICK; @@ sequencer.c: static int do_merge(struct repository *r, strbuf_release(&ref_name); - unlink(git_path_cherry_pick_head(r)); -+ refs_delete_pseudoref(get_main_ref_store(r), "CHERRY_PICK_HEAD", -+ NULL); ++ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD", ++ NULL, 0); rollback_lock_file(&lock); rollback_lock_file(&lock); @@ sequencer.c: static int commit_staged_changes(struct repository *r, - if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + if (refs_ref_exists(get_main_ref_store(r), + "CHERRY_PICK_HEAD") && -+ refs_delete_pseudoref(get_main_ref_store(r), -+ "CHERRY_PICK_HEAD", NULL)) ++ refs_delete_ref(get_main_ref_store(r), "", ++ "CHERRY_PICK_HEAD", NULL, 0)) return error(_("could not remove CHERRY_PICK_HEAD")); if (!final_fixup) return 0; 8: 0a5f97ea34 ! 8: 224f7bf224 Treat REVERT_HEAD as a pseudo ref @@ sequencer.c: void sequencer_post_commit_cleanup(struct repository *r, int verbos - if (file_exists(git_path_revert_head(r))) { - if (!unlink(git_path_revert_head(r)) && verbose) + if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) { -+ if (!refs_delete_pseudoref(get_main_ref_store(r), "REVERT_HEAD", -+ NULL) && ++ if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD", ++ NULL, 0) && + verbose) warning(_("cancelling a revert in progress")); opts.action = REPLAY_REVERT; 9: 9463ed9093 = 9: 4b601b545c Move REF_LOG_ONLY to refs-internal.h 10: a116aebe11 ! 10: ea8a78374f Iterate over the "refs/" namespace in for_each_[raw]ref @@ Metadata Author: Han-Wen Nienhuys <hanwen@xxxxxxxxxx> ## Commit message ## - Iterate over the "refs/" namespace in for_each_[raw]ref + Iteration over entire ref namespace is iterating over "refs/" + + This changes behavior for in for_each_[raw]ref and + for_each_fullref_in_pattern. This happens implicitly in the files/packed ref backend; making it explicit simplifies adding alternate ref storage backends, such as @@ Commit message Signed-off-by: Han-Wen Nienhuys <hanwen@xxxxxxxxxx> + ## ref-filter.c ## +@@ ref-filter.c: static int for_each_fullref_in_pattern(struct ref_filter *filter, + { + struct string_list prefixes = STRING_LIST_INIT_DUP; + struct string_list_item *prefix; ++ const char *all = "refs/"; + int ret; + + if (!filter->match_as_path) { +@@ ref-filter.c: static int for_each_fullref_in_pattern(struct ref_filter *filter, + * prefixes like "refs/heads/" etc. are stripped off, + * so we have to look at everything: + */ +- return for_each_fullref_in("", cb, cb_data, broken); ++ return for_each_fullref_in(all, cb, cb_data, broken); + } + + if (filter->ignore_case) { +@@ ref-filter.c: static int for_each_fullref_in_pattern(struct ref_filter *filter, + * so just return everything and let the caller + * sort it out. + */ +- return for_each_fullref_in("", cb, cb_data, broken); ++ return for_each_fullref_in(all, cb, cb_data, broken); + } + + if (!filter->name_patterns[0]) { + /* no patterns; we have to look at everything */ +- return for_each_fullref_in("", cb, cb_data, broken); ++ return for_each_fullref_in(all, cb, cb_data, broken); + } + + find_longest_prefixes(&prefixes, filter->name_patterns); + ## refs.c ## @@ refs.c: static int do_for_each_ref(struct ref_store *refs, const char *prefix, 11: e4545658ed = 11: 0606b2a1c8 Add .gitattributes for the reftable/ directory 12: 169f6c7f54 = 12: 12d98125c2 Add reftable library 13: d155240b16 = 13: 70ce4ce49d Add standalone build infrastructure for reftable 14: 073bff7279 ! 14: 7af3b2340e Reftable support for git-core @@ builtin/init-db.c: int init_db(const char *git_dir, const char *real_git_dir, git_config_set("receive.denyNonFastforwards", "true"); } -+ git_config_set("extensions.refStorage", ref_storage_format); ++ if (!strcmp(ref_storage_format, "reftable")) ++ git_config_set("extensions.refStorage", ref_storage_format); + if (!(flags & INIT_DB_QUIET)) { int len = strlen(git_dir); @@ cache.h: int path_inside_repo(const char *prefix, const char *path); void sanitize_stdfds(void); int daemonize(void); @@ cache.h: struct repository_format { + int is_bare; int hash_algo; - int has_extensions; char *work_tree; + char *ref_storage; struct string_list unknown_extensions; + struct string_list v1_only_extensions; }; - ## refs.c ## @@ @@ refs.c: struct ref_store *get_main_ref_store(struct repository *r) - 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(), ++ r->ref_storage_format : ++ default_ref_storage(), + REF_STORE_ALL_CAPS); return r->refs_private; } @@ refs/reftable-backend.c (new) + struct reftable_addition *add = NULL; + struct reftable_stack *stack = + transaction->nr ? -+ stack_for(refs, transaction->updates[0]->refname) : -+ refs->main_stack; ++ stack_for(refs, transaction->updates[0]->refname) : ++ refs->main_stack; + int err = refs->err; + if (err < 0) { + goto done; @@ refs/reftable-backend.c (new) + return git_reftable_transaction_finish(ref_store, transaction, errmsg); +} + -+struct write_pseudoref_arg { -+ struct reftable_stack *stack; -+ const char *pseudoref; -+ const struct object_id *new_oid; -+ const struct object_id *old_oid; -+}; -+ -+static int write_pseudoref_table(struct reftable_writer *writer, void *argv) -+{ -+ struct write_pseudoref_arg *arg = (struct write_pseudoref_arg *)argv; -+ uint64_t ts = reftable_stack_next_update_index(arg->stack); -+ int err = 0; -+ struct reftable_ref_record read_ref = { NULL }; -+ struct reftable_ref_record write_ref = { NULL }; -+ -+ reftable_writer_set_limits(writer, ts, ts); -+ if (arg->old_oid) { -+ struct object_id read_oid; -+ err = reftable_stack_read_ref(arg->stack, arg->pseudoref, -+ &read_ref); -+ if (err < 0) -+ goto done; -+ -+ if ((err > 0) != is_null_oid(arg->old_oid)) { -+ err = REFTABLE_LOCK_ERROR; -+ goto done; -+ } -+ -+ /* XXX If old_oid is set, and we have a symref? */ -+ -+ if (err == 0 && read_ref.value == NULL) { -+ err = REFTABLE_LOCK_ERROR; -+ goto done; -+ } -+ -+ hashcpy(read_oid.hash, read_ref.value); -+ if (!oideq(arg->old_oid, &read_oid)) { -+ err = REFTABLE_LOCK_ERROR; -+ goto done; -+ } -+ } -+ -+ write_ref.refname = (char *)arg->pseudoref; -+ write_ref.update_index = ts; -+ if (!is_null_oid(arg->new_oid)) -+ write_ref.value = (uint8_t *)arg->new_oid->hash; -+ -+ err = reftable_writer_add_ref(writer, &write_ref); -+done: -+ assert(err != REFTABLE_API_ERROR); -+ reftable_ref_record_clear(&read_ref); -+ return err; -+} -+ -+static int git_reftable_write_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, -+ struct strbuf *errbuf) -+{ -+ struct git_reftable_ref_store *refs = -+ (struct git_reftable_ref_store *)ref_store; -+ struct reftable_stack *stack = stack_for(refs, pseudoref); -+ struct write_pseudoref_arg arg = { -+ .stack = stack, -+ .pseudoref = pseudoref, -+ .new_oid = oid, -+ }; -+ struct reftable_addition *add = NULL; -+ int err = refs->err; -+ if (err < 0) { -+ goto done; -+ } -+ -+ err = reftable_stack_reload(stack); -+ if (err) { -+ goto done; -+ } -+ err = reftable_stack_new_addition(&add, stack); -+ if (err) { -+ goto done; -+ } -+ if (old_oid) { -+ struct object_id actual_old_oid; -+ -+ /* XXX this is cut & paste from files-backend - should factor -+ * out? */ -+ if (read_ref(pseudoref, &actual_old_oid)) { -+ if (!is_null_oid(old_oid)) { -+ strbuf_addf(errbuf, -+ _("could not read ref '%s'"), -+ pseudoref); -+ goto done; -+ } -+ } else if (is_null_oid(old_oid)) { -+ strbuf_addf(errbuf, _("ref '%s' already exists"), -+ pseudoref); -+ goto done; -+ } else if (!oideq(&actual_old_oid, old_oid)) { -+ strbuf_addf(errbuf, -+ _("unexpected object ID when writing '%s'"), -+ pseudoref); -+ goto done; -+ } -+ } -+ -+ err = reftable_addition_add(add, &write_pseudoref_table, &arg); -+ if (err < 0) { -+ strbuf_addf(errbuf, "reftable: pseudoref update failure: %s", -+ reftable_error_str(err)); -+ } -+ -+ err = reftable_addition_commit(add); -+ if (err < 0) { -+ strbuf_addf(errbuf, "reftable: pseudoref commit failure: %s", -+ reftable_error_str(err)); -+ } -+ -+done: -+ assert(err != REFTABLE_API_ERROR); -+ reftable_addition_destroy(add); -+ return err; -+} -+ -+static int reftable_delete_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *old_oid) -+{ -+ struct strbuf errbuf = STRBUF_INIT; -+ int ret = git_reftable_write_pseudoref(ref_store, pseudoref, &null_oid, -+ old_oid, &errbuf); -+ /* XXX what to do with the error message? */ -+ strbuf_release(&errbuf); -+ return ret; -+} -+ +struct write_delete_refs_arg { + struct reftable_stack *stack; + struct string_list *refnames; @@ refs/reftable-backend.c (new) +static int git_reftable_reflog_exists(struct ref_store *ref_store, + const char *refname) +{ -+ /* always exists. */ -+ return 1; ++ struct reftable_iterator it = { NULL }; ++ struct git_reftable_ref_store *refs = ++ (struct git_reftable_ref_store *)ref_store; ++ struct reftable_stack *stack = stack_for(refs, refname); ++ struct reftable_merged_table *mt = reftable_stack_merged_table(stack); ++ struct reftable_log_record log = { NULL }; ++ int err = refs->err; ++ ++ if (err < 0) { ++ goto done; ++ } ++ err = reftable_merged_table_seek_log(mt, &it, refname); ++ if (err) { ++ goto done; ++ } ++ err = reftable_iterator_next_log(&it, &log); ++ if (err) { ++ goto done; ++ } ++ ++ if (strcmp(log.refname, refname)) { ++ err = 1; ++ } ++ ++done: ++ reftable_iterator_destroy(&it); ++ reftable_log_record_clear(&log); ++ return !err; +} + +static int git_reftable_create_reflog(struct ref_store *ref_store, @@ refs/reftable-backend.c (new) + git_reftable_rename_ref, + git_reftable_copy_ref, + -+ git_reftable_write_pseudoref, -+ reftable_delete_pseudoref, -+ + git_reftable_ref_iterator_begin, + git_reftable_read_raw_ref, + @@ repository.h: struct repository { */ ## setup.c ## -@@ setup.c: static int check_repo_format(const char *var, const char *value, void *vdata) - if (!value) - return config_error_nonbool(var); - data->partial_clone = xstrdup(value); -- } else if (!strcmp(ext, "worktreeconfig")) -+ } else if (!strcmp(ext, "worktreeconfig")) { - data->worktree_config = git_config_bool(var, value); -- else -+ } else if (!strcmp(ext, "refstorage")) { -+ data->ref_storage = xstrdup(value); -+ } else - string_list_append(&data->unknown_extensions, ext); +@@ setup.c: static enum extension_result handle_extension(const char *var, + { + if (!strcmp(ext, "noop-v1")) { + return EXTENSION_OK; ++ } else if (!strcmp(ext, "refstorage")) { ++ data->ref_storage = xstrdup(value); ++ return EXTENSION_OK; } +- + return EXTENSION_UNKNOWN; + } @@ setup.c: void clear_repository_format(struct repository_format *format) - string_list_clear(&format->unknown_extensions, 0); + string_list_clear(&format->v1_only_extensions, 0); free(format->work_tree); free(format->partial_clone); + free(format->ref_storage); -: ---------- > 15: a6449d4346 Read FETCH_HEAD as loose ref 15: 04c86e7395 ! 16: f13e4798c4 Hookup unittests for the reftable library. @@ Makefile: t/helper/test-svn-fe$X: $(VCSSVN_LIB) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) check-sha1:: t/helper/test-tool$X +@@ Makefile: cocciclean: + clean: profile-clean coverage-clean cocciclean + $(RM) *.res + $(RM) $(OBJECTS) +- $(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(REFTABLE_LIB) ++ $(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB) + $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X + $(RM) $(TEST_PROGRAMS) + $(RM) $(FUZZ_PROGRAMS) ## t/helper/test-reftable.c (new) ## @@ 16: c751265705 ! 17: 679dc37871 Add GIT_DEBUG_REFS debugging mechanism @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam strbuf_reset(&sb); - if (get_main_ref_store(the_repository)->be == &refs_be_reftable) { + -+ /* XXX: check GIT_TEST_REFTABLE because GIT_DEBUG_REFS obscures the -+ * instance type. */ -+ if (get_main_ref_store(the_repository)->be == &refs_be_reftable || -+ getenv("GIT_TEST_REFTABLE") != NULL) { ++ if (!strcmp(ref_store_backend_name(get_main_ref_store(the_repository)), ++ "reftable")) { /* XXX this is cut & paste from reftable_init_db. */ strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); write_file(sb.buf, "%s", "ref: refs/heads/.invalid\n"); ## refs.c ## +@@ refs.c: int ref_storage_backend_exists(const char *name) + return find_ref_storage_backend(name) != NULL; + } + ++const char *ref_store_backend_name(struct ref_store *refs) ++{ ++ return refs->be->name; ++} ++ + /* + * How to handle various characters in refnames: + * 0: An acceptable character for refs @@ refs.c: struct ref_store *get_main_ref_store(struct repository *r) - r->ref_storage_format : - default_ref_storage(), + r->ref_storage_format : + default_ref_storage(), REF_STORE_ALL_CAPS); + if (getenv("GIT_DEBUG_REFS")) { + r->refs_private = debug_wrap(r->gitdir, r->refs_private); @@ refs.c: struct ref_store *get_main_ref_store(struct repository *r) } + ## refs.h ## +@@ refs.h: int reflog_expire(const char *refname, const struct object_id *oid, + int ref_storage_backend_exists(const char *name); + + struct ref_store *get_main_ref_store(struct repository *r); ++const char *ref_store_backend_name(struct ref_store *refs); + + /** + * Submodules + ## refs/debug.c (new) ## @@ + @@ refs/debug.c (new) +struct ref_store *debug_wrap(const char *gitdir, struct ref_store *store) +{ + struct debug_ref_store *res = malloc(sizeof(struct debug_ref_store)); ++ struct ref_storage_be *be_copy = malloc(sizeof(*be_copy)); ++ *be_copy = refs_be_debug; ++ be_copy->name = ref_store_backend_name(store); + fprintf(stderr, "ref_store for %s\n", gitdir); + res->refs = store; -+ base_ref_store_init((struct ref_store *)res, &refs_be_debug); ++ base_ref_store_init((struct ref_store *)res, be_copy); + return (struct ref_store *)res; +} + @@ refs/debug.c (new) + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = + drefs->refs->be->delete_refs(drefs->refs, msg, refnames, flags); ++ int i = 0; ++ fprintf(stderr, "delete_refs {\n"); ++ for (i = 0; i < refnames->nr; i++) ++ fprintf(stderr, "%s\n", refnames->items[i].string); ++ fprintf(stderr, "}: %d\n", res); + return res; +} + @@ refs/debug.c (new) + return res; +} + -+static int debug_write_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *oid, -+ const struct object_id *old_oid, -+ struct strbuf *err) -+{ -+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; -+ int res = drefs->refs->be->write_pseudoref(drefs->refs, pseudoref, oid, -+ old_oid, err); -+ char o[100] = "null"; -+ char n[100] = "null"; -+ if (oid) -+ oid_to_hex_r(o, oid); -+ if (old_oid) -+ oid_to_hex_r(n, old_oid); -+ fprintf(stderr, "write_pseudoref: %s, %s => %s, err %s: %d\n", -+ pseudoref, o, n, err->buf, res); -+ return res; -+} -+ -+static int debug_delete_pseudoref(struct ref_store *ref_store, -+ const char *pseudoref, -+ const struct object_id *old_oid) -+{ -+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; -+ int res = drefs->refs->be->delete_pseudoref(drefs->refs, pseudoref, -+ old_oid); -+ char hex[100] = "null"; -+ if (old_oid) -+ oid_to_hex_r(hex, old_oid); -+ fprintf(stderr, "delete_pseudoref: %s (%s): %d\n", pseudoref, hex, res); -+ return res; -+} -+ +struct debug_ref_iterator { + struct ref_iterator base; + struct ref_iterator *iter; @@ refs/debug.c (new) + debug_rename_ref, + debug_copy_ref, + -+ debug_write_pseudoref, -+ debug_delete_pseudoref, -+ + debug_ref_iterator_begin, + debug_read_raw_ref, + @@ refs/debug.c (new) +}; ## refs/refs-internal.h ## -@@ refs/refs-internal.h: struct ref_store { +@@ refs/refs-internal.h: int parse_loose_ref_contents(const char *buf, struct object_id *oid, void base_ref_store_init(struct ref_store *refs, const struct ref_storage_be *be); 17: ef0dd45f07 = 18: 9e6cdbe204 vcxproj: adjust for the reftable changes 18: d4dc1deae5 = 19: 9cd73da381 git-prompt: prepare for reftable refs backend 19: 5a85abf9c4 = 20: a2e5b3082e Add reftable testing infrastructure 20: 0ebf7beb95 = 21: 539fa0935f Add "test-tool dump-reftable" command. -- gitgitgadget