bloom: changed-path Bloom filters v2
cover bloom: changed-path Bloom filters v2
This series combines the efforts in [1] and [2] to culminate in v2 of the changed-path Bloom filter format (which uses a bona-fide murmur3 implementation) as well as an efficient upgrade path for repositories with few non-ASCII paths.
The first seven patches are from [1], and are unchanged from that version. Most of the remaining patches are from [2], and they have been modified based on the review therein.
The final patch is new, and avoids leaking memory when the Bloom subsystem is initialized both in the read- and read/write-cases.
Thanks to Jonathan and Peff who have both helped a great deal in putting these patches together. And, as always, thanks in advance for your review!
[1]: https://lore.kernel.org/git/[email protected]/
[2]: https://lore.kernel.org/git/[email protected]/
Jonathan Tan (4):
gitformat-commit-graph: describe version 2 of BDAT
t4216: test changed path filters with high bit paths
repo-settings: introduce commitgraph.changedPathsVersion
commit-graph: new filter ver. that fixes murmur3
Taylor Blau (11):
t/helper/test-read-graph.c: extract dump_graph_info() bloom.h: make load_bloom_filter_from_graph() public t/helper/test-read-graph: implement bloom-filters mode
bloom: annotate filters with hash version
bloom: prepare to discard incompatible Bloom filters t/t4216-log-bloom.sh: harden test_bloom_filters_not_used() commit-graph.c: unconditionally load Bloom filters
commit-graph: drop unnecessary graph_read_bloom_data_context object.h: fix mis-aligned flag bits table
commit-graph: reuse existing Bloom filters where possible
bloom: introduce deinit_bloom_filters()
Documentation/config/commitgraph.txt | 26 ++- Documentation/gitformat-commit-graph.txt | 9 +- bloom.c | 208 +++++++++++++++++++++-- bloom.h | 38 ++++- commit-graph.c | 36 +++- object.h | 3 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +- repository.h | 2 +- t/helper/test-bloom.c | 9 +- t/helper/test-read-graph.c | 67 ++++++-- t/t0095-bloom.sh | 8 + t/t4216-log-bloom.sh | 184 +++++++++++++++++++- 13 files changed, 548 insertions(+), 50 deletions(-)
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 1/15 gitformat-commit-graph: describe version 2 of BDAT
From: Jonathan Tan <[email protected]>
The code change to Git to support version 2 will be done in subsequent commits.
--- Documentation/gitformat-commit-graph.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
Documentation/gitformat-commit-graph.txt +6 -3
@@ -142,13 +142,16 @@ All multi-byte numbers are in network byte order. ==== Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional] * It starts with header consisting of three unsigned 32-bit integers: - - Version of the hash algorithm being used. We currently only support - value 1 which corresponds to the 32-bit version of the murmur3 hash + - Version of the hash algorithm being used. We currently support + value 2 which corresponds to the 32-bit version of the murmur3 hash implemented exactly as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm and the double hashing technique using seed values 0x293ae76f and 0x7e646e2 as described in https://doi.org/10.1007/978-3-540-30494-4_26 "Bloom Filters - in Probabilistic Verification" + in Probabilistic Verification". Version 1 Bloom filters have a bug that appears + when char is signed and the repository has path names that have characters >= + 0x80; Git supports reading and writing them, but this ability will be removed + in a future version of Git. - The number of times a path is hashed and hence the number of bit positions that cumulatively determine whether a file is present in the commit. - The minimum number of bits 'b' per entry in the Bloom filter. If the filter
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 2/15 t/helper/test-read-graph.c: extract `dump_graph_info()`
In reply to Taylor BlauPrepare for the 'read-graph' test helper to perform other tasks besides dumping high-level information about the commit-graph by extracting its main routine into a separate function.
--- t/helper/test-read-graph.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-)
t/helper/test-read-graph.c +18 -13
@@ -5,20 +5,8 @@#include "bloom.h" #include "setup.h" -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_info (struct commit_graph *graph) { -struct commit_graph *graph =NULL ; -struct object_directory *odb; - -setup_git_directory (); - odb = the_repository->objects->odb; - -prepare_repo_settings (the_repository); - - graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; - printf("header: %08x %d %d %d %d\n", ntohl(*(uint32_t*)graph->data), *(unsigned char*)(graph->data + 4),
@@ -57,6 +45,23 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)if (graph->topo_levels)printf (" topo_levels" );printf ("\n" ); +} + +int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +{ +struct commit_graph *graph =NULL ; +struct object_directory *odb; + +setup_git_directory (); + odb = the_repository->objects->odb; + +prepare_repo_settings (the_repository); + + graph =read_commit_graph_one (the_repository, odb); +if (!graph) +return 1 ; + +dump_graph_info (graph);UNLEAK (graph);
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 3/15 bloom.h: make `load_bloom_filter_from_graph()` public
In reply to Taylor BlauPrepare for a future commit to use the load_bloom_filter_from_graph() function directly to load specific Bloom filters out of the commit-graph for manual inspection (to be used during tests).
--- bloom.c | 6 +++--- bloom.h | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-)
bloom.c +3 -3
@@ -29,9 +29,9 @@ static inline unsigned char get_bitmask(uint32_t pos)return ((unsigned char )1 ) << (pos & (BITS_PER_WORD -1 )); } -static int load_bloom_filter_from_graph (struct commit_graph *g, -struct bloom_filter *filter, -uint32_t graph_pos) +int load_bloom_filter_from_graph (struct commit_graph *g, +struct bloom_filter *filter, +uint32_t graph_pos) {uint32_t lex_pos, start_index, end_index;
bloom.h +5 -0
@@ -3,6 +3,7 @@struct commit ;struct repository ; +struct commit_graph ;struct bloom_filter_settings {/*
@@ -68,6 +69,10 @@ struct bloom_key {uint32_t *hashes; }; +int load_bloom_filter_from_graph (struct commit_graph *g, +struct bloom_filter *filter, +uint32_t graph_pos); +/* * Calculate the murmur3 32-bit hash value for the given data * using the given seed.
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 4/15 t/helper/test-read-graph: implement `bloom-filters` mode
In reply to Taylor BlauImplement a mode of the "read-graph" test helper to dump out the hexadecimal contents of the Bloom filter(s) contained in a commit-graph.
--- t/helper/test-read-graph.c | 44 +++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-)
t/helper/test-read-graph.c +39 -5
@@ -47,10 +47,32 @@ static void dump_graph_info(struct commit_graph *graph)printf ("\n" ); } -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_bloom_filters (struct commit_graph *graph) +{ +uint32_t i; + +for (i =0 ; i < graph->num_commits + graph->num_commits_in_base; i++) { +struct bloom_filter filter = {0 }; +size_t j; + +if (load_bloom_filter_from_graph (graph, &filter, i) <0 ) { +fprintf (stderr,"missing Bloom filter for graph " +"position %" PRIu32"\n" , i); +continue ; + } + +for (j =0 ; j < filter.len; j++) +printf ("%02x" , filter.data[j]); +if (filter.len) +printf ("\n" ); + } +} + +int cmd__read_graph (int argc,const char **argv) {struct commit_graph *graph =NULL ;struct object_directory *odb; +int ret =0 ;setup_git_directory (); odb = the_repository->objects->odb;
@@ -58,12 +80,24 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)prepare_repo_settings (the_repository); graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; +if (!graph) { + ret =1 ; + goto done; + } -dump_graph_info (graph); +if (argc <=1 ) +dump_graph_info (graph); +else if (!strcmp (argv[1 ],"bloom-filters" )) +dump_graph_bloom_filters (graph); +else { +fprintf (stderr,"unknown sub-command: '%s'\n" , argv[1 ]); + ret =1 ; + } +done:UNLEAK (graph); -return 0 ; +return ret; } + +
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 5/15 t4216: test changed path filters with high bit paths
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
Subsequent commits will teach Git another version of changed path filter that has different behavior with paths that contain at least one character with its high bit set, so test the existing behavior as a baseline.
--- t/t4216-log-bloom.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+)
t/t4216-log-bloom.sh +43 -0
@@ -404,4 +404,47 @@ test_expect_success 'Bloom generation backfills empty commits' ' ) ' +get_first_changed_path_filter () { + test-tool read-graph bloom-filters >filters.dat && + head -n 1 filters.dat +} + +# chosen to be the same under all Unicode normalization forms +CENT=$(printf "\302\242") + +test_expect_success 'set up repo with high bit path, version 1 changed-path' ' + git init highbit1 && + test_commit -C highbit1 c1 "$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths +' + +test_expect_success 'setup check value of version 1 changed-path' ' + ( + cd highbit1 && + echo "52a9" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +# expect will not match actual if char is unsigned by default. Write the test +# in this way, so that a user running this test script can still see if the two +# files match. (It will appear as an ordinary success if they match, and a skip +# if not.) +if test_cmp highbit1/expect highbit1/actual +then + test_set_prereq SIGNED_CHAR_BY_DEFAULT +fi +test_expect_success SIGNED_CHAR_BY_DEFAULT 'check value of version 1 changed-path' ' + # Only the prereq matters for this test. + true +' + +test_expect_success 'version 1 changed-path used when version 1 requested' ' + ( + cd highbit1 && + test_bloom_filters_used "-- $CENT" + ) +' + test_done
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 6/15 repo-settings: introduce commitgraph.changedPathsVersion
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
A subsequent commit will introduce another version of the changed-path filter in the commit graph file. In order to control which version to write (and read), a config variable is needed.
Therefore, introduce this config variable. For forwards compatibility, teach Git to not read commit graphs when the config variable is set to an unsupported version. Because we teach Git this, commitgraph.readChangedPaths is now redundant, so deprecate it and define its behavior in terms of the config variable we introduce.
This commit does not change the behavior of writing (Git writes changed path filters when explicitly instructed regardless of any config variable), but a subsequent commit will restrict Git such that it will only write when commitgraph.changedPathsVersion is a recognized value.
--- Documentation/config/commitgraph.txt | 23 ++++++++++++++++++++--- commit-graph.c | 2 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +++++- repository.h | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-)
Documentation/config/commitgraph.txt +20 -3
@@ -9,6 +9,23 @@ commitGraph.maxNewFilters:: commit-graph write` (c.f., linkgit:git-commit-graph[1]). commitGraph.readChangedPaths:: - If true, then git will use the changed-path Bloom filters in the - commit-graph file (if it exists, and they are present). Defaults to - true. See linkgit:git-commit-graph[1] for more information. + Deprecated. Equivalent to commitGraph.changedPathsVersion=-1 if true, and + commitGraph.changedPathsVersion=0 if false. (If commitGraph.changedPathVersion + is also set, commitGraph.changedPathsVersion takes precedence.) + +commitGraph.changedPathsVersion:: + Specifies the version of the changed-path Bloom filters that Git will read and + write. May be -1, 0 or 1. ++ +Defaults to -1. ++ +If -1, Git will use the version of the changed-path Bloom filters in the +repository, defaulting to 1 if there are none. ++ +If 0, Git will not read any Bloom filters, and will write version 1 Bloom +filters when instructed to write. ++ +If 1, Git will only read version 1 Bloom filters, and will write version 1 +Bloom filters. ++ +See linkgit:git-commit-graph[1] for more information.
commit-graph.c +1 -1
@@ -401,7 +401,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, graph->read_generation_data =1 ; } -if (s->commit_graph_read_changed_paths) { +if (s->commit_graph_changed_paths_version) {pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes); read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA,
oss-fuzz/fuzz-commit-graph.c +1 -1
@@ -19,7 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)* possible. */ the_repository->settings.commit_graph_generation_version =2 ; - the_repository->settings.commit_graph_read_changed_paths =1 ; + the_repository->settings.commit_graph_changed_paths_version =1 ; g =parse_commit_graph (&the_repository->settings, (void *)data, size);repo_clear (the_repository);free_commit_graph (g);
repo-settings.c +5 -1
@@ -24,6 +24,7 @@ void prepare_repo_settings(struct repository *r)int value;const char *strval;int manyfiles; +int read_changed_paths;if (!r->gitdir)BUG ("Cannot add settings for uninitialized repository" );
@@ -54,7 +55,10 @@ void prepare_repo_settings(struct repository *r)/* Commit graph config or default, does not cascade (simple) */ repo_cfg_bool (r,"core.commitgraph" , &r->settings.core_commit_graph,1 );repo_cfg_int (r,"commitgraph.generationversion" , &r->settings.commit_graph_generation_version,2 ); -repo_cfg_bool (r,"commitgraph.readchangedpaths" , &r->settings.commit_graph_read_changed_paths,1 ); +repo_cfg_bool (r,"commitgraph.readchangedpaths" , &read_changed_paths,1 ); +repo_cfg_int (r,"commitgraph.changedpathsversion" , + &r->settings.commit_graph_changed_paths_version, + read_changed_paths ?-1 :0 );repo_cfg_bool (r,"gc.writecommitgraph" , &r->settings.gc_write_commit_graph,1 );repo_cfg_bool (r,"fetch.writecommitgraph" , &r->settings.fetch_write_commit_graph,0 );
repository.h +1 -1
@@ -29,7 +29,7 @@ struct repo_settings {int core_commit_graph;int commit_graph_generation_version; -int commit_graph_read_changed_paths; +int commit_graph_changed_paths_version;int gc_write_commit_graph;int fetch_write_commit_graph;int command_requires_full_index;
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 7/15 commit-graph: new filter ver. that fixes murmur3
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
The murmur3 implementation in bloom.c has a bug when converting series of 4 bytes into network-order integers when char is signed (which is controllable by a compiler option, and the default signedness of char is platform-specific). When a string contains characters with the high bit set, this bug causes results that, although internally consistent within Git, does not accord with other implementations of murmur3 (thus, the changed path filters wouldn't be readable by other off-the-shelf implementatios of murmur3) and even with Git binaries that were compiled with different signedness of char. This bug affects both how Git writes changed path filters to disk and how Git interprets changed path filters on disk.
Therefore, introduce a new version (2) of changed path filters that corrects this problem. The existing version (1) is still supported and is still the default, but users should migrate away from it as soon as possible.
Because this bug only manifests with characters that have the high bit set, it may be possible that some (or all) commits in a given repo would have the same changed path filter both before and after this fix is applied. However, in order to determine whether this is the case, the changed paths would first have to be computed, at which point it is not much more expensive to just compute a new changed path filter.
So this patch does not include any mechanism to "salvage" changed path filters from repositories. There is also no "mixed" mode - for each invocation of Git, reading and writing changed path filters are done with the same version number; this version number may be explicitly stated (typically if the user knows which version they need) or automatically determined from the version of the existing changed path filters in the repository.
There is a change in write_commit_graph(). graph_read_bloom_data() makes it possible for chunk_bloom_data to be non-NULL but bloom_filter_settings to be NULL, which causes a segfault later on. I produced such a segfault while developing this patch, but couldn't find a way to reproduce it neither after this complete patch (or before), but in any case it seemed like a good thing to include that might help future patch authors.
The value in t0095 was obtained from another murmur3 implementation using the following Go source code:
package main
import "fmt"
import "github.com/spaolacci/murmur3"
func main() {
fmt.Printf("%x\n", murmur3.Sum32([]byte("Hello world!")))
fmt.Printf("%x\n", murmur3.Sum32([]byte{0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}))
}--- Documentation/config/commitgraph.txt | 5 +- bloom.c | 69 +++++++++++++++++++- bloom.h | 8 ++- commit-graph.c | 32 ++++++++-- t/helper/test-bloom.c | 9 ++- t/t0095-bloom.sh | 8 +++ t/t4216-log-bloom.sh | 96 ++++++++++++++++++++++++++++ 7 files changed, 214 insertions(+), 13 deletions(-)
Documentation/config/commitgraph.txt +4 -1
@@ -15,7 +15,7 @@ commitGraph.readChangedPaths:: commitGraph.changedPathsVersion:: Specifies the version of the changed-path Bloom filters that Git will read and - write. May be -1, 0 or 1. + write. May be -1, 0, 1, or 2. + Defaults to -1. +
@@ -28,4 +28,7 @@ filters when instructed to write. If 1, Git will only read version 1 Bloom filters, and will write version 1 Bloom filters. + +If 2, Git will only read version 2 Bloom filters, and will write version 2 +Bloom filters. ++ See linkgit:git-commit-graph[1] for more information.
bloom.c +66 -3
81 lines not on this page · raw message
bloom.h +5 -3
@@ -8,9 +8,11 @@ struct commit_graph;struct bloom_filter_settings {/* * The version of the hashing technique being used. -* We currently only support version = 1 which is +* The newest version is 2, which is * the seeded murmur3 hashing technique implemented -* in bloom.c. +* in bloom.c. Bloom filters of version 1 were created +* with prior versions of Git, which had a bug in the +* implementation of the hash function. */ uint32_t hash_version;
@@ -80,7 +82,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len); +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len); void fill_bloom_key(const char *data, size_t len,
commit-graph.c +27 -5
@@ -304,17 +304,26 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } +struct graph_read_bloom_data_context { +struct commit_graph *g; +int *commit_graph_changed_paths_version; +}; +static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct commit_graph *g = data; +struct graph_read_bloom_data_context *c = data; +struct commit_graph *g = c->g;uint32_t hash_version; - g->chunk_bloom_data = chunk_start; hash_version =get_be32 (chunk_start); -if (hash_version !=1 ) +if (*c->commit_graph_changed_paths_version ==-1 ) { + *c->commit_graph_changed_paths_version = hash_version; + }else if (hash_version != *c->commit_graph_changed_paths_version) {return 0 ; + } + g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version; g->bloom_filter_settings->num_hashes =get_be32 (chunk_start +4 );
@@ -402,10 +411,14 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { +struct graph_read_bloom_data_context context = { + .g = graph, + .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version + };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, graph); + graph_read_bloom_data, &context); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
@@ -2376,6 +2389,13 @@ int write_commit_graph(struct object_directory *odb, }if (!commit_graph_compatible (r))return 0 ; +if (r->settings.commit_graph_changed_paths_version <-1 + || r->settings.commit_graph_changed_paths_version >2 ) { +warning (_ ("attempting to write a commit-graph, but " +"'commitgraph.changedPathsVersion' (%d) is not supported" ), + r->settings.commit_graph_changed_paths_version); +return 0 ; + }CALLOC_ARRAY (ctx,1 ); ctx->r = r;
@@ -2388,6 +2408,8 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 + ?2 :1 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
@@ -2417,7 +2439,7 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph;/* We have changed-paths already. Keep them in the next graph */ -if (g && g->chunk_bloom_data) { +if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; ctx->bloom_settings = g->bloom_filter_settings; }
t/helper/test-bloom.c +8 -1
@@ -50,6 +50,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)static const char *bloom_usage ="\n" " test-tool bloom get_murmur3 <string>\n" +" test-tool bloom get_murmur3_seven_highbit\n" " test-tool bloom generate_filter <string> [<string>...]\n" " test-tool bloom get_filter_for_commit <commit-hex>\n" ;
@@ -64,7 +65,13 @@ int cmd__bloom(int argc, const char **argv)uint32_t hashed;if (argc <3 )usage (bloom_usage); - hashed =murmur3_seeded (0 , argv[2 ],strlen (argv[2 ])); + hashed =murmur3_seeded_v2 (0 , argv[2 ],strlen (argv[2 ])); +printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); + } + +if (!strcmp (argv[1 ],"get_murmur3_seven_highbit" )) { +uint32_t hashed; + hashed =murmur3_seeded_v2 (0 ,"\x99\xaa\xbb\xcc\xdd\xee\xff" ,7 );printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); }
t/t0095-bloom.sh +8 -0
@@ -29,6 +29,14 @@ test_expect_success 'compute unseeded murmur3 hash for test string 2' ' test_cmp expect actual ' +test_expect_success 'compute unseeded murmur3 hash for test string 3' ' + cat >expect <<-\EOF && + Murmur3 Hash with seed=0:0xa183ccfd + EOF + test-tool bloom get_murmur3_seven_highbit >actual && + test_cmp expect actual +' + test_expect_success 'compute bloom key for empty string' ' cat >expect <<-\EOF && Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|
t/t4216-log-bloom.sh +96 -0
100 lines not on this page · raw message
Signature · 1 line
2.42.0.4.g52b49bb434
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 94 lines · Taylor Blau
From: Jonathan Tan <[email protected]>
The murmur3 implementation in
bloom.chas a bug when converting series of 4 bytes into network-order integers when char is signed (which is controllable by a compiler option, and the default signedness of char is platform-specific). When a string contains characters with the high bit set, this bug causes results that, although internally consistent within Git, does not accord with other implementations of murmur3 (thus, the changed path filters wouldn't be readable by other off-the-shelf implementatios of murmur3) and even with Git binaries that were compiled with different signedness of char. This bug affects both how Git writes changed path filters to disk and how Git interprets changed path filters on disk.Therefore, introduce a new version (2) of changed path filters that corrects this problem. The existing version (1) is still supported and is still the default, but users should migrate away from it as soon as possible.
Because this bug only manifests with characters that have the high bit set, it may be possible that some (or all) commits in a given repo would have the same changed path filter both before and after this fix is applied. However, in order to determine whether this is the case, the changed paths would first have to be computed, at which point it is not much more expensive to just compute a new changed path filter.
So this patch does not include any mechanism to "salvage" changed path filters from repositories. There is also no "mixed" mode - for each invocation of Git, reading and writing changed path filters are done with the same version number; this version number may be explicitly stated (typically if the user knows which version they need) or automatically determined from the version of the existing changed path filters in the repository.
There is a change in
write_commit_graph().graph_read_bloom_data()makes it possible forchunk_bloom_datato be non-NULL butbloom_filter_settingsto be NULL, which causes a segfault later on. I produced such a segfault while developing this patch, but couldn't find a way to reproduce it neither after this complete patch (or before), but in any case it seemed like a good thing to include that might help future patch authors.The value in t0095 was obtained from another murmur3 implementation using the following Go source code:
package main
import "fmt"
import "github.com/spaolacci/murmur3"func main() { fmt.Printf("%x\n", murmur3.Sum32([]byte("Hello world!"))) fmt.Printf("%x\n", murmur3.Sum32([]byte{0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})) }Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>--- Documentation/config/commitgraph.txt | 5 +- bloom.c | 69 +++++++++++++++++++- bloom.h | 8 ++- commit-graph.c | 32 ++++++++-- t/helper/test-bloom.c | 9 ++- t/t0095-bloom.sh | 8 +++ t/t4216-log-bloom.sh | 96 ++++++++++++++++++++++++++++ 7 files changed, 214 insertions(+), 13 deletions(-)Documentation/config/commitgraph.txt +4
-1@@ -15,7 +15,7 @@ commitGraph.readChangedPaths:: commitGraph.changedPathsVersion:: Specifies the version of the changed-path Bloom filters that Git will read and - write. May be -1, 0 or 1. + write. May be -1, 0, 1, or 2. + Defaults to -1. +@@ -28,4 +28,7 @@ filters when instructed to write. If 1, Git will only read version 1 Bloom filters, and will write version 1 Bloom filters. + +If 2, Git will only read version 2 Bloom filters, and will write version 2 +Bloom filters. ++ See linkgit:git-commit-graph[1] for more information.bloom.c +0
-0
bloom.c int load_bloom_filter_from_graph(struct commit_graph *g, · line 69
Quoted 5 lines
bloom.c +1
-1@@ -66,7 +66,64 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len) +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len)
Nit: MurmurHash3 implementations in C/C++ (apart from the sample implementation on Wikipedia), and other hash functions taking data pointer and buffer size parameters in general, have a 'const void *data' parameter, not 'const char*'.
Quoted 249 lines
bloom.c +65
-2+{ +const uint32_t c1 =0xcc9e2d51 ; +const uint32_t c2 =0x1b873593 ; +const uint32_t r1 =15 ; +const uint32_t r2 =13 ; +const uint32_t m =5 ; +const uint32_t n =0xe6546b64 ; +int i; +uint32_t k1 =0 ; +const char *tail; + +int len4 = len /sizeof (uint32_t ); + +uint32_t k; +for (i =0 ; i < len4; i++) { +uint32_t byte1 = (uint32_t )(unsigned char )data[4 *i]; +uint32_t byte2 = ((uint32_t )(unsigned char )data[4 *i +1 ]) <<8 ; +uint32_t byte3 = ((uint32_t )(unsigned char )data[4 *i +2 ]) <<16 ; +uint32_t byte4 = ((uint32_t )(unsigned char )data[4 *i +3 ]) <<24 ; + k = byte1 | byte2 | byte3 | byte4; + k *= c1; + k =rotate_left (k, r1); + k *= c2; + + seed ^= k; + seed =rotate_left (seed, r2) * m + n; + } + + tail = (data + len4 *sizeof (uint32_t )); + +switch (len & (sizeof (uint32_t ) -1 )) { +case 3 : + k1 ^= ((uint32_t )(unsigned char )tail[2 ]) <<16 ; +/*-fallthrough*/ +case 2 : + k1 ^= ((uint32_t )(unsigned char )tail[1 ]) <<8 ; +/*-fallthrough*/ +case 1 : + k1 ^= ((uint32_t )(unsigned char )tail[0 ]) <<0 ; + k1 *= c1; + k1 =rotate_left (k1, r1); + k1 *= c2; + seed ^= k1; +break ; + } + + seed ^= (uint32_t )len; + seed ^= (seed >>16 ); + seed *=0x85ebca6b ; + seed ^= (seed >>13 ); + seed *=0xc2b2ae35 ; + seed ^= (seed >>16 ); + +return seed; +} + +static uint32_t murmur3_seeded_v1 (uint32_t seed,const char *data,size_t len) {const uint32_t c1 =0xcc9e2d51 ;const uint32_t c2 =0x1b873593 ;@@ -131,8 +188,14 @@ void fill_bloom_key(const char *data,int i;const uint32_t seed0 =0x293ae76f ;const uint32_t seed1 =0x7e646e2c ; -const uint32_t hash0 =murmur3_seeded (seed0, data, len); -const uint32_t hash1 =murmur3_seeded (seed1, data, len); +uint32_t hash0, hash1; +if (settings->hash_version ==2 ) { + hash0 =murmur3_seeded_v2 (seed0, data, len); + hash1 =murmur3_seeded_v2 (seed1, data, len); + }else { + hash0 =murmur3_seeded_v1 (seed0, data, len); + hash1 =murmur3_seeded_v1 (seed1, data, len); + } key->hashes = (uint32_t *)xcalloc (settings->num_hashes,sizeof (uint32_t ));for (i =0 ; i < settings->num_hashes; i++)bloom.h +5
-3@@ -8,9 +8,11 @@ struct commit_graph;struct bloom_filter_settings {/* * The version of the hashing technique being used. -* We currently only support version = 1 which is +* The newest version is 2, which is * the seeded murmur3 hashing technique implemented -* in bloom.c. +* in bloom.c. Bloom filters of version 1 were created +* with prior versions of Git, which had a bug in the +* implementation of the hash function. */ uint32_t hash_version;@@ -80,7 +82,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len); +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len); void fill_bloom_key(const char *data, size_t len,commit-graph.c +27
-5@@ -304,17 +304,26 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } +struct graph_read_bloom_data_context { +struct commit_graph *g; +int *commit_graph_changed_paths_version; +}; +static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct commit_graph *g = data; +struct graph_read_bloom_data_context *c = data; +struct commit_graph *g = c->g;uint32_t hash_version; - g->chunk_bloom_data = chunk_start; hash_version =get_be32 (chunk_start); -if (hash_version !=1 ) +if (*c->commit_graph_changed_paths_version ==-1 ) { + *c->commit_graph_changed_paths_version = hash_version; + }else if (hash_version != *c->commit_graph_changed_paths_version) {return 0 ; + } + g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version; g->bloom_filter_settings->num_hashes =get_be32 (chunk_start +4 );@@ -402,10 +411,14 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { +struct graph_read_bloom_data_context context = { + .g = graph, + .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version + };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, graph); + graph_read_bloom_data, &context); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {@@ -2376,6 +2389,13 @@ int write_commit_graph(struct object_directory *odb, }if (!commit_graph_compatible (r))return 0 ; +if (r->settings.commit_graph_changed_paths_version <-1 + || r->settings.commit_graph_changed_paths_version >2 ) { +warning (_ ("attempting to write a commit-graph, but " +"'commitgraph.changedPathsVersion' (%d) is not supported" ), + r->settings.commit_graph_changed_paths_version); +return 0 ; + }CALLOC_ARRAY (ctx,1 ); ctx->r = r;@@ -2388,6 +2408,8 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 + ?2 :1 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",@@ -2417,7 +2439,7 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph;/* We have changed-paths already. Keep them in the next graph */ -if (g && g->chunk_bloom_data) { +if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; ctx->bloom_settings = g->bloom_filter_settings; }t/helper/test-bloom.c +8
-1@@ -50,6 +50,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)static const char *bloom_usage ="\n" " test-tool bloom get_murmur3 <string>\n" +" test-tool bloom get_murmur3_seven_highbit\n" " test-tool bloom generate_filter <string> [<string>...]\n" " test-tool bloom get_filter_for_commit <commit-hex>\n" ;@@ -64,7 +65,13 @@ int cmd__bloom(int argc, const char **argv)uint32_t hashed;if (argc <3 )usage (bloom_usage); - hashed =murmur3_seeded (0 , argv[2 ],strlen (argv[2 ])); + hashed =murmur3_seeded_v2 (0 , argv[2 ],strlen (argv[2 ])); +printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); + } + +if (!strcmp (argv[1 ],"get_murmur3_seven_highbit" )) { +uint32_t hashed; + hashed =murmur3_seeded_v2 (0 ,"\x99\xaa\xbb\xcc\xdd\xee\xff" ,7 );printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); }t/t0095-bloom.sh +8
-0@@ -29,6 +29,14 @@ test_expect_success 'compute unseeded murmur3 hash for test string 2' ' test_cmp expect actual ' +test_expect_success 'compute unseeded murmur3 hash for test string 3' ' + cat >expect <<-\EOF && + Murmur3 Hash with seed=0:0xa183ccfd + EOF + test-tool bloom get_murmur3_seven_highbit >actual && + test_cmp expect actual +' + test_expect_success 'compute bloom key for empty string' ' cat >expect <<-\EOF && Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|t/t4216-log-bloom.sh +9
-0@@ -447,4 +447,100 @@ test_expect_success 'version 1 changed-path used when version 1 requested' ' ) ' +test_expect_success 'version 1 changed-path not used when version 2 requested' ' + ( + cd highbit1 && + git config --add commitgraph.changedPathsVersion 2 && + test_bloom_filters_not_used "-- $CENT" + ) +' + +test_expect_success 'version 1 changed-path used when autodetect requested' '
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 468
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+ ( + cd highbit1 && + git config --add commitgraph.changedPathsVersion -1 && + test_bloom_filters_used "-- $CENT" + ) +' + +test_expect_success 'when writing another commit graph, preserve existing version 1 of changed-path' ' + test_commit -C highbit1 c1double "$CENT$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths &&
Nit: Since there is a subshell cd-ing into the 'highbit1' directory anyway, it would look clearer to put these two commands into that subshell as well.
This applies to some of the later test cases as well.
Quoted 20 lines
t/t4216-log-bloom.sh +20
-0+ ( + cd highbit1 && + git config --add commitgraph.changedPathsVersion -1 && + echo "options: bloom(1,10,7) read_generation_data" >expect && + test-tool read-graph >full && + grep options full >actual && + test_cmp expect actual + ) +' + +test_expect_success 'set up repo with high bit path, version 2 changed-path' ' + git init highbit2 && + git -C highbit2 config --add commitgraph.changedPathsVersion 2 && + test_commit -C highbit2 c2 "$CENT" && + git -C highbit2 commit-graph write --reachable --changed-paths +' + +test_expect_success 'check value of version 2 changed-path' ' + ( + cd highbit2 &&
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 498
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+ echo "c01f" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +test_expect_success 'version 2 changed-path used when version 2 requested' ' + ( + cd highbit2 && + test_bloom_filters_used "-- $CENT"
This test_bloom_filter_used helper runs two pathspec-limited 'git log' invocations, one with disabled and the other with enabled commit-graph, and thus with disabled and enabled modified path Bloom filters, and compares their output.
One of the flaws of the current modified path Bloom filters implementation is that it doesn't check Bloom filters for root commits.
In several of the above test cases test_bloom_filters_used is invoked in a repository with only a root commit, so they don't check that the output is the same with and without Bloom filters.
Quoted 36 lines
t/t4216-log-bloom.sh +36
-0+ ) +' + +test_expect_success 'version 2 changed-path not used when version 1 requested' ' + ( + cd highbit2 && + git config --add commitgraph.changedPathsVersion 1 && + test_bloom_filters_not_used "-- $CENT" + ) +' + +test_expect_success 'version 2 changed-path used when autodetect requested' ' + ( + cd highbit2 && + git config --add commitgraph.changedPathsVersion -1 && + test_bloom_filters_used "-- $CENT" + ) +' + +test_expect_success 'when writing another commit graph, preserve existing version 2 of changed-path' ' + test_commit -C highbit2 c2double "$CENT$CENT" && + git -C highbit2 commit-graph write --reachable --changed-paths && + ( + cd highbit2 && + git config --add commitgraph.changedPathsVersion -1 && + echo "options: bloom(2,10,7) read_generation_data" >expect && + test-tool read-graph >full && + grep options full >actual && + test_cmp expect actual + ) +' + +test_expect_success 'when writing commit graph, do not reuse changed-path of another version' ' + git init doublewrite && + test_commit -C doublewrite c "$CENT" && + git -C doublewrite config --add commitgraph.changedPathsVersion 1 &&
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 544
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+ git -C doublewrite commit-graph write --reachable --changed-paths && + git -C doublewrite config --add commitgraph.changedPathsVersion 2 && + git -C doublewrite commit-graph write --reachable --changed-paths && + ( + cd doublewrite && + echo "c01f" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +'
The string "split" occurs twice in this patch series, but only in patch hunk contexts, and it doesn't occur at all in the previous long thread about the original patch series.
Unfortunately, split commit-graphs weren't really considered in the design of the modified path Bloom filters feature, and layers with different Bloom filter settings weren't considered at all. I've reported it back then, but the fixes so far were incomplete, and e.g. the test cases shown in
https://public-inbox.org/git/[email protected]/
still fail.
Since the interaction of different versions and split commit-graphs was neither mentioned in any of the commit messages nor discussed during the previous rounds, and there isn't any test case excercising it, and since the Bloom filter version information is stored in the same 'g->bloom_filter_settings' structure as the number of hashes, I'm afraid (though haven't actually checked) that handling commit-graph layers with different Bloom filter versions is prone to the same issues as well.
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 11 lines · SZEDER Gábor
bloom.c int load_bloom_filter_from_graph(struct commit_graph *g, · line 69
Quoted 6 lines
bloom.c +1
-1@@ -66,7 +66,64 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len) +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len)Nit: MurmurHash3 implementations in C/C++ (apart from the sample implementation on Wikipedia), and other hash functions taking data pointer and buffer size parameters in general, have a 'const void *data' parameter, not 'const char*'.
I think either works, so I'll stick with what the existing code uses. (Well, we probably should have used "unsigned char" in the first place.)
Quoted 8 lines
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 468
Quoted 3 lines
t/t4216-log-bloom.sh +3
-0+test_expect_success 'when writing another commit graph, preserve existing version 1 of changed-path' ' + test_commit -C highbit1 c1double "$CENT$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths &&Nit: Since there is a subshell cd-ing into the 'highbit1' directory anyway, it would look clearer to put these two commands into that subshell as well.
This applies to some of the later test cases as well.
Makes sense, but this patch series has already been reviewed a few times so I don't think it's worth making this change at this point in the review process for a small increase in readability.
Quoted 10 lines
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 498
Quoted 4 lines
t/t4216-log-bloom.sh +4
-0+test_expect_success 'version 2 changed-path used when version 2 requested' ' + ( + cd highbit2 && + test_bloom_filters_used "-- $CENT"This
test_bloom_filter_usedhelper runs two pathspec-limited 'git log' invocations, one with disabled and the other with enabled commit-graph, and thus with disabled and enabled modified path Bloom filters, and compares their output.
Quoted 7 lines
One of the flaws of the current modified path Bloom filters implementation is that it doesn't check Bloom filters for root commits.
In several of the above test cases
test_bloom_filters_usedis invoked in a repository with only a root commit, so they don't check that the output is the same with and without Bloom filters.
Ah...you are right. Indeed, when I flip one of the tests from test_bloom_filters_used to _not_, the test still passes. I'll change the tests.
Quoted 14 lines
The string "split" occurs twice in this patch series, but only in patch hunk contexts, and it doesn't occur at all in the previous long thread about the original patch series.
Unfortunately, split commit-graphs weren't really considered in the design of the modified path Bloom filters feature, and layers with different Bloom filter settings weren't considered at all. I've reported it back then, but the fixes so far were incomplete, and e.g. the test cases shown in
https://public-inbox.org/git/[email protected]/
still fail.
Quoted 8 lines
Since the interaction of different versions and split commit-graphs was neither mentioned in any of the commit messages nor discussed during the previous rounds, and there isn't any test case excercising it, and since the Bloom filter version information is stored in the same '
g->bloom_filter_settings' structure as the number of hashes, I'm afraid (though haven't actually checked) that handling commit-graph layers with different Bloom filter versions is prone to the same issues as well.
My original design (up to patch 7 in this patch set) defends against this by taking the very first version detected and rejecting every other version, and Taylor's subsequent design reads every version, but annotates filters with its version. So I think we're covered.
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 16 lines · Jonathan Tan
Quoted 14 lines · SZEDER Gábor
Review
Quoted 4 lines
(quoted diff) +4
-0+test_expect_success 'version 2 changed-path used when version 2 requested' ' + ( + cd highbit2 && + test_bloom_filters_used "-- $CENT"This
test_bloom_filter_usedhelper runs two pathspec-limited 'git log' invocations, one with disabled and the other with enabled commit-graph, and thus with disabled and enabled modified path Bloom filters, and compares their output.One of the flaws of the current modified path Bloom filters implementation is that it doesn't check Bloom filters for root commits.
Quoted 7 lines
Quoted 3 lines
In several of the above test cases
test_bloom_filters_usedis invoked in a repository with only a root commit, so they don't check that the output is the same with and without Bloom filters.Ah...you are right. Indeed, when I flip one of the tests from
test_bloom_filters_usedto _not_, the test still passes. I'll change the tests.
I'd prefer to leave the test cases unchanged, and make the revision walking machinery look at Bloom filters even for root commits, because this is an annoying and recurring testing issue. I remember it annoyed me back then, when I wanted to reproduce a couple of bugs that I knew were there, but my initial test cases didn't fail because the Bloom filter of the root commit was ignored; Derrick overlooked this in b16a8277644, you overlooked it now, and none of the reviewers then and now caught it, either.
Quoted 23 lines
Quoted 14 lines
The string "split" occurs twice in this patch series, but only in patch hunk contexts, and it doesn't occur at all in the previous long thread about the original patch series.
Unfortunately, split commit-graphs weren't really considered in the design of the modified path Bloom filters feature, and layers with different Bloom filter settings weren't considered at all. I've reported it back then, but the fixes so far were incomplete, and e.g. the test cases shown in
https://public-inbox.org/git/[email protected]/
still fail.
Quoted 8 lines
Since the interaction of different versions and split commit-graphs was neither mentioned in any of the commit messages nor discussed during the previous rounds, and there isn't any test case excercising it, and since the Bloom filter version information is stored in the same '
g->bloom_filter_settings' structure as the number of hashes, I'm afraid (though haven't actually checked) that handling commit-graph layers with different Bloom filter versions is prone to the same issues as well.
Quoted 4 lines
My original design (up to patch 7 in this patch set) defends against this by taking the very first version detected and rejecting every other version, and Taylor's subsequent design reads every version, but annotates filters with its version. So I think we're covered.
In the meantime I adapted the test cases from the above linked message to write commit-graph layers with different Bloom filter versions, and it does fail, because commits from the bottom commit-graph layer are omitted from the revision walk's output. And the test case doesn't even need a middle layer without modified path Bloom filters to "hide" the different version in the bottom layer. Merging the layers seems to work, though.
Besides fixing this issue, I think that the interaction of different Bloom filter versions and split commit-graphs needs to be thoroughly covered with test cases and discussed in the commit messages before this series could be considered good for merging.
t/t4216-log-bloom.sh +36 -0
@@ -586,4 +586,40 @@ test_expect_success 'when writing commit graph, reuse changed-path of another ve test_filter_upgraded 1 trace2.txt ' +test_expect_success 'split commit graph vs changed paths breakage - setup' ' + git init split-breakage && + ( + cd split-breakage && + git commit --allow-empty -m "Bloom filters are written but still ignored for root commits :(" && + for i in 1 2 3 + do + echo $i >$CENT && + git add $CENT && + git commit -m "$i" || return 1 + done && + git log --oneline -- $CENT >expect + ) +' + +test_expect_failure 'split commit graph vs changed paths breakage - split' ' + ( + cd split-breakage && + + # Compute v1 Bloom filters for the commits at the bottom. + git rev-parse HEAD^ | git commit-graph write --stdin-commits --changed-paths --split && + # Compute v2 Bloom filters for the rest of the commits at the top. + git rev-parse HEAD | git -c commitgraph.changedPathsVersion=2 commit-graph write --stdin-commits --changed-paths --split=no-merge && + + # Just to make sure that there are as many graph layers as I + # think there should be. + test_line_count = 2 .git/objects/info/commit-graphs/commit-graph-chain && + + # This checks Bloom filters using version information in the + # top layer, thus misses commits modifying the file in the + # bottom commit-graph layer. + git log --oneline -- $CENT >actual && + test_cmp expect actual + ) +' + test_done
It fails with:
+ cd split-breakage
+ git rev-parse HEAD^
+ git commit-graph write --stdin-commits --changed-paths --split
+ git rev-parse HEAD
+ git -c commitgraph.changedPathsVersion=2 commit-graph write --stdin-commits --changed-paths --split=no-merge
+ test_line_count = 2 .git/objects/info/commit-graphs/commit-graph-chain
+ git log --oneline -- ¢
+ test_cmp expect actual
--- expect 2023-08-30 19:07:59.882066827 +0000
+++ actual 2023-08-30 19:07:59.894067148 +0000
@@ -1,3 +1 @@
1db2248 3
-cfcc97f 2
-bd9c2c8 1
error: last command exited with $?=1
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 8 lines · SZEDER Gábor
I'd prefer to leave the test cases unchanged, and make the revision walking machinery look at Bloom filters even for root commits, because this is an annoying and recurring testing issue. I remember it annoyed me back then, when I wanted to reproduce a couple of bugs that I knew were there, but my initial test cases didn't fail because the Bloom filter of the root commit was ignored; Derrick overlooked this in
b16a8277644, you overlooked it now, and none of the reviewers then and now caught it, either.
I agree that making the revwalk look at Bloom filters of root commits is an urgent matter for the reasons you describe (it's something easily missed that slows down future development and/or makes future development error-prone), but so is moving to a correct murmur3 implementation, I think, and one shouldn't stop the other. There could be an argument that because the revwalk doesn't look at root commit Bloom filters, any development on a new Bloom filter hash version is suspect, but I don't think that has to be completely true.
Quoted 5 lines
Quoted 4 lines
My original design (up to patch 7 in this patch set) defends against this by taking the very first version detected and rejecting every other version, and Taylor's subsequent design reads every version, but annotates filters with its version. So I think we're covered.
Quoted 7 lines
In the meantime I adapted the test cases from the above linked message to write commit-graph layers with different Bloom filter versions, and it does fail, because commits from the bottom commit-graph layer are omitted from the revision walk's output. And the test case doesn't even need a middle layer without modified path Bloom filters to "hide" the different version in the bottom layer. Merging the layers seems to work, though.
For what it's worth, your test case below (with test_expect_success instead of test_expect_failure) passes with my original design. With the full patch set, it does fail, but for what it's worth, I did spot this, providing an incomplete solution [1] and then a complete one [2]. Your test case passes if I also include the following:
diff --git a/bloom.c b/bloom.c index ff131893cd..1bafd62a4e 100644 --- a/bloom.c +++ b/bloom.c @@ -344,6 +344,10 @@ struct bloom_filter *get_bloom_filter(struct repository *r, struct commit *c)
prepare_repo_settings(r);
hash_version = r->settings.commit_graph_changed_paths_version; + if (hash_version == -1) {
+ struct bloom_filter_settings *s = get_bloom_filter_settings(r);
+ hash_version = (s && s->hash_version == 2) ? 2 : 1;
+ }
if (!(hash_version == -1 || hash_version == filter->version))
return NULL; /* unusable filter */
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/[email protected]/
Quoted 4 lines
Besides fixing this issue, I think that the interaction of different Bloom filter versions and split commit-graphs needs to be thoroughly covered with test cases and discussed in the commit messages before this series could be considered good for merging.
Regarding commit messages, I can see that in "commit-graph: new filter ver. that fixes murmur3", I could add "(the first version read if there are multiple versions of changed path filters in the repository)" after "automatically determined from the version of the existing changed path filters in the repository". Taylor's patches already inherently cover multiple versions, I think, since Bloom filters are annotated with their versions, individually.
Regarding tests, yes, we could add the one you provided.
If you (or anyone else) can spot anything else that should be added, please let us know.
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 32 lines · Jonathan Tan
Quoted 5 lines
Quoted 4 lines
My original design (up to patch 7 in this patch set) defends against this by taking the very first version detected and rejecting every other version, and Taylor's subsequent design reads every version, but annotates filters with its version. So I think we're covered.
Quoted 7 lines
In the meantime I adapted the test cases from the above linked message to write commit-graph layers with different Bloom filter versions, and it does fail, because commits from the bottom commit-graph layer are omitted from the revision walk's output. And the test case doesn't even need a middle layer without modified path Bloom filters to "hide" the different version in the bottom layer. Merging the layers seems to work, though.
For what it's worth, your test case below (with
test_expect_successinstead oftest_expect_failure) passes with my original design. With the full patch set, it does fail, but for what it's worth, I did spot this, providing an incomplete solution [1] and then a complete one [2]. Your test case passes if I also include the following:diff --git a/bloom.c b/bloom.c index ff131893cd..1bafd62a4e 100644 --- a/bloom.c +++ b/bloom.c @@ -344,6 +344,10 @@ struct bloom_filter *get_bloom_filter(struct repository *r, struct commit *c)prepare_repo_settings(r); hash_version = r->settings.commit_graph_changed_paths_version;+ if (
hash_version== -1) {
+ structbloom_filter_settings*s =get_bloom_filter_settings(r);
+hash_version= (s &&s->hash_version== 2) ? 2 : 1;
+ }
Quoted 5 lines
if (!(hash_version == -1 ||
hash_version==filter->version))
return NULL; /* unusable filter */[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/[email protected]/
Hmm. I am confused -- are you saying that this series breaks existing functionality, or merely does not patch an existing breakage? I *think* that it's the latter, since this test case fails identically on master, but I am not sure.
If my understanding is correct, I think that patching this would involve annotating each Bloom filter with a pointer to the bloom_filter_settings it was written with, and then using those settings when querying it.
Thanks,
Taylor
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 36 lines · Taylor Blau
Quoted 35 lines · Jonathan Tan
Quoted 5 lines
Quoted 4 lines
My original design (up to patch 7 in this patch set) defends against this by taking the very first version detected and rejecting every other version, and Taylor's subsequent design reads every version, but annotates filters with its version. So I think we're covered.
Quoted 7 lines
In the meantime I adapted the test cases from the above linked message to write commit-graph layers with different Bloom filter versions, and it does fail, because commits from the bottom commit-graph layer are omitted from the revision walk's output. And the test case doesn't even need a middle layer without modified path Bloom filters to "hide" the different version in the bottom layer. Merging the layers seems to work, though.
For what it's worth, your test case below (with
test_expect_successinstead oftest_expect_failure) passes with my original design. With the full patch set, it does fail, but for what it's worth, I did spot this, providing an incomplete solution [1] and then a complete one [2]. Your test case passes if I also include the following:diff --git a/bloom.c b/bloom.c index ff131893cd..1bafd62a4e 100644 --- a/bloom.c +++ b/bloom.c @@ -344,6 +344,10 @@ struct bloom_filter *get_bloom_filter(struct repository *r, struct commit *c)prepare_repo_settings(r); hash_version = r->settings.commit_graph_changed_paths_version;+ if (
hash_version== -1) {
+ structbloom_filter_settings*s =get_bloom_filter_settings(r);
+hash_version= (s &&s->hash_version== 2) ? 2 : 1;
+ }if (!(hash_version == -1 ||
hash_version==filter->version))
return NULL; /* unusable filter */
Quoted 6 lines
Quoted 2 lines
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/[email protected]/Hmm. I am confused -- are you saying that this series breaks existing functionality, or merely does not patch an existing breakage? I *think* that it's the latter,
It's neither: the new functionality added in this series is broken.
Quoted 2 lines
since this test case fails identically on master,
but I am not sure.
Not sure what test you are referring to. My test demonstrating the breakage succeeds when adaped to master, because master doesn't understand the commitgraph.changedPathsVersion=2 setting, and keeps writing v1 Bloom filter chunks instead, so all commit-graphs layers contain the same version.
Quoted 3 lines
If my understanding is correct, I think that patching this would involve annotating each Bloom filter with a pointer to the
bloom_filter_settingsit was written with, and then using those settings when querying it.
In general we are better off when we don't write Bloom filter chunks with different settings in the first place.
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 6 lines · SZEDER Gábor
Quoted 3 lines
Hmm. I am confused -- are you saying that this series breaks existing functionality, or merely does not patch an existing breakage? I *think* that it's the latter,
It's neither: the new functionality added in this series is broken.
Quoted 8 lines
Quoted 2 lines
since this test case fails identically on master,
but I am not sure.Not sure what test you are referring to. My test demonstrating the breakage succeeds when adaped to master, because master doesn't understand the commitgraph.changedPathsVersion=2 setting, and keeps writing v1 Bloom filter chunks instead, so all commit-graphs layers contain the same version.
I was referring to the test you sent back in:
https://public-inbox.org/git/[email protected]/
but I think that I should have been looking at the one you sent more recently in:
https://lore.kernel.org/git/[email protected]/
Thanks,
Taylor
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 16 lines · Taylor Blau
Quoted 6 lines · SZEDER Gábor
Quoted 3 lines
Hmm. I am confused -- are you saying that this series breaks existing functionality, or merely does not patch an existing breakage? I *think* that it's the latter,
It's neither: the new functionality added in this series is broken.
Quoted 8 lines
Quoted 2 lines
since this test case fails identically on master,
but I am not sure.Not sure what test you are referring to. My test demonstrating the breakage succeeds when adaped to master, because master doesn't understand the commitgraph.changedPathsVersion=2 setting, and keeps writing v1 Bloom filter chunks instead, so all commit-graphs layers contain the same version.
Quoted 8 lines
I was referring to the test you sent back in:
https://public-inbox.org/git/[email protected]/but I think that I should have been looking at the one you sent more recently in:
https://lore.kernel.org/git/[email protected]/
OK, I think that I now have my head wrapped around what you're saying.
However, I am not entirely sure I agree with you that this is a "new" issue. At least in the sense that Git (on 'master') does not currently know how to deal with Bloom filters that have different settings across commit-graph layers.
IOW, you could produce this problem today using the test you wrote in <[email protected]> using different values of the GIT_BLOOM_SETTINGS environment variables as a proxy for different values of the commitGraph.changedPathsVersion configuration variable introduced in this series.
So I think that this series makes it easier to fall into that trap, but the trap itself is not new. I think a reasonable stopgap (which IIUC you have suggested earlier) is to prevent writing a new commit-graph layer with a different hash version than the previous layer.
How does that sound?
Thanks,
Taylor
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 11 lines · Taylor Blau
However, I am not entirely sure I agree with you that this is a "new" issue. At least in the sense that Git (on 'master') does not currently know how to deal with Bloom filters that have different settings across commit-graph layers.
IOW, you could produce this problem today using the test you wrote in <[email protected]> using different values of the
GIT_BLOOM_SETTINGSenvironment variables as a proxy for different values of the commitGraph.changedPathsVersion configuration variable introduced in this series.
Quoted 4 lines
So I think that this series makes it easier to fall into that trap, but the trap itself is not new. I think a reasonable stopgap (which IIUC you have suggested earlier) is to prevent writing a new commit-graph layer with a different hash version than the previous layer.
What we probably want more urgently than that stopgap is to perhaps teach the code pretend as if commit-graph did not exist when we detect multiple layers use different hash versions (or perhaps only use the base layer and ignore the rest as an anti-pessimization), to protect correctness first, no?
Re: [PATCH 07/15] commit-graph: new filter ver. that fixes murmur3
Quoted 18 lines · Junio C Hamano
Quoted 11 lines · Taylor Blau
However, I am not entirely sure I agree with you that this is a "new" issue. At least in the sense that Git (on 'master') does not currently know how to deal with Bloom filters that have different settings across commit-graph layers.
IOW, you could produce this problem today using the test you wrote in <[email protected]> using different values of the
GIT_BLOOM_SETTINGSenvironment variables as a proxy for different values of the commitGraph.changedPathsVersion configuration variable introduced in this series.Quoted 4 lines
So I think that this series makes it easier to fall into that trap, but the trap itself is not new. I think a reasonable stopgap (which IIUC you have suggested earlier) is to prevent writing a new commit-graph layer with a different hash version than the previous layer.
Quoted 5 lines
What we probably want more urgently than that stopgap is to perhaps teach the code pretend as if commit-graph did not exist when we detect multiple layers use different hash versions (or perhaps only use the base layer and ignore the rest as an anti-pessimization), to protect correctness first, no?
Very good suggestion, thanks.
Thanks,
Taylor
PATCH 8/15 bloom: annotate filters with hash version
In reply to Taylor BlauIn subsequent commits, we will want to load existing Bloom filters out of a commit-graph, even when the hash version they were computed with does not match the value of commitGraph.changedPathVersion.
In order to differentiate between the two, add a "version" field to each Bloom filter.
--- bloom.c | 11 ++++++++--- bloom.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-)
bloom.c +8 -3
@@ -55,6 +55,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g, filter->data = (unsigned char *)(g->chunk_bloom_data +sizeof (unsigned char ) * start_index +BLOOMDATA_CHUNK_HEADER_SIZE ); + filter->version = g->bloom_filter_settings->hash_version;return 1 ; }
@@ -240,11 +241,13 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,return strcmp (e1->path, e2->path); } -static void init_truncated_large_filter (struct bloom_filter *filter) +static void init_truncated_large_filter (struct bloom_filter *filter, +int version) { filter->data =xmalloc (1 ); filter->data[0 ] =0xFF ; filter->len =1 ; + filter->version = version; } struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
@@ -329,13 +332,15 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, }if (hashmap_get_size (&pathmap) > settings->max_changed_paths) { -init_truncated_large_filter (filter); +init_truncated_large_filter (filter, + settings->hash_version);if (computed) *computed |=BLOOM_TRUNC_LARGE ; goto cleanup; } filter->len = (hashmap_get_size (&pathmap) * settings->bits_per_entry +BITS_PER_WORD -1 ) /BITS_PER_WORD ; + filter->version = settings->hash_version;if (!filter->len) {if (computed) *computed |=BLOOM_TRUNC_EMPTY ;
@@ -355,7 +360,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, }else {for (i =0 ; i < diff_queued_diff.nr; i++)diff_free_filepair (diff_queued_diff.queue[i]); -init_truncated_large_filter (filter); +init_truncated_large_filter (filter, settings->hash_version);if (computed) *computed |=BLOOM_TRUNC_LARGE ;
bloom.h +1 -0
@@ -55,6 +55,7 @@ struct bloom_filter_settings {struct bloom_filter {unsigned char *data;size_t len; +int version; };/*
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 9/15 bloom: prepare to discard incompatible Bloom filters
In reply to Taylor BlauCallers use the inline get_bloom_filter() implementation as a thin wrapper around get_or_compute_bloom_filter(). The former calls the latter with a value of "0" for compute_if_not_present, making get_bloom_filter() the default read-only path for fetching an existing Bloom filter.
Callers expect the value returned from get_bloom_filter() is usable, that is that it's compatible with the configured value corresponding to commitGraph.changedPathsVersion.
This is OK, since the commit-graph machinery only initializes its BDAT chunk (thereby enabling it to service Bloom filter queries) when the Bloom filter hash_version is compatible with our settings. So any value returned by get_bloom_filter() is trivially useable.
However, subsequent commits will load the BDAT chunk even when the Bloom filters are built with incompatible hash versions. Prepare to handle this by teaching get_bloom_filter() to discard filters that are incompatible with the configured hash version.
Callers who wish to read incompatible filters (e.g., for upgrading filters from v1 to v2) may use the lower level routine, get_or_compute_bloom_filter().
--- bloom.c | 20 +++++++++++++++++++- bloom.h | 20 ++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-)
bloom.c +19 -1
@@ -250,6 +250,23 @@ static void init_truncated_large_filter(struct bloom_filter *filter, filter->version = version; } +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c) +{ +struct bloom_filter *filter; +int hash_version; + + filter =get_or_compute_bloom_filter (r, c,0 ,NULL ,NULL ); +if (!filter) +return NULL ; + +prepare_repo_settings (r); + hash_version = r->settings.commit_graph_changed_paths_version; + +if (!(hash_version ==-1 || hash_version == filter->version)) +return NULL ;/* unusable filter */ +return filter; +} + struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, struct commit *c, int compute_if_not_present,
@@ -275,7 +292,8 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter, graph_pos); } -if (filter->data && filter->len) +if ((filter->data && filter->len) && + (!settings || settings->hash_version == filter->version))return filter;if (!compute_if_not_present)return NULL ;
bloom.h +18 -2
@@ -110,8 +110,24 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,const struct bloom_filter_settings *settings, enum bloom_filter_computed *computed); -#define get_bloom_filter (r, c) get_or_compute_bloom_filter( \ - (r), (c), 0, NULL, NULL) +/* +* Find the Bloom filter associated with the given commit "c". +* +* If any of the following are true +* +* - the repository does not have a commit-graph, or +* - the repository disables reading from the commit-graph, or +* - the given commit does not have a Bloom filter computed, or +* - there is a Bloom filter for commit "c", but it cannot be read +* because the filter uses an incompatible version of murmur3 +* +* , then `get_bloom_filter()` will return NULL. Otherwise, the corresponding +* Bloom filter will be returned. +* +* For callers who wish to inspect Bloom filters with incompatible hash +* versions, use get_or_compute_bloom_filter(). +*/ +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c); int bloom_filter_contains(const struct bloom_filter *filter, const struct bloom_key *key,
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 10/15 t/t4216-log-bloom.sh: harden `test_bloom_filters_not_used()`
In reply to Taylor BlauThe existing implementation of test_bloom_filters_not_used() asserts that the Bloom filter sub-system has not been initialized at all, by checking for the absence of any data from it from trace2.
In the following commit, it will become possible to load Bloom filters without using them (e.g., because commitGraph.changedPathVersion is incompatible with the hash version with which the commit-graph's Bloom filters were written).
When this is the case, it's possible to initialize the Bloom filter sub-system, while still not using any Bloom filters. When this is the case, check that the data dump from the Bloom sub-system is all zeros, indicating that no filters were used.
--- t/t4216-log-bloom.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)
t/t4216-log-bloom.sh +13 -1
@@ -81,7 +81,19 @@ test_bloom_filters_used () { test_bloom_filters_not_used () { log_args=$1 setup "$log_args" && - ! grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" && + + if grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" + then + # if the Bloom filter system is initialized, ensure that no + # filters were used + data="statistics:{" + data="$data\"filter_not_present\":0," + data="$data\"maybe\":0," + data="$data\"definitely_not\":0," + data="$data\"false_positive\":0}" + + grep -q "$data" "$TRASH_DIRECTORY/trace.perf" + fi && test_cmp log_wo_bloom log_w_bloom }
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 11/15 commit-graph.c: unconditionally load Bloom filters
In reply to Taylor BlauIn 9e4df4da07 (commit-graph: new filter ver. that fixes murmur3, 2023-08-01), we began ignoring the Bloom data ("BDAT") chunk for commit-graphs whose Bloom filters were computed using a hash version incompatible with the value of commitGraph.changedPathVersion.
Now that the Bloom API has been hardened to discard these incompatible filters (with the exception of low-level APIs), we can safely load these Bloom filters unconditionally.
We no longer want to return early from graph_read_bloom_data(), and similarly do not want to set the bloom_settings' hash_version field as a side-effect. The latter is because we want to wait until we know which Bloom settings we're using (either the defaults, from the GIT_TEST variables, or from the previous commit-graph layer) before deciding what hash_version to use.
If we detect an existing BDAT chunk, we'll infer the rest of the settings (e.g., number of hashes, bits per entry, and maximum number of changed paths) from the earlier graph layer. The hash_version will be inferred from the previous layer as well, unless one has already been specified via configuration.
Once all of that is done, we normalize the value of the hash_version to either "1" or "2".
--- commit-graph.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-)
commit-graph.c +10 -9
@@ -317,12 +317,6 @@ static int graph_read_bloom_data(const unsigned char *chunk_start,uint32_t hash_version; hash_version =get_be32 (chunk_start); -if (*c->commit_graph_changed_paths_version ==-1 ) { - *c->commit_graph_changed_paths_version = hash_version; - }else if (hash_version != *c->commit_graph_changed_paths_version) { -return 0 ; - } - g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version;
@@ -2408,8 +2402,7 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; - bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 - ?2 :1 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
@@ -2441,10 +2434,18 @@ int write_commit_graph(struct object_directory *odb,/* We have changed-paths already. Keep them in the next graph */ if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; - ctx->bloom_settings = g->bloom_filter_settings; + +/* don't propagate the hash_version unless unspecified */ +if (bloom_settings.hash_version ==-1 ) + bloom_settings.hash_version = g->bloom_filter_settings->hash_version; + bloom_settings.bits_per_entry = g->bloom_filter_settings->bits_per_entry; + bloom_settings.num_hashes = g->bloom_filter_settings->num_hashes; + bloom_settings.max_changed_paths = g->bloom_filter_settings->max_changed_paths; } } + bloom_settings.hash_version = bloom_settings.hash_version ==2 ?2 :1 ; +if (ctx->split) {struct commit_graph *g = ctx->r->objects->commit_graph;
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 12/15 commit-graph: drop unnecessary `graph_read_bloom_data_context`
In reply to Taylor BlauThe graph_read_bloom_data_context struct was introduced in an earlier commit in order to pass pointers to the commit-graph and changed-path Bloom filter version when reading the BDAT chunk.
The previous commit no longer writes through the changed_paths_version pointer, making the surrounding context structure unnecessary. Drop it and pass a pointer to the commit-graph directly when reading the BDAT chunk.
--- commit-graph.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-)
commit-graph.c +2 -12
@@ -304,16 +304,10 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } -struct graph_read_bloom_data_context { -struct commit_graph *g; -int *commit_graph_changed_paths_version; -}; -static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct graph_read_bloom_data_context *c = data; -struct commit_graph *g = c->g; +struct commit_graph *g = data;uint32_t hash_version; hash_version =get_be32 (chunk_start);
@@ -405,14 +399,10 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { -struct graph_read_bloom_data_context context = { - .g = graph, - .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version - };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, &context); + graph_read_bloom_data, graph); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 13/15 object.h: fix mis-aligned flag bits table
In reply to Taylor BlauBit position 23 is one column too far to the left.
--- object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
object.h +1 -1
@@ -62,7 +62,7 @@ void object_array_init(struct object_array *array);/* * object flag allocation: -* revision.h: 0---------10 15 23------27 +* revision.h: 0---------10 15 23------27 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 * walker.c: 0-2
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 14/15 commit-graph: reuse existing Bloom filters where possible
In reply to Taylor BlauIn 9e4df4da07 (commit-graph: new filter ver. that fixes murmur3, 2023-08-01), a bug was described where it's possible for Git to produce non-murmur3 hashes when the platform's "char" type is signed, and there are paths with characters whose highest bit is set (i.e. all characters
Quoted 1 line
=
0x80).
That patch allows the caller to control which version of Bloom filters are read and written. However, even on platforms with a signed "char" type, it is possible to reuse existing Bloom filters if and only if there are no changed paths in any commit's first parent tree-diff whose characters have their highest bit set.
When this is the case, we can reuse the existing filter without having to compute a new one. This is done by marking trees which are known to have (or not have) any such paths. When a commit's root tree is verified to not have any such paths, we mark it as such and declare that the commit's Bloom filter is reusable.
Note that this heuristic only goes in one direction. If neither a commit nor its first parent have any paths in their trees with non-ASCII characters, then we know for certain that a path with non-ASCII characters will not appear in a tree-diff against that commit's first parent. The reverse isn't necessarily true: just because the tree-diff doesn't contain any such paths does not imply that no such paths exist in either tree.
So we end up recomputing some Bloom filters that we don't strictly have to (i.e. their bits are the same no matter which version of murmur3 we use). But culling these out is impossible, since we'd have to perform the full tree-diff, which is the same effort as computing the Bloom filter from scratch.
But because we can cache our results in each tree's flag bits, we can often avoid recomputing many filters, thereby reducing the time it takes to run
$ git commit-graph write --changed-paths --reachable
when upgrading from v1 to v2 Bloom filters.
To benchmark this, let's generate a commit-graph in linux.git with v1 changed-paths in generation order[^1]:
$ git clone [email protected]:torvalds/linux.git $ cd linux $ git commit-graph write --reachable --changed-paths $ graph=".git/objects/info/commit-graph" $ mv $graph{,.bak}
Then let's time how long it takes to go from v1 to v2 filters (with and without the upgrade path enabled), resetting the state of the commit-graph each time:
$ git config commitGraph.changedPathsVersion 2
$ hyperfine -p 'cp -f $graph.bak $graph' -L v 0,1 \
'GIT_TEST_UPGRADE_BLOOM_FILTERS={v} git.compile commit-graph write --reachable --changed-paths'On linux.git (where there aren't any non-ASCII paths), the timings indicate that this patch represents a speed-up over recomputing all Bloom filters from scratch:
Benchmark 1: GIT_TEST_UPGRADE_BLOOM_FILTERS=0 git.compile commit-graph write --reachable --changed-paths
Time (mean ± σ): 124.873 s ± 0.316 s [User: 124.081 s, System: 0.643 s]
Range (min … max): 124.621 s … 125.227 s 3 runs
Benchmark 2: GIT_TEST_UPGRADE_BLOOM_FILTERS=1 git.compile commit-graph write --reachable --changed-paths
Time (mean ± σ): 79.271 s ± 0.163 s [User: 74.611 s, System: 4.521 s]
Range (min … max): 79.112 s … 79.437 s 3 runs
Summary
'GIT_TEST_UPGRADE_BLOOM_FILTERS=1 git.compile commit-graph write --reachable --changed-paths' ran
1.58 ± 0.01 times faster than 'GIT_TEST_UPGRADE_BLOOM_FILTERS=0 git.compile commit-graph write --reachable --changed-paths'On git.git, we do have some non-ASCII paths, giving us a more modest improvement from 4.163 seconds to 3.348 seconds, for a 1.24x speed-up. On my machine, the stats for git.git are:
- 8,285 Bloom filters computed from scratch
- 10 Bloom filters generated as empty
- 4 Bloom filters generated as truncated due to too many changed paths
- 65,114 Bloom filters were reused when transitioning from v1 to v2.
[^1]: Note that this is is important, since --stdin-packs or
--stdin-commits orders commits in the commit-graph by their pack position (with --stdin-packs) or in the raw input (with --stdin-commits).
Since we compute Bloom filters in the same order that commits appear in the graph, we must see a commit's (first) parent before we process the commit itself. This is only guaranteed to happen when sorting commits by their generation number.
--- bloom.c | 90 ++++++++++++++++++++++++++++++++++++++++++-- bloom.h | 1 + commit-graph.c | 5 +++ object.h | 1 + t/t4216-log-bloom.sh | 35 ++++++++++++++++- 5 files changed, 127 insertions(+), 5 deletions(-)
bloom.c +87 -3
@@ -7,6 +7,9 @@#include "commit-graph.h" #include "commit.h" #include "commit-slab.h" +#include "tree.h" +#include "tree-walk.h" +#include "config.h" define_commit_slab(bloom_filter_slab, struct bloom_filter);
@@ -250,6 +253,73 @@ static void init_truncated_large_filter(struct bloom_filter *filter, filter->version = version; } +#define VISITED (1u<<21) +#define HIGH_BITS (1u<<22) + +static int has_entries_with_high_bit (struct repository *r,struct tree *t) +{ +if (parse_tree (t)) +return 1 ; + +if (!(t->object.flags &VISITED )) { +struct tree_desc desc; +struct name_entry entry; + +init_tree_desc (&desc, t->buffer, t->size); +while (tree_entry (&desc, &entry)) { +size_t i; +for (i =0 ; i < entry.pathlen; i++) { +if (entry.path[i] &0x80 ) { + t->object.flags |=HIGH_BITS ; + goto done; + } + } + +if (S_ISDIR (entry.mode)) { +struct tree *sub =lookup_tree (r, &entry.oid); +if (sub &&has_entries_with_high_bit (r, sub)) { + t->object.flags |=HIGH_BITS ; + goto done; + } + } + + } + +done: + t->object.flags |=VISITED ; + } + +return !!(t->object.flags &HIGH_BITS ); +} + +static int commit_tree_has_high_bit_paths (struct repository *r, +struct commit *c) +{ +struct tree *t; +if (repo_parse_commit (r, c)) +return 1 ; + t =repo_get_commit_tree (r, c); +if (!t) +return 1 ; +return has_entries_with_high_bit (r, t); +} + +static struct bloom_filter *upgrade_filter (struct repository *r,struct commit *c, +struct bloom_filter *filter, +int hash_version) +{ +struct commit_list *p = c->parents; +if (commit_tree_has_high_bit_paths (r, c)) +return NULL ; + +if (p &&commit_tree_has_high_bit_paths (r, p->item)) +return NULL ; + + filter->version = hash_version; + +return filter; +} +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c) {struct bloom_filter *filter;
@@ -292,9 +362,23 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter, graph_pos); } -if ((filter->data && filter->len) && - (!settings || settings->hash_version == filter->version)) -return filter; +if (filter->data && filter->len) { +struct bloom_filter *upgrade; +if (!settings || settings->hash_version == filter->version) +return filter; + +/* version mismatch, see if we can upgrade */ +if (compute_if_not_present && +git_env_bool ("GIT_TEST_UPGRADE_BLOOM_FILTERS" ,1 )) { + upgrade =upgrade_filter (r, c, filter, + settings->hash_version); +if (upgrade) { +if (computed) + *computed |=BLOOM_UPGRADED ; +return upgrade; + } + } + }if (!compute_if_not_present)return NULL ;
bloom.h +1 -0
@@ -102,6 +102,7 @@ enum bloom_filter_computed {BLOOM_COMPUTED = (1 <<1 ),BLOOM_TRUNC_LARGE = (1 <<2 ),BLOOM_TRUNC_EMPTY = (1 <<3 ), + BLOOM_UPGRADED = (1 << 4), }; struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
commit-graph.c +5 -0
@@ -1045,6 +1045,7 @@ struct write_commit_graph_context {int count_bloom_filter_not_computed;int count_bloom_filter_trunc_empty;int count_bloom_filter_trunc_large; +int count_bloom_filter_upgraded; }; static int write_graph_chunk_fanout(struct hashfile *f,
@@ -1651,6 +1652,8 @@ static void trace2_bloom_filter_write_statistics(struct write_commit_graph_conte ctx->count_bloom_filter_trunc_empty);trace2_data_intmax ("commit-graph" , ctx->r,"filter-trunc-large" , ctx->count_bloom_filter_trunc_large); +trace2_data_intmax ("commit-graph" , ctx->r,"filter-upgraded" , + ctx->count_bloom_filter_upgraded); }static void compute_bloom_filters (struct write_commit_graph_context *ctx)
@@ -1692,6 +1695,8 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx) ctx->count_bloom_filter_trunc_empty++;if (computed &BLOOM_TRUNC_LARGE ) ctx->count_bloom_filter_trunc_large++; + }else if (computed &BLOOM_UPGRADED ) { + ctx->count_bloom_filter_upgraded++; }else if (computed &BLOOM_NOT_COMPUTED ) ctx->count_bloom_filter_not_computed++; ctx->total_bloom_filter_data_size += filter
object.h +1 -0
@@ -75,6 +75,7 @@ void object_array_init(struct object_array *array);* commit-reach.c: 16-----19 * sha1-name.c: 20 * list-objects-filter.c: 21 +* bloom.c: 2122 * builtin/fsck.c: 0--3 * builtin/gc.c: 0 * builtin/index-pack.c: 2021
t/t4216-log-bloom.sh +33 -2
@@ -217,6 +217,10 @@ test_filter_trunc_large () { grep "\"key\":\"filter-trunc-large\",\"value\":\"$1\"" $2 } +test_filter_upgraded () { + grep "\"key\":\"filter-upgraded\",\"value\":\"$1\"" $2 +} + test_expect_success 'correctly report changes over limit' ' git init limits && (
@@ -543,10 +547,19 @@ test_expect_success 'when writing another commit graph, preserve existing versio test_expect_success 'when writing commit graph, do not reuse changed-path of another version' ' git init doublewrite && test_commit -C doublewrite c "$CENT" && + git -C doublewrite config --add commitgraph.changedPathsVersion 1 && - git -C doublewrite commit-graph write --reachable --changed-paths && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C doublewrite commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + git -C doublewrite config --add commitgraph.changedPathsVersion 2 && - git -C doublewrite commit-graph write --reachable --changed-paths && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C doublewrite commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + ( cd doublewrite && echo "c01f" >expect &&
@@ -555,4 +568,22 @@ test_expect_success 'when writing commit graph, do not reuse changed-path of ano ) ' +test_expect_success 'when writing commit graph, reuse changed-path of another version where possible' ' + git init upgrade && + + test_commit -C upgrade base no-high-bits && + + git -C upgrade config --add commitgraph.changedPathsVersion 1 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C upgrade commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + + git -C upgrade config --add commitgraph.changedPathsVersion 2 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C upgrade commit-graph write --reachable --changed-paths && + test_filter_computed 0 trace2.txt && + test_filter_upgraded 1 trace2.txt +' + test_done
Signature · 1 line
2.42.0.4.g52b49bb434
PATCH 15/15 bloom: introduce `deinit_bloom_filters()`
In reply to Taylor BlauAfter we are done using Bloom filters, we do not currently clean up any memory allocated by the commit slab used to store those filters in the first place.
Besides the bloom_filter structures themselves, there is mostly nothing to free() in the first place, since in the read-only path all Bloom filter's data members point to a memory mapped region in the commit-graph file itself.
But when generating Bloom filters from scratch (or initializing truncated filters) we allocate additional memory to store the filter's data.
Keep track of when we need to free() this additional chunk of memory by using an extra pointer to_free. Most of the time this will be NULL (indicating that we are representing an existing Bloom filter stored in a memory mapped region). When it is non-NULL, free it before discarding the Bloom filters slab.
--- bloom.c | 16 +++++++++++++++- bloom.h | 3 +++ commit-graph.c | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-)
bloom.c +15 -1
@@ -59,6 +59,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,sizeof (unsigned char ) * start_index + BLOOMDATA_CHUNK_HEADER_SIZE); filter->version = g->bloom_filter_settings->hash_version; + filter->to_free =NULL ;return 1 ; }
@@ -231,6 +232,18 @@ void init_bloom_filters(void)init_bloom_filter_slab (&bloom_filters); } +static void free_one_bloom_filter (struct bloom_filter *filter) +{ +if (!filter) +return ; +free (filter->to_free); +} + +void deinit_bloom_filters (void ) +{ +deep_clear_bloom_filter_slab (&bloom_filters, free_one_bloom_filter); +} + static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key,
@@ -247,7 +260,7 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,static void init_truncated_large_filter (struct bloom_filter *filter,int version) { - filter->data =xmalloc (1 ); + filter->data = filter->to_free =xmalloc (1 ); filter->data[0 ] =0xFF ; filter->len =1 ; filter->version = version;
@@ -449,6 +462,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter->len =1 ; }CALLOC_ARRAY (filter->data, filter->len); + filter->to_free = filter->data;hashmap_for_each_entry (&pathmap, &iter, e, entry) {struct bloom_key key;
bloom.h +3 -0
@@ -56,6 +56,8 @@ struct bloom_filter {unsigned char *data;size_t len;int version; + +void *to_free; };/*
@@ -96,6 +98,7 @@ void add_key_to_filter(const struct bloom_key *key, const struct bloom_filter_settings *settings);void init_bloom_filters (void ); +void deinit_bloom_filters (void );enum bloom_filter_computed {BLOOM_NOT_COMPUTED = (1 <<0 ),
commit-graph.c +4 -0
@@ -723,6 +723,7 @@ static void close_commit_graph_one(struct commit_graph *g)void close_commit_graph (struct raw_object_store *o) {close_commit_graph_one (o->commit_graph); +deinit_bloom_filters (); o->commit_graph =NULL ; }
@@ -2523,6 +2524,9 @@ int write_commit_graph(struct object_directory *odb, res =write_commit_graph_file (ctx); +if (ctx->changed_paths) +deinit_bloom_filters (); +if (ctx->split)mark_commit_graphs (ctx);
Signature · 1 line
2.42.0.4.g52b49bb434
Quoted 6 lines · Taylor Blau
Jonathan Tan (4):
gitformat-commit-graph: describe version 2 of BDAT
t4216: test changed path filters with high bit paths
repo-settings: introduce commitgraph.changedPathsVersion
commit-graph: new filter ver. that fixes murmur3
Quoted 12 lines
Taylor Blau (11):
t/helper/test-read-graph.c: extractdump_graph_info()bloom.h: makeload_bloom_filter_from_graph()public t/helper/test-read-graph: implementbloom-filtersmode
bloom: annotate filters with hash version
bloom: prepare to discard incompatible Bloom filterst/t4216-log-bloom.sh: hardentest_bloom_filters_not_used()commit-graph.c: unconditionally load Bloom filters
commit-graph: drop unnecessarygraph_read_bloom_data_contextobject.h: fix mis-aligned flag bits table
commit-graph: reuse existing Bloom filters where possible
bloom: introducedeinit_bloom_filters()
Thanks. I had one small comment (sent as an email reply to one of the patches), but everything else looks good.
Quoted 20 lines · Jonathan Tan
Quoted 6 lines · Taylor Blau
Jonathan Tan (4):
gitformat-commit-graph: describe version 2 of BDAT
t4216: test changed path filters with high bit paths
repo-settings: introduce commitgraph.changedPathsVersion
commit-graph: new filter ver. that fixes murmur3Quoted 12 lines
Taylor Blau (11):
t/helper/test-read-graph.c: extractdump_graph_info()bloom.h: makeload_bloom_filter_from_graph()public t/helper/test-read-graph: implementbloom-filtersmode
bloom: annotate filters with hash version
bloom: prepare to discard incompatible Bloom filterst/t4216-log-bloom.sh: hardentest_bloom_filters_not_used()commit-graph.c: unconditionally load Bloom filters
commit-graph: drop unnecessarygraph_read_bloom_data_contextobject.h: fix mis-aligned flag bits table
commit-graph: reuse existing Bloom filters where possible
bloom: introducedeinit_bloom_filters()
Quoted 2 lines
Thanks. I had one small comment (sent as an email reply to one of the patches), but everything else looks good.
I mistakenly sent my reply to an earlier version [1]. (Taylor has seen it, so this note is more for future readers who might be curious about what that email reply contains.)
Quoted 5 lines · Jonathan Tan
I mistakenly sent my reply to an earlier version [1]. (Taylor has seen it, so this note is more for future readers who might be curious about what that email reply contains.)
I think that this patch set is good for merging. There is a discussion between Taylor and me at the link above in which Taylor thinks something is OK and I think that that thing is OK except in a very specific situation (there exists a commit-graph chain in which the different layers have different Bloom filter versions). (Hopefully that's a good summary of the discussion.) I think that the situation is narrow enough and have seen that Taylor (whose capabilities I have a high opinion of) has looked into it. I think the only way for me to be fully convinced is to write a test that includes these different layers and see how the code functions.
So I think that we can merge this patch set. An alternative would be to wait for me (or someone else) to write such a test, but if it's just me that's worried about this situation, it's probably not worth waiting just for this.
The other outstanding thing is that Szeder Gabor pointed out that Bloom filters are not applied to root commits so some of the tests don't test what you would expect [2]. I've updated the tests and pushed the results to GitHub [3]. I'm OK with using that version, or the current version (in which case I'll resend the updated tests once this version is merged).
[2] https://lore.kernel.org/git/[email protected]/
[3] https://github.com/jonathantanmy/git/tree/changedpath
Quoted 12 lines · Jonathan Tan
So I think that we can merge this patch set. An alternative would be to wait for me (or someone else) to write such a test, but if it's just me that's worried about this situation, it's probably not worth waiting just for this.
The other outstanding thing is that Szeder Gabor pointed out that Bloom filters are not applied to root commits so some of the tests don't test what you would expect [2]. I've updated the tests and pushed the results to GitHub [3]. I'm OK with using that version, or the current version (in which case I'll resend the updated tests once this version is merged).
Quoted 2 lines
[2] https://lore.kernel.org/git/[email protected]/
[3] https://github.com/jonathantanmy/git/tree/changedpath
Thanks for a concise and well thought out summary.
If we know that some tests in the current round is not doing the right thing, and we already have an updated set of tests to fix them, I doubt that the topic is so urgent that merging a known to be incomplete version is preferrable than seeing the hopefully final version on the list that everybody can agree with.
Thanks.
v2 cover bloom: changed-path Bloom filters v2
In reply to Taylor BlauHere's an updated patch set containing updates to the tests, making them run on non-root commits to ensure that we exercise the Bloom filters. Thanks to SZEDER Gábor for spotting this.
Jonathan Tan (4):
gitformat-commit-graph: describe version 2 of BDAT
t4216: test changed path filters with high bit paths
repo-settings: introduce commitgraph.changedPathsVersion
commit-graph: new filter ver. that fixes murmur3
Taylor Blau (11):
t/helper/test-read-graph.c: extract dump_graph_info() bloom.h: make load_bloom_filter_from_graph() public t/helper/test-read-graph: implement bloom-filters mode
bloom: annotate filters with hash version
bloom: prepare to discard incompatible Bloom filters t/t4216-log-bloom.sh: harden test_bloom_filters_not_used() commit-graph.c: unconditionally load Bloom filters
commit-graph: drop unnecessary graph_read_bloom_data_context object.h: fix mis-aligned flag bits table
commit-graph: reuse existing Bloom filters where possible
bloom: introduce deinit_bloom_filters()
Documentation/config/commitgraph.txt | 26 ++- Documentation/gitformat-commit-graph.txt | 9 +- bloom.c | 208 +++++++++++++++++++++-- bloom.h | 38 ++++- commit-graph.c | 36 +++- object.h | 3 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +- repository.h | 2 +- t/helper/test-bloom.c | 9 +- t/helper/test-read-graph.c | 65 +++++-- t/t0095-bloom.sh | 8 + t/t4216-log-bloom.sh | 202 +++++++++++++++++++++- 13 files changed, 564 insertions(+), 50 deletions(-)
Range-diff against v1:
1: dcfc987741 = 1: dcfc987741 gitformat-commit-graph: describe version 2 of BDAT
2: 0c56f2a9e9 = 2: 0c56f2a9e9 t/helper/test-read-graph.c: extract `dump_graph_info()`
3: 8405b845e5 = 3: 8405b845e5 bloom.h: make `load_bloom_filter_from_graph()` public
4: 3a25f90c15 = 4: 3a25f90c15 t/helper/test-read-graph: implement `bloom-filters` mode
5: e300d338e1 ! 5: 7858010665 t4216: test changed path filters with high bit paths
@@ t/t4216-log-bloom.sh: test_expect_success 'Bloom generation backfills empty comm
+ true
+'
+
++test_expect_success 'setup make another commit' '
++ # "git log" does not use Bloom filters for root commits - see how, in
++ # revision.c, rev_compare_tree() (the only code path that eventually calls
++ # get_bloom_filter()) is only called by try_to_simplify_commit() when the commit
++ # has one parent. Therefore, make another commit so that we perform the tests on
++ # a non-root commit.
++ test_commit -C highbit1 anotherc1 "another$CENT"
++'
++
+test_expect_success 'version 1 changed-path used when version 1 requested' '
+ (
+ cd highbit1 &&
-+ test_bloom_filters_used "-- $CENT"
++ test_bloom_filters_used "-- another$CENT"
+ )
+'
+
6: 6bc665e1d3 = 6: 94ad289dbb repo-settings: introduce commitgraph.changedPathsVersion
7: 7ef3b2bbbd ! 7: 44d3163125 commit-graph: new filter ver. that fixes murmur3
@@ t/t4216-log-bloom.sh: test_expect_success 'version 1 changed-path used when vers
+ (
+ cd highbit1 &&
+ git config --add commitgraph.changedPathsVersion 2 &&
-+ test_bloom_filters_not_used "-- $CENT"
++ test_bloom_filters_not_used "-- another$CENT"
+ )
+'
+
@@ t/t4216-log-bloom.sh: test_expect_success 'version 1 changed-path used when vers
+ (
+ cd highbit1 &&
+ git config --add commitgraph.changedPathsVersion -1 &&
-+ test_bloom_filters_used "-- $CENT"
++ test_bloom_filters_used "-- another$CENT"
+ )
+'
+
@@ t/t4216-log-bloom.sh: test_expect_success 'version 1 changed-path used when vers
+ )
+'
+
++test_expect_success 'setup make another commit' '
++ # "git log" does not use Bloom filters for root commits - see how, in
++ # revision.c, rev_compare_tree() (the only code path that eventually calls
++ # get_bloom_filter()) is only called by try_to_simplify_commit() when the commit
++ # has one parent. Therefore, make another commit so that we perform the tests on
++ # a non-root commit.
++ test_commit -C highbit2 anotherc2 "another$CENT"
++'
++
+test_expect_success 'version 2 changed-path used when version 2 requested' '
+ (
+ cd highbit2 &&
-+ test_bloom_filters_used "-- $CENT"
++ test_bloom_filters_used "-- another$CENT"
+ )
+'
+
@@ t/t4216-log-bloom.sh: test_expect_success 'version 1 changed-path used when vers
+ (
+ cd highbit2 &&
+ git config --add commitgraph.changedPathsVersion 1 &&
-+ test_bloom_filters_not_used "-- $CENT"
++ test_bloom_filters_not_used "-- another$CENT"
+ )
+'
+
@@ t/t4216-log-bloom.sh: test_expect_success 'version 1 changed-path used when vers
+ (
+ cd highbit2 &&
+ git config --add commitgraph.changedPathsVersion -1 &&
-+ test_bloom_filters_used "-- $CENT"
++ test_bloom_filters_used "-- another$CENT"
+ )
+'
+
8: 302caee39d = 8: a5bf23a7d3 bloom: annotate filters with hash version
9: d2b0726266 = 9: 3de6cd8460 bloom: prepare to discard incompatible Bloom filters
10: 9a9992220f = 10: ef04389a0e t/t4216-log-bloom.sh: harden `test_bloom_filters_not_used()`
11: 607945ab05 = 11: e0c1c1ccec commit-graph.c: unconditionally load Bloom filters
12: e397d83895 = 12: 4d57f51854 commit-graph: drop unnecessary `graph_read_bloom_data_context`
13: bd3ad6b6c0 = 13: a3b4e7ef59 object.h: fix mis-aligned flag bits table
14: 2996f0fdb6 = 14: 05357f9533 commit-graph: reuse existing Bloom filters where possible
15: a3b5b22db0 = 15: 58a1d90e6d bloom: introduce `deinit_bloom_filters()`Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 1/15 gitformat-commit-graph: describe version 2 of BDAT
The code change to Git to support version 2 will be done in subsequent commits.
--- Documentation/gitformat-commit-graph.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
Documentation/gitformat-commit-graph.txt +6 -3
@@ -142,13 +142,16 @@ All multi-byte numbers are in network byte order. ==== Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional] * It starts with header consisting of three unsigned 32-bit integers: - - Version of the hash algorithm being used. We currently only support - value 1 which corresponds to the 32-bit version of the murmur3 hash + - Version of the hash algorithm being used. We currently support + value 2 which corresponds to the 32-bit version of the murmur3 hash implemented exactly as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm and the double hashing technique using seed values 0x293ae76f and 0x7e646e2 as described in https://doi.org/10.1007/978-3-540-30494-4_26 "Bloom Filters - in Probabilistic Verification" + in Probabilistic Verification". Version 1 Bloom filters have a bug that appears + when char is signed and the repository has path names that have characters >= + 0x80; Git supports reading and writing them, but this ability will be removed + in a future version of Git. - The number of times a path is hashed and hence the number of bit positions that cumulatively determine whether a file is present in the commit. - The minimum number of bits 'b' per entry in the Bloom filter. If the filter
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 2/15 t/helper/test-read-graph.c: extract `dump_graph_info()`
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
Prepare for the 'read-graph' test helper to perform other tasks besides dumping high-level information about the commit-graph by extracting its main routine into a separate function.
--- t/helper/test-read-graph.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-)
t/helper/test-read-graph.c +18 -13
@@ -5,20 +5,8 @@#include "bloom.h" #include "setup.h" -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_info (struct commit_graph *graph) { -struct commit_graph *graph =NULL ; -struct object_directory *odb; - -setup_git_directory (); - odb = the_repository->objects->odb; - -prepare_repo_settings (the_repository); - - graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; - printf("header: %08x %d %d %d %d\n", ntohl(*(uint32_t*)graph->data), *(unsigned char*)(graph->data + 4),
@@ -57,6 +45,23 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)if (graph->topo_levels)printf (" topo_levels" );printf ("\n" ); +} + +int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +{ +struct commit_graph *graph =NULL ; +struct object_directory *odb; + +setup_git_directory (); + odb = the_repository->objects->odb; + +prepare_repo_settings (the_repository); + + graph =read_commit_graph_one (the_repository, odb); +if (!graph) +return 1 ; + +dump_graph_info (graph);UNLEAK (graph);
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 3/15 bloom.h: make `load_bloom_filter_from_graph()` public
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
Prepare for a future commit to use the load_bloom_filter_from_graph() function directly to load specific Bloom filters out of the commit-graph for manual inspection (to be used during tests).
--- bloom.c | 6 +++--- bloom.h | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-)
bloom.c +3 -3
@@ -29,9 +29,9 @@ static inline unsigned char get_bitmask(uint32_t pos)return ((unsigned char )1 ) << (pos & (BITS_PER_WORD -1 )); } -static int load_bloom_filter_from_graph (struct commit_graph *g, -struct bloom_filter *filter, -uint32_t graph_pos) +int load_bloom_filter_from_graph (struct commit_graph *g, +struct bloom_filter *filter, +uint32_t graph_pos) {uint32_t lex_pos, start_index, end_index;
bloom.h +5 -0
@@ -3,6 +3,7 @@struct commit ;struct repository ; +struct commit_graph ;struct bloom_filter_settings {/*
@@ -68,6 +69,10 @@ struct bloom_key {uint32_t *hashes; }; +int load_bloom_filter_from_graph (struct commit_graph *g, +struct bloom_filter *filter, +uint32_t graph_pos); +/* * Calculate the murmur3 32-bit hash value for the given data * using the given seed.
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 4/15 t/helper/test-read-graph: implement `bloom-filters` mode
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
Implement a mode of the "read-graph" test helper to dump out the hexadecimal contents of the Bloom filter(s) contained in a commit-graph.
--- t/helper/test-read-graph.c | 42 +++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-)
t/helper/test-read-graph.c +37 -5
@@ -47,10 +47,32 @@ static void dump_graph_info(struct commit_graph *graph)printf ("\n" ); } -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_bloom_filters (struct commit_graph *graph) +{ +uint32_t i; + +for (i =0 ; i < graph->num_commits + graph->num_commits_in_base; i++) { +struct bloom_filter filter = {0 }; +size_t j; + +if (load_bloom_filter_from_graph (graph, &filter, i) <0 ) { +fprintf (stderr,"missing Bloom filter for graph " +"position %" PRIu32"\n" , i); +continue ; + } + +for (j =0 ; j < filter.len; j++) +printf ("%02x" , filter.data[j]); +if (filter.len) +printf ("\n" ); + } +} + +int cmd__read_graph (int argc,const char **argv) {struct commit_graph *graph =NULL ;struct object_directory *odb; +int ret =0 ;setup_git_directory (); odb = the_repository->objects->odb;
@@ -58,12 +80,22 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)prepare_repo_settings (the_repository); graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; +if (!graph) { + ret =1 ; + goto done; + } -dump_graph_info (graph); +if (argc <=1 ) +dump_graph_info (graph); +else if (!strcmp (argv[1 ],"bloom-filters" )) +dump_graph_bloom_filters (graph); +else { +fprintf (stderr,"unknown sub-command: '%s'\n" , argv[1 ]); + ret =1 ; + } +done:UNLEAK (graph); -return 0 ; +return ret; }
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 5/15 t4216: test changed path filters with high bit paths
In reply to Jonathan TanSubsequent commits will teach Git another version of changed path filter that has different behavior with paths that contain at least one character with its high bit set, so test the existing behavior as a baseline.
--- t/t4216-log-bloom.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
t/t4216-log-bloom.sh +52 -0
@@ -404,4 +404,56 @@ test_expect_success 'Bloom generation backfills empty commits' ' ) ' +get_first_changed_path_filter () { + test-tool read-graph bloom-filters >filters.dat && + head -n 1 filters.dat +} + +# chosen to be the same under all Unicode normalization forms +CENT=$(printf "\302\242") + +test_expect_success 'set up repo with high bit path, version 1 changed-path' ' + git init highbit1 && + test_commit -C highbit1 c1 "$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths +' + +test_expect_success 'setup check value of version 1 changed-path' ' + ( + cd highbit1 && + echo "52a9" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +# expect will not match actual if char is unsigned by default. Write the test +# in this way, so that a user running this test script can still see if the two +# files match. (It will appear as an ordinary success if they match, and a skip +# if not.) +if test_cmp highbit1/expect highbit1/actual +then + test_set_prereq SIGNED_CHAR_BY_DEFAULT +fi +test_expect_success SIGNED_CHAR_BY_DEFAULT 'check value of version 1 changed-path' ' + # Only the prereq matters for this test. + true +' + +test_expect_success 'setup make another commit' ' + # "git log" does not use Bloom filters for root commits - see how, in + # revision.c, rev_compare_tree() (the only code path that eventually calls + # get_bloom_filter()) is only called by try_to_simplify_commit() when the commit + # has one parent. Therefore, make another commit so that we perform the tests on + # a non-root commit. + test_commit -C highbit1 anotherc1 "another$CENT" +' + +test_expect_success 'version 1 changed-path used when version 1 requested' ' + ( + cd highbit1 && + test_bloom_filters_used "-- another$CENT" + ) +' + test_done
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 6/15 repo-settings: introduce commitgraph.changedPathsVersion
In reply to Jonathan TanA subsequent commit will introduce another version of the changed-path filter in the commit graph file. In order to control which version to write (and read), a config variable is needed.
Therefore, introduce this config variable. For forwards compatibility, teach Git to not read commit graphs when the config variable is set to an unsupported version. Because we teach Git this, commitgraph.readChangedPaths is now redundant, so deprecate it and define its behavior in terms of the config variable we introduce.
This commit does not change the behavior of writing (Git writes changed path filters when explicitly instructed regardless of any config variable), but a subsequent commit will restrict Git such that it will only write when commitgraph.changedPathsVersion is a recognized value.
--- Documentation/config/commitgraph.txt | 23 ++++++++++++++++++++--- commit-graph.c | 2 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +++++- repository.h | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-)
Documentation/config/commitgraph.txt +20 -3
@@ -9,6 +9,23 @@ commitGraph.maxNewFilters:: commit-graph write` (c.f., linkgit:git-commit-graph[1]). commitGraph.readChangedPaths:: - If true, then git will use the changed-path Bloom filters in the - commit-graph file (if it exists, and they are present). Defaults to - true. See linkgit:git-commit-graph[1] for more information. + Deprecated. Equivalent to commitGraph.changedPathsVersion=-1 if true, and + commitGraph.changedPathsVersion=0 if false. (If commitGraph.changedPathVersion + is also set, commitGraph.changedPathsVersion takes precedence.) + +commitGraph.changedPathsVersion:: + Specifies the version of the changed-path Bloom filters that Git will read and + write. May be -1, 0 or 1. ++ +Defaults to -1. ++ +If -1, Git will use the version of the changed-path Bloom filters in the +repository, defaulting to 1 if there are none. ++ +If 0, Git will not read any Bloom filters, and will write version 1 Bloom +filters when instructed to write. ++ +If 1, Git will only read version 1 Bloom filters, and will write version 1 +Bloom filters. ++ +See linkgit:git-commit-graph[1] for more information.
commit-graph.c +1 -1
@@ -401,7 +401,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, graph->read_generation_data =1 ; } -if (s->commit_graph_read_changed_paths) { +if (s->commit_graph_changed_paths_version) {pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes); read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA,
oss-fuzz/fuzz-commit-graph.c +1 -1
@@ -19,7 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)* possible. */ the_repository->settings.commit_graph_generation_version =2 ; - the_repository->settings.commit_graph_read_changed_paths =1 ; + the_repository->settings.commit_graph_changed_paths_version =1 ; g =parse_commit_graph (&the_repository->settings, (void *)data, size);repo_clear (the_repository);free_commit_graph (g);
repo-settings.c +5 -1
@@ -24,6 +24,7 @@ void prepare_repo_settings(struct repository *r)int value;const char *strval;int manyfiles; +int read_changed_paths;if (!r->gitdir)BUG ("Cannot add settings for uninitialized repository" );
@@ -54,7 +55,10 @@ void prepare_repo_settings(struct repository *r)/* Commit graph config or default, does not cascade (simple) */ repo_cfg_bool (r,"core.commitgraph" , &r->settings.core_commit_graph,1 );repo_cfg_int (r,"commitgraph.generationversion" , &r->settings.commit_graph_generation_version,2 ); -repo_cfg_bool (r,"commitgraph.readchangedpaths" , &r->settings.commit_graph_read_changed_paths,1 ); +repo_cfg_bool (r,"commitgraph.readchangedpaths" , &read_changed_paths,1 ); +repo_cfg_int (r,"commitgraph.changedpathsversion" , + &r->settings.commit_graph_changed_paths_version, + read_changed_paths ?-1 :0 );repo_cfg_bool (r,"gc.writecommitgraph" , &r->settings.gc_write_commit_graph,1 );repo_cfg_bool (r,"fetch.writecommitgraph" , &r->settings.fetch_write_commit_graph,0 );
repository.h +1 -1
@@ -29,7 +29,7 @@ struct repo_settings {int core_commit_graph;int commit_graph_generation_version; -int commit_graph_read_changed_paths; +int commit_graph_changed_paths_version;int gc_write_commit_graph;int fetch_write_commit_graph;int command_requires_full_index;
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 7/15 commit-graph: new filter ver. that fixes murmur3
In reply to Jonathan TanThe murmur3 implementation in bloom.c has a bug when converting series of 4 bytes into network-order integers when char is signed (which is controllable by a compiler option, and the default signedness of char is platform-specific). When a string contains characters with the high bit set, this bug causes results that, although internally consistent within Git, does not accord with other implementations of murmur3 (thus, the changed path filters wouldn't be readable by other off-the-shelf implementatios of murmur3) and even with Git binaries that were compiled with different signedness of char. This bug affects both how Git writes changed path filters to disk and how Git interprets changed path filters on disk.
Therefore, introduce a new version (2) of changed path filters that corrects this problem. The existing version (1) is still supported and is still the default, but users should migrate away from it as soon as possible.
Because this bug only manifests with characters that have the high bit set, it may be possible that some (or all) commits in a given repo would have the same changed path filter both before and after this fix is applied. However, in order to determine whether this is the case, the changed paths would first have to be computed, at which point it is not much more expensive to just compute a new changed path filter.
So this patch does not include any mechanism to "salvage" changed path filters from repositories. There is also no "mixed" mode - for each invocation of Git, reading and writing changed path filters are done with the same version number; this version number may be explicitly stated (typically if the user knows which version they need) or automatically determined from the version of the existing changed path filters in the repository.
There is a change in write_commit_graph(). graph_read_bloom_data() makes it possible for chunk_bloom_data to be non-NULL but bloom_filter_settings to be NULL, which causes a segfault later on. I produced such a segfault while developing this patch, but couldn't find a way to reproduce it neither after this complete patch (or before), but in any case it seemed like a good thing to include that might help future patch authors.
The value in t0095 was obtained from another murmur3 implementation using the following Go source code:
package main
import "fmt"
import "github.com/spaolacci/murmur3"
func main() {
fmt.Printf("%x\n", murmur3.Sum32([]byte("Hello world!")))
fmt.Printf("%x\n", murmur3.Sum32([]byte{0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}))
}--- Documentation/config/commitgraph.txt | 5 +- bloom.c | 69 +++++++++++++++++- bloom.h | 8 +- commit-graph.c | 32 ++++++-- t/helper/test-bloom.c | 9 ++- t/t0095-bloom.sh | 8 ++ t/t4216-log-bloom.sh | 105 +++++++++++++++++++++++++++ 7 files changed, 223 insertions(+), 13 deletions(-)
Documentation/config/commitgraph.txt +4 -1
@@ -15,7 +15,7 @@ commitGraph.readChangedPaths:: commitGraph.changedPathsVersion:: Specifies the version of the changed-path Bloom filters that Git will read and - write. May be -1, 0 or 1. + write. May be -1, 0, 1, or 2. + Defaults to -1. +
@@ -28,4 +28,7 @@ filters when instructed to write. If 1, Git will only read version 1 Bloom filters, and will write version 1 Bloom filters. + +If 2, Git will only read version 2 Bloom filters, and will write version 2 +Bloom filters. ++ See linkgit:git-commit-graph[1] for more information.
bloom.c +66 -3
81 lines not on this page · raw message
bloom.h +5 -3
@@ -8,9 +8,11 @@ struct commit_graph;struct bloom_filter_settings {/* * The version of the hashing technique being used. -* We currently only support version = 1 which is +* The newest version is 2, which is * the seeded murmur3 hashing technique implemented -* in bloom.c. +* in bloom.c. Bloom filters of version 1 were created +* with prior versions of Git, which had a bug in the +* implementation of the hash function. */ uint32_t hash_version;
@@ -80,7 +82,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len); +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len); void fill_bloom_key(const char *data, size_t len,
commit-graph.c +27 -5
@@ -304,17 +304,26 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } +struct graph_read_bloom_data_context { +struct commit_graph *g; +int *commit_graph_changed_paths_version; +}; +static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct commit_graph *g = data; +struct graph_read_bloom_data_context *c = data; +struct commit_graph *g = c->g;uint32_t hash_version; - g->chunk_bloom_data = chunk_start; hash_version =get_be32 (chunk_start); -if (hash_version !=1 ) +if (*c->commit_graph_changed_paths_version ==-1 ) { + *c->commit_graph_changed_paths_version = hash_version; + }else if (hash_version != *c->commit_graph_changed_paths_version) {return 0 ; + } + g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version; g->bloom_filter_settings->num_hashes =get_be32 (chunk_start +4 );
@@ -402,10 +411,14 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { +struct graph_read_bloom_data_context context = { + .g = graph, + .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version + };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, graph); + graph_read_bloom_data, &context); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
@@ -2376,6 +2389,13 @@ int write_commit_graph(struct object_directory *odb, }if (!commit_graph_compatible (r))return 0 ; +if (r->settings.commit_graph_changed_paths_version <-1 + || r->settings.commit_graph_changed_paths_version >2 ) { +warning (_ ("attempting to write a commit-graph, but " +"'commitgraph.changedPathsVersion' (%d) is not supported" ), + r->settings.commit_graph_changed_paths_version); +return 0 ; + }CALLOC_ARRAY (ctx,1 ); ctx->r = r;
@@ -2388,6 +2408,8 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 + ?2 :1 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
@@ -2417,7 +2439,7 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph;/* We have changed-paths already. Keep them in the next graph */ -if (g && g->chunk_bloom_data) { +if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; ctx->bloom_settings = g->bloom_filter_settings; }
t/helper/test-bloom.c +8 -1
@@ -50,6 +50,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)static const char *bloom_usage ="\n" " test-tool bloom get_murmur3 <string>\n" +" test-tool bloom get_murmur3_seven_highbit\n" " test-tool bloom generate_filter <string> [<string>...]\n" " test-tool bloom get_filter_for_commit <commit-hex>\n" ;
@@ -64,7 +65,13 @@ int cmd__bloom(int argc, const char **argv)uint32_t hashed;if (argc <3 )usage (bloom_usage); - hashed =murmur3_seeded (0 , argv[2 ],strlen (argv[2 ])); + hashed =murmur3_seeded_v2 (0 , argv[2 ],strlen (argv[2 ])); +printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); + } + +if (!strcmp (argv[1 ],"get_murmur3_seven_highbit" )) { +uint32_t hashed; + hashed =murmur3_seeded_v2 (0 ,"\x99\xaa\xbb\xcc\xdd\xee\xff" ,7 );printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); }
t/t0095-bloom.sh +8 -0
@@ -29,6 +29,14 @@ test_expect_success 'compute unseeded murmur3 hash for test string 2' ' test_cmp expect actual ' +test_expect_success 'compute unseeded murmur3 hash for test string 3' ' + cat >expect <<-\EOF && + Murmur3 Hash with seed=0:0xa183ccfd + EOF + test-tool bloom get_murmur3_seven_highbit >actual && + test_cmp expect actual +' + test_expect_success 'compute bloom key for empty string' ' cat >expect <<-\EOF && Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|
t/t4216-log-bloom.sh +105 -0
109 lines not on this page · raw message
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 8/15 bloom: annotate filters with hash version
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
In subsequent commits, we will want to load existing Bloom filters out of a commit-graph, even when the hash version they were computed with does not match the value of commitGraph.changedPathVersion.
In order to differentiate between the two, add a "version" field to each Bloom filter.
--- bloom.c | 11 ++++++++--- bloom.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-)
bloom.c +8 -3
@@ -55,6 +55,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g, filter->data = (unsigned char *)(g->chunk_bloom_data +sizeof (unsigned char ) * start_index +BLOOMDATA_CHUNK_HEADER_SIZE ); + filter->version = g->bloom_filter_settings->hash_version;return 1 ; }
@@ -240,11 +241,13 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,return strcmp (e1->path, e2->path); } -static void init_truncated_large_filter (struct bloom_filter *filter) +static void init_truncated_large_filter (struct bloom_filter *filter, +int version) { filter->data =xmalloc (1 ); filter->data[0 ] =0xFF ; filter->len =1 ; + filter->version = version; } struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
@@ -329,13 +332,15 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, }if (hashmap_get_size (&pathmap) > settings->max_changed_paths) { -init_truncated_large_filter (filter); +init_truncated_large_filter (filter, + settings->hash_version);if (computed) *computed |=BLOOM_TRUNC_LARGE ; goto cleanup; } filter->len = (hashmap_get_size (&pathmap) * settings->bits_per_entry +BITS_PER_WORD -1 ) /BITS_PER_WORD ; + filter->version = settings->hash_version;if (!filter->len) {if (computed) *computed |=BLOOM_TRUNC_EMPTY ;
@@ -355,7 +360,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, }else {for (i =0 ; i < diff_queued_diff.nr; i++)diff_free_filepair (diff_queued_diff.queue[i]); -init_truncated_large_filter (filter); +init_truncated_large_filter (filter, settings->hash_version);if (computed) *computed |=BLOOM_TRUNC_LARGE ;
bloom.h +1 -0
@@ -55,6 +55,7 @@ struct bloom_filter_settings {struct bloom_filter {unsigned char *data;size_t len; +int version; };/*
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 9/15 bloom: prepare to discard incompatible Bloom filters
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
Callers use the inline get_bloom_filter() implementation as a thin wrapper around get_or_compute_bloom_filter(). The former calls the latter with a value of "0" for compute_if_not_present, making get_bloom_filter() the default read-only path for fetching an existing Bloom filter.
Callers expect the value returned from get_bloom_filter() is usable, that is that it's compatible with the configured value corresponding to commitGraph.changedPathsVersion.
This is OK, since the commit-graph machinery only initializes its BDAT chunk (thereby enabling it to service Bloom filter queries) when the Bloom filter hash_version is compatible with our settings. So any value returned by get_bloom_filter() is trivially useable.
However, subsequent commits will load the BDAT chunk even when the Bloom filters are built with incompatible hash versions. Prepare to handle this by teaching get_bloom_filter() to discard filters that are incompatible with the configured hash version.
Callers who wish to read incompatible filters (e.g., for upgrading filters from v1 to v2) may use the lower level routine, get_or_compute_bloom_filter().
--- bloom.c | 20 +++++++++++++++++++- bloom.h | 20 ++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-)
bloom.c +19 -1
@@ -250,6 +250,23 @@ static void init_truncated_large_filter(struct bloom_filter *filter, filter->version = version; } +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c) +{ +struct bloom_filter *filter; +int hash_version; + + filter =get_or_compute_bloom_filter (r, c,0 ,NULL ,NULL ); +if (!filter) +return NULL ; + +prepare_repo_settings (r); + hash_version = r->settings.commit_graph_changed_paths_version; + +if (!(hash_version ==-1 || hash_version == filter->version)) +return NULL ;/* unusable filter */ +return filter; +} + struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, struct commit *c, int compute_if_not_present,
@@ -275,7 +292,8 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter, graph_pos); } -if (filter->data && filter->len) +if ((filter->data && filter->len) && + (!settings || settings->hash_version == filter->version))return filter;if (!compute_if_not_present)return NULL ;
bloom.h +18 -2
@@ -110,8 +110,24 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,const struct bloom_filter_settings *settings, enum bloom_filter_computed *computed); -#define get_bloom_filter (r, c) get_or_compute_bloom_filter( \ - (r), (c), 0, NULL, NULL) +/* +* Find the Bloom filter associated with the given commit "c". +* +* If any of the following are true +* +* - the repository does not have a commit-graph, or +* - the repository disables reading from the commit-graph, or +* - the given commit does not have a Bloom filter computed, or +* - there is a Bloom filter for commit "c", but it cannot be read +* because the filter uses an incompatible version of murmur3 +* +* , then `get_bloom_filter()` will return NULL. Otherwise, the corresponding +* Bloom filter will be returned. +* +* For callers who wish to inspect Bloom filters with incompatible hash +* versions, use get_or_compute_bloom_filter(). +*/ +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c); int bloom_filter_contains(const struct bloom_filter *filter, const struct bloom_key *key,
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 10/15 t/t4216-log-bloom.sh: harden `test_bloom_filters_not_used()`
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
The existing implementation of test_bloom_filters_not_used() asserts that the Bloom filter sub-system has not been initialized at all, by checking for the absence of any data from it from trace2.
In the following commit, it will become possible to load Bloom filters without using them (e.g., because commitGraph.changedPathVersion is incompatible with the hash version with which the commit-graph's Bloom filters were written).
When this is the case, it's possible to initialize the Bloom filter sub-system, while still not using any Bloom filters. When this is the case, check that the data dump from the Bloom sub-system is all zeros, indicating that no filters were used.
--- t/t4216-log-bloom.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)
t/t4216-log-bloom.sh +13 -1
@@ -81,7 +81,19 @@ test_bloom_filters_used () { test_bloom_filters_not_used () { log_args=$1 setup "$log_args" && - ! grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" && + + if grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" + then + # if the Bloom filter system is initialized, ensure that no + # filters were used + data="statistics:{" + data="$data\"filter_not_present\":0," + data="$data\"maybe\":0," + data="$data\"definitely_not\":0," + data="$data\"false_positive\":0}" + + grep -q "$data" "$TRASH_DIRECTORY/trace.perf" + fi && test_cmp log_wo_bloom log_w_bloom }
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 11/15 commit-graph.c: unconditionally load Bloom filters
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
In 9e4df4da07 (commit-graph: new filter ver. that fixes murmur3, 2023-08-01), we began ignoring the Bloom data ("BDAT") chunk for commit-graphs whose Bloom filters were computed using a hash version incompatible with the value of commitGraph.changedPathVersion.
Now that the Bloom API has been hardened to discard these incompatible filters (with the exception of low-level APIs), we can safely load these Bloom filters unconditionally.
We no longer want to return early from graph_read_bloom_data(), and similarly do not want to set the bloom_settings' hash_version field as a side-effect. The latter is because we want to wait until we know which Bloom settings we're using (either the defaults, from the GIT_TEST variables, or from the previous commit-graph layer) before deciding what hash_version to use.
If we detect an existing BDAT chunk, we'll infer the rest of the settings (e.g., number of hashes, bits per entry, and maximum number of changed paths) from the earlier graph layer. The hash_version will be inferred from the previous layer as well, unless one has already been specified via configuration.
Once all of that is done, we normalize the value of the hash_version to either "1" or "2".
--- commit-graph.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-)
commit-graph.c +10 -9
@@ -317,12 +317,6 @@ static int graph_read_bloom_data(const unsigned char *chunk_start,uint32_t hash_version; hash_version =get_be32 (chunk_start); -if (*c->commit_graph_changed_paths_version ==-1 ) { - *c->commit_graph_changed_paths_version = hash_version; - }else if (hash_version != *c->commit_graph_changed_paths_version) { -return 0 ; - } - g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version;
@@ -2408,8 +2402,7 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; - bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 - ?2 :1 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
@@ -2441,10 +2434,18 @@ int write_commit_graph(struct object_directory *odb,/* We have changed-paths already. Keep them in the next graph */ if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; - ctx->bloom_settings = g->bloom_filter_settings; + +/* don't propagate the hash_version unless unspecified */ +if (bloom_settings.hash_version ==-1 ) + bloom_settings.hash_version = g->bloom_filter_settings->hash_version; + bloom_settings.bits_per_entry = g->bloom_filter_settings->bits_per_entry; + bloom_settings.num_hashes = g->bloom_filter_settings->num_hashes; + bloom_settings.max_changed_paths = g->bloom_filter_settings->max_changed_paths; } } + bloom_settings.hash_version = bloom_settings.hash_version ==2 ?2 :1 ; +if (ctx->split) {struct commit_graph *g = ctx->r->objects->commit_graph;
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 12/15 commit-graph: drop unnecessary `graph_read_bloom_data_context`
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
The graph_read_bloom_data_context struct was introduced in an earlier commit in order to pass pointers to the commit-graph and changed-path Bloom filter version when reading the BDAT chunk.
The previous commit no longer writes through the changed_paths_version pointer, making the surrounding context structure unnecessary. Drop it and pass a pointer to the commit-graph directly when reading the BDAT chunk.
--- commit-graph.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-)
commit-graph.c +2 -12
@@ -304,16 +304,10 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } -struct graph_read_bloom_data_context { -struct commit_graph *g; -int *commit_graph_changed_paths_version; -}; -static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct graph_read_bloom_data_context *c = data; -struct commit_graph *g = c->g; +struct commit_graph *g = data;uint32_t hash_version; hash_version =get_be32 (chunk_start);
@@ -405,14 +399,10 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { -struct graph_read_bloom_data_context context = { - .g = graph, - .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version - };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, &context); + graph_read_bloom_data, graph); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 13/15 object.h: fix mis-aligned flag bits table
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
Bit position 23 is one column too far to the left.
--- object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
object.h +1 -1
@@ -62,7 +62,7 @@ void object_array_init(struct object_array *array);/* * object flag allocation: -* revision.h: 0---------10 15 23------27 +* revision.h: 0---------10 15 23------27 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 * walker.c: 0-2
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 14/15 commit-graph: reuse existing Bloom filters where possible
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
In 9e4df4da07 (commit-graph: new filter ver. that fixes murmur3, 2023-08-01), a bug was described where it's possible for Git to produce non-murmur3 hashes when the platform's "char" type is signed, and there are paths with characters whose highest bit is set (i.e. all characters
Quoted 1 line
=
0x80).
That patch allows the caller to control which version of Bloom filters are read and written. However, even on platforms with a signed "char" type, it is possible to reuse existing Bloom filters if and only if there are no changed paths in any commit's first parent tree-diff whose characters have their highest bit set.
When this is the case, we can reuse the existing filter without having to compute a new one. This is done by marking trees which are known to have (or not have) any such paths. When a commit's root tree is verified to not have any such paths, we mark it as such and declare that the commit's Bloom filter is reusable.
Note that this heuristic only goes in one direction. If neither a commit nor its first parent have any paths in their trees with non-ASCII characters, then we know for certain that a path with non-ASCII characters will not appear in a tree-diff against that commit's first parent. The reverse isn't necessarily true: just because the tree-diff doesn't contain any such paths does not imply that no such paths exist in either tree.
So we end up recomputing some Bloom filters that we don't strictly have to (i.e. their bits are the same no matter which version of murmur3 we use). But culling these out is impossible, since we'd have to perform the full tree-diff, which is the same effort as computing the Bloom filter from scratch.
But because we can cache our results in each tree's flag bits, we can often avoid recomputing many filters, thereby reducing the time it takes to run
$ git commit-graph write --changed-paths --reachable
when upgrading from v1 to v2 Bloom filters.
To benchmark this, let's generate a commit-graph in linux.git with v1 changed-paths in generation order[^1]:
$ git clone [email protected]:torvalds/linux.git $ cd linux $ git commit-graph write --reachable --changed-paths $ graph=".git/objects/info/commit-graph" $ mv $graph{,.bak}
Then let's time how long it takes to go from v1 to v2 filters (with and without the upgrade path enabled), resetting the state of the commit-graph each time:
$ git config commitGraph.changedPathsVersion 2
$ hyperfine -p 'cp -f $graph.bak $graph' -L v 0,1 \
'GIT_TEST_UPGRADE_BLOOM_FILTERS={v} git.compile commit-graph write --reachable --changed-paths'On linux.git (where there aren't any non-ASCII paths), the timings indicate that this patch represents a speed-up over recomputing all Bloom filters from scratch:
Benchmark 1: GIT_TEST_UPGRADE_BLOOM_FILTERS=0 git.compile commit-graph write --reachable --changed-paths
Time (mean ± σ): 124.873 s ± 0.316 s [User: 124.081 s, System: 0.643 s]
Range (min … max): 124.621 s … 125.227 s 3 runs
Benchmark 2: GIT_TEST_UPGRADE_BLOOM_FILTERS=1 git.compile commit-graph write --reachable --changed-paths
Time (mean ± σ): 79.271 s ± 0.163 s [User: 74.611 s, System: 4.521 s]
Range (min … max): 79.112 s … 79.437 s 3 runs
Summary
'GIT_TEST_UPGRADE_BLOOM_FILTERS=1 git.compile commit-graph write --reachable --changed-paths' ran
1.58 ± 0.01 times faster than 'GIT_TEST_UPGRADE_BLOOM_FILTERS=0 git.compile commit-graph write --reachable --changed-paths'On git.git, we do have some non-ASCII paths, giving us a more modest improvement from 4.163 seconds to 3.348 seconds, for a 1.24x speed-up. On my machine, the stats for git.git are:
- 8,285 Bloom filters computed from scratch
- 10 Bloom filters generated as empty
- 4 Bloom filters generated as truncated due to too many changed paths
- 65,114 Bloom filters were reused when transitioning from v1 to v2.
[^1]: Note that this is is important, since --stdin-packs or
--stdin-commits orders commits in the commit-graph by their pack position (with --stdin-packs) or in the raw input (with --stdin-commits).
Since we compute Bloom filters in the same order that commits appear in the graph, we must see a commit's (first) parent before we process the commit itself. This is only guaranteed to happen when sorting commits by their generation number.
--- bloom.c | 90 ++++++++++++++++++++++++++++++++++++++++++-- bloom.h | 1 + commit-graph.c | 5 +++ object.h | 1 + t/t4216-log-bloom.sh | 35 ++++++++++++++++- 5 files changed, 127 insertions(+), 5 deletions(-)
bloom.c +87 -3
@@ -7,6 +7,9 @@#include "commit-graph.h" #include "commit.h" #include "commit-slab.h" +#include "tree.h" +#include "tree-walk.h" +#include "config.h" define_commit_slab(bloom_filter_slab, struct bloom_filter);
@@ -250,6 +253,73 @@ static void init_truncated_large_filter(struct bloom_filter *filter, filter->version = version; } +#define VISITED (1u<<21) +#define HIGH_BITS (1u<<22) + +static int has_entries_with_high_bit (struct repository *r,struct tree *t) +{ +if (parse_tree (t)) +return 1 ; + +if (!(t->object.flags &VISITED )) { +struct tree_desc desc; +struct name_entry entry; + +init_tree_desc (&desc, t->buffer, t->size); +while (tree_entry (&desc, &entry)) { +size_t i; +for (i =0 ; i < entry.pathlen; i++) { +if (entry.path[i] &0x80 ) { + t->object.flags |=HIGH_BITS ; + goto done; + } + } + +if (S_ISDIR (entry.mode)) { +struct tree *sub =lookup_tree (r, &entry.oid); +if (sub &&has_entries_with_high_bit (r, sub)) { + t->object.flags |=HIGH_BITS ; + goto done; + } + } + + } + +done: + t->object.flags |=VISITED ; + } + +return !!(t->object.flags &HIGH_BITS ); +} + +static int commit_tree_has_high_bit_paths (struct repository *r, +struct commit *c) +{ +struct tree *t; +if (repo_parse_commit (r, c)) +return 1 ; + t =repo_get_commit_tree (r, c); +if (!t) +return 1 ; +return has_entries_with_high_bit (r, t); +} + +static struct bloom_filter *upgrade_filter (struct repository *r,struct commit *c, +struct bloom_filter *filter, +int hash_version) +{ +struct commit_list *p = c->parents; +if (commit_tree_has_high_bit_paths (r, c)) +return NULL ; + +if (p &&commit_tree_has_high_bit_paths (r, p->item)) +return NULL ; + + filter->version = hash_version; + +return filter; +} +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c) {struct bloom_filter *filter;
@@ -292,9 +362,23 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter, graph_pos); } -if ((filter->data && filter->len) && - (!settings || settings->hash_version == filter->version)) -return filter; +if (filter->data && filter->len) { +struct bloom_filter *upgrade; +if (!settings || settings->hash_version == filter->version) +return filter; + +/* version mismatch, see if we can upgrade */ +if (compute_if_not_present && +git_env_bool ("GIT_TEST_UPGRADE_BLOOM_FILTERS" ,1 )) { + upgrade =upgrade_filter (r, c, filter, + settings->hash_version); +if (upgrade) { +if (computed) + *computed |=BLOOM_UPGRADED ; +return upgrade; + } + } + }if (!compute_if_not_present)return NULL ;
bloom.h +1 -0
@@ -102,6 +102,7 @@ enum bloom_filter_computed {BLOOM_COMPUTED = (1 <<1 ),BLOOM_TRUNC_LARGE = (1 <<2 ),BLOOM_TRUNC_EMPTY = (1 <<3 ), + BLOOM_UPGRADED = (1 << 4), }; struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
commit-graph.c +5 -0
@@ -1045,6 +1045,7 @@ struct write_commit_graph_context {int count_bloom_filter_not_computed;int count_bloom_filter_trunc_empty;int count_bloom_filter_trunc_large; +int count_bloom_filter_upgraded; }; static int write_graph_chunk_fanout(struct hashfile *f,
@@ -1651,6 +1652,8 @@ static void trace2_bloom_filter_write_statistics(struct write_commit_graph_conte ctx->count_bloom_filter_trunc_empty);trace2_data_intmax ("commit-graph" , ctx->r,"filter-trunc-large" , ctx->count_bloom_filter_trunc_large); +trace2_data_intmax ("commit-graph" , ctx->r,"filter-upgraded" , + ctx->count_bloom_filter_upgraded); }static void compute_bloom_filters (struct write_commit_graph_context *ctx)
@@ -1692,6 +1695,8 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx) ctx->count_bloom_filter_trunc_empty++;if (computed &BLOOM_TRUNC_LARGE ) ctx->count_bloom_filter_trunc_large++; + }else if (computed &BLOOM_UPGRADED ) { + ctx->count_bloom_filter_upgraded++; }else if (computed &BLOOM_NOT_COMPUTED ) ctx->count_bloom_filter_not_computed++; ctx->total_bloom_filter_data_size += filter
object.h +1 -0
@@ -75,6 +75,7 @@ void object_array_init(struct object_array *array);* commit-reach.c: 16-----19 * sha1-name.c: 20 * list-objects-filter.c: 21 +* bloom.c: 2122 * builtin/fsck.c: 0--3 * builtin/gc.c: 0 * builtin/index-pack.c: 2021
t/t4216-log-bloom.sh +33 -2
@@ -217,6 +217,10 @@ test_filter_trunc_large () { grep "\"key\":\"filter-trunc-large\",\"value\":\"$1\"" $2 } +test_filter_upgraded () { + grep "\"key\":\"filter-upgraded\",\"value\":\"$1\"" $2 +} + test_expect_success 'correctly report changes over limit' ' git init limits && (
@@ -561,10 +565,19 @@ test_expect_success 'when writing another commit graph, preserve existing versio test_expect_success 'when writing commit graph, do not reuse changed-path of another version' ' git init doublewrite && test_commit -C doublewrite c "$CENT" && + git -C doublewrite config --add commitgraph.changedPathsVersion 1 && - git -C doublewrite commit-graph write --reachable --changed-paths && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C doublewrite commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + git -C doublewrite config --add commitgraph.changedPathsVersion 2 && - git -C doublewrite commit-graph write --reachable --changed-paths && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C doublewrite commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + ( cd doublewrite && echo "c01f" >expect &&
@@ -573,4 +586,22 @@ test_expect_success 'when writing commit graph, do not reuse changed-path of ano ) ' +test_expect_success 'when writing commit graph, reuse changed-path of another version where possible' ' + git init upgrade && + + test_commit -C upgrade base no-high-bits && + + git -C upgrade config --add commitgraph.changedPathsVersion 1 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C upgrade commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + + git -C upgrade config --add commitgraph.changedPathsVersion 2 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C upgrade commit-graph write --reachable --changed-paths && + test_filter_computed 0 trace2.txt && + test_filter_upgraded 1 trace2.txt +' + test_done
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
PATCH v2 15/15 bloom: introduce `deinit_bloom_filters()`
In reply to Jonathan TanFrom: Taylor Blau <[email protected]>
After we are done using Bloom filters, we do not currently clean up any memory allocated by the commit slab used to store those filters in the first place.
Besides the bloom_filter structures themselves, there is mostly nothing to free() in the first place, since in the read-only path all Bloom filter's data members point to a memory mapped region in the commit-graph file itself.
But when generating Bloom filters from scratch (or initializing truncated filters) we allocate additional memory to store the filter's data.
Keep track of when we need to free() this additional chunk of memory by using an extra pointer to_free. Most of the time this will be NULL (indicating that we are representing an existing Bloom filter stored in a memory mapped region). When it is non-NULL, free it before discarding the Bloom filters slab.
--- bloom.c | 16 +++++++++++++++- bloom.h | 3 +++ commit-graph.c | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-)
bloom.c +15 -1
@@ -59,6 +59,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,sizeof (unsigned char ) * start_index + BLOOMDATA_CHUNK_HEADER_SIZE); filter->version = g->bloom_filter_settings->hash_version; + filter->to_free =NULL ;return 1 ; }
@@ -231,6 +232,18 @@ void init_bloom_filters(void)init_bloom_filter_slab (&bloom_filters); } +static void free_one_bloom_filter (struct bloom_filter *filter) +{ +if (!filter) +return ; +free (filter->to_free); +} + +void deinit_bloom_filters (void ) +{ +deep_clear_bloom_filter_slab (&bloom_filters, free_one_bloom_filter); +} + static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key,
@@ -247,7 +260,7 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,static void init_truncated_large_filter (struct bloom_filter *filter,int version) { - filter->data =xmalloc (1 ); + filter->data = filter->to_free =xmalloc (1 ); filter->data[0 ] =0xFF ; filter->len =1 ; filter->version = version;
@@ -449,6 +462,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter->len =1 ; }CALLOC_ARRAY (filter->data, filter->len); + filter->to_free = filter->data;hashmap_for_each_entry (&pathmap, &iter, e, entry) {struct bloom_key key;
bloom.h +3 -0
@@ -56,6 +56,8 @@ struct bloom_filter {unsigned char *data;size_t len;int version; + +void *to_free; };/*
@@ -96,6 +98,7 @@ void add_key_to_filter(const struct bloom_key *key, const struct bloom_filter_settings *settings);void init_bloom_filters (void ); +void deinit_bloom_filters (void );enum bloom_filter_computed {BLOOM_NOT_COMPUTED = (1 <<0 ),
commit-graph.c +4 -0
@@ -723,6 +723,7 @@ static void close_commit_graph_one(struct commit_graph *g)void close_commit_graph (struct raw_object_store *o) {close_commit_graph_one (o->commit_graph); +deinit_bloom_filters (); o->commit_graph =NULL ; }
@@ -2523,6 +2524,9 @@ int write_commit_graph(struct object_directory *odb, res =write_commit_graph_file (ctx); +if (ctx->changed_paths) +deinit_bloom_filters (); +if (ctx->split)mark_commit_graphs (ctx);
Signature · 1 line
2.42.0.rc2.253.gd59a3bf2b4-goog
Quoted 2 lines · Jonathan Tan
Here's an updated patch set containing updates to the tests, making them run on non-root commits to ensure that we exercise the Bloom filters.
Thanks for a quick turnaround.
Quoted 1 line
Thanks to SZEDER Gábor for spotting this.
With this everybody involved are happy, I presume. Thank you, everybody, who's worked on polishing and assembling these patches.
v3 cover bloom: changed-path Bloom filters v2 (& sundries)
In reply to Taylor Blau(Rebased onto the tip of 'master', which is 3a06386e31 (The fifteenth batch, 2023-10-04), at the time of writing).
This series is a reroll of the combined efforts of [1] and [2] to introduce the v2 changed-path Bloom filters, which fixes a bug in our existing implementation of murmur3 paths with non-ASCII characters (when the "char" type is signed).
In large part, this is the same as the previous round. But this round includes some extra bits that address issues pointed out by SZEDER Gábor, which are:
- not reading Bloom filters for root commits
- corrupting Bloom filter reads by tweaking the filter settings between layers.
These issues were discussed in (among other places) [3], and [4], respectively.
Thanks to Jonathan, Peff, and SZEDER who have helped a great deal in assembling these patches. As usual, a range-diff is included below. Thanks in advance for your
review!
[1]: https://lore.kernel.org/git/[email protected]/
[2]: https://lore.kernel.org/git/[email protected]/
[3]: https://public-inbox.org/git/[email protected]/
[4]: https://lore.kernel.org/git/[email protected]/
Jonathan Tan (4):
gitformat-commit-graph: describe version 2 of BDAT
t4216: test changed path filters with high bit paths
repo-settings: introduce commitgraph.changedPathsVersion
commit-graph: new filter ver. that fixes murmur3
Taylor Blau (13):
t/t4216-log-bloom.sh: harden test_bloom_filters_not_used() revision.c: consult Bloom filters for root commits
commit-graph: ensure Bloom filters are read with consistent settings t/helper/test-read-graph.c: extract dump_graph_info() bloom.h: make load_bloom_filter_from_graph() public t/helper/test-read-graph: implement bloom-filters mode
bloom: annotate filters with hash version
bloom: prepare to discard incompatible Bloom filters commit-graph.c: unconditionally load Bloom filters
commit-graph: drop unnecessary graph_read_bloom_data_context object.h: fix mis-aligned flag bits table
commit-graph: reuse existing Bloom filters where possible
bloom: introduce deinit_bloom_filters()
Documentation/config/commitgraph.txt | 26 ++- Documentation/gitformat-commit-graph.txt | 9 +- bloom.c | 208 ++++++++++++++++- bloom.h | 38 +++- commit-graph.c | 61 ++++- object.h | 3 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +- repository.h | 2 +- revision.c | 26 ++- t/helper/test-bloom.c | 9 +- t/helper/test-read-graph.c | 67 ++++-- t/t0095-bloom.sh | 8 + t/t4216-log-bloom.sh | 272 ++++++++++++++++++++++- 14 files changed, 682 insertions(+), 55 deletions(-)
Range-diff against v2:
10: 002a06d1e9 ! 1: fe671d616c t/t4216-log-bloom.sh: harden test_bloom_filters_not_used()
@@ Commit message
indicating that no filters were used.
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## t/t4216-log-bloom.sh ##
@@ t/t4216-log-bloom.sh: test_bloom_filters_used () {
-: ---------- > 2: 7d0fa93543 revision.c: consult Bloom filters for root commits -: ---------- > 3: 2ecc0a2d58 commit-graph: ensure Bloom filters are read with consistent settings 1: 5fa681b58e ! 4: 17703ed89a gitformat-commit-graph: describe version 2 of BDAT
@@ Commit message
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## Documentation/gitformat-commit-graph.txt ##
@@ Documentation/gitformat-commit-graph.txt: All multi-byte numbers are in network byte order.
2: 623d840575 ! 5: 94552abf45 t/helper/test-read-graph.c: extract dump_graph_info()
@@ Commit message
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## t/helper/test-read-graph.c ##
@@
3: bc9d77ae60 ! 6: 3d81efa27b bloom.h: make load_bloom_filter_from_graph() public
@@ Commit message
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## bloom.c ##
@@ bloom.c: static inline unsigned char get_bitmask(uint32_t pos)
4: ac7008aed3 ! 7: d23cd89037 t/helper/test-read-graph: implement bloom-filters mode
@@ Commit message
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## t/helper/test-read-graph.c ##
@@ t/helper/test-read-graph.c: static void dump_graph_info(struct commit_graph *graph)
@@ t/helper/test-read-graph.c: int cmd__read_graph(int argc UNUSED, const char **ar
- return 0;
+ return ret;
}
++
++
5: 71755ba856 ! 8: cba766f224 t4216: test changed path filters with high bit paths
@@ Commit message
## t/t4216-log-bloom.sh ##
-@@ t/t4216-log-bloom.sh: test_expect_success 'Bloom generation backfills empty commits' '
- )
+@@ t/t4216-log-bloom.sh: test_expect_success 'merge graph layers with incompatible Bloom settings' '
+ ! grep "disabling Bloom filters" err
'
+get_first_changed_path_filter () {
6: 9768d92c0f ! 9: a08a961f41 repo-settings: introduce commitgraph.changedPathsVersion
@@ Commit message
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## Documentation/config/commitgraph.txt ##
@@ Documentation/config/commitgraph.txt: commitGraph.maxNewFilters::
7: f911b4bfab = 10: 61d44519a5 commit-graph: new filter ver. that fixes murmur3 8: 35009900df ! 11: a8c10f8de8 bloom: annotate filters with hash version
@@ Commit message
Bloom filter.
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## bloom.c ##
@@ bloom.c: int load_bloom_filter_from_graph(struct commit_graph *g,
9: 138bc16905 ! 12: 2ba10a4b4b bloom: prepare to discard incompatible Bloom filters
@@ Commit message
get_or_compute_bloom_filter().
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## bloom.c ##
@@ bloom.c: static void init_truncated_large_filter(struct bloom_filter *filter,
11: 2437e62813 ! 13: 09d8669c3a commit-graph.c: unconditionally load Bloom filters
@@ Commit message
either "1" or "2".
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## commit-graph.c ##
@@ commit-graph.c: static int graph_read_bloom_data(const unsigned char *chunk_start,
12: fe8fb2f5fe ! 14: 0d4f9dc4ee commit-graph: drop unnecessary graph_read_bloom_data_context
@@ Commit message
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## commit-graph.c ##
@@ commit-graph.c: static int graph_read_oid_lookup(const unsigned char *chunk_start,
13: 825af91e11 ! 15: 1f7f27bc47 object.h: fix mis-aligned flag bits table
@@ Commit message
Bit position 23 is one column too far to the left.
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## object.h ##
@@ object.h: void object_array_init(struct object_array *array);
14: 593b317192 ! 16: abbef95ae8 commit-graph: reuse existing Bloom filters where possible
@@ Commit message
commits by their generation number.
- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>
## bloom.c ##
@@
15: 8bf2c9cf98 = 17: ca362408d5 bloom: introduce deinit_bloom_filters()
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 1/17 t/t4216-log-bloom.sh: harden `test_bloom_filters_not_used()`
The existing implementation of test_bloom_filters_not_used() asserts that the Bloom filter sub-system has not been initialized at all, by checking for the absence of any data from it from trace2.
In the following commit, it will become possible to load Bloom filters without using them (e.g., because commitGraph.changedPathVersion is incompatible with the hash version with which the commit-graph's Bloom filters were written).
When this is the case, it's possible to initialize the Bloom filter sub-system, while still not using any Bloom filters. When this is the case, check that the data dump from the Bloom sub-system is all zeros, indicating that no filters were used.
--- t/t4216-log-bloom.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)
t/t4216-log-bloom.sh +13 -1
@@ -81,7 +81,19 @@ test_bloom_filters_used () { test_bloom_filters_not_used () { log_args=$1 setup "$log_args" && - ! grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" && + + if grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf" + then + # if the Bloom filter system is initialized, ensure that no + # filters were used + data="statistics:{" + data="$data\"filter_not_present\":0," + data="$data\"maybe\":0," + data="$data\"definitely_not\":0," + data="$data\"false_positive\":0}" + + grep -q "$data" "$TRASH_DIRECTORY/trace.perf" + fi && test_cmp log_wo_bloom log_w_bloom }
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 2/17 revision.c: consult Bloom filters for root commits
In reply to Taylor BlauThe commit-graph stores changed-path Bloom filters which represent the set of paths included in a tree-level diff between a commit's root tree and that of its parent.
When a commit has no parents, the tree-diff is computed against that commit's root tree and the empty tree. In other words, every path in that commit's tree is stored in the Bloom filter (since they all appear in the diff).
Consult these filters during pathspec-limited traversals in the function rev_same_tree_as_empty(). Doing so yields a performance improvement where we can avoid enumerating the full set of paths in a parentless commit's root tree when we know that the path(s) of interest were not listed in that commit's changed-path Bloom filter.
--- revision.c | 26 ++++++++++++++++++++++---- t/t4216-log-bloom.sh | 8 ++++++-- 2 files changed, 28 insertions(+), 6 deletions(-)
revision.c +22 -4
@@ -834,17 +834,28 @@ static int rev_compare_tree(struct rev_info *revs,return tree_difference; } -static int rev_same_tree_as_empty (struct rev_info *revs,struct commit *commit) +static int rev_same_tree_as_empty (struct rev_info *revs,struct commit *commit, +int nth_parent) {struct tree *t1 =repo_get_commit_tree (the_repository, commit); +int bloom_ret =1 ;if (!t1)return 0 ; +if (nth_parent ==1 && revs->bloom_keys_nr) { + bloom_ret =check_maybe_different_in_bloom_filter (revs, commit); +if (!bloom_ret) +return 1 ; + } + tree_difference =REV_TREE_SAME ; revs->pruning.flags.has_changes =0 ;diff_tree_oid (NULL , &t1->object.oid,"" , &revs->pruning); +if (bloom_ret ==1 && tree_difference ==REV_TREE_SAME ) + count_bloom_filter_false_positive++; +return tree_difference ==REV_TREE_SAME ; }
@@ -882,7 +893,7 @@ static int compact_treesame(struct rev_info *revs, struct commit *commit, unsignif (nth_parent !=0 )die ("compact_treesame %u" , nth_parent); old_same = !!(commit->object.flags &TREESAME ); -if (rev_same_tree_as_empty (revs, commit)) +if (rev_same_tree_as_empty (revs, commit, nth_parent)) commit->object.flags |=TREESAME ;else commit->object.flags &= ~TREESAME ;
@@ -978,7 +989,14 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)return ;if (!commit->parents) { -if (rev_same_tree_as_empty (revs, commit)) +/* +* Pretend as if we are comparing ourselves to the +* (non-existent) first parent of this commit object. Even +* though no such parent exists, its changed-path Bloom filter +* (if one exists) is relative to the empty tree, using Bloom +* filters is allowed here. +*/ +if (rev_same_tree_as_empty (revs, commit,1 )) commit->object.flags |=TREESAME ;return ; }
@@ -1059,7 +1077,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)case REV_TREE_NEW :if (revs->remove_empty_trees && -rev_same_tree_as_empty (revs, p)) { +rev_same_tree_as_empty (revs, p, nth_parent)) {/* We are adding all the specified * paths from this parent, so the * history beyond this parent is not
t/t4216-log-bloom.sh +6 -2
@@ -87,7 +87,11 @@ test_bloom_filters_not_used () { # if the Bloom filter system is initialized, ensure that no # filters were used data="statistics:{" - data="$data\"filter_not_present\":0," + # unusable filters (e.g., those computed with a + # different value of commitGraph.changedPathsVersion) + # are counted in the filter_not_present bucket, so any + # value is OK there. + data="$data\"filter_not_present\":[0-9][0-9]*," data="$data\"maybe\":0," data="$data\"definitely_not\":0," data="$data\"false_positive\":0}"
@@ -174,7 +178,7 @@ test_expect_success 'setup - add commit-graph to the chain with Bloom filters' ' test_bloom_filters_used_when_some_filters_are_missing () { log_args=$1 - bloom_trace_prefix="statistics:{\"filter_not_present\":3,\"maybe\":6,\"definitely_not\":9" + bloom_trace_prefix="statistics:{\"filter_not_present\":3,\"maybe\":6,\"definitely_not\":10" setup "$log_args" && grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" && test_cmp log_wo_bloom log_w_bloom
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 3/17 commit-graph: ensure Bloom filters are read with consistent settings
In reply to Taylor BlauThe changed-path Bloom filter mechanism is parameterized by a couple of variables, notably the number of bits per hash (typically "m" in Bloom filter literature) and the number of hashes themselves (typically "k").
It is critically important that filters are read with the Bloom filter settings that they were written with. Failing to do so would mean that each query is liable to compute different fingerprints, meaning that the filter itself could return a false negative. This goes against a basic assumption of using Bloom filters (that they may return false positives, but never false negatives) and can lead to incorrect results.
We have some existing logic to carry forward existing Bloom filter settings from one layer to the next. In write_commit_graph(), we have something like:
if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) {
struct commit_graph *g = ctx->r->objects->commit_graph;
/* We have changed-paths already. Keep them in the next graph */
if (g && g->chunk_bloom_data) {
ctx->changed_paths = 1;
ctx->bloom_settings = g->bloom_filter_settings;
}
}, which drags forward Bloom filter settings across adjacent layers.
This doesn't quite address all cases, however, since it is possible for intermediate layers to contain no Bloom filters at all. For example, suppose we have two layers in a commit-graph chain, say, {G1, G2}. If G1 contains Bloom filters, but G2 doesn't, a new G3 (whose base graph is G2) may be written with arbitrary Bloom filter settings, because we only check the immediately adjacent layer's settings for compatibility.
This behavior has existed since the introduction of changed-path Bloom filters. But in practice, this is not such a big deal, since the only way up until this point to modify the Bloom filter settings at write time is with the undocumented environment variables:
- GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY
- GIT_TEST_BLOOM_SETTINGS_NUM_HASHES
- GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS
(it is still possible to tweak MAX_CHANGED_PATHS between layers, but this does not affect reads, so is allowed to differ across multiple graph layers).
But in future commits, we will introduce another parameter to change the hash algorithm used to compute Bloom fingerprints itself. This will be exposed via a configuration setting, making this foot-gun easier to use.
To prevent this potential issue, validate that all layers of a split commit-graph have compatible settings with the newest layer which contains Bloom filters.
--- commit-graph.c | 25 +++++++++++++++++ t/t4216-log-bloom.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+)
commit-graph.c +25 -0
@@ -498,6 +498,30 @@ static int validate_mixed_generation_chain(struct commit_graph *g)return 0 ; } +static void validate_mixed_bloom_settings (struct commit_graph *g) +{ +struct bloom_filter_settings *settings =NULL ; +for (; g; g = g->base_graph) { +if (!g->bloom_filter_settings) +continue ; +if (!settings) { + settings = g->bloom_filter_settings; +continue ; + } + +if (g->bloom_filter_settings->bits_per_entry != settings->bits_per_entry || + g->bloom_filter_settings->num_hashes != settings->num_hashes) { + g->chunk_bloom_indexes =NULL ; + g->chunk_bloom_data =NULL ; +FREE_AND_NULL (g->bloom_filter_settings); + +warning (_ ("disabling Bloom filters for commit-graph " +"layer '%s' due to incompatible settings" ), +oid_to_hex (&g->oid)); + } + } +} + static int add_graph_to_chain(struct commit_graph *g, struct commit_graph *chain, struct object_id *oids,
@@ -614,6 +638,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, }validate_mixed_generation_chain (graph_chain); +validate_mixed_bloom_settings (graph_chain);free (oids);fclose (fp);
t/t4216-log-bloom.sh +64 -0
68 lines not on this page · raw message
Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 03/17] commit-graph: ensure Bloom filters are read with consistent settings
Quoted 147 lines · Taylor Blau
The changed-path Bloom filter mechanism is parameterized by a couple of variables, notably the number of bits per hash (typically "m" in Bloom filter literature) and the number of hashes themselves (typically "k").
It is critically important that filters are read with the Bloom filter settings that they were written with. Failing to do so would mean that each query is liable to compute different fingerprints, meaning that the filter itself could return a false negative. This goes against a basic assumption of using Bloom filters (that they may return false positives, but never false negatives) and can lead to incorrect results.
We have some existing logic to carry forward existing Bloom filter settings from one layer to the next. In
write_commit_graph(), we have something like:if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) { struct commit_graph *g = ctx->r->objects->commit_graph; /* We have changed-paths already. Keep them in the next graph */ if (g && g->chunk_bloom_data) { ctx->changed_paths = 1; ctx->bloom_settings = g->bloom_filter_settings; } }, which drags forward Bloom filter settings across adjacent layers.
This doesn't quite address all cases, however, since it is possible for intermediate layers to contain no Bloom filters at all. For example, suppose we have two layers in a commit-graph chain, say, {G1, G2}. If G1 contains Bloom filters, but G2 doesn't, a new G3 (whose base graph is G2) may be written with arbitrary Bloom filter settings, because we only check the immediately adjacent layer's settings for compatibility.
This behavior has existed since the introduction of changed-path Bloom filters. But in practice, this is not such a big deal, since the only way up until this point to modify the Bloom filter settings at write time is with the undocumented environment variables:
-
GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY
-GIT_TEST_BLOOM_SETTINGS_NUM_HASHES
-GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS(it is still possible to tweak
MAX_CHANGED_PATHSbetween layers, but this does not affect reads, so is allowed to differ across multiple graph layers).But in future commits, we will introduce another parameter to change the hash algorithm used to compute Bloom fingerprints itself. This will be exposed via a configuration setting, making this foot-gun easier to use.
To prevent this potential issue, validate that all layers of a split commit-graph have compatible settings with the newest layer which contains Bloom filters.
Reported-by SZEDER Gábor <[email protected]>Original-test-by SZEDER Gábor <[email protected]>Signed-off-by Taylor Blau <[email protected]>--- commit-graph.c | 25 +++++++++++++++++ t/t4216-log-bloom.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+)commit-graph.c +25
-0@@ -498,6 +498,30 @@ static int validate_mixed_generation_chain(struct commit_graph *g)return 0 ; } +static void validate_mixed_bloom_settings (struct commit_graph *g) +{ +struct bloom_filter_settings *settings =NULL ; +for (; g; g = g->base_graph) { +if (!g->bloom_filter_settings) +continue ; +if (!settings) { + settings = g->bloom_filter_settings; +continue ; + } + +if (g->bloom_filter_settings->bits_per_entry != settings->bits_per_entry || + g->bloom_filter_settings->num_hashes != settings->num_hashes) { + g->chunk_bloom_indexes =NULL ; + g->chunk_bloom_data =NULL ; +FREE_AND_NULL (g->bloom_filter_settings); + +warning (_ ("disabling Bloom filters for commit-graph " +"layer '%s' due to incompatible settings" ), +oid_to_hex (&g->oid)); + } + } +} + static int add_graph_to_chain(struct commit_graph *g, struct commit_graph *chain, struct object_id *oids,@@ -614,6 +638,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, }validate_mixed_generation_chain (graph_chain); +validate_mixed_bloom_settings (graph_chain);free (oids);fclose (fp);t/t4216-log-bloom.sh +33
-0@@ -420,4 +420,68 @@ test_expect_success 'Bloom generation backfills empty commits' ' ) ' +graph=.git/objects/info/commit-graph +graphdir=.git/objects/info/commit-graphs +chain=$graphdir/commit-graph-chain + +test_expect_success 'setup for mixed Bloom setting tests' ' + repo=mixed-bloom-settings && + + git init $repo && + for i in one two three + do + test_commit -C $repo $i file || return 1 + done +' + +test_expect_success 'split' ' + # Compute Bloom filters with "unusual" settings. + git -C $repo rev-parse one >in && + GIT_TEST_BLOOM_SETTINGS_NUM_HASHES=3 git -C $repo commit-graph write \ + --stdin-commits --changed-paths --split <in && + layer=$(head -n 1 $repo/$chain) && + + # A commit-graph layer without Bloom filters "hides" the layers + # below ... + git -C $repo rev-parse two >in && + git -C $repo commit-graph write --stdin-commits --no-changed-paths \ + --split=no-merge <in && + + # Another commit-graph layer that has Bloom filters, but with + # standard settings, and is thus incompatible with the base + # layer written above. + git -C $repo rev-parse HEAD >in && + git -C $repo commit-graph write --stdin-commits --changed-paths \ + --split=no-merge <in &&
t/t4216-log-bloom.sh test_expect_success 'Bloom generation backfills empty commits' ' · line 465
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+ + test_line_count = 3 $repo/$chain && + + # Ensure that incompatible Bloom filters are ignored. + git -C $repo -c core.commitGraph=false log --oneline --no-decorate -- file \ + >expect 2>err && + git -C $repo log --oneline --no-decorate -- file >actual 2>err && + test_cmp expect actual && + grep "disabling Bloom filters for commit-graph layer .$layer." err +'
Up to this point everything looks sensible to me.
Quoted 4 lines
t/t4216-log-bloom.sh +4
-0+test_expect_success 'merge graph layers with incompatible Bloom settings' ' + # Ensure that incompatible Bloom filters are ignored when + # generating new layers. + git -C $repo commit-graph write --reachable --changed-paths 2>err &&
t/t4216-log-bloom.sh test_expect_success 'Bloom generation backfills empty commits' ' · line 480
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+ grep "disabling Bloom filters for commit-graph layer .$layer." err && + + test_path_is_file $repo/$graph && + test_dir_is_empty $repo/$graphdir && + + # ...and merging existing ones. + git -C $repo -c core.commitGraph=false log --oneline --no-decorate -- file \ + >expect 2>err && + GIT_TRACE2_PERF="$(pwd)/trace.perf" \ + git -C $repo log --oneline --no-decorate -- file >actual 2>err &&
But this test is a bit confusing to me, to be honest, also because the comment for the second block here reads funny. We don't really merge anything, do we? We only generate logs and compare that the log with and without the resulting merged commit graph is the same. The actual logic happened before.
t/t4216-log-bloom.sh test_expect_success 'Bloom generation backfills empty commits' ' · line 482
Quoted 1 line
t/t4216-log-bloom.sh +1
-0+ test_cmp expect actual && cat err &&
The cat err looks like a leftover from debugging.
t/t4216-log-bloom.sh test_expect_success 'Bloom generation backfills empty commits' ' · line 483
Quoted 1 line
t/t4216-log-bloom.sh +1
-0+ grep "statistics:{\"filter_not_present\":0" trace.perf &&
Also, why should the filter not be present here? If we merge the commit-graphs with --changed-paths I'd have expected that we either carry over bloom filters from preexisting commit graphs if compatible, or otherwise generate them if they are either incompatible or don't exist.
I feel like I'm missing something obvious, so this may be me just missing the bigger picture.
t/t4216-log-bloom.sh test_expect_success 'Bloom generation backfills empty commits' ' · line 484
Quoted 1 line
t/t4216-log-bloom.sh +1
-0+ ! grep "disabling Bloom filters" err
Can we make this assertion stricter and verify that err is empty? I always think that ! grep is quite a fragile pattern as it is quite prone to becoming stale, e.g. when the error message itself would change.
Patrick
Quoted 6 lines
t/t4216-log-bloom.sh +2
-0+' + test_doneSignature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 4/17 gitformat-commit-graph: describe version 2 of BDAT
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
The code change to Git to support version 2 will be done in subsequent commits.
--- Documentation/gitformat-commit-graph.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
Documentation/gitformat-commit-graph.txt +6 -3
@@ -142,13 +142,16 @@ All multi-byte numbers are in network byte order. ==== Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional] * It starts with header consisting of three unsigned 32-bit integers: - - Version of the hash algorithm being used. We currently only support - value 1 which corresponds to the 32-bit version of the murmur3 hash + - Version of the hash algorithm being used. We currently support + value 2 which corresponds to the 32-bit version of the murmur3 hash implemented exactly as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm and the double hashing technique using seed values 0x293ae76f and 0x7e646e2 as described in https://doi.org/10.1007/978-3-540-30494-4_26 "Bloom Filters - in Probabilistic Verification" + in Probabilistic Verification". Version 1 Bloom filters have a bug that appears + when char is signed and the repository has path names that have characters >= + 0x80; Git supports reading and writing them, but this ability will be removed + in a future version of Git. - The number of times a path is hashed and hence the number of bit positions that cumulatively determine whether a file is present in the commit. - The minimum number of bits 'b' per entry in the Bloom filter. If the filter
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 5/17 t/helper/test-read-graph.c: extract `dump_graph_info()`
In reply to Taylor BlauPrepare for the 'read-graph' test helper to perform other tasks besides dumping high-level information about the commit-graph by extracting its main routine into a separate function.
--- t/helper/test-read-graph.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-)
t/helper/test-read-graph.c +18 -13
@@ -5,20 +5,8 @@#include "bloom.h" #include "setup.h" -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_info (struct commit_graph *graph) { -struct commit_graph *graph =NULL ; -struct object_directory *odb; - -setup_git_directory (); - odb = the_repository->objects->odb; - -prepare_repo_settings (the_repository); - - graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; - printf("header: %08x %d %d %d %d\n", ntohl(*(uint32_t*)graph->data), *(unsigned char*)(graph->data + 4),
@@ -57,6 +45,23 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)if (graph->topo_levels)printf (" topo_levels" );printf ("\n" ); +} + +int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +{ +struct commit_graph *graph =NULL ; +struct object_directory *odb; + +setup_git_directory (); + odb = the_repository->objects->odb; + +prepare_repo_settings (the_repository); + + graph =read_commit_graph_one (the_repository, odb); +if (!graph) +return 1 ; + +dump_graph_info (graph);UNLEAK (graph);
Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 05/17] t/helper/test-read-graph.c: extract `dump_graph_info()`
Quoted 8 lines · Taylor Blau
Prepare for the 'read-graph' test helper to perform other tasks besides dumping high-level information about the commit-graph by extracting its main routine into a separate function.
Signed-off-by Taylor Blau <[email protected]>Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>
Nit: your signoff is duplicated here. This is also still the case for some of the other commits.
Patrick
Quoted 57 lines
--- t/helper/test-read-graph.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-)t/helper/test-read-graph.c +18
-13@@ -5,20 +5,8 @@#include "bloom.h" #include "setup.h" -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_info (struct commit_graph *graph) { -struct commit_graph *graph =NULL ; -struct object_directory *odb; - -setup_git_directory (); - odb = the_repository->objects->odb; - -prepare_repo_settings (the_repository); - - graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; - printf("header: %08x %d %d %d %d\n", ntohl(*(uint32_t*)graph->data), *(unsigned char*)(graph->data + 4),@@ -57,6 +45,23 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)if (graph->topo_levels)printf (" topo_levels" );printf ("\n" ); +} + +int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +{ +struct commit_graph *graph =NULL ; +struct object_directory *odb; + +setup_git_directory (); + odb = the_repository->objects->odb; + +prepare_repo_settings (the_repository); + + graph =read_commit_graph_one (the_repository, odb); +if (!graph) +return 1 ; + +dump_graph_info (graph);UNLEAK (graph);Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 05/17] t/helper/test-read-graph.c: extract `dump_graph_info()`
Quoted 5 lines · Patrick Steinhardt
Quoted 4 lines · Taylor Blau
Prepare for the 'read-graph' test helper to perform other tasks besides dumping high-level information about the commit-graph by extracting its main routine into a separate function.
Quoted 7 lines
Quoted 4 lines
Signed-off-by Taylor Blau <[email protected]>Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>Nit: your signoff is duplicated here. This is also still the case for some of the other commits.
Yeah, this is an artifact of having tossed these patches back and forth (originally Jonathan sent some of these, then I sent another round, then Jonathan, now me again). It's a little verbose, but accurately tracks the DCO across multiple rounds.
Thanks,
Taylor
Re: [PATCH v3 05/17] t/helper/test-read-graph.c: extract `dump_graph_info()`
Quoted 11 lines · Taylor Blau
Quoted 10 lines · Patrick Steinhardt
Quoted 8 lines · Taylor Blau
Prepare for the 'read-graph' test helper to perform other tasks besides dumping high-level information about the commit-graph by extracting its main routine into a separate function.
Signed-off-by Taylor Blau <[email protected]>Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>
Quoted 7 lines
Quoted 2 lines
Nit: your signoff is duplicated here. This is also still the case for some of the other commits.
Yeah, this is an artifact of having tossed these patches back and forth (originally Jonathan sent some of these, then I sent another round, then Jonathan, now me again). It's a little verbose, but accurately tracks the DCO across multiple rounds.
Well, between you and Jonathan Tan, that might be true, but my involvement in the chain is only that I happened to have looked at an earlier round and without doing anything else. Even if you refetched from my tree and based the new round on that version, it would have been the same if you started from what you sent out earlier without even looking at my tree. So I do not think that chain of DCO adds very little value by recording my sign-off there.
PATCH v3 6/17 bloom.h: make `load_bloom_filter_from_graph()` public
In reply to Taylor BlauPrepare for a future commit to use the load_bloom_filter_from_graph() function directly to load specific Bloom filters out of the commit-graph for manual inspection (to be used during tests).
--- bloom.c | 6 +++--- bloom.h | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-)
bloom.c +3 -3
@@ -29,9 +29,9 @@ static inline unsigned char get_bitmask(uint32_t pos)return ((unsigned char )1 ) << (pos & (BITS_PER_WORD -1 )); } -static int load_bloom_filter_from_graph (struct commit_graph *g, -struct bloom_filter *filter, -uint32_t graph_pos) +int load_bloom_filter_from_graph (struct commit_graph *g, +struct bloom_filter *filter, +uint32_t graph_pos) {uint32_t lex_pos, start_index, end_index;
bloom.h +5 -0
@@ -3,6 +3,7 @@struct commit ;struct repository ; +struct commit_graph ;struct bloom_filter_settings {/*
@@ -68,6 +69,10 @@ struct bloom_key {uint32_t *hashes; }; +int load_bloom_filter_from_graph (struct commit_graph *g, +struct bloom_filter *filter, +uint32_t graph_pos); +/* * Calculate the murmur3 32-bit hash value for the given data * using the given seed.
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 7/17 t/helper/test-read-graph: implement `bloom-filters` mode
In reply to Taylor BlauImplement a mode of the "read-graph" test helper to dump out the hexadecimal contents of the Bloom filter(s) contained in a commit-graph.
--- t/helper/test-read-graph.c | 44 +++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-)
t/helper/test-read-graph.c +39 -5
@@ -47,10 +47,32 @@ static void dump_graph_info(struct commit_graph *graph)printf ("\n" ); } -int cmd__read_graph(int argc UNUSED, const char **argv UNUSED) +static void dump_graph_bloom_filters (struct commit_graph *graph) +{ +uint32_t i; + +for (i =0 ; i < graph->num_commits + graph->num_commits_in_base; i++) { +struct bloom_filter filter = {0 }; +size_t j; + +if (load_bloom_filter_from_graph (graph, &filter, i) <0 ) { +fprintf (stderr,"missing Bloom filter for graph " +"position %" PRIu32"\n" , i); +continue ; + } + +for (j =0 ; j < filter.len; j++) +printf ("%02x" , filter.data[j]); +if (filter.len) +printf ("\n" ); + } +} + +int cmd__read_graph (int argc,const char **argv) {struct commit_graph *graph =NULL ;struct object_directory *odb; +int ret =0 ;setup_git_directory (); odb = the_repository->objects->odb;
@@ -58,12 +80,24 @@ int cmd__read_graph(int argc UNUSED, const char **argv UNUSED)prepare_repo_settings (the_repository); graph =read_commit_graph_one (the_repository, odb); -if (!graph) -return 1 ; +if (!graph) { + ret =1 ; + goto done; + } -dump_graph_info (graph); +if (argc <=1 ) +dump_graph_info (graph); +else if (!strcmp (argv[1 ],"bloom-filters" )) +dump_graph_bloom_filters (graph); +else { +fprintf (stderr,"unknown sub-command: '%s'\n" , argv[1 ]); + ret =1 ; + } +done:UNLEAK (graph); -return 0 ; +return ret; } + +
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 8/17 t4216: test changed path filters with high bit paths
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
Subsequent commits will teach Git another version of changed path filter that has different behavior with paths that contain at least one character with its high bit set, so test the existing behavior as a baseline.
--- t/t4216-log-bloom.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
t/t4216-log-bloom.sh +52 -0
@@ -484,4 +484,56 @@ test_expect_success 'merge graph layers with incompatible Bloom settings' ' ! grep "disabling Bloom filters" err ' +get_first_changed_path_filter () { + test-tool read-graph bloom-filters >filters.dat && + head -n 1 filters.dat +} + +# chosen to be the same under all Unicode normalization forms +CENT=$(printf "\302\242") + +test_expect_success 'set up repo with high bit path, version 1 changed-path' ' + git init highbit1 && + test_commit -C highbit1 c1 "$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths +' + +test_expect_success 'setup check value of version 1 changed-path' ' + ( + cd highbit1 && + echo "52a9" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +# expect will not match actual if char is unsigned by default. Write the test +# in this way, so that a user running this test script can still see if the two +# files match. (It will appear as an ordinary success if they match, and a skip +# if not.) +if test_cmp highbit1/expect highbit1/actual +then + test_set_prereq SIGNED_CHAR_BY_DEFAULT +fi +test_expect_success SIGNED_CHAR_BY_DEFAULT 'check value of version 1 changed-path' ' + # Only the prereq matters for this test. + true +' + +test_expect_success 'setup make another commit' ' + # "git log" does not use Bloom filters for root commits - see how, in + # revision.c, rev_compare_tree() (the only code path that eventually calls + # get_bloom_filter()) is only called by try_to_simplify_commit() when the commit + # has one parent. Therefore, make another commit so that we perform the tests on + # a non-root commit. + test_commit -C highbit1 anotherc1 "another$CENT" +' + +test_expect_success 'version 1 changed-path used when version 1 requested' ' + ( + cd highbit1 && + test_bloom_filters_used "-- another$CENT" + ) +' + test_done
Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 08/17] t4216: test changed path filters with high bit paths
Quoted 7 lines · Taylor Blau
From: Jonathan Tan <[email protected]>
Subsequent commits will teach Git another version of changed path filter that has different behavior with paths that contain at least one character with its high bit set, so test the existing behavior as a baseline.
Quoted 5 lines
Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>
Nit: the signoffs are still funny here.
Quoted 37 lines
--- t/t4216-log-bloom.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+)t/t4216-log-bloom.sh +25
-0@@ -484,4 +484,56 @@ test_expect_success 'merge graph layers with incompatible Bloom settings' ' ! grep "disabling Bloom filters" err ' +get_first_changed_path_filter () { + test-tool read-graph bloom-filters >filters.dat && + head -n 1 filters.dat +} + +# chosen to be the same under all Unicode normalization forms +CENT=$(printf "\302\242") + +test_expect_success 'set up repo with high bit path, version 1 changed-path' ' + git init highbit1 && + test_commit -C highbit1 c1 "$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths +' + +test_expect_success 'setup check value of version 1 changed-path' ' + ( + cd highbit1 && + echo "52a9" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +# expect will not match actual if char is unsigned by default. Write the test +# in this way, so that a user running this test script can still see if the two
t/t4216-log-bloom.sh test_expect_success 'merge graph layers with incompatible Bloom settings' ' · line 521
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+# files match. (It will appear as an ordinary success if they match, and a skip +# if not.) +if test_cmp highbit1/expect highbit1/actual +then + test_set_prereq SIGNED_CHAR_BY_DEFAULT +fi +test_expect_success SIGNED_CHAR_BY_DEFAULT 'check value of version 1 changed-path' ' + # Only the prereq matters for this test. + true +'
Doesn't this mean that the preceding test where we test_cmp expect actual can fail on some platforms depending on the signedness of char?
Patrick
Quoted 20 lines
t/t4216-log-bloom.sh +16
-0+test_expect_success 'setup make another commit' ' + # "git log" does not use Bloom filters for root commits - see how, in + # revision.c, rev_compare_tree() (the only code path that eventually calls + # get_bloom_filter()) is only called by try_to_simplify_commit() when the commit + # has one parent. Therefore, make another commit so that we perform the tests on + # a non-root commit. + test_commit -C highbit1 anotherc1 "another$CENT" +' + +test_expect_success 'version 1 changed-path used when version 1 requested' ' + ( + cd highbit1 && + test_bloom_filters_used "-- another$CENT" + ) +' + test_doneSignature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 08/17] t4216: test changed path filters with high bit paths
Quoted 17 lines · Patrick Steinhardt
Quoted 17 lines
(quoted diff) +17
-0+test_expect_success 'setup check value of version 1 changed-path' ' + ( + cd highbit1 && + echo "52a9" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +# expect will not match actual if char is unsigned by default. Write the test +# in this way, so that a user running this test script can still see if the two +# files match. (It will appear as an ordinary success if they match, and a skip +# if not.) +if test_cmp highbit1/expect highbit1/actual +then + test_set_prereq SIGNED_CHAR_BY_DEFAULT +fi
Quoted 8 lines
Review
Quoted 4 lines
(quoted diff) +4
-0+test_expect_success SIGNED_CHAR_BY_DEFAULT 'check value of version 1 changed-path' ' + # Only the prereq matters for this test. + true +'Doesn't this mean that the preceding test where we
test_cmp expect actualcan fail on some platforms depending on the signedness ofchar?
Great catch, I am surprised this slipped by in earlier rounds. This should do the trick, since we don't actually care about conditioning that test's passing on test_cmp coming up clean. We check that in the if statement you pointed out here, so:
--- 8< ---
t/t4216-log-bloom.sh +1 -3
@@ -502,8 +502,7 @@ test_expect_success 'setup check value of version 1 changed-path' ' ( cd highbit1 && echo "52a9" >expect && - get_first_changed_path_filter >actual && - test_cmp expect actual + get_first_changed_path_filter >actual ) ' --- >8 ---
Thanks,
Taylor
PATCH v3 9/17 repo-settings: introduce commitgraph.changedPathsVersion
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
A subsequent commit will introduce another version of the changed-path filter in the commit graph file. In order to control which version to write (and read), a config variable is needed.
Therefore, introduce this config variable. For forwards compatibility, teach Git to not read commit graphs when the config variable is set to an unsupported version. Because we teach Git this, commitgraph.readChangedPaths is now redundant, so deprecate it and define its behavior in terms of the config variable we introduce.
This commit does not change the behavior of writing (Git writes changed path filters when explicitly instructed regardless of any config variable), but a subsequent commit will restrict Git such that it will only write when commitgraph.changedPathsVersion is a recognized value.
--- Documentation/config/commitgraph.txt | 23 ++++++++++++++++++++--- commit-graph.c | 2 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +++++- repository.h | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-)
Documentation/config/commitgraph.txt +20 -3
@@ -9,6 +9,23 @@ commitGraph.maxNewFilters:: commit-graph write` (c.f., linkgit:git-commit-graph[1]). commitGraph.readChangedPaths:: - If true, then git will use the changed-path Bloom filters in the - commit-graph file (if it exists, and they are present). Defaults to - true. See linkgit:git-commit-graph[1] for more information. + Deprecated. Equivalent to commitGraph.changedPathsVersion=-1 if true, and + commitGraph.changedPathsVersion=0 if false. (If commitGraph.changedPathVersion + is also set, commitGraph.changedPathsVersion takes precedence.) + +commitGraph.changedPathsVersion:: + Specifies the version of the changed-path Bloom filters that Git will read and + write. May be -1, 0 or 1. ++ +Defaults to -1. ++ +If -1, Git will use the version of the changed-path Bloom filters in the +repository, defaulting to 1 if there are none. ++ +If 0, Git will not read any Bloom filters, and will write version 1 Bloom +filters when instructed to write. ++ +If 1, Git will only read version 1 Bloom filters, and will write version 1 +Bloom filters. ++ +See linkgit:git-commit-graph[1] for more information.
commit-graph.c +1 -1
@@ -411,7 +411,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, graph->read_generation_data =1 ; } -if (s->commit_graph_read_changed_paths) { +if (s->commit_graph_changed_paths_version) {pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes); read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA,
oss-fuzz/fuzz-commit-graph.c +1 -1
@@ -19,7 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)* possible. */ the_repository->settings.commit_graph_generation_version =2 ; - the_repository->settings.commit_graph_read_changed_paths =1 ; + the_repository->settings.commit_graph_changed_paths_version =1 ; g =parse_commit_graph (&the_repository->settings, (void *)data, size);repo_clear (the_repository);free_commit_graph (g);
repo-settings.c +5 -1
@@ -24,6 +24,7 @@ void prepare_repo_settings(struct repository *r)int value;const char *strval;int manyfiles; +int read_changed_paths;if (!r->gitdir)BUG ("Cannot add settings for uninitialized repository" );
@@ -54,7 +55,10 @@ void prepare_repo_settings(struct repository *r)/* Commit graph config or default, does not cascade (simple) */ repo_cfg_bool (r,"core.commitgraph" , &r->settings.core_commit_graph,1 );repo_cfg_int (r,"commitgraph.generationversion" , &r->settings.commit_graph_generation_version,2 ); -repo_cfg_bool (r,"commitgraph.readchangedpaths" , &r->settings.commit_graph_read_changed_paths,1 ); +repo_cfg_bool (r,"commitgraph.readchangedpaths" , &read_changed_paths,1 ); +repo_cfg_int (r,"commitgraph.changedpathsversion" , + &r->settings.commit_graph_changed_paths_version, + read_changed_paths ?-1 :0 );repo_cfg_bool (r,"gc.writecommitgraph" , &r->settings.gc_write_commit_graph,1 );repo_cfg_bool (r,"fetch.writecommitgraph" , &r->settings.fetch_write_commit_graph,0 );
repository.h +1 -1
@@ -29,7 +29,7 @@ struct repo_settings {int core_commit_graph;int commit_graph_generation_version; -int commit_graph_read_changed_paths; +int commit_graph_changed_paths_version;int gc_write_commit_graph;int fetch_write_commit_graph;int command_requires_full_index;
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 10/17 commit-graph: new filter ver. that fixes murmur3
In reply to Taylor BlauFrom: Jonathan Tan <[email protected]>
The murmur3 implementation in bloom.c has a bug when converting series of 4 bytes into network-order integers when char is signed (which is controllable by a compiler option, and the default signedness of char is platform-specific). When a string contains characters with the high bit set, this bug causes results that, although internally consistent within Git, does not accord with other implementations of murmur3 (thus, the changed path filters wouldn't be readable by other off-the-shelf implementatios of murmur3) and even with Git binaries that were compiled with different signedness of char. This bug affects both how Git writes changed path filters to disk and how Git interprets changed path filters on disk.
Therefore, introduce a new version (2) of changed path filters that corrects this problem. The existing version (1) is still supported and is still the default, but users should migrate away from it as soon as possible.
Because this bug only manifests with characters that have the high bit set, it may be possible that some (or all) commits in a given repo would have the same changed path filter both before and after this fix is applied. However, in order to determine whether this is the case, the changed paths would first have to be computed, at which point it is not much more expensive to just compute a new changed path filter.
So this patch does not include any mechanism to "salvage" changed path filters from repositories. There is also no "mixed" mode - for each invocation of Git, reading and writing changed path filters are done with the same version number; this version number may be explicitly stated (typically if the user knows which version they need) or automatically determined from the version of the existing changed path filters in the repository.
There is a change in write_commit_graph(). graph_read_bloom_data() makes it possible for chunk_bloom_data to be non-NULL but bloom_filter_settings to be NULL, which causes a segfault later on. I produced such a segfault while developing this patch, but couldn't find a way to reproduce it neither after this complete patch (or before), but in any case it seemed like a good thing to include that might help future patch authors.
The value in t0095 was obtained from another murmur3 implementation using the following Go source code:
package main
import "fmt"
import "github.com/spaolacci/murmur3"
func main() {
fmt.Printf("%x\n", murmur3.Sum32([]byte("Hello world!")))
fmt.Printf("%x\n", murmur3.Sum32([]byte{0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}))
}--- Documentation/config/commitgraph.txt | 5 +- bloom.c | 69 +++++++++++++++++- bloom.h | 8 +- commit-graph.c | 32 ++++++-- t/helper/test-bloom.c | 9 ++- t/t0095-bloom.sh | 8 ++ t/t4216-log-bloom.sh | 105 +++++++++++++++++++++++++++ 7 files changed, 223 insertions(+), 13 deletions(-)
Documentation/config/commitgraph.txt +4 -1
@@ -15,7 +15,7 @@ commitGraph.readChangedPaths:: commitGraph.changedPathsVersion:: Specifies the version of the changed-path Bloom filters that Git will read and - write. May be -1, 0 or 1. + write. May be -1, 0, 1, or 2. + Defaults to -1. +
@@ -28,4 +28,7 @@ filters when instructed to write. If 1, Git will only read version 1 Bloom filters, and will write version 1 Bloom filters. + +If 2, Git will only read version 2 Bloom filters, and will write version 2 +Bloom filters. ++ See linkgit:git-commit-graph[1] for more information.
bloom.c +66 -3
81 lines not on this page · raw message
bloom.h +5 -3
@@ -8,9 +8,11 @@ struct commit_graph;struct bloom_filter_settings {/* * The version of the hashing technique being used. -* We currently only support version = 1 which is +* The newest version is 2, which is * the seeded murmur3 hashing technique implemented -* in bloom.c. +* in bloom.c. Bloom filters of version 1 were created +* with prior versions of Git, which had a bug in the +* implementation of the hash function. */ uint32_t hash_version;
@@ -80,7 +82,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len); +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len); void fill_bloom_key(const char *data, size_t len,
commit-graph.c +27 -5
@@ -314,17 +314,26 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } +struct graph_read_bloom_data_context { +struct commit_graph *g; +int *commit_graph_changed_paths_version; +}; +static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct commit_graph *g = data; +struct graph_read_bloom_data_context *c = data; +struct commit_graph *g = c->g;uint32_t hash_version; - g->chunk_bloom_data = chunk_start; hash_version =get_be32 (chunk_start); -if (hash_version !=1 ) +if (*c->commit_graph_changed_paths_version ==-1 ) { + *c->commit_graph_changed_paths_version = hash_version; + }else if (hash_version != *c->commit_graph_changed_paths_version) {return 0 ; + } + g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version; g->bloom_filter_settings->num_hashes =get_be32 (chunk_start +4 );
@@ -412,10 +421,14 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { +struct graph_read_bloom_data_context context = { + .g = graph, + .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version + };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, graph); + graph_read_bloom_data, &context); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
@@ -2441,6 +2454,13 @@ int write_commit_graph(struct object_directory *odb, }if (!commit_graph_compatible (r))return 0 ; +if (r->settings.commit_graph_changed_paths_version <-1 + || r->settings.commit_graph_changed_paths_version >2 ) { +warning (_ ("attempting to write a commit-graph, but " +"'commitgraph.changedPathsVersion' (%d) is not supported" ), + r->settings.commit_graph_changed_paths_version); +return 0 ; + }CALLOC_ARRAY (ctx,1 ); ctx->r = r;
@@ -2453,6 +2473,8 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 + ?2 :1 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
@@ -2482,7 +2504,7 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph;/* We have changed-paths already. Keep them in the next graph */ -if (g && g->chunk_bloom_data) { +if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; ctx->bloom_settings = g->bloom_filter_settings; }
t/helper/test-bloom.c +8 -1
@@ -50,6 +50,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)static const char *bloom_usage ="\n" " test-tool bloom get_murmur3 <string>\n" +" test-tool bloom get_murmur3_seven_highbit\n" " test-tool bloom generate_filter <string> [<string>...]\n" " test-tool bloom get_filter_for_commit <commit-hex>\n" ;
@@ -64,7 +65,13 @@ int cmd__bloom(int argc, const char **argv)uint32_t hashed;if (argc <3 )usage (bloom_usage); - hashed =murmur3_seeded (0 , argv[2 ],strlen (argv[2 ])); + hashed =murmur3_seeded_v2 (0 , argv[2 ],strlen (argv[2 ])); +printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); + } + +if (!strcmp (argv[1 ],"get_murmur3_seven_highbit" )) { +uint32_t hashed; + hashed =murmur3_seeded_v2 (0 ,"\x99\xaa\xbb\xcc\xdd\xee\xff" ,7 );printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); }
t/t0095-bloom.sh +8 -0
@@ -29,6 +29,14 @@ test_expect_success 'compute unseeded murmur3 hash for test string 2' ' test_cmp expect actual ' +test_expect_success 'compute unseeded murmur3 hash for test string 3' ' + cat >expect <<-\EOF && + Murmur3 Hash with seed=0:0xa183ccfd + EOF + test-tool bloom get_murmur3_seven_highbit >actual && + test_cmp expect actual +' + test_expect_success 'compute bloom key for empty string' ' cat >expect <<-\EOF && Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|
t/t4216-log-bloom.sh +105 -0
109 lines not on this page · raw message
Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 10/17] commit-graph: new filter ver. that fixes murmur3
Quoted 224 lines · Taylor Blau
From: Jonathan Tan <[email protected]>
The murmur3 implementation in
bloom.chas a bug when converting series of 4 bytes into network-order integers when char is signed (which is controllable by a compiler option, and the default signedness of char is platform-specific). When a string contains characters with the high bit set, this bug causes results that, although internally consistent within Git, does not accord with other implementations of murmur3 (thus, the changed path filters wouldn't be readable by other off-the-shelf implementatios of murmur3) and even with Git binaries that were compiled with different signedness of char. This bug affects both how Git writes changed path filters to disk and how Git interprets changed path filters on disk.Therefore, introduce a new version (2) of changed path filters that corrects this problem. The existing version (1) is still supported and is still the default, but users should migrate away from it as soon as possible.
Because this bug only manifests with characters that have the high bit set, it may be possible that some (or all) commits in a given repo would have the same changed path filter both before and after this fix is applied. However, in order to determine whether this is the case, the changed paths would first have to be computed, at which point it is not much more expensive to just compute a new changed path filter.
So this patch does not include any mechanism to "salvage" changed path filters from repositories. There is also no "mixed" mode - for each invocation of Git, reading and writing changed path filters are done with the same version number; this version number may be explicitly stated (typically if the user knows which version they need) or automatically determined from the version of the existing changed path filters in the repository.
There is a change in
write_commit_graph().graph_read_bloom_data()makes it possible forchunk_bloom_datato be non-NULL butbloom_filter_settingsto be NULL, which causes a segfault later on. I produced such a segfault while developing this patch, but couldn't find a way to reproduce it neither after this complete patch (or before), but in any case it seemed like a good thing to include that might help future patch authors.The value in t0095 was obtained from another murmur3 implementation using the following Go source code:
package main
import "fmt"
import "github.com/spaolacci/murmur3"func main() { fmt.Printf("%x\n", murmur3.Sum32([]byte("Hello world!"))) fmt.Printf("%x\n", murmur3.Sum32([]byte{0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})) }Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>--- Documentation/config/commitgraph.txt | 5 +- bloom.c | 69 +++++++++++++++++- bloom.h | 8 +- commit-graph.c | 32 ++++++-- t/helper/test-bloom.c | 9 ++- t/t0095-bloom.sh | 8 ++ t/t4216-log-bloom.sh | 105 +++++++++++++++++++++++++++ 7 files changed, 223 insertions(+), 13 deletions(-)Documentation/config/commitgraph.txt +4
-1@@ -15,7 +15,7 @@ commitGraph.readChangedPaths:: commitGraph.changedPathsVersion:: Specifies the version of the changed-path Bloom filters that Git will read and - write. May be -1, 0 or 1. + write. May be -1, 0, 1, or 2. + Defaults to -1. +@@ -28,4 +28,7 @@ filters when instructed to write. If 1, Git will only read version 1 Bloom filters, and will write version 1 Bloom filters. + +If 2, Git will only read version 2 Bloom filters, and will write version 2 +Bloom filters. ++ See linkgit:git-commit-graph[1] for more information.bloom.c +66
-3@@ -66,7 +66,64 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len) +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len) +{ +const uint32_t c1 =0xcc9e2d51 ; +const uint32_t c2 =0x1b873593 ; +const uint32_t r1 =15 ; +const uint32_t r2 =13 ; +const uint32_t m =5 ; +const uint32_t n =0xe6546b64 ; +int i; +uint32_t k1 =0 ; +const char *tail; + +int len4 = len /sizeof (uint32_t ); + +uint32_t k; +for (i =0 ; i < len4; i++) { +uint32_t byte1 = (uint32_t )(unsigned char )data[4 *i]; +uint32_t byte2 = ((uint32_t )(unsigned char )data[4 *i +1 ]) <<8 ; +uint32_t byte3 = ((uint32_t )(unsigned char )data[4 *i +2 ]) <<16 ; +uint32_t byte4 = ((uint32_t )(unsigned char )data[4 *i +3 ]) <<24 ; + k = byte1 | byte2 | byte3 | byte4; + k *= c1; + k =rotate_left (k, r1); + k *= c2; + + seed ^= k; + seed =rotate_left (seed, r2) * m + n; + } + + tail = (data + len4 *sizeof (uint32_t )); + +switch (len & (sizeof (uint32_t ) -1 )) { +case 3 : + k1 ^= ((uint32_t )(unsigned char )tail[2 ]) <<16 ; +/*-fallthrough*/ +case 2 : + k1 ^= ((uint32_t )(unsigned char )tail[1 ]) <<8 ; +/*-fallthrough*/ +case 1 : + k1 ^= ((uint32_t )(unsigned char )tail[0 ]) <<0 ; + k1 *= c1; + k1 =rotate_left (k1, r1); + k1 *= c2; + seed ^= k1; +break ; + } + + seed ^= (uint32_t )len; + seed ^= (seed >>16 ); + seed *=0x85ebca6b ; + seed ^= (seed >>13 ); + seed *=0xc2b2ae35 ; + seed ^= (seed >>16 ); + +return seed; +} + +static uint32_t murmur3_seeded_v1 (uint32_t seed,const char *data,size_t len) {const uint32_t c1 =0xcc9e2d51 ;const uint32_t c2 =0x1b873593 ;@@ -131,8 +188,14 @@ void fill_bloom_key(const char *data,int i;const uint32_t seed0 =0x293ae76f ;const uint32_t seed1 =0x7e646e2c ; -const uint32_t hash0 =murmur3_seeded (seed0, data, len); -const uint32_t hash1 =murmur3_seeded (seed1, data, len); +uint32_t hash0, hash1; +if (settings->hash_version ==2 ) { + hash0 =murmur3_seeded_v2 (seed0, data, len); + hash1 =murmur3_seeded_v2 (seed1, data, len); + }else { + hash0 =murmur3_seeded_v1 (seed0, data, len); + hash1 =murmur3_seeded_v1 (seed1, data, len); + } key->hashes = (uint32_t *)xcalloc (settings->num_hashes,sizeof (uint32_t ));for (i =0 ; i < settings->num_hashes; i++)bloom.h +5
-3@@ -8,9 +8,11 @@ struct commit_graph;struct bloom_filter_settings {/* * The version of the hashing technique being used. -* We currently only support version = 1 which is +* The newest version is 2, which is * the seeded murmur3 hashing technique implemented -* in bloom.c. +* in bloom.c. Bloom filters of version 1 were created +* with prior versions of Git, which had a bug in the +* implementation of the hash function. */ uint32_t hash_version;@@ -80,7 +82,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,* Not considered to be cryptographically secure. * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm */ -uint32_t murmur3_seeded (uint32_t seed,const char *data,size_t len); +uint32_t murmur3_seeded_v2 (uint32_t seed,const char *data,size_t len); void fill_bloom_key(const char *data, size_t len,commit-graph.c +7
-1@@ -314,17 +314,26 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } +struct graph_read_bloom_data_context { +struct commit_graph *g; +int *commit_graph_changed_paths_version; +}; +static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct commit_graph *g = data; +struct graph_read_bloom_data_context *c = data; +struct commit_graph *g = c->g;
commit-graph.c static int graph_read_oid_lookup(const unsigned char *chunk_start, · line 334
Quoted 10 lines
commit-graph.c +4
-2uint32_t hash_version; - g->chunk_bloom_data = chunk_start; hash_version =get_be32 (chunk_start); -if (hash_version !=1 ) +if (*c->commit_graph_changed_paths_version ==-1 ) { + *c->commit_graph_changed_paths_version = hash_version; + }else if (hash_version != *c->commit_graph_changed_paths_version) {return 0 ; + }
In case we have c->commit_graph_changed_paths_version == -1 we lose the check that the hash version is something that we know and support, don't we? And while we do start to handle -1 in the writing path, I think we don't in the reading path unless I missed something.
Quoted 201 lines
commit-graph.c +16
-2+ g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version; g->bloom_filter_settings->num_hashes =get_be32 (chunk_start +4 );@@ -412,10 +421,14 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { +struct graph_read_bloom_data_context context = { + .g = graph, + .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version + };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, graph); + graph_read_bloom_data, &context); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {@@ -2441,6 +2454,13 @@ int write_commit_graph(struct object_directory *odb, }if (!commit_graph_compatible (r))return 0 ; +if (r->settings.commit_graph_changed_paths_version <-1 + || r->settings.commit_graph_changed_paths_version >2 ) { +warning (_ ("attempting to write a commit-graph, but " +"'commitgraph.changedPathsVersion' (%d) is not supported" ), + r->settings.commit_graph_changed_paths_version); +return 0 ; + }CALLOC_ARRAY (ctx,1 ); ctx->r = r;@@ -2453,6 +2473,8 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 + ?2 :1 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",@@ -2482,7 +2504,7 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph;/* We have changed-paths already. Keep them in the next graph */ -if (g && g->chunk_bloom_data) { +if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; ctx->bloom_settings = g->bloom_filter_settings; }t/helper/test-bloom.c +8
-1@@ -50,6 +50,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)static const char *bloom_usage ="\n" " test-tool bloom get_murmur3 <string>\n" +" test-tool bloom get_murmur3_seven_highbit\n" " test-tool bloom generate_filter <string> [<string>...]\n" " test-tool bloom get_filter_for_commit <commit-hex>\n" ;@@ -64,7 +65,13 @@ int cmd__bloom(int argc, const char **argv)uint32_t hashed;if (argc <3 )usage (bloom_usage); - hashed =murmur3_seeded (0 , argv[2 ],strlen (argv[2 ])); + hashed =murmur3_seeded_v2 (0 , argv[2 ],strlen (argv[2 ])); +printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); + } + +if (!strcmp (argv[1 ],"get_murmur3_seven_highbit" )) { +uint32_t hashed; + hashed =murmur3_seeded_v2 (0 ,"\x99\xaa\xbb\xcc\xdd\xee\xff" ,7 );printf ("Murmur3 Hash with seed=0:0x%08x\n" , hashed); }t/t0095-bloom.sh +8
-0@@ -29,6 +29,14 @@ test_expect_success 'compute unseeded murmur3 hash for test string 2' ' test_cmp expect actual ' +test_expect_success 'compute unseeded murmur3 hash for test string 3' ' + cat >expect <<-\EOF && + Murmur3 Hash with seed=0:0xa183ccfd + EOF + test-tool bloom get_murmur3_seven_highbit >actual && + test_cmp expect actual +' + test_expect_success 'compute bloom key for empty string' ' cat >expect <<-\EOF && Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|t/t4216-log-bloom.sh +95
-0@@ -536,4 +536,109 @@ test_expect_success 'version 1 changed-path used when version 1 requested' ' ) ' +test_expect_success 'version 1 changed-path not used when version 2 requested' ' + ( + cd highbit1 && + git config --add commitgraph.changedPathsVersion 2 && + test_bloom_filters_not_used "-- another$CENT" + ) +' + +test_expect_success 'version 1 changed-path used when autodetect requested' ' + ( + cd highbit1 && + git config --add commitgraph.changedPathsVersion -1 && + test_bloom_filters_used "-- another$CENT" + ) +' + +test_expect_success 'when writing another commit graph, preserve existing version 1 of changed-path' ' + test_commit -C highbit1 c1double "$CENT$CENT" && + git -C highbit1 commit-graph write --reachable --changed-paths && + ( + cd highbit1 && + git config --add commitgraph.changedPathsVersion -1 && + echo "options: bloom(1,10,7) read_generation_data" >expect && + test-tool read-graph >full && + grep options full >actual && + test_cmp expect actual + ) +' + +test_expect_success 'set up repo with high bit path, version 2 changed-path' ' + git init highbit2 && + git -C highbit2 config --add commitgraph.changedPathsVersion 2 && + test_commit -C highbit2 c2 "$CENT" && + git -C highbit2 commit-graph write --reachable --changed-paths +' + +test_expect_success 'check value of version 2 changed-path' ' + ( + cd highbit2 && + echo "c01f" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' + +test_expect_success 'setup make another commit' ' + # "git log" does not use Bloom filters for root commits - see how, in + # revision.c, rev_compare_tree() (the only code path that eventually calls + # get_bloom_filter()) is only called by try_to_simplify_commit() when the commit + # has one parent. Therefore, make another commit so that we perform the tests on + # a non-root commit. + test_commit -C highbit2 anotherc2 "another$CENT" +' + +test_expect_success 'version 2 changed-path used when version 2 requested' ' + ( + cd highbit2 && + test_bloom_filters_used "-- another$CENT" + ) +' + +test_expect_success 'version 2 changed-path not used when version 1 requested' ' + ( + cd highbit2 && + git config --add commitgraph.changedPathsVersion 1 && + test_bloom_filters_not_used "-- another$CENT" + ) +' + +test_expect_success 'version 2 changed-path used when autodetect requested' ' + ( + cd highbit2 && + git config --add commitgraph.changedPathsVersion -1 && + test_bloom_filters_used "-- another$CENT" + ) +' + +test_expect_success 'when writing another commit graph, preserve existing version 2 of changed-path' ' + test_commit -C highbit2 c2double "$CENT$CENT" && + git -C highbit2 commit-graph write --reachable --changed-paths && + ( + cd highbit2 && + git config --add commitgraph.changedPathsVersion -1 && + echo "options: bloom(2,10,7) read_generation_data" >expect && + test-tool read-graph >full && + grep options full >actual && + test_cmp expect actual + ) +' + +test_expect_success 'when writing commit graph, do not reuse changed-path of another version' ' + git init doublewrite && + test_commit -C doublewrite c "$CENT" && + git -C doublewrite config --add commitgraph.changedPathsVersion 1 && + git -C doublewrite commit-graph write --reachable --changed-paths &&
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 643
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+ git -C doublewrite config --add commitgraph.changedPathsVersion 2 && + git -C doublewrite commit-graph write --reachable --changed-paths && + ( + cd doublewrite && + echo "c01f" >expect && + get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' +
With the supposedly missing check in mind, should we also add tests for currently unknown versions like 3 or -2?
Patrick
Quoted 4 lines
t/t4216-log-bloom.sh +0
-0test_doneSignature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 10/17] commit-graph: new filter ver. that fixes murmur3
Quoted 19 lines · Patrick Steinhardt
Quoted 19 lines
commit-graph.c +7
-2@@ -314,17 +314,26 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } +struct graph_read_bloom_data_context { +struct commit_graph *g; +int *commit_graph_changed_paths_version; +}; +static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct commit_graph *g = data; +struct graph_read_bloom_data_context *c = data; +struct commit_graph *g = c->g;uint32_t hash_version; - g->chunk_bloom_data = chunk_start; hash_version =get_be32 (chunk_start);
Quoted 11 lines
commit-graph.c static int graph_read_oid_lookup(const unsigned char *chunk_start, · line 334
Quoted 6 lines
commit-graph.c +4
-1-if (hash_version !=1 ) +if (*c->commit_graph_changed_paths_version ==-1 ) { + *c->commit_graph_changed_paths_version = hash_version; + }else if (hash_version != *c->commit_graph_changed_paths_version) {return 0 ; + }In case we have
c->commit_graph_changed_paths_version == -1we lose the check that the hash version is something that we know and support, don't we? And while we do start to handle-1in the writing path, I think we don't in the reading path unless I missed something.
We don't have to deal with c->commit_graph_changed_paths_version being -1 here, since we normalize it when reading the BDAT chunk. See commit-graph.c::graph_read_bloom_data(), particularly:
if (*c->commit_graph_changed_paths_version == -1)
*c->commit_graph_changed_paths_version = hash_version;
else if (hash_version != *c->commit_graph_changed_paths_version)
return 0;Quoted 10 lines
Quoted 10 lines
t/t4216-log-bloom.sh +10
-0+test_expect_success 'when writing commit graph, do not reuse changed-path of another version' ' + git init doublewrite && + test_commit -C doublewrite c "$CENT" && + git -C doublewrite config --add commitgraph.changedPathsVersion 1 && + git -C doublewrite commit-graph write --reachable --changed-paths && + git -C doublewrite config --add commitgraph.changedPathsVersion 2 && + git -C doublewrite commit-graph write --reachable --changed-paths && + ( + cd doublewrite && + echo "c01f" >expect &&
Quoted 8 lines
t/t4216-log-bloom.sh test_expect_success 'version 1 changed-path used when version 1 requested' ' · line 583
Quoted 5 lines
t/t4216-log-bloom.sh +5
-0+ get_first_changed_path_filter >actual && + test_cmp expect actual + ) +' +With the supposedly missing check in mind, should we also add tests for currently unknown versions like 3 or -2?
Good idea, I'll update the test to reflect.
Thanks,
Taylor
PATCH v3 11/17 bloom: annotate filters with hash version
In reply to Taylor BlauIn subsequent commits, we will want to load existing Bloom filters out of a commit-graph, even when the hash version they were computed with does not match the value of commitGraph.changedPathVersion.
In order to differentiate between the two, add a "version" field to each Bloom filter.
--- bloom.c | 11 ++++++++--- bloom.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-)
bloom.c +8 -3
@@ -55,6 +55,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g, filter->data = (unsigned char *)(g->chunk_bloom_data +sizeof (unsigned char ) * start_index +BLOOMDATA_CHUNK_HEADER_SIZE ); + filter->version = g->bloom_filter_settings->hash_version;return 1 ; }
@@ -240,11 +241,13 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,return strcmp (e1->path, e2->path); } -static void init_truncated_large_filter (struct bloom_filter *filter) +static void init_truncated_large_filter (struct bloom_filter *filter, +int version) { filter->data =xmalloc (1 ); filter->data[0 ] =0xFF ; filter->len =1 ; + filter->version = version; } struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
@@ -329,13 +332,15 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, }if (hashmap_get_size (&pathmap) > settings->max_changed_paths) { -init_truncated_large_filter (filter); +init_truncated_large_filter (filter, + settings->hash_version);if (computed) *computed |=BLOOM_TRUNC_LARGE ; goto cleanup; } filter->len = (hashmap_get_size (&pathmap) * settings->bits_per_entry +BITS_PER_WORD -1 ) /BITS_PER_WORD ; + filter->version = settings->hash_version;if (!filter->len) {if (computed) *computed |=BLOOM_TRUNC_EMPTY ;
@@ -355,7 +360,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, }else {for (i =0 ; i < diff_queued_diff.nr; i++)diff_free_filepair (diff_queued_diff.queue[i]); -init_truncated_large_filter (filter); +init_truncated_large_filter (filter, settings->hash_version);if (computed) *computed |=BLOOM_TRUNC_LARGE ;
bloom.h +1 -0
@@ -55,6 +55,7 @@ struct bloom_filter_settings {struct bloom_filter {unsigned char *data;size_t len; +int version; };/*
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 12/17 bloom: prepare to discard incompatible Bloom filters
In reply to Taylor BlauCallers use the inline get_bloom_filter() implementation as a thin wrapper around get_or_compute_bloom_filter(). The former calls the latter with a value of "0" for compute_if_not_present, making get_bloom_filter() the default read-only path for fetching an existing Bloom filter.
Callers expect the value returned from get_bloom_filter() is usable, that is that it's compatible with the configured value corresponding to commitGraph.changedPathsVersion.
This is OK, since the commit-graph machinery only initializes its BDAT chunk (thereby enabling it to service Bloom filter queries) when the Bloom filter hash_version is compatible with our settings. So any value returned by get_bloom_filter() is trivially useable.
However, subsequent commits will load the BDAT chunk even when the Bloom filters are built with incompatible hash versions. Prepare to handle this by teaching get_bloom_filter() to discard filters that are incompatible with the configured hash version.
Callers who wish to read incompatible filters (e.g., for upgrading filters from v1 to v2) may use the lower level routine, get_or_compute_bloom_filter().
--- bloom.c | 20 +++++++++++++++++++- bloom.h | 20 ++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-)
bloom.c +19 -1
@@ -250,6 +250,23 @@ static void init_truncated_large_filter(struct bloom_filter *filter, filter->version = version; } +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c) +{ +struct bloom_filter *filter; +int hash_version; + + filter =get_or_compute_bloom_filter (r, c,0 ,NULL ,NULL ); +if (!filter) +return NULL ; + +prepare_repo_settings (r); + hash_version = r->settings.commit_graph_changed_paths_version; + +if (!(hash_version ==-1 || hash_version == filter->version)) +return NULL ;/* unusable filter */ +return filter; +} + struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, struct commit *c, int compute_if_not_present,
@@ -275,7 +292,8 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter, graph_pos); } -if (filter->data && filter->len) +if ((filter->data && filter->len) && + (!settings || settings->hash_version == filter->version))return filter;if (!compute_if_not_present)return NULL ;
bloom.h +18 -2
@@ -110,8 +110,24 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,const struct bloom_filter_settings *settings, enum bloom_filter_computed *computed); -#define get_bloom_filter (r, c) get_or_compute_bloom_filter( \ - (r), (c), 0, NULL, NULL) +/* +* Find the Bloom filter associated with the given commit "c". +* +* If any of the following are true +* +* - the repository does not have a commit-graph, or +* - the repository disables reading from the commit-graph, or +* - the given commit does not have a Bloom filter computed, or +* - there is a Bloom filter for commit "c", but it cannot be read +* because the filter uses an incompatible version of murmur3 +* +* , then `get_bloom_filter()` will return NULL. Otherwise, the corresponding +* Bloom filter will be returned. +* +* For callers who wish to inspect Bloom filters with incompatible hash +* versions, use get_or_compute_bloom_filter(). +*/ +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c); int bloom_filter_contains(const struct bloom_filter *filter, const struct bloom_key *key,
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 13/17 commit-graph.c: unconditionally load Bloom filters
In reply to Taylor BlauIn 9e4df4da07 (commit-graph: new filter ver. that fixes murmur3, 2023-08-01), we began ignoring the Bloom data ("BDAT") chunk for commit-graphs whose Bloom filters were computed using a hash version incompatible with the value of commitGraph.changedPathVersion.
Now that the Bloom API has been hardened to discard these incompatible filters (with the exception of low-level APIs), we can safely load these Bloom filters unconditionally.
We no longer want to return early from graph_read_bloom_data(), and similarly do not want to set the bloom_settings' hash_version field as a side-effect. The latter is because we want to wait until we know which Bloom settings we're using (either the defaults, from the GIT_TEST variables, or from the previous commit-graph layer) before deciding what hash_version to use.
If we detect an existing BDAT chunk, we'll infer the rest of the settings (e.g., number of hashes, bits per entry, and maximum number of changed paths) from the earlier graph layer. The hash_version will be inferred from the previous layer as well, unless one has already been specified via configuration.
Once all of that is done, we normalize the value of the hash_version to either "1" or "2".
--- commit-graph.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-)
commit-graph.c +10 -9
@@ -327,12 +327,6 @@ static int graph_read_bloom_data(const unsigned char *chunk_start,uint32_t hash_version; hash_version =get_be32 (chunk_start); -if (*c->commit_graph_changed_paths_version ==-1 ) { - *c->commit_graph_changed_paths_version = hash_version; - }else if (hash_version != *c->commit_graph_changed_paths_version) { -return 0 ; - } - g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version;
@@ -2473,8 +2467,7 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; - bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 - ?2 :1 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
@@ -2506,10 +2499,18 @@ int write_commit_graph(struct object_directory *odb,/* We have changed-paths already. Keep them in the next graph */ if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; - ctx->bloom_settings = g->bloom_filter_settings; + +/* don't propagate the hash_version unless unspecified */ +if (bloom_settings.hash_version ==-1 ) + bloom_settings.hash_version = g->bloom_filter_settings->hash_version; + bloom_settings.bits_per_entry = g->bloom_filter_settings->bits_per_entry; + bloom_settings.num_hashes = g->bloom_filter_settings->num_hashes; + bloom_settings.max_changed_paths = g->bloom_filter_settings->max_changed_paths; } } + bloom_settings.hash_version = bloom_settings.hash_version ==2 ?2 :1 ; +if (ctx->split) {struct commit_graph *g = ctx->r->objects->commit_graph;
Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 13/17] commit-graph.c: unconditionally load Bloom filters
Quoted 1 line · Taylor Blau
In
9e4df4da07(commit-graph: new filter ver. that fixes murmur3,
Nit: It's a bit funny to read this reference to a commit ID when the commit in question is part of the same series. Isn't it likely to grow stale?
Quoted 63 lines
2023-08-01), we began ignoring the Bloom data ("BDAT") chunk for commit-graphs whose Bloom filters were computed using a hash version incompatible with the value of
commitGraph.changedPathVersion.Now that the Bloom API has been hardened to discard these incompatible filters (with the exception of low-level APIs), we can safely load these Bloom filters unconditionally.
We no longer want to return early from
graph_read_bloom_data(), and similarly do not want to set thebloom_settings'hash_versionfield as a side-effect. The latter is because we want to wait until we know which Bloom settings we're using (either the defaults, from theGIT_TESTvariables, or from the previous commit-graph layer) before deciding whathash_versionto use.If we detect an existing BDAT chunk, we'll infer the rest of the settings (e.g., number of hashes, bits per entry, and maximum number of changed paths) from the earlier graph layer. The
hash_versionwill be inferred from the previous layer as well, unless one has already been specified via configuration.Once all of that is done, we normalize the value of the
hash_versionto either "1" or "2".Signed-off-by Taylor Blau <[email protected]>--- commit-graph.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-)commit-graph.c +3
-9@@ -327,12 +327,6 @@ static int graph_read_bloom_data(const unsigned char *chunk_start,uint32_t hash_version; hash_version =get_be32 (chunk_start); -if (*c->commit_graph_changed_paths_version ==-1 ) { - *c->commit_graph_changed_paths_version = hash_version; - }else if (hash_version != *c->commit_graph_changed_paths_version) { -return 0 ; - } - g->chunk_bloom_data = chunk_start; g->bloom_filter_settings =xmalloc (sizeof (struct bloom_filter_settings )); g->bloom_filter_settings->hash_version = hash_version;@@ -2473,8 +2467,7 @@ int write_commit_graph(struct object_directory *odb, ctx->write_generation_data = (get_configured_generation_version (r) ==2 ); ctx->num_generation_data_overflows =0 ; - bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version ==2 - ?2 :1 ; + bloom_settings.hash_version = r->settings.commit_graph_changed_paths_version; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry); bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",@@ -2506,10 +2499,18 @@ int write_commit_graph(struct object_directory *odb,/* We have changed-paths already. Keep them in the next graph */ if (g && g->bloom_filter_settings) { ctx->changed_paths =1 ; - ctx->bloom_settings = g->bloom_filter_settings; + +/* don't propagate the hash_version unless unspecified */
commit-graph.c int write_commit_graph(struct object_directory *odb, · line 2513
Quoted 10 lines
commit-graph.c +7
-0+if (bloom_settings.hash_version ==-1 ) + bloom_settings.hash_version = g->bloom_filter_settings->hash_version; + bloom_settings.bits_per_entry = g->bloom_filter_settings->bits_per_entry; + bloom_settings.num_hashes = g->bloom_filter_settings->num_hashes; + bloom_settings.max_changed_paths = g->bloom_filter_settings->max_changed_paths; } } + bloom_settings.hash_version = bloom_settings.hash_version ==2 ?2 :1 ; +
What if there is a future version of Git that writes Bloom filters with hash version 3? Should we really normalize that to 1?
Patrick
Quoted 6 lines
commit-graph.c +0
-0if (ctx->split) {struct commit_graph *g = ctx->r->objects->commit_graph;Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 14/17 commit-graph: drop unnecessary `graph_read_bloom_data_context`
In reply to Taylor BlauThe graph_read_bloom_data_context struct was introduced in an earlier commit in order to pass pointers to the commit-graph and changed-path Bloom filter version when reading the BDAT chunk.
The previous commit no longer writes through the changed_paths_version pointer, making the surrounding context structure unnecessary. Drop it and pass a pointer to the commit-graph directly when reading the BDAT chunk.
--- commit-graph.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-)
commit-graph.c +2 -12
@@ -314,16 +314,10 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start,return 0 ; } -struct graph_read_bloom_data_context { -struct commit_graph *g; -int *commit_graph_changed_paths_version; -}; -static int graph_read_bloom_data (const unsigned char *chunk_start,size_t chunk_size,void *data) { -struct graph_read_bloom_data_context *c = data; -struct commit_graph *g = c->g; +struct commit_graph *g = data;uint32_t hash_version; hash_version =get_be32 (chunk_start);
@@ -415,14 +409,10 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, }if (s->commit_graph_changed_paths_version) { -struct graph_read_bloom_data_context context = { - .g = graph, - .commit_graph_changed_paths_version = &s->commit_graph_changed_paths_version - };pair_chunk (cf,GRAPH_CHUNKID_BLOOMINDEXES , &graph->chunk_bloom_indexes);read_chunk (cf,GRAPH_CHUNKID_BLOOMDATA , - graph_read_bloom_data, &context); + graph_read_bloom_data, graph); }if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 15/17 object.h: fix mis-aligned flag bits table
In reply to Taylor BlauBit position 23 is one column too far to the left.
--- object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
object.h +1 -1
@@ -62,7 +62,7 @@ void object_array_init(struct object_array *array);/* * object flag allocation: -* revision.h: 0---------10 15 23------27 +* revision.h: 0---------10 15 23------27 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 * walker.c: 0-2
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 16/17 commit-graph: reuse existing Bloom filters where possible
In reply to Taylor BlauIn 9e4df4da07 (commit-graph: new filter ver. that fixes murmur3, 2023-08-01), a bug was described where it's possible for Git to produce non-murmur3 hashes when the platform's "char" type is signed, and there are paths with characters whose highest bit is set (i.e. all characters
Quoted 1 line
=
0x80).
That patch allows the caller to control which version of Bloom filters are read and written. However, even on platforms with a signed "char" type, it is possible to reuse existing Bloom filters if and only if there are no changed paths in any commit's first parent tree-diff whose characters have their highest bit set.
When this is the case, we can reuse the existing filter without having to compute a new one. This is done by marking trees which are known to have (or not have) any such paths. When a commit's root tree is verified to not have any such paths, we mark it as such and declare that the commit's Bloom filter is reusable.
Note that this heuristic only goes in one direction. If neither a commit nor its first parent have any paths in their trees with non-ASCII characters, then we know for certain that a path with non-ASCII characters will not appear in a tree-diff against that commit's first parent. The reverse isn't necessarily true: just because the tree-diff doesn't contain any such paths does not imply that no such paths exist in either tree.
So we end up recomputing some Bloom filters that we don't strictly have to (i.e. their bits are the same no matter which version of murmur3 we use). But culling these out is impossible, since we'd have to perform the full tree-diff, which is the same effort as computing the Bloom filter from scratch.
But because we can cache our results in each tree's flag bits, we can often avoid recomputing many filters, thereby reducing the time it takes to run
$ git commit-graph write --changed-paths --reachable
when upgrading from v1 to v2 Bloom filters.
To benchmark this, let's generate a commit-graph in linux.git with v1 changed-paths in generation order[^1]:
$ git clone [email protected]:torvalds/linux.git $ cd linux $ git commit-graph write --reachable --changed-paths $ graph=".git/objects/info/commit-graph" $ mv $graph{,.bak}
Then let's time how long it takes to go from v1 to v2 filters (with and without the upgrade path enabled), resetting the state of the commit-graph each time:
$ git config commitGraph.changedPathsVersion 2
$ hyperfine -p 'cp -f $graph.bak $graph' -L v 0,1 \
'GIT_TEST_UPGRADE_BLOOM_FILTERS={v} git.compile commit-graph write --reachable --changed-paths'On linux.git (where there aren't any non-ASCII paths), the timings indicate that this patch represents a speed-up over recomputing all Bloom filters from scratch:
Benchmark 1: GIT_TEST_UPGRADE_BLOOM_FILTERS=0 git.compile commit-graph write --reachable --changed-paths
Time (mean ± σ): 124.873 s ± 0.316 s [User: 124.081 s, System: 0.643 s]
Range (min … max): 124.621 s … 125.227 s 3 runs
Benchmark 2: GIT_TEST_UPGRADE_BLOOM_FILTERS=1 git.compile commit-graph write --reachable --changed-paths
Time (mean ± σ): 79.271 s ± 0.163 s [User: 74.611 s, System: 4.521 s]
Range (min … max): 79.112 s … 79.437 s 3 runs
Summary
'GIT_TEST_UPGRADE_BLOOM_FILTERS=1 git.compile commit-graph write --reachable --changed-paths' ran
1.58 ± 0.01 times faster than 'GIT_TEST_UPGRADE_BLOOM_FILTERS=0 git.compile commit-graph write --reachable --changed-paths'On git.git, we do have some non-ASCII paths, giving us a more modest improvement from 4.163 seconds to 3.348 seconds, for a 1.24x speed-up. On my machine, the stats for git.git are:
- 8,285 Bloom filters computed from scratch
- 10 Bloom filters generated as empty
- 4 Bloom filters generated as truncated due to too many changed paths
- 65,114 Bloom filters were reused when transitioning from v1 to v2.
[^1]: Note that this is is important, since --stdin-packs or
--stdin-commits orders commits in the commit-graph by their pack position (with --stdin-packs) or in the raw input (with --stdin-commits).
Since we compute Bloom filters in the same order that commits appear in the graph, we must see a commit's (first) parent before we process the commit itself. This is only guaranteed to happen when sorting commits by their generation number.
--- bloom.c | 90 ++++++++++++++++++++++++++++++++++++++++++-- bloom.h | 1 + commit-graph.c | 5 +++ object.h | 1 + t/t4216-log-bloom.sh | 35 ++++++++++++++++- 5 files changed, 127 insertions(+), 5 deletions(-)
bloom.c +87 -3
@@ -7,6 +7,9 @@#include "commit-graph.h" #include "commit.h" #include "commit-slab.h" +#include "tree.h" +#include "tree-walk.h" +#include "config.h" define_commit_slab(bloom_filter_slab, struct bloom_filter);
@@ -250,6 +253,73 @@ static void init_truncated_large_filter(struct bloom_filter *filter, filter->version = version; } +#define VISITED (1u<<21) +#define HIGH_BITS (1u<<22) + +static int has_entries_with_high_bit (struct repository *r,struct tree *t) +{ +if (parse_tree (t)) +return 1 ; + +if (!(t->object.flags &VISITED )) { +struct tree_desc desc; +struct name_entry entry; + +init_tree_desc (&desc, t->buffer, t->size); +while (tree_entry (&desc, &entry)) { +size_t i; +for (i =0 ; i < entry.pathlen; i++) { +if (entry.path[i] &0x80 ) { + t->object.flags |=HIGH_BITS ; + goto done; + } + } + +if (S_ISDIR (entry.mode)) { +struct tree *sub =lookup_tree (r, &entry.oid); +if (sub &&has_entries_with_high_bit (r, sub)) { + t->object.flags |=HIGH_BITS ; + goto done; + } + } + + } + +done: + t->object.flags |=VISITED ; + } + +return !!(t->object.flags &HIGH_BITS ); +} + +static int commit_tree_has_high_bit_paths (struct repository *r, +struct commit *c) +{ +struct tree *t; +if (repo_parse_commit (r, c)) +return 1 ; + t =repo_get_commit_tree (r, c); +if (!t) +return 1 ; +return has_entries_with_high_bit (r, t); +} + +static struct bloom_filter *upgrade_filter (struct repository *r,struct commit *c, +struct bloom_filter *filter, +int hash_version) +{ +struct commit_list *p = c->parents; +if (commit_tree_has_high_bit_paths (r, c)) +return NULL ; + +if (p &&commit_tree_has_high_bit_paths (r, p->item)) +return NULL ; + + filter->version = hash_version; + +return filter; +} +struct bloom_filter *get_bloom_filter (struct repository *r,struct commit *c) {struct bloom_filter *filter;
@@ -292,9 +362,23 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter, graph_pos); } -if ((filter->data && filter->len) && - (!settings || settings->hash_version == filter->version)) -return filter; +if (filter->data && filter->len) { +struct bloom_filter *upgrade; +if (!settings || settings->hash_version == filter->version) +return filter; + +/* version mismatch, see if we can upgrade */ +if (compute_if_not_present && +git_env_bool ("GIT_TEST_UPGRADE_BLOOM_FILTERS" ,1 )) { + upgrade =upgrade_filter (r, c, filter, + settings->hash_version); +if (upgrade) { +if (computed) + *computed |=BLOOM_UPGRADED ; +return upgrade; + } + } + }if (!compute_if_not_present)return NULL ;
bloom.h +1 -0
@@ -102,6 +102,7 @@ enum bloom_filter_computed {BLOOM_COMPUTED = (1 <<1 ),BLOOM_TRUNC_LARGE = (1 <<2 ),BLOOM_TRUNC_EMPTY = (1 <<3 ), + BLOOM_UPGRADED = (1 << 4), }; struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
commit-graph.c +5 -0
@@ -1109,6 +1109,7 @@ struct write_commit_graph_context {int count_bloom_filter_not_computed;int count_bloom_filter_trunc_empty;int count_bloom_filter_trunc_large; +int count_bloom_filter_upgraded; }; static int write_graph_chunk_fanout(struct hashfile *f,
@@ -1716,6 +1717,8 @@ static void trace2_bloom_filter_write_statistics(struct write_commit_graph_conte ctx->count_bloom_filter_trunc_empty);trace2_data_intmax ("commit-graph" , ctx->r,"filter-trunc-large" , ctx->count_bloom_filter_trunc_large); +trace2_data_intmax ("commit-graph" , ctx->r,"filter-upgraded" , + ctx->count_bloom_filter_upgraded); }static void compute_bloom_filters (struct write_commit_graph_context *ctx)
@@ -1757,6 +1760,8 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx) ctx->count_bloom_filter_trunc_empty++;if (computed &BLOOM_TRUNC_LARGE ) ctx->count_bloom_filter_trunc_large++; + }else if (computed &BLOOM_UPGRADED ) { + ctx->count_bloom_filter_upgraded++; }else if (computed &BLOOM_NOT_COMPUTED ) ctx->count_bloom_filter_not_computed++; ctx->total_bloom_filter_data_size += filter
object.h +1 -0
@@ -75,6 +75,7 @@ void object_array_init(struct object_array *array);* commit-reach.c: 16-----19 * sha1-name.c: 20 * list-objects-filter.c: 21 +* bloom.c: 2122 * builtin/fsck.c: 0--3 * builtin/gc.c: 0 * builtin/index-pack.c: 2021
t/t4216-log-bloom.sh +33 -2
@@ -221,6 +221,10 @@ test_filter_trunc_large () { grep "\"key\":\"filter-trunc-large\",\"value\":\"$1\"" $2 } +test_filter_upgraded () { + grep "\"key\":\"filter-upgraded\",\"value\":\"$1\"" $2 +} + test_expect_success 'correctly report changes over limit' ' git init limits && (
@@ -629,10 +633,19 @@ test_expect_success 'when writing another commit graph, preserve existing versio test_expect_success 'when writing commit graph, do not reuse changed-path of another version' ' git init doublewrite && test_commit -C doublewrite c "$CENT" && + git -C doublewrite config --add commitgraph.changedPathsVersion 1 && - git -C doublewrite commit-graph write --reachable --changed-paths && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C doublewrite commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + git -C doublewrite config --add commitgraph.changedPathsVersion 2 && - git -C doublewrite commit-graph write --reachable --changed-paths && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C doublewrite commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + ( cd doublewrite && echo "c01f" >expect &&
@@ -641,4 +654,22 @@ test_expect_success 'when writing commit graph, do not reuse changed-path of ano ) ' +test_expect_success 'when writing commit graph, reuse changed-path of another version where possible' ' + git init upgrade && + + test_commit -C upgrade base no-high-bits && + + git -C upgrade config --add commitgraph.changedPathsVersion 1 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C upgrade commit-graph write --reachable --changed-paths && + test_filter_computed 1 trace2.txt && + test_filter_upgraded 0 trace2.txt && + + git -C upgrade config --add commitgraph.changedPathsVersion 2 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C upgrade commit-graph write --reachable --changed-paths && + test_filter_computed 0 trace2.txt && + test_filter_upgraded 1 trace2.txt +' + test_done
Signature · 1 line
2.42.0.342.g8bb3a896ee
PATCH v3 17/17 bloom: introduce `deinit_bloom_filters()`
In reply to Taylor BlauAfter we are done using Bloom filters, we do not currently clean up any memory allocated by the commit slab used to store those filters in the first place.
Besides the bloom_filter structures themselves, there is mostly nothing to free() in the first place, since in the read-only path all Bloom filter's data members point to a memory mapped region in the commit-graph file itself.
But when generating Bloom filters from scratch (or initializing truncated filters) we allocate additional memory to store the filter's data.
Keep track of when we need to free() this additional chunk of memory by using an extra pointer to_free. Most of the time this will be NULL (indicating that we are representing an existing Bloom filter stored in a memory mapped region). When it is non-NULL, free it before discarding the Bloom filters slab.
--- bloom.c | 16 +++++++++++++++- bloom.h | 3 +++ commit-graph.c | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-)
bloom.c +15 -1
@@ -59,6 +59,7 @@ int load_bloom_filter_from_graph(struct commit_graph *g,sizeof (unsigned char ) * start_index + BLOOMDATA_CHUNK_HEADER_SIZE); filter->version = g->bloom_filter_settings->hash_version; + filter->to_free =NULL ;return 1 ; }
@@ -231,6 +232,18 @@ void init_bloom_filters(void)init_bloom_filter_slab (&bloom_filters); } +static void free_one_bloom_filter (struct bloom_filter *filter) +{ +if (!filter) +return ; +free (filter->to_free); +} + +void deinit_bloom_filters (void ) +{ +deep_clear_bloom_filter_slab (&bloom_filters, free_one_bloom_filter); +} + static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key,
@@ -247,7 +260,7 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,static void init_truncated_large_filter (struct bloom_filter *filter,int version) { - filter->data =xmalloc (1 ); + filter->data = filter->to_free =xmalloc (1 ); filter->data[0 ] =0xFF ; filter->len =1 ; filter->version = version;
@@ -449,6 +462,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, filter->len =1 ; }CALLOC_ARRAY (filter->data, filter->len); + filter->to_free = filter->data;hashmap_for_each_entry (&pathmap, &iter, e, entry) {struct bloom_key key;
bloom.h +3 -0
@@ -56,6 +56,8 @@ struct bloom_filter {unsigned char *data;size_t len;int version; + +void *to_free; };/*
@@ -96,6 +98,7 @@ void add_key_to_filter(const struct bloom_key *key, const struct bloom_filter_settings *settings);void init_bloom_filters (void ); +void deinit_bloom_filters (void );enum bloom_filter_computed {BLOOM_NOT_COMPUTED = (1 <<0 ),
commit-graph.c +4 -0
@@ -787,6 +787,7 @@ static void close_commit_graph_one(struct commit_graph *g)void close_commit_graph (struct raw_object_store *o) {close_commit_graph_one (o->commit_graph); +deinit_bloom_filters (); o->commit_graph =NULL ; }
@@ -2588,6 +2589,9 @@ int write_commit_graph(struct object_directory *odb, res =write_commit_graph_file (ctx); +if (ctx->changed_paths) +deinit_bloom_filters (); +if (ctx->split)mark_commit_graphs (ctx);
Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 00/17] bloom: changed-path Bloom filters v2 (& sundries)
In reply to Taylor BlauQuoted 16 lines · Taylor Blau
(Rebased onto the tip of 'master', which is
3a06386e31(The fifteenth batch, 2023-10-04), at the time of writing).This series is a reroll of the combined efforts of [1] and [2] to introduce the v2 changed-path Bloom filters, which fixes a bug in our existing implementation of murmur3 paths with non-ASCII characters (when the "char" type is signed).
In large part, this is the same as the previous round. But this round includes some extra bits that address issues pointed out by SZEDER Gábor, which are:
- not reading Bloom filters for root commits
- corrupting Bloom filter reads by tweaking the filter settings between layers.
Quoted 7 lines
These issues were discussed in (among other places) [3], and [4], respectively.
Thanks to Jonathan, Peff, and SZEDER who have helped a great deal in assembling these patches. As usual, a range-diff is included below. Thanks in advance for your
review!
As this patch series has been sitting around without reviews for a week I've tried my best to give it a go. Note though that this area is mostly outside of my own comfort zone, so some of the questions and suggestions might ultimately not apply.
Patrick
Quoted 187 lines
[1]: https://lore.kernel.org/git/[email protected]/
[2]: https://lore.kernel.org/git/[email protected]/
[3]: https://public-inbox.org/git/[email protected]/
[4]: https://lore.kernel.org/git/[email protected]/Jonathan Tan (4):
gitformat-commit-graph: describe version 2 of BDAT
t4216: test changed path filters with high bit paths
repo-settings: introduce commitgraph.changedPathsVersion
commit-graph: new filter ver. that fixes murmur3Taylor Blau (13):
t/t4216-log-bloom.sh: hardentest_bloom_filters_not_used()revision.c: consult Bloom filters for root commits
commit-graph: ensure Bloom filters are read with consistent settingst/helper/test-read-graph.c: extractdump_graph_info()bloom.h: makeload_bloom_filter_from_graph()public t/helper/test-read-graph: implementbloom-filtersmode
bloom: annotate filters with hash version
bloom: prepare to discard incompatible Bloom filterscommit-graph.c: unconditionally load Bloom filters
commit-graph: drop unnecessarygraph_read_bloom_data_contextobject.h: fix mis-aligned flag bits table
commit-graph: reuse existing Bloom filters where possible
bloom: introducedeinit_bloom_filters()Documentation/config/commitgraph.txt | 26 ++- Documentation/gitformat-commit-graph.txt | 9 +- bloom.c | 208 ++++++++++++++++- bloom.h | 38 +++- commit-graph.c | 61 ++++- object.h | 3 +- oss-fuzz/fuzz-commit-graph.c | 2 +- repo-settings.c | 6 +- repository.h | 2 +- revision.c | 26 ++- t/helper/test-bloom.c | 9 +- t/helper/test-read-graph.c | 67 ++++-- t/t0095-bloom.sh | 8 + t/t4216-log-bloom.sh | 272 ++++++++++++++++++++++- 14 files changed, 682 insertions(+), 55 deletions(-)Range-diff against v2:
10:002a06d1e9! 1:fe671d616ct/t4216-log-bloom.sh: hardentest_bloom_filters_not_used()
@@ Commit message
indicating that no filters were used.Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
t/t4216-log-bloom.sh##
@@t/t4216-log-bloom.sh:test_bloom_filters_used() {-: ---------- > 2: 7d0fa93543 revision.c: consult Bloom filters for root commits -: ---------- > 3: 2ecc0a2d58 commit-graph: ensure Bloom filters are read with consistent settings 1: 5fa681b58e ! 4: 17703ed89a gitformat-commit-graph: describe version 2 of BDAT@@ Commit message
Signed-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
Documentation/gitformat-commit-graph.txt##
@@Documentation/gitformat-commit-graph.txt: All multi-byte numbers are in network byte order.
2:623d840575! 5:94552abf45t/helper/test-read-graph.c: extractdump_graph_info()
@@ Commit messageSigned-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
t/helper/test-read-graph.c##
@@
3:bc9d77ae60! 6:3d81efa27bbloom.h: makeload_bloom_filter_from_graph()public
@@ Commit messageSigned-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
bloom.c##
@@bloom.c: static inline unsigned charget_bitmask(uint32_t pos)
4:ac7008aed3! 7:d23cd89037t/helper/test-read-graph: implementbloom-filtersmode
@@ Commit messageSigned-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
t/helper/test-read-graph.c##
@@t/helper/test-read-graph.c: static voiddump_graph_info(struct commit_graph *graph)
@@t/helper/test-read-graph.c: int cmd__read_graph(int argc UNUSED, const char **ar
- return 0;
+ return ret;
}
++
++
5:71755ba856! 8:cba766f224t4216: test changed path filters with high bit paths
@@ Commit messageSigned-off-by Taylor Blau <[email protected]>##
t/t4216-log-bloom.sh##
-@@t/t4216-log-bloom.sh:test_expect_success'Bloom generation backfills empty commits' '
- )
+@@t/t4216-log-bloom.sh:test_expect_success'merge graph layers with incompatible Bloom settings' '
+ ! grep "disabling Bloom filters" err
'+get_first_changed_path_filter () {
6:9768d92c0f! 9:a08a961f41repo-settings: introduce commitgraph.changedPathsVersion
@@ Commit messageSigned-off-by Jonathan Tan <[email protected]>Signed-off-by Junio C Hamano <[email protected]>Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
Documentation/config/commitgraph.txt##
@@Documentation/config/commitgraph.txt: commitGraph.maxNewFilters::7: f911b4bfab = 10: 61d44519a5 commit-graph: new filter ver. that fixes murmur3 8: 35009900df ! 11: a8c10f8de8 bloom: annotate filters with hash version@@ Commit message
Bloom filter.Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
bloom.c##
@@bloom.c: int load_bloom_filter_from_graph(structcommit_graph*g,
9:138bc16905! 12:2ba10a4b4bbloom: prepare to discard incompatible Bloom filters
@@ Commit message
get_or_compute_bloom_filter().Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
bloom.c##
@@bloom.c: static void init_truncated_large_filter(structbloom_filter*filter,
11:2437e62813! 13:09d8669c3acommit-graph.c: unconditionally load Bloom filters
@@ Commit message
either "1" or "2".Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
commit-graph.c##
@@commit-graph.c: static int graph_read_bloom_data(const unsigned char*chunk_start,
12:fe8fb2f5fe! 14:0d4f9dc4eecommit-graph: drop unnecessarygraph_read_bloom_data_context
@@ Commit messageNoticed-by Jonathan Tan <[email protected]>Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
commit-graph.c##
@@commit-graph.c: static int graph_read_oid_lookup(const unsigned char*chunk_start,
13:825af91e11! 15:1f7f27bc47object.h: fix mis-aligned flag bits table
@@ Commit message
Bit position 23 is one column too far to the left.Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
object.h##
@@object.h: voidobject_array_init(struct object_array *array);
14:593b317192! 16:abbef95ae8commit-graph: reuse existing Bloom filters where possible
@@ Commit message
commits by their generation number.Signed-off-by Taylor Blau <[email protected]>- Signed-off-by: Junio C Hamano <[email protected]>
- Signed-off-by: Taylor Blau <[email protected]>##
bloom.c##
@@
15:8bf2c9cf98= 17:ca362408d5bloom: introducedeinit_bloom_filters()Signature · 1 line
2.42.0.342.g8bb3a896ee
Re: [PATCH v3 00/17] bloom: changed-path Bloom filters v2 (& sundries)
Quoted 9 lines · Patrick Steinhardt
Quoted 4 lines
Thanks to Jonathan, Peff, and SZEDER who have helped a great deal in assembling these patches. As usual, a range-diff is included below. Thanks in advance for your
review!As this patch series has been sitting around without reviews for a week I've tried my best to give it a go. Note though that this area is mostly outside of my own comfort zone, so some of the questions and suggestions might ultimately not apply.
Thanks for giving it a look! I generated a few small tweaks on top of what I already had here based on your review, so I'll send a reroll shortly.
Thanks,
Taylor