Refs cleanup
cover Refs cleanup
This patch series contains preliminary cleanups for adding reftable support to git-core
Han-Wen Nienhuys (5):
refs.h: clarify reflog iteration order
t: use update-ref and show-ref to reading/writing refs
refs: improve documentation for ref iterator
reftable: clarify how empty tables should be written
reftable: define version 2 of the spec to accomodate SHA256
Jonathan Nieder (1):
reftable: file format documentation
Documentation/Makefile | 1 + Documentation/technical/reftable.txt | 1083 ++++++++++++++++++++++++++ refs.h | 18 +- refs/refs-internal.h | 18 +- t/t0002-gitfile.sh | 2 +- t/t1400-update-ref.sh | 32 +- t/t1506-rev-parse-diagnosis.sh | 2 +- t/t6050-replace.sh | 2 +- t/t9020-remote-svn.sh | 4 +- 9 files changed, 1137 insertions(+), 25 deletions(-) create mode 100644 Documentation/technical/reftable.txt
base-commit: b34789c0b0d3b137f0bb516b417bd8d75e0cb306
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-638%2Fhanwen%2Frefs-cleanup-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-638/hanwen/refs-cleanup-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/638
Signature · 1 line
gitgitgadget
PATCH 1/6 refs.h: clarify reflog iteration order
From: Han-Wen Nienhuys <[email protected]>
--- refs.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
refs.h +17 -1
@@ -432,19 +432,35 @@ int delete_refs(const char *msg, struct string_list *refnames,int refs_delete_reflog (struct ref_store *refs,const char *refname);int delete_reflog (const char *refname); -/* iterate over reflog entries */ +/* +* Callback to process a reflog entry found by the iteration functions (see +* below) +*/ typedef int each_reflog_ent_fn (struct object_id *old_oid,struct object_id *new_oid,const char *committer,timestamp_t timestamp,int tz,const char *msg,void *cb_data); +/* Iterate over reflog entries in the log for `refname`. */ + +/* oldest entry first */ int refs_for_each_reflog_ent (struct ref_store *refs,const char *refname,each_reflog_ent_fn fn,void *cb_data); + +/* youngest entry first */ int refs_for_each_reflog_ent_reverse (struct ref_store *refs,const char *refname,each_reflog_ent_fn fn,void *cb_data); + +/* +* Iterate over reflog entries in the log for `refname` in the main ref store. +*/ + +/* oldest entry first */ int for_each_reflog_ent (const char *refname,each_reflog_ent_fn fn,void *cb_data); + +/* youngest entry first */ int for_each_reflog_ent_reverse (const char *refname,each_reflog_ent_fn fn,void *cb_data);/*
Signature · 1 line
gitgitgadget
PATCH 2/6 t: use update-ref and show-ref to reading/writing refs
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
Reading and writing .git/refs/* assumes that refs are stored in the 'files' ref backend.
--- t/t0002-gitfile.sh | 2 +- t/t1400-update-ref.sh | 32 ++++++++++++++++---------------- t/t1506-rev-parse-diagnosis.sh | 2 +- t/t6050-replace.sh | 2 +- t/t9020-remote-svn.sh | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-)
t/t0002-gitfile.sh +1 -1
@@ -62,7 +62,7 @@ test_expect_success 'check commit-tree' ' ' test_expect_success 'check rev-list' ' - echo $SHA >"$REAL/HEAD" && + git update-ref "HEAD" "$SHA" && test "$SHA" = "$(git rev-list HEAD)" '
t/t1400-update-ref.sh +16 -16
@@ -37,15 +37,15 @@ test_expect_success setup ' test_expect_success "create $m" ' git update-ref $m $A && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' test_expect_success "create $m with oldvalue verification" ' git update-ref $m $B $A && - test $B = $(cat .git/$m) + test $B = $(git show-ref -s --verify $m) ' test_expect_success "fail to delete $m with stale ref" ' test_must_fail git update-ref -d $m $A && - test $B = "$(cat .git/$m)" + test $B = "$(git show-ref -s --verify $m)" ' test_expect_success "delete $m" ' test_when_finished "rm -f .git/$m" &&
@@ -56,7 +56,7 @@ test_expect_success "delete $m" ' test_expect_success "delete $m without oldvalue verification" ' test_when_finished "rm -f .git/$m" && git update-ref $m $A && - test $A = $(cat .git/$m) && + test $A = $(git show-ref -s --verify $m) && git update-ref -d $m && test_path_is_missing .git/$m '
@@ -69,15 +69,15 @@ test_expect_success "fail to create $n" ' test_expect_success "create $m (by HEAD)" ' git update-ref HEAD $A && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' test_expect_success "create $m (by HEAD) with oldvalue verification" ' git update-ref HEAD $B $A && - test $B = $(cat .git/$m) + test $B = $(git show-ref -s --verify $m) ' test_expect_success "fail to delete $m (by HEAD) with stale ref" ' test_must_fail git update-ref -d HEAD $A && - test $B = $(cat .git/$m) + test $B = $(git show-ref -s --verify $m) ' test_expect_success "delete $m (by HEAD)" ' test_when_finished "rm -f .git/$m" &&
@@ -178,14 +178,14 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' test_expect_success "create $m (by HEAD)" ' git update-ref HEAD $A && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' test_expect_success 'pack refs' ' git pack-refs --all ' test_expect_success "move $m (by HEAD)" ' git update-ref HEAD $B $A && - test $B = $(cat .git/$m) + test $B = $(git show-ref -s --verify $m) ' test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" ' test_when_finished "rm -f .git/$m" &&
@@ -255,7 +255,7 @@ test_expect_success '(not) change HEAD with wrong SHA1' ' ' test_expect_success "(not) changed .git/$m" ' test_when_finished "rm -f .git/$m" && - ! test $B = $(cat .git/$m) + ! test $B = $(git show-ref -s --verify $m) ' rm -f .git/logs/refs/heads/master
@@ -263,19 +263,19 @@ test_expect_success "create $m (logged by touch)" ' test_config core.logAllRefUpdates false && GIT_COMMITTER_DATE="2005-05-26 23:30" \ git update-ref --create-reflog HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' test_expect_success "update $m (logged by touch)" ' test_config core.logAllRefUpdates false && GIT_COMMITTER_DATE="2005-05-26 23:31" \ git update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m) + test $B = $(git show-ref -s --verify $m) ' test_expect_success "set $m (logged by touch)" ' test_config core.logAllRefUpdates false && GIT_COMMITTER_DATE="2005-05-26 23:41" \ git update-ref HEAD $A && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' test_expect_success 'empty directory removal' '
@@ -319,19 +319,19 @@ test_expect_success "create $m (logged by config)" ' test_config core.logAllRefUpdates true && GIT_COMMITTER_DATE="2005-05-26 23:32" \ git update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' test_expect_success "update $m (logged by config)" ' test_config core.logAllRefUpdates true && GIT_COMMITTER_DATE="2005-05-26 23:33" \ git update-ref HEAD'" $B $A "'-m "Switch" && - test $B = $(cat .git/$m) + test $B = $(git show-ref -s --verify $m) ' test_expect_success "set $m (logged by config)" ' test_config core.logAllRefUpdates true && GIT_COMMITTER_DATE="2005-05-26 23:43" \ git update-ref HEAD $A && - test $A = $(cat .git/$m) + test $A = $(git show-ref -s --verify $m) ' cat >expect <<EOF
t/t1506-rev-parse-diagnosis.sh +1 -1
@@ -207,7 +207,7 @@ test_expect_success 'arg before dashdash must be a revision (ambiguous)' ' { # we do not want to use rev-parse here, because # we are testing it - cat .git/refs/heads/foobar && + git show-ref -s refs/heads/foobar && printf "%s\n" -- } >expect && git rev-parse foobar -- >actual &&
t/t6050-replace.sh +1 -1
@@ -135,7 +135,7 @@ test_expect_success 'tag replaced commit' ' test_expect_success '"git fsck" works' ' git fsck master >fsck_master.out && test_i18ngrep "dangling commit $R" fsck_master.out && - test_i18ngrep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out && + test_i18ngrep "dangling tag $(git show-ref -s refs/tags/mytag)" fsck_master.out && test -z "$(git fsck)" '
t/t9020-remote-svn.sh +2 -2
@@ -48,8 +48,8 @@ test_expect_success REMOTE_SVN 'simple fetch' ' ' test_debug ' - cat .git/refs/svn/svnsim/master - cat .git/refs/remotes/svnsim/master + git show-ref -s refs/svn/svnsim/master + git show-ref -s refs/remotes/svnsim/master ' test_expect_success REMOTE_SVN 'repeated fetch, nothing shall change' '
Signature · 1 line
gitgitgadget
PATCH 3/6 refs: improve documentation for ref iterator
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
Document some of the flag options in refs_ref_iterator_begin, and explain how ref_iterator_advance_fn should handle them.
--- refs/refs-internal.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-)
refs/refs-internal.h +15 -3
@@ -347,9 +347,13 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator);/* * Return an iterator that goes over each reference in `refs` for * which the refname begins with prefix. If trim is non-zero, then -* trim that many characters off the beginning of each refname. flags -* can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in -* the iteration. The output is ordered by refname. +* trim that many characters off the beginning of each refname. +* The output is ordered by refname. The following flags are supported: +* +* DO_FOR_EACH_INCLUDE_BROKEN: include broken references in +* the iteration. +* +* DO_FOR_EACH_PER_WORKTREE_ONLY: only produce REF_TYPE_PER_WORKTREE refs. */ struct ref_iterator *refs_ref_iterator_begin( struct ref_store *refs,
@@ -438,6 +442,14 @@ void base_ref_iterator_free(struct ref_iterator *iter);/* Virtual function declarations for ref_iterators: */ +/* +* backend-specific implementation of ref_iterator_advance. For symrefs, the +* function should set REF_ISSYMREF, and it should also dereference the symref +* to provide the OID referent. If DO_FOR_EACH_INCLUDE_BROKEN is set, symrefs +* with non-existent referents and refs pointing to non-existent object names +* should also be returned. If DO_FOR_EACH_PER_WORKTREE_ONLY, only +* REF_TYPE_PER_WORKTREE refs should be returned. +*/ typedef int ref_iterator_advance_fn (struct ref_iterator *ref_iterator); typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,
Signature · 1 line
gitgitgadget
PATCH 4/6 reftable: file format documentation
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Jonathan Nieder <[email protected]>
Shawn Pearce explains:
Some repositories contain a lot of references (e.g. android at 866k, rails at 31k). The reftable format provides:
- Near constant time lookup for any single reference, even when the repository is cold and not in process or kernel cache.
- Near constant time verification if a SHA-1 is referred to by at least one reference (for allow-tip-sha1-in-want).
- Efficient lookup of an entire namespace, such as refs/tags/.
- Support atomic push O(size_of_update) operations.
- Combine reflog storage with ref storage.
This file format spec was originally written in July, 2017 by Shawn Pearce. Some refinements since then were made by Shawn and by Han-Wen Nienhuys based on experiences implementing and experimenting with the format. (All of this was in the context of our work at Google and Google is happy to contribute the result to the Git project.)
Imported from JGit[1]'s current version (c217d33ff, "Documentation/technical/reftable: improve repo layout", 2020-02-04) of Documentation/technical/reftable.md and converted to asciidoc by running
pandoc -t asciidoc -f markdown reftable.md >reftable.txt
using pandoc 2.2.1. The result required the following additional minor changes:
- removed the [TOC] directive to add a table of contents, since asciidoc does not support it
- replaced git-scm.com/docs links with linkgit: directives that link to other pages within Git's documentation
[1] https://eclipse.googlesource.com/jgit/jgit
--- Documentation/Makefile | 1 + Documentation/technical/reftable.txt | 1069 ++++++++++++++++++++++++++ 2 files changed, 1070 insertions(+) create mode 100644 Documentation/technical/reftable.txt
Documentation/Makefile +1 -0
@@ -93,6 +93,7 @@ TECH_DOCS += technical/protocol-capabilities TECH_DOCS += technical/protocol-common TECH_DOCS += technical/protocol-v2 TECH_DOCS += technical/racy-git +TECH_DOCS += technical/reftable TECH_DOCS += technical/send-pack-pipeline TECH_DOCS += technical/shallow TECH_DOCS += technical/signature-format
Documentation/technical/reftable.txt new file +1069 -0
1,069 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH 5/6 reftable: clarify how empty tables should be written
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
The format allows for some ambiguity, as a lone footer also starts with a valid file header. However, the current JGit code will barf on this. This commit codifies this behavior into the standard.
--- Documentation/technical/reftable.txt | 6 ++++++ 1 file changed, 6 insertions(+)
Documentation/technical/reftable.txt +6 -0
@@ -733,6 +733,12 @@ version) Once verified, the other fields of the footer can be accessed. +Empty tables +++++++++++++ + +A reftable may be empty. In this case, the file starts with a header +and is immediately followed by a footer. + Binary search ^^^^^^^^^^^^^
Signature · 1 line
gitgitgadget
PATCH 6/6 reftable: define version 2 of the spec to accomodate SHA256
In reply to Han-Wen Nienhuys via GitGitGadgetFrom: Han-Wen Nienhuys <[email protected]>
Version appends a hash ID to the file header, making it slightly larger.
This commit also changes "SHA-1" into "object ID" in many places.
--- Documentation/technical/reftable.txt | 82 +++++++++++++++------------- 1 file changed, 45 insertions(+), 37 deletions(-)
Documentation/technical/reftable.txt +45 -37
@@ -30,7 +30,7 @@ Objectives * Near constant time lookup for any single reference, even when the repository is cold and not in process or kernel cache. -* Near constant time verification if a SHA-1 is referred to by at least +* Near constant time verification if an object name is referred to by at least one reference (for allow-tip-sha1-in-want). * Efficient enumeration of an entire namespace, such as `refs/tags/`. * Support atomic push with `O(size_of_update)` operations.
@@ -210,8 +210,8 @@ and non-aligned files. Very small files (e.g. a single ref block) may omit `padding` and the ref index to reduce total file size. -Header -^^^^^^ +Header (version 1) +^^^^^^^^^^^^^^^^^^ A 24-byte header appears at the beginning of the file:
@@ -232,6 +232,26 @@ used in a stack for link:#Update-transactions[transactions], these fields can order the files such that the prior file's `max_update_index + 1` is the next file's `min_update_index`. +Header (version 2) +^^^^^^^^^^^^^^^^^^ + +A 28-byte header appears at the beginning of the file: + +.... +'REFT' +uint8( version_number = 2 ) +uint24( block_size ) +uint64( min_update_index ) +uint64( max_update_index ) +uint32( hash_id ) +.... + +The header is identical to `version_number=1`, with the 4-byte hash ID +("sha1" for SHA1 and "s256" for SHA-256) append to the header. + +For maximum backward compatibility, it is recommended to use version 1 when +writing SHA1 reftables. + First ref block ^^^^^^^^^^^^^^^
@@ -319,8 +339,8 @@ The `value` follows. Its format is determined by `value_type`, one of the following: * `0x0`: deletion; no value data (see transactions, below) -* `0x1`: one 20-byte object name; value of the ref -* `0x2`: two 20-byte object names; value of the ref, peeled target +* `0x1`: one object name; value of the ref +* `0x2`: two object names; value of the ref, peeled target * `0x3`: symbolic reference: `varint( target_len ) target` Symbolic references use `0x3`, followed by the complete name of the
@@ -421,9 +441,9 @@ Obj block format ^^^^^^^^^^^^^^^^ Object blocks are optional. Writers may choose to omit object blocks, -especially if readers will not use the SHA-1 to ref mapping. +especially if readers will not use the object name to ref mapping. -Object blocks use unique, abbreviated 2-20 byte SHA-1 keys, mapping to +Object blocks use unique, abbreviated 2-32 object name keys, mapping to ref blocks containing references pointing to that object directly, or as the peeled value of an annotated tag. Like ref blocks, object blocks use the file's standard block size. The abbrevation length is available in
@@ -432,7 +452,7 @@ the footer as `obj_id_len`. To save space in small files, object blocks may be omitted if the ref index is not present, as brute force search will only need to read a few ref blocks. When missing, readers should brute force a linear search of -all references to lookup by SHA-1. +all references to lookup by object name. An object block is written as:
@@ -449,9 +469,9 @@ padding? Fields are identical to ref block. Binary search using the restart table works the same as in reference blocks. -Because object names are abbreviated by writers to the shortest -unique abbreviation within the reftable, obj key lengths are variable -between 2 and 20 bytes. Readers must compare only for common prefix +Because object names are abbreviated by writers to the shortest unique +abbreviation within the reftable, obj key lengths have a variable length. Their +length must be at least 2 bytes. Readers must compare only for common prefix match within an obj block or obj index. obj record
@@ -504,9 +524,9 @@ for (j = 1; j < position_count; j++) { .... With a position in hand, a reader must linearly scan the ref block, -starting from the first `ref_record`, testing each reference's SHA-1s +starting from the first `ref_record`, testing each reference's object names (for `value_type = 0x1` or `0x2`) for full equality. Faster searching by -SHA-1 within a single ref block is not supported by the reftable format. +object name within a single ref block is not supported by the reftable format. Smaller block sizes reduce the number of candidates this step must consider.
@@ -621,7 +641,7 @@ reflogs must treat this as a deletion. For `log_type = 0x1`, the `log_data` section follows linkgit:git-update-ref[1] logging and includes: -* two 20-byte SHA-1s (old id, new id) +* two object names (old id, new id) * varint string of committer's name * varint string of committer's email * varint time in seconds since epoch (Jan 1, 1970)
@@ -689,14 +709,8 @@ Footer After the last block of the file, a file footer is written. It begins like the file header, but is extended with additional data. -A 68-byte footer appears at the end: - .... - 'REFT' - uint8( version_number = 1 ) - uint24( block_size ) - uint64( min_update_index ) - uint64( max_update_index ) + HEADER uint64( ref_index_position ) uint64( (obj_position << 5) | obj_id_len )
@@ -719,12 +733,16 @@ obj blocks. * `obj_index_position`: byte position for the start of the obj index. * `log_index_position`: byte position for the start of the log index. +The size of the footer is 68 bytes for version 1, and 72 bytes for +version 2. + Reading the footer ++++++++++++++++++ -Readers must seek to `file_length - 68` to access the footer. A trusted -external source (such as `stat(2)`) is necessary to obtain -`file_length`. When reading the footer, readers must verify: +Readers must first read the file start to determine the version +number. Then they seek to `file_length - FOOTER_LENGTH` to access the +footer. A trusted external source (such as `stat(2)`) is necessary to +obtain `file_length`. When reading the footer, readers must verify: * 4-byte magic is correct * 1-byte version number is recognized
@@ -780,11 +798,11 @@ Lightweight refs dominate ^^^^^^^^^^^^^^^^^^^^^^^^^ The reftable format assumes the vast majority of references are single -SHA-1 valued with common prefixes, such as Gerrit Code Review's +object names valued with common prefixes, such as Gerrit Code Review's `refs/changes/` namespace, GitHub's `refs/pulls/` namespace, or many lightweight tags in the `refs/tags/` namespace. -Annotated tags storing the peeled object cost an additional 20 bytes per +Annotated tags storing the peeled object cost an additional object name per reference. Low overhead
@@ -817,7 +835,7 @@ Scans and lookups dominate Scanning all references and lookup by name (or namespace such as `refs/heads/`) are the most common activities performed on repositories. -SHA-1s are stored directly with references to optimize this use case. +Object names are stored directly with references to optimize this use case. Logs are infrequently read ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1063,13 +1081,3 @@ impossible. A common format that can be supported by all major Git implementations (git-core, JGit, libgit2) is strongly preferred. - -Future -~~~~~~ - -Longer hashes -^^^^^^^^^^^^^ - -Version will bump (e.g. 2) to indicate `value` uses a different object -name length other than 20. The length could be stored in an expanded file -heaer, or hardcoded as part of the version.
Signature · 1 line
gitgitgadget
Quoted 2 lines · Han-Wen Nienhuys via GitGitGadget
This patch series contains preliminary cleanups for adding reftable support to git-core
I'll rebase hn/reftable on top of these patches, and hopefully we can merge this topic early to 'next' to start cooking it early before the rest, unless there is no other major issues identified by others.
Thanks for a quick turnaround.